make some code
84 subscribers
13 photos
33 links
Репосты с блога makesomecode.me.
Небольшие интересности из C#/Python/Go. Пишу редко — надоесть не успею.
Download Telegram
Визуальный прогресс бар в Python. Продолжение

🤨 Проблема: Часто в своих проектах для работы с данными я использую библиотеку pandas. Класс pandas.DataFrame содержит полезный метод apply, который позволяет применить какую-то функцию к выбранной колонке набора данных или ко всему DataFrame. Бывает, что этот процесс занимает длительное время. Отследить прогресс при этом сложно.

🤓 Решение: В прошлый раз я писал про библиотеку tqdm. Её можно применять для задачи визуализации прогресса выполнения метода apply: после инициалзации библиотеки вызовом tqdm.pandas() pandas.DataFrame получает метод progress_apply, который добавляет прогресс бар при применении функции к данным:

import pandas as pd
import numpy as np
from tqdm import tqdm

df = pd.DataFrame({
'a': np.random.randint(0, 1000, 1_000_000)
})

tqdm.pandas()

df['b'] = df['a'].progress_apply(lambda x: x**0.5)

Пример можно найти на Google Colab

#python #lib
Визуальный progress bar для .NET

🤨 Проблема:
Когда программа обрабатывает большое количество объектов в цикле, то как и в Python хотелось бы иметь простой и красивый progress bar в консольных приложениях. Аналог tqdm для Python.

🤓 Решение: Такой проект существует. Не так популярен как tqdm, но вполне поддерживаемое решение. Называется yaap: Yet Another ANSI Progressbar.

Библиотека содержит метод расширения .Yaap() для коллекций, который можно вызвать в цикле:

using Yaap;

foreach (var i in Enumerable.Range(0, 1000).Yaap()) {
Thread.Sleep(10);
}


#Csharp #lib
`icecream` на замену `print`

🤨 Проблема:
У меня есть не самая хорошая привычка отлаживать Python-код print-ами: в VS Code это значительно быстрее. Но каждый раз приходится писать лишний код для более понятного вывода:
def bar(x):
return x ** 2

foo = bar(2)
print(f`foo={foo}`)

>>> foo=4

Не сложно, но раздражает.

🤓Решение: Под руку попалась библиотека icecream. Она решает эту проблему великолепно. Вместо вывода print-ом форматированных строк — передаём переменную и icecream сам выводит всё в правильном виде:
from icecream import ic

def foo(i):
return i + 333

ic(foo(123))

>>> ic| foo(123): 456


Если функции ic не передать аргумент, то она выведен имя файла и номер строки. Что очень удобно при отладке.

Пример на google colab

#python #lib
reload библиотек в Python

Продолжаю тему отладки Python скриптов.

🤨 Проблема: Рассмотрим следующий сценарий. Питонист разрабатывает функцию sqr, которая возводит число в квадрат. Функция помещена в модуль mysqr. Программист запускает REPL, импортирует модуль, вызывает функцию sqr и понимает, что допустил в коде ошибку: вместо возведения в квадрат возвращает квадратный корень от переданного параметра. Автор функции возвращается в текстовый редактор, исправляет функцию, снова запускает REPL, импортирует модуль, проверяет функцию.

Если функция сложная, то таких итераций:

1. исправил код в текстовом редакторе
2. запустил REPL
3. импортировал модуль
4. вызывал функцию
5. проверил

может быть значительно больше двух. Это занимает много времени.

🤓 Решение: Решений два:
1. Не закрывать REPL и после каждого изменения вызывать import mysqr
2. Использовать функцию reload из модуля importlib

Мне второй вариант нравится больше:
1. Он явно говорит перезагрузить модуль
2. Функцию reload может быть использована внутри вашего кода (например, при разработке плагинной системы в приложении)

Функция максимально простая: требуется только передать модуль, который необходимо перезагрузить:
from importlib import reload
import mysqr

a = mysqr.sqr(5)

# изменяем в текстовом редакторе модуль `mysqr`
# перезагрузим модуль
reload(mysqr)

