Forwarded from IT лекции
📌 Асинхронность в Python
#1: Введение - Смотреть
#2: Асинхронность с простыми функциями. Событийный цикл. - Смотреть
#3: Асинхронность на колбэках - Смотреть
#4: Генераторы и событийный цикл Round Robin - Смотреть
#5: Асинхронность на генераторах - Смотреть
#6: Корутины и yield from - Смотреть
#7: Asyncio, async/await - Смотреть
#8: Опять про генераторы - Смотреть
@itlecture
#1: Введение - Смотреть
#2: Асинхронность с простыми функциями. Событийный цикл. - Смотреть
#3: Асинхронность на колбэках - Смотреть
#4: Генераторы и событийный цикл Round Robin - Смотреть
#5: Асинхронность на генераторах - Смотреть
#6: Корутины и yield from - Смотреть
#7: Asyncio, async/await - Смотреть
#8: Опять про генераторы - Смотреть
@itlecture
YouTube
Основы асинхронности в Python #1: Введение
Мои курсы:
Boosty:
https://boosty.to/omolchanov/posts/995a18dd-487b-4000-9b3f-0aafa5e060cd
Patreon:
https://www.patreon.com/posts/karty-vsekh-41011404
Основы асинхронности в Python для начинающих, она же "кооперативная многозадачность".
Речь в этой серии…
Boosty:
https://boosty.to/omolchanov/posts/995a18dd-487b-4000-9b3f-0aafa5e060cd
Patreon:
https://www.patreon.com/posts/karty-vsekh-41011404
Основы асинхронности в Python для начинающих, она же "кооперативная многозадачность".
Речь в этой серии…
Forwarded from ИЦ "ГЕВИССТА"
Общие советы по прогнозированию рядов для учеников и не только
Активно общался на этой неделе с учениками, занимающимися прогнозированием временных рядов. Не спорьте, что лучше работает в вашей задаче, не зацикливайтесь на чем-то одном – бустинге, ARIMA, TFT или DeepAR. Пробуйте разные модели, идя от простых к сложным. Лучше написать метакласс, подать ему на вход разные модели от наивного прогноза до TFT и построить бейслайны. Ну а если хотите бустинг, то и про лес не забудьте, потом часто ребята забывают про метакласс BaggingRegressor, который дает вам сразу несколько методов ансаблирования: бэггинг, пэстинг, случайные подпространства и случайные патчи. Удобнее всего сделать такой метакласс в ETNA, у меня выше в выложенном материале по ETNA примеры есть (можно такой класс сделать на базе функции train_and_evaluate_model()). Иначе кучу времени убьете на тот же бустинг, а потом окажется, что его легко бьет модель сезонного скользящего среднего. Если рядов много, кластеризуйте (евклидово, DTW, по домену – по скорости оборота, по ценовому диапазону, сроку хранения и прочему) и стройте по кластерам.
В прогнозировании большого количества рядов обычно две универсальные ошибки – агрегация там, где нужна сегментация, и сегментация там, где нужна агрегация. Я постоянно привожу пример со скорингом кредитных карт – определяем размер лимита. У вас есть активные пользователи и пассивные пользователи. Для одних утилизация – сильная переменная, для других – слабая, в итоге модель выводит среднее по больнице. Так и в рядах. Взяли – собрали вместе редко покупаемые товары вместе с часто покупаемыми товарами и прогнозируем одной моделью, а лучше выделить в разные сегменты и разными моделями, не только разные наборы гиперпараметров, но надо и разные лоссы пробовать. С бустингом вообще частая ошибка – зацикленность на одном лоссе. Другой пример – пытаемся прогнозировать отдельными моделями втулки и скрепки. Возможно, лучше агрегировать в более общую категорию. Для товаров с редкими продажами обязательно попробовать zero-inflated models.
Джентльменский набор по FE для бейслайна – лаг по горизонту, скользящая статистика – по горизонту, если есть сезонность – компоненты ряда Фурье (период задаете по периодограмме) и календарные признаки для этой сезонности (а не скопом, сделаю-ка я все разом, бустинг сам разберется, так вот не разберется), если есть тренд и применяете бустинг – детрендинг. Но если тренд слабо выражен или затухающий, детрендинг может и навредить. Сразу кучу лагов и скользящих статистик не делаете, у вас на начальном этапе слишком мало информации. Делайте отладку – добавляйте признаки поэтапно и смотрите, как меняются прогнозы. На следующей неделе скину примеры такой отладки.
Если слышите «сейчас встречаю столько статей, посвященных применению (название метода/библиотеки), надо применять именно (название метода/библиотеки)», вспомните про «ошибку выжившего». Да, возможно много статей опубликовано по успешному применению какого-то конкретного метода/библиотеки для рядов. Однако задайте себе вопрос «а сколько статей не было напечатано/свернуто/выкинуто в корзину из-за того, что данный метод не оправдал себя, потерпел неудачу?». Полезно было бы именно эти статьи прочитать, но вряд ли мы их увидим, люди редко делятся проколами, для этого нужна определенная зрелость. Внимательно изучайте свои собственные неудачные проекты по рядам, неудачные соревнования по рядам, ведите failure history и интересуйтесь неудачными проектами по рядам, которые были у коллег, если те готовы делиться.
Активно общался на этой неделе с учениками, занимающимися прогнозированием временных рядов. Не спорьте, что лучше работает в вашей задаче, не зацикливайтесь на чем-то одном – бустинге, ARIMA, TFT или DeepAR. Пробуйте разные модели, идя от простых к сложным. Лучше написать метакласс, подать ему на вход разные модели от наивного прогноза до TFT и построить бейслайны. Ну а если хотите бустинг, то и про лес не забудьте, потом часто ребята забывают про метакласс BaggingRegressor, который дает вам сразу несколько методов ансаблирования: бэггинг, пэстинг, случайные подпространства и случайные патчи. Удобнее всего сделать такой метакласс в ETNA, у меня выше в выложенном материале по ETNA примеры есть (можно такой класс сделать на базе функции train_and_evaluate_model()). Иначе кучу времени убьете на тот же бустинг, а потом окажется, что его легко бьет модель сезонного скользящего среднего. Если рядов много, кластеризуйте (евклидово, DTW, по домену – по скорости оборота, по ценовому диапазону, сроку хранения и прочему) и стройте по кластерам.
В прогнозировании большого количества рядов обычно две универсальные ошибки – агрегация там, где нужна сегментация, и сегментация там, где нужна агрегация. Я постоянно привожу пример со скорингом кредитных карт – определяем размер лимита. У вас есть активные пользователи и пассивные пользователи. Для одних утилизация – сильная переменная, для других – слабая, в итоге модель выводит среднее по больнице. Так и в рядах. Взяли – собрали вместе редко покупаемые товары вместе с часто покупаемыми товарами и прогнозируем одной моделью, а лучше выделить в разные сегменты и разными моделями, не только разные наборы гиперпараметров, но надо и разные лоссы пробовать. С бустингом вообще частая ошибка – зацикленность на одном лоссе. Другой пример – пытаемся прогнозировать отдельными моделями втулки и скрепки. Возможно, лучше агрегировать в более общую категорию. Для товаров с редкими продажами обязательно попробовать zero-inflated models.
Джентльменский набор по FE для бейслайна – лаг по горизонту, скользящая статистика – по горизонту, если есть сезонность – компоненты ряда Фурье (период задаете по периодограмме) и календарные признаки для этой сезонности (а не скопом, сделаю-ка я все разом, бустинг сам разберется, так вот не разберется), если есть тренд и применяете бустинг – детрендинг. Но если тренд слабо выражен или затухающий, детрендинг может и навредить. Сразу кучу лагов и скользящих статистик не делаете, у вас на начальном этапе слишком мало информации. Делайте отладку – добавляйте признаки поэтапно и смотрите, как меняются прогнозы. На следующей неделе скину примеры такой отладки.
Если слышите «сейчас встречаю столько статей, посвященных применению (название метода/библиотеки), надо применять именно (название метода/библиотеки)», вспомните про «ошибку выжившего». Да, возможно много статей опубликовано по успешному применению какого-то конкретного метода/библиотеки для рядов. Однако задайте себе вопрос «а сколько статей не было напечатано/свернуто/выкинуто в корзину из-за того, что данный метод не оправдал себя, потерпел неудачу?». Полезно было бы именно эти статьи прочитать, но вряд ли мы их увидим, люди редко делятся проколами, для этого нужна определенная зрелость. Внимательно изучайте свои собственные неудачные проекты по рядам, неудачные соревнования по рядам, ведите failure history и интересуйтесь неудачными проектами по рядам, которые были у коллег, если те готовы делиться.
Forwarded from DevFM
Подборка базовых материалов для python-разработчиков на 2022 год
Современная разработка требует не только знания языка программирования. Нужно знать язык, типы данных, фреймворки, базы данных, подходы к тестированию, linux и вообще кучу инструментов вроде docker. Совершенно не лишним будут книги, собирающие большую разнородную информацию в понятном и последовательном виде.
Мы сформировали подборку бесплатных материалов из разных областей, которые гарантированно нужны разработчику. Опубликовали на pikabu и VC, кому как удобнее. Поддержите лайком, если годно.
Не нашли крутых материалов для начинающих по Linux. Если знаете такие, поделитесь в комментариях. Планируем ещё несколько подборок по специализациям.
#python #devfm
Современная разработка требует не только знания языка программирования. Нужно знать язык, типы данных, фреймворки, базы данных, подходы к тестированию, linux и вообще кучу инструментов вроде docker. Совершенно не лишним будут книги, собирающие большую разнородную информацию в понятном и последовательном виде.
Мы сформировали подборку бесплатных материалов из разных областей, которые гарантированно нужны разработчику. Опубликовали на pikabu и VC, кому как удобнее. Поддержите лайком, если годно.
Не нашли крутых материалов для начинающих по Linux. Если знаете такие, поделитесь в комментариях. Планируем ещё несколько подборок по специализациям.
#python #devfm
Пикабу
Ответ trdm в «Яндекс и "Цифровые профессии"»
Автор: anetto1502
Forwarded from Записки MLEшника (Egor)
Хочу рассказать о горячо любомом мной инструменте - pre-commit хуках. Начну немного издалека.
Если меня спросят: "В чем сила, брат?", я отвечу: "В унификации и стандартизации". Врядли бы мы достигли текущего уровня развития техники, не стандартизируй предки болты, транзисторы и еще куча всего. Программное обеспечение - не исключение.
Рекомендации по оформлению кода содержатся в PEP8 (стайлгайд к языку). Но лень же каждый раз дотошно редактировать все строки кода. Для автоматизации форматирования кода есть соответствующие инструменты - форматеры (formatters). Например, black (форматирует классы, функции, литералы и др.), isort (сортирует импорты по алфавиту) или pycln (удаляет неиспользуемые импорты). Запустив их по очереди, вы получите отформатированный по код. Чтобы дополнительно проверить на соответствие PEP8, можем запустить flake8 (проверяет, что код соответствует PEP8). Также flake находит неиспользуемые переменные, повтореную инициализацию или просто слишком сложные функции. Красота 🌝
Возник резонный вопрос: "А не многовато всего руками на каждый коммит запускать?!". Ответ убил - pre-commit хуки.
Эти ребята будут за вас запускать весь этот паровоз кода при каждом коммите (пуше, пуле - как настроите). Я влюбился в это с первого взгляда.
Очень советую данный инструмент командам. С момента добавления хуков в репозиторий у нас не было ни одного спора в пиарах по поводу кавычек, переноса строк и тому подобного 💁♀️. Также мы добавили эту проверку в CI, чтобы наверняка.
Также в хуки можно добавить mypy или даже dvc. Например, если хотите делать dvc push на каждый git push.
Тут пример моего конфига, который без особых изменений кочует из проекта в проект. На каждый коммит:
1. Запускает набор хуков из репы pre-commit (форматирует жейсоны, ямли, иксемели, рекваерменты)
2. Запускает pycln (удаляются все неиспользуемые импорты)
3. Запускает black (форматируется код по PEP8. Тут же, например, кавычки заменяются на двойные)
4. Запускает isort (переупорядочивает и сортирует импорты)
5. Запускает flake8 (проверяет, все ли у нас по PEP8. Также покажет, если остались неиспользуемые переменные)
Пайплайн использования такой:
1. Делаете комит
2. Хуки запускаются. Видят, что есть косяки. Редактируют код. Отменяют ваш комит.
3. Добавляете изменения, которые внесли хуки в комит.
4. Делаете комит
Вся эта красота устанавливается в две команды:
Если меня спросят: "В чем сила, брат?", я отвечу: "В унификации и стандартизации". Врядли бы мы достигли текущего уровня развития техники, не стандартизируй предки болты, транзисторы и еще куча всего. Программное обеспечение - не исключение.
Рекомендации по оформлению кода содержатся в PEP8 (стайлгайд к языку). Но лень же каждый раз дотошно редактировать все строки кода. Для автоматизации форматирования кода есть соответствующие инструменты - форматеры (formatters). Например, black (форматирует классы, функции, литералы и др.), isort (сортирует импорты по алфавиту) или pycln (удаляет неиспользуемые импорты). Запустив их по очереди, вы получите отформатированный по код. Чтобы дополнительно проверить на соответствие PEP8, можем запустить flake8 (проверяет, что код соответствует PEP8). Также flake находит неиспользуемые переменные, повтореную инициализацию или просто слишком сложные функции. Красота 🌝
Возник резонный вопрос: "А не многовато всего руками на каждый коммит запускать?!". Ответ убил - pre-commit хуки.
Эти ребята будут за вас запускать весь этот паровоз кода при каждом коммите (пуше, пуле - как настроите). Я влюбился в это с первого взгляда.
Очень советую данный инструмент командам. С момента добавления хуков в репозиторий у нас не было ни одного спора в пиарах по поводу кавычек, переноса строк и тому подобного 💁♀️. Также мы добавили эту проверку в CI, чтобы наверняка.
Также в хуки можно добавить mypy или даже dvc. Например, если хотите делать dvc push на каждый git push.
Тут пример моего конфига, который без особых изменений кочует из проекта в проект. На каждый коммит:
1. Запускает набор хуков из репы pre-commit (форматирует жейсоны, ямли, иксемели, рекваерменты)
2. Запускает pycln (удаляются все неиспользуемые импорты)
3. Запускает black (форматируется код по PEP8. Тут же, например, кавычки заменяются на двойные)
4. Запускает isort (переупорядочивает и сортирует импорты)
5. Запускает flake8 (проверяет, все ли у нас по PEP8. Также покажет, если остались неиспользуемые переменные)
Пайплайн использования такой:
1. Делаете комит
2. Хуки запускаются. Видят, что есть косяки. Редактируют код. Отменяют ваш комит.
3. Добавляете изменения, которые внесли хуки в комит.
4. Делаете комит
Вся эта красота устанавливается в две команды:
pip install pre-commit
pre-commit install
Forwarded from DevFM
Kubernetes в небольших проектах
Для запуска приложения из нескольких компонент применяется docker compose. А с Kubernetes обычно сталкиваешься уже на достаточно больших проектах со сложной сервисной архитектурой.
Современному инженеру следует знать о Kubernetes. Он сейчас применяется повсеместно и позволяет сделать проект надёжным, отказоустойчивым и горизонтально масштабируемым.
Начинается статья с основ: что такое кубер, из каких компонентов состоит и как функционирует. Рассказав базу, автор переходит к инфраструктурным задачам, которые перед ними стоят и то, как кубер помогает их решать.
Среди задач выделяют:
— сбор логов
— сбор метрик
— проверка работоспособности сервисов
— автоматическое обнаружение сервисов
— масштабирование
Для любителей видео формата: доклад.
Для особо любопытных в конце приводятся ссылки на статьи по смежным областям. Нам нравится статья: Лучшие практики CI/CD с Kubernetes и GitLab.
#skills
Для запуска приложения из нескольких компонент применяется docker compose. А с Kubernetes обычно сталкиваешься уже на достаточно больших проектах со сложной сервисной архитектурой.
Современному инженеру следует знать о Kubernetes. Он сейчас применяется повсеместно и позволяет сделать проект надёжным, отказоустойчивым и горизонтально масштабируемым.
Начинается статья с основ: что такое кубер, из каких компонентов состоит и как функционирует. Рассказав базу, автор переходит к инфраструктурным задачам, которые перед ними стоят и то, как кубер помогает их решать.
Среди задач выделяют:
— сбор логов
— сбор метрик
— проверка работоспособности сервисов
— автоматическое обнаружение сервисов
— масштабирование
Для любителей видео формата: доклад.
Для особо любопытных в конце приводятся ссылки на статьи по смежным областям. Нам нравится статья: Лучшие практики CI/CD с Kubernetes и GitLab.
#skills
Хабр
Наш опыт с Kubernetes в небольших проектах (обзор и видео доклада)
6 июня на конференции RootConf 2017, проходившей в рамках фестиваля «Российские интернет-технологии» (РИТ++ 2017), в секции «Непрерывное развертывание и деплой» прозвучал доклад «Наш опыт с...
Forwarded from тревожный эйчар
Вы знаете кого-то, кто мог бы самостоятельно решить все важные вопросы в современной компании? Мы тоже нет. Время единоличных лидеров, которые стремятся к абсолютной власти, уходит. Сегодня эффективнее командные лидеры, способные сознательно ограничить свою власть.
Разницу между двумя этими подходами описал автор модели командных ролей Рэймонд Мередит Белбин. Смотрите главное в карточках ☝️
Разницу между двумя этими подходами описал автор модели командных ролей Рэймонд Мередит Белбин. Смотрите главное в карточках ☝️
Forwarded from Кодим на Коленке | Уроки по программированию
Forwarded from commit history
Как и где искать новую работу.
Собрал небольшую заметку про поиск работы (больше про зарубежные компании). Тут оговорка, что в большинстве позиций нужно, чтобы у вас было право работы на территории страны. Но есть и вакансии full-remote и с релокацией.
Во-первых, нужно актуализировать или составить CV (резюме), профиль на Linkedin и других платформах.
1. Посмотреть материалы гарварда.
2. Полистать закрепы и примеры в чате @resume_review
3. Составить резюме. Я использовал вот такой шаблон в overleaf
4. Можно закинуть CV в чат из 2 пункта для обратной связи.
Во-вторых, искать вакансии и подаваться.
1. Найти рефералов (человек из компании, который вас рекомендует) в компании через знакомых, чаты и сеть Linkedin. Самый рабочий способ, если хотите в конкретные компании.
2. Заполнить свой профиль на Linkedin. Будут прилетать вакансии от рекрутеров в личку/на почту. Откликался на вакансии через сам Linkedin, отдача маленькая, но собесы оттуда были.
3. Откликаться на вакансии в чатах. На первый этап точно попадете. Круто, что в #_jobs в слаке ods, описание вакансий сразу с вилкой зп.
4. Бот @g_jobbot. Были и предложения, и офферы, и сам откликался.
5. На hackernews каждый месяц появляется тред, где публикуют вакансии. Вот их агрегатор.
6. Есть разные ресурсы, которые матчат вас с работодателем. Вроде honeypot. Я пробовал, даже был собес. Но там были низкие вилки и мало предложений.
7. Indeed.com или angel.co Регался, даже была пара собесов оттуда.
8. Находить на crunchbase свежие стартапы, которые подняли раунд и теперь нанимают. Не пробовал такой способ, но хотел.
Еще можно подаваться напрямую в компании через сайт, но я такой способ не пробовал без рефералов.
Напишите в комменты, какие еще ресурсы вам помогали в поиске работы. Или что из этого списка cработало.
Собрал небольшую заметку про поиск работы (больше про зарубежные компании). Тут оговорка, что в большинстве позиций нужно, чтобы у вас было право работы на территории страны. Но есть и вакансии full-remote и с релокацией.
Во-первых, нужно актуализировать или составить CV (резюме), профиль на Linkedin и других платформах.
1. Посмотреть материалы гарварда.
2. Полистать закрепы и примеры в чате @resume_review
3. Составить резюме. Я использовал вот такой шаблон в overleaf
4. Можно закинуть CV в чат из 2 пункта для обратной связи.
Во-вторых, искать вакансии и подаваться.
1. Найти рефералов (человек из компании, который вас рекомендует) в компании через знакомых, чаты и сеть Linkedin. Самый рабочий способ, если хотите в конкретные компании.
2. Заполнить свой профиль на Linkedin. Будут прилетать вакансии от рекрутеров в личку/на почту. Откликался на вакансии через сам Linkedin, отдача маленькая, но собесы оттуда были.
3. Откликаться на вакансии в чатах. На первый этап точно попадете. Круто, что в #_jobs в слаке ods, описание вакансий сразу с вилкой зп.
4. Бот @g_jobbot. Были и предложения, и офферы, и сам откликался.
5. На hackernews каждый месяц появляется тред, где публикуют вакансии. Вот их агрегатор.
6. Есть разные ресурсы, которые матчат вас с работодателем. Вроде honeypot. Я пробовал, даже был собес. Но там были низкие вилки и мало предложений.
7. Indeed.com или angel.co Регался, даже была пара собесов оттуда.
8. Находить на crunchbase свежие стартапы, которые подняли раунд и теперь нанимают. Не пробовал такой способ, но хотел.
Еще можно подаваться напрямую в компании через сайт, но я такой способ не пробовал без рефералов.
Напишите в комменты, какие еще ресурсы вам помогали в поиске работы. Или что из этого списка cработало.
Forwarded from Борис опять
Разбирался в апскейле изображений, bilinear и bicubic интерполяции, aliasing.
Процесс апскейла изображений на пальцах такой:
1. Создаем новое изображение большего размера, добавляя "пустые” пиксели между пикселями изначальной сетки.
2. Применяем интерполяцию, чтобы определить значения “пустых" пикселей на основе значений соседних пикселей изначального изображения.
* Простейший вариант: nearest neighbor. Берем значение ближайшего пикселя.
* Поумнее: bilinear. Значение "пустого" пикселя расчитывается как взвешенная сумма ближайших четырех исходных пикселей. Веса зависят от расстояния: чем ближе пиксель, тем больше он влияет.
* Еще умнее: bicubic. Принцип как у billinear, но используюстя пиксели в окне 16х16 и уравнение посложнее. Соответственно результат более гладкий.
3. Применям алгоритм anti-aliasing, чтобы убрать артефакты апскейла. Например, "лесенки" на диагональных линиях, которые можно наблюдать, если повращать какой-нибудь контур в пейнте.
Unfun fact: по умолчанию torch.nn.functional.interpolate использует nearest-neighbor интерполяцию. Самую быструю и самую плохую по качеству.
Unfun fact 2: из Python библиотек для обработки изображений только Pillow делает интерполяцию нормально. По умолчанию использует bicubic.
Вывод: при ресайзе изображений для CV стоит обращать внимание на то, каким образом происходит интерполяция. Если вы по-разному ресайзите трейн и тест, это может внести в данные сдвиг распределения. Особенно опасно, если при обучении вы делаете ресайз одной библиотекой, а в продакшне другой.
Как правило можно использовать bilinear/bicubic из Pillow и не волноваться.
Хорошие статьи:
* https://zuru.tech/blog/the-dangers-behind-image-resizing
* https://www.cambridgeincolour.com/tutorials/image-interpolation.htm
Процесс апскейла изображений на пальцах такой:
1. Создаем новое изображение большего размера, добавляя "пустые” пиксели между пикселями изначальной сетки.
2. Применяем интерполяцию, чтобы определить значения “пустых" пикселей на основе значений соседних пикселей изначального изображения.
* Простейший вариант: nearest neighbor. Берем значение ближайшего пикселя.
* Поумнее: bilinear. Значение "пустого" пикселя расчитывается как взвешенная сумма ближайших четырех исходных пикселей. Веса зависят от расстояния: чем ближе пиксель, тем больше он влияет.
* Еще умнее: bicubic. Принцип как у billinear, но используюстя пиксели в окне 16х16 и уравнение посложнее. Соответственно результат более гладкий.
3. Применям алгоритм anti-aliasing, чтобы убрать артефакты апскейла. Например, "лесенки" на диагональных линиях, которые можно наблюдать, если повращать какой-нибудь контур в пейнте.
Unfun fact: по умолчанию torch.nn.functional.interpolate использует nearest-neighbor интерполяцию. Самую быструю и самую плохую по качеству.
Unfun fact 2: из Python библиотек для обработки изображений только Pillow делает интерполяцию нормально. По умолчанию использует bicubic.
Вывод: при ресайзе изображений для CV стоит обращать внимание на то, каким образом происходит интерполяция. Если вы по-разному ресайзите трейн и тест, это может внести в данные сдвиг распределения. Особенно опасно, если при обучении вы делаете ресайз одной библиотекой, а в продакшне другой.
Как правило можно использовать bilinear/bicubic из Pillow и не волноваться.
Хорошие статьи:
* https://zuru.tech/blog/the-dangers-behind-image-resizing
* https://www.cambridgeincolour.com/tutorials/image-interpolation.htm