{ "cells": [ { "cell_type": "markdown", "source": [ "# Early time series classification with aeon\n", "\n", "Early time series classification (eTSC) is the problem of classifying a time series after as few measurements as possible with the highest possible accuracy. The most critical issue of any eTSC method is to decide when enough data of a time series has been seen to take a decision: Waiting for more data points usually makes the classification problem easier but delays the time in which a classification is made; in contrast, earlier classification has to cope with less input data, often leading to inferior accuracy.\n", "\n", "This notebook gives a quick guide to get you started with running eTSC algorithms in aeon.\n", "\n", "\n", "#### References:\n", "\n", "\\[1\\] Schäfer, P., & Leser, U. (2020). TEASER: early and accurate time series classification. Data mining and knowledge discovery, 34(5), 1336-1362" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "## Data sets and problem types\n", "The UCR/UEA [time series classification archive](https://timeseriesclassification.com/) contains a large number of example TSC problems that have been used thousands of times in the literature to assess TSC algorithms. Read the data loading documentation and notebooks for details on the aeon data formats and loading data for aeon." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "source": [ "# Imports used in this notebook\n", "import numpy as np\n", "\n", "from aeon.classification.early_classification._teaser import TEASER\n", "from aeon.classification.interval_based import TimeSeriesForestClassifier\n", "from aeon.datasets import load_arrow_head" ], "metadata": { "collapsed": false }, "execution_count": 1, "outputs": [] }, { "cell_type": "code", "execution_count": 2, "outputs": [ { "data": { "text/plain": "(175, 1, 251)" }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Load default train/test splits from aeon/datasets/data\n", "arrow_train_X, arrow_train_y = load_arrow_head(split=\"train\", return_type=\"numpy3d\")\n", "arrow_test_X, arrow_test_y = load_arrow_head(split=\"test\", return_type=\"numpy3d\")\n", "\n", "arrow_test_X.shape" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "## Building the TEASER classifier\n", "\n", "TEASER \\[1\\] is a two-tier model using a base classifier to make predictions and a decision making estimator to decide whether these predictions are safe. As a first tier, TEASER requires a TSC algorithm, such as WEASEL, which produces class probabilities as output. As a second tier an anomaly detector is required, such as a one-class SVM." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 3, "outputs": [ { "data": { "text/plain": "TEASER(classification_points=[25, 50, 75, 100, 125, 150, 175, 200, 251],\n estimator=TimeSeriesForestClassifier(n_estimators=10, random_state=0),\n random_state=0)", "text/html": "
TEASER(classification_points=[25, 50, 75, 100, 125, 150, 175, 200, 251],\n       estimator=TimeSeriesForestClassifier(n_estimators=10, random_state=0),\n       random_state=0)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "teaser = TEASER(\n", " random_state=0,\n", " classification_points=[25, 50, 75, 100, 125, 150, 175, 200, 251],\n", " estimator=TimeSeriesForestClassifier(n_estimators=10, random_state=0),\n", ")\n", "teaser.fit(arrow_train_X, arrow_train_y)" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "## Determine the accuracy and earliness on the test data\n", "\n", "Commonly accuracy is used to determine the correctness of the predictions, while earliness is used to determine how much of the series is required on average to obtain said accuracy. I.e. for the below values, using just 43% of the full test data, we were able to get an accuracy of 69%." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 10, "outputs": [ { "ename": "ValueError", "evalue": "zero-size array to reduction operation minimum which has no identity", "output_type": "error", "traceback": [ "\u001B[1;31m---------------------------------------------------------------------------\u001B[0m", "\u001B[1;31mValueError\u001B[0m Traceback (most recent call last)", "Cell \u001B[1;32mIn[10], line 1\u001B[0m\n\u001B[1;32m----> 1\u001B[0m hm, acc, earl \u001B[38;5;241m=\u001B[39m \u001B[43mteaser\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mscore\u001B[49m\u001B[43m(\u001B[49m\u001B[43marrow_test_X\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43marrow_test_y\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 2\u001B[0m \u001B[38;5;28mprint\u001B[39m(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mEarliness on Test Data \u001B[39m\u001B[38;5;132;01m%2.2f\u001B[39;00m\u001B[38;5;124m\"\u001B[39m \u001B[38;5;241m%\u001B[39m earl)\n\u001B[0;32m 3\u001B[0m \u001B[38;5;28mprint\u001B[39m(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mAccuracy on Test Data \u001B[39m\u001B[38;5;132;01m%2.2f\u001B[39;00m\u001B[38;5;124m\"\u001B[39m \u001B[38;5;241m%\u001B[39m acc)\n", "File \u001B[1;32mC:\\Code\\aeon\\aeon\\classification\\early_classification\\base.py:314\u001B[0m, in \u001B[0;36mBaseEarlyClassifier.score\u001B[1;34m(self, X, y)\u001B[0m\n\u001B[0;32m 311\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcheck_is_fitted()\n\u001B[0;32m 313\u001B[0m \u001B[38;5;66;03m# boilerplate input checks for predict-like methods\u001B[39;00m\n\u001B[1;32m--> 314\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_check_X\u001B[49m\u001B[43m(\u001B[49m\u001B[43mX\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 315\u001B[0m X \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_convert_X(X)\n\u001B[0;32m 317\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_score(X, y)\n", "File \u001B[1;32mC:\\Code\\aeon\\aeon\\classification\\early_classification\\base.py:631\u001B[0m, in \u001B[0;36mBaseEarlyClassifier._check_X\u001B[1;34m(self, X)\u001B[0m\n\u001B[0;32m 629\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21m_check_X\u001B[39m(\u001B[38;5;28mself\u001B[39m, X):\n\u001B[0;32m 630\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"To follow.\"\"\"\u001B[39;00m\n\u001B[1;32m--> 631\u001B[0m metadata \u001B[38;5;241m=\u001B[39m \u001B[43m_get_metadata\u001B[49m\u001B[43m(\u001B[49m\u001B[43mX\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 632\u001B[0m \u001B[38;5;66;03m# Check classifier capabilities for X\u001B[39;00m\n\u001B[0;32m 633\u001B[0m allow_multivariate \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mget_tag(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mcapability:multivariate\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n", "File \u001B[1;32mC:\\Code\\aeon\\aeon\\classification\\early_classification\\base.py:707\u001B[0m, in \u001B[0;36m_get_metadata\u001B[1;34m(X)\u001B[0m\n\u001B[0;32m 705\u001B[0m metadata \u001B[38;5;241m=\u001B[39m {}\n\u001B[0;32m 706\u001B[0m metadata[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mmultivariate\u001B[39m\u001B[38;5;124m\"\u001B[39m] \u001B[38;5;241m=\u001B[39m \u001B[38;5;129;01mnot\u001B[39;00m is_univariate(X)\n\u001B[1;32m--> 707\u001B[0m metadata[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mmissing_values\u001B[39m\u001B[38;5;124m\"\u001B[39m] \u001B[38;5;241m=\u001B[39m \u001B[43mhas_missing\u001B[49m\u001B[43m(\u001B[49m\u001B[43mX\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 708\u001B[0m metadata[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124munequal_length\u001B[39m\u001B[38;5;124m\"\u001B[39m] \u001B[38;5;241m=\u001B[39m \u001B[38;5;129;01mnot\u001B[39;00m is_equal_length(X)\n\u001B[0;32m 709\u001B[0m metadata[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mn_cases\u001B[39m\u001B[38;5;124m\"\u001B[39m] \u001B[38;5;241m=\u001B[39m get_n_cases(X)\n", "File \u001B[1;32mC:\\Code\\aeon\\aeon\\utils\\validation\\collection.py:305\u001B[0m, in \u001B[0;36mhas_missing\u001B[1;34m(X)\u001B[0m\n\u001B[0;32m 303\u001B[0m \u001B[38;5;28mtype\u001B[39m \u001B[38;5;241m=\u001B[39m get_type(X)\n\u001B[0;32m 304\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mtype\u001B[39m \u001B[38;5;241m==\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mnumpy3D\u001B[39m\u001B[38;5;124m\"\u001B[39m \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mtype\u001B[39m \u001B[38;5;241m==\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mnumpy2D\u001B[39m\u001B[38;5;124m\"\u001B[39m:\n\u001B[1;32m--> 305\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m np\u001B[38;5;241m.\u001B[39many(np\u001B[38;5;241m.\u001B[39misnan(\u001B[43mnp\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmin\u001B[49m\u001B[43m(\u001B[49m\u001B[43mX\u001B[49m\u001B[43m)\u001B[49m))\n\u001B[0;32m 306\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mtype\u001B[39m \u001B[38;5;241m==\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mnp-list\u001B[39m\u001B[38;5;124m\"\u001B[39m:\n\u001B[0;32m 307\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m x \u001B[38;5;129;01min\u001B[39;00m X:\n", "File \u001B[1;32m<__array_function__ internals>:180\u001B[0m, in \u001B[0;36mamin\u001B[1;34m(*args, **kwargs)\u001B[0m\n", "File \u001B[1;32mC:\\Code\\aeon\\venv\\lib\\site-packages\\numpy\\core\\fromnumeric.py:2918\u001B[0m, in \u001B[0;36mamin\u001B[1;34m(a, axis, out, keepdims, initial, where)\u001B[0m\n\u001B[0;32m 2802\u001B[0m \u001B[38;5;129m@array_function_dispatch\u001B[39m(_amin_dispatcher)\n\u001B[0;32m 2803\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mamin\u001B[39m(a, axis\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m, out\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m, keepdims\u001B[38;5;241m=\u001B[39mnp\u001B[38;5;241m.\u001B[39m_NoValue, initial\u001B[38;5;241m=\u001B[39mnp\u001B[38;5;241m.\u001B[39m_NoValue,\n\u001B[0;32m 2804\u001B[0m where\u001B[38;5;241m=\u001B[39mnp\u001B[38;5;241m.\u001B[39m_NoValue):\n\u001B[0;32m 2805\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[0;32m 2806\u001B[0m \u001B[38;5;124;03m Return the minimum of an array or minimum along an axis.\u001B[39;00m\n\u001B[0;32m 2807\u001B[0m \n\u001B[1;32m (...)\u001B[0m\n\u001B[0;32m 2916\u001B[0m \u001B[38;5;124;03m 6\u001B[39;00m\n\u001B[0;32m 2917\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[1;32m-> 2918\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43m_wrapreduction\u001B[49m\u001B[43m(\u001B[49m\u001B[43ma\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mnp\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mminimum\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;124;43m'\u001B[39;49m\u001B[38;5;124;43mmin\u001B[39;49m\u001B[38;5;124;43m'\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43maxis\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43;01mNone\u001B[39;49;00m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mout\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m 2919\u001B[0m \u001B[43m \u001B[49m\u001B[43mkeepdims\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mkeepdims\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43minitial\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43minitial\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mwhere\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mwhere\u001B[49m\u001B[43m)\u001B[49m\n", "File \u001B[1;32mC:\\Code\\aeon\\venv\\lib\\site-packages\\numpy\\core\\fromnumeric.py:86\u001B[0m, in \u001B[0;36m_wrapreduction\u001B[1;34m(obj, ufunc, method, axis, dtype, out, **kwargs)\u001B[0m\n\u001B[0;32m 83\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m 84\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m reduction(axis\u001B[38;5;241m=\u001B[39maxis, out\u001B[38;5;241m=\u001B[39mout, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mpasskwargs)\n\u001B[1;32m---> 86\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m ufunc\u001B[38;5;241m.\u001B[39mreduce(obj, axis, dtype, out, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mpasskwargs)\n", "\u001B[1;31mValueError\u001B[0m: zero-size array to reduction operation minimum which has no identity" ] } ], "source": [ "hm, acc, earl = teaser.score(arrow_test_X, arrow_test_y)\n", "print(\"Earliness on Test Data %2.2f\" % earl)\n", "print(\"Accuracy on Test Data %2.2f\" % acc)\n", "print(\"Harmonic Mean on Test Data %2.2f\" % hm)" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "### Determine the accuracy and earliness on the train data" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 5, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Earliness on Train Data 0.31\n", "Accuracy on Train Data 0.69\n" ] } ], "source": [ "print(\"Earliness on Train Data %2.2f\" % teaser._train_earliness)\n", "print(\"Accuracy on Train Data %2.2f\" % teaser._train_accuracy)" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "### Comparison to Classification on full Test Data\n", "\n", "With the full test data, we would obtain 68% accuracy with the same classifier." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 6, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Accuracy on the full Test Data 0.67\n" ] } ], "source": [ "accuracy = (\n", " TimeSeriesForestClassifier(n_estimators=10, random_state=0)\n", " .fit(arrow_train_X, arrow_train_y)\n", " .score(arrow_test_X, arrow_test_y)\n", ")\n", "print(\"Accuracy on the full Test Data %2.2f\" % accuracy)" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "## Classifying with incomplete time series\n", "\n", "The main draw of eTSC is the capabilility to make classifications with incomplete time series. aeon eTSC algorithms accept inputs with less time points than the full series length, and output two items: The prediction made and whether the algorithm thinks the prediction is safe. Information about the decision such as the time stamp it was made at can be obtained from the state_info attribute.\n", "\n", "### First test with only 50 datapoints (out of 251)" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 7, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "First 10 Finished prediction\n", " [ 0 1 4 5 9 11 24 30 32 35]\n", "First 10 Probabilities of finished predictions\n", " [[0.9 0. 0.1]\n", " [0.3 0.1 0.6]\n", " [0.8 0.1 0.1]\n", " [0.7 0.3 0. ]\n", " [0.5 0.2 0.3]\n", " [0.6 0.2 0.2]\n", " [0.1 0.2 0.7]\n", " [0.8 0. 0.2]\n", " [0.3 0.1 0.6]\n", " [0.9 0. 0.1]]\n" ] } ], "source": [ "X = arrow_test_X[:, :, :50]\n", "probas, _ = teaser.predict_proba(X)\n", "idx = (probas >= 0).all(axis=1)\n", "print(\"First 10 Finished prediction\\n\", np.argwhere(idx).flatten()[:10])\n", "print(\"First 10 Probabilities of finished predictions\\n\", probas[idx][:10])" ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 8, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Accuracy with 50 points on Test Data 0.57\n" ] } ], "source": [ "_, acc, _ = teaser.score(X, arrow_test_y)\n", "print(\"Accuracy with 50 points on Test Data %2.2f\" % acc)" ], "metadata": { "collapsed": false } }, { "cell_type": "markdown", "source": [ "### We may also do predictions in a streaming scenario where more data becomes available from time to time\n", "\n", "The rationale is to keep the state info from the previous predictions in the TEASER object and use it whenever new data is available." ], "metadata": { "collapsed": false } }, { "cell_type": "code", "execution_count": 9, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Earliness on length 25 is 0.10\n", "Accuracy on length 25 is 0.50\n", "Harmonic Mean on length 25 is 0.64\n", "...........\n", "Earliness on length 50 is 0.20\n", "Accuracy on length 50 is 0.57\n", "Harmonic Mean on length 50 is 0.67\n", "...........\n", "Earliness on length 75 is 0.27\n", "Accuracy on length 75 is 0.72\n", "Harmonic Mean on length 75 is 0.72\n", "...........\n", "Earliness on length 100 is 0.32\n", "Accuracy on length 100 is 0.62\n", "Harmonic Mean on length 100 is 0.65\n", "...........\n", "Earliness on length 125 is 0.35\n", "Accuracy on length 125 is 0.69\n", "Harmonic Mean on length 125 is 0.67\n", "...........\n", "Earliness on length 150 is 0.37\n", "Accuracy on length 150 is 0.69\n", "Harmonic Mean on length 150 is 0.66\n", "...........\n", "Earliness on length 175 is 0.39\n", "Accuracy on length 175 is 0.68\n", "Harmonic Mean on length 175 is 0.64\n", "...........\n", "Earliness on length 200 is 0.40\n", "Accuracy on length 200 is 0.69\n", "Harmonic Mean on length 200 is 0.65\n", "...........\n", "Earliness on length 251 is 0.40\n", "Accuracy on length 251 is 0.67\n", "Harmonic Mean on length 251 is 0.63\n", "...........\n", "Time Stamp of final decisions [ 50 50 251 75 50 50 175 200 175 50 75 50 75 75 100 251 100 100\n", " 125 75 100 100 75 100 50 125 75 100 75 75 50 75 50 125 175 50\n", " 50 75 75 125 50 75 75 50 175 100 150 125 75 100 75 75 75 75\n", " 50 100 50 175 75 50 200 50 50 50 75 200 75 125 75 125 150 175\n", " 125 50 150 50 75 75 50 100 75 251 251 75 50 100 50 150 100 50\n", " 75 100 251 50 50 50 200 100 75 50 200 100 50 50 50 50 251 100\n", " 75 75 125 50 125 100 100 50 75 175 175 50 50 100 175 150 100 100\n", " 50 100 100 100 175 50 50 100 100 175 251 125 125 100 100 125 100 125\n", " 100 125 50 175 75 125 100 100 125 50 50 100 125 100 100 100 251 150\n", " 50 75 175 125 50 50 125 75 50 100 175 50 100]\n" ] } ], "source": [ "test_points = [25, 50, 75, 100, 125, 150, 175, 200, 251]\n", "final_states = np.zeros((arrow_test_X.shape[0], 4), dtype=int)\n", "final_decisions = np.zeros(arrow_test_X.shape[0], dtype=int)\n", "open_idx = np.arange(0, arrow_test_X.shape[0])\n", "teaser.reset_state_info()\n", "\n", "for i in test_points:\n", " probas, decisions = teaser.update_predict_proba(arrow_test_X[:, :, :i])\n", " final_states[open_idx] = teaser.get_state_info()\n", "\n", " arrow_test_X, open_idx, final_idx = teaser.split_indices_and_filter(\n", " arrow_test_X, open_idx, decisions\n", " )\n", " final_decisions[final_idx] = i\n", "\n", " (\n", " hm,\n", " acc,\n", " earliness,\n", " ) = teaser.compute_harmonic_mean(final_states, arrow_test_y)\n", "\n", " print(\"Earliness on length %2i is %2.2f\" % (i, earliness))\n", " print(\"Accuracy on length %2i is %2.2f\" % (i, acc))\n", " print(\"Harmonic Mean on length %2i is %2.2f\" % (i, hm))\n", "\n", " print(\"...........\")\n", "\n", "print(\"Time Stamp of final decisions\", final_decisions)" ], "metadata": { "collapsed": false } } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "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 }