# вызывается новая реализация функции `sqr`
a = mysqr.sqr(5)


#python #stdlib
Периодическое выполнение задач

Внезапно возникла задача периодического выполнения функции в Python приложении (очередной бот для telegram).
Писать с нуля, конечно, можно, но довольно быстро нашлёлся проект schedule на github.

Библиотека замечательная своим простым интерфейсом, но подходит не для всех задач (со слов самого автора):
1. Работает только in-memory. Для хранения информации между перезапусками потребуется городить свой слой хранения
2. Точность не самая высокая (для high frequency trading и подобных задач не подойдёт)
3. Выполнение в многопоточном окружении надо делать самостоятельно (но это не сложно)
4. Нет локализации и часовых поясов: не учитывает временные зоны, календари рабочего времени и пр.

Пример использования (из документации):
import schedule
import time

def job():
print("I'm working...")

schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)
schedule.every().monday.do(job)
schedule.every().wednesday.at("13:15").do(job)
schedule.every().minute.at(":17").do(job)

while True:
schedule.run_pending()
time.sleep(1)

Пример на colab

#python #lib
pydantic — очередная Python-библиотека, которой мне не хватало.
Если вкратце, то это библиотека для валидации при сериализации/десериализации JSON в/из Python-объекта реализованная на стандартных type hint-ах.

В библиотеке есть класс BaseModel. Класс используется для базовой функциональности валидации и содержит дополнительные полезные методы. Например, dict(), json():

from typing import List
from pydantic import BaseModel

class Foo(BaseModel):
count: int
size: float = None

class Bar(BaseModel):
apple = 'x'
banana = 'y'

class Spam(BaseModel):
foo: Foo
bars: List[Bar]

m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(m)
#> foo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'),
#> Bar(apple='x2', banana='y')]

print(m.dict())
"""
{
'foo': {'count': 4, 'size': None},
'bars': [
{'apple': 'x1', 'banana': 'y'},
{'apple': 'x2', 'banana': 'y'},
],
}
"""

Библиотека может использоваться совместно с ORM. Например, sqlalchemy.

pydantic содержит класс ValidationError, который позволяет получить ошибки валидации в структурированном виде. Например, в JSON:

from typing import List
from pydantic import BaseModel, ValidationError, conint


class Location(BaseModel):
lat = 0.1
lng = 10.1


class Model(BaseModel):
is_required: float
gt_int: conint(gt=42)
list_of_ints: List[int] = None
a_float: float = None
recursive_model: Location = None


data = dict(
list_of_ints=['1', 2, 'bad'],
a_float='not a float',
recursive_model={'lat': 4.2, 'lng': 'New York'},
gt_int=21,
)

try:
Model(**data)
except ValidationError as e:
print(e.json())
"""
[
{
"loc": [
"is_required"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"gt_int"
],
"msg": "ensure this value is greater than 42",
"type": "value_error.number.not_gt",
"ctx": {
"limit_value": 42
}
},
{
"loc": [
"list_of_ints",
2
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
},
{
"loc": [
"a_float"
],
"msg": "value is not a valid float",
"type": "type_error.float"
},
{
"loc": [
"recursive_model",
"lng"
],
"msg": "value is not a valid float",
"type": "type_error.float"
}
]
"""


К сожалению, информации о возможности локализации не нашёл.

Примеры можно попробовать в google colab

#python #lib
В очередной раз после переустановки Windows осознал, что надо накатить драйвера, CUDA, cuDNN, Tensorflow/Keras для обучения нейронных сетей.

Каждый раз для меня это оказывается несложной, но времязатратной операцией: найти подходящую комбинацию Tensorflow/Keras, CUDA, cuDNN и Python несложно, но вспоминаю про эти зависимости только в тот момент, когда при импорте Tensorflow вижу, что видеокарта не обнаружена и начинаю поиск нужной страницы в документации Tensorflow.

В этот раз ситуация немного усложнилась. Помимо установки Tensorflow мне потребовалось установить PyTorch.

