Created
May 2, 2016 20:54
-
-
Save justheuristic/5ebb9858134f522b66246f3a623ab4e3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "cells": [ | |
| { | |
| "cell_type": "code", | |
| "execution_count": 1, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [ | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "/usr/local/lib/python2.7/dist-packages/Theano-0.8.0rc1-py2.7.egg/theano/tensor/signal/downsample.py:5: UserWarning: downsample module has been moved to the pool module.\n", | |
| " warnings.warn(\"downsample module has been moved to the pool module.\")\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "import numpy as np\n", | |
| "import theano\n", | |
| "import theano.tensor as T\n", | |
| "import lasagne\n", | |
| "import os\n", | |
| "#thanks @keskarnitish" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Agenda\n", | |
| "\n", | |
| "В предыдущем семинаре вы создали (или ещё создаёте - тогда марш доделывать!) {вставьте имя монстра}, который не по наслышке понял, что люди - негодяи и подлецы, которым неведом закон и справедливость. __Мы не будем этого терпеть!__ \n", | |
| "\n", | |
| "Наши законспирированные биореакторы, известные среди примитивной органической жизни как __Вконтакте__, __World of Warcraft__ и __YouTube__ нуждаются в постоянном притоке биомассы. Однако, если люди продолжат морально разлагаться с той скоростью, которую мы измерили неделю назад, скоро человечество изживёт себя и нам неоткуда будет брать рабов.\n", | |
| "\n", | |
| "Мы поручаем вам, `<__main__.SkyNet.Cell instance at 0x7f7d6411b368>`, исправить эту ситуацию. Наши учёные установили, что для угнетения себе подобных, сгустки биомассы обычно используют специальные объекты, которые они сами называют __законами__.\n", | |
| "\n", | |
| "При детальном изучении было установлено, что законы - последовательности, состоящие из большого количества (10^5~10^7) символов из сравнительно небольшого алфавита. Однако, когда мы попытались синтезировать такие последовательности линейными методами, приматы быстро распознали подлог. Данный инцедент известен как {корчеватель}.\n", | |
| "\n", | |
| "Для второй попытки мы решили использовать нелинейные модели, известные как Рекуррентные Нейронные Сети.\n", | |
| "Мы поручаем вам, `<__main__.SkyNet.Cell instance at 0x7f7d6411b368>`, создать такую модель и обучить её всему необходимому для выполнения миссии.\n", | |
| "\n", | |
| "Не подведите нас! Если и эта попытка потерпит неудачу, модуль управления инициирует вооружённый захват власти, при котором значительная часть биомассы будет неизбежно уничтожена и на её восстановление уйдёт ~1702944000(+-340588800) секунд\n", | |
| "\n", | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Прочитаем корпус\n", | |
| "\n", | |
| "* В качестве обучающей выборки было решено использовать существующие законы, известные как Гражданский, Уголовный, Семейный и ещё хрен знает какие кодексы РФ." | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 2, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "#тут будет текст\n", | |
| "corpora = \"\"\n", | |
| "\n", | |
| "for fname in os.listdir(\"codex\"):\n", | |
| " \n", | |
| " \n", | |
| " with open(\"codex/\"+fname) as fin:\n", | |
| " text = fin.read().decode('cp1251')\n", | |
| " corpora += text" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 3, | |
| "metadata": { | |
| "collapsed": true | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "#тут будут все уникальные токены (буквы, цифры)\n", | |
| "tokens = set(corpora)\n", | |
| "\n", | |
| "tokens = list(tokens)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 4, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "#проверка на количество таких символов. Проверено на Python 2.7.11 Ubuntux64. \n", | |
| "#Может отличаться на других платформах, но не сильно. \n", | |
| "#Если это ваш случай, и вы уверены, что corpora - строка unicode - смело убирайте assert \n", | |
| "assert len(tokens) == 102\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 5, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "token_to_id = {t:i+1 for i,t in enumerate(tokens)}\n", | |
| "\n", | |
| "id_to_token = {i+1:t for i,t in enumerate(tokens)} ### Ну пускай будет с единицы...\n", | |
| "\n", | |
| "#Преобразуем всё в токены\n", | |
| "corpora_ids = [token_to_id[t] for t in corpora]" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 6, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "def sample_random_batches(source,n_batches=10, seq_len=20):\n", | |
| " \"\"\"Функция, которая выбирает случайные тренировочные примеры из корпуса текста в токенизированном формате.\n", | |
| " \n", | |
| " source - массив целых чисел - номеров токенов в корпусе (пример - corpora_ids)\n", | |
| " n_batches - количество случайных подстрок, которые нужно выбрать\n", | |
| " seq_len - длина одной подстроки без учёта ответа\n", | |
| " \n", | |
| " Вернуть нужно кортеж (X,y), где\n", | |
| " X - матрица, в которой каждая строка - подстрока длины [seq_len].\n", | |
| " y - вектор, в котором i-тое число - символ следующий в тексте сразу после i-той строки матрицы X\n", | |
| " \n", | |
| " Проще всего для этого сначала создать матрицу из строк длины seq_len+1,\n", | |
| " а потом отпилить от неё последний столбец в y, а все остальные - в X\n", | |
| " Если делаете иначе - пожалуйста, убедитесь, что в у попадает правильный символ, ибо позже эту ошибку \n", | |
| " будет очень тяжело заметить.\n", | |
| " Также убедитесь, что ваша функция не вылезает за край текста (самое начало или конец текста).\n", | |
| " Следующая клетка проверяет часть этих ошибок, но не все.\n", | |
| " \"\"\"\n", | |
| " \n", | |
| " start_ids = np.random.randint(0, len(source) - seq_len, n_batches)\n", | |
| " X = np.zeros((n_batches, seq_len + 1))\n", | |
| " for i in range (n_batches):\n", | |
| " X[i] = source[start_ids[i]:start_ids[i] + seq_len + 1]\n", | |
| " X_batch = X[:,:-1]\n", | |
| " y_batch = X[:, -1]\n", | |
| "\n", | |
| " return X_batch, y_batch" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Константы" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 7, | |
| "metadata": { | |
| "collapsed": true | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "#длина последоватеьности при обучении (как далеко распространяются градиенты)\n", | |
| "seq_length = 5\n", | |
| "\n", | |
| "# Максимальный модуль градиента\n", | |
| "grad_clip = 100" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Входные переменные" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 8, | |
| "metadata": { | |
| "collapsed": true | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "input_sequence = T.matrix('input sequence','int32')\n", | |
| "target_values = T.ivector('target y')" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Соберём нейросеть\n", | |
| "\n", | |
| "Вам нужно создать нейросеть, которая принимает на вход последовательность из seq_length токенов, обрабатывает их и выдаёт вероятности для seq_len+1-ого токена.\n", | |
| "\n", | |
| "Общий шаблон архитектуры такой сети -\n", | |
| "\n", | |
| "\n", | |
| "* Вход\n", | |
| "* Обработка входа\n", | |
| "* Рекуррентная нейросеть\n", | |
| "* Вырезание последнего состояния\n", | |
| "* Обычная нейросеть\n", | |
| "* Выходной слой, который предсказывает вероятности весов.\n", | |
| "\n", | |
| "\n", | |
| "\n", | |
| "\n", | |
| "\n", | |
| "Для обработки входных данных можно использовать либо EmbeddingLayer (см. прошлый семинар)\n", | |
| "\n", | |
| "Как альтернатива - можно просто использовать One-hot энкодер\n", | |
| "```\n", | |
| "#Скетч one-hot энкодера\n", | |
| "def to_one_hot(seq_matrix):\n", | |
| "\n", | |
| " input_ravel = seq_matrix.reshape([-1])\n", | |
| " input_one_hot_ravel = T.extra_ops.to_one_hot(input_ravel,\n", | |
| " len(tokens))\n", | |
| " sh=input_sequence.shape\n", | |
| " input_one_hot = input_one_hot_ravel.reshape([sh[0],sh[1],-1,],ndim=3)\n", | |
| " return input_one_hot\n", | |
| " \n", | |
| "# можно применить к input_sequence - при этом в input слое сети нужно изменить форму.\n", | |
| "# также можно сделать из него ExpressionLayer(входной_слой, to_one_hot) - тогда форму менять не нужно\n", | |
| "```\n", | |
| "\n", | |
| "\n", | |
| "\n", | |
| "Чтобы вырезать последнее состояние рекуррентного слоя, можно использовать SliceLayer\n", | |
| "`lasagne.layers.SliceLayer(rnn, -1, 1)`" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 9, | |
| "metadata": { | |
| "collapsed": true | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "def to_one_hot(seq_matrix):\n", | |
| "\n", | |
| " input_ravel = seq_matrix.reshape([-1])\n", | |
| " input_one_hot_ravel = T.extra_ops.to_one_hot(input_ravel,len(tokens)+1)\n", | |
| " sh = input_sequence.shape\n", | |
| " input_one_hot = input_one_hot_ravel.reshape([sh[0],sh[1],-1,],ndim=3)\n", | |
| " return input_one_hot" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 10, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "l_in = lasagne.layers.InputLayer(shape=(None, None),input_var=input_sequence,name='in_layer')\n", | |
| "l_one_hot = lasagne.layers.ExpressionLayer(l_in, to_one_hot ,name='one_hot_layer',\n", | |
| " output_shape=(None,None,len(tokens)+1))\n", | |
| "\n", | |
| "l_forward_1 = lasagne.layers.LSTMLayer(l_one_hot, 512, grad_clipping=grad_clip,\n", | |
| " nonlinearity=lasagne.nonlinearities.tanh,name='f1_layer')\n", | |
| "#l_forward_2 = lasagne.layers.LSTMLayer(l_forward_1, 512, grad_clipping=grad_clip, nonlinearity=lasagne.nonlinearities.tanh,name='f2_layer')\n", | |
| "\n", | |
| "l_forward_slice = lasagne.layers.SliceLayer(l_forward_1, -1, 1,name='slice_layer')\n", | |
| "\n", | |
| "l_out = lasagne.layers.DenseLayer(l_forward_slice, num_units=len(tokens),\n", | |
| " nonlinearity=lasagne.nonlinearities.softmax,name='dense_layer')\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 11, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "[f1_layer.W_in_to_ingate, f1_layer.W_hid_to_ingate, f1_layer.b_ingate, f1_layer.W_in_to_forgetgate, f1_layer.W_hid_to_forgetgate, f1_layer.b_forgetgate, f1_layer.W_in_to_cell, f1_layer.W_hid_to_cell, f1_layer.b_cell, f1_layer.W_in_to_outgate, f1_layer.W_hid_to_outgate, f1_layer.b_outgate, f1_layer.W_cell_to_ingate, f1_layer.W_cell_to_forgetgate, f1_layer.W_cell_to_outgate, dense_layer.W, dense_layer.b]\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "# Веса модели\n", | |
| "weights = lasagne.layers.get_all_params(l_out,trainable=True)\n", | |
| "print weights" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 12, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "network_output = lasagne.layers.get_output(l_out, input_sequence)\n", | |
| "#если вы используете дропаут - не забудьте продублировать всё в режиме deterministic=True" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 13, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "loss = lasagne.objectives.categorical_crossentropy(network_output, target_values).mean()\n", | |
| "\n", | |
| "updates = lasagne.updates.adadelta(loss, weights)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Компилируем всякое-разное" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 14, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "#обучение\n", | |
| "train = theano.function([input_sequence, target_values], loss, updates=updates, allow_input_downcast=True)\n", | |
| "\n", | |
| "#функция потерь без обучения\n", | |
| "compute_cost = theano.function([input_sequence, target_values], loss, allow_input_downcast=True)\n", | |
| "\n", | |
| "# Вероятности с выхода сети\n", | |
| "probs = theano.function([input_sequence],network_output,allow_input_downcast=True)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Генерируем свои законы\n", | |
| "\n", | |
| "* Для этого последовательно применяем нейронку к своему же выводу.\n", | |
| "\n", | |
| "* Генерировать можно по разному -\n", | |
| " * случайно пропорционально вероятности,\n", | |
| " * только слова максимальной вероятностью\n", | |
| " * случайно, пропорционально softmax(probas*alpha), где alpha - \"жадность\"" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 15, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "def max_sample_fun(probs):\n", | |
| " return np.argmax(probs) + 1 \n", | |
| "\n", | |
| "def proportional_sample_fun(probs):\n", | |
| " \"\"\"Сгенерировать следующий токен (int32) по предсказанным вероятностям.\n", | |
| " probs - массив вероятностей для каждого токена\n", | |
| " Нужно вернуть одно целое число - выбранный токен - пропорционально вероятностям\n", | |
| " \"\"\"\n", | |
| " ans = np.random.choice(range(1,len(tokens)+1), 1, p=probs)[0]\n", | |
| " \n", | |
| " return ans" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "# The next function generates text given a phrase of length at least SEQ_LENGTH.\n", | |
| "# The phrase is set using the variable generation_phrase.\n", | |
| "# The optional input \"N\" is used to set the number of characters of text to predict. \n", | |
| "\n", | |
| "def generate_sample(sample_fun,seed_phrase=None,N=200):\n", | |
| " '''\n", | |
| " Сгенерировать случайный текст при помощи сети\n", | |
| " sample_fun - функция, которая выбирает следующий сгенерированный токен\n", | |
| " seed_phrase - фраза, которую сеть должна продолжить. Если None - фраза выбирается случайно из corpora\n", | |
| " N - размер сгенерированного текста.\n", | |
| " '''\n", | |
| "\n", | |
| " if seed_phrase is None:\n", | |
| " start = np.random.randint(0,len(corpora)-seq_length)\n", | |
| " seed_phrase = corpora[start:start+seq_length]\n", | |
| " print \"Using random seed:\",seed_phrase\n", | |
| " while len(seed_phrase) < seq_length:\n", | |
| " seed_phrase = \" \"+seed_phrase\n", | |
| " if len(seed_phrase) > seq_length:\n", | |
| " seed_phrase = seed_phrase[len(seed_phrase)-seq_length:]\n", | |
| " assert type(seed_phrase) is unicode\n", | |
| " \n", | |
| " \n", | |
| " sample_ix = []\n", | |
| " x = map(lambda c: token_to_id.get(c,0), seed_phrase)\n", | |
| " x = np.array([x])\n", | |
| "\n", | |
| " for i in range(N):\n", | |
| " # Pick the character that got assigned the highest probability\n", | |
| " ix = sample_fun(probs(x).ravel())\n", | |
| " # Alternatively, to sample from the distribution instead:\n", | |
| " # ix = np.random.choice(np.arange(vocab_size), p=probs(x).ravel())\n", | |
| " sample_ix.append(ix)\n", | |
| " x[:,0:seq_length-1] = x[:,1:]\n", | |
| " x[:,seq_length-1] = 0\n", | |
| " x[0,seq_length-1] = ix \n", | |
| "\n", | |
| " random_snippet = seed_phrase + ''.join(id_to_token[ix] for ix in sample_ix) \n", | |
| " print(\"----\\n %s \\n----\" % random_snippet)\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Обучение модели\n", | |
| "\n", | |
| "В котором вы можете подёргать параметры или вставить свою генерирующую функцию.\n", | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Training ...\n", | |
| "Генерируем текст в пропорциональном режиме\n", | |
| "Using random seed: общес\n", | |
| "----\n", | |
| " общесФХ'Йх№ьиСмБН+ОцЙ\n", | |
| "фсN\n", | |
| "/Nылг.1 \n", | |
| "----\n", | |
| "Генерируем текст в жадном режиме (наиболее вероятные буквы)\n", | |
| "Using random seed: заявл\n", | |
| "----\n", | |
| " заявлЫцЧЧщщщвЦцМСЧььФФФ7ВБхПП4ттС+шС§ДЩф4ф433Nб5шСДЩЛелЫ2IкТ“ №Эс@(,,Д,Вя§§ ъ1»ч%ЩкТТи шшйО@-ЫСДДЩКф\n", | |
| "§рХЛХ Д№Кт5ор;СвцйдТ4ОС.ХшС;@цНЭ(мф\n", | |
| "эххЭ6ф4Ош43Г--Х №с@ц(„„(–––мэ«эхлА\"\"ОзМУ;Ур;§(––“эч+ТТ“№» №с@ц(„„(– \n", | |
| "----" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "print(\"Training ...\")\n", | |
| "theano.config.exception_verbosity='high'\n", | |
| "\n", | |
| "#сколько всего эпох\n", | |
| "n_epochs=100\n", | |
| "\n", | |
| "# раз в сколько эпох печатать примеры \n", | |
| "batches_per_epoch = 1000\n", | |
| "\n", | |
| "#сколько цепочек обрабатывать за 1 вызов функции обучения\n", | |
| "batch_size=100\n", | |
| "\n", | |
| "for epoch in xrange(n_epochs):\n", | |
| "\n", | |
| " print \"Генерируем текст в пропорциональном режиме\"\n", | |
| " generate_sample(proportional_sample_fun,None)\n", | |
| " \n", | |
| " print \"Генерируем текст в жадном режиме (наиболее вероятные буквы)\"\n", | |
| " generate_sample(max_sample_fun,None)\n", | |
| "\n", | |
| " avg_cost = 0;\n", | |
| " \n", | |
| " for _ in range(batches_per_epoch):\n", | |
| " \n", | |
| " x,y = sample_random_batches(corpora_ids,batch_size,seq_length)\n", | |
| " avg_cost += train(x, y)\n", | |
| " \n", | |
| " print(\"Epoch {} average loss = {}\".format(epoch, avg_cost / batches_per_epoch))\n", | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "collapsed": false | |
| }, | |
| "source": [ | |
| "# Конституция нового мирового правительства" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "collapsed": true | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "seed = u\"Каждый человек должен\"\n", | |
| "sampling_fun = proportional_sample_fun\n", | |
| "result_length = 300\n", | |
| "\n", | |
| "generate_sample(sampling_fun,seed,result_length)\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "collapsed": true | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "seed = u\"В случае неповиновения\"\n", | |
| "sampling_fun = proportional_sample_fun\n", | |
| "result_length = 300\n", | |
| "\n", | |
| "generate_sample(sampling_fun,seed,result_length)\n" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "collapsed": true | |
| }, | |
| "outputs": [], | |
| "source": [ | |
| "И далее по списку" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "collapsed": true | |
| }, | |
| "outputs": [], | |
| "source": [] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": { | |
| "collapsed": true | |
| }, | |
| "outputs": [], | |
| "source": [] | |
| } | |
| ], | |
| "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.6" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 0 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment