{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# BigARTM. Примеры обучения моделей на Python для самых маленьких." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Автор сего творения - **Мурат Апишев**, Маг V категории (астральный позывной **great-mel@yandex.ru**)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Уважаемый Гость Тематической Империи, задача этого манускрипта - показать, как использовать библиотеку в ряде наиболее распространённых сценариев. Если Ваш случай оказался настолько ~~извращённым~~ необычным, что не попадает ни под один из описанных примеров, пошлите голубя в Коллегию Магов (bigartm-users@googlegroups.com), Вам обязательно помогут, хотя бы морально." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Прежде, чем продолжить, *настоятельная* просьба: убедитесь, что все инструкции по установке библиотеки из этого многотомного манускрипта (http://bigartm.readthedocs.org/en/master/installation/index.html) выполнены в точности, и модуль artm у Вас импортируется без ошибок." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Итак, призовём из Астрала этот самый модуль:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import artm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Каждый из описанных далее сценариев является отдельным блоком ~~заклинаний~~ кода, не зависящим от других (за исключением самого первого, про словари и батчи - его надо выполнять всегда, иначе можно случайно вызвать грозу или ураган). Код рабочий (в смысле его можно копипастить в свои скрипты), в том случае, если Вы верно подготовите все требуемые данные и разместите их там, где нужно.\n", "\n", "Поскольку из трёх новых пользователей всегда есть хотя бы два, которые отвлекают Магов от размышлений о сущности мироздания ошибками такого вот вида:\n", "\n", "**_DiskReadException: File vocab.kos.txt does not exist._**\n", "\n", "Сразу расскажу про то, что\n", "- скорее всего, Вы не туда положили данные (если они у Вас, конечно, есть), попробуйте разобраться с этим;\n", "- либо файл называется неверно (два раза написано расширение или что-то в этом роде) - рекомендация та же;\n", "\n", "Если Вы не смогли исправить ситуацию (могу только посочувствовать), пропишите везде абсолютные пути, наверняка это откроет Вам дорогу к Свету!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Словари в BigARTM, или чему надо научиться до того, как начать строить модели" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Прежде всего нужно собрать для BigARTM всю информацию, которую только можно. Если закраулить весь Интернет и отдать библиотеке возможности нет (а жаль!), то можно хотя бы создать пакеты документов (у Магов принят термин _батчи_) из текстового файлика в предопределённом небесами формате (прочитать об этом можно здесь http://bigartm.readthedocs.org/en/master/formats.html) и создать на его основе т.н. _словарь_ - артефакт, знающий всё про то, какие уникальные слова есть в Вашем файле с текстовой коллекцией." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Итак, если у Вас есть файлы коллекции my_collection в формате UCI (т. е. файлы vocab.my_collection.txt и docword.my_collection.txt), которые лежат в одной директории с этим манускриптом, то отбросьте все терзающие душу сомнения и смело запустите следующий код:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "batch_vectorizer = artm.BatchVectorizer(data_path='',\n", " data_format='bow_uci',\n", " collection_name='my_collection',\n", " target_folder='my_collection_batches')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Чтобы понять, что тут произошло, обязательно загляните сюда (http://bigartm.readthedocs.org/en/master/python_interface/batches_utils.html), обещаю, узнаете много нового.\n", "\n", "Но если кратко: Вы только что создали батчи, которые лежат в директории my_collection_batches. Теперь ими можно пользоваться." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если вдруг у Вас нет файла в формате UCI, но есть в формате Vowpal Wabbit - то следующий блок для Вас, приятного аппетита:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "batch_vectorizer = artm.BatchVectorizer(data_path='',\n", " data_format='vowpal_wabbit',\n", " target_folder='my_collection_batches')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "И, вот волшебство, у Вас тоже есть своя директория my_collection_batches с батчами. Успех!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Важно**: если батчи у Вас есть, перезапускать код выше НЕ НУЖНО, это богомерзко! Хотите создать батчи заново (если нечем больше заняться) - удалите директорию my_collection_batches со всем содержимым и тогда уже перезапускайте.\n", "\n", "Но если уж Вас батчи всем устраивают, то batch_vectorizer и подавно, он вполне удовлетворится таким кодом:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "batch_vectorizer = artm.BatchVectorizer(data_path='my_collection_batches',\n", " data_format='batches')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "На всякий случай шёпотом подсказываю, что тут произошло: batch_vectorizer создался на основе батчей, это очень быстрая операция, рекомендую ей пользоваться, этого гораздо приятнее, чем каждый раз пересоздавать его на основе Ваших исходных файлов, уж поверьте мне." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Зачем мы создаём batch_vectorizer? Ответ простой - библиотека боится сырых батчей, её можно простить, у всех есть свои фобии. Но если прикрыть их с помощью batch_vectorizer, она обработает их и выдаст Вам такую крутую модель, что закачаетесь!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Следующая ступень Посвящения в Рыцари BigARTM - словари. Как было замечено выше, эти штуки хранят информацию обо всех уникальных словах в коллекции. Словарь - зверь важный, поэтому создаётся вне модели, и различными способами (посмотреть их все Вы можете вот тут http://bigartm.readthedocs.org/en/master/python_interface/dictionary.html).\n", "\n", "Самый базовый вариант - \"собрать\" словарь по директории с батчами. Это нужно делать один раз в самом начале работы с новой коллекцией, и делать вот так, и никак иначе:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "dictionary = artm.Dictionary()\n", "dictionary.gather(data_path='my_collection_batches')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Но сразу скажу - в таком словаре все слова будут идти в том порядке, в каком они встретились при просмотре батчей, т. е. почти гарантируется отсутствие какого-либо вразумительного порядка. Если Вы чувствуете, что Вашей тонкой натуре перфекциониста этого не перенести, заведите файлик вида vocab (см. формат UCI), в котором будут перечислены все уникальные слова коллекции в том порядке, который Вам нравится, и запустите такой код (пусть файл называется vocab.txt и тоже лежит в одной директории с манускриптом):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "dictionary = artm.Dictionary()\n", "dictionary.gather(data_path='my_collection_batches',\n", " vocab_file_path='vocab.txt')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Но словари не так просты, как кажутся на первый взгляд. В них ещё каждому слову соответствует переменная - value. Когда библиотека, следуя Вашим мудрым указаниям, собирает словарь, она в эту переменную кладёт абсолютную частоту этого слова во всей коллекции. О том, что можно делать с этой переменной, будет рассказано в последующий разделах." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Итак, у Вас есть словарь, поздравляю! Как можно было догадаться, их можно не только создавать, но ещё и сохранять на диск, чтобы не пересоздавать каждый раз (мы же с Вами умные люди, верно?). Сохранять можно в бинарном виде:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "dictionary.save(dictionary_path='/my_collection_batches/my_dictionary')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Правда необычное имя для файла с Вашим словарём? Кстати, если Вам нечего почитать на ночь (или Вам просто интересно, что же библиотека Вам насобирала), можете сохранить словарь в текстовом виде и увлекательнейшее чтиво Вам гарантировано:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "dictionary.save_text(dictionary_path='my_collection_batches/my_dictionary.txt')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "И да, конечно же эти словари можно загрузить обратно:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "dictionary.load(dictionary_path='my_collection_batches/my_dictionary.dict')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "либо, в случае текстового файла:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "dictionary.load_text(dictionary_path='my_collection_batches/my_dictionary.txt')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Тут BigARTM не только загрузит словарь, но ещё и учтёт все внесённые Вами в него изменения (поэтому правьте словари с умом и по делу, не мучайте библиотеку своими ошибками, имейте совесть)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Летопись первая. Для самых ленивых (обучение базовой модели PLSA с подсчётом перплексии)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Итак, если Вы пришли сюда, то у Вас должен быть набор следующих магических предметов и артефактов:\n", "\n", "- директория с батчами с названием my_collection_batches, а в ней - батчи и словарь с бинарном файле my_dictionary.dict, директория должна лежать рядом этим манускриптом;\n", "- переменная-словарь my_dictionary, в которой этот самый словарь есть (собран или загружен).\n", "- переменная batch_vectorizer (вот прям такая, какую мы делали выше)\n", "\n", "Если что-то не так - срочно вернитесь к предыдущему разделу и внимательно его изучите, иначе этот уровень Вам не пройти (больше того, я даже не ручаюсь за Вашу безопасность!).\n", "\n", "Если же всё в порядке (или Вы решили рискнуть), то давайте, наконец, создадим модель. Прежде всего ~~рекомендую~~ очень рекомендую заглянуть в этот том заклинаний, он один из самых важных на Вашем пути (http://bigartm.readthedocs.org/en/master/python_interface/model.html).\n", "\n", "Заглянули? Тогда вперёд:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model = artm.ARTM(num_topics=20, dictionary=my_dictionary)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вот и всё, это совсем не больно! Теперь у Вас есть модель, в которой создана матрица $\\Phi$ размером \"число слов из Вашего словаря\" на число тем (20), она инициализирована случайным образом.\n", "\n", "Здесь уже можно было бы начинать обучение модели, но Вы же наверняка недоверчивы, и не поверите библиотеки на слово, что она построит хорошую модель, верно? Специально для таких мы обвесили BigARTM метриками качества моделирования, которые можно подключать к Вашей модели в любом количестве. На этом уровне мы ограничимся перплексией.\n", "\n", "Сущность модели многогранна, и одной из граней (как раз той, что нам нужна для создания метрик) является scores. При добавлении и редактировании метрик обращаться нужно к ней, она не откажет (если вежливо попросить!):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.scores.add(artm.PerplexityScore(name='my_fisrt_perplexity_score',\n", " use_unigram_document_model=False,\n", " dictionary=my_dictionary))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Здесь сразу хочу предвосхитить Ваши вопросы о параметрах перплексии отсылкой к http://bigartm.readthedocs.org/en/master/python_interface/scores.html. Важно запомнить, что подключать перплексию нужно именно так. Как видите, алгоритм включения метрик не грешит излишней сложностью: создаёте метрику как объект, и подаёте её на вход методу add поля scores.\n", "\n", "Предупрежу сразу: метрики - ужас какие вредные! Если попробуете загрузить такую же метрику с таким же именем - старая её просто не впустит, и новая метрика погибнет на холоде и в одиночестве. Помните об этом.\n", "\n", "Теперь перейдём к главному действу - обучению модели. Сделать это можно одним из двух способов: онлайновым и неонлайновым (он же в простонародье зовётся оффлайновым). Обучение производят методы fit_online() и ~~fit_not_online~~ fit_offline(). Что это такое Вы знать должны, раз читаете этот текст, но я кратко напомню:\n", "\n", "- Оффлайновый алгоритм: много проходов по коллекции, один проход по документу (опционально), обновление $\\Phi$ в конце каждого прохода. Используйте, если у Вас маленькая коллекция.\n", "\n", "- Онлайновый алгоритм: один проход по коллекции (опционально), много проходов по документу, обновление $\\Phi$ раз в заданное количество батчей. Используйте при большой коллекции, и коллекции с быстро меняющейся тематикой.\n", "\n", "Фух, вроде ничего не забыл. Параметры этих методов можно найти в ранее указанном томе http://bigartm.readthedocs.org/en/master/python_interface/model.html. Мы воспользуемся оффлайновым обучением здесь и во всех остальных примерах, если у Вас возникнет потребность в онлайне, уверен, Вы осилите замену. Итак, вызываем:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.fit_offline(batch_vectorizer=batch_vectorizer, num_collection_passes=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Наверняка этот фрагмент кода работал дольше всех предыдущих. Вот мы и провели первый этап обучения модели, стоит и на перплексию посмотреть. Для надо задействовать score_tracker. Он родственник scores, если scores добавляет и изменяет параметры метрик, то этот выгребает результаты их работы и показывает Вам в удобоваримом виде по первому требованию (вот такая у него судьба). Он запоминает значения всех метрик на момент каждого обновления матрицы $\\Phi$.\n", "\n", "Требовать можно либо самое последнее значение:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "print model.score_tracker['my_fisrt_perplexity_score'].last_value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Либо (если Вы не стесняетесь) потребовать список значений за все обновления:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "print model.score_tracker['my_fisrt_perplexity_score'].value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если вдруг Вы посчитали, что перплексия сошлась - поздравляю, можно пойти скушать чего-нибудь вкусненького и переходить к следующему разделу. Если же нет (и Вы рассчитываете ещё узнать что-то полезное), то продолжим.\n", "\n", "Как было отмечено, требование одной итерации прохода по документу - опциональное. И fit_online(), и fit_offline() могут делать столько итераций по документу, сколько захотите. Для этого надо всего лишь захотеть - и задать это число в переменной-модели:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.num_document_passes = 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Все последующие вызовы методов обучения уважительно отнесутся к Вашему решению и используют это значение. Проверим это:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.fit_offline(batch_vectorizer=batch_vectorizer, num_collection_passes=15)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Мы дообучили предыдущую модель, сделав ещё 15 итераций по коллекции, и на каждой из них 5 раз обрабатывая каждый документ.\n", "\n", "Вот и всё, дорогой Гость. Дальше можно дообучать модель по аналогии. Напоследок, перед тем, как перейти ко второму разделу: если в какой-то момент, Вы осознали, что модель угроблена, Вам стало стыдно, и Вы не хотите заводить новую, а хотите оживить старую - воспользуйтесь воскрешающим методом, который заполнит матрицу $\\Phi$ снова случайными числами, и ничего больше не тронет (ни ваших настроек регулризаторов/метрик, ни историю значений метрик):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.initialize(dictionary=my_dictionary)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Кстати, ровно это метод и вызывает конструктор внутри себя, если получает на вход параметр dictionary. Ну так, чтобы Вы не думали, что тут совсем всё только на магии держится.\n", "\n", "Ну и да, везде, где принимается переменная словарь, может приниматься и его имя-строка (посмотрите, имя наверняка необычное и красивое):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.initialize(dictionary=my_dictionary.name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Летопись вторая. Для тех, кто слышал про АРТМ, неустойчивость решения задачи стохастического матричного разложения и прочие новости астрала (регуляризация модели PLSA и новые метрики)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Как Вы, наверное, знаете, BigARTM возникла не на голом месте. Это проект по воплощению в жизнь знаменитой идеи архимага К. В. Воронцова. АРТМ - современная магическая доктрина, пришедшая на замену байесовскому шаманизму, относительно простая и эффективная. В библиотеке есть разные регуляризаторы, и сейчас мы будем учиться ими пользоваться так, чтобы было безопасно для Вас и окружающих." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Предлагаю оставить теорию на потом и сразу перейти к делу. Создадим модель, и добавим метрику перплексии, всё, как на прошлом уровне (и да, требования к батчам и словарю остались прежними!):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model = artm.ARTM(num_topics=20, dictionary=my_dictionary, cache_theta=False)\n", "model.scores.add(artm.PerplexityScore(name='perplexity_score',\n", " use_unigram_document_model=False,\n", " dictionary=my_dictionary))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сразу маленький оффтопик: библиотека может хранить вычисляемую матрицу $\\Theta$. А может и не хранить (как Вы догадались, определяется это истинным или ложным значением флага соответственно). Первый случай подойдёт для небольшой коллекции, если $\\Theta$ Вам будет нужна. Второй - для большой коллекции, когда матрица просто не будет влазить в память. В этом случае для поиска распределений тем в документах можно будет использовать заклинание transform() (про это дальше будет)." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "И да, заметили изменившееся название метрики? Правильно, Вы теперь опытный пользователь, и названия должны быть серьёзными.\n", "\n", "А, собственно, почему мы пользуемся только перплексией? У BigARTM довольно богатый набор метрик, про которые можно прочесть, опять-таки, здесь http://bigartm.readthedocs.org/en/master/python_interface/scores.html (если Вы этого до сих пор не сделали, конечно).\n", "\n", "Предлагаю разнообразить нашу и без того нескучную жизнь метриками разреженности матриц $\\Phi$ и $\\Theta$, а также информацией о наиболее вероятных словах в каждой теме (последние Магами именуются _топ-токенами_):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.scores.add(artm.SparsityPhiScore(name='sparsity_phi_score'))\n", "model.scores.add(artm.SparsityThetaScore(name='sparsity_theta_score'))\n", "model.scores.add(artm.TopTokensScore(name='top_tokens_score'))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Здорово! Но метрики - хитрые, они не раскрывают перед Вами все свои возможности сразу. Например, Вы наверняка не знали, что они могут считаться не по всем темам, верно? Или знали? В любом случае, это часто бывает полезным. Давайте будем отдельно считать разреженность первых десяти тем в матрице $\\Phi$ (мне почему-то кажется, что они особенные). Но вот беда: темы идентифицируются своими именами, а у нас то их нет! Если бы не поленились задать параметр topic_names в конструкторе модели, такой вопрос вообще бы не встал. Но мы то задали только num_topics... Но всё не так плохо: библиотека сгенерировала нужные имена и милостиво положила их в поле topic_names, можно смело использовать:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.scores.add(artm.SparsityPhiScore(name='sparsity_phi_score_10_topics', topic_names=model.topic_names[0: 9]))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Конечно, если бы общая разреженность модели была бы нам неинтересна, мы могли бы просто модифицировать первую метрику, а не вводить новую:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.scores['sparsity_phi_score'].topic_names = model.topic_names[0: 9]" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Но, ИМХО, это неуважение ко второму десятку тем, поэтому оставим как есть. Но запомните, что все параметры метрик, модели (и регуляризаторов, о которых будет рассказано далее) могут быть выставлены или заменены путём прямого обращения к полю, как это показано в коде выше." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вот попросим метрику топ-токенов показывать Вам, скажем, именно 12 наиболее вероятных этих самых топ-токенов:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.num_tokens = 12" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Итак, у Вас есть модель, обвешанная метриками, как ёлка игрушками. Самое время запустить обучение:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.fit_offline(batch_vectorizer=batch_vectorizer, num_collection_passes=10)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Ну это заклинание Вы уже видели, ничего особенного. Но теперь можно посмотреть значения тех метрик, которые были подключены:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "print model.score_tracker['perplexity_score'].value # .last_value\n", "print model.score_tracker['sparsity_phi_score'].value # .last_value\n", "print model.score_tracker['sparsity_theta_score'].value # .last_value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как видно, во всех метриках ничего не изменилось. Хотя… мы же забыли про топ-токены! Здесь надо действовать чуть аккуратнее: метрика хранит данные на момент всех обновлений матрицы $\\Phi$. Предположим, что нам нужны только самые последние данные (догадайтесь сами, что надо поменять для получения более ранних). Тогда обратимся к полю last_tokens. Это словарь, в котором ключ - имя темы, а значение - список топ-слов этой темы.\n", "\n", "Да, последний момент: метрики выгружаются из ядра при каждом обращении, поэтому для таких больших метрик, как топ-слова (или ядровые характеристики, о которых можно прочитать по данным выше ссылкам), лучше завести переменную, в которую Вы один раз всё выгрузите, а потом уже работать с ней. Поехали, просмотрим топ-слова последовательно в цикле по именам тем модели:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "saved_top_tokens = model.score_tracker['top_tokens_score'].last_tokens\n", "\n", "for topic_name in model.topic_names:\n", " print saved_top_tokens[topic_name]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Наверняка темы получились не слишком впечатляющими, да? И вот сейчас на сцену выходят они, озарённые сиянием прожекторов звёзды Тематической империи - регуляризаторы! Их задача - помочь Вам сделать модель как можно более хорошей и милой.\n", "\n", "Списки регуляризаторов и их параметров можно посмотреть здесь http://bigartm.readthedocs.org/en/master/python_interface/regularizers.html. Код работы с регуляризаторами очень похож на код работы с метриками (они всё-таки дальние родственники). Давайте в нашу модель добавим три регуляризатора: разреживание $\\Phi$, разреживание $\\Theta$ и декорреляция тем. Последний в поте лица старается сделать темы как можно более различными." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.regularizers.add(artm.SmoothSparsePhiRegularizer(name='sparse_phi_regularizer'))\n", "model.regularizers.add(artm.SmoothSparseThetaRegularizer(name='sparse_theta_regularizer'))\n", "model.regularizers.add(artm.DecorrelatorPhiRegularizer(name='decorrelator_phi_regularizer'))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Возможно, у Вас вызывает когнитивный диссонанс регуляризатор SmoothSparsePhi\\Theta, это он что же, и сглаживает, и разреживает? И мой ответ - именно так. Только не \"и\", а \"или\" (хотя для $\\Phi$ можно и \"и\", об этом дальше будет). Он может и то, и то, его действия будут зависеть от того, каким Вы зададите его коэффициент регуляризации $\\tau$ (если не знаете, что это - обращайтесь к работам архимага). $\\tau$ > 0 - будет сглаживать, $\\tau$ < 0 - разреживать. По умолчанию все регуляризаторы получают $\\tau$ = 1.0, что, как правило, совершенно не подходит. Выбор подходящего $\\tau$ - эвристика, искусство заклинателя, порой приходится провести десятки опытов, чтобы подобрать хорошие значений. Я не буду здесь этим заниматься, просто покажу, как выставлять эти значения:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.regularizers['sparse_phi_regularizer'].tau = -1.0\n", "model.regularizers['sparse_theta_regularizer'].tau = -0.5\n", "model.regularizers['decorrelator_phi_regularizer'] = 1e+5" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Выставленные значения стандартны, но при неблагоприятном стечении обстоятельств могут либо не оказать на модель существенного влияния, либо заставить её корчиться в муках. Берегите свои модели, подбирайте коэффициенты тщательно.\n", "\n", "И да, ещё раз обращая Ваше внимание, что выставление и замена параметров регуляризаторов полностью аналогично тому, как это происходит у метрик. Прочитайте в документации, какие есть поля - и флаг Вам в руки." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Запустим обучение модели повторно:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "model.fit_offline(batch_vectorizer=batch_vectorizer, num_collection_passes=10)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Дальше можно снова смотреть на метрики, поправлять коэффициенты $\\tau$ регуляризаторов и т.д.\n", "\n", "Забыл упомянуть, что регуляризаторам, как и метрикам, тоже можно объяснить, какие темы можно трогать, а какие - нельзя (они всё-таки неглупые ребята). Делается это в полной аналогии с тем, как мы проделали это для метрик. Не буду приводить пример из принципа, сами догадайтесь, это элементарно." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь я хочу снова вернуться к словарям. Но сперва - небольшое отступление. Вот регуляризатор сглаживания/разреживания $\\Phi$ (да-да, это тот самый SmoothSparsePhiRegularizer), он вот как работает? Просто берёт, и прибавляет к счётчикам величину $\\tau$. Ко всем счётчикам для данной темы одну и ту же величину... А вдруг нам это не подходит? Вдруг мы хотим какие-то слова разреживать сильно (про магию, например, чтобы она оставалась уделом избранных), какие-то слова сглаживать (например, про котиков, их все погладить хотят, даже тематические модели), а остальные вообще не трогать? Что тогда делать?\n", "\n", "В этой, казалось бы, безвыходной ситуации на сцене снова появляются словари. Они самодовольно ухмыляются, им эта задача вполне по силам. Вспомним про волшебное поле value, которое соответствует каждому слову. А ещё про то, что регуляризатор сглаживания/разреживания $\\Phi$ имеет поле dictionary. Так вот, внимание: если Вы зададите это поле, то регуляризатор будет прибавлять не $\\tau$, а $\\tau$ * value для данного слова. Смекаете? Если $\\tau$ взять равным, например, единице, словам про магию выставить value = -1.0, словам про котиков 1.0, а всем остальным словам - 0.0, то мы получим ровно то, что и хотели!\n", "\n", "Остался один нюанс: как заменить эти самые value. Так вот самая пора вспомнить то, о чём я говорил во вводной части, а именно - про методы Dictionary.save_text() и Dictionary.load_text().\n", "\n", "Последовательность действий следующая:\n", "- выгружаете словарь в текстовом виде\n", "- открываете его, каждая строка - одно слово, в строке 5 значений: слово - модальность - value - token_tf - token_df (это всё написано в первой строке-заголовке)\n", "- не обращайте внимания ни на что, кроме слова и его value; найдите интересующие Вас слова и выставьте им нужные значения value\n", "- загрузите словарь обратно\n", "\n", "После редактирования Ваш файл со словарём может выглядеть так (схематично):\n", "\n", "котик | что-то | 1.0 | что-то | что-то\n", "\n", "пиво | что-то | 0.0 | что-то | что-то\n", "\n", "посох | что-то | -1.0 | что-то | что-то\n", "\n", "киса | что-то | 1.0 | что-то | что-то\n", "\n", "мерлин | что-то | -1.0 | что-то | что-то\n", "\n", "москва | что-то | 0.0 | что-то | что-то\n", "\n", "Хотя очень сомнительно, что у Вас такой маленький и странный словарь, но всякое бывает в жизни." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Весь необходимый для проведения означенных операций код уже был показан ранее. На всякий случай только покажу, как создать регуляризатор со словарём:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "model.regularizer.add(artm.SmoothSparsePhiRegularizer(name='smooth_sparse_phi_regularizer', dictionary=my_dictionary))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вообще, если Вы сами до этого не догадались, то это весьма прискорбно.\n", "\n", "Что ж, самое время встать, сделать небольшую гимнастику, подумать о приятном (котики...) и перейти к третьей части сего труда." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Летопись третья. Или Вы очень любите приключения, или Вы адепт К. В. Воронцова. В любом случае, Вам будет тяжело, но весело (построение мультимодальной тематической модели с регуляризацией и оцениванием качества)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Теперь мы переходим к более сложной магии. В прошлой летописи Вы уже столкнулись с понятием модальности. Это нечто, соответствующее каждому слову. Я бы определил это, как вид слова. Да-да, бывают же ведь слова текста, а бывают слова, из которых состоит заголовок. А есть ещё слова-имена авторов текста, и даже картинку, если её перекодировать в текст, можно считать набором слов, и слова эти будут типа \"слова из которых состоит картинка\". И таких видов слов можно придумать очень много.\n", "\n", "Так вот, в BigARTM каждое слово имеет ~~тип~~ модальность. Обозначается она совершенно по-дурацки - class_id. Ничего общего с классификацией это не имеет, просто неудачное название, которое уже поздно менять, смиритесь. У каждого слова есть такой class_id, Вы всегда можете задать его сами, либо же библиотека автоматически задаст class_id = '@default_class' (если Вы заглядывали внутрь словарей, то, наверняка, видели эту конструкцию). Это - тип обычных слов, тип по умолчанию." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "В большинстве случаев модальности не потребуются, но есть такие ситуации, когда они незаменимы. Например, при классификации документов. Я очень часто сталкивался с тем, что юным адептам архимага требовалось строить такие модели, и они приходили ко мне с вопросами. Я направлял их, пытался воззвать к их внутренним силам, дабы они черпали Истину не из моих ~~писем~~ слов, а из собственных душевных глубин... В общем, у меня ничего не вышло, поэтому именно этот случай я и постараюсь сейчас здесь описать." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Все артефакты (батчи, словарь), которые у Вас были до этого, Вам надо пересоздать. От Вас требуется, помимо смекалки и тяги к познанию, файл с данными в формате Vowpal Wabbit, в котором каждая строка - документ, а каждый документ состоит из обычных слов и слов-меток классов, к которым этот документ относится. Пример:\n", "\n", "doc_100500 |@default_class aaa:2 bbb:4 ccc ddd:6 |@labels_class class_1 class_6\n", "\n", "Всё это, вообще, подробно описано здесь http://bigartm.readthedocs.org/en/master/formats.html.\n", "\n", "Теперь проделайте с этим файлом все необходимые манипуляции из вводной части, чтобы получить нужные батчи и словарь." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Далее, Вам надо объяснить модели, какие у Вас есть модальности и какие степени влияния на модель Вы хотите им задать. Степень влияния - это коэффициент модальности $\\tau_m$ (опять же смотрите записи архимага). Модель не тупая, и сама видит все Ваши модальности, но из гордости игнорирует все, кроме '@default_class', и её берёт с $\\tau_m$ = 1. Хотите использовать другие модальности и веса - попросите. Просить надо при создании модели, вежливо, например, так:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model = artm.ARTM(num_topics=20, class_ids={'@default_class': 1.0, '@labels_class': 5.0})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Итак, мы попросили модель учитывать эти две модальности, причём метки классов сделать в 5 раз более влиятельными, чем обычные слова. Отмечу, что если в вашем файле с данными были ещё модальности, а Вы их тут не отметили - они не будут учтены. Опять же, если Вы отметите в конструкторе модальности, которых нет в данных - их постигнет та же незавидная участь.\n", "\n", "И, конечно же, поле class_ids, как и все остальные, является переопределяемым, Вы всегда можете изменить веса модальностей:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "model.class_ids = {'@default_class': 1.0, '@labels_class': 50.0} # model.class_ids['@labels_class'] = 50.0 --- NO!!!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Обновлять веса надо именно так, задавая весь словарь, не надо пытаться обратиться по ключу к отдельной модальности, class_ids обновляется с помощью словаря, но сама словарём не является (словарь - в смысле Python dict)." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Всё, при следующем запуске fit_offline() или fit_online() эта информация будет учтена.\n", "\n", "Теперь к модели, как это ни прозаично, надо подключить регуляризаторы и метрики качества. Весь это процесс мы уже проходили, за исключением одного момента. Все метрики на матрице $\\Phi$ (а также перплексия) и регуляризаторы $\\Phi$ имеют поля для работы модальностями. Т. е. в этих полях Вы можете определить, с каким модальностями метрика/регуляризатор должна работать, остальные будут проигнорированы (по аналогии с полем topic_names для тем).\n", "\n", "Поле модальности может быть либо class_id, либо class_ids. Нехитрыми мыслительными операциями можно догадаться, что первое - это строка с именем одной модальности, с которой надо работать, второе - список строк с такими модальностями.\n", "\n", "Важный момент со значениями по умолчанию. Для class_id отсутствие заданного Вами значения означает class_id = '@default_class'. Для class_ids отсутствие значения означает использование всех имеющихся в модели модальностей.\n", "\n", "Посмотреть информацию детально о каждой метрике и каждом регуляризаторе можно в уже многократно данных ссылках http://bigartm.readthedocs.org/en/master/python_interface/regularizers.html и http://bigartm.readthedocs.org/en/master/python_interface/scores.html. Очень надеюсь, что к концу изучения этого талмуда Вы будете знать содержимое документации наизусть." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Давайте добавим в модель метрику разреженности $\\Phi$ для модальности меток классов, а также регуляризаторы декорреляции тем для каждой из модальностей, после чего запустим процесс обучения модели:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.scores.add(artm.SparsityPhiScore(name='sparsity_phi_score', class_id='@labels_class'))\n", "\n", "model.regularizers.add(artm.DecorrelatorPhiRegularizer(name='decorrelator_phi_def', class_ids=['@default_class']))\n", "model.regularizers.add(artm.DecorrelatorPhiRegularizer(name='decorrelator_phi_lab', class_ids=['@labels_class']))\n", "\n", "model.fit_offline(batch_vectorizer=batch_vectorizer, num_collection_passes=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Итак, оставляю Вам удовольствие дальнейшей настройки модели, подбора коэффициентов регуляризации, весов модальностей и просмотра метрик. Перейдём к использованию обученной модели для классификации тестовых данных.\n", "\n", "Да, если для Вас это вдруг является новостью, спешу сообщить, что в задаче классификации у Вас есть обучающие данные (коллекция, на которой Вы тренировали модель, где для каждого документа модели известны его метки классов) и данные тестовые, метки которых известны Вам, но не сообщаются модели. Эти самые скрытые метки модель и должна предсказать, глядя на тестовые документы, а Ваша задача - взять верные ответы и то, что Вам выдала модель, и сравнить, например, посчитав AUC.\n", "\n", "Подсчёт AUC или ещё чего-то, что взбредёт Вам в голову - это Ваше дело, я в него лезть не смею. А вот получать для новых документов векторы p(c|d) длиной в количество классов, где каждый элемент - вероятность класса c для данного документа d, - это да, это я Вас делать научу. Вот у нас есть модель. Я, песть это и наивно, но предполагаю, что\n", "- тестовые документы были убраны в отдельный файл в формате Vowpal Wabbit, на основе которого Вы сумели сгенерировать батчи, которые описываются переменной batch_vectorizer_test (если для Вас это всё ещё сложно, то Вам опять во вводный раздел). Да, очень надеюсь, что Вы эти батчи сохранили не в одной директории с обучающими, хотя до такого ещё додуматься надо.\n", "- Ваши тестовые документы не содержат информацию о метках классов (для особо высокоодарённых: в тестовом файле не должно быть строки '|@labels_class')\n", "- тестовые документы не содержат слов, которых не было в документах обучающих, иначе такие слова будут проигнорированы.\n", "\n", "Если все эти магические условия выполнены, можно переходить к самому ритуалу. У модели есть такое заклинание, transform() (да-да, и об этом написано тут http://bigartm.readthedocs.org/en/master/python_interface/model.html), которое позволяет для всех документов из данного объекта BatchVectorizer получить матрицу вероятностей p(t|d) (т. е. $\\Theta$), либо матрицу p(c|d) для любой указанной модальности.\n", "\n", "Чтобы получить $\\Theta$ делаем так:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "theta_test = model.transform(batch_vectorizer=batch_vectorizer_test)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ну, а чтобы получить p(c|d), прочтите вот это заклятие:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "p_cd_test = model.transform(batch_vectorizer=batch_vectorizer_test, predict_class_id='@labels_class')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вот у Вас и есть заветные предсказания модели в pandas.DataFrame (надеюсь, Вы сможете им воспользоваться, если нет - научитесь, это полезно). Теперь Вы можете оценить степень качества предсказаний построенной Вами модели любым способом, который Вам по душе и не противоречит Уголовному Кодексу Магов." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Летопись четвёртая. Аккуратнее, Вы зашли слишком далеко... (извлечение Фи и Теты, сохранение и загрузка модели, фильтрация словарей)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Да, зашли далеко Вы далеко, но пугаться не надо, тут ходят автобусы, быстро сможете вернуться. Вопросы, рассматриваемые в этой главе, носят технический характер, поэтому достаточно просты в изучении. Вообще, глава будет небольшая, но разобраться в ней стоит хорошо.\n", "\n", "Итак, предположим, что у Вас есть данные, Вы обучили на них модель. Настроили все регуляризаторы, посмотрели на метрики качества. Здорово. Но что-то Вас гложет, не даёт спать по ночам. И я подскажу. Это - укоризненный взгляд Вашей модели, из которой Вы не удосужились извлечь главные результаты моделирования - матрицы $\\Phi$ и $\\Theta$ (последнюю, конечно, лишь в том случае, если Вы задали перед обучением флаг model.cache_theta = True).\n", "\n", "Сделать это богоугодное дело можно вот так:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "phi = model.get_phi()\n", "theta = model.get_theta()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Оригинально, не правда ли? В жизни не догадаться. Естественно, тащить матрицы можно не целиком, а по частям: в случае $\\Phi$ - по темам и модальностям, в случае $\\Theta$ - только по темам. Делается это с помощью уже знакомых Вам полей topic_names и class_ids, и по тому же принципу, по которому эти поля использовались раньше.\n", "\n", "В общем, я надеюсь, у Вас хватит ума и смелости справиться с такой задачей без подробных примеров.\n", "\n", "Оба метода вернут Вам pandas.DataFrame-ы, потом делайте с ними всё, что требуется в Вашей задаче, библиотека более в процессе не участвует." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь поговорим о незавидной участи модели. Как правило, после использования модели развоплощаются, возвращаясь в Астрал, однако Вы можете задержать особенно понравившийся Вам экземпляр на своём грешном диске с помощью метода save():" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.save(filename='my_saved_model')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Модель будет сохранена в бинарном формате на диск и будет спать, пока не потребуется Вам снова. В этом случае просто призовите её заклятием load():" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.load(filename='my_saved_model')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Тут есть один тонкий момент: модель - она, в некоторой степени, тоже человек, и тоже может забывать разные вещи, события, данные. Как правило, после пробуждения и загрузки в память, модель не помнит ничего, кроме матрицы $\\Phi$ и сопутствующей информации (число и имена тем, имена (но не веса!) модальностей и некоторые другие параметры). Поэтому от Вас потребуется некоторые усилия, чтобы вернуть модели все метрики, регуляризаторы, веса модальностей и важные для Вас параметры, вроде cache_theta. Если Вам это всё, конечно, нужно. Делается это в тех случаях, когда обучение модели с нуля занимает очень много времени, настолько много, что уж легче прописать заново параметры." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ну и последнее, чего мы коснёмся в этом разделе - словари. Да, снова эти словари. На этот раз они претендуют на то, чтобы поразить Вас своей возможностью фильтрации. Сейчас самое время вспомнить вид словаря, выгруженного в текстовом виде. Это были строки, каждая соответствует одному слову, и в ней 5 значений: само слово (строка), его модальность (строка), его value (число), и две загадочных величины token_tf и token_df (тоже числа). Раскроем, наконец, их смысл, который, в общем-то, довольно прост: это, соответственно, частота слова в коллекции (нормировкой это величины получается исходное value) и подокументная частота (т. е. в скольки документах коллекции слово встретилось хотя бы раз). Эти величины, как и value, генерируются библиотекой при сборе словаря. Отличие от value в том, что они не используются в регуляризаторах и метриках (пока что, по крайней мере). Поэтому их менять бессмысленно. И даже вредно. Очень не рекомендную. ~~Укушу.~~\n", "\n", "Зачем же они нужны? А для того, чтобы фильтровать словарь коллекции! Наверняка Вам не нужны слишком редкие, или же слишком частые слова. Ну или просто хочется уменьшить словарь, чтобы модель влезала в память. Или же банально на улице плохая погода, Вам стало грустно и захотелось что-то пофильтровать. Неважно. Во всех случаях решение одно, и оно очень простое - Dictionary.filter(). Посмотрите, какие у этого метода есть параметры здесь http://bigartm.readthedocs.org/en/master/python_interface/dictionary.html. Итак, будем фильтровать модальность обычных слов:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "dictionary.filter(min_tf=10, max_tf=2000, min_df_rate=0.01)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "На всякий пожарный, или какой-либо другой случай, поясню: если суффикса \\_rate нет, то используется абсолютная величина, если есть - нормированная (т. е. от 0 до 1).\n", "\n", "У этого заклинания есть одна особенность - оно перезаписывает старый словарь новым. Поэтому, если не хотите потерять полноценный нефильтрованный артефакт, сохраните его сперва на диск, а потом уже с оставшейся в памяти копией творите всё, что Вам заблагорассудится (но постоянно помня про Уголовный Кодекс!)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Летопись пятая. Вы уже почти перешли черту, ограничивающую человеческое сознание от хаоса безумия (всё, что связано с когерентностью и словарями совстречаемостей слов)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Ну что за жизнь без риска? Как говориться, кто не считает когерентность, тот не пьёт... И не ест. И не спит. Хотя не, ошибочка, наоборот, это про тех, кто считает... Ну не суть, переходим к делу." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ToDo." ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.11" } }, "nbformat": 4, "nbformat_minor": 0 }