{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"model_name = 'jplu/tf-xlm-r-ner-40-lang'\ntokenizer_name = \"novinsh/xlm-roberta-large-toxicomments-12k\"","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"import os\n\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras.layers import Dense, Input\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.models import Model, load_model\nfrom tensorflow.keras.callbacks import ModelCheckpoint\nfrom kaggle_datasets import KaggleDatasets\nimport transformers\nfrom tqdm.notebook import tqdm\nfrom tokenizers import BertWordPieceTokenizer, SentencePieceBPETokenizer","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Detect hardware, return appropriate distribution strategy\ntry:\n    # TPU detection. No parameters necessary if TPU_NAME environment variable is\n    # set: this is always the case on Kaggle.\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n    print('Running on TPU ', tpu.master())\nexcept ValueError:\n    tpu = None\n\nif tpu:\n    tf.config.experimental_connect_to_cluster(tpu)\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    strategy = tf.distribute.experimental.TPUStrategy(tpu)\nelse:\n    # Default distribution strategy in Tensorflow. Works on CPU and single GPU.\n    strategy = tf.distribute.get_strategy()\n\nprint(\"REPLICAS: \", strategy.num_replicas_in_sync)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"AUTO = tf.data.experimental.AUTOTUNE\n\n# Data access\nGCS_DS_PATH = KaggleDatasets().get_gcs_path('jigsaw-multilingual-toxic-comment-classification')\n\n# Configuration\nif model_name == 't5-large':\n    EPOCHs = 1\n    BATCH_SIZE = 8 * strategy.num_replicas_in_sync\nelse:\n    EPOCHS = 3\n    BATCH_SIZE = 8 * strategy.num_replicas_in_sync\n\n\nMAX_LEN = 128","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Create fast tokenizer","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# First load the real tokenizer\ntokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer_name)\nif model_name == 't5-large':\n    tokenizer.pad_token = tokenizer.eos_token\n\n# Save the loaded tokenizer locally\ntokenizer.save_pretrained('.')\n# Reload it with the huggingface tokenizers library\n# fast_tokenizer = SentencePieceBPETokenizer('vocab.txt')\n# fast_tokenizer","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Load text data into memory\n","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"## Jigsaw","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"train1 = pd.read_csv(\"/kaggle/input/jigsaw-multilingual-toxic-comment-classification/jigsaw-toxic-comment-train.csv\")\ntrain2 = pd.read_csv(\"/kaggle/input/jigsaw-multilingual-toxic-comment-classification/jigsaw-unintended-bias-train.csv\")\ntrain2.toxic = train2.toxic.round().astype(int)\n\nvalid = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/validation.csv')\ntest = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/test.csv')\nsub = pd.read_csv('/kaggle/input/jigsaw-multilingual-toxic-comment-classification/sample_submission.csv')\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train1.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train = pd.concat([\n    train1[['comment_text', 'toxic']],\n    train2[['comment_text', 'toxic']].query('toxic==1'),\n    train2[['comment_text', 'toxic']].query('toxic==0').sample(n=150000, random_state=0)\n])\nprint(train.toxic.value_counts())\nprint(valid.toxic.value_counts())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## OLID","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"olid = pd.read_csv('/kaggle/input/olid2019/olid-training-v1.0.tsv', sep='\\t')\nolid = olid.rename(columns={\"id\": \"id\", \"tweet\": \"comment_text\", 'subtask_a': 'toxic'})\nolid.toxic = (olid.toxic == 'OFF').astype(int)\n# olid['comment_text'] = olid['comment_text'].str.replace('@USER', '')\nolid.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"olid_test = pd.read_csv('/kaggle/input/olid2019/testset-levela.tsv', sep='\\t')\nolid_test_label = pd.read_csv('/kaggle/input/olid2019/labels-levela.csv', names = ['id', 'label'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"olid_test_label.label.value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"olid_test = olid_test.set_index('id').join(olid_test_label.set_index('id'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"olid_test['toxic'] = (olid_test.label == 'OFF').astype(int).values","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"olid_test.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nolid_train, olid_valid = train_test_split(olid, test_size=3240, random_state = 2020)\nolid_train_1k = olid_train[0:1000]\nolid_train_2k = olid_train[0:2000]\nolid_train_5k = olid_train[0:5000]\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Encode them","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def fast_encode_xlm(texts, tokenizer, chunk_size=256, maxlen=512):\n    \"\"\"\n    https://www.kaggle.com/xhlulu/jigsaw-tpu-distilbert-with-huggingface-and-keras\n    \"\"\"\n    all_ids = []\n    \n    for i in tqdm(range(0, len(texts), chunk_size)):\n        text_chunk = texts[i:i+chunk_size].tolist()\n        encs = tokenizer.batch_encode_plus(text_chunk, pad_to_max_length = True, max_length = maxlen)\n        all_ids.extend(np.array(encs.input_ids))\n    \n    return np.array(all_ids)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"x_train = fast_encode_xlm(train.comment_text.astype(str), tokenizer, maxlen=MAX_LEN)\nx_valid = fast_encode_xlm(valid.comment_text.astype(str), tokenizer, maxlen=MAX_LEN)\nx_test = fast_encode_xlm(test.content.astype(str), tokenizer, maxlen=MAX_LEN)\n\ny_train = train.toxic.values\ny_valid = valid.toxic.values","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# olid_encode = fast_encode_xlm(olid.comment_text.astype(str), tokenizer, maxlen=MAX_LEN)\nolid_train_encode = fast_encode_xlm(olid_train.comment_text.astype(str), tokenizer, maxlen=MAX_LEN)\nolid_train_1k_encode = fast_encode_xlm(olid_train_1k.comment_text.astype(str), tokenizer, maxlen=MAX_LEN)\nolid_train_2k_encode = fast_encode_xlm(olid_train_2k.comment_text.astype(str), tokenizer, maxlen=MAX_LEN)\nolid_train_5k_encode = fast_encode_xlm(olid_train_5k.comment_text.astype(str), tokenizer, maxlen=MAX_LEN)\nolid_valid_encode = fast_encode_xlm(olid_valid.comment_text.astype(str), tokenizer, maxlen=MAX_LEN)\nolid_test_encode = fast_encode_xlm(olid_test.tweet.astype(str), tokenizer, maxlen = MAX_LEN)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"y_olid_train = olid_train.toxic.values\ny_olid_train_1k = olid_train_1k.toxic.values\ny_olid_train_2k = olid_train_2k.toxic.values\ny_olid_train_5k = olid_train_5k.toxic.values\ny_olid_valid = olid_valid.toxic.values\ny_olid_test = (olid_test_label.label == 'OFF').astype(int).values","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Build datasets objects","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"train_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices((x_train, y_train))\n    .repeat()\n    .shuffle(2048)\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)\n\nvalid_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices((x_valid, y_valid))\n    .batch(BATCH_SIZE)\n    .cache()\n    .prefetch(AUTO)\n)\n\ntest_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices(x_test)\n    .batch(BATCH_SIZE)\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"olid_test_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices(olid_test_encode)\n    .batch(BATCH_SIZE)\n)\nolid_valid_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices((olid_valid_encode, y_olid_valid))\n    .batch(BATCH_SIZE)\n)\nolid_train_1k_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices((olid_train_1k_encode, y_olid_train_1k))\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)\nolid_train_2k_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices((olid_train_2k_encode, y_olid_train_2k))\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)\nolid_train_5k_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices((olid_train_5k_encode, y_olid_train_5k))\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)\nolid_train_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices((olid_train_encode, y_olid_train))\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Load model into the TPU\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def build_model(transformer, max_len=512):\n    \"\"\"\n    https://www.kaggle.com/xhlulu/jigsaw-tpu-distilbert-with-huggingface-and-keras\n    \"\"\"\n    input_word_ids = Input(shape=(max_len,), dtype=tf.int32, name=\"input_word_ids\")\n    sequence_output = transformer(input_word_ids)[0]\n    cls_token = sequence_output[:, 0, :]\n    out = Dense(1, activation='sigmoid')(cls_token)\n#     run_opts = tf.RunOptions(report_tensor_allocations_upon_oom = True)\n\n    model = Model(inputs=input_word_ids, outputs=out)\n    model.compile(Adam(lr=1e-5), loss='binary_crossentropy', metrics=['accuracy']) #, options = run_opts)\n    \n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\nwith strategy.scope():\n    transformer_layer = (\n        transformers.TFAutoModelWithLMHead.from_pretrained(model_name)\n    )\n    model = build_model(transformer_layer, max_len=MAX_LEN)\nmodel.summary()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# tf.tpu.experimental.initialize_tpu_system(tpu)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train Model","execution_count":null},{"metadata":{"trusted":true,"collapsed":true},"cell_type":"code","source":"n_steps = x_train.shape[0] // BATCH_SIZE\nmodel.fit(\n    train_dataset,\n    steps_per_epoch=n_steps,\n    validation_data=valid_dataset,\n    epochs=EPOCHS\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"n_steps = x_valid.shape[0] // BATCH_SIZE\nmodel.fit(\n    valid_dataset.repeat(),\n    steps_per_epoch=n_steps,\n    epochs=EPOCHS\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Submission","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"sub['toxic'] = model.predict(test_dataset, verbose=0)\nsub.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub.describe()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Olid Zero-shot","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.metrics import roc_auc_score","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"olid_test.toxic_predict = model.predict(olid_test_dataset, verbose=0)\nolid_test.to_csv('olid_test_0shot.csv', index=False)\n\nroc_auc_score(y_true = olid_test.toxic, y_score = olid_test.toxic_predict)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.metrics import roc_curve, auc, accuracy_score, confusion_matrix, log_loss, f1_score\nimport seaborn as sns\nimport numpy as np\nfrom matplotlib import pyplot as plt\n\ndef Find_Optimal_Cutoff(target, predicted):\n    \"\"\" Find the optimal probability cutoff point for a classification model related to event rate\n    Parameters\n    ----------\n    target : Matrix with dependent or target data, where rows are observations\n\n    predicted : Matrix with predicted data, where rows are observations\n\n    Returns\n    -------     \n    list type, with optimal cutoff value\n\n    \"\"\"\n    fpr, tpr, threshold = roc_curve(target, predicted)\n    i = np.arange(len(tpr)) \n    roc = pd.DataFrame({'tf' : pd.Series(tpr-(1-fpr), index=i), 'threshold' : pd.Series(threshold, index=i)})\n    roc_t = roc.iloc[(roc.tf-0).abs().argsort()[:1]]\n\n    return list(roc_t['threshold']) \n\nthreshold = Find_Optimal_Cutoff(olid_test.toxic, olid_test.toxic_predict)\nprint(\"the optimal threshold is \" + str(threshold[0]))\nolid_test.toxic_predict_binary = [1 if p > threshold[0] else 0 for p in olid_test.toxic_predict]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"f1_score(y_true = olid_test.toxic, y_pred = olid_test.toxic_predict_binary)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def plot_matrix(target, predicted_binary, name):\n    matrix = confusion_matrix(target, predicted_binary)\n    TN, FP, FN, TP = matrix.ravel()\n    if (TP + FP > 0) and (TP + FN > 0):\n        precision = TP / (TP + FP)\n        recall = TP / (TP + FN)\n        F =  2 * (precision*recall) / (precision + recall)\n    else:\n        F = 0\n    cm_df = pd.DataFrame(matrix,\n                         index = ['Nagative', 'Positive'], \n                         columns = ['Nagative', 'Positive'])\n    subtitle = 'Precision ' + str(round(precision, 2)) + ' Recall ' + str(round(recall, 2))\n    fig, ax = plt.subplots(figsize=(8,6))\n    ax = sns.heatmap(cm_df, annot=True, fmt=\"d\")\n    bottom, top = ax.get_ylim()\n    ax.set_ylim(bottom + 0.5, top - 0.5)\n    plt.title('Confusion Matrix - ' + name + \"\\n\" + subtitle)\n    plt.ylabel('True label')\n    plt.xlabel('Predicted label')\n    plt.show()\nplot_matrix(olid_test.toxic, olid_test.toxic_predict_binary, name = 'Zero-Shot')\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Olid Few-shot","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"model.save_weights(\"/kaggle/working/ckpt.h5\")\nmodel.predict(olid_test_dataset, verbose=0)[0:5]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"with strategy.scope():\n    model.compile(Adam(lr=2e-6), loss='binary_crossentropy', metrics=['accuracy'])\n    \nn_steps = olid_train_1k.shape[0] // BATCH_SIZE\nmodel.fit(\n    olid_train_1k_dataset.repeat(),\n    steps_per_epoch=n_steps,\n    validation_data=olid_valid_dataset,\n    callbacks = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True),\n    epochs=10\n)\n\nolid_test1k = olid_test\nolid_test1k.toxic_predict = model.predict(olid_test_dataset, verbose=0)\nolid_test1k.to_csv('olid_test_1k.csv', index=False)\n\nprint('1k roc is ' + str(roc_auc_score(y_true = olid_test1k.toxic, y_score = olid_test1k.toxic_predict)))\n\nthreshold = Find_Optimal_Cutoff(olid_test1k.toxic, olid_test1k.toxic_predict)\nprint(\"the optimal threshold is \" + str(threshold[0]))\nolid_test1k.toxic_predict_binary = [1 if p > threshold[0] else 0 for p in olid_test1k.toxic_predict]\n\nprint('1k f1-score is ' + str(f1_score(y_true = olid_test1k.toxic, y_pred = olid_test1k.toxic_predict_binary)))\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"tf.tpu.experimental.initialize_tpu_system(tpu)\nwith strategy.scope():\n    model.load_weights(\"/kaggle/working/ckpt.h5\") \n    model.compile(Adam(lr=2e-6), loss='binary_crossentropy', metrics=['accuracy'])\nmodel.predict(olid_test_dataset, verbose=0)[0:5]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"n_steps = olid_train_2k.shape[0] // BATCH_SIZE\nmodel.fit(\n    olid_train_2k_dataset.repeat(),\n    steps_per_epoch=n_steps,\n    validation_data=olid_valid_dataset,\n    callbacks = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True),\n    epochs=10\n)\n\nolid_test2k = olid_test\nolid_test2k.toxic_predict = model.predict(olid_test_dataset, verbose=0)\nolid_test2k.to_csv('olid_test_2k.csv', index=False)\n\nprint('2k roc is ' + str(roc_auc_score(y_true = olid_test2k.toxic, y_score = olid_test2k.toxic_predict)))\n\nthreshold = Find_Optimal_Cutoff(olid_test2k.toxic, olid_test2k.toxic_predict)\nprint(\"the optimal threshold is \" + str(threshold[0]))\nolid_test2k.toxic_predict_binary = [1 if p > threshold[0] else 0 for p in olid_test2k.toxic_predict]\n\nprint('2k f1-score is ' + str(f1_score(y_true = olid_test2k.toxic, y_pred = olid_test2k.toxic_predict_binary)))\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"tf.tpu.experimental.initialize_tpu_system(tpu)\nwith strategy.scope():\n    model.load_weights(\"/kaggle/working/ckpt.h5\") \n    model.compile(Adam(lr=2e-6), loss='binary_crossentropy', metrics=['accuracy'])\nmodel.predict(olid_test_dataset, verbose=0)[0:5]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"n_steps = olid_train_5k.shape[0] // BATCH_SIZE \nmodel.fit(\n    olid_train_5k_dataset.repeat(),\n    steps_per_epoch=n_steps,\n    validation_data=olid_valid_dataset,\n    callbacks = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True),\n    epochs=10\n)\n\nolid_test5k = olid_test\nolid_test5k.toxic_predict = model.predict(olid_test_dataset, verbose=0)\nolid_test5k.to_csv('olid_test_5k.csv', index=False)\n\nprint('5k roc is ' + str(roc_auc_score(y_true = olid_test5k.toxic, y_score = olid_test5k.toxic_predict)))\n\nthreshold = Find_Optimal_Cutoff(olid_test5k.toxic, olid_test5k.toxic_predict)\nprint(\"the optimal threshold is \" + str(threshold[0]))\nolid_test5k.toxic_predict_binary = [1 if p > threshold[0] else 0 for p in olid_test5k.toxic_predict]\n\nprint('5k f1-score is ' + str(f1_score(y_true = olid_test5k.toxic, y_pred = olid_test5k.toxic_predict_binary)))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"tf.tpu.experimental.initialize_tpu_system(tpu)\n\nwith strategy.scope():\n    model.load_weights(\"/kaggle/working/ckpt.h5\") \n    model.compile(Adam(lr=2e-6), loss='binary_crossentropy', metrics=['accuracy'])\n    \nmodel.predict(olid_test_dataset, verbose=0)[0:5]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"n_steps = olid_train.shape[0] // BATCH_SIZE\nmodel.fit(\n    olid_train_dataset.repeat(),\n    steps_per_epoch=n_steps,\n    validation_data=olid_valid_dataset,\n    callbacks = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True),\n    epochs=10\n)\n\nolid_test10k = olid_test\nolid_test10k.toxic_predict = model.predict(olid_test_dataset, verbose=0)\nolid_test10k.to_csv('olid_test_10k.csv', index=False)\n\nprint('10k roc is ' + str(roc_auc_score(y_true = olid_test10k.toxic, y_score = olid_test10k.toxic_predict)))\n\nthreshold = Find_Optimal_Cutoff(olid_test10k.toxic, olid_test10k.toxic_predict)\nprint(\"the optimal threshold is \" + str(threshold[0]))\nolid_test10k.toxic_predict_binary = [1 if p > threshold[0] else 0 for p in olid_test10k.toxic_predict]\n\nprint('10k f1-score is ' + str(f1_score(y_true = olid_test10k.toxic, y_pred = olid_test10k.toxic_predict_binary)))\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}