Русский
Русский
English
Статистика
Реклама

Jupyter

Решаем NLP-задачу классификация текстов по темам

22.01.2021 12:16:53 | Автор: admin

В предыдущейстатьея рассказал, как подготовить датасет, содержащий тексты блогаhabr.comс информацией об их принадлежности к определенной категории. Теперь на базе этого датасета я расскажу о подходах, позволяющих создать классификатор, автоматически относящий текст к той или иной категории.

Сегодня нам предстоит описать решение задачи по созданию классификатора текстовых документов. Шаг за шагом мы будем пытаться улучшить нашу модель. Давайте посмотрим, что же из этого получится.

Для решения нашей задачи снова используем язык программирования python и среду разработки Jupyternotebook на платформе GoogleColab.

В работе понадобятся следующие библиотеки:

  • scikit-learn свободно распространяемая библиотека на python, содержащая реализации различных методов машинного обучения;

  • nltk пакетбиблиотекдля символьной и статистическойобработки естественного языка;

  • matplotlib библиотека, содержащая набор инструментов для визуализации данных, понадобится для отображения облака слов.

Подробности реализации

Датасет сохранен в файле формата csv и содержит чуть более 8 тысяч записей. Для работы с ним будем использовать библиотеку pandas загружаем данные в память при помощи метода read_csv и отображаем на экране несколько первых строк:

import pandas as pddf_habr = pd.read_csv(habrParse.csv)df_habr.head()

Набор данных представляет собой таблицу, в первой колонке которой хранится текст статьи, во второй присвоенная категория (класс):

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

# Получение текстовой строки из списка словdef str_corpus(corpus):    str_corpus = ''    for i in corpus:        str_corpus += ' ' + i    str_corpus = str_corpus.strip()    return str_corpus# Получение списка всех слов в корпусеdef get_corpus(data):    corpus = []    for phrase in data:        for word in phrase.split():            corpus.append(word)    return corpus# Получение облака словdef get_wordCloud(corpus):    wordCloud = WordCloud(background_color='white',                              stopwords=STOPWORDS,                              width=3000,                              height=2500,                              max_words=200,                              random_state=42                         ).generate(str_corpus(corpus))    return wordCloudcorpus = get_corpus(df_train['text'].values)procWordCloud = get_wordCloud(corpus)fig = plt.figure(figsize=(20, 8))plt.subplot(1, 2, 1)plt.imshow(procWordCloud)plt.axis('off')plt.subplot(1, 2, 1)

Для необработанного набора данных облако слов содержит 243024 уникальных слова и выглядит так:

Попробуем очистить данные:

import nltknltk.download("stopwords")from nltk.corpus import stopwordsfrom string import punctuationrussian_stopwords = stopwords.words("russian")# Удаление знаков пунктуации из текстаdef remove_punct(text):    table = {33: ' ', 34: ' ', 35: ' ', 36: ' ', 37: ' ', 38: ' ', 39: ' ', 40: ' ', 41: ' ', 42: ' ', 43: ' ', 44: ' ', 45: ' ', 46: ' ', 47: ' ', 58: ' ', 59: ' ', 60: ' ', 61: ' ', 62: ' ', 63: ' ', 64: ' ', 91: ' ', 92: ' ', 93: ' ', 94: ' ', 95: ' ', 96: ' ', 123: ' ', 124: ' ', 125: ' ', 126: ' '}    return text.translate(table)habrParse_df['Post_clean'] = habrParse_df['Post'].map(lambda x: x.lower())habrParse_df['Post_clean'] = habrParse_df['Post_clean'].map(lambda x: remove_punct(x))habrParse_df['Post_clean'] = habrParse_df['Post_clean'].map(lambda x: x.split(' '))habrParse_df['Post_clean'] = habrParse_df['Post_clean'].map(lambda x: [token for token in x if token not in russian_stopwords\                                                                  and token != " " \                                                                  and token.strip() not in punctuation])habrParse_df['Post_clean'] = habrParse_df['Post_clean'].map(lambda x: ' '.join(x))

После небольшой очистки текстов от стоп-слов и знаков пунктуации количество уникальных слов снизилось до 142253, а облако слов стало более осмысленным:

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

Посмотрим статистику по классам:

Видно, что некоторые классы представлены только одним элементом, а класс Чулан составляет более 65% датасета. Для того чтобы работать с более или менее сбалансированным датасетом, выберем тексты только четырех классов:

df_habr_clean = df_habr.loc[df_habr['hubs'].isin(['IT-компании', 'Habr', 'Управление медиа', 'Я пиарюсь'])]

Разделим датасет на тренировочную, тестовую и валидационную части:

from sklearn.model_selection import train_test_splitX_train, X_valid, y_train, y_valid = train_test_split(df_habr_clean ['Post_clean'], df_habr_clean ['hubs'], test_size=0.1, random_state=42)X_train, X_test, y_train, y_test = train_test_split(X_train, y_train, test_size=0.2, random_state=42)

Получили следующее соотношение выборок:X_train 1136 элементов,X_test 283 элемента,X_valid 158 элементов

Для дальнейшей работы понадобится импортировать несколько модулей из библиотеки scikit-learn:

from sklearn.pipeline import Pipeline# pipeline позволяет объединить в один блок трансформер и модель, что упрощает написание кода и улучшает его читаемостьfrom sklearn.feature_extraction.text import TfidfVectorizer# TfidfVectorizer преобразует тексты в числовые вектора, отражающие важность использования каждого слова из некоторого набора слов (количество слов набора определяет размерность вектора) в каждом текстеfrom sklearn.linear_model import SGDClassifierfrom sklearn.neighbors import KNeighborsClassifier# линейный классификатор и классификатор методом ближайших соседейfrom sklearn import metrics# набор метрик для оценки качества моделиfrom sklearn.model_selection import GridSearchCV# модуль поиска по сетке параметров

Сначала создадим 2 классификатора (чтобы можно было в дальнейшем сравнить качество получившихся моделей) и обучим их на тестовых данных:

sgd_ppl_clf = Pipeline([    ('tfidf', TfidfVectorizer()),    ('sgd_clf', SGDClassifier(random_state=42))])knb_ppl_clf = Pipeline([    ('tfidf', TfidfVectorizer()),    ('knb_clf', KNeighborsClassifier(n_neighbors=10))])sgd_ppl_clf.fit(X_train, y_train)knb_ppl_clf.fit(X_train, y_train)

Предскажем получившимися моделями класс текстов в тестовой выборке и сравним метрики:

predicted_sgd = sgd_ppl_clf.predict(X_test)print(metrics.classification_report(predicted_sgd, y_test))
predicted_sgd = knb_ppl_clf.predict(X_test)print(metrics.classification_report(predicted_sgd, y_test))

В связи с тем, что датасет не сбалансирован, метрику accuracy (доля верных ответов) использовать нельзя, так как это приведет к завышенной оценке качества работы классификатора. В данном случае самое правильное считать сразу несколько метрик, устойчивых к распределению классов (в данном случае, это точность, полнота и f-мера) и смотреть на них все. Однако часто бывает удобно получить не большой набор цифр, а одно число, по которому можно понять, насколько хорошо модель работает. В нашей задаче лучше всего подходит macro-avg (сначала подсчитывается каждая метрика по каждому классу, а потом усредняется). Macro-avg более устойчива к скошенным распределениям классов.

Линейный классификатор показал лучший результат на тестовых данных (0,77 против 0,72 у классификатора методом ближайших соседей), поэтому для дальнейшей настройки модели будем использовать его.

На текущий момент классификатор обучен с параметрами по умолчанию. Для большинства задач это вполне приемлемо. Но для более качественной работы модели лучше провести ее дополнительную настройку. В этом случае удобно использовать поиск лучшего сочетания по сетке параметров. Так, можно задать набор значений для параметров классификатора и передать его в модуль GridSearchCV. Он выполнит обучение и оценку модели на каждом сочетании параметров и выдаст лучший результат.

Попробуем улучшить нашу модель, используя различные параметры. Следует иметь в виду, что для доступа к параметрам объекта pipeline необходимо указывать их в виденазвание объекта__название параметра:

Обучим модель, используя предложенные параметры (часть из них получила значение по умолчанию, поэтому их указывать не обязательно), и оценим ее качество на тестовых данных:

sgd_ppl_clf = Pipeline([    ('tfidf', TfidfVectorizer(ngram_range=(1, 2))),    ('sgd_clf', SGDClassifier(penalty='elasticnet', class_weight='balanced', random_state=42))])sgd_ppl_clf.fit(X_train, y_train)predicted_sgd = sgd_ppl_clf.predict(X_test)print(metrics.classification_report(predicted_sgd, y_test))

Значение целевой метрики выросло.

predicted_sgd_val = sgd_ppl_clf.predict(X_valid)print(metrics.classification_report(predicted_sgd_val, y_valid))

На валидационной выборке качество также выросло (0,8 против 0,76 с использованием стандартных параметров классификатора), следовательно, мы успешно справились с поставленной задачей.

В данной статье я постарался описать решение задачи по созданию классификатора текстовых документов. Здесь представлены только общие шаги, но и они позволили достичь достаточно хорошего результата. Для дальнейшего улучшения модели можно увеличить датасет и провести его дополнительную очистку, попробовать другие классификаторы (например, RandomForestClassifier) и другие сочетания их параметров.

Надеюсь, статья была полезной и поможет вам начать самостоятельно решать nlp-задачи.

Подробнее..

Перевод Трассировка Python GIL

22.01.2021 18:12:57 | Автор: admin


Есть много статей, объясняющих, для чего нужен Python GIL (The Global Interpreter Lock) (я подразумеваю CPython). Если вкратце, то GIL не даёт многопоточному чистому коду на Python использовать несколько ядер процессора.

Однако мы в Vaex исполняем большинство задач с интенсивными вычислениями на С++ с отключением GIL. Это нормальная практика для высокопроизводительных Pythonбиблиотек, в которых Python всего лишь выступает в роли высокоуровневого связующего звена.

GIL нужно отключать явно, и это ответственность программиста, о которой он может забыть, что приведёт к неэффективному использованию мощностей. Недавно я сам побывал в роли забывшего, и нашёл подобную проблему в Apache Arrow (это зависимость Vaex, так что когда GIL не отключается в Arrow, мы (и все остальные) сталкиваемся с падением производительности).

Кроме того, при исполнении на 64 ядрах производительность Vaex иногда далека от идеала. Возможно, он использует 4000 % процессора вместо 6400 %, что меня не устраивает. Вместо того, чтобы наугад вставлять выключатели для изучения этого эффекта, я хочу разобраться в происходящем, и если проблема в GIL, то хочу понять, почему и как он тормозит Vaex.

Почему я это пишу


Я планирую написать серию статей, рассказывающих о некоторых инструментах и методиках профилирования и трассировки Python с нативными расширениями, а также о том, как эти инструменты можно объединить для анализа и визуализации работы Python при включённом и отключённом GIL.

Надеюсь, это поможет улучшить трассировку, профилирование и другие измерения производительности в экосистеме языка, а также производительности всей экосистемы Python.

Требования


Linux


Вам нужен доступ к Linux-машине с root-привилегиями (sudo достаточно). Или попросите сисадмина выполнить для вас нижеописанные команды. Для остальной статьи достаточно пользовательских привилегий.

Perf


Убедитесь, что у вас установлен perf, к примеру, на Ubuntu это можно сделать так:

$ sudo yum install perf

Конфигурация ядра


Запуск в роли пользователя:

# Enable users to run perf (use at own risk)$ sudo sysctl kernel.perf_event_paranoid=-1# Enable users to see schedule trace events:$ sudo mount -o remount,mode=755 /sys/kernel/debug$ sudo mount -o remount,mode=755 /sys/kernel/debug/tracing

Пакеты Python


Мы будем использовать VizTracer и per4m

$ pip install "viztracer>=0.11.2" "per4m>=0.1,<0.2"

Отслеживание состояний потоков и процессов с помощью perf


В Python нельзя выяснить состояние GIL (кроме использования поллинга), потому что для этого нет API. Мы можем отслеживать состояние из ядра, и для этого нам нужен инструмент perf.

С его помощью (он также известен как perf_events) мы можем прослушивать изменения состояний процессов и потоков (нас интересует только засыпание и исполнение) и журналировать их. Perf может выглядеть пугающе, но это мощный инструмент. Если хотите узнать о нём больше, рекомендую почитать статью Джулии Эванс или сайт Брендана Грегга.

Чтобы настроиться, для начала применим perf к простенькой программе:

import timefrom threading import Threaddef sleep_a_bit():    time.sleep(1)def main():    t = Thread(target=sleep_a_bit)    t.start()    t.join()main()

Мы прослушиваем всего несколько событий, чтобы уменьшить зашумлённость (обратите внимание на использование символов подстановки (wildcards)):

$ perf record -e sched:sched_switch  -e sched:sched_process_fork \        -e 'sched:sched_wak*' -- python -m per4m.example0[ perf record: Woken up 2 times to write data ][ perf record: Captured and wrote 0,032 MB perf.data (33 samples) ]

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

