{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# BigARTM. Руководство для пользователей Python API." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Автор - **Мурат Апишев** (great-mel@yandex.ru)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Этот ноутбук представляет собой руководство по использованию библиотеки в основных случаях использования. Свои вопросы по случаям, не рассмотренным в данном документе, присылайте в сообщество bigartm-users@googlegroups.com." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Предполагается, что Вы в точности выполнили инструкции по установке библиотеки и её настройки для использования из Python (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": [ { "name": "stdout", "output_type": "stream", "text": [ "0.8.0\n" ] } ], "source": [ "import artm\n", "\n", "print artm.version()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Каждый из описанных далее сценариев является отдельным блоком кода, не зависящим от других (за исключением самого первого, про словари и батчи - его надо выполнять всегда). Код рабочий (в том смысле его можно копипастить в свои скрипты), в том случае, если Вы верно подготовите все требуемые данные и разместите их там, где нужно.\n", "\n", "Прежде, чем задавать вопросы по указанному выше адресу, убедись в том, что он не связан с Вашими некорректными действиями. Одна из наиболее типичных ошибок имеет примерно такой вид:\n", "\n", "DiskReadException: File vocab.kos.txt does not exist.\n", "\n", "Скорее всего, она связана с тем, что Вы не подготовили данные, неверно разместили их, либо неверно назвали файл с ними (дважды написали расширение или что-то в этом роде).\n", "\n", "Также предполагается, что Вы владеете языком Python на достаточном уровне, вопросы по особенностям языка лучше задавать на соответствующих интернет-ресурсах." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Словари и батчи в BigARTM" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Прежде, чем приступать непосредственно к моделированию, необходимо привести данные к формату, подходящему для использования библиотекой. Сперва ознакомьтесь с форматами сырых данных, которые можно подавать BigARTM (http://bigartm.readthedocs.org/en/master/formats.html). Задача подготовки файла в одном из таких форматах лежит на Вас. Перевод же этих данных во внутренний формат библиотеки (пакеты документов, именуемы батчами), можно проделать с помощью создания объекта класса BatchVectorizer.\n", "\n", "Впрочем, есть один более простой вариант обработки Вашей коллекции на тот случай, если она не слишком велика и Вам не нужно сохранять её в батчи. Для этого Вам необходимо получить для своей коллекции переменную n\\_wd типа numpy.ndarray размера \"число уникальных слова в коллекции\" на \"число документов\", содержащую счётчики $n_{wd}$ (т. е. матрицу \"мешка слов\") и Python dict vocabulary в котором ключом является индекс строки этой матрицы, а значением - исходное слово. Получить такие переменные при наличии у Вас сырых текстов проще всего с использованием CountVectorizer (или схожих классов) из sklearn.\n", "\n", "При наличии этих переменных можно запустить следующий код:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "batch_vectorizer = artm.BatchVectorizer(data_format='bow_n_wd', n_wd=n_wd, vocabulary=vocabulary)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Итак, в том случае, если у Вам есть данные в формате 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": [ "Встроенный парсер библиотеки преобразовал Ваши данные в батчи, обернув их в объект класса BatchVectorizer, который является универсальным типом входных данных для всех методов Python API, прочесть о нём можно тут http://bigartm.readthedocs.org/en/master/python_interface/batches_utils.html. Сами батчи разместились в директории, которую Вы указали как target_folder." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если же у Вас есть файл в формате 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": [ "Результат аналогичен описанному выше." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Важно**: если Вы один раз проделали операцию по созданию батчей из исходных файлов, то в дальнейшем перезапускать этот процесс не нужно, поскольку для больших коллекций он довольно емкий по времени. Вместо этого достаточно запустить следующий код, который создаст BatchVectorizer на основе существующих батчей (данная операция мгновенная):" ] }, { "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": [ "Следующая цель после создания батчей - создание словаря. Они хранят информацию обо всех уникальных словах в коллекции. Словарь создаётся вне модели, и различными способами (посмотреть их все Вы можете вот тут http://bigartm.readthedocs.org/en/master/python_interface/dictionary.html).\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": [ "В таком варианте слова в словаре (и в дальнейшей матрице $\\Phi$) будут идти в случайном порядке. Если Вам требуется сохранить какой-то порядок, создайте файл вида 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": [ "Важно понимать, что в случае использования файла vocab этого библиотека будет работать только с теми словами, которые Вы указали в этом файле, прочие слова батчей будут игнорироваться. Словари содержат много различной полезной информации о коллекции. В них каждому слову соответствует переменная - value. Когда библиотека собирает словарь, она в эту переменную кладёт относительную частоту соответствующего слова во всей коллекции. О том, что можно делать с этой переменной, будет рассказано в последующий разделах.\n", "\n", "Итак, теперь у Вас есть словарь. Его можно сохранять на диск, чтобы не пересоздавать каждый раз. Сохранять можно в бинарном виде:" ] }, { "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": [ "Кроме того, работая с текстовым словарём, Вы можете изменять его содержимое (менять значения поля value, например), после загрузки изменённого словаря в библиотеку эти правки будут учтены." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Последний момент: все методы создания BatchVectorizer автоматически генерируют словарь по умолчанию, доступ к которому можно получить, написав:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "batch_vectorizer.dictionary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если Вам это, по каким-то причинам, не нужно, при создании BatchVectorizer нужно добавить параметр gather_dictionary=False.\n", "Этот флаг будет проигнорирован только в случае data_format равного n\\_wd, поскольку в этом случае другого способа получить словарь не существует." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Раздел 1: обучение базовой модели PLSA с подсчётом перплексии." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В этот момент Вам необходимо иметь следующие объекты:\n", "\n", "- директория названием my_collection_batches, а в ней - батчи и словарь в бинарном файле my_dictionary.dict, директория должна лежать рядом с этим ноутбуком;\n", "- переменная-словарь my_dictionary, в которой этот самый словарь есть (собран или загружен).\n", "- переменная batch_vectorizer (именно такая, какая создавалась выше).\n", "\n", "Если всё в порядке, можно приступить к созданию модели. Прежде всего рекомендуется ознакомиться со спецификацией класса ARTM, представляющего собой модель (http://bigartm.readthedocs.org/en/master/python_interface/artm.html). Затем для создания модели можно использовать следующий код:" ] }, { "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), она инициализирована случайным образом. Необходимо учитывать, что случайное приближение по умолчанию генерируется всегда с одним и тем же random seed (для воспроизводимости результатов). Если Вы хотите получить иное приближение, воспользуйтесь параметром seed класса ARTM (его различные неотрицательные целочисленные значения будут приводить к различным случайным начальным приближениям).\n", "\n", "С этого момента можно начинать процесс обучения модели, однако, как правило, сперва на модель навешиваются различные метрики качества моделирования. Сейчас мы воспользуемся перплексией.\n", "\n", "Оперирование метриками происходит через поле scores класса ARTM, добавить метрику перплексии можно так:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.scores.add(artm.PerplexityScore(name='my_fisrt_perplexity_score',\n", " dictionary=my_dictionary))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ответ на вопрос о смысле параметров метрики можно найти здесь http://bigartm.readthedocs.org/en/master/python_interface/scores.html. Важно запомнить, что подключать перплексию нужно именно так.\n", "\n", "**Важный момент**: если Вы попытаетесь создать вторую метрику с тем же именем - вызов будет проигнорирован (это позволяет безопасно перезапускать ячейки кода, создающие метрики в Jupyter notebook).\n", "\n", "Теперь перейдём к главному действию - обучению модели. Сделать это можно одним из двух способов: онлайновым или оффлайновым. Обучение производят методы fit_online() и fit_offline() соответственно. Предполагается, что Вы знакомы с особенностями этих алгоритмов, но кратко напомню:\n", "\n", "- Оффлайновый алгоритм: много проходов по коллекции, один проход по документу (опционально), обновление Φ в конце каждого прохода. Используйте, если у Вас маленькая коллекция.\n", "\n", "- Онлайновый алгоритм: один проход по коллекции (опционально), много проходов по документу, обновление Φ раз в заданное количество батчей. Используйте при большой коллекции, и коллекции с быстро меняющейся тематикой.\n", "\n", "Параметры этих методов можно найти в ранее указанном документации http://bigartm.readthedocs.org/en/master/python_interface/artm.html. Мы воспользуемся оффлайновым обучением здесь и во всех остальных примерах (правильное использование онлайнового алгоритма - это почти искусство, об этом рассказывается в отдельном соответствующем туториале). \n", "\n", "Итак, приступим к обучению:" ] }, { "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. Это поле класса ARTM, отвечающее за хранение результатов подсчёта метрик. Он запоминает значения всех метрик на момент каждого обновления матрицы $\\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": [ "О том, какие поля результатов есть у каждой из метрик, и как их правильно извлекать, написано здесь http://bigartm.readthedocs.org/en/master/python_interface/score_tracker.html.\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. При этом изменение поля seed конструктора соответствующим образом скажется на вызове initialize().\n", "\n", "Заметим, что везде, где принимается переменная словарь, может приниматься и его имя-строка:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.initialize(dictionary=my_dictionary.name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Раздел 2: регуляризованная модель PLSA и новые метрики." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "BigARTM - это проект, эффективно реализующий теорию аддитивной регуляризации тематических моделей К. В. Воронцова. АРТМ является более гибкой заменой существующего байесовского подхода. В основе теории лежат регуляризаторы, предполагается, что Вы знакомы с тем, что это такое." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "В библиотеке есть предопределённый набор регуляризаторов (при необходимости можно создавать новые, о создании регуляризаторов и метрик качества написано в отдельном соответствующем пособии). Сейчас мы будем учиться ими пользоваться.\n", "\n", "Предполагается, что все требования, предъявленные в начале первого раздела, выполнены. Итак, создадим модель, добавим к ней метрику перплексии:" ] }, { "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": [ "Сразу следует отметить смысл флага cache_theta. Обрабатываемые коллекции могут иметь довольно большие размеры в смысле числа документов, и матрица $\\Theta$ для них может занимать слишком много места, что часто неприемлемо. Для этих целей существует флаг cache_theta, который позволяет запретить хранение это матрицы. По умолчанию матрица сохраняется, чтобы Вы имели возможность на неё посмотреть. В тех случаях, когда надо посмотреть на распределения документов, в $\\Theta$ в память не влезает, можно воспользоваться методов ARTM.transform() (о нём будет подробно рассказано в соответствующем разделе)." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Теперь попробуем добавить другие метрики, поскольку перплексия далеко не единственная и не лучшая (снова делаю отсылку к списку метрик и их параметров 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 - получается, что он и сглаживает, и разреживает? Именно так. Он может и то, и то, его действия будут зависеть от того, каким Вы зададите его коэффициент регуляризации $\\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'].tau = 1e+5" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Выставленные значения стандартны, но при неблагоприятном стечении обстоятельств могут либо не оказать на модель существенного влияния, либо существенно её ухудшить.\n", "\n", "Ещё раз обращаю Ваше внимание, что выставление и замена параметров регуляризаторов полностью аналогично тому, как это происходит у метрик.\n", "\n", "Запустим обучение модели повторно:" ] }, { "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", "Регуляризаторам, как и метрикам, тоже можно задать, какие темы можно трогать, а какие - нельзя. Делается это в полной аналогии с тем, как мы проделали это для метрик." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вернёмся теперь к словарям. Но для начала небольшое отступление. Рассмотрим принцип работы регуляризатора сглаживания/разреживания $\\Phi$. Он просто прибавляет ко всем счётчикам одну и ту же величину $\\tau$, что может оказаться неподходящей нам стратегией. Возможный сценарий: необходимость разреживания части слов при сглаживании другой части и игнорировании всех остальных. Для примера, хотим разреживать слова про магию, сглаживать - про котов, а остальные не трогать.\n", "\n", "В этой ситуации необходимо использовать словари.\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", "- выгружаете словарь в текстовом виде;\n", "- открываете его, каждая строка - одно слово, в строке 5 значений: слово - модальность - value - token_tf - token_df (это всё написано в первой строке-заголовке);\n", "- не обращайте внимания ни на что, кроме слова и его value; найдите интересующие Вас слова и выставьте им нужные значения value;\n", "- загрузите словарь обратно.\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 | что-то | что-то" ] }, { "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": [ "На этом данный раздел окончен, можно двигаться далее." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Раздел 3: построение мультимодальной тематической модели с регуляризацией и оцениванием качества; метод ARTM.transform()." ] }, { "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", "Пример:\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.0. Хотите использовать другие модальности и веса - надо задать эти требования в конструкторе модели следующим кодом:" ] }, { "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", "\n", "Итак, у нас имеется модель. Предполагается, что тестовые документы были убраны в отдельный файл в формате Vowpal Wabbit, на основе которого Вы сумели сгенерировать батчи, которые описываются переменной batch_vectorizer_test (см. вводный раздел). Также предполагается, что сохранили Вы тестовые батчи в отдельную директорию (не в ту, в которую сохранялись обучающие батчи).\n", "\n", "Ваши тестовые документы не должны содержать информацию о метках классов (а именно: в тестовом файле не должно быть строки '|@labels_class'), также тестовые документы не содержат слов, которых не было в документах обучающих, иначе такие слова будут проигнорированы.\n", "\n", "Если все эти условия выполнены, можно переходить к использованию ARTM.transform() (об этом написано тут http://bigartm.readthedocs.org/en/master/python_interface/artm.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": [ "### Раздел 4: извлечение $\\Phi$ и $\\Theta$, сохранение и загрузка модели, фильтрация словарей." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Вопросы, рассматриваемые в этой главе, носят технический характер, поэтому достаточно просты в изучении. Глава будет небольшая, но разобраться в ней стоит хорошо.\n", "\n", "Предположим, что у Вас есть данные, Вы обучили на них модель. Настроили все регуляризаторы, посмотрели на метрики качества. Однако набор метрик качества оказался недостаточным, и Вам требуется посчитать какие-то свои величины, основывающиеся на матрицах $\\Phi$ и $\\Theta$. В этом случае Вы можете извлечь эти матрицы наружу следующим кодом:" ] }, { "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": [ "Обязательно учтите, что для извлечения $\\Theta$ флаг cache_theta должен быть равен True. Извлечение матриц можно производить не только целиком (что произойдёт в коде выше), но и по темам (а для $\\Phi$ - еще и по модальностям).\n", "\n", "Оба метода возвращают pandas.DataFrame." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь перейдём к вопросу сохранения модели на диск для дальнейшего использования.\n", "\n", "Здесь важно понимать, что модель состоит из двух матриц - $\\Phi$ (она же $p_{wt}$) и $n_{wt}$. Для того, чтобы её можно было загрузить обратно и продолжить обучение с того же места, необходимо сохранять и загружать обе матрицы. Текущая версия библиотеки умеет сохранять только одну матрицу, поэтому придётся сделать два вызова save:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.save(filename='saved_p_wt', model_name='p_wt')\n", "model.save(filename='saved_n_wt', model_name='n_wt')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Модель будет сохранена в бинарном виде, её можно использовать в дальнейшем, достаточно загрузить эти файлы обратно вызовами:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "model.load(filename='saved_p_wt', model_name='p_wt')\n", "model.load(filename='saved_n_wt', model_name='n_wt')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Надо понимать, что модель после загрузки содержит только матрицы $\\Phi$ и $n_{wt}$ и сопутствующую информацию (число и имена тем, имена (но не веса!) модальностей и некоторые другие параметры). Поэтому от Вас потребуется некоторые усилия, чтобы вернуть модели все метрики, регуляризаторы, веса модальностей и важные для Вас параметры, вроде 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": [ "### Раздел 5: всё, что связано с когерентностью и словарями совстречаемости слов." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ToDo." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Раздел 6: attach_model и кастомная инициализация $\\Phi$-подобных матриц" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Библиотека поддерживает возможность прямого доступа ко всем $\\Phi$-подобным матрицам модели (в самом базовом случае это сама $\\Phi$ и $n_{wt}$) из Python. Эта функциональность относится к разряду низкоуровневых и поэтому находится не в самом классе ARTM, а в скрытом внутри него низкоуровневом интерфейсе master\\_component. Пользователь \"подключается\" к матрице, то есть получает объект-ссылку на неё в Python, и между итерациями алгоритма может вручную изменять её содержимое - информация будет напрямую записывать в память исполняемого кода на C++.\n", "\n", "Наиболее очевидной возможностью применения функциональности является кастомная инициализация матрицы $\\Phi$. По умолчанию библиотека инициализирует эту матрицу рандомными числами. В то же время, существует ряд более сложных и полезных методов инициализации, которые сейчас библиотека, к сожалению, не умеет производить \"из коробки\". Здесь приходит на помощь attach\\_model.\n", "\n", "Итак, присоединимся к матрице $\\Phi$ нашей модели:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "(_, phi_ref) = model.master.attach_model(model=model.model_pwt)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В этот момент можно распечатать матрицу $\\Phi$, чтобы посмотреть, как она выглядит:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "model.get_phi(model_name=model.model_pwt)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Следующий код можно выполнить для того, чтобы убедиться, что присоединение произошло успешно:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "for model_description in model.info.model:\n", " print model_description" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "На выходе получится что-то вроде этого\n", "\n", "-----\n", "\n", "name: \"nwt\"\n", "\n", "type: \"class artm::core::DensePhiMatrix\"\n", "\n", "num_topics: 50\n", "\n", "num_tokens: 2500\n", "\n", "-----\n", "\n", "name: \"pwt\"\n", "\n", "type: \"class __artm::core::AttachedPhiMatrix__\"\n", "\n", "num_topics: 50\n", "\n", "num_tokens: 2500\n", "\n", "-----\n", "\n", "Видно, что тип матрицы $\\Phi$ теперь изменился (раньше он был такой же, как у $n_{wt}$)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь предположим, что Вы создали матрицу pwt\\_new такого же размера со своими значениями. Запишем её значения в модель. __Важно__: значения нужно именно записывать, обращаясь к переменной phi_ref, присваивать ей свою матрицу нельзя, это приведёт к ошибке при последующей работе библиотеки." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "for tok in xrange(num_tokens):\n", " for top in xrange(num_topics):\n", " phi_ref[tok, top] = pwt_new[tok, top] # CORRECT!\n", " \n", "phi_ref = pwt_new # NO!!!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "После этого можно снова распечатать $\\Phi$ и убедиться, что значения изменились. Дальше работу с моделью можно продолжить, матрица будет иметь нужный Вам вид." ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python [default]", "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.12" } }, "nbformat": 4, "nbformat_minor": 0 }