https://makesomecode.me/2021/07/cuda-cudnn-tf-pytorch-windows-installation/
Форматированный вывод в Jupyter Notebook

Запишу, чтобы не забыть и, вдруг, вы тоже про это не помните.

Jupyter Notebook отличная штука для прототипирования ML моделей и визуализации данных. Однако, для того, чтобы вспомнить как вывести в output форматированный текст мне приходится каждый раз искать в интернете правильный модуль.

Правильный ответ здесь — IPython.display. Модуль содержит методы для вывода разного контента: текста, аудио, изображений и пр. Например, можно вывести в output форматированный Markdown текст:

from IPython.display import display, Markdown

display(Markdown('[**_Hello_ world**](https://google.com)'))

В output получим форматированную строку "Hello world", которая будет является ссылкой на https://google.com.

Пример на google colab

#python #jupyter
Возникла идея простого бота, который будет отправлять переданные ему файлы в облако. Однако для аутентификации сейчас практически везде используется OAuth2. Если в web-приложениях пользоваться OAuth2 все уже научились, то с telegram у меня возникли вопросы. В этой заметке будет минимальный пример telegram-бота, который работает с box.com через API с аутентификацией через OAuth2. disclaimer #1: В статье не будет подробного описания работы OAuth2 — только необходимый минимум информации для понимания процесса.

Продолжение

#python
Справка и исходники в IPython

Думаю, все здесь знают про функции help и dir, которые позволяют в REPL посмотреть справку по функции или классу и получить список методов и атрибутов объекта соответственно:

>>> help(help)
Help on _Helper in module _sitebuiltins object:
...
>>> dir(list)
['__add__',
'__class__',
'__class_getitem__',
...
'remove',
'reverse',
'sort']


Но только сегодня я узнал, что в интерактивном шелле IPython можно посмотреть исходный код функции или класса при помощи команды (операции, функции?) ??:

>>> help??
Signature: help(*args, **kwds)
Type: _Helper
String form: Type help() for interactive help, or help(object) for help about object.
Namespace: Python builtin
File: c:\program files\python39\lib\_sitebuiltins.py
Source:
class _Helper(object):
"""Define the builtin 'help'.

This is a wrapper around pydoc.help that provides a helpful message
when 'help' is typed at the Python interactive prompt.

Calling help() at the Python prompt starts an interactive help session.
Calling help(thing) prints help for the python object 'thing'.
"""

def __repr__(self):
return "Type help() for interactive help, " \
"or help(object) for help about object."
def __call__(self, *args, **kwds):
import pydoc
return pydoc.help(*args, **kwds)


Так же это работает и в Jupyter Notebook/Lab или Google Colab. Пример, можно посмотреть в блокноте

#TIL #python
Всё ещё пользуетесь GridSearchCV? Тогда мы идём к вам!

В последнее время замечаю, что народ соскакивает с проверенного временем метода подбора параметров моделей при помощи GridSearchCV из модуля model_selection библиотеки scikit-learn на библиотеку optuna.

Судя по Google Trends эта волна началась около трёх лет назад, но я узнал про библиотеку лишь несколько месяцев назад и успел применить только в паре соревнований.

Написал небольшую заметку почему тоже перешёл на эту библиотеку в блоге.

#python #lib
Preview для любых текстовых файлов

По умолчанию в Windows в панели предпросмотра отображается содержимое для ограниченного числа текстовых форматов.

Но помимо .txt есть большое количество других, не менее полезных, текстовых форматов. Например, .xml, .py, .md или .log.

Исправить это недоразумение можно просто: необходимо добавить нужный ключ в реестр. Для этого есть два способа. Покажу на примере .md.

Первый способ:
1. Открыть редактор реестра: Win+R -> regedit -> Enter
2. В редакторе реестра найти ветку HKEY_LOCAL_MACHINE\SOFTWARE\Classes\.md. Если такой нет, то создать
3. Добавить строковый атрибут с именем PerceivedType и значением text

Второй способ:
1. Открыть терминал cmd с правами администратора
2. Выполнить команду reg add HKLM\SOFTWARE\Classes\.md /v PerceivedType /t REG_SZ /d text
Управление ML проектами

Каждый раз, когда залетаю в ML-соревнование сталкиваюсь с одной и той же проблемой: через два-три дня активной работы над задачей генерируется с десяток новых файлов. Например, промежуточные CSV файлы, тестовые submission-ы и модели, jupyter блокноты и python-скрипты. Разбираться с этим через неделю становится крайне сложно: проблемы jupyter-ноутбуков известны и пока, кажется, ультимативного решения для них нет.

Возникла логичная мысль, что я не единственный, кто столкнулся с подобной проблемой. Покопавшись некоторое время в интернетах нашёл несколько интересных обсуждений и статей на Kaggle и Toward Data Science, которые навели меня на любопытную штуку: cookiecutter.

По сути это генератор шаблонного ML проекта с предопределённой файловой структурой: директория созданного проекта содержит поддиректории для моделей, данных (исходных и промежуточных), python-сценариев (для предобработки данных, генерации признаков и пр.), jupyter-ноутбуков, моделей и прочего.

Устанавливается cookiecutter стандартно — через pip:
pip install cookiecutter

Создать новый проект:
python -m cookiecutter https://github.com/drivendata/cookiecutter-data-science

GitHub репозиторий, передаваемый первым аргументом, — шаблон проекта.

По-умолчанию структура проекта выглядит следующим образом:
├───data
│ ├───external
│ ├───interim
│ ├───processed
│ └───raw
├───docs
├───models
├───notebooks
├───references
├───reports
│ └───figures
└───src
├───data
├───features
├───models
└───visualization

Пока применил на единственном соревновании и нашёл вещи, которые мне не нравятся (например, нет директории для submission-ов и конфигурационных файлов). Буду продолжать использовать и, возможно, сделаю заточенный под мои нужды шаблон.

#python #lib #tool
Форматированный вывод дат в Python

Сегодня заметка одной строкой для того, чтобы в следующий раз сразу знать где искать.

Наткнулся на крутой cheat sheet про форматированный вывод дат в Python — вещь, которую каждый раз приходится гуглить.

Сайт Python strftime cheatsheet содержит понятную шпаргалку по теме.

PS: На КДПВ только первая треть всей шпаргалки.

#python #cheatsheet
Форматированный вывод без лишних зависимостей

Ранее я писал про icecream — библиотеку для более удобных/красивых "отладочных" print-ов. Да, модуль классный и полезный, но тащить каждый раз его ради двух-трёх использований кажется излишним.

Для такого случая в Python 3.8 появился новый синтаксис для f-строк:
>>> int_field = 42
>>> print(f'{int_field=}')
int_field=42

>>> str_field = 'hello world!'
>>> print(f'{str_field=}')
str_field='hello world!'

При этом работает и со сложными типами:
>>> arr = list(range(5))
>>> print(f'{arr=}')
arr=[0, 1, 2, 3, 4]

>>> d = {'a': 1, 'b': 2}
>>> print(f'{d=}')
d={'a': 1, 'b': 2}


И даже с классами:
>>> class Foo:
>>> def __init__(self, field_1: str, field_2: int) -> None:
>>> self.field_1 = field_1
>>> self.field_2 = field_2
>>>
>>> def __repr__(self) -> str:
>>> return f'{self.field_1=}; {self.field_2=}'
>>>
>>> foo = Foo('string field', 146)
>>> print(f'{foo=}')

foo=self.field_1='string field'; self.field_2=146


К сожалению в Google Colab и Kaggle Notebooks используется Python 3.7. Так что в этот раз будет ссылка на неинтерактивную версию в DataLore.

Если зарегистрированы в DataLore, то можете потыкать по этой ссылке.

#python
Визуализация git diff

Сегодня в очередной раз возникла необходимость сравнить две ветки в git-репозитории.

По-старинке сделал git diff main..other-branch > main.diff получил файл на несколько сотен строк. Просматривать его в текстовом редакторе — не то, чем бы я хотел заниматься.

Порывшись в интернете буквально 5-10 минут наткнулся на сервис https://diffy.org. Сервис простой как железная дорога — кидаете в него diff, а он рисует по нему дерево. Именно то, что я искал.

Для примера сделал diff для двух веток scikit-learn. Полученное дерево можно посмотреть здесь — https://diffy.org/diff/04db055a56b82.

По-умолчанию ссылка живёт 24 часа, но срок жизни можно продлить. Этот diff будет доступен ещё около 80 часов.

#TIL
Канал жив, но был в анабиозе. Сегодняшним постом постараюсь вернуться к более активному ведению канала — запасы интересностей пополнялись все четыре месяца молчания.

Сегодня расскажу про любопытную штуку для Jupyter Notebook.

Для начала напомню, что у pip — менеджера пакетов в python — есть замечательная команда freeze, которая позволяет отобразить список установленных пакетов:
>>> pip freeze
accelerate==0.3.0
argon2-cffi==21.1.0
arrow==1.2.1
...
widgetsnbextension==3.5.1
xgboost==1.4.2
xxhash==2.0.2
В реальности команда выдала список из 127 установленных пакетов в виртуальном окружении (поставьте 😱, если не знаете про виртуальные окружения — мало ли).

Я вижу как минимум два сценария, как это можно использоваться:
1. сохранить список в requirements.txt для фиксации версий библиотек для потомков
2. отобразить список используемых библиотек и их версий в отчёте, написанном в Jupyter Notebook.

И для второго случая есть более удобная библиотека: watermark.

Библиотека добавляет magic-команду, которая позволяет отобразить список библиотек и их версии, но только тех, которые были импортированы в блокнот.

Преимущество, как мне кажется, очевидно: вместо списка из миллионов библиотек, покажем только те, которые действительно требуются для работы блокнота.

Например, в Google Colab предустановлено более 300 библиотек (можно посмотреть в примере), но в примере явно импортированы только 6 и остальные мне не важны.

Пользоваться максимально просто:
1. установить через pip install watermark
2. загрузить extension командой %load_ext watermark
3. выполнить magic-команды %watermark с необходимыми аргументами (весь список можно узнать через %watermark?)

Вывод выглядит значительно аккуратнее и не имеет лишнего:
>>> %watermark --iversions --machine --python
Python implementation: CPython
Python version : 3.7.13
IPython version : 5.5.0

Compiler : GCC 7.5.0
OS : Linux
Release : 5.4.188+
Machine : x86_64
Processor : x86_64
CPU cores : 2
Architecture: 64bit

skimage : 0.18.3
torch : 1.11.0+cu113
sklearn : 0.0
tensorflow: 2.8.2+zzzcolab20220527125636
IPython : 5.5.0
numpy : 1.21.6
pandas : 1.3.5

Пример в colab

#python #lib
Возведение в степень в Python

Сегодня открытие связанное с Python пришло совершенно с неожиданной стороны — с привычной операции возведения числа в некоторую степень: x ** y.

Как вы считаете, что вернёт следующий код -1 ** 2? В моём мире розовых пони эта операция банальна как железная дорога: ответ должен быть 1. Однако Python здесь со мной не согласен. По его мнению -1 ** 2 == -1.

Дело здесь в следующем. Степенное выражение вычисляется справа налево, а унарный оператор - учитывается в итоговом результате:
1. сначала возводится 1 во вторую степень
2. результат умножается на -1.

Есть три способа обойти эту особенность:
1. записать -1 в отдельную переменную: x = -1; x ** 2
2. добавить скобки, для повышения приоритета: (-1) ** 2
3. использовать встроенную функцию pow(-1, 2).

Примеры на colab

Ссылка на документацию Python

#TIL #python
NLP в три строчки кода

Неожиданно прилетела по работе задача: разделять текст на предложения.

На первый взгляд проблема не выглядит очень сложной, но в процессе изучения понимаешь, что ".", "!", "?" — не всегда значат конец предложения.