Скрытый текст
$ perf script        :3040108 3040108 [032] 5563910.979408:                sched:sched_waking: comm=perf pid=3040114 prio=120 target_cpu=031        :3040108 3040108 [032] 5563910.979431:                sched:sched_wakeup: comm=perf pid=3040114 prio=120 target_cpu=031          python 3040114 [031] 5563910.995616:                sched:sched_waking: comm=kworker/31:1 pid=2502104 prio=120 target_cpu=031          python 3040114 [031] 5563910.995618:                sched:sched_wakeup: comm=kworker/31:1 pid=2502104 prio=120 target_cpu=031          python 3040114 [031] 5563910.995621:                sched:sched_waking: comm=ksoftirqd/31 pid=198 prio=120 target_cpu=031          python 3040114 [031] 5563910.995622:                sched:sched_wakeup: comm=ksoftirqd/31 pid=198 prio=120 target_cpu=031          python 3040114 [031] 5563910.995624:                sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=R+ ==> next_comm=kworker/31:1 next_pid=2502104 next_prio=120          python 3040114 [031] 5563911.003612:                sched:sched_waking: comm=kworker/32:1 pid=2467833 prio=120 target_cpu=032          python 3040114 [031] 5563911.003614:                sched:sched_wakeup: comm=kworker/32:1 pid=2467833 prio=120 target_cpu=032          python 3040114 [031] 5563911.083609:                sched:sched_waking: comm=ksoftirqd/31 pid=198 prio=120 target_cpu=031          python 3040114 [031] 5563911.083612:                sched:sched_wakeup: comm=ksoftirqd/31 pid=198 prio=120 target_cpu=031          python 3040114 [031] 5563911.083613:                sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/31 next_pid=198 next_prio=120          python 3040114 [031] 5563911.108984:                sched:sched_waking: comm=node pid=2446812 prio=120 target_cpu=045          python 3040114 [031] 5563911.109059:                sched:sched_waking: comm=node pid=2446812 prio=120 target_cpu=045          python 3040114 [031] 5563911.112250:          sched:sched_process_fork: comm=python pid=3040114 child_comm=python child_pid=3040116          python 3040114 [031] 5563911.112260:            sched:sched_wakeup_new: comm=python pid=3040116 prio=120 target_cpu=037          python 3040114 [031] 5563911.112262:            sched:sched_wakeup_new: comm=python pid=3040116 prio=120 target_cpu=037          python 3040114 [031] 5563911.112273:                sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=S ==> next_comm=swapper/31 next_pid=0 next_prio=120          python 3040116 [037] 5563911.112418:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031          python 3040116 [037] 5563911.112450:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031          python 3040116 [037] 5563911.112473: sched:sched_wake_idle_without_ipi: cpu=31         swapper     0 [031] 5563911.112476:                sched:sched_wakeup: comm=python pid=3040114 prio=120 target_cpu=031          python 3040114 [031] 5563911.112485:                sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=S ==> next_comm=swapper/31 next_pid=0 next_prio=120          python 3040116 [037] 5563911.112485:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031          python 3040116 [037] 5563911.112489:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031          python 3040116 [037] 5563911.112496:                sched:sched_switch: prev_comm=python prev_pid=3040116 prev_prio=120 prev_state=S ==> next_comm=swapper/37 next_pid=0 next_prio=120         swapper     0 [031] 5563911.112497:                sched:sched_wakeup: comm=python pid=3040114 prio=120 target_cpu=031          python 3040114 [031] 5563911.112513:                sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=S ==> next_comm=swapper/31 next_pid=0 next_prio=120         swapper     0 [037] 5563912.113490:                sched:sched_waking: comm=python pid=3040116 prio=120 target_cpu=037         swapper     0 [037] 5563912.113529:                sched:sched_wakeup: comm=python pid=3040116 prio=120 target_cpu=037          python 3040116 [037] 5563912.113595:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031          python 3040116 [037] 5563912.113620:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031         swapper     0 [031] 5563912.113697:                sched:sched_wakeup: comm=python pid=3040114 prio=120 target_cpu=031


Взгляните на четвёртую колонку (время в секундах): программа заснула (прошла одна секунда). Здесь мы видим вход в состояние сна:

python 3040114 [031] 5563911.112513: sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=S ==> next_comm=swapper/31 next_pid=0 next_prio=120

Это означает, что ядро изменило состояние потока Python на S (=sleeping).

Секунду спустя программа пробудилась:

swapper 0 [031] 5563912.113697: sched:sched_wakeup: comm=python pid=3040114 prio=120 target_cpu=031

Конечно, чтобы разобраться в происходящем придётся собрать инструментарий. Да, результат можно также легко отпарсить с помощью per4m, но прежде чем мы продолжим, хочу с помощью VizTracer визуализировать поток чуть более сложной программы.

VizTracer


Это трассировщик Python, способный визуализировать работу, которую программа выполняет в браузере. Давайте применим его к более сложной программе:

import threadingimport timedef some_computation():    total = 0       for i in range(1_000_000):        total += i    return totaldef main():    thread1 = threading.Thread(target=some_computation)    thread2 = threading.Thread(target=some_computation)    thread1.start()    thread2.start()    time.sleep(0.2)    for thread in [thread1, thread2]:        thread.join()main()

Результат работы трассировщика:

$ viztracer -o example1.html --ignore_frozen -m per4m.example1Loading finish                                        Saving report to /home/maartenbreddels/github/maartenbreddels/per4m/example1.html ...Dumping trace data to json, total entries: 94, estimated json file size: 11.0KiBGenerating HTML reportReport saved.

Так выглядит получившийся HTML:


Похоже, some_computation исполнялась параллельно (дважды), хотя мы знаем, что GIL это предотвращает. Что тут происходит?

Объединение результатов работы VizTracer и perf


Давайте применим к программе perf, как в случае с example0.py. Только теперь добавим аргумент -k CLOCK_MONOTONIC, чтобы использовать те же часы, что и VizTracer, и попросим его сгенерировать JSON вместо HTML:

$ perf record -e sched:sched_switch  -e sched:sched_process_fork -e 'sched:sched_wak*' \   -k CLOCK_MONOTONIC  -- viztracer -o viztracer1.json --ignore_frozen -m per4m.example1

Затем с помощью per4m преобразуем результаты скрипта perf в JSON, который может прочитать VizTracer:

$ perf script | per4m perf2trace sched -o perf1.jsonWrote to perf1.json

Теперь с помощью VizTracer объединим два JSON-файла:

$ viztracer --combine perf1.json viztracer1.json -o example1_state.htmlSaving report to /home/maartenbreddels/github/maartenbreddels/per4m/example1.html ...Dumping trace data to json, total entries: 131, estimated json file size: 15.4KiBGenerating HTML reportReport saved.

Вот что получили:


Очевидно, что потоки регулярно засыпают из-за GIL и не исполняются параллельно.

Примечание: длина фазы сна около 5 мс, что соответствует значению по умолчанию sys.getswitchinterval

Определение GIL


Наш процесс засыпает, но мы не видим разницы между состояниями сна, которое инициируется вызовом time.sleep или GIL. Есть несколько способов определить разницу, рассмотрим два из них.

Через трассировку стека


С помощью perf record -g (а лучше с помощью perf record --call-graph dwarf, что подразумевает -g), мы получим трассировки стека для каждого события perf.

$ perf record -e sched:sched_switch  -e sched:sched_process_fork -e 'sched:sched_wak*'\   -k CLOCK_MONOTONIC  --call-graph dwarf -- viztracer -o viztracer1-gil.json --ignore_frozen -m per4m.example1Loading finish                                        Saving report to /home/maartenbreddels/github/maartenbreddels/per4m/viztracer1-gil.json ...Dumping trace data to json, total entries: 94, estimated json file size: 11.0KiBReport saved.[ perf record: Woken up 3 times to write data ][ perf record: Captured and wrote 0,991 MB perf.data (164 samples) ]

Взглянув на результат скрипта perf (в который мы ради производительности добавили --no-inline), мы получим много информации. Посмотрите на событие изменения состояния, оказывается, вызывался take_gil!

