{"cells":[{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom nltk.tokenize import word_tokenize\nimport re\nimport random\nfrom gensim.models import KeyedVectors\nimport csv\nimport tensorflow as tf\nfrom keras.preprocessing.sequence import pad_sequences\nfrom keras.layers import Dense, Dropout, Embedding,LSTM, CuDNNLSTM ,ZeroPadding2D, Conv1D, MaxPooling1D, Flatten ,Input\nfrom keras.layers import Concatenate\nfrom keras.models import Sequential, Model\nfrom keras.preprocessing.text import Tokenizer\nfrom tqdm import tqdm,tqdm_notebook \nimport spacy\nfrom keras.models import load_model\nfrom keras import backend as K\nfrom keras.engine.topology import Layer\nfrom keras import initializers, regularizers, constraints, optimizers, layers\nfrom sklearn.metrics import f1_score\nimport h5py\nimport gc\nimport operator\n\ndftrain = pd.read_csv(\"../input/train.csv\")\ndftest = pd.read_csv(\"../input/test.csv\")\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The source of the punct string, dictionaries mispell_dict and contraction_mapping:\nhttps://www.kaggle.com/theoviel/improve-your-score-with-some-text-preprocessing"},{"metadata":{"trusted":true},"cell_type":"code","source":"punct = \"/-'?!.,#$%\\'()*+-/:;<=>@[\\\\]^_`{|}~\" + '\"\"“”’' + '∞θ÷α•à−β∅³π‘₹´°£€\\×™√²—–&'\nmispell_dict = {'colour': 'color', 'centre': 'center', 'favourite': 'favorite', 'travelling': 'traveling', 'counselling': 'counseling', 'theatre': 'theater', 'cancelled': 'canceled', 'labour': 'labor', 'organisation': 'organization', 'wwii': 'world war 2', 'citicise': 'criticize', 'youtu ': 'youtube ', 'Qoura': 'Quora', 'sallary': 'salary', 'Whta': 'What', 'narcisist': 'narcissist', 'howdo': 'how do', 'whatare': 'what are', 'howcan': 'how can', 'howmuch': 'how much', 'howmany': 'how many', 'whydo': 'why do', 'doI': 'do I', 'theBest': 'the best', 'howdoes': 'how does', 'mastrubation': 'masturbation', 'mastrubate': 'masturbate', \"mastrubating\": 'masturbating', 'pennis': 'penis', 'Etherium': 'Ethereum', 'narcissit': 'narcissist', 'bigdata': 'big data', '2k17': '2017', '2k18': '2018', 'qouta': 'quota', 'exboyfriend': 'ex boyfriend', 'airhostess': 'air hostess', \"whst\": 'what', 'watsapp': 'whatsapp', 'demonitisation': 'demonetization', 'demonitization': 'demonetization', 'demonetisation': 'demonetization'}\ncontraction_mapping = {\"ain't\": \"is not\", \"aren't\": \"are not\",\"can't\": \"cannot\", \"'cause\": \"because\", \"could've\": \"could have\", \"couldn't\": \"could not\", \"didn't\": \"did not\",  \"doesn't\": \"does not\", \"don't\": \"do not\", \"hadn't\": \"had not\", \"hasn't\": \"has not\", \"haven't\": \"have not\", \"he'd\": \"he would\",\"he'll\": \"he will\", \"he's\": \"he is\", \"how'd\": \"how did\", \"how'd'y\": \"how do you\", \"how'll\": \"how will\", \"how's\": \"how is\",  \"I'd\": \"I would\", \"I'd've\": \"I would have\", \"I'll\": \"I will\", \"I'll've\": \"I will have\",\"I'm\": \"I am\", \"I've\": \"I have\", \"i'd\": \"i would\", \"i'd've\": \"i would have\", \"i'll\": \"i will\",  \"i'll've\": \"i will have\",\"i'm\": \"i am\", \"i've\": \"i have\", \"isn't\": \"is not\", \"it'd\": \"it would\", \"it'd've\": \"it would have\", \"it'll\": \"it will\", \"it'll've\": \"it will have\",\"it's\": \"it is\", \"let's\": \"let us\", \"ma'am\": \"madam\", \"mayn't\": \"may not\", \"might've\": \"might have\",\"mightn't\": \"might not\",\"mightn't've\": \"might not have\", \"must've\": \"must have\", \"mustn't\": \"must not\", \"mustn't've\": \"must not have\", \"needn't\": \"need not\", \"needn't've\": \"need not have\",\"o'clock\": \"of the clock\", \"oughtn't\": \"ought not\", \"oughtn't've\": \"ought not have\", \"shan't\": \"shall not\", \"sha'n't\": \"shall not\", \"shan't've\": \"shall not have\", \"she'd\": \"she would\", \"she'd've\": \"she would have\", \"she'll\": \"she will\", \"she'll've\": \"she will have\", \"she's\": \"she is\", \"should've\": \"should have\", \"shouldn't\": \"should not\", \"shouldn't've\": \"should not have\", \"so've\": \"so have\",\"so's\": \"so as\", \"this's\": \"this is\",\"that'd\": \"that would\", \"that'd've\": \"that would have\", \"that's\": \"that is\", \"there'd\": \"there would\", \"there'd've\": \"there would have\", \"there's\": \"there is\", \"here's\": \"here is\",\"they'd\": \"they would\", \"they'd've\": \"they would have\", \"they'll\": \"they will\", \"they'll've\": \"they will have\", \"they're\": \"they are\", \"they've\": \"they have\", \"to've\": \"to have\", \"wasn't\": \"was not\", \"we'd\": \"we would\", \"we'd've\": \"we would have\", \"we'll\": \"we will\", \"we'll've\": \"we will have\", \"we're\": \"we are\", \"we've\": \"we have\", \"weren't\": \"were not\", \"what'll\": \"what will\", \"what'll've\": \"what will have\", \"what're\": \"what are\",  \"what's\": \"what is\", \"what've\": \"what have\", \"when's\": \"when is\", \"when've\": \"when have\", \"where'd\": \"where did\", \"where's\": \"where is\", \"where've\": \"where have\", \"who'll\": \"who will\", \"who'll've\": \"who will have\", \"who's\": \"who is\", \"who've\": \"who have\", \"why's\": \"why is\", \"why've\": \"why have\", \"will've\": \"will have\", \"won't\": \"will not\", \"won't've\": \"will not have\", \"would've\": \"would have\", \"wouldn't\": \"would not\", \"wouldn't've\": \"would not have\", \"y'all\": \"you all\", \"y'all'd\": \"you all would\",\"y'all'd've\": \"you all would have\",\"y'all're\": \"you all are\",\"y'all've\": \"you all have\",\"you'd\": \"you would\", \"you'd've\": \"you would have\", \"you'll\": \"you will\", \"you'll've\": \"you will have\", \"you're\": \"you are\", \"you've\": \"you have\" }\nspell=dict(mispell_dict)\nspell.update(contraction_mapping)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":""},{"metadata":{"trusted":true},"cell_type":"code","source":"train_ques=dftrain[\"question_text\"].fillna(\"_##_\").values\ntest_ques=dftest[\"question_text\"].fillna(\"_##_\").values\nids=dftest[\"qid\"].fillna(\"_##_\").values","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"features_nb=75000\nseq_len=80\ntkn=Tokenizer(lower = True, filters='', num_words=features_nb)\ntkn.fit_on_texts(train_ques)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":""},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"def preproc(words):\n    newwords=[]\n    for word in words:\n        punc=0\n        for p in punct:\n            if word==p:\n                punc=1\n        if punc==0:\n            word=word.lower()\n            word=re.sub('[0-9]{1,}','#',word)\n            for mispelling in spell.keys():\n                word=word.replace(mispelling,spell[mispelling])\n            newwords.append(word)\n    \n    return newwords\n\n\ndef vectorize(text):\n    questions=[]\n    for item in tqdm_notebook(text):   \n        i=word_tokenize(item)\n        i=preproc(i)\n        i=' '.join(i)\n        questions.append(i)\n    \n    seq=tkn.texts_to_sequences(questions)\n    seq = pad_sequences(seq,maxlen=seq_len)\n    \n    return seq\n\ntrain_data=vectorize(train_ques)\ntest_data=vectorize(test_ques)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def folds(k):\n    m=len(dftrain)//5\n    if(k==0):\n        test=train_data[0:m]\n        train=train_data[m:5*m]\n        y_test=train_labels[0:m]\n        y_train=train_labels[m:5*m]\n    else:\n        test=train_data[m*k:(k+1)*m]\n        train=np.concatenate((train_data[0:m*k] , train_data[(k+1)*m:5*m]))\n        y_test=train_labels[m*k:(k+1)*m]\n        y_train=np.concatenate((train_labels[0:m*k] , train_labels[(k+1)*m:5*m]))\n    \n    return test,y_test,train,y_train","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_labels=dftrain['target'].fillna(\"_##_\").values\ntest_samples,test_labels,train_samples,train_labels=folds(1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"file='../input/embeddings/GoogleNews-vectors-negative300/GoogleNews-vectors-negative300.bin'\nword2vec_index=KeyedVectors.load_word2vec_format(file, binary=True,limit=75000)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"index=tkn.word_index\nglove_index={}\nfile='../input/embeddings/glove.840B.300d/glove.840B.300d.txt'\nf=open(file)\nk=0\nfor line in tqdm_notebook(f):\n    components=line.split()\n    word=components[0]\n    vector=np.asarray(components[1:])\n    if len(vector)<301 and k<features_nb:\n        try:\n            i=index[word]\n            glove_index[word]=vector\n            k+=1\n        except KeyError:\n            pass\n    \nf.close()\nprint(k)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"del dftrain,train_data\ngc.collect()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"length=features_nb+1\nemb_matrix=np.zeros((length,300))\nfor word, i in index.items():\n    if i<features_nb:\n        try:    \n            emb_matrix[i]=np.asarray(glove_index[word],dtype='float32')*0.7 + word2vec_index[word]*0.3\n        except KeyError:\n            pass","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"del glove_index,word2vec_index\ngc.collect()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"x_array=np.vstack(train_samples)\n\ny_array=np.zeros((len(train_labels),2))\n\nfor i in range(len(train_labels)):\n    if int(train_labels[i])==0:\n        y_array[i]=np.array([1,0])\n    else:\n        y_array[i]=np.array([0,1])\n   \n\n\nx_validate=np.vstack(test_samples)\n\ny_validate=np.zeros((len(test_labels),2))\n\nfor i in range (len(test_labels)):\n    if int(test_labels[i])==0:\n        y_validate[i]=np.array([1,0])\n    else:\n        y_validate[i]=np.array([0,1])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#https://www.kaggle.com/artgor/eda-and-lstm-cnn/notebook\n\nclass Attention(Layer):\n    def __init__(self, step_dim,\n                 W_regularizer=None, b_regularizer=None,\n                 W_constraint=None, b_constraint=None,\n                 bias=True, **kwargs):\n        \"\"\"\n        Keras Layer that implements an Attention mechanism for temporal data.\n        Supports Masking.\n        Follows the work of Raffel et al. [https://arxiv.org/abs/1512.08756]\n        # Input shape\n            3D tensor with shape: `(samples, steps, features)`.\n        # Output shape\n            2D tensor with shape: `(samples, features)`.\n        :param kwargs:\n        Just put it on top of an RNN Layer (GRU/LSTM/SimpleRNN) with return_sequences=True.\n        The dimensions are inferred based on the output shape of the RNN.\n        Example:\n            model.add(LSTM(64, return_sequences=True))\n            model.add(Attention())\n        \"\"\"\n        self.supports_masking = True\n        #self.init = initializations.get('glorot_uniform')\n        self.init = initializers.get('glorot_uniform')\n\n        self.W_regularizer = regularizers.get(W_regularizer)\n        self.b_regularizer = regularizers.get(b_regularizer)\n\n        self.W_constraint = constraints.get(W_constraint)\n        self.b_constraint = constraints.get(b_constraint)\n\n        self.bias = bias\n        self.step_dim = step_dim\n        self.features_dim = 0\n        super(Attention, self).__init__(**kwargs)\n\n    def build(self, input_shape):\n        assert len(input_shape) == 3\n\n        self.W = self.add_weight((input_shape[-1],),\n                                 initializer=self.init,\n                                 name='{}_W'.format(self.name),\n                                 regularizer=self.W_regularizer,\n                                 constraint=self.W_constraint)\n        self.features_dim = input_shape[-1]\n\n        if self.bias:\n            self.b = self.add_weight((input_shape[1],),\n                                     initializer='zero',\n                                     name='{}_b'.format(self.name),\n                                     regularizer=self.b_regularizer,\n                                     constraint=self.b_constraint)\n        else:\n            self.b = None\n\n        self.built = True\n\n    def compute_mask(self, input, input_mask=None):\n        # do not pass the mask to the next layers\n        return None\n\n    def call(self, x, mask=None):\n        # eij = K.dot(x, self.W) TF backend doesn't support it\n\n        # features_dim = self.W.shape[0]\n        # step_dim = x._keras_shape[1]\n\n        features_dim = self.features_dim\n        step_dim = self.step_dim\n\n        eij = K.reshape(K.dot(K.reshape(x, (-1, features_dim)), K.reshape(self.W, (features_dim, 1))), (-1, step_dim))\n\n        if self.bias:\n            eij += self.b\n\n        eij = K.tanh(eij)\n\n        a = K.exp(eij)\n\n        # apply mask after the exp. will be re-normalized next\n        if mask is not None:\n            # Cast the mask to floatX to avoid float64 upcasting in theano\n            a *= K.cast(mask, K.floatx())\n\n        # in some cases especially in the early stages of training the sum may be almost zero\n        a /= K.cast(K.sum(a, axis=1, keepdims=True) + K.epsilon(), K.floatx())\n\n        a = K.expand_dims(a)\n        weighted_input = x * a\n    #print weigthted_input.shape\n        return K.sum(weighted_input, axis=1)\n\n    def compute_output_shape(self, input_shape):\n        #return input_shape[0], input_shape[-1]\n        return input_shape[0],  self.features_dim","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"inp = Input(shape=(seq_len,))\nemb = Embedding(features_nb+1,\n                        300,\n                        weights=[emb_matrix],\n                        trainable=False,\n                        input_length=seq_len)(inp)\nconv1=Conv1D(32, 3, activation='relu')(emb)\nmax_pool1=MaxPooling1D(pool_size=2)(conv1)\nconv2=Conv1D(32, 5, activation='relu')(emb)\nmax_pool2=MaxPooling1D(pool_size=2)(conv2)\nx=Concatenate(axis=1)([max_pool1,max_pool2])\nx=Flatten()(x)\nx=Dropout(0.2)(x)\nx=Dense(128,activation='relu')(x)\noutp=Dense(2, activation='softmax')(x)\ncnn = Model(inputs=inp, outputs=outp)\ncnn.compile(loss='binary_crossentropy',\n              optimizer='adam',\n              metrics=['accuracy'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"cnn.fit(x_array,y_array,epochs=3,batch_size=256,validation_data=(x_validate,y_validate))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def find_best_threshold(model):\n    pred_val_y = model.predict(x_validate)\n    best_thresh = 0.5\n    best_score = 0.0\n    for thresh in np.arange(0, 1, 0.01):\n        #thresh = np.round(thresh, 2)\n        score = f1_score(y_validate, (pred_val_y > thresh).astype(int),average='micro')\n        if score > best_score:\n            best_thresh = thresh\n            best_score = score\n    print(best_thresh)\n    print(\"Val F1 Score: {:.4f}\".format(best_score))\n    return best_thresh","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"find_best_threshold(cnn)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.layers import SpatialDropout1D , Bidirectional,CuDNNGRU,BatchNormalization\n\nsp=SpatialDropout1D(0.3)(emb)\ncgru=Bidirectional(CuDNNGRU(128,return_sequences=True))(sp)\nx=Attention(seq_len)(cgru)\nx=Dropout(0.2)(x)\nx=Dense(128,activation='relu')(x)\nx = BatchNormalization()(x)\noutgru=Dense(2, activation='softmax')(x)\ngru = Model(inputs=inp, outputs=outgru)\ngru.compile(loss='binary_crossentropy',\n              optimizer='adam',\n              metrics=['accuracy'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"gru.fit(x_array,y_array,epochs=3,batch_size=256,validation_data=(x_validate,y_validate))\nfind_best_threshold(gru)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.layers import AveragePooling1D\nz=Conv1D(32, 3, activation='relu')(cgru)\navgp=AveragePooling1D()(z)\nmaxp=MaxPooling1D()(z)\nz=Concatenate(axis=1)([avgp,maxp])\nz=BatchNormalization()(z)\nz=Dropout(0.2)(z)\nz=Dense(128,activation='relu')(z)\noutpool=Dense(2, activation='softmax')(x)\ngrupool = Model(inputs=inp, outputs=outpool)\ngrupool.compile(loss='binary_crossentropy',\n              optimizer='adam',\n              metrics=['accuracy'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"grupool.fit(x_array,y_array,epochs=3,batch_size=256,validation_data=(x_validate,y_validate))\nfind_best_threshold(grupool)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.layers import CuDNNLSTM\n\nlstm = Bidirectional(CuDNNLSTM(128, return_sequences = True))(sp)\nconvlstm=Conv1D(32, 3, activation='relu')(lstm)\nmaxlstm=MaxPooling1D(pool_size=2)(convlstm)\nconvgru=Conv1D(32, 3, activation='relu')(cgru)\nmaxgru=MaxPooling1D(pool_size=2)(convgru)\nx=Concatenate(axis=1)([maxlstm,maxgru])\nx=Flatten()(x)\nx=Dense(128,activation='relu')(x)\nx=Dropout(0.2)(x)\noutp=Dense(2, activation='softmax')(x)\ngru_lstm = Model(inputs=inp, outputs=outp)\ngru_lstm.compile(loss='binary_crossentropy',\n              optimizer='adam',\n              metrics=['accuracy'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"gru_lstm.fit(x_array,y_array,epochs=3,batch_size=256,validation_data=(x_validate,y_validate))\nfind_best_threshold(gru_lstm)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"gru2=Bidirectional(CuDNNGRU(64,return_sequences=True))(cgru)\nx=Attention(seq_len)(gru2)\nx=Dropout(0.2)(x)\nx=Dense(64,activation='relu')(x)\nx = BatchNormalization()(x)\noutgrux2=Dense(2, activation='softmax')(x)\ngrux2 = Model(inputs=inp, outputs=outgrux2)\ngrux2.compile(loss='binary_crossentropy',\n              optimizer='adam',\n              metrics=['accuracy'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"grux2.fit(x_array,y_array,epochs=3,batch_size=256,validation_data=(x_validate,y_validate))\nfind_best_threshold(grux2)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"del x_array,y_array,index, train_ques\ngc.collect()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def results(test_samples,ques_id):\n    res={}\n    qid=ques_id\n    questions=np.vstack(test_samples)\n    \n    predictions_cnn=cnn.predict(questions)\n    cnnThreshold=find_best_threshold(cnn)\n    predictions_gru=gru.predict(questions)\n    gruThreshold=find_best_threshold(gru)\n    predictions_pool=grupool.predict(questions)\n    poolThreshold=find_best_threshold(grupool)\n    predictions_gru_lstm=gru_lstm.predict(questions)\n    lstmThreshold=find_best_threshold(gru_lstm)\n    predictions_grux2=grux2.predict(questions)\n    gx2Threshold=find_best_threshold(grux2)\n    \n    predictions=np.zeros(len(qid))\n    for i in tqdm(range(len(qid))):\n        vote=0\n        if predictions_cnn[i][1]>cnnThreshold:\n            vote+=predictions_cnn[i][1]\n        if predictions_gru[i][1]>gruThreshold:\n            vote+=predictions_gru[i][1]\n        if predictions_pool[i][1]>poolThreshold:\n            vote+=predictions_pool[i][1]\n        if predictions_gru_lstm[i][1]>lstmThreshold:\n            vote+=predictions_gru_lstm[i][1]\n        if predictions_grux2[i][1]>gx2Threshold:\n            vote+=predictions_grux2[i][1] \n            \n        mean_threshold=(cnnThreshold + gruThreshold + poolThreshold + lstmThreshold + gx2Threshold)/5\n        if(vote>mean_threshold):\n            predictions[i]=1\n        else:\n            predictions[i]=0\n        \n    \n    for m,ids in tqdm_notebook(enumerate(qid)):\n        res[ids]=predictions[m]\n    \n    return res\n\nresults_dict=results(test_data,ids)","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-output":false,"trusted":true},"cell_type":"code","source":"def writeOutput(results):\n    header = [\"qid\", \"prediction\"]\n    output_file=open(\"submission.csv\", \"w\")\n    writer = csv.DictWriter(output_file,fieldnames=header)\n    writer.writeheader()\n    \n    m=0\n    k=0\n    \n    for item in results.keys():\n        if results[item]==1:\n            k+=1\n        else:\n            m+=1\n        ro={\"qid\":item,\"prediction\":int(results[item])}\n        writer.writerow(ro)\n    print(k)\n    print(k/len(results))\n    print(m)\n    print(m/len(results))\n    \n    output_file.close() \n    \n\nwriteOutput(results_dict)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"i=0\nfor i in item.","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.4"}},"nbformat":4,"nbformat_minor":1}