Текущая реализация использует SRX-правила, но решать эту задачу десятками эвристик кажется не рационально: сложно добавлять новые так, чтобы не сломалось предыдущие; для каждого нового языка следует с нуля набирать примеры и писать правила.

Быстрый поиск по github выдал, что хорошие люди уже запили библиотеку trankit, которая из коробки умеет решать эту и несколько других задач для 56 языков (в том числе русский, английский, китайский, вьетнамский и др.).

Trankit умеет рашать задачи Named Entity Recognition, Sentence Boundary Detection, Part-Of-Speech Recognition, Lemmatization. Для меня более важна вторая — Sentence Boundary Detection — но, возможно, кому-то будут интересны и другие. Интерфейс библиотеки прямой как железная дорога:

from trankit import Pipeline
# Инициализация пайплайна, который автоматически определяет язык
p = Pipeline('auto', gpu=True)

text = '''Входная дверь открылась, и к вошедшей маме тут же подбежали Арина и Настя.
— Мама, папа сказал, что мы завтра все вместе пойдём в парк!
— Нет, он сказал: «Девочки, я так рад, что мы завтра все вместе пойдём в парк, наконец-то!»
— Вот зануда! Какая разница, мы всё равно пойдём в парк, — Арина была недовольна.
'''
sentences = p.ssplit(text)
print(sentences)


Вывод:
{'text': 'Входная дверь открылась, и к вошедшей маме тут же подбежали Арина и Настя.\n— Мама, папа сказал, что мы завтра все вместе пойдём в парк!\n— Нет, он сказал: «Девочки, я так рад, что мы завтра все вместе пойдём в парк, наконец-то!»\n— Вот зануда! Какая разница, мы всё равно пойдём в парк, — Арина была недовольна.\n',
'sentences': [{'id': 1,
'text': 'Входная дверь открылась, и к вошедшей маме тут же подбежали Арина и Настя.',
'dspan': (0, 74)},
{'id': 2,
'text': '— Мама, папа сказал, что мы завтра все вместе пойдём в парк!',
'dspan': (75, 135)},
{'id': 3,
'text': '— Нет, он сказал: «Девочки, я так рад, что мы завтра все вместе пойдём в парк, наконец-то!»',
'dspan': (136, 227)},
{'id': 4, 'text': '— Вот зануда!', 'dspan': (228, 241)},
{'id': 5,
'text': 'Какая разница, мы всё равно пойдём в парк, — Арина была недовольна.',
'dspan': (242, 309)}],
'lang': 'russian'}


Пример специально взял непростой: с диалогом и прямой речью, т.к. эти штуки сбивают основанные на правилах алгоритмы.

Исходники, как всегда, можно найти в ноутбуке на Google Colab

PS: Trankit основан на широко используемой библиотеке для NLP transformers от Hugging Face

#python #lib #ml
Не одним лишь argparse

До недавнего времени я сходу мог назвать два способа работы с command line аргументами в Python:
1. вручную разбирать параметры из sys.argv:

import sys

def log():
args = sys.argv
print(args)

if __name__ == '__main__':
log()

2. использовать стандартный модуль argparse:

import argparse

def log():
parser = argparse.ArgumentParser()
parser.add_argument('--name')
args = parser.parse_args()
print(args)

if __name__ == '__main__':
log()

Однако, кроме argparse есть и другие библиотеки для разбора аргументов командной строки. Один из них `click`.

Чем click лучше argparse:
- поддержка разных типов параметров (в том числе файлов, путей в ФС, флагов, перечислений и т.д.)
- поддержка кастомных типов параметров
- на мой взгляд более понятный API
- поддержка prompt-аргументов
- поддержка переменных окружения
- etc.

Пример выше на click выглядит просто:

import click

@click.command()
@click.option('--name', type=str)
def log(name):
print(name)

if __name__ == '__main__':
log()

Больше примеров в google colab (но на самом colab-е проверить работу не получится — придётся копировать локально и экспериментировать).

#python #lib