<commit_before>{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "kR-4eNdK6lYS"
   },
   "source": [
    "Deep Learning\n",
    "=============\n",
    "\n",
    "Assignment 2\n",
    "------------\n",
    "\n",
    "Previously in `1_notmnist.ipynb`, we created a pickle with formatted datasets for training, development and testing on the [notMNIST dataset](http://yaroslavvb.blogspot.com/2011/09/notmnist-dataset.html).\n",
    "\n",
    "The goal of this assignment is to progressively train deeper and more accurate models using TensorFlow."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     }
    },
    "colab_type": "code",
    "collapsed": true,
    "id": "JLpLa8Jt7Vu4"
   },
   "outputs": [],
   "source": [
    "# These are all the modules we'll be using later. Make sure you can import them\n",
    "# before proceeding further.\n",
    "from __future__ import print_function\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "from six.moves import cPickle as pickle\n",
    "from six.moves import range"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "1HrCK6e17WzV"
   },
   "source": [
    "First reload the data we generated in `1_notmnist.ipynb`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "output_extras": [
      {
       "item_id": 1
      }
     ]
    },
    "colab_type": "code",
    "collapsed": false,
    "executionInfo": {
     "elapsed": 19456,
     "status": "ok",
     "timestamp": 1449847956073,
     "user": {
      "color": "",
      "displayName": "",
      "isAnonymous": false,
      "isMe": true,
      "permissionId": "",
      "photoUrl": "",
      "sessionId": "0",
      "userId": ""
     },
     "user_tz": 480
    },
    "id": "y3-cj1bpmuxc",
    "outputId": "0ddb1607-1fc4-4ddb-de28-6c7ab7fb0c33"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training set (200000, 28, 28) (200000,)\n",
      "Validation set (10000, 28, 28) (10000,)\n",
      "Test set (10000, 28, 28) (10000,)\n"
     ]
    }
   ],
   "source": [
    "pickle_file = 'notMNIST.pickle'\n",
    "#with is just a safe way of dealing with resources. handles correct closing if exceptions, etc\n",
    "with open(pickle_file, 'rb') as f:\n",
    "  save = pickle.load(f)\n",
    "  train_dataset = save['train_dataset']\n",
    "  train_labels = save['train_labels']\n",
    "  valid_dataset = save['valid_dataset']\n",
    "  valid_labels = save['valid_labels']\n",
    "  test_dataset = save['test_dataset']\n",
    "  test_labels = save['test_labels']\n",
    "  del save  # hint to help gc free up memory\n",
    "  print('Training set', train_dataset.shape, train_labels.shape)\n",
    "  print('Validation set', valid_dataset.shape, valid_labels.shape)\n",
    "  print('Test set', test_dataset.shape, test_labels.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "L7aHrm6nGDMB"
   },
   "source": [
    "Reformat into a shape that's more adapted to the models we're going to train:\n",
    "- data as a flat matrix,\n",
    "- labels as float 1-hot encodings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "output_extras": [
      {
       "item_id": 1
      }
     ]
    },
    "colab_type": "code",
    "collapsed": false,
    "executionInfo": {
     "elapsed": 19723,
     "status": "ok",
     "timestamp": 1449847956364,
     "user": {
      "color": "",
      "displayName": "",
      "isAnonymous": false,
      "isMe": true,
      "permissionId": "",
      "photoUrl": "",
      "sessionId": "0",
      "userId": ""
     },
     "user_tz": 480
    },
    "id": "IRSyYiIIGIzS",
    "outputId": "2ba0fc75-1487-4ace-a562-cf81cae82793"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(200000, 10)\n",
      "(10000, 10)\n",
      "(10000, 10)\n",
      "Training set (200000, 784) (200000, 10)\n",
      "Validation set (10000, 784) (10000, 10)\n",
      "Test set (10000, 784) (10000, 10)\n"
     ]
    }
   ],
   "source": [
    "image_size = 28\n",
    "num_labels = 10\n",
    "\n",
    "def reformat(dataset, labels):\n",
    "    #-1 in reshape means 'use whatever makes sense', either flatten the whole thing or keep previous dimensions.\n",
    "    # here it keeps the previous dimension\n",
    "    print(labels.shape)\n",
    "    dataset = dataset.reshape((-1, image_size * image_size)).astype(np.float32)\n",
    "    # Map 0 to [1.0, 0.0, 0.0 ...], 1 to [0.0, 1.0, 0.0 ...]\n",
    "    #the first part of this, np.arange(num_labels), creates an array that is [0,1,2...9]\n",
    "    #we then check, for all rows of labels, if that arange is equal to the content of the row\n",
    "    #so if labels[3] = 1, we get something like [false,true,false...], and this is converted to [0.0,1.0,0.0...]\n",
    "    #None is actually optional, it just means don't bother about this dimension or something. \n",
    "    #or actually, we need None if labels has more than 1 column\n",
    "    labels = (np.arange(num_labels) == labels[:,None]).astype(np.float32)\n",
    "    return dataset, labels\n",
    "\n",
    "train_dataset, train_labels = reformat(train_dataset, train_labels)\n",
    "valid_dataset, valid_labels = reformat(valid_dataset, valid_labels)\n",
    "test_dataset,  test_labels  = reformat(test_dataset,  test_labels)\n",
    "\n",
    "print('Training set',   train_dataset.shape, train_labels.shape)\n",
    "print('Validation set', valid_dataset.shape, valid_labels.shape)\n",
    "print('Test set',       test_dataset.shape,  test_labels.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "nCLVqyQ5vPPH"
   },
   "source": [
    "We're first going to train a multinomial logistic regression using simple gradient descent.\n",
    "\n",
    "TensorFlow works like this:\n",
    "* First you describe the computation that you want to see performed: what the inputs, the variables, and the operations look like. These get created as nodes over a computation graph. This description is all contained within the block below:\n",
    "\n",
    "      with graph.as_default():\n",
    "          ...\n",
    "\n",
    "* Then you can run the operations on this graph as many times as you want by calling `session.run()`, providing it outputs to fetch from the graph that get returned. This runtime operation is all contained in the block below:\n",
    "\n",
    "      with tf.Session(graph=graph) as session:\n",
    "          ...\n",
    "\n",
    "Let's load all the data into TensorFlow and build the computation graph corresponding to our training:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     }
    },
    "colab_type": "code",
    "collapsed": true,
    "id": "Nfv39qvtvOl_"
   },
   "outputs": [],
   "source": [
    "# With gradient descent training, even this much data is prohibitive. Subset the training data for faster turnaround.\n",
    "train_subset = 10000\n",
    "#set everything up for training\n",
    "graph = tf.Graph()\n",
    "with graph.as_default():\n",
    "    # Input data. Load the training, validation and test data into constants that are attached to the graph.\n",
    "    tf_train_dataset = tf.constant(train_dataset[:train_subset, :])\n",
    "    tf_train_labels  = tf.constant(train_labels[:train_subset])\n",
    "    tf_valid_dataset = tf.constant(valid_dataset)\n",
    "    tf_test_dataset  = tf.constant(test_dataset)\n",
    "    # Variables. These are the parameters that we are going to be training. The weight matrix will be initialized \n",
    "    # using random values following a (truncated) normal distribution. The biases get initialized to zero.\n",
    "    weights   = tf.Variable(tf.truncated_normal([image_size * image_size, num_labels]))\n",
    "    biases    = tf.Variable(tf.zeros([num_labels]))\n",
    "    # Training computation. We multiply the inputs with the weight matrix, and add biases.\n",
    "    logits    = tf.matmul(tf_train_dataset, weights) + biases\n",
    "    #We compute the softmax and cross-entropy (it's one operation in TensorFlow, because it's very common, and it \n",
    "    #can be optimized). We take the average of this cross-entropy across all training examples: that's our loss.\n",
    "    loss      = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits, tf_train_labels))\n",
    "    # Optimizer. We are going to find the minimum of this loss using gradient descent.\n",
    "    optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(loss)\n",
    "    # Predictions for the training, validation, and test data.\n",
    "    # These are not part of training, but merely here so that we can report accuracy figures as we train.\n",
    "    train_prediction = tf.nn.softmax(logits)\n",
    "    valid_prediction = tf.nn.softmax(tf.matmul(tf_valid_dataset, weights) + biases)\n",
    "    test_prediction  = tf.nn.softmax(tf.matmul(tf_test_dataset,  weights) + biases)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "KQcL4uqISHjP"
   },
   "source": [
    "Let's run this computation and iterate:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "output_extras": [
      {
       "item_id": 9
      }
     ]
    },
    "colab_type": "code",
    "collapsed": false,
    "executionInfo": {
     "elapsed": 57454,
     "status": "ok",
     "timestamp": 1449847994134,
     "user": {
      "color": "",
      "displayName": "",
      "isAnonymous": false,
      "isMe": true,
      "permissionId": "",
      "photoUrl": "",
      "sessionId": "0",
      "userId": ""
     },
     "user_tz": 480
    },
    "id": "z2cjdenH869W",
    "outputId": "4c037ba1-b526-4d8e-e632-91e2a0333267",
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Initialized\n",
      "Loss at step 0: 17.033060\n",
      "Training accuracy: 3.2%\n",
      "Validation accuracy: 26.8%\n",
      "Loss at step 100: 1.530666\n",
      "Training accuracy: 82.2%\n",
      "Validation accuracy: 81.5%\n",
      "Loss at step 200: 1.632765\n",
      "Training accuracy: 84.0%\n",
      "Validation accuracy: 83.6%\n",
      "Loss at step 300: 1.912267\n",
      "Training accuracy: 84.5%\n",
      "Validation accuracy: 84.0%\n",
      "Loss at step 400: 2.243961\n",
      "Training accuracy: 84.8%\n",
      "Validation accuracy: 84.2%\n",
      "Loss at step 500: 2.599519\n",
      "Training accuracy: 84.8%\n",
      "Validation accuracy: 84.1%\n",
      "Loss at step 600: 2.968367\n",
      "Training accuracy: 84.9%\n",
      "Validation accuracy: 84.1%\n",
      "Loss at step 700: 3.345928\n",
      "Training accuracy: 84.9%\n",
      "Validation accuracy: 84.2%\n",
      "Loss at step 800: 3.729438\n",
      "Training accuracy: 84.9%\n",
      "Validation accuracy: 84.3%\n",
      "Test accuracy: 84.6%\n"
     ]
    }
   ],
   "source": [
    "num_steps = 801\n",
    "def accuracy(predictions, labels):\n",
    "    #argmax returns the indices of the maximum values across dimension 1 ie colums\n",
    "    #predictions  has shape (10000,10). this == tests if the max from predictions matches the max (ie the only non-null) label\n",
    "    sum_all_correct = np.sum(np.argmax(predictions, 1) == np.argmax(labels, 1))\n",
    "    sum_all = predictions.shape[0]\n",
    "    return 100.0 * sum_all_correct / sum_all\n",
    "\n",
    "with tf.Session(graph=graph) as session:\n",
    "    # This is a one-time operation which ensures the parameters get initialized as\n",
    "    # we described in the graph: random weights for the matrix, zeros for the biases. \n",
    "    tf.initialize_all_variables().run()\n",
    "    print('Initialized')\n",
    "    for step in range(num_steps):\n",
    "        # Run the computations. We tell .run() that we want to run the optimizer,\n",
    "        # and get the loss value and the training predictions returned as numpy arrays.\n",
    "        _, l, predictions = session.run([optimizer, loss, train_prediction])\n",
    "        if (step % 100 == 0):\n",
    "            print('Loss at step %d: %f' % (step, l))\n",
    "            print('Training accuracy: %.1f%%' % accuracy(predictions, train_labels[:train_subset, :]))\n",
    "            # Calling .eval() on valid_prediction is basically like calling run(), but\n",
    "            # just to get that one numpy array. Note that it recomputes all its graph dependencies.\n",
    "            print('Validation accuracy: %.1f%%' % accuracy(valid_prediction.eval(), valid_labels))\n",
    "\n",
    "    print('Test accuracy: %.1f%%' % accuracy(test_prediction.eval(), test_labels))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "x68f-hxRGm3H"
   },
   "source": [
    "Let's now switch to stochastic gradient descent training instead, which is much faster.\n",
    "\n",
    "The graph will be similar, except that instead of holding all the training data into a constant node, we create a `Placeholder` node which will be fed actual data at every call of `sesion.run()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     }
    },
    "colab_type": "code",
    "collapsed": true,
    "id": "qhPMzWYRGrzM"
   },
   "outputs": [],
   "source": [
    "batch_size = 128\n",
    "\n",
    "graph = tf.Graph()\n",
    "\n",
    "with graph.as_default():\n",
    "    # Input data. For the training data, we use a placeholder that will be fed at run time with a training minibatch.\n",
    "    tf_train_dataset = tf.placeholder(tf.float32, shape=(batch_size, image_size * image_size))\n",
    "    tf_train_labels  = tf.placeholder(tf.float32, shape=(batch_size, num_labels))\n",
    "    tf_valid_dataset = tf.constant(valid_dataset)\n",
    "    tf_test_dataset  = tf.constant(test_dataset)\n",
    "\n",
    "    # Variables.\n",
    "    weights = tf.Variable(tf.truncated_normal([image_size * image_size, num_labels]))\n",
    "    biases  = tf.Variable(tf.zeros([num_labels]))\n",
    "\n",
    "    # Training computation.\n",
    "    logits = tf.matmul(tf_train_dataset, weights) + biases\n",
    "    loss   = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits, tf_train_labels))\n",
    "\n",
    "    # Optimizer.\n",
    "    optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(loss)\n",
    "\n",
    "    # Predictions for the training, validation, and test data.\n",
    "    train_prediction = tf.nn.softmax(logits)\n",
    "    valid_prediction = tf.nn.softmax(tf.matmul(tf_valid_dataset, weights) + biases)\n",
    "    test_prediction  = tf.nn.softmax(tf.matmul(tf_test_dataset, weights) + biases)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "XmVZESmtG4JH"
   },
   "source": [
    "Let's run it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "output_extras": [
      {
       "item_id": 6
      }
     ]
    },
    "colab_type": "code",
    "collapsed": false,
    "executionInfo": {
     "elapsed": 66292,
     "status": "ok",
     "timestamp": 1449848003013,
     "user": {
      "color": "",
      "displayName": "",
      "isAnonymous": false,
      "isMe": true,
      "permissionId": "",
      "photoUrl": "",
      "sessionId": "0",
      "userId": ""
     },
     "user_tz": 480
    },
    "id": "FoF91pknG_YW",
    "outputId": "d255c80e-954d-4183-ca1c-c7333ce91d0a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Initialized\n",
      "Minibatch loss at step 0: 12.113108\n",
      "Minibatch accuracy: 18.0%\n",
      "Validation accuracy: 43.6%\n",
      "Minibatch loss at step 500: 1.391131\n",
      "Minibatch accuracy: 89.1%\n",
      "Validation accuracy: 83.2%\n",
      "Minibatch loss at step 1000: 2.604872\n",
      "Minibatch accuracy: 84.4%\n",
      "Validation accuracy: 83.6%\n",
      "Minibatch loss at step 1500: 1.967522\n",
      "Minibatch accuracy: 89.8%\n",
      "Validation accuracy: 83.8%\n",
      "Minibatch loss at step 2000: 9.103417\n",
      "Minibatch accuracy: 82.0%\n",
      "Validation accuracy: 85.0%\n",
      "Minibatch loss at step 2500: 9.347385\n",
      "Minibatch accuracy: 87.5%\n",
      "Validation accuracy: 83.7%\n",
      "Minibatch loss at step 3000: 6.351113\n",
      "Minibatch accuracy: 89.8%\n",
      "Validation accuracy: 85.1%\n",
      "Test accuracy: 85.6%\n"
     ]
    }
   ],
   "source": [
    "num_steps = 3001\n",
    "\n",
    "with tf.Session(graph=graph) as session:\n",
    "    tf.initialize_all_variables().run()\n",
    "    print(\"Initialized\")\n",
    "    for step in range(num_steps):\n",
    "        # Pick an offset within the training data, which has been randomized.\n",
    "        # Note: we could use better randomization across epochs.\n",
    "        offset = (step * batch_size) % (train_labels.shape[0] - batch_size)\n",
    "        # Generate a minibatch.\n",
    "        batch_data   = train_dataset[offset:(offset + batch_size), :]\n",
    "        batch_labels = train_labels [offset:(offset + batch_size), :]\n",
    "        # Prepare a dictionary telling the session where to feed the minibatch.\n",
    "        # The key is the placeholder node of the graph to be fed and the value is the numpy array to feed to it.\n",
    "        feed_dict = {tf_train_dataset : batch_data, tf_train_labels : batch_labels}\n",
    "        _, l, predictions = session.run([optimizer, loss, train_prediction], feed_dict=feed_dict)\n",
    "        if (step % 500 == 0):\n",
    "            print(\"Minibatch loss at step %d: %f\" % (step, l))\n",
    "            print(\"Minibatch accuracy: %.1f%%\"  % accuracy(predictions, batch_labels))\n",
    "            print(\"Validation accuracy: %.1f%%\" % accuracy(valid_prediction.eval(), valid_labels))\n",
    "    print(\"Test accuracy: %.1f%%\" % accuracy(test_prediction.eval(), test_labels))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "7omWxtvLLxik"
   },
   "source": [
    "---\n",
    "Problem\n",
    "-------\n",
    "\n",
    "Turn the logistic regression example with SGD into a 1-hidden layer neural network with rectified linear units (nn.relu()) and 1024 hidden nodes. This model should improve your validation / test accuracy.\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Initialized\n",
      "Minibatch loss at step 0: 3.175418\n",
      "Minibatch accuracy: 5.5%\n",
      "Validation accuracy: 87.1%\n",
      "Minibatch loss at step 500: 0.976396\n",
      "Minibatch accuracy: 89.1%\n",
      "Validation accuracy: 83.6%\n",
      "Minibatch loss at step 1000: 2.186871\n",
      "Minibatch accuracy: 82.0%\n",
      "Validation accuracy: 85.8%\n",
      "Minibatch loss at step 1500: 1.698240\n",
      "Minibatch accuracy: 89.1%\n",
      "Validation accuracy: 82.8%\n",
      "Minibatch loss at step 2000: 8.991287\n",
      "Minibatch accuracy: 84.4%\n",
      "Validation accuracy: 84.6%\n",
      "Minibatch loss at step 2500: 9.305231\n",
      "Minibatch accuracy: 88.3%\n",
      "Validation accuracy: 82.5%\n",
      "Minibatch loss at step 3000: 6.121900\n",
      "Minibatch accuracy: 89.8%\n",
      "Validation accuracy: 83.8%\n",
      "Test accuracy: 84.4%\n"
     ]
    }
   ],
   "source": [
    "import math\n",
    "\n",
    "batch_size = 128 #this random number of training patterns will be used\n",
    "graph = tf.Graph()\n",
    "\n",
    "#initialize everything\n",
    "with graph.as_default():\n",
    "    # Input data. The training data is currently empty, but a random minibatch will be fed in the placeholder during training\n",
    "    tf_train_dataset = tf.placeholder(tf.float32, shape=(batch_size, image_size * image_size))\n",
    "    tf_train_labels  = tf.placeholder(tf.float32, shape=(batch_size, num_labels))\n",
    "    tf_valid_dataset = tf.constant(valid_dataset)\n",
    "    tf_test_dataset  = tf.constant(test_dataset)\n",
    "    # Variables.\n",
    "    weights = tf.Variable(tf.truncated_normal([image_size * image_size, num_labels], stddev=0.1))\n",
    "    biases  = tf.Variable(tf.zeros([num_labels]))\n",
    "    # Training computation.\n",
    "    logits = tf.matmul(tf_train_dataset, weights) + biases\n",
    "    loss   = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits, tf_train_labels))\n",
    "    # Optimizer.\n",
    "    optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(loss)\n",
    "    # Predictions for the training, validation, and test data.\n",
    "    train_prediction = tf.nn.softmax(logits)\n",
    "    valid_prediction = tf.nn.softmax(tf.matmul(tf_valid_dataset, weights) + biases)\n",
    "    test_prediction  = tf.nn.softmax(tf.matmul(tf_test_dataset, weights) + biases)\n",
    "  \n",
    "    #build the hidden layer\n",
    "    hidden1_units = 1024\n",
    "#with tf.name_scope('hidden1'):\n",
    "    stddev  = 1.0 / math.sqrt(float(image_size * image_size))\n",
    "    weights1= tf.Variable(tf.truncated_normal([image_size * image_size, hidden1_units], stddev=stddev))\n",
    "    biases1 = tf.Variable(tf.zeros([hidden1_units]))\n",
    "    hidden1 = tf.nn.relu(tf.matmul(tf_train_dataset, weights1) + biases1)\n",
    "    \n",
    "#train the thing\n",
    "num_steps = 3001\n",
    "with tf.Session(graph=graph) as session:\n",
    "    tf.initialize_all_variables().run()\n",<commit_msg>running with hidden layer, but not training<commit_after>{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "kR-4eNdK6lYS"
   },
   "source": [
    "Deep Learning\n",
    "=============\n",
    "\n",
    "Assignment 2\n",
    "------------\n",
    "\n",
    "Previously in `1_notmnist.ipynb`, we created a pickle with formatted datasets for training, development and testing on the [notMNIST dataset](http://yaroslavvb.blogspot.com/2011/09/notmnist-dataset.html).\n",
    "\n",
    "The goal of this assignment is to progressively train deeper and more accurate models using TensorFlow."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     }
    },
    "colab_type": "code",
    "collapsed": true,
    "id": "JLpLa8Jt7Vu4"
   },
   "outputs": [],
   "source": [
    "# These are all the modules we'll be using later. Make sure you can import them\n",
    "# before proceeding further.\n",
    "from __future__ import print_function\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "from six.moves import cPickle as pickle\n",
    "from six.moves import range"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "1HrCK6e17WzV"
   },
   "source": [
    "First reload the data we generated in `1_notmnist.ipynb`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "output_extras": [
      {
       "item_id": 1
      }
     ]
    },
    "colab_type": "code",
    "collapsed": false,
    "executionInfo": {
     "elapsed": 19456,
     "status": "ok",
     "timestamp": 1449847956073,
     "user": {
      "color": "",
      "displayName": "",
      "isAnonymous": false,
      "isMe": true,
      "permissionId": "",
      "photoUrl": "",
      "sessionId": "0",
      "userId": ""
     },
     "user_tz": 480
    },
    "id": "y3-cj1bpmuxc",
    "outputId": "0ddb1607-1fc4-4ddb-de28-6c7ab7fb0c33"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training set (200000, 28, 28) (200000,)\n",
      "Validation set (10000, 28, 28) (10000,)\n",
      "Test set (10000, 28, 28) (10000,)\n"
     ]
    }
   ],
   "source": [
    "pickle_file = 'notMNIST.pickle'\n",
    "#with is just a safe way of dealing with resources. handles correct closing if exceptions, etc\n",
    "with open(pickle_file, 'rb') as f:\n",
    "  save = pickle.load(f)\n",
    "  train_dataset = save['train_dataset']\n",
    "  train_labels = save['train_labels']\n",
    "  valid_dataset = save['valid_dataset']\n",
    "  valid_labels = save['valid_labels']\n",
    "  test_dataset = save['test_dataset']\n",
    "  test_labels = save['test_labels']\n",
    "  del save  # hint to help gc free up memory\n",
    "  print('Training set', train_dataset.shape, train_labels.shape)\n",
    "  print('Validation set', valid_dataset.shape, valid_labels.shape)\n",
    "  print('Test set', test_dataset.shape, test_labels.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "L7aHrm6nGDMB"
   },
   "source": [
    "Reformat into a shape that's more adapted to the models we're going to train:\n",
    "- data as a flat matrix,\n",
    "- labels as float 1-hot encodings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "output_extras": [
      {
       "item_id": 1
      }
     ]
    },
    "colab_type": "code",
    "collapsed": false,
    "executionInfo": {
     "elapsed": 19723,
     "status": "ok",
     "timestamp": 1449847956364,
     "user": {
      "color": "",
      "displayName": "",
      "isAnonymous": false,
      "isMe": true,
      "permissionId": "",
      "photoUrl": "",
      "sessionId": "0",
      "userId": ""
     },
     "user_tz": 480
    },
    "id": "IRSyYiIIGIzS",
    "outputId": "2ba0fc75-1487-4ace-a562-cf81cae82793"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(200000, 10)\n",
      "(10000, 10)\n",
      "(10000, 10)\n",
      "Training set (200000, 784) (200000, 10)\n",
      "Validation set (10000, 784) (10000, 10)\n",
      "Test set (10000, 784) (10000, 10)\n"
     ]
    }
   ],
   "source": [
    "image_size = 28\n",
    "num_labels = 10\n",
    "\n",
    "def reformat(dataset, labels):\n",
    "    #-1 in reshape means 'use whatever makes sense', either flatten the whole thing or keep previous dimensions.\n",
    "    # here it keeps the previous dimension\n",
    "    print(labels.shape)\n",
    "    dataset = dataset.reshape((-1, image_size * image_size)).astype(np.float32)\n",
    "    # Map 0 to [1.0, 0.0, 0.0 ...], 1 to [0.0, 1.0, 0.0 ...]\n",
    "    #the first part of this, np.arange(num_labels), creates an array that is [0,1,2...9]\n",
    "    #we then check, for all rows of labels, if that arange is equal to the content of the row\n",
    "    #so if labels[3] = 1, we get something like [false,true,false...], and this is converted to [0.0,1.0,0.0...]\n",
    "    #None is actually optional, it just means don't bother about this dimension or something. \n",
    "    #or actually, we need None if labels has more than 1 column\n",
    "    labels = (np.arange(num_labels) == labels[:,None]).astype(np.float32)\n",
    "    return dataset, labels\n",
    "\n",
    "train_dataset, train_labels = reformat(train_dataset, train_labels)\n",
    "valid_dataset, valid_labels = reformat(valid_dataset, valid_labels)\n",
    "test_dataset,  test_labels  = reformat(test_dataset,  test_labels)\n",
    "\n",
    "print('Training set',   train_dataset.shape, train_labels.shape)\n",
    "print('Validation set', valid_dataset.shape, valid_labels.shape)\n",
    "print('Test set',       test_dataset.shape,  test_labels.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "nCLVqyQ5vPPH"
   },
   "source": [
    "We're first going to train a multinomial logistic regression using simple gradient descent.\n",
    "\n",
    "TensorFlow works like this:\n",
    "* First you describe the computation that you want to see performed: what the inputs, the variables, and the operations look like. These get created as nodes over a computation graph. This description is all contained within the block below:\n",
    "\n",
    "      with graph.as_default():\n",
    "          ...\n",
    "\n",
    "* Then you can run the operations on this graph as many times as you want by calling `session.run()`, providing it outputs to fetch from the graph that get returned. This runtime operation is all contained in the block below:\n",
    "\n",
    "      with tf.Session(graph=graph) as session:\n",
    "          ...\n",
    "\n",
    "Let's load all the data into TensorFlow and build the computation graph corresponding to our training:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     }
    },
    "colab_type": "code",
    "collapsed": true,
    "id": "Nfv39qvtvOl_"
   },
   "outputs": [],
   "source": [
    "# With gradient descent training, even this much data is prohibitive. Subset the training data for faster turnaround.\n",
    "train_subset = 10000\n",
    "#set everything up for training\n",
    "graph = tf.Graph()\n",
    "with graph.as_default():\n",
    "    # Input data. Load the training, validation and test data into constants that are attached to the graph.\n",
    "    tf_train_dataset = tf.constant(train_dataset[:train_subset, :])\n",
    "    tf_train_labels  = tf.constant(train_labels[:train_subset])\n",
    "    tf_valid_dataset = tf.constant(valid_dataset)\n",
    "    tf_test_dataset  = tf.constant(test_dataset)\n",
    "    # Variables. These are the parameters that we are going to be training. The weight matrix will be initialized \n",
    "    # using random values following a (truncated) normal distribution. The biases get initialized to zero.\n",
    "    weights   = tf.Variable(tf.truncated_normal([image_size * image_size, num_labels]))\n",
    "    biases    = tf.Variable(tf.zeros([num_labels]))\n",
    "    # Training computation. We multiply the inputs with the weight matrix, and add biases.\n",
    "    logits    = tf.matmul(tf_train_dataset, weights) + biases\n",
    "    #We compute the softmax and cross-entropy (it's one operation in TensorFlow, because it's very common, and it \n",
    "    #can be optimized). We take the average of this cross-entropy across all training examples: that's our loss.\n",
    "    loss      = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits, tf_train_labels))\n",
    "    # Optimizer. We are going to find the minimum of this loss using gradient descent.\n",
    "    optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(loss)\n",
    "    # Predictions for the training, validation, and test data.\n",
    "    # These are not part of training, but merely here so that we can report accuracy figures as we train.\n",
    "    train_prediction = tf.nn.softmax(logits)\n",
    "    valid_prediction = tf.nn.softmax(tf.matmul(tf_valid_dataset, weights) + biases)\n",
    "    test_prediction  = tf.nn.softmax(tf.matmul(tf_test_dataset,  weights) + biases)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "KQcL4uqISHjP"
   },
   "source": [
    "Let's run this computation and iterate:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "output_extras": [
      {
       "item_id": 9
      }
     ]
    },
    "colab_type": "code",
    "collapsed": false,
    "executionInfo": {
     "elapsed": 57454,
     "status": "ok",
     "timestamp": 1449847994134,
     "user": {
      "color": "",
      "displayName": "",
      "isAnonymous": false,
      "isMe": true,
      "permissionId": "",
      "photoUrl": "",
      "sessionId": "0",
      "userId": ""
     },
     "user_tz": 480
    },
    "id": "z2cjdenH869W",
    "outputId": "4c037ba1-b526-4d8e-e632-91e2a0333267",
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Initialized\n",
      "Loss at step 0: 17.033060\n",
      "Training accuracy: 3.2%\n",
      "Validation accuracy: 26.8%\n",
      "Loss at step 100: 1.530666\n",
      "Training accuracy: 82.2%\n",
      "Validation accuracy: 81.5%\n",
      "Loss at step 200: 1.632765\n",
      "Training accuracy: 84.0%\n",
      "Validation accuracy: 83.6%\n",
      "Loss at step 300: 1.912267\n",
      "Training accuracy: 84.5%\n",
      "Validation accuracy: 84.0%\n",
      "Loss at step 400: 2.243961\n",
      "Training accuracy: 84.8%\n",
      "Validation accuracy: 84.2%\n",
      "Loss at step 500: 2.599519\n",
      "Training accuracy: 84.8%\n",
      "Validation accuracy: 84.1%\n",
      "Loss at step 600: 2.968367\n",
      "Training accuracy: 84.9%\n",
      "Validation accuracy: 84.1%\n",
      "Loss at step 700: 3.345928\n",
      "Training accuracy: 84.9%\n",
      "Validation accuracy: 84.2%\n",
      "Loss at step 800: 3.729438\n",
      "Training accuracy: 84.9%\n",
      "Validation accuracy: 84.3%\n",
      "Test accuracy: 84.6%\n"
     ]
    }
   ],
   "source": [
    "num_steps = 801\n",
    "def accuracy(predictions, labels):\n",
    "    #argmax returns the indices of the maximum values across dimension 1 ie colums\n",
    "    #predictions  has shape (10000,10). this == tests if the max from predictions matches the max (ie the only non-null) label\n",
    "    sum_all_correct = np.sum(np.argmax(predictions, 1) == np.argmax(labels, 1))\n",
    "    sum_all = predictions.shape[0]\n",
    "    return 100.0 * sum_all_correct / sum_all\n",
    "\n",
    "with tf.Session(graph=graph) as session:\n",
    "    # This is a one-time operation which ensures the parameters get initialized as\n",
    "    # we described in the graph: random weights for the matrix, zeros for the biases. \n",
    "    tf.initialize_all_variables().run()\n",
    "    print('Initialized')\n",
    "    for step in range(num_steps):\n",
    "        # Run the computations. We tell .run() that we want to run the optimizer,\n",
    "        # and get the loss value and the training predictions returned as numpy arrays.\n",
    "        _, l, predictions = session.run([optimizer, loss, train_prediction])\n",
    "        if (step % 100 == 0):\n",
    "            print('Loss at step %d: %f' % (step, l))\n",
    "            print('Training accuracy: %.1f%%' % accuracy(predictions, train_labels[:train_subset, :]))\n",
    "            # Calling .eval() on valid_prediction is basically like calling run(), but\n",
    "            # just to get that one numpy array. Note that it recomputes all its graph dependencies.\n",
    "            print('Validation accuracy: %.1f%%' % accuracy(valid_prediction.eval(), valid_labels))\n",
    "\n",
    "    print('Test accuracy: %.1f%%' % accuracy(test_prediction.eval(), test_labels))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "x68f-hxRGm3H"
   },
   "source": [
    "Let's now switch to stochastic gradient descent training instead, which is much faster.\n",
    "\n",
    "The graph will be similar, except that instead of holding all the training data into a constant node, we create a `Placeholder` node which will be fed actual data at every call of `sesion.run()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     }
    },
    "colab_type": "code",
    "collapsed": true,
    "id": "qhPMzWYRGrzM"
   },
   "outputs": [],
   "source": [
    "batch_size = 128\n",
    "\n",
    "graph = tf.Graph()\n",
    "\n",
    "with graph.as_default():\n",
    "    # Input data. For the training data, we use a placeholder that will be fed at run time with a training minibatch.\n",
    "    tf_train_dataset = tf.placeholder(tf.float32, shape=(batch_size, image_size * image_size))\n",
    "    tf_train_labels  = tf.placeholder(tf.float32, shape=(batch_size, num_labels))\n",
    "    tf_valid_dataset = tf.constant(valid_dataset)\n",
    "    tf_test_dataset  = tf.constant(test_dataset)\n",
    "\n",
    "    # Variables.\n",
    "    weights = tf.Variable(tf.truncated_normal([image_size * image_size, num_labels]))\n",
    "    biases  = tf.Variable(tf.zeros([num_labels]))\n",
    "\n",
    "    # Training computation.\n",
    "    logits = tf.matmul(tf_train_dataset, weights) + biases\n",
    "    loss   = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits, tf_train_labels))\n",
    "\n",
    "    # Optimizer.\n",
    "    optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(loss)\n",
    "\n",
    "    # Predictions for the training, validation, and test data.\n",
    "    train_prediction = tf.nn.softmax(logits)\n",
    "    valid_prediction = tf.nn.softmax(tf.matmul(tf_valid_dataset, weights) + biases)\n",
    "    test_prediction  = tf.nn.softmax(tf.matmul(tf_test_dataset, weights) + biases)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "XmVZESmtG4JH"
   },
   "source": [
    "Let's run it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "cellView": "both",
    "colab": {
     "autoexec": {
      "startup": false,
      "wait_interval": 0
     },
     "output_extras": [
      {
       "item_id": 6
      }
     ]
    },
    "colab_type": "code",
    "collapsed": false,
    "executionInfo": {
     "elapsed": 66292,
     "status": "ok",
     "timestamp": 1449848003013,
     "user": {
      "color": "",
      "displayName": "",
      "isAnonymous": false,
      "isMe": true,
      "permissionId": "",
      "photoUrl": "",
      "sessionId": "0",
      "userId": ""
     },
     "user_tz": 480
    },
    "id": "FoF91pknG_YW",
    "outputId": "d255c80e-954d-4183-ca1c-c7333ce91d0a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Initialized\n",
      "Minibatch loss at step 0: 12.113108\n",
      "Minibatch accuracy: 18.0%\n",
      "Validation accuracy: 43.6%\n",
      "Minibatch loss at step 500: 1.391131\n",
      "Minibatch accuracy: 89.1%\n",
      "Validation accuracy: 83.2%\n",
      "Minibatch loss at step 1000: 2.604872\n",
      "Minibatch accuracy: 84.4%\n",
      "Validation accuracy: 83.6%\n",
      "Minibatch loss at step 1500: 1.967522\n",
      "Minibatch accuracy: 89.8%\n",
      "Validation accuracy: 83.8%\n",
      "Minibatch loss at step 2000: 9.103417\n",
      "Minibatch accuracy: 82.0%\n",
      "Validation accuracy: 85.0%\n",
      "Minibatch loss at step 2500: 9.347385\n",
      "Minibatch accuracy: 87.5%\n",
      "Validation accuracy: 83.7%\n",
      "Minibatch loss at step 3000: 6.351113\n",
      "Minibatch accuracy: 89.8%\n",
      "Validation accuracy: 85.1%\n",
      "Test accuracy: 85.6%\n"
     ]
    }
   ],
   "source": [
    "num_steps = 3001\n",
    "\n",
    "with tf.Session(graph=graph) as session:\n",
    "    tf.initialize_all_variables().run()\n",
    "    print(\"Initialized\")\n",
    "    for step in range(num_steps):\n",
    "        # Pick an offset within the training data, which has been randomized.\n",
    "        # Note: we could use better randomization across epochs.\n",
    "        offset = (step * batch_size) % (train_labels.shape[0] - batch_size)\n",
    "        # Generate a minibatch.\n",
    "        batch_data   = train_dataset[offset:(offset + batch_size), :]\n",
    "        batch_labels = train_labels [offset:(offset + batch_size), :]\n",
    "        # Prepare a dictionary telling the session where to feed the minibatch.\n",
    "        # The key is the placeholder node of the graph to be fed and the value is the numpy array to feed to it.\n",
    "        feed_dict = {tf_train_dataset : batch_data, tf_train_labels : batch_labels}\n",
    "        _, l, predictions = session.run([optimizer, loss, train_prediction], feed_dict=feed_dict)\n",
    "        if (step % 500 == 0):\n",
    "            print(\"Minibatch loss at step %d: %f\" % (step, l))\n",
    "            print(\"Minibatch accuracy: %.1f%%\"  % accuracy(predictions, batch_labels))\n",
    "            print(\"Validation accuracy: %.1f%%\" % accuracy(valid_prediction.eval(), valid_labels))\n",
    "    print(\"Test accuracy: %.1f%%\" % accuracy(test_prediction.eval(), test_labels))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "7omWxtvLLxik"
   },
   "source": [
    "---\n",
    "Problem\n",
    "-------\n",
    "\n",
    "Turn the logistic regression example with SGD into a 1-hidden layer neural network with rectified linear units (nn.relu()) and 1024 hidden nodes. This model should improve your validation / test accuracy.\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Initialized\n",
      "Minibatch loss at step 0: 329.736023\n",
      "Minibatch accuracy: 9.4%\n",
      "Validation accuracy: 0.0%\n",
      "Minibatch loss at step 500: nan\n",
      "Minibatch accuracy: 100.0%\n",
      "Validation accuracy: 100.0%\n",
      "Minibatch loss at step 1000: nan\n",
      "Minibatch accuracy: 100.0%\n",
      "Validation accuracy: 100.0%\n",
      "Minibatch loss at step 1500: nan\n",
      "Minibatch accuracy: 100.0%\n",
      "Validation accuracy: 100.0%\n",
      "Minibatch loss at step 2000: nan\n",
      "Minibatch accuracy: 100.0%\n",
      "Validation accuracy: 100.0%\n",
      "Minibatch loss at step 2500: nan\n",
      "Minibatch accuracy: 100.0%\n",
      "Validation accuracy: 100.0%\n",
      "Minibatch loss at step 3000: nan\n",
      "Minibatch accuracy: 100.0%\n",
      "Validation accuracy: 100.0%\n",
      "Test accuracy: 100.0%\n"
     ]
    }
   ],
   "source": [
    "import math\n",
    "\n",
    "batch_size = 128 #this random number of training patterns will be used\n",
    "graph = tf.Graph()\n",
    "hidden1_units = 1024\n",
    "\n",
    "#initialize everything\n",
    "with graph.as_default():\n",
    "    # Input data. The training data is currently empty, but a random minibatch will be fed in the placeholder during training\n",
    "    tf_train_dataset = tf.placeholder(tf.float32, shape=(batch_size, image_size * image_size))\n",
    "    tf_train_labels  = tf.placeholder(tf.float32, shape=(batch_size, num_labels))\n",
    "    tf_valid_dataset = tf.constant(valid_dataset)\n",
    "    tf_test_dataset  = tf.constant(test_dataset)\n",
    "    \n",
    "    \n",
    "    # Input layer\n",
    "    weights = tf.Variable(tf.truncated_normal([image_size * image_size, hidden1_units]))\n",
    "    biases  = tf.Variable(tf.zeros([hidden1_units]))\n",
    "    InputLayerOutput = tf.matmul(tf_train_dataset, weights) + biases\n",
    "        \n",
    "    # 1st hidden layer\n",
    "    weights1= tf.Variable(tf.truncated_normal([hidden1_units, num_labels]))\n",
    "    biases1 = tf.Variable(tf.zeros([num_labels]))\n",
    "    \n",
    "    #hidden1 = tf.nn.relu(InputLayerOutput)\n",
    "      \n",
    "    \n",
    "    # Training computation.\n",
    "    # logits = tf.matmul(tf_train_dataset, weights) + biases\n",
    "    logits = tf.nn.relu(tf.matmul(InputLayerOutput, weights1) + biases1)\n",
    "    \n",
    "    loss   = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits, tf_train_labels))\n",
    "    # Optimizer.\n",
    "    optimizer = tf.train.GradientDescentOptimizer(0.5).minimize(loss)\n",
    "    # Predictions for the training, validation, and test data.\n",
    "    train_prediction = tf.nn.softmax(logits)\n",
    "    valid_prediction = tf.nn.softmax(tf.matmul(tf_valid_dataset, weights) + biases)\n",
    "    test_prediction  = tf.nn.softmax(tf.matmul(tf_test_dataset, weights) + biases)\n",
    "  \n",
    "    \n",
    "    \n",
    "#train the thing\n",
    "num_steps = 3001\n",
    "with tf.Session(graph=graph) as session:\n",
    "    tf.initialize_all_variables().run()\n",
{'fraction_non_alphanumeric': 0.16618867263611883, 'fraction_numerical': 0.0363640344155702, 'mean_word_length': 2.5505635445005828, 'pattern_counts': {'":': 444, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3938857', 'n_tokens_mistral': 17251, 'n_tokens_neox': 15803, 'n_words': 3848}