Скрытый текст
$ perf script --no-inline | less...viztracer 3306851 [059] 5614683.022539:                sched:sched_switch: prev_comm=viztracer prev_pid=3306851 prev_prio=120 prev_state=S ==> next_comm=swapper/59 next_pid=0 next_prio=120        ffffffff96ed4785 __sched_text_start+0x375 ([kernel.kallsyms])        ffffffff96ed4785 __sched_text_start+0x375 ([kernel.kallsyms])        ffffffff96ed4b92 schedule+0x42 ([kernel.kallsyms])        ffffffff9654a51b futex_wait_queue_me+0xbb ([kernel.kallsyms])        ffffffff9654ac85 futex_wait+0x105 ([kernel.kallsyms])        ffffffff9654daff do_futex+0x10f ([kernel.kallsyms])        ffffffff9654dfef __x64_sys_futex+0x13f ([kernel.kallsyms])        ffffffff964044c7 do_syscall_64+0x57 ([kernel.kallsyms])        ffffffff9700008c entry_SYSCALL_64_after_hwframe+0x44 ([kernel.kallsyms])        7f4884b977b1<a href="http://personeltest.ru/aways/www.maartenbreddels.com/cdn-cgi/l/email-protection"> [email protected]@GLIBC_2.3.2+0x271 (/usr/lib/x86_64-linux-gnu/libpthread-2.31.so)            55595c07fe6d take_gil+0x1ad (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfaa0b3 PyEval_RestoreThread+0x23 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c000872 lock_PyThread_acquire_lock+0x1d2 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfe71f3 _PyMethodDef_RawFastCallKeywords+0x263 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfe7313 _PyCFunction_FastCallKeywords+0x23 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c01d657 call_function+0x3b7 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfb6db1 _PyEval_EvalCodeWithName+0x251 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfd6b00 _PyFunction_FastCallKeywords+0x520 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfb6db1 _PyEval_EvalCodeWithName+0x251 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfd6b00 _PyFunction_FastCallKeywords+0x520 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfd6766 _PyFunction_FastCallKeywords+0x186 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfd6766 _PyFunction_FastCallKeywords+0x186 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c060ae4 _PyEval_EvalFrameDefault+0x3f4 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfb6db1 _PyEval_EvalCodeWithName+0x251 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c074e5d builtin_exec+0x33d (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfe7078 _PyMethodDef_RawFastCallKeywords+0xe8 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfe7313 _PyCFunction_FastCallKeywords+0x23 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c066c39 _PyEval_EvalFrameDefault+0x6549 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfb77e0 _PyEval_EvalCodeWithName+0xc80 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfd6b62 _PyFunction_FastCallKeywords+0x582 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfd6766 _PyFunction_FastCallKeywords+0x186 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfd6766 _PyFunction_FastCallKeywords+0x186 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfd6766 _PyFunction_FastCallKeywords+0x186 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c060ae4 _PyEval_EvalFrameDefault+0x3f4 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfb6db1 _PyEval_EvalCodeWithName+0x251 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595bfb81e2 PyEval_EvalCode+0x22 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c0c51d1 run_mod+0x31 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c0cf31d PyRun_FileExFlags+0x9d (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c0cf50a PyRun_SimpleFileExFlags+0x1ba (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c0d05f0 pymain_main+0x3e0 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            55595c0d067b _Py_UnixMain+0x3b (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)            7f48849bc0b2 __libc_start_main+0xf2 (/usr/lib/x86_64-linux-gnu/libc-2.31.so)            55595c075100 _start+0x28 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)


Примечание: также вызывался pthread_cond_timedwait, он используется https://github.com/sumerc/gilstats.py для eBPF, если вас интересуют другие мьютексы.

Ещё примечание: обратите внимание, что здесь нет трассировки стека Python, а вместо неё мы получили _PyEval_EvalFrameDefault и прочее. В будущем я планирую написать, как вставлять трассировку стека.

Инструмент для конвертирования per4m perf2trace понимает это и генерирует другой результат, когда в трассировке присутствует take_gil:

$ perf script --no-inline | per4m perf2trace sched -o perf1-gil.jsonWrote to perf1-gil.json$ viztracer --combine perf1-gil.json viztracer1-gil.json -o example1-gil.htmlSaving report to /home/maartenbreddels/github/maartenbreddels/per4m/example1.html ...Dumping trace data to json, total entries: 131, estimated json file size: 15.4KiBGenerating HTML reportReport saved.

Получаем:


Теперь мы точно видим, где GIL играет роль!

Через зондирование (kprobes/uprobes)


Мы знаем, когда процессы засыпают (из-за GIL или иных причин), но если хочется подробнее узнать о том, когда включается или выключается GIL, то нужно знать, когда вызываются и возвращаются результаты take_gil и drop_gil. Такую трассировку можно получить благодаря зондированию с помощью perf. В пользовательской среде зондами являются uprobes, это аналог kprobes, которые, как вы могли догадаться, работают в среде ядра. Опять же, прекрасный источник дополнительной информации Джулия Эванс.

Установим 4 зонда:

sudo perf probe -f -x `which python` python:take_gil=take_gilsudo perf probe -f -x `which python` python:take_gil=take_gil%returnsudo perf probe -f -x `which python` python:drop_gil=drop_gilsudo perf probe -f -x `which python` python:drop_gil=drop_gil%returnAdded new events:  python:take_gil      (on take_gil in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)  python:take_gil_1    (on take_gil in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)You can now use it in all perf tools, such as:        perf record -e python:take_gil_1 -aR sleep 1Failed to find "take_gil%return", because take_gil is an inlined function and has no return point.Added new event:  python:take_gil__return (on take_gil%return in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)You can now use it in all perf tools, such as:        perf record -e python:take_gil__return -aR sleep 1Added new events:  python:drop_gil      (on drop_gil in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)  python:drop_gil_1    (on drop_gil in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)You can now use it in all perf tools, such as:        perf record -e python:drop_gil_1 -aR sleep 1Failed to find "drop_gil%return", because drop_gil is an inlined function and has no return point.Added new event:  python:drop_gil__return (on drop_gil%return in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)You can now use it in all perf tools, such as:        perf record -e python:drop_gil__return -aR sleep 1

Есть какие-то жалобы, а из-за инлайненных drop_gil и take_gil добавилось несколько зондов/событий (то есть функция представлена в бинарном файле несколько раз), но всё работает.

Примечание: возможно, мне повезло, что мой бинарник Python (от conda-forge) скомпилирован так, что соответствующие take_gil/drop_gil (и их результаты), успешно отработавшие, подошли для решения этой проблемы.

Обратите внимание, что зонды не влияют на производительность, и только когда они активны (например, когда мы их мониторим из perf), то запускают код по другой ветке. При мониторинге копируются затронутые страницы для отслеживаемого процесса, а в нужные места вставляются контрольные точки (INT3 для процессоров x86). Контрольная точка запускает событие для perf с маленькими накладными расходами. Если вы хотите убрать зонды, то выполните команду:

$ sudo perf probe --del 'python*'

Теперь perf знает о новых событиях, которые он может прослушивать, так что давайте снова его запустим с дополнительным аргументом -e 'python:*gil*':

$ perf record -e sched:sched_switch  -e sched:sched_process_fork -e 'sched:sched_wak*' -k CLOCK_MONOTONIC  \  -e 'python:*gil*' -- viztracer  -o viztracer1-uprobes.json --ignore_frozen -m per4m.example1

Примечание: мы убрали --call-graph dwarf, иначе perf не успеет и мы потеряем события.

Затем с помощью per4m perf2trace преобразуем в JSON, который понятен для VizTracer, и заодно получим новую статистику.

$ perf script --no-inline | per4m perf2trace gil -o perf1-uprobes.json...Summary of threads:PID         total(us)    no gil%    has gil%    gil wait%--------  -----------  -----------  ------------  -------------3353567*     164490.0         65.9          27.3            6.73353569       66560.0          0.3          48.2           51.53353570       60900.0          0.0          56.4           43.6High 'no gil' is good, we like low 'has gil', and we don't want 'gil wait'. (* indicates main thread)... Wrote to perf1-uprobes.json

Подкоманда per4m perf2trace gil также выдаёт в качестве результата gil_load. Из него мы узнаём, что оба потока ждут GIL примерно половину времени, как и ожидалось.

С помощью того же файла perf.data, записанного perf, мы также можем сгенерировать информацию о состоянии потока или процесса. Но поскольку мы выполняли без трассировок стека, мы не знаем, заснул ли процесс из-за GIL или нет.

$ perf script --no-inline | per4m perf2trace sched -o perf1-state.jsonWrote to perf1-state.json

Наконец, соберём вместе все три результата:

$ viztracer --combine perf1-state.json perf1-uprobes.json viztracer1-uprobes.json -o example1-uprobes.html Saving report to /home/maartenbreddels/github/maartenbreddels/per4m/example1-uprobes.html ...Dumping trace data to json, total entries: 10484, estimated json file size: 1.2MiBGenerating HTML reportReport saved.

VizTracer даёт хорошее представление о том, у кого был GIL и кто его ждал:


Над каждым потоком написано, когда поток или процесс ждёт GIL и когда тот включается (помечено как LOCK). Обратите внимание, что эти периоды пересекаются с периодами, когда поток или процесс не спит (выполняется!). Также обратите внимание, что в состоянии выполнения мы видим только один поток или процесс, как и должно быть из-за GIL.

Время между вызовами take_gil, то есть между блокировками (а следовательно, между засыпанием или пробуждением), точно такое же, как в вышеприведённой таблице в колонке gil wait%. Длительность включения GIL для каждого потока, помеченная как LOCK, соответствует времени в колонке gil%.

Выпускаем Кракена гхм, GIL


Мы видели, как при многопоточном исполнении программы на чистом Python GIL ограничивает производительность, разрешая одновременно исполнять только один поток или процесс (для одного процесса Python, разумеется, и, возможно, в будущем для одного (суб)интерпретатора). Теперь посмотрим, что будет, если отключить GIL, как это происходит при исполнении функций NumPy.

Во втором примере исполняется some_numpy_computation, которая вызывает функцию NumPy M=4 раз параллельно в двух потоках, в то время как основной поток исполняет чистый код на Python.

import threadingimport timeimport numpy as npN = 1024*1024*32M = 4x = np.arange(N, dtype='f8')def some_numpy_computation():    total = 0    for i in range(M):        total += x.sum()    return totaldef main(args=None):    thread1 = threading.Thread(target=some_numpy_computation)    thread2 = threading.Thread(target=some_numpy_computation)    thread1.start()    thread2.start()    total = 0    for i in range(2_000_000):        total += i    for thread in [thread1, thread2]:        thread.join()main()

Вместо исполнения этого скрипта с помощью perf и VizTracer мы применим утилиту per4m giltracer, которая автоматизирует все вышеописанные этапы. Она сделает это немного умнее. По сути, мы дважды запустим perf, один раз без трассировки стека, а второй раз с ней, и импортируем модуль/скрипт до исполнения его основной функции, чтобы избавиться от неинтересной трассировки вроде того же импортирования. Это произойдёт достаточно быстро, чтобы мы не потеряли события.

$ giltracer --state-detect -o example2-uprobes.html -m per4m.example2...

Итоги по потокам:

PID         total(us)    no gil%    has gil%    gil wait%--------  -----------  -----------  ------------  -------------3373601*    1359990.0         95.8           4.2            0.13373683       60276.4         84.6           2.2           13.23373684       57324.0         89.2           1.9            8.9High 'no gil' is good, we like low 'has gil', and we don't want 'gil wait'. (* indicates main thread)...Saving report to /home/maartenbreddels/github/maartenbreddels/per4m/example2-uprobes.html ......


Хотя в основном потоке исполняется код на Python (для него включён GIL, что обозначается словом LOCK), параллельно исполняются и другие потоки. Обратите внимание, что в примере с чистым Python у нас одновременно исполняется один поток или процесс. А здесь действительно параллельно исполняется три потока. Это стало возможно потому, что подпрограммы NumPy, входящие в C/C++/Fortran, отключили GIL.

Однако GIL всё же влияет на потоки, потому что когда функция NumPy возвращается в Python, ей снова нужно получить GIL, как видно из длительных блоков take_gil. На это тратится 10 % времени каждого потока.

Интеграция Jupyter


Поскольку мой рабочий процесс часто подразумевает работу на MacBook (который не исполняет perf, но поддерживает dtrace), удалённо подключённом к Linux-машине, я использую Jupyter notebook для удалённого исполнения кода. И раз я Jupyter-разработчик, мне пришлось сделать обёртку с помощью cell magic.

# this registers the giltracer cell magic%load_ext per4m%%giltracer# this call the main define above, but this can also be a multiline code cellmain()Saving report to /tmp/tmpvi8zw9ut/viztracer.json ...Dumping trace data to json, total entries: 117, estimated json file size: 13.7KiBReport saved.[ perf record: Woken up 1 times to write data ][ perf record: Captured and wrote 0,094 MB /tmp/tmpvi8zw9ut/perf.data (244 samples) ]Wait for perf to finish...perf script -i /tmp/tmpvi8zw9ut/perf.data --no-inline --ns | per4m perf2trace gil -o /tmp/tmpvi8zw9ut/giltracer.json -q -v Saving report to /home/maartenbreddels/github/maartenbreddels/fastblog/_notebooks/giltracer.html ...Dumping trace data to json, total entries: 849, estimated json file size: 99.5KiBGenerating HTML reportReport saved.

Скачать giltracer.html

Открыть giltracer.html в новой вкладке (может не работать в связи с обеспечением безопасности)

Заключение


С помощью perf мы можем определять состояния процессов или потоков, что помогает понять, у кого из них включался GIL в Python. А с помощью трассировок стека можно определить, что причиной сна был GIL, а не time.sleep, например.

Сочетание uprobes с perf позволяет трассировать вызов и возвращение результатов функций take_gil и drop_gil, получая ещё больше информации о влиянии GIL на вашу Python-программу.

Работу нам облегчает экспериментальный пакет per4m, преобразующий скрипт perf в JSON-формат VizTracer, а также некоторые инструменты оркестрации.

Много букаф, не осилил


Если вам просто хочется посмотреть на влияние GIL, то однократно запустите это:

sudo yum install perfsudo sysctl kernel.perf_event_paranoid=-1sudo mount -o remount,mode=755 /sys/kernel/debugsudo mount -o remount,mode=755 /sys/kernel/debug/tracingsudo perf probe -f -x `which python` python:take_gil=take_gilsudo perf probe -f -x `which python` python:take_gil=take_gil%returnsudo perf probe -f -x `which python` python:drop_gil=drop_gilsudo perf probe -f -x `which python` python:drop_gil=drop_gil%returnpip install "viztracer>=0.11.2" "per4m>=0.1,<0.2"

Пример использования:

# module$ giltracer per4m/example2.py# script$ giltracer -m per4m.example2# add thread/process state detection$ giltracer --state-detect -m per4m.example2# without uprobes (in case that fails)$ giltracer --state-detect --no-gil-detect -m per4m.example2

Планы на будущее


Хотелось бы мне, чтобы не пришлось разрабатывать эти инструменты. Надеюсь, мне удалось вдохновить кого-то на создание более совершенных средств, чем мои. Я хочу сосредоточиться на написании высокопроизводительного кода. Однако у меня есть такие планы на будущее:

  • Раскопать измеритель производительности в VizTracer, чтобы посмотреть на промахи кеша или простои процесса.
  • Внедрить трассировку стека Python в трассировки perf, чтобы объединить их с инструментами вроде http://www.brendangregg.com/offcpuanalysis.html
  • Повторить то же упражнение с помощью dtrace, чтобы использовать в MacOS.
  • Автоматически определять, какие C-функции не отключают GIL (https://github.com/vaexio/vaex/pull/1114, https://github.com/apache/arrow/pull/7756 )
  • Применить всё это для решения других задач, например https://github.com/h5py/h5py/issues/1516
Подробнее..

Перевод Запускаем Golang на Jupyter Notebook

13.05.2021 16:14:30 | Автор: admin

Если вы знакомы с Python, то уже сталкивались с Jupyter Notebook или работали в нём по крайней мере один раз. Jupyter Notebook это удобный инструмент, позволяющий писать мини-код и отслеживать его выполнение. Он также помогает в документировании, ведении журнала и в том, чтобы поделиться своими работами с коллегами.

Неудивительно, что многие люди и крупные организации, такие как Netflix, для своих целей в разработке предпочитают Jupyter Notebook. Специально к старту нового потока курса по разработке на Go 26 мая мы решили поделиться переводом, автор которого рассказывает, как документировать проекты на Golang в Jupyter Notebook.


Если вы работаете на машине с Windows, потребуется установка Docker. Пожалуйста, следуйте этим инструкциям. Если вы работаете на Mac или Linux, вы можете либо использовать метод с docker выше, либо следовать процессам локальной установки, о которых я напишу ниже.

Содержание

  1. Установка.

  2. Запуск Jupyter Notebook.

  3. Написание простой программы.

Установка

Установка может показаться сложной, но я постараюсь сделать её как можно проще. Если при настройке вы столкнулись с какими-либо трудностями, пожалуйста, обратитесь к FAQ по устранению неполадок gophernote.

Я приведу три основных метода установки, хотя сам рекомендовал бы подход с *Docker*, поскольку он не зависит от операционной системы, то есть сможет работать на любой операционной системе.

1. Докер (рекомендация)

Вот основная команда:

$ docker run -it -p 8888:8888 -v /path/to/local/notebooks:/path/to/notebooks/in/docker gopherdata/gophernotes:latest-ds

Тег latest-ds указывает докеру, чтобы он извлёк версию пакета gophernotes, где уже установленные библиотеки Data Science, такие как GoNum, GoLearn и GoDa. Команда на вашей машине может выглядеть так:

$ docker run -it -p 8888:8888 -v /home/user/Documents/notebook:/notebook gopherdata/gophernotes:latest-ds

Затем вам будут предоставлены URL-адрес локального хоста подключённого блокнота и соответствующий ему токен. Скопируйте и вставьте его в свой браузер (например localhost:8888/?token=<your_given_token>).

Успешное монтирование Notebok в Docker (изображение от автора)Успешное монтирование Notebok в Docker (изображение от автора)

Кроме того, вы сможете увидеть папку notebook, которую указали при инициализации контейнера docker в браузере.

Папка блокнота, которую вы указали, когда инициализировали Docker (изображение от автора)Папка блокнота, которую вы указали, когда инициализировали Docker (изображение от автора)

Следуйте приведённым ниже инструкциям, если предпочитаете локальную установку. Однако они работают только для Linux и Mac. Машины с Windows в настоящее время не поддерживаются, и вы должны использовать вышеупомянутый метод Docker.

2. Linux

Вот команды локальной установки для Linux:

$ env GO111MODULE=on go get github.com/gopherdata/gophernotes$ mkdir -p ~/.local/share/jupyter/kernels/gophernotes$ cd ~/.local/share/jupyter/kernels/gophernotes$ cp "$(go env GOPATH)"/pkg/mod/github.com/gopherdata/gophernotes@v0.7.2/kernel/*  "."$ chmod +w ./kernel.json # in case copied kernel.json has no write permission$ sed "s|gophernotes|$(go env GOPATH)/bin/gophernotes|" < kernel.json.in > kernel.json

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

$ "$(go env GOPATH)"/bin/gophernotes

И вы сможете открыть блокнот этой командой:

$ jupyter --data-dir

3. Mac

Аналогично локальную установку для Mac можно выполнить, написав в терминале следующие команды:

$ env GO111MODULE=on go get github.com/gopherdata/gophernotes$ mkdir -p ~/Library/Jupyter/kernels/gophernotes$ cd ~/Library/Jupyter/kernels/gophernotes$ cp "$(go env GOPATH)"/pkg/mod/github.com/gopherdata/gophernotes@v0.7.2/kernel/*  "."$ chmod +w ./kernel.json # in case copied kernel.json has no write permission$ sed "s|gophernotes|$(go env GOPATH)/bin/gophernotes|" < kernel.json.in > kernel.json

Вы можете подтвердить, что ваша локальная установка прошла успешно, выполнив команду ниже. Успешную установку также подтверждает отсутствие сообщений об ошибках.

$ "$(go env GOPATH)"/bin/gophernotes

Теперь можно открыть блокнот этой командой:

$ jupyter --data-dir

Фух, переварить такое довольно трудно. Переходим к частям веселее!

Запуск Jupyter Notebook

Теперь, когда вы настроили Gophernotes, перейдите в папку, где хотите хранить свои блокноты Golang, там мы создадим наш первый блокнот! В правом верхнем углу вы увидите новую кнопку. Нажмите на неё и выберите "Go" в качестве ядра блокнота.

Создание первого блокнота Go в Jupyter (изображение от автора)Создание первого блокнота Go в Jupyter (изображение от автора)

Как только вы сделаете это, вас встретит знакомый чистый блокнот Jupyter. Теперь первым делом нужно изменить название на My First Golang Notebook (или любое другое, как показано ниже):

Изменение названия блокнотаИзменение названия блокнота

Давайте напишем какую-нибудь простую программу в наш Golang Notebook.

Рекурсивный факториал

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

Импорт пакетов в блокнот GoИмпорт пакетов в блокнот Go

Теперь напишем рекурсивный факториал. Факториал числа n это произведение всех положительных целых чисел, меньших или равных n. Например 3!, то есть факториал числа 3, это 3 x 2 x 1 = 6. Записать функцию вычисления факториала можно в одну из ячеек Jupyter Notebook:

Рекурсивный факториал на GoРекурсивный факториал на Go

Осталось только запустить программу, которая распечатывает значение, вот так:

Вызов рекурсивной функции вычисления факториала и печать значенияВызов рекурсивной функции вычисления факториала и печать значения

Бонус

Мощь Jupyter Notebook в возможности аннотирования и комментирования без загромождения кодовой базы. Воспользоваться этими возможностями можно, изменив тип ячейки на markdown, то есть выделить ячейку, нажать ctrl+M и ввести соответствующие примечания.

Комментирование и аннотирование кодовой базы (изображение от автора)Комментирование и аннотирование кодовой базы (изображение от автора)

Заключение

Вот и всё. Теперь вы можете создать прототип приложения Golang и отслеживать его с помощью блокнота Jupyter. Вы можете поделиться этим блокнотом со своими друзьями или коллегами в целях документирования (или если вы хотите помочь новым разработчикам выполнить логический шаг, который выполняете, когда пишете код самостоятельно). Ссылка на репозиторий gophernotes.

А если вам не хочется ограничиваться столь простыми программами на Go, обратите внимание на специальный курс Backend-разработчик на Go. У всех поступивших на курс появляется поддержка в виде экспертов, готовых ответить на вопросы и пояснить нюансы языка. Хотите дополнить свой арсенал навыков умением кодить на GO добро пожаловать. А по ссылке можно ознакомиться с программой курса.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Перевод Как протестировать блокноты Jupyter с помощью pytest и nbmake

20.05.2021 16:23:11 | Автор: admin

Файлы блокнотов Jupyter, в смысле количества одного из самых быстрорастущих типов файлов на Github, предоставляют простой интерфейс для итераций при решении визуальных задач, будь то анализ наборов данных или написание документов с большим объёмом кода. Однако популярность блокнотов Jupyter сопровождается проблемами: в репозитории накапливается большое количество файлов ipynb, многие из которых находятся в нерабочем состоянии. В результате другим людям трудно повторно запустить или даже понять ваши блокноты. В этом руководстве рассказывается, как для автоматизации сквозного (end-to-end) тестирования блокнотов можно воспользоваться плагином pytest nbmake.

К старту флагманского курса о Data Science области, в которой блокноты Jupyter незаменимы делимся переводом статьи из блога CI Semaphore о том, как при помощи Semaphore проверить, что ваши блокноты Jupyter находятся в рабочем состоянии и для этого больше не запускать блокноты вручную.


Предварительные требования

Это руководство предполагает владение основными навыками тестирования проектов на Python, о них рассказывается в статьеНепрерывная интеграция и развёртывание на Python с нуля. Прежде чем продолжить, пожалуйста, убедитесь, что вы знакомы с основами и на вашем компьютере установлен набор инструментов Python 3, таких как pip + virtualenv.

Демонстрационное приложение

Обычно проекты Python содержат папки с файлами блокнотов с расширением .ipynb, это может быть:

  • код доказательства концепции;

  • документация с примерами применения API;

  • длинный научный туториал.

В этой статье мы разберём, как автоматизировать простые сквозные тесты некоторых блокнотов, содержащих упражнения на Python. Благодарим pytudes за предоставленные для нашего примера материалы; воспользуемся этим примером проекта, не стесняйтесь делать форк и клонировать его с GitHub:

fig:fig:

Внутри репозитория вы найдёте содержащую блокноты папку, она называется ipynb. Установите зависимости из requirements.txt, а при необходимости сначала создайте виртуальную среду.

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

Локальное тестирование блокнота

Вполне вероятно, что до этого момента при тестировании вы запускали блокноты вручную через пользовательский интерфейс Jupyter Lab или аналогичный клиент. Однако этот подход отнимает много времени и подвержен ошибкам.

Давайте начнём автоматизировать этот процесс в вашей среде разработки; первым шагом в автоматизации станет nbmake. Nbmake это пакет python и плагин к pytest. Он разработан автором этого руководства и используется известными научными организациями, такими как Dask, Quansight и Kitware. Вы можете установить nbmake с помощью pip:

pip install nbmake==0.5

Перед первым тестированием блокнотов давайте проверим, всё ли правильно настроено, указав pytest просто собрать (но не запускать) все тест-кейсы для блокнотов.

 pytest --collect-only --nbmake "./ipynb" ================================ test session starts =================================platform darwin -- Python 3.8.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1rootdir: /Users/a/git/alex-treebeard/semaphore-demo-python-jupyter-notebooksplugins: nbmake-0.5collected 3 items           ============================= 3 tests collected in 0.01s =============================

Мы видим, что pytest собрал несколько элементов.

Примечание: если вы получаете сообщение unrecognized arguments: --nbmake, оно означает, что плагин nbmake не установлен. Такое может произойти, если ваш CLI вызывает двоичный файл pytest за пределами текущей виртуальной среды. Проверьте, где находится ваш двоичный файл pytest, с помощью команды which pytest.

Теперь, когда мы проверили, что nbmake и pytest видят ваши блокноты и работают вместе, давайте выполним настоящие тесты:

 pytest --nbmake "./ipynb"================================ test session starts =================================platform darwin -- Python 3.8.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1rootdir: /Users/a/git/alex-treebeard/semaphore-demo-python-jupyter-notebooksplugins: nbmake-0.5collected 3 items                                                                    ipynb/Boggle.ipynb .                                                           [ 33%]ipynb/Cheryl-and-Eve.ipynb .                                                   [ 66%]ipynb/Differentiation.ipynb .                                                  [100%]================================= 3 passed in 37.65s =================================

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

Игнорирование ожидаемых ошибок в блокноте

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

К счастью, чтобы указать nbmake, что такие блокноты нужно игнорировать и продолжать работу после возникновения ошибок, в метаданные нашего блокнота мы можем поместить директивы. Возможно, вы раньше не использовали метаданные блокнота, поэтому самое время упомянуть, что, несмотря на их расширение ipynb, блокноты это просто JSON-файлы. Чтобы добиться от них нового поведения, добавьте новые поля:

  • откройте файл .ipynb в простом текстовом редакторе, например Sublime;

  • в метаданных блокнота найдите поле kernelspec;

  • добавьте объект execution в качестве родственного элемента kernelspec.

Должно получиться что-то вроде этого:

{  "cells": [ ... ],  "metadata": {    "kernelspec": { ... },    "execution": {      "allow_errors": true,      "timeout": 300    }  }}

Теперь можно перезапустить блокнот, чтобы проверить, что JSON валиден, а ошибки игнорируются.

Ускорение тестов с помощью pytest-xdist

В крупных проектах может быть много блокнотов, каждый из которых требует много времени на установку пакетов, извлечение данных из сети и выполнение анализа. Если ваши тесты занимают более нескольких секунд, стоит проверить, как на время выполнения повлияет распараллеливание. Сделать это можно с помощью другого плагина pytest pytest-xdist. Сначала установите пакет xdist. Это похожий на nbmake плагин pytest, он добавит новые параметры командной строки:

pip install pytest-xdist

Запустите команду ниже с количеством рабочих процессов, равным auto:

pytest --nbmake -n=auto "./ipynb"

Запись выполненных блокнотов обратно в репозиторий

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

  • отладить неработающие блокноты путём просмотра выходных данных;

  • создать коммит в вашем репозитории с блокнотами в воспроизводимом состоянии;

  • встроить выполненные блокноты в сайт с документацией при помощи nbsphinx или jupyter book.

Мы можем направить nbmake на сохранение выполненных блокнотов на диск с помощью флага overwrite:

pytest --nbmake --overwrite "./ipynb"

Исключение блокнотов из тестов

Некоторые блокноты может быть трудно протестировать автоматически из-за необходимости ввода данных через stdin самим пользователем или длительного времени запуска. Чтобы отменить выбор, воспользуйтесь флагом игнорирования pytest:

pytest --nbmake docs --overwrite --ignore=docs/landing-page.ipynb

Примечание: это не сработает, если вы выбираете все блокноты с помощью шаблона поиска, например (*ipynb), вручную переопределяющего флаги игнорирования pytest.

Автоматизация тестирования блокнотов на Semaphore CI

С помощью вышеописанных методов можно создать автоматизированный процесс тестирования с использованием непрерывной интеграции (CI) Semaphore. Ключ здесь прагматизм: нужно найти способ протестировать большую часть ваших блокнотов и проигнорировать сложные блокноты; это хороший шаг к улучшению качества.

Начните с создания содержащего ваши блокноты проекта для репозитория. Выберите Choose repository:

Затем подключите Semaphore к репозиторию с вашими блокнотами:

Пока пропустите Add People, чтобы мы могли настроить рабочий процесс с нуля (даже если вы работаете с деморепозиторием). Semaphore предоставляет некоторую начальную конфигурацию, настроим её перед запуском:

Создайте простой рабочий процесс в один блок; ниже о процессе в деталях:

  1. Названием блока будет Test.

  2. Названием задачи Test Notebooks.

  3. В поле Commands введите команды ниже:

checkoutcache restorepip install -r requirements.txtpip install nbmake pytest-xdistcache storepytest --nbmake -n=auto ./ipynb

Чтобы лучше понять происходящее, давайте разберёмся, что делает каждая команда:

  1. checkout клонирует репозиторий с GitHub;

  2. cache restore загружает зависимости Python из кэша Semaphore. Это ускоряет процесс установки;

  3. pip устанавливает зависимости и инструменты;

  4. cache store сохраняет загруженные зависимости обратно в кэш;

  5. pytest запускает тесты nbmake.

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

Конвейер должен заработать сразу же:

Устранение некоторых распространённых ошибок

Добавление отсутствующего ядра Jupyter в среду CI

Если вы используете имя ядра, отличное от имени по умолчанию python3, то при выполнении ноутбуков в свежей среде CI увидите сообщение об ошибке: Error - No such kernel: 'mycustomkernel'. Чтобы установить пользовательское ядро, воспользуйтесь командой ipykernel:

python -m ipykernel install --user --name mycustomkernel

Если в своих блокнотах вы работаете с другим языком, например С++, ваш процесс установки ядра может быть другим.

Добавление недостающих секретов в среду CI

В некоторых случаях ваш блокнот получает данные от API, требующих токена или аутентифицированного CLI. Всё, что работает в вашей среде разработки, должно работать и на Semaphore. Чтобы увидеть, как настроить секреты, сначала посмотрите этот пост. Как только вы установили секреты в Semaphore, чтобы прочитать их из переменной среды в средах CI, вам может потребоваться настроить блокнот.

Добавление недостающих зависимостей в среду CI

Стек Data Science на Python часто требует установки собственных библиотек. Если вы работаете с CONDA, вполне вероятно, что они будут присутствовать в вашем нормальном процессе установки. Если же вы используете стандартные библиотеки Python, их присутствие немного менее вероятно.

Если вы пытаетесь установить необходимые вам библиотеки, посмотрите на статью о создании образа docker в CI. С ним тестирование упрощается, и по сравнению со стандартными средами Semaphore этот подход стабильнее во времени.

Пожалуйста, помните совет о прагматизме; 90 % полезности часто можно достичь за 10 % времени. Следование совету может привести к компромиссам, таким как игнорирование блокнотов, на которых запускаются требующие GPU модели ML.

Заключение

В этом руководстве показано, как можно автоматизировать тестирование блокнотов Jupyter, чтобы поддерживать их воспроизводимость. Блокноты оказались эффективным инструментом для построения технических нарративов. Однако из-за новизны некоторые команды разработчиков программного обеспечения исключают их использование до тех пор, пока не появятся более чёткие процессы для тестирования, ревью и повторного использования их содержимого.

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

  • для удаления громоздких выходных данных блокнота перед коммитом запустите pre-commit и nbstripout;

  • для компиляции ваших блокнотов в красивый сайт с документацией используйте jupyter book;

  • для просмотра блокнотов в пул-реквестах используйте ReviewNB.

Действительно, процессы в разработке ПО и в научных исследованих всё дальше уходят с локальных компьютеров на арендуемые мощности самого разного рода, в том числе потому, что требуют всё больших ресурсов. То же касается и растущих объёмов данных, работа с которыми преобразилась в несколько смежных, но очень разных областей; среди них центральное место занимает Data Science. Если вам интересна эта сфера, вы можете присмотреться к нашему курсу Data Science, итог которого это 16 проектов, то есть 2-3 года постоянного самостоятельного изучения науки о данных.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Jupyter в Visual Studio Code июньский релиз

17.06.2021 10:17:44 | Автор: admin

Мы рады сообщить, что стал доступен июньский релиз расширения Jupyter для Visual Studio Code. Если вы работаете с Python, мы рекомендуем загрузить расширение Python из Marketplace или установить его прямо из галереи расширений в Visual Studio Code. Если у вас уже установлено расширение Python, вы также можете получить последнее обновление, перезапустив Visual Studio Code. Узнайте больше о поддержке Python в Visual Studio Code в документации.

В этом релизе основное внимание уделялось:

  • Усилению мер безопасности

  • Дополнительным настройкам макета Native Notebook

  • Улучшению средств Data Viewer и Variable Explorer

Если вам интересно, вы можете изучить полный список улучшений в нашем журнале изменений.

Подробнее о самых интересных новинках под катом.

Workspace Trust

Команда Visual Studio Code серьезно относится к безопасности. Функция Workspace Trust позволяет определить, каким папкам и содержимому проекта вы доверяете, а какие остаются в ограниченном режиме.

Что это значит для notebooks?

При открытии папки в VS Code вас спросят, доверяете ли вы авторам и содержимому папок.

Если вы доверяете папке, любые notebooks и их выходные данные будут отображаться и смогут запускать код. Если вы не доверяете папке с notebooks в них, вы будете в ограниченном режиме. Если вы хотите сменить режим с ограничениями на доверенный, щелкните шестеренку в левом нижнем углу и выберите Управление доверием рабочей области.

** Примечание. Важно понимать, что notebooks могут содержать вредоносный код, встроенный в их выходные данные, и могут работать без выполнения вами ячеек. VS Code будет подавлять выходные данные до тех пор, пока вы явно не доверяете записной книжке. Важно определить, доверяете ли вы notebooks, загруженным из внешних источников.

Дополнительные сведения и сведения о доверии рабочей области см. в разделе Visual Studio Code - доверие рабочей области.

Улучшенная фильтрация в средстве просмотра данных

Средство просмотра данных теперь имеет улучшенные возможности для фильтрации строковых значений с добавленной поддержкой символа подстановки *. В приведенном ниже примере столбец, по которому выполняется фильтрация, содержит записи мужской и женский. Поиск значений мужской теперь будет возвращать только точные совпадения. Чтобы найти все значения, оканчивающиеся на мужской, просто введите * мужской в поле фильтра. Подстановочный знак * соответствует любому количеству символов и может использоваться в любом месте строки.

Сортировка в проводнике переменных

По многочисленным просьбам переменные в вашем notebook теперь можно сортировать. Щелкните заголовок Имя или Тип в проводнике переменных для сортировки. Направление стрелки в заголовке будет указывать, по какому заголовку сортируются переменные и в каком порядке выполняется сортировка: в алфавитном или обратном алфавитном порядке.

Новый взгляд на нативные notebooks

Чтобы опробовать нативные notebooks сегодня, загрузите VS Code Insiders и расширение Jupyter. Расширение Python настоятельно рекомендуется для работы с записными книжками Python.

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

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

  • Индикатор ячейки выделился жирным шрифтом. Вы можете найти этот индикатор более полезным, чтобы определить, где вы находитесь в записной книжке, особенно при работе с длинными ячейками и выводами.

Кастомизируемые нативные notebooks

Хотя приведенные выше основные моменты показывают, что уже работает в notebooks из коробки, вы всегда можете настроить все по своему вкусу. Мы добавили ряд настроек, чтобы по-настоящему сделать ваш notebook идеальным. Чтобы изучить настройки макета notebook, щелкните значок Дополнительные действия в конце панели инструментов и выберите Настроить макет notebook.

Вы попадете на страницу настроек со всеми настройками макета, относящимися к notebook, где вы сможете создать идеальный макет, который вам больше нравится.

Полный список настроек компоновки notebook:

  • notebook.insertToolbarLocation

  • notebook.consolidatedRunButton

  • notebook.cellFocusIndicator

  • notebook.cellToolbarVisibility

  • notebook.compactView

  • notebook.consolidatedOutputButton

  • notebook.dragAndDropEnabled

  • notebook.globalToolbar

  • notebook.showCellStatusBar

  • notebook.showFoldingControls

  • notebook.editorOptionsCustomizations

Прочие изменения и улучшения

Мы также добавили небольшие улучшения и исправили проблемы, запрошенные пользователями, которые должны улучшить ваш опыт работы с notebooks в Visual Studio Code. Некоторые заметные изменения включают:

  • Ограничение количества языков, отображаемых в средстве выбора языка ячейки, языками, поддерживаемыми ядром

  • Добавление ABCMeta и ввод в список исключений проводника переменных

  • Настройка размера и вида переменных в соответствии с VS Code

  • Скрытие ядер, принадлежащих удаленным средам Python, из средства выбора ядра

Загрузите расширение Python и расширение Jupyter для Visual Studio Code сейчас, чтобы опробовать вышеуказанные улучшения. Если у вас возникнут какие-либо проблемы или у вас есть предложения, сообщите о проблеме на странице Jupyter VS Code GitHub.

Подробнее..

Интерактивная визуализация алгоритмов на базе Jupyter

29.08.2020 20:22:07 | Автор: admin
Jupyter уже давно зарекомендовал себя как удобную платформу для работы в различных областях на стыке программирования, анализа данных, машинного обучения, математики и других. Вот например очень известная книга по анализу данных, состоящая из Jupyter блокнотов. Поддержка $\TeX$, markdown, html дает возможность использовать использовать Jupyter в качестве платформы для удобного оформления научного-технического материала. Преимущество таких блокнотов заключается в интерактивности, возможности сопровождать сухой материал примерами программ, при этом эта интерактивность очень естественна и проста в использовании. В этой статье хотелось бы рассказать про возможность создания в Jupyter анимированных примеров работы различных алгоритмов и привести несколько из них с исходным кодом. В качестве кликбейта алгоритм Дейкстры




Предисловие


Все примеры, которые будут представлены в статье, можно найти вот в этом ноутбуке, основной материал будет спрятан под спойлерами из-за того, что кода и gif довольно много. Чтобы воспроизвести часть примеров, которые будут представлены в любом случае понадобится этот репозиторий из-за того, что он содержит некоторые промежуточные утилиты.

Чем анимируем


Под Jupyter есть набор виджетов (ipywidgets), которые представляю собой различного рода инструменты управления, взаимодействуя с модулем IPython.display предоставляют интерактувную визуализацию. Следующий код представляет все ключевое взаимодействие с виджетами, с помощью которого можно сделать интерактвную анимацию на содержимом списка
from ipywidgets import interact, interactive, fixed, interact_manualimport ipywidgets as widgetsfrom IPython.display import displaydef step_slice(lst, step):    return lst[step]def animate_list(lst, play=False, interval=200):    slider = widgets.IntSlider(min=0, max=len(lst) - 1, step=1, value=0)    if play:        play_widjet = widgets.Play(interval=interval)        widgets.jslink((play_widjet, 'value'), (slider, 'value'))        display(play_widjet)        # slider = widgets.Box([play_widject, slider])    return interact(step_slice,                    lst=fixed(lst),                    step=slider)


Вот что получится, если подать функции animate_list список из целых чисел
a = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]animate_list(a, play=True, interval=200);


Чтобы продемонстрировать работу какого-либо алгоритма с помощью animate_list нужно сгенерировать промежуточные состояния алгоритма и сохранить их визуальное представление в нужном формате.

Тектовые анимации


Базовые алгоритмы для работы с последовательностями/массивами достаточно текстового представления. У меня к сожалению были проблемы с базовыми строками, которые отказывались обрабатывать перевод строки, в результате я использовал IPython.display.Code. Начнем с классической быстрой сортировки.

Код
from IPython.display import Codeimport randomdef qsort_state(array, left, right, x, p, q):    extended_array = list(map(str, array[:left])) + ['['] + list(map(str, array[left: right])) + [']'] + list(map(str, array[right:]))    offset_x = sum(list(map(len, extended_array[:left]))) + left + 2    zero_line = ''.join([' ' for i in range(offset_x)]) + f'x = {x}'    first_line = ' '.join(extended_array)    offset_p = sum(list(map(len, extended_array[:p + 1]))) + p + 1 + len(extended_array[p + 1]) // 2    offset_q = sum(list(map(len, extended_array[:q + 1]))) + q + 1 + len(extended_array[q + 1]) // 2    second_line = ''.join([' ' if i != offset_p and i != offset_q else '' for i in range(len(first_line))])    return Code(zero_line + '\n' + first_line + '\n' + second_line)def qsort(array, left, right, states):    if right - left <= 1:        return    x = array[random.randint(left, right - 1)]    p = left    q = right - 1    states.append(qsort_state(array, left, right, x, p, q))    while p <= q:        while array[p] < x:            p += 1            states.append(qsort_state(array, left, right, x, p, q))        while array[q] > x:            q -= 1            states.append(qsort_state(array, left, right, x, p, q))        if p <= q:            array[p], array[q] = (array[q], array[p])            states.append(qsort_state(array, left, right, x, p, q))            p += 1            q -= 1            if p <= q:                states.append(qsort_state(array, left, right, x, p, q))    qsort(array, left, q + 1, states)    qsort(array, p, right, states)  

a = [234, 1, 42, 3, 15, 3, 10, 9, 2]states = []qsort(a, 0, len(a), states)animate_list(states, play=True);


Результат


Похожим образом можно визуализировать и бинарный поиск
Код
def bs_state(array, left, right, x):    extended_array = list(map(str, array[:left])) + ['['] + list(map(str, array[left: right])) + [']'] + list(map(str, array[right:]))     mid = (left + right) // 2    offset_x = sum(list(map(len, extended_array[:mid + 1]))) + mid + 1    return Code(' '.join(extended_array) + '\n' + ''.join([' ' for i in range(offset_x)]) + str(x))# Эта версия бинарного поиска находит последний элемент, который# меньше или равен искомогоstates = []left = 0right = len(a)x = 14while right - left > 1:    states.append(bs_state(a, left, right, x))    mid = (left + right) // 2    if a[mid] <= x:        left = mid    else:        right = midstates.append(bs_state(a, left, right, x))animate_list(states, play=True, interval=400);


Результат


А вот пример для строк: процесс построения префикс-функции
Код
def prefix_function_state(s, p, k, intermidiate=False):    third_string = ''.join([s[i] if i < k else ' ' for i in range(len(p))])    fourth_string = ''.join([s[i] if i >= len(p) - k else ' ' for i in range(len(p))])    return Code(s + '\n' + ''.join(list(map(str, (p + ['*'] if intermidiate else p )))) \                  + '\n' + third_string + '\n' + fourth_string)def prefix_function(s, states):    p = [0]    k = 0    states.append(prefix_function_state(s, p, k))    for letter in s[1:]:        states.append(prefix_function_state(s, p, k, True))        while k > 0 and s[k] != letter:            k = p[k - 1]            states.append(prefix_function_state(s, p, k, True))        if s[k] == letter:            k += 1        p.append(k)        states.append(prefix_function_state(s, p, k))    return pstates = []p = prefix_function('ababadababa', states)animate_list(states, play=True);


Результат



Визуализация с использованием Matplotlib


Matplotlib библиотека Python для отрисовки различных графиков. Вот несколько примеров как можно её использовать для визуализации алгоритмов. Начнем с примера итеративных алгоритмов поиска минимума функции, наиболее простым из которых является метод случайного локального поиска, которые делает локальное изменение текущего приближения и переходит в него если значение значение функции в новой точки оказалось лучше
Код
import numpy as npimport matplotlib.pyplot as plt# Функция, которую минимизируем, минимум в точке (0, 0)def f(x, y):    return 1.3 * (x - y) ** 2 + 0.7 * (x + y) ** 2# Отрисовка траектории и линий уровня функцииdef plot_trajectory(func, traj, limit_point=None):    fig = plt.figure(figsize=(7, 7))    ax = fig.add_axes([0, 0, 1, 1])            if limit_point:        ax.plot([limit_point[0]], [limit_point[1]], 'o', color='green')    #Level contours    delta = 0.025    x = np.arange(-2, 2, delta)    y = np.arange(-2, 2, delta)    X, Y = np.meshgrid(x, y)    Z = np.zeros_like(X)    for i in range(X.shape[0]):        for j in range(X.shape[1]):            Z[i][j] = func(X[i][j], Y[i][j])    CS = ax.contour(X, Y, Z, [0.5, 1.5, 3], colors=['blue', 'purple', 'red'])    ax.plot([u[0] for u in traj], [u[1] for u in traj], color='black')    ax.plot([u[0] for u in traj], [u[1] for u in traj], 'o', color='black')        plt.close(fig)    return figx, y = (1.0, 1.0)num_iters = 50trajectory = [(x, y)]plots = []# Итерируемся и сохраняем текущий путь на каждом шагеfor i in range(num_iters):    angle = 2 * np.pi * np.random.rand(1)    dx, dy = (np.cos(angle) / 2 / (i + 1) ** 0.5, np.sin(angle) / 2 / (i + 1) ** 0.5)    trajectory.append((x + dx, y + dy))    plots.append(plot_trajectory(f, trajectory, limit_point=(0, 0)))    if f(x, y) > f(x + dx, y + dy):        x = x + dx        y = y + dy    else:        trajectory = trajectory[:-1]animate_list(plots, play=True, interval=300);


Результат


А вот пример ЕМ алгоритма для данных извержений Old Faithful гейзера, такой же пример приведен на википедии
Код
# Данные можно взять например здесь# http://www.stat.cmu.edu/~larry/all-of-statistics/=data/faithful.datdata = []with open('data/faithful.csv') as f:    for line in f:        _, x, y = line.split(',')        try:            data.append((float(x), float(y)))        except ValueError:            passcolors = ['red', 'blue', 'yellow', 'green']# За основу взято https://jakevdp.github.io/PythonDataScienceHandbook/05.12-gaussian-mixtures.htmlfrom matplotlib.patches import Ellipsedef draw_ellipse(position, covariance, ax=None, **kwargs):    """Draw an ellipse with a given position and covariance"""    ax = ax or plt.gca()        # Convert covariance to principal axes    if covariance.shape == (2, 2):        U, s, Vt = np.linalg.svd(covariance)        angle = np.degrees(np.arctan2(U[1, 0], U[0, 0]))        width, height = 2 * np.sqrt(s)    else:        angle = 0        width, height = 2 * np.sqrt(covariance)        # Draw the Ellipse    for nsig in range(1, 4):        ax.add_patch(Ellipse(position, nsig * width, nsig * height,                             angle, color='red', **kwargs))        def plot_gmm(gmm, X, label=True, ax=None):    ax = ax or plt.gca()    if label:        labels = gmm.predict(X)        ax.scatter(X[:, 0], X[:, 1], c=labels, s=20, cmap='plasma', zorder=2)    else:        ax.scatter(X[:, 0], X[:, 1], s=20, zorder=2)        w_factor = 0.2 / gmm.weights_.max()    for pos, covar, w in zip(gmm.means_, gmm.covariances_, gmm.weights_):        draw_ellipse(pos, covar, alpha=w * w_factor)        def step_figure(gmm, X, label=True):    fig = plt.figure(figsize=(7, 7))    ax = fig.add_axes([0, 0, 1, 1])    ax.set_ylim(30, 100)    ax.set_xlim(1, 6)    plot_gmm(gmm, X, label=True, ax=ax)    plt.close(fig)    return figfrom sklearn.mixture import GaussianMixturex = np.array(data)# max_iters=1 и warm_start=True заставляют gmm.fit делать ровно одну итерацию# и сохранять состояниеgmm = GaussianMixture(2, warm_start=True, init_params='random', max_iter=1)# GMM выдает предупреждения на то, что одной итерации малоimport warnings warnings.simplefilter('ignore')# Инициализируем на небольшой порции данныхgmm.fit(x[:10,:])steps = [step_figure(gmm, x)]   for i in range(17):    gmm.fit(x)    steps.append(step_figure(gmm, x))animate_list(steps, play=True, interval=400);


Результат


Следующий пример скорее игрушечный, но тоже показывает, что можно сделать в matplotlib: визуализация замощения клетчатой фигуры на плоскости максимальным числом доминошек с помощью нахождения максимального паросочетания
Код
# Обертка над matplotlib для отрисовки прямоугольника, разделенного на квадраты разных цветовfrom animation_utils.matplotlib import draw_fillingdef check_valid(i, j, n, m, tiling):    return 0 <= i and i < n and 0 <= j and j < m and tiling[i][j] != '#'def find_augmenting_path(x, y, n, m, visited, matched, tiling):    if not check_valid(x, y, n, m, tiling):        return False    if (x, y) in visited:        return False    visited.add((x, y))        for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:        if not check_valid(x + dx, y + dy, n, m, tiling):            continue        if (x + dx, y + dy) not in matched or find_augmenting_path(*matched[(x + dx , y + dy)], n, m, visited, matched, tiling):            matched[(x + dx, y + dy)] = (x, y)            return True    return Falsedef convert_match(matched, tiling, n, m):    result = [[-1 if tiling[i][j] == '#' else -2 for j in range(m)] for i in range(n)]    num = 0    for x, y in matched:        _x, _y = matched[(x, y)]        result[x][y] = num        result[_x][_y] = num        num += 1    return resultdef match_with_flow(tiling):    result_slices = []    n = len(tiling)    m = len(tiling[0])        matched = dict()    # Для наглядности визуализации    rows = list(range(n))    columns = list(range(m))    random.shuffle(rows)    random.shuffle(columns)    result_slices.append(convert_match(matched, tiling, n, m))                for i in rows:        for j in columns:            if (i + j) % 2 == 1:                continue            visited = set()            if find_augmenting_path(i, j, n, m, visited, matched, tiling):                result_slices.append(convert_match(matched, tiling, n, m))                return result_slicestiling_custom=[    '...####',    '....###',    '......#',    '#.#....',    '#......',    '##.....',    '###...#',]sequencial_match = match_with_flow(tiling_custom)animate_list(list(map(draw_filling, sequencial_match)), play=True);


Результат


Ну и попутно демонстрация алгоритма раскраски планарного графа в 5 цветов, чтобы визуально разбиение смотрелось лучше
Код
def color_5(filling):    result = [[i for i in row] for row in filling]    # Строим граф    domino_tiles = [[] for i in range(max(map(max, filling)) + 1)]    domino_neighbours = [set() for i in range(max(map(max, filling)) + 1)]    degree = [0 for i in range(max(map(max, filling)) + 1)]        n = len(filling)    m = len(filling[0])        for i, row in enumerate(filling):        for j, num in enumerate(row):            if num >= 0:                domino_tiles[num].append((i, j))                    for i, tiles in enumerate(domino_tiles):        for x, y in tiles:            for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (-1, 1), (1, -1), (1, 1)]:                a, b = x + dx, y + dy                if 0 <= a and a < n and 0 <= b and b < m and filling[a][b] >= 0 and filling[a][b] != i \                        and filling[a][b] not in domino_neighbours[i]:                    domino_neighbours[i].add(filling[a][b])                    degree[i] += 1        # Первым делом нужно найти такой порядок вершин, все вершины имели не больше 5 соседей среди    # предыдущих. Такой существует в силу того, что граф планарный, а найти его эффективнее всего    # по очереди находя вершину наименшей степени и удаляя её из графа, так мы получем обратный порядок    active_degrees = [set() for i in range(max(degree) + 1)]    for i, deg in enumerate(degree):        active_degrees[deg].add(i)        reversed_order = []        for step in range(len(domino_tiles)):        min_degree = min([i for i, dominoes in enumerate(active_degrees) if len(dominoes) > 0])        domino = active_degrees[min_degree].pop()                reversed_order.append(domino)        for other in domino_neighbours[domino]:            if other in active_degrees[degree[other]]:                active_degrees[degree[other]].remove(other)                degree[other] -= 1                active_degrees[degree[other]].add(other)                            # Теперь перебираем в обратном порядке и либо красим в еще не занятый цвет,    # если есть свободный из 5 цветов, иначе находим цепочку из чередующихся цветов,    # которые могут быть перекрашены. Такая найдется в силу планарности    colors = [-1 for domino in domino_tiles]    slices = [draw_filling(result)]    for domino in reversed(reversed_order):        used_colors = [colors[other] for other in domino_neighbours[domino] if colors[other] != -1]                domino_color = len(used_colors)        for i, color in enumerate(sorted(set(used_colors))):            if i != color:                domino_color = i                break             if domino_color < 5:            colors[domino] = domino_color            for x, y in domino_tiles[domino]:                result[x][y] = domino_color                            slices.append(draw_filling(result))                    continue                   # Начиная отсюда код я не тестировал, не нашел примера                c = 0        other = [other for other in domino_neighbours[domino] if colors[other] == c]        visited = set([other])        q = Queue()        q.put(other)        domino_was_reached = False        while not q.empty():            cur = q.get()            for other in domino_neighbours[cur]:                if other == domino:                    domino_was_reached = True                    break                if color[other] == c or color[other] == c + 1 and other not in visited:                    visited.add(other)                    q.put(other)                            if not domino_was_reached:            for other in visited:                color[other] = color[other] ^ 1                for x, y in domino_tiles[other]:                    result[x][y] = color[other]            color[domino] = c            for x, y in domino_tiles[domino]:                result[x][y] = c                            slices.append(draw_filling(result))            continue                    # Проделываем тоже самое для 2 и 3.        c = 2        other = [other for other in domino_neighbours[domino] if colors[other] == c]        visited = set([other])        q = Queue()        q.put(other)        domino_was_reached = False        while not q.empty():            cur = q.get()            for other in domino_neighbours[cur]:                if other == domino:                    domino_was_reached = True                    break                if color[other] == c or color[other] == c + 1 and other not in visited:                    visited.add(other)                    q.put(other)        for other in visited:            color[other] = color[other] ^ 1            for x, y in domino_tiles[other]:                result[x][y] = color[other]        color[domino] = c        for x, y in domino_tiles[domino]:            result[x][y] = c        slices.append(draw_filling(result))                        return result, slicesfilling_colored, slices =color_5(sequencial_match[-1])animate_list(slices, play=True);


Результат


Последний пример с matplotlib из вычислительной геометрии алгоритм Грэхэма-Эндрю для построения выпуклой оболочки на плоскости
Код
def convex_hull_state(points, lower_path, upper_path):    fig = plt.figure(figsize=(6, 6))    ax = fig.add_axes([0, 0, 1, 1])        ax.get_xaxis().set_visible(False)    ax.get_yaxis().set_visible(False)    for name, spine in ax.spines.items():        spine.set_visible(False)        spine.set_visible(False)        ax.scatter([x for x, y in points], [y for x, y in points])    ax.plot([x for x, _ in lower_path], [y for _, y in lower_path], color='red')    ax.plot([x for x, _ in upper_path], [y for _, y in upper_path], color='blue')        plt.close(fig)    return figdef vector_prod(point_a, point_b):    return point_a[0] *  point_b[1] - point_a[1] * point_b[0]def convex_hull(poitns):    sorted_points = sorted(points, key=lambda x: x[1])    sorted_points = sorted(sorted_points, key=lambda x: x[0])    states = []        upper_path = [sorted_points[0]]    lower_path = [sorted_points[0]]    states.append(convex_hull_state(points, lower_path, upper_path))        for point in sorted_points[1:]:        while len(upper_path) > 1 and vector_prod(point - upper_path[-1], upper_path[-1] - upper_path[-2]) > 0:            upper_path = upper_path[:-1]            upper_path.append(point)            states.append(convex_hull_state(poitns, lower_path, upper_path))            upper_path = upper_path[:-1]        upper_path.append(point)        states.append(convex_hull_state(points, lower_path, upper_path))            for point in sorted_points[1:]:        while len(lower_path) > 1 and vector_prod(point - lower_path[-1], lower_path[-1] - lower_path[-2]) < 0:            lower_path = lower_path[:-1]            lower_path.append(point)            states.append(convex_hull_state(poitns, lower_path, upper_path))            lower_path = lower_path[:-1]        lower_path.append(point)        states.append(convex_hull_state(poitns, lower_path, upper_path))        return statespoints = [np.random.rand(2) for i in range(20)]states = convex_hull(points)animate_list(states, play=True, interval=300);


Результат


Последнее, что хотелось бы отметить в контексте matplotlib это альтернативный способ создания анимаций через matplotlib.animation.FuncAnimation. У этого способа есть свои плюсы: его можно конвертировать в html с помощью IPython.display.HTML, результат будет более надежным, чем на виджетах (у меня виджеты периодически торомозят), не будет требовать рабочего ядра Jupyter, но в этом случае анимация стоновится обычным видео и средства управления ограничены проигрывателем.

Graphviz


С помощью graphviz можно отрисовывать графы. Обратите внимание, что для воспроизведения примеров с его помощью необходимо будет установить graphviz не только в python, но и в систему. Начнем с обхода в глубину
Код
# Обертка для упрощения работы с графамиfrom graph_utils.graph import Graph, Arc, Nodedef enter_node(node):    node.SetColor('blue')    def enter_arc(node, arc):    node.SetColor('green')    arc.attributes['style'] = 'dashed'    arc.attributes['color'] = 'green'    def return_from_arc(node, arc):    arc.attributes['style'] = 'solid'    arc.attributes['color'] = 'red'    node.SetColor('blue')    def ignore_arc(arc):    arc.attributes['color'] = 'blue'    def leave_node(node):    node.SetColor('red')    def dfs(graph, node_id, visited, outlist, path):    visited.add(node_id)    path.append(node_id)    enter_node(graph.nodes[node_id])    outlist.append(graph.Visualize())    for arc in graph.nodes[node_id].arcs:        if arc.end not in visited:            enter_arc(graph.nodes[node_id], arc)            dfs(graph, arc.end, visited, outlist, path)             return_from_arc(graph.nodes[node_id], arc)            path.append(node_id)        else:            ignore_arc(arc)        outlist.append(graph.Visualize())          leave_node(graph.nodes[node_id])arcs = [    Arc(1, 3, 3),    Arc(1, 4, 7),    Arc(4, 3, 2),    Arc(4, 5, 3),    Arc(1, 5, 2),    Arc(6, 4, 2),    Arc(5, 6, 2),    Arc(6, 7, 1),    Arc(7, 2, 7),    Arc(4, 2, 2),    Arc(3, 2, 5)]# Если следующий код выдает ошибку, что ему не удается выполнить `dot`, то# скорее всего придется отдельно поставить graphviz# https://graphviz.org/download/graph = Graph(arcs)visited = set()dfs_outlist = []path = []dfs_outlist.append(graph.Visualize())dfs(graph, 1, visited, dfs_outlist, path)dfs_outlist.append(graph.Visualize())animate_list(dfs_outlist, play=True, interval=400);


Результат


Ну а вот и алгоритм Дейкстры из заголовка
Код
def mark_labelled(node):    node.SetColor('red')    def mark_scanned(node):    node.SetColor('green')    def process_node(node):    node.SetColor('blue')    def set_previous(arc):    arc.SetColor('green')    def unset_previous(arc):    arc.SetColor('black')def scan_arc(graph, arc, l, p, mark):    if l[arc.end] > l[arc.beginning] + arc.weight:        l[arc.end] = l[arc.beginning] + arc.weight        if p[arc.end] is not None:            unset_previous(p[arc.end])        # Сохраняем arc, а не arc.beginning, чтобы было больше информации        p[arc.end] = arc        set_previous(p[arc.end])        mark[arc.end] = True        mark_labelled(graph.nodes[arc.end])def scan_node(graph, node_id, l, p, mark):    for arc in graph.nodes[node_id].arcs:        scan_arc(graph, arc, l, p, mark)    mark[node_id] = False    mark_scanned(graph.nodes[node_id])             # Это не традиционная реализация алгоритма Дейкстры, а реализация# сканирующего метода, подробности смотрите тут# http://forskning.diku.dk/PATH05/GoldbergSlides.pdfdef base_scanning_method(graph, s, choice_function):    l    = {key: float('Inf') for key in graph.nodes.keys()}    p    = {key: None for key in graph.nodes.keys()}    mark = {key: False for key in graph.nodes.keys()}        l[s] = 0    mark[s] = True    mark_labelled(graph.nodes[s])        out_lst = []        while True:        node_id = choice_function(l, mark)        if node_id is None:            break        process_node(graph.nodes[node_id])        out_lst.append(graph.Visualize(l))        scan_node(graph, node_id, l, p, mark)        out_lst.append(graph.Visualize(l))    return l, p, out_lst# Функция выбора в алгоритме Дейкстрыdef least_distance_choice(l, mark):    labelled = [node_id for node_id, value in mark.items() if value == True]    if len(labelled) == 0:        return None    return min(labelled, key=lambda x: l[x]) graph = Graph(arcs)l, p, bfs_shortest_path_lst = \    base_scanning_method(graph, 1, least_distance_choice)animate_list(bfs_shortest_path_lst, play=True, interval=400);


Результат


А вот таким образом происходит построение префиксного дерева для слов 'мама', 'мать', 'мартышка', 'мыла', 'молоко'
Код
class TrieNode:    def __init__(self, parent, word=None):        # Хранение этого поля очень расточительно, используется исключительно        # для демонстрации ленивого подсчета суффиксных ссылок        self.parent = parent        # Здесь аналогично, это поле чаще всего избыточно        self.word = word        self.children = {}        self.suff_link = Nonedef init_trie():    trie = [TrieNode(-1)]    return triedef to_graph(trie):    arcs = []    for i, node in enumerate(trie):        for c, nextstate in node.children.items():            arcs.append(Arc(i, nextstate, c))        if node.suff_link is not None and node.suff_link != 0:            arcs.append(Arc(i,                             node.suff_link,                             attributes={"constraint" : "False", "style" : "dashed"}))                return Graph(arcs)def add_word(trie, word, steps):    _num = 0    for ch in word:        if not ch in trie[_num].children:            _n = len(trie)            trie[_num].children[ch] = _n            trie.append(TrieNode((_num, ch)))        _num = trie[_num].children[ch]        graph = to_graph(trie)        graph.nodes[_num].SetColor('red')        steps.append(graph.Visualize())    trie[_num].word = word    def make_trie(words):    steps = []    trie = init_trie()    steps.append(to_graph(trie).Visualize())    for word in words:        add_word(trie, word, steps)        steps.append(to_graph(trie).Visualize())    return trie, stepswords = [    'мама',    'мать',    'мартышка',    'мыла',    'молоко']trie, steps = make_trie(words)animate_list(steps, play=True, interval=500);


Результат


Ну и напоследок алгоритм Куна для нахождения максимального паросочетания
Код
def mark_for_delete(arc):    arc.SetColor('red')    arc.SetStyle('dashed')    def mark_for_add(arc):    arc.SetColor('blue')    def clear(arc):    arc.SetColor('black')    arc.SetStyle('solid')        def find_augmenting_path(graph, node_id, visited, match, deleted):    if node_id in visited:        return False    visited.add(node_id)    for arc in graph.nodes[node_id].arcs:        if arc.end not in match or find_augmenting_path(graph, match[arc.end].beginning, visited, match, deleted):            if arc.end in match:                mark_for_delete(match[arc.end])                deleted.append(match[arc.end])            match[arc.end] = arc            mark_for_add(arc)            return True    return Falsedef kuhns_matching(graph, first_part):    states = [graph.Visualize()]    match = dict()    for node_id in first_part:        node = graph.nodes[node_id]        node.SetColor('Blue')        states.append(graph.Visualize())        deleted = []        if find_augmenting_path(graph, node_id, set(), match, deleted):            states.append(graph.Visualize())            for arc in deleted:                clear(arc)            states.append(graph.Visualize())        node.SetColor('red')    states.append(graph.Visualize())    return statesarcs = [    Arc(1, 6),    Arc(1, 7),    Arc(2, 6),    Arc(3, 7),    Arc(3, 8),    Arc(4, 8),    Arc(4, 9),    Arc(4, 10),    Arc(5, 10),    Arc(2, 8)]first_part = [1, 2, 3, 4, 5]graph = Graph(arcs)states = kuhns_matching(graph, first_part)animate_list(states, play=True, interval=400);


Результат



Алгоритмы с матрицами


А вот эта часть относится к неудавшейся попытке. IPython.display умеет парсить latex, однако при попытки его использовать вот что у меня получилось (должен был быть метод Гаусса)
Код
from animation_utils.latex import Matrixfrom IPython.display import Mathn = 5A = np.random.rand(n, n)L = np.identity(n)U = np.array(A)steps = []steps.append(Math(str(Matrix(L)) + str(Matrix(U))))for k in range(n):    x = U[k,k]    for i in range(k+1, n):        L[i,k] = U[i,k] / x        U[i,k:] -= L[i,k] * U[k,k:]    steps.append(Math(str(Matrix(L)) + str(Matrix(U))))animate_list(steps, play=True, interval=500);


Результат


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

Мы скачали 10 миллионов Jupyter-ноутбуков с Github и вот что мы выяснили

17.12.2020 20:07:58 | Автор: admin

Привет, Хабр!

На связи команда Datalore by JetBrains. Хотим поделиться с вами результатами анализа нескольких миллионов публично доступных репозиториев Github с Jupyter-ноутбуками. Мы скачали ноутбуки, чтобы немного больше узнать в цифрах о текущем статусе, пожалуй, самого популярного инструмента для data science.

Вдохновившись исследованием, проведенным командой Design Lab из UC San Diego, мы дважды скачали Jupyter-ноутбуки: в октябре 2019 и в октябре 2020.

Два года назад в открытом доступе было 1,23 миллиона ноутбуков. В октябре 2020 года число ноутбуков выросло в 8 раз, и мы смогли скачать 9,72 миллиона файлов. Этот датасет мы сделали публичным инструкцию по скачиванию можно найти в конце поста.

Все цифры и графики были подсчитаны в этом Datalore ноутбуке. Datalore это онлайн Jupyter-ноутбуки с умной поддержкой кода, которые мы делаем в JetBrains. Вы можете создать копию ноутбука, перейдя по ссылке, и работать с данными в Datalore.

Мы будем рады, если вы захотите поработать с данными и провести собственный анализ. Делитесь с нами результатами, отмечая в Твиттере @JBDatalore или написав нам на contact@datalore.jetbrains.com.

Теперь перейдем к цифрам.

Язык data science

Несмотря на большой рост популярности R и Julia в последние годы, Python остается лидирующим программным языком для Jupyter-ноутбуков.

Помимо этого встречаются ноутбуки, написанные на Bash, MatLab и Scilab, а также на языках, с которыми ноутбуки ассоциируются, пожалуй, в последнюю очередь: Scala, C++ и Java.

На графике можно посмотреть распределение языков программирования в ноутбуках. Ноутбуки, язык которых не был указан в метаданных, относятся к категории nan.

В табличке можно увидеть разницу в процентах использования Python 2 и Python 3 в ноутбуках в 2018, 2019 и 2020 годах.

Python 2

Python 3

Other languages

Исследование 2018

52,5%

43,8%

3,7%

Исследование 2019 (JetBrains Datalore)

18,1% (всего 1029 K)

72,6% (всего 4128 K)

9,3% (всего 529 K)

Исследование 2020 (JetBrains Datalore)

11,8% (всего 1154 K, +125 K к 2019)

79,3% (всего 7710 K, +3582 K к 2019)

10,8% (всего 1050 K, +521 K к 2019)

Количество ноутбуков, написанных на Python 3, увеличилось с 2019 года на 87%, а количество ноутбуков с Python 2 на 12%.

На графике ниже можно увидеть распределение количества ноутбуков, написанных на Python и R, по версиям языков:

Топ библиотек data science

Чтобы помочь пользователям Datalore начать работу с ноутбуками как можно быстрее, мы предустановили самые используемые Python-библиотеки. Для этого мы посчитали статистику импортов в скачанных Jupyter-ноутбуках.

Не оказалось неожиданностью, что 60% ноутбуков содержат в списке зависимостей Numpy, 47% импортируют Pandas и Matplotlib.

Более подробную информацию можно увидеть на графике:

Самые популярные комбинации библиотек:

Рост PyTorch и TensorFlow

Члены нашей команды интересуются библиотеками для глубинного обучения, и мы решили сравнить рост библиотек PyTorch и TensorFlow.

Из таблицы ниже можно увидеть, что число импортов у PyTorch растет значительно быстрее, чем у TensorFlow.

В то же время нужно учитывать, что библиотека Keras может использовать TensorFlow в качестве транзитивной зависимости, а Fast.ai использует PyTorch в качестве зависимости. Это означает, что скорость роста TensorFlow, вероятно, выше, но мы не можем говорить с уверенностью, какая из библиотек больше использовалась в последние годы.

TensorFlow

Keras

PyTorch

Fastai

Исследование 2019 (JetBrains Datalore)

321 K

231 K

110 K

19 K

Исследование 2020

(JetBrains Datalore)

430 K (+34%)

367 K(+59%)

253 K(+130%)

25 K(+32%)

Содержание ячеек в ноутбуках

Немного общих цифр относительно ячеек (данные подсчитаны для ноутбуков, написанных на Python 3.6 и выше):

  • 71,90% ноутбуков содержат Markdown.

  • 42,13% ноутбуков содержат графики или картинки в output.

  • 12,34% ноутбуков содержат LaTex.

  • 19,77% ноутбуков содержат HTML.

  • 20,63% ноутбуков содержат код внутри Markdown.

Markdown очень широко используется в Jupyter-ноутбуках. 50% ноутбуков содержат более 4 ячеек Markdown и более 14 ячеек кода.

Графики ниже показывают распределения Markdown-ячеек и ячеек с кодом:

На графике ниже можно увидеть распределение количества строк кода. Хотя существуют отдельные экземпляры ноутбуков, имеющие более 25 000 строк кода, 95% ноутбуков содержат менее 465 строк:

Использование графиков тоже популярно. Выше мы упоминали, что 42% ноутбуков содержат визуализации. 10% этих ноутбуков содержат более 8 изображений.

Воспроизводимость Jupyter-ноутбуков

Для Jupyter-ноутбуков существует проблема не всегда готовый ноутбук можно воспроизвести. Зачастую это означает, что ячейки были выполнены автором не в прямой последовательности. Мы проверили порядок выполнения ячеек кода всех доступных Jupyter-ноутбуков и выяснили, что 36% Jupyter-ноутбуков выполнены в нелинейном порядке, т. е. при исполнении кода линейно результат выполнения может отличаться.

Мы стали смотреть детальнее, зависит ли порядок выполнения кода в ноутбуке от количества Markdown-ячеек и ячеек кода. Не удивительно, но оказалось, что, чем меньше ячеек кода в ноутбуке, тем меньше вероятность, что код выполнен в линейном порядке.

Количество Jupyter-ноутбуков невероятно выросло за последние годы, и в этом исследовании мы постарались побольше узнать об этом очень популярном инструменте работы над задачами data science.

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

Ссылки

Предыдущее исследование 2018 года

Ноутбук в Datalore с предобработанными данными

Инструкция по получению доступа к данным:

  1. Скачайте оригинальный датасет:

    1. Ссылка для скачивания исходных данных из бакета (10 млн файлов, 4,4 ТБ): https://github-notebooks-update1.s3-eu-west-1.amazonaws.com/

    2. Получение списка файлов c помощью AWS S3 API может занять время, поэтому воспользуйтесь этим JSON со всеми именами файлов: https://github-notebooks-samples.s3-eu-west-1.amazonaws.com/ntbslist.json

    3. Добавьте имя файла из JSON к адресу бакета, чтобы получить прямую ссылку, например: https://github-notebooks-update1.s3-eu-west-1.amazonaws.com/0000036466ae1fe8f89eada0a7e55faa1773e7ed.ipynb

  2. Или воспользуйтесь предобработанными данными из исследования (3 ГБ). Файлы доступны в этом Datalore-ноутбуке.

Подробнее..

Как изменился Datalore за 2020 год мощная онлайн-среда для Jupyter-ноутбуков

21.01.2021 18:22:37 | Автор: admin

Привет, Хабр!


Datalore это мощная онлайн-среда для Jupyter-ноутбуков, разработанная в JetBrains. Здесь мы собрали описание самых важных обновлений Datalore за прошедший год.


image


Профессиональный план Datalore


В ноябре мы запустили Datalore Professional. Этот план был разработан для решения сложных задач с большими наборами данных, которым требуется мощное вычислительное оборудование.


Вот сравнительная таблица параметров Datalore Community и Datalore Professional.


Community Professional
Базовый CPU-процессор (4 ГБ RAM, имя AWS: t3.medium) 120 часов
Мощный CPU-процессор (16 ГБ RAM, 2 ядра vCPUs, на 400% быстрее базового CPU-процессора, имя AWS: r5.large) 120 часов
GPU-процессор (1 NVIDIA T4 GPU, 16 ГБ RAM GPU, 4 ядра vCPU, имя AWS: g4dn.xlarge) - 20 часов
Хранилище 10 ГБ 20 ГБ
Цена Бесплатно 19,90$ в месяц

Улучшения в редакторе кода


Анализ кода из PyCharm


В начале года мы интегрировали анализ кода из PyCharm, добавив автодополнение кода, рефакторинги, быстрые исправления и навигацию в Datalore. Теперь вы можете пользоваться такой же первоклассной помощью при написании кода, как в PyCharm, в своих онлайновых Jupyter-ноутбуках.




Совместимость с ядром Jupyter


В прошлом году мы также много работали над стабильностью ядра IPython в Datalore, и сейчас оно стало намного надежнее. Еще есть над чем работать, так что мы продолжим делать ядро более стабильным в этом году.


Ядро Jupyter теперь полностью поддерживается вместе с виджетами, графическими библиотеками и shell-командами.




Кроме того, ноутбуки теперь загружаются значительно быстрее, так как нам удалось вдвое сократить время запуска Conda.


Поддержка Kotlin


Kotlin это язык программирования с открытым исходным кодом, разработанный в JetBrains. Он хорошо подходит для анализа данных и разработки мультиплатформенных приложений. В Datalore мы добавили поддержку Kotlin в ноутбуки IPython. Попробуйте! Просто выберите Kotlin в качестве языка при создании ноутбука.


Поддержка workspace-файлов и S3-бакетов


В декабре мы реализовали поддержку workspace-файлов, что позволяет обмениваться файлами данных между несколькими ноутбуками.


Для тех, кто работает с большим объемом данных, мы также добавили поддержку подключения S3-бакетов. Подробности читайте в этом блогпосте.


Улучшения пользовательского интерфейса


Боковая панель для быстрых действий


Чтобы вы могли быстрее работать с файлами и перемещаться по содержимому ноутбуков, мы добавили вкладку боковой панели внутри редактора. С этой панели есть прямой доступ к прикрепленным файлам, включая файлы ноутбука и workspace-файлы. Кроме того, вы можете использовать оглавление и обозреватель переменных. Окно быстрых команд (Shortcuts) также появится на боковой панели при открытии из меню Help.




Темный режим


В прошлом году мы представили темный режим. Вы можете изменить тему ноутбука в меню View в редакторе, где также можно включить режим Distraction free и опцию разделенного просмотра Split view.




Панель инструментов Markdown


Мы также представили панель инструментов для более удобного редактирования Markdown. Она помогает описывать код с помощью текста, формул LaTex и HTML-кода внутри ячеек Markdown.


Сотрудничество с Anaconda


У JetBrains долгая история сотрудничества с Anaconda, а PyCharm IDE для Python, рекомендованная в установщике Anaconda. С октября 2020 года и Datalore, и PyCharm представлены в новом Anaconda Navigator! Обновите Anaconda Navigator до последней версии и запускайте Datalore прямо оттуда.




Исследования и уроки:


В прошлом году мы сделали несколько интересных исследовательских и обучающих проектов:



Вот и все, что касается основных обновлений, представленных в 2020 году. Следите за нашим блогом, чтобы узнавать о новых возможностях Datalore.


Всем здорового и продуктивного года!


Команда Datalore

Подробнее..

Шпаргалка по Ansible k8s, практичный учебник по awk, а также 4 причины использовать Jamstack при веб-разработке

24.09.2020 14:19:46 | Автор: admin


Традиционно короткий дайджест полезных материалов, найденных нами в сети за последние две недели.

Начни новое:



Качай:


  • Шпаргалка по Ansible k8s
    Ansible k8s это специальный модуль для управления объектами Kubernetes из плейбуков Ansible. Как объединить Ansible и Kubernetes при автоматизации облака? Ответ: использовать модуль Ansible k8s, чтобы управлять объектами Kubernetes прямо из плейбуков. И поможет в этом наша шпаргалка, которая содержит полезные советы и сведения по ключевым командам этого модуля.
  • Шпаргалка по тестированию приложений Quarkus


  • Книжка-раскраска Контейнерные супергерои
    Децентрализованная команда опенсорсных контейнерных супергероев в лице Podman, CRI-O, Buildah, Skopeo и OpenShift спасает Землю от атаки астероидов, развертывая над планетой защитный экран.



Почитать на досуге:



Мероприятия:


  • 30 сентября, jconf.dev
    Бесплатная виртуальная Java-конференция прямо у вас на экране. Четыре технотрека с экспертами по Java и облаку, 28 углубленных сессий и два потрясающих основных доклада.
  • 13-14 октября, AnsibleFest
    Выступления, демонстрации, практические занятия и все это в онлайне. Отличная возможность виртуально пообщаться с девелоперами, админами и ЛПР-ами, которые успешно справляются с вызовами перемен с помощью опенсорсных технологий ИТ-автоматизации.

По-русски:


Мы продолжаем серию пятничных вебинаров про нативный опыт использования Red Hat OpenShift Container Platform и Kubernetes. Регистрируйтесь и приходите:

Император Оператор: Операторы в OpenShift и Kubernetes
Упс, вебинар прошел, но есть запись.

OpenShift-специфичные волшебные вещи для сборки и развертывания приложений
Вебинар кончился, но остался в истории ловите запись.

Подробнее..

Jupyter для .NET. Как в питоне

18.11.2020 18:12:16 | Автор: admin
Несколько месяцев назад Microsoft рассказали о Jupyter в .NET. Но активности по этому топику очень мало, а ведь тема очень интересная. Но что такое прикольное придумать? Я решил сделать удобный вывод класса Entity из библиотеки символьной алгебры:



Выглядит круче, чем в питоне. Делается просто, доставляет массу удовольствия. Приглашаю под кат!



О Jupyter


Это что-то вроде IDE для создания интерактивных блокнотиков. Вместо того, что бы в привычных средах запускать весь код сразу, здесь вы запускаете его по кускам, сохраняя состояния переменных. Очень удобно для исследований и простых скриптов чего-нибудь подсчитать.

О dotnet/interactive


Этот проект как раз позволяет встроить дотнет в юпитер. То есть вы буквально можете написать

И получать результаты работы кода прямо вот сразу.

Причем некоторые фишки работают из коробки


Об AngouriMath


Это относительно небольшая библиотека символьной алгебры для .NET для работы с математическими выражениями. Разумеется, с математическими выражениями можно работать в одну строку, но это не всегда удобно/красиво.

Все математические выражения так или иначе наследуются от класса Entity, который имеет метод Latexise, возвращающий строку. Поэтому остается всего лишь его отрендерить.

Встраиваем латех


У нас есть возможность зарегистрировать собственный вывод для наших типов, что я и делаю:

let magic() =    let register (value : ILatexiseable) = $@"            <script src='https://polyfill.io/v3/polyfill.min.js?features=es6'></script>            <script id='MathJax-script' async src='https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'></script>            \[{value.Latexise()}\]            "    Formatter.Register<ILatexiseable>(register, "text/html")

(хабр почему-то не поддерживает F#)

То есть мы просто регистрируем, что тип ILatexiseable должен выводиться в html-формате, а сам код будет генерироваться нашей функцией. Для рендеринга я взял MathJax, хотя наверное имеет смысл кешировать js-скрипт, но это для будущих обновлений.

Ну и собственно все, теперь все выражения, унаследованные от этого интерфейса, будут красиво рендериться. Вот как это выглядит в C#:



Что именно тут происходит?
1. В первом блоке мы вызываем extension-метод ToEntity(), который парсит выражение
2. Во втором блоке мы создаем систему уравнений и сразу ее выводим
3. В третьем создается матрица и сразу выводится


Так как юпитер предзназначен для небольших участков кода, которые не обязаны следовать каким-то проектным требованиям, F# подходит лучше для этого. Поэтому, как это понятно из скриншота в начале статьи, он тоже поддерживается. Для примера, так выглядит решение школьного уравнения:



Дальнейшие планы


Я очень большой любитель .NET-а, но я также очень люблю Jupyter. Поэтому Interactive меня очень порадовал, и я поспешил сделать поддержку Interactive для AngouriMath для вывода выражений в LaTeX. Но дальше интереснее. Я думаю сделать что-то типа Entity.Plot(), который выводил бы сразу график функции. Для простых use-кейсов очень нужна штука, мне кажется.

Если хочется попробовать не устанавливая ничего из необходимого, вы можете потыкаться тут: (аларм: почему-то долго грузится, придется подождать)

Спасибо за внимание! Такая вот короткая заметка.

Ссылки


1. Jupyter удобная браузерная среда для интерактивного программирования
2. .NET Interactive та самая гениальная вещь, благодаря которой можно использовать дотнет в юпитере
3. AngouriMath математическая библиотека, для которой я написал оболочку для латеха
4. MyBinder демка для ленивых
Подробнее..
Категории: C , Математика , Net , F , Jupyter notebook , Jupyter , Latex , Angourimath

Из песочницы Data Science блог с помощью fastpages

14.07.2020 12:05:49 | Автор: admin

Как запустить свой DS/ML/AI/Tech блог с минимумом сложностей связанных с хостингом и деплойем этого блога.


В конце февраля 2020 года ребята из fast.ai представили миру fastpages платформу для ведения блога. Отмечу, что fastpages основан на Jekyll, о котором на Хабре есть множество постов.


Примером блога на движке fastpages является данный блог.


Главное отличительная черта и преимущество fastpages состоит в поддерживаемых из коробки форматах постов:


  • Jupyter ноутбуки (расширение .ipynb);
  • Markdown файлы (расширение .md);
  • Word файлы (расширение .docx)

Таким образом, автору блога необходимо сохранить пост в любом из перечисленных выше форматах в соответствующей директории:


  • './_notebooks/' для .ipynb;
  • './_posts/' для .md;
  • './_word/' для .docx.

А все остальное сделает fastpages, как утверждают его авторы.


fastpages использует Github Pages для хостинга и Github Actions для автоматизации публикации постов.


Как я понимаю, fastpages является доработкой связки Github Pages + Jekyll, где можно сразу же из Jupyter ноутбука получить опубликованный пост.


Создание блога с помощью fastpages и GitHub


Если хотите самостоятельно разобраться, то вот официальная инструкция по настройке в репозитории fastpages.


Процесс настройки fastpages:


  1. Создать собственную копию репозитория из шаблона fastpages по ссылке

    image
  2. Далее автоматически откроется pull request (через ~ 30 секунд), который отвечает за настройку вашего блога, чтобы он мог начать работать.

    image
  3. Вам нужно выполнить инструкции из полученного pull request'a и вы получите свою собственную уже работающую платформу для блога.

Видео туториал



Настройка блога


Есть возможность для персонализированной конфигурации вашего блога. Параметры конфигурации находятся в файле ./_config.yml, некоторые из них приведены ниже:


  • title название вашего блога, которое отображается в верхнем левом углу на каждой странице;


  • description описание, которое будет отображаться в разных местах при предварительном просмотре вашего сайта (например, в социальных сетях);


  • github_username позволяет вашему сайту отображать ссылку на вашу страницу GitHub в нижнем колонтитуле;


  • github_repo позволяет вашему сайту отображать ссылки на ваш репозиторий для различных функций, таких как ссылки на GitHub, Google Colab и Binder для Jupyter ноутбуков;


  • default_badges по умолчанию ссылки GitHub, Google Colab и Binder будут отображаться в постах созданных из Jupyter ноутбуков. Вы можете задать, какие из них будут отображаться по умолчанию, установив для соответствующего значения в default_badges значение true или false. Например, если вы хотите отключить ссылки на Binder, вы должны поправить default_badges:


    default_badges:github: truebinder: falsecolab: true
    

  • url это не нужно менять, если у вас нет собственного домена;


  • baseurl см. комментарии в /_config.yml для получения инструкций ("Special Instructions for baseurl"). Если у вас нет настраиваемого домена, вы можете игнорировать эту опцию;


  • twitter_username создает ссылку в нижнем колонтитуле на страницу Twitter;


  • use_math установите значение true, чтобы получить поддержку математических формул LaTeX;


  • show_description отображает на домашней странице описание под заголовком ваших постов в блоге. По умолчанию установлено значение true;


  • google_analytics опционально можно использовать идентификатор Google Analytics;


  • pagination максимальное количество постов, отображаемых на каждой странице вашей домашней страницы. Значение по умолчанию равно 15. Когда число постов превысит заданное значение, тогда произойдет разбивка на страницы, которая выглядит так:
    image


  • show_tags включает отображение тегов внутри постов, которые выглядят следующим образом:

    image


  • show_image при значении true включается возможность добавления изображений к постам на домашней странице. Выглядит следующим образом (первые 2 поста сопровождаются изображениями):

    image



Публикация постов из .ipynb с помощью fastpages


  1. Сохраните исходный файл вашего поста (в одном из форматов: .ipynb, .md или .docx) в соответствующей папке репозитория (./_notebooks, ./_posts или ./_word). Пример имени для поста 2020-05-26-DS-fastpages-blog.ipynb. Такое наименование является необходимым для отображения поста движком Jekyll (больше деталей).
    Важные аспекты наименования постов:
    • Вначале имени поста указывается дата в формате YYYY-MM-DD-;
    • Символ, следующий сразу за тире, должен быть буквой алфавита.
  2. Сделайте commit и push ваших файлов на удаленный репозиторий GitHub в ветку master.
  3. GitHub автоматически конвертирует ваши файлы в посты блога. Процесс конвертации займет ~5 минут. Можно перейти на вкладку Actions в репозитории на GitHub. Вы увидите три workflow, которые запускаются при каждом push в ветку master:
    • Check Configurations процесс проверки ваших файлов (например, ссылок на изображения), перед обновлением контента в блоге;
    • CI процесс непрерывного деплоя вашего блога;
    • GH Pages Status процесс проверки доступа к блогу.
      Если эти процессы завершаются зеленой галочкой для последнего коммита, то сайт блога успешно обновился.
  4. Для предварительного локального просмотра того, как ваш блог будет выглядеть, см. этот раздел.
    Ниже представлены различные возможности форматирования, которые fastpages поддерживает из коробки.

Возможности форматирования постов


Хочу заранее предупредить, что ряд вещей не могут быть отображены в посте на Хабре, поэтому я добавил их как изображения и гифки. Для того чтобы полностью оценить то, как выглядят эти элементы, советую посмотреть пост в моем блоге, созданном на движке fastpages.


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


# "Title"> "Awesome summary"- toc: true- branch: master- badges: true- comments: true- author: Hamel Husain & Jeremy Howard- categories: [fastpages, jupyter]

Для указания таких в Markdown файлах необходимо в начале файла задать опции как и в ноутбуке, только поместив эти метаданные между строк содержащих по три минуса, т.е. ---.


Выглядит это так:


---title: "Title"description: "Awesome description"toc: truelayout: postcategories: [markdown]---

Все, что определено в начале поста, должно соответствовать YAML разметке. Поэтому если вы хотите использовать двоеточие в заголовке, вы должны экранировать его двойными кавычками: - title: "Deep learning: A tutorial"


Для большего понимания советую ознакомиться с туториалом по YAML.


Перечень управляющих конструкций для форматирования поста (взято отсюда):


  • toc при значении true автоматически будет сгенерировано оглавление поста из заголовков, обозначенных Markdown разметкой;
  • badges [notebooks only] при значении true отображаются ссылки Google Colab, Binder и GitHub, не работает при приватном репозитории;
  • hide_github_badge [notebooks only] при значении true скроет ссылку на GitHub;
  • hide_colab_badge [notebooks only] при значении true скроет ссылку на Google Colab;
  • hide_binder_badge [notebooks only] при значении true скроет ссылку на Binder;
  • branch [notebooks only] используется для дополнительной ссылки на ваш Jupyter ноутбук на Colab и GitHub. Значение по умолчанию: master;
  • comments при значении true будут включены комментарии (больше деталей);
  • author при значении true отображаются имена авторов;
  • categories позволяют группировать посты по тегам (на странице "Tags").
  • image задает изображение для поста, которое будет отображаться на главной странице блога и в соц. сетях (Twitter) вместе с ссылкой на пост:
    • пример задания изображения к посту images/figure.png;
    • изображение обязательно должно находиться внутри папке /images вашего репозитория;
  • search_exclude позволяет скрывать пост в поиске блога (страница Search), стоит заменить, поиск работает только с латиницей;
  • hide при значении true пост будет скрыт на главной странице блога, но будет доступен по прямой ссылке:
    • рекомендуется использовать permalinks для создания предсказуемых ссылок на сам пост;
    • если search_exclude будет иметь значение true, то пост можно будет найти через поиск блога (страница Search);
  • sticky_rank позволяет закрепить пост на конкретной позиции, задав ему порядковый номер. Если двум постам задать одинаковый номер, то между собой они будут отсортированы по дате.

Скрытие и сворачивание кода


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


Ниже приведены управляющие конструкции, которые задаются в первой строке ячейки в Jupyter ноутбука. Как это выглядит, можно посмотреть в моем посте.


  • #hide скрывает как ввод, так и вывод текущей ячейки;
  • #hide_input скрывает только ввод текущей ячейки;
  • #collapse-hide скрывает код этой ячейки внутри поста, но добавляет кнопку, позволяющую показать эту ячейку;
  • #collapse-show добавляет кнопку, позволяющую показать эту ячейку.

Интерактивные графики с помощью Altair


Графики построенные с помощью библиотеки Altair внутри поста остаются интерактивными как в ноутбуке. Для проверки интерактивности опять же см. мой пост.


Отображение таблиц


Таблицы в опубликованных постах отображаются примерно как и в Jupyter ноутбуках.
image


Вставка изображений


Вы можете добавлять изображения с подписями следующим образом:


![](http://personeltest.ru/aways/www.fast.ai/images/fastai_paper/show_batch.png "Credit: https://www.fast.ai/2020/02/13/fastai-A-Layered-API-for-Deep-Learning/")

Результат выглядит так:
image


Напомню, что подписи опциональны, и что изображения можно указывать как локально (в рамках репозитория блога), так и находящиеся в открытом доступе (имею в виду интернет).


Анимированные гифки


Гифки вставляются как изображения и полноценно отображаются в постах.



Видео Youtube


Чтобы красиво вставить видео с Youtube достаточно использовать конструкцию:


> youtube: https://youtu.be/L0boq3zqazI

Посты из Twitter


Есть возможность отображать посты из Twitter.


Например, ссылка на этот пост


> twitter: https://twitter.com/jakevdp/status/1204765621767901185?s=20

отобразит следующее:
image


LaTeX формулы


Jupyter ноутбуки поддерживают синтаксис LaTeX формул. Чтобы формулы отображались в постах, нужно убедиться, что опция use_math включена внутри _config.yml (см. Настройка блога).


Следующий LaTeX код:


> $$L(\theta) = \frac{1}{N} \sum_i^N{(y_i - \hat{y_i})^2} \rightarrow \min_{\theta}$$

будет отображен таким образом:


$$display$$L(\theta) = \frac{1}{N} \sum_i^N{(y_i - \hat{y_i})^2} \rightarrow \min_{\theta}$$display$$


Примечания


Есть возможность отображать примечания различных типов.


Предупреждение: > Warning: There will be no second warning!

image
Важно: > Important: Pay attention! It's important.

image
Подсказка: > Tip: This is my tip.

image
Заметка: > Note: Take note of this.

image


Если вставить в любое из примечаний ссылку, то она будет работать.
Например,


> Note: A doc link to [an example website: fast.ai](http://personeltest.ru/aways/www.fast.ai/) should also work fine.

отобразится так:
image


Отображение Emoji


Если написать


Сейчас будет эмоджи :robot:.

то получится:
Сейчас будет эмоджи :robot:.


Шпаргалка по Emoji.


Сноски


В jupyter ноутбуках можно использовать сноски, однако синтаксис отличается от Markdown разметки. Это руководство содержит более подробную информацию об этом синтаксисе, который выглядит следующим образом:


{% raw %}For example, here is a footnote {% fn 1 %}.And another {% fn 2 %}{{ 'This is the footnote.' | fndetail: 1 }}{{ 'This is the other footnote. You can even have a [link](http://personeltest.ru/aways/fastpages.fast.ai/jupyter/2020/02/20/test.html#Footnotes)' | fndetail: 2 }}{% endraw %}

image


Как fastpages конвертирует исходные файлы посты


Для этого fastpages использует nbdev для преобразования jupyter ноутбуков, word и .md файлов в посты блога. После того, как вы сохраните исходные файлы своих постов в папках /_notebooks, /_word или /_posts, то GitHub Actions c помощью nbdev автоматически преобразует их в конечный вид, в котором посты отображаются на сайте вашего блога.


fast_template младший брат fastpages


Стоит упомянуть, что ранее fast.ai выпустили аналогичный проект под названием fast_template, который еще проще в настройке, но не поддерживает автоматическое создание постов из Word и Jupyter файлов, а также многие другие функции перечисленные выше. Поскольку fastpages более гибок и расширяем, его авторы рекомендуют использовать его там, где это возможно.


Авторы предполагают, что fast_template может быть лучшим вариантом для тех, кто ведет не технические блоги. В этом случае посты можно создавать только с помощью встроенного онлайн-редактора Github, не заморачиваясь с использованием git.


Плюсы и минусы


Что понравилось


  • простота создания и размещения блога и публикации контента;
  • возможность публиковать Jupyter ноутбуки в качестве постов + удобства оформления:
    • поддержка отображения интерактивных графиков;
    • скрытие/сворачивание кода;
    • поддержка отображения GIF-анимации;
    • интеграция видео с youtube и тд.
  • нет зависимости от сторонней платформы по типу Medium;
  • возможность разместить блог по собственному url;
  • параметр badges в метаинформации к посту позволяет прикрепить ссылки на GitHub, Binder, Google Colab, что позволяет сразу перейти от поста к коду и его исполнению;
  • комментарии для блога из коробки;
  • возможность прикрепить пост на конкретную позицию на общей странице с помощью sticky_rank, смотреть тут;
  • отсутствие сторонней рекламы;

Что не понравилось или вызывало вопросы


  • непонятно, как сделать структурированный блог с вложенностью:
    • возможное решение permalinks;
    • структура нужна для объединения нескольких постов общей темой;
    • хочется структуру, чтобы в одной директорий хранить все, что связанно с постом (данные, изображения для ноутбуков) в одной папке, а не искать их в куче общих файлов и не городить какую-то структуру в этих общих для всех постов папках.
  • нет WYSIWYG (What You See Is What You Get):
  • в Jekyll в заголовке и описании поста не поддерживаются обратные кавычки, квадратные скобки и тд.
  • Jekyll подразумевает использование git для публикации постов;
  • целесообразность хранения Jupyter ноутбуков в репозитории под вопросом;
  • непонятно, как привязать spell checker для Jupyter ноутбуков.

Резюме


Команда fast.ai предложили DS сообществу интересный и достаточно функциональный инструмент для ведения блога, автору которого остается думать только о том, какой контент публиковать.


Сложность использования практически минимальная, нужны базовые знания git, разметки Markdown и Jupyter Notebook. И никаких проблем с тем, как и где хостить и деплоить сам блог.


Конечно, есть определенные пожелания по поводу функционала этого движка, для этого можно участвовать в развитии проекта, находя баги или предлагая те или иные улучшения. В последнем случае даже pull request не обязателен, порой хватает текстового описание тех или иных желаний пользователей.


В заключение хочу сказать, что сам пользуюсь и всем советую.


DS/ML/AI блоги



Блоги компаний



Полезные ссылки


Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru