{"cells":[{"metadata":{"trusted":true,"_uuid":"54208499b32937d35a6a2b1247e6addef232362a"},"cell_type":"code","source":"import os\nprint(os.listdir(\"../input\"))\nprint(os.listdir(\"../input/embeddings\"))\nprint(os.listdir(\"../input/embeddings/wiki-news-300d-1M\"))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"88a53f801befa32318acb33b2b4389566eadfbbc"},"cell_type":"code","source":"# Check if GPU is enabled\nfrom keras import backend as K\nK.tensorflow_backend._get_available_gpus()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"46227fb1469e006796364aea1961c4ac8dae99c5"},"cell_type":"code","source":"import warnings\nwarnings.filterwarnings('ignore', category=RuntimeWarning)\n\nfrom IPython.display import display\n\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n%matplotlib inline\nfrom plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot\nimport plotly.graph_objs as go\ninit_notebook_mode(connected=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e906f4be7b5b05c1e58e80e280609849a9508040"},"cell_type":"code","source":"import pickle\nfrom keras.preprocessing.text import Tokenizer\nfrom sklearn.utils import shuffle\nfrom sklearn.preprocessing import OneHotEncoder","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"8de39b6f195d5fd49a0e17969baba4a792296264"},"cell_type":"code","source":"# Import the data","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b6648889088f9f09108ec73be5ee879dec5c4206"},"cell_type":"code","source":"input_dir = '../input/'\ntrain_file_path = input_dir + 'train.csv'\ntest_file_path = input_dir + 'test.csv'\n\nSUBMISSION_FILE_PATH = 'submission.csv'\nEVALUATION_FILE_PATH = 'model/evaluation.txt'\nMAX_SEQ_SIZE=150\nTRAIN_FRACTION = 0.9\n\nif not os.path.isdir('model'):\n    os.mkdir('model')\nTOKENIZER_FILE_PATH='model/token_model.pickle'\nMODEL_FILE_PATH='model/lstm_model'\n\n#WIKI_VECTORS_FILE = input_dir + 'embeddings/wiki-news-300d-1M/wiki-news-300d-1M.vec'\n#PARAGRAM_VECTORS_FILE = input_dir + 'embeddings/paragram_300_sl999/paragram_300_sl999.txt'\nGLOVE_VECTOR_FILE = input_dir + 'embeddings/glove.840B.300d/glove.840B.300d.txt'\n\nWORD_EMB_FILE = GLOVE_VECTOR_FILE\nVECTOR_LEN = 300","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"9ee31071eeb5f927367f297ad320cce893f6d12c"},"cell_type":"code","source":"# Clean the text\nimport re\nimport nltk\n#Looks like already downloaded: nltk.download('stopwords')\nfrom nltk.corpus import stopwords\nfrom nltk.stem import WordNetLemmatizer\n#from nltk.stem import SnowballStemmer\nimport string\n\n#Initialization\nstop_word_set = set(stopwords.words(\"english\"))\n#stemmer = SnowballStemmer('english')\nwordnet_lemmatizer = WordNetLemmatizer()\n\ndef clean_text(text):\n    \"\"\"Cleans and returns the given text/sentence by removing stop-words and stemming\"\"\"\n    ## Remove puncuation\n    text = text.translate(string.punctuation)\n    ##> Replace with spaces\n    text = re.sub(r\"[^A-Za-z0-9^,.\\/'+-=]\", \" \", text)\n    ##> Convert words to lower case\n    text = text.lower()\n    ##> Clean the text\n    text = re.sub(r\"\\'s\", \" \", text)\n    text = re.sub(r\"\\'ve\", \" have \", text)\n    text = re.sub(r\"n't\", \" not \", text)\n    text = re.sub(r\"i'm\", \"i am \", text)\n    text = re.sub(r\"\\'re\", \" are \", text)\n    text = re.sub(r\"\\'d\", \" would \", text)\n    text = re.sub(r\"\\'ll\", \" will \", text)\n    \n    ##> Stop word removal\n    ##split them\n    text = text.split()\n    ## Remove stop words\n    text = [w for w in text if not w in stop_word_set]\n    \n    ##> Stemming\n    #words = [stemmer.stem(word) for word in text]\n    ##> Lemmatize\n    words = [wordnet_lemmatizer.lemmatize(word) for word in text]\n    text = \" \".join(words)\n    return text\n\n#Test\nclean_text(\"This isn't likes of you doing! at alll?\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"24ffcfcf10eccc6593a1df57ca6c68f3fdf4c634"},"cell_type":"code","source":"import random\ndef balance_train_data(train_df, sincere_reduce_ratio=0.8):\n    \"\"\"\n    Remove the sincere training data from the train_df to make it more balanced\n    sincere_reduce_ratio=0.5 The sincere documents will be reduced by given percentage(approximately)\n    \"\"\"\n    print('-------Balancing---------')\n    tot_len = train_df.shape[0]\n    sincere_idx = train_df.index[train_df['target'] == 0].tolist()\n    print('Sincere questions before balancing:',len(sincere_idx))\n    rand_idx_size_approx = int(len(sincere_idx) * sincere_reduce_ratio)\n    rand_idx = list(set(random.choices(sincere_idx, k=rand_idx_size_approx)))\n    rand_idx.extend(list(set(random.choices(sincere_idx, k=rand_idx_size_approx))))\n    \n    print('Going to drop {} sincere questions'.format(len(rand_idx)))\n    train_df = train_df.drop(rand_idx)\n    train_df = train_df.reset_index(drop=True)\n    ln = train_df[train_df['target'] == 0].shape[0]\n    print('Sincere questions after balancing:',ln)\n    print('-------Balancing---------')\n    return train_df\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"1b40f5f74eb2a830e76d45db03cdd284cddffdd2"},"cell_type":"code","source":"# Train and Test Data\ntrain_df = pd.read_csv(train_file_path)\ntest_df = pd.read_csv(test_file_path)\n\nprint('Original Train shape:', train_df.shape)\n# Take the evaluation data first\nevaluation_df = train_df.tail(5000)\n\n#Take the sample data\ntrain_df = balance_train_data(train_df, sincere_reduce_ratio=0.5)\nprint('Re-balanced Train shape:', train_df.shape)\n\n#Small data\n#train_df = train_df.head(10000)\n\n#Clean the dataframes before using them\ntrain_df['question_text'] = train_df['question_text'].map(lambda t: clean_text(t))\ntest_df['question_text'] = test_df['question_text'].map(lambda t: clean_text(t))\nevaluation_df['question_text'] = evaluation_df['question_text'].map(lambda t: clean_text(t))\n\nprint('Evaluation data:')\ndisplay(evaluation_df.groupby(['target']).count())\n\nprint('Train Shape:', train_df.shape)\ndisplay(train_df.groupby(['target']).count())\ndisplay(train_df.head())\n\n# Prepare features and labels\nqstns_np = train_df.question_text\ntarget_labels = train_df.target","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"682458cd5fe231f516c6362b9601aa6516bf19bc"},"cell_type":"code","source":"# Vectorization","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"22a38b7cfb810a72d8809d89da33b06470882dbf"},"cell_type":"code","source":"#Features and lables\ndef vectorize_text(qstns_np, tokenizer_file_path, fit=True):\n    \"\"\"Tokenizes and then converts the them into sequences\n    qstns_np               List of strings\n    fit                    If true, the tokenizer will be fit on the given qstns_np\n    tokenizer_file_path    The file path where the tokenizer weights will be saved to or restored from\n    \n    Returns word_to_idx, idx_to_word, vocab_size, qstns_seqs\"\"\"\n    \n    if fit:\n        tokenizer = Tokenizer(filters='\"#$%&*+/:;<=>@[\\\\]^_`{|}~\\t\\n')\n        # Fit and then save\n        tokenizer.fit_on_texts(qstns_np)\n        with open(tokenizer_file_path, 'wb') as handle:\n            pickle.dump(tokenizer, handle, protocol=pickle.HIGHEST_PROTOCOL)\n        print('Saved the tokenizer model at', tokenizer_file_path)\n    else:\n        # Load the tokenizer\n        with open(tokenizer_file_path, 'rb') as handle:\n            tokenizer = pickle.load(handle)\n        print('Loaded the tokenizer model from', tokenizer_file_path)\n\n    qstns_seqs = tokenizer.texts_to_sequences(qstns_np)\n    vocab_size = len(tokenizer.word_index) + 1\n    return tokenizer.word_index, tokenizer.index_word, vocab_size, qstns_seqs    \n    \n## Test case\n_,_,_,_ = vectorize_text(qstns_np, TOKENIZER_FILE_PATH)\nwi,iw,vs,sq = vectorize_text(['Why is this common in india?', 'test'], TOKENIZER_FILE_PATH, fit=False)\nprint('Vocab Size:', vs)\nprint('Test seq:', sq)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"786de37de04de4ecc3b379d72d1a907cf2f8b38e"},"cell_type":"code","source":"def preprocess_sequences(qstns_seqs, max_seq_size=MAX_SEQ_SIZE):\n    \"\"\"\n    Trim down the token sequences qstns_seqs to max_seq_size\n    qstns_seqs         List of tokenized sequences\n    max_seq_size=MAX_SEQ_SIZE   Maximum length of the tokenized sequences(to trim down or to pad)\n\n    Returns the updated token sequences of shape (len(qstns_seqs), max_seq_size)\n    \"\"\"\n    #print('Before trim', np.array(qstns_seqs).shape)\n    new_qstns_seqs = []\n    for q in qstns_seqs:\n        # Zero Padding\n        if len(q) < max_seq_size:\n            q.extend(np.zeros(max_seq_size - len(q)))\n        # Trim down\n        if len(q) > max_seq_size:\n            q = q[:max_seq_size]\n        new_qstns_seqs.append(q)\n    #print('After trim', np.array(new_qstns_seqs).shape)\n    return new_qstns_seqs\n\ndef prepare_train_data(qstns_seqs, target_labels, train_ratio=TRAIN_FRACTION):\n    \"\"\"\n    qstns_seqs         List of tokenized sequences\n    target_labels      List of 0/1 labels\n    train_ratio=TRAIN_FRACTION    Percentage of random tokenized sequences to put in training set\n    \n    Returns train_x,train_y,test_x,test_y\n    \"\"\"\n    # Sequence preprocessing\n    qstns_seqs = preprocess_sequences(qstns_seqs, MAX_SEQ_SIZE)\n            \n    # Debug logs\n    s = 0\n    print('Sample x len', len(qstns_seqs[s]))\n    print('Sample x', qstns_seqs[s])\n    print('Sample y', target_labels[s])\n    print('---------------')\n        \n    # Select random train_ration samples\n    qstns_seqs, target_labels = shuffle(qstns_seqs, target_labels)\n    train_size = int(len(target_labels) * train_ratio)\n    train_x = np.array(qstns_seqs[:train_size])\n    test_x = np.array(qstns_seqs[train_size:])\n    \n    train_y = np.array(target_labels[:train_size])\n    test_y = np.array(target_labels[train_size:])\n    \n    #OneHot encoding of target labels\n    #onehot_enc = OneHotEncoder(categories='auto')\n    onehot_enc = OneHotEncoder()\n    onehot_enc.fit(train_y.reshape(-1,1))\n    train_y = onehot_enc.transform(train_y.reshape(-1,1)).toarray()\n    test_y = onehot_enc.transform(test_y.reshape(-1,1)).toarray()\n    \n    return train_x, train_y, test_x, test_y\n\n## Test case\n_,_,_,qstns_seqs = vectorize_text(qstns_np, TOKENIZER_FILE_PATH, fit=False)\ntrain_x, train_y, test_x, test_y = prepare_train_data(qstns_seqs, target_labels, train_ratio=TRAIN_FRACTION)\nprint('Train X shape:', train_x.shape)\nprint('Train Y shape:', train_y.shape)\nprint('Test X shape:', test_x.shape)\nprint('Test Y shape:', test_y.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"51981a4147ba25da365e3d39a44ab3dfc46a67f8"},"cell_type":"code","source":"## Word embeddings\n# Load in Embedding file\ndef load_word_emb(word_emb_file):\n    \"\"\"\n    Loads the word-embeddings from the given file path into a dictionary {word: embedding vector} and returns it.\n    \"\"\"\n    print('Loading word embedding:', word_emb_file)\n    model = {}\n    errors = 0\n    with open(word_emb_file,'r', errors='surrogateescape') as f:\n        for line in f:\n            splitLine = line.split()\n            word = splitLine[0]\n            try:\n                embedding = np.array([float(val) for val in splitLine[1:]])\n            except:\n                errors += 1\n            model[word] = embedding\n    print('Done', len(model), 'words loaded!')\n    print('Errorneous lines:', errors)\n    return model\n\ndef prepare_embedding_matrix(word_lookup, word_to_index, vocab_size, vector_len):\n    \"\"\"\n    Prepares the embedding_matrix of shape (vocab_size, vector_len)\n    word_lookup    Dictionary of {word: embeddings of length vector_len}\n    word_to_index  Dictionary of {word: token index in the tokenized vocabulary}\n    vocab_size     Number of unique words in the tokenized vocabulary\n    vector_len     Length of embedding vector\n    \n    Returns the embedding_matrix of shape (vocab_size, vector_len)\n    \"\"\"\n    embedding_matrix = np.zeros((vocab_size, vector_len))\n    word_not_found = 0\n    for i, word in enumerate(word_to_index.keys()):\n        vector = word_lookup.get(word, None)\n        if vector is not None:\n            embedding_matrix[i + 1, :] = vector\n        else:\n            word_not_found += 1\n    \n    print('WARNING: There were {0} words without pre-trained embeddings!'.format(word_not_found))\n    return embedding_matrix\n\n##>COSTLY\nword_lookup = load_word_emb(word_emb_file=WORD_EMB_FILE)\nprint('Loaded the ', len(word_lookup), ' word embeddings')\n\n# Test case\nembedding_matrix = prepare_embedding_matrix(word_lookup, wi, vs, vector_len=VECTOR_LEN)\nprint('Embedding matrix shape:', embedding_matrix.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"9c2481cedca425b4a6ecdfa72d28440f36e66965"},"cell_type":"code","source":"## Below cells build the actual network and training flow","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b0056508e675b20d76e8f491f2e52ced5aa0d989"},"cell_type":"code","source":"# Keras imports\nfrom keras.models import Sequential, load_model\nfrom keras.layers import LSTM, Dense, Dropout, Embedding, Masking, Bidirectional\nfrom keras.optimizers import Adam\nfrom keras.utils import plot_model\n\n# Build the LSTM network\ndef build_lstm_network(vocab_size,\n                       output_size,\n                       embedding_matrix,\n                       lstm_cells=64,\n                       trainable=False,\n                       lstm_layers=1,\n                       bi_direc=False,\n                       dropout=0.1):\n    \"\"\"\n    Builds a LSTM network that uses a pretrained embeddings with lstm_layers layers each containing lstm_cells cells and returns it.\n    vocab_size         Unique words in the tokenized vocabulary of training data\n    output_size        Output vector size produced by the network\n    embedding_matrix   Embedding matrix of shape (vocab_size, embedding length)\n    lstm_cells=64      Number of cells in each LSTM layer\n    trainable=False    Whether to train the pre-trained word embedding weights or not\n    lstm_layers=1      Number of LSTM layers in the network\n    bi_direc=False     Whether to add bi-directional layers(both past and future contexts)\n    dropout=0.1        Dropout value at LSTM layers\n    \"\"\"\n    lstm_model = Sequential()\n    \n    # Trainable embeddings or not\n    if trainable:\n        lstm_model.add(\n            Embedding(\n                input_dim=vocab_size,\n                output_dim=embedding_matrix.shape[1],\n                weights=[embedding_matrix],\n                trainable=True))\n    else:\n        lstm_model.add(\n            Embedding(\n                input_dim=vocab_size,\n                output_dim=embedding_matrix.shape[1],\n                weights=[embedding_matrix],\n                trainable=False,\n                mask_zero=True))\n        lstm_model.add(Masking())\n        \n    # Adding initial set of LSTM layers if lstm_layers is more than 1\n    if lstm_layers > 1:\n        for i in range(lstm_layers - 1):\n            lstm_model.add(\n                LSTM(\n                    lstm_cells,\n                    return_sequences=True,\n                    dropout=dropout,\n                    recurrent_dropout=dropout))\n    \n    # Adding the final LSTM layer\n    if bi_direc:\n        lstm_model.add(\n            Bidirectional(\n                LSTM(\n                lstm_cells,\n                return_sequences=False,\n                dropout=dropout,\n                recurrent_dropout=dropout)))\n    else:\n        lstm_model.add(\n            LSTM(\n                lstm_cells,\n                return_sequences=False,\n                dropout=dropout,\n                recurrent_dropout=dropout))\n        \n    # Rest of the network after LSTM layers\n    # Dense layer\n    lstm_model.add(Dense(64, activation='relu'))\n    \n    # Dropout of regularization\n    lstm_model.add(Dropout(0.5))\n    \n    # Output layer\n    lstm_model.add(Dense(output_size, activation='softmax'))\n    \n    #Compile the model\n    lstm_model.compile(\n        optimizer='adam',\n        loss='categorical_crossentropy',\n        metrics=['accuracy'])\n    \n    return lstm_model\n\nprint('LSTM network')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"da1d6ccf8d95b11a4662d139f41945abb1d54527"},"cell_type":"code","source":"# Training callbacks\nfrom keras.callbacks import EarlyStopping, ModelCheckpoint\n\ndef initialize_callbacks(model_file_path):\n    \"\"\"\n    Initialize a list of keras callbacks to be used during training process.\n    model_file_path    File path where the model file needs to be saved/checkpointed\n    \"\"\"\n    # Early stopping callback\n    callbacks = [EarlyStopping(monitor='val_loss', patience=10)]\n    \n    # Model saving callback\n    callbacks.append(\n        ModelCheckpoint(\n            '{0}.h5'.format(model_file_path),\n            save_best_only=True,\n            save_weights_only=False))\n        \n    return callbacks\n\n# Test case\ncallbacks = initialize_callbacks(model_file_path=MODEL_FILE_PATH)\nprint('Initialized the callbacks')","execution_count":null,"outputs":[]},{"metadata":{"scrolled":true,"trusted":true,"_uuid":"c06f2923064ace7fbf4be2dc601c85c6fe5b27b9"},"cell_type":"code","source":"# Start the training\nEPOCHS = 70\nBATCH_SIZE = 1500\nSAVE_MODEL = True\nVERBOSE = 1\n\n# Build the model\nlstm_model = build_lstm_network(vocab_size=vs,\n                   output_size=2,\n                   embedding_matrix=embedding_matrix,\n                   lstm_cells=64,\n                   trainable=True,\n                   lstm_layers=2,\n                   bi_direc=True,\n                   dropout=0.1)\nlstm_model.summary()\n\n# Train the model\ntrain_history = lstm_model.fit(\n    train_x,\n    train_y,\n    epochs=EPOCHS,\n    batch_size=BATCH_SIZE,\n    verbose=VERBOSE,\n    callbacks=callbacks,\n    validation_data=(test_x, test_y))\ntrain_history","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"52fdacd0185e4e861beb1f130b86fb62eee1d755"},"cell_type":"code","source":"# Run the submission data\ndef load_lstm_model(model_file_path):\n    \"\"\"\n    Load the trained model and return it\n    \"\"\"\n    lstm_model = load_model('{0}.h5'.format(model_file_path))\n    return lstm_model\n    \n# Test case\nlstm_model = load_lstm_model(model_file_path=MODEL_FILE_PATH)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"scrolled":false,"_uuid":"93d58f05b01149518847e5675029d38dbce046c9"},"cell_type":"code","source":"# Save evaluation results\nfrom sklearn.metrics import classification_report, confusion_matrix\n#TODO predict and output evaluation results evaluation_df\n\n# Sequence preprocessing\n_,_,_,eval_q_seqs = vectorize_text(evaluation_df.question_text, TOKENIZER_FILE_PATH, fit=False)\neval_y = evaluation_df.target\neval_q_seqs = preprocess_sequences(eval_q_seqs, MAX_SEQ_SIZE)\neval_x = np.array(eval_q_seqs)\neval_y = np.array(eval_y)\n\nonehot_enc = OneHotEncoder()\nonehot_enc.fit(eval_y.reshape(-1,1))\neval_y = onehot_enc.transform(eval_y.reshape(-1,1)).toarray()\n\nprint('Eval x:', eval_x.shape)\n#display(eval_x[:5])\nprint('Eval y:', eval_y.shape)\n#display(eval_y[:5])\n\ny_eval_pred = lstm_model.predict(eval_x)\ny_eval_pred_bin = np.argmax(y_eval_pred, axis=1)\n#OneHot to 0s and 1s\ny_eval_actual = [0 if t[0]==1 else 1 for t in eval_y]\n#print(confusion_matrix(y_true, y_pred))\nprint(classification_report(y_eval_actual, y_eval_pred_bin, target_names=['sincere', 'insincere']))\n\nevaluation_df['predicted'] = y_eval_pred_bin\ndisplay(evaluation_df.sample(10))\nevaluation_df.to_csv(EVALUATION_FILE_PATH, index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"bab0a5b3ab684ffb6003dbed31e503c9a2a729f6"},"cell_type":"code","source":"# Model performance metrics\nfrom sklearn.metrics import classification_report, confusion_matrix\n\ny_pred = lstm_model.predict(test_x)\ny_pred = np.argmax(y_pred, axis=1)\n#OneHot to 0s and 1s\ny_true = [0 if t[0]==1 else 1 for t in test_y]\n#print(confusion_matrix(y_true, y_pred))\nprint(classification_report(y_true, y_pred, target_names=['sincere', 'insincere']))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"344d151de925fc77fc9265cf13011c8f14047eca"},"cell_type":"code","source":"# Submission test data\nprint('Sample test data')\ndisplay(test_df.head())\ndef submission_results():\n    test_questions = test_df.question_text\n    test_qids = test_df.qid\n    \n    _,_,_,test_seq = vectorize_text(test_questions, TOKENIZER_FILE_PATH, fit=False)\n    submission_x = preprocess_sequences(test_seq, MAX_SEQ_SIZE)\n    submission_x = np.array(submission_x)\n    print('Submission X shape:', submission_x.shape)\n    \n    # Run the prediction\n    lstm_model = load_lstm_model(model_file_path=MODEL_FILE_PATH)\n    submission_y = lstm_model.predict(submission_x)\n    submission_y = np.argmax(submission_y, axis=1)\n    submission_df = test_df.copy(deep=True)\n    submission_df['prediction'] = submission_y\n    return submission_df\n\n##>COSTLY \nsubmission_df = submission_results()\ndisplay(submission_df.head())\nsubmission_df.loc[:,['qid','prediction']].to_csv(SUBMISSION_FILE_PATH, index=False)\nprint('----------####---------')\nprint('Saved the submission data to ', SUBMISSION_FILE_PATH)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"7aab996be9b1075835e07136eccfe8a23844d793"},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}