{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'2.0.8'"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import keras\n",
    "keras.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Classifying movie reviews: a binary classification example\n",
    "\n",
    "This notebook contains the code samples found in Chapter 3, Section 5 of [Deep Learning with Python](https://www.manning.com/books/deep-learning-with-python?a_aid=keras&a_bid=76564dff). Note that the original text features far more content, in particular further explanations and figures: in this notebook, you will only find source code and related comments.\n",
    "\n",
    "----\n",
    "\n",
    "\n",
    "Two-class classification, or binary classification, may be the most widely applied kind of machine learning problem. In this example, we \n",
    "will learn to classify movie reviews into \"positive\" reviews and \"negative\" reviews, just based on the text content of the reviews."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The IMDB dataset\n",
    "\n",
    "\n",
    "We'll be working with \"IMDB dataset\", a set of 50,000 highly-polarized reviews from the Internet Movie Database. They are split into 25,000 \n",
    "reviews for training and 25,000 reviews for testing, each set consisting in 50% negative and 50% positive reviews.\n",
    "\n",
    "Why do we have these two separate training and test sets? You should never test a machine learning model on the same data that you used to \n",
    "train it! Just because a model performs well on its training data doesn't mean that it will perform well on data it has never seen, and \n",
    "what you actually care about is your model's performance on new data (since you already know the labels of your training data -- obviously \n",
    "you don't need your model to predict those). For instance, it is possible that your model could end up merely _memorizing_ a mapping between \n",
    "your training samples and their targets -- which would be completely useless for the task of predicting targets for data never seen before. \n",
    "We will go over this point in much more detail in the next chapter.\n",
    "\n",
    "Just like the MNIST dataset, the IMDB dataset comes packaged with Keras. It has already been preprocessed: the reviews (sequences of words) \n",
    "have been turned into sequences of integers, where each integer stands for a specific word in a dictionary.\n",
    "\n",
    "The following code will load the dataset (when you run it for the first time, about 80MB of data will be downloaded to your machine):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from keras.datasets import imdb\n",
    "\n",
    "(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "The argument `num_words=10000` means that we will only keep the top 10,000 most frequently occurring words in the training data. Rare words \n",
    "will be discarded. This allows us to work with vector data of manageable size.\n",
    "\n",
    "The variables `train_data` and `test_data` are lists of reviews, each review being a list of word indices (encoding a sequence of words). \n",
    "`train_labels` and `test_labels` are lists of 0s and 1s, where 0 stands for \"negative\" and 1 stands for \"positive\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1,\n",
       " 14,\n",
       " 22,\n",
       " 16,\n",
       " 43,\n",
       " 530,\n",
       " 973,\n",
       " 1622,\n",
       " 1385,\n",
       " 65,\n",
       " 458,\n",
       " 4468,\n",
       " 66,\n",
       " 3941,\n",
       " 4,\n",
       " 173,\n",
       " 36,\n",
       " 256,\n",
       " 5,\n",
       " 25,\n",
       " 100,\n",
       " 43,\n",
       " 838,\n",
       " 112,\n",
       " 50,\n",
       " 670,\n",
       " 2,\n",
       " 9,\n",
       " 35,\n",
       " 480,\n",
       " 284,\n",
       " 5,\n",
       " 150,\n",
       " 4,\n",
       " 172,\n",
       " 112,\n",
       " 167,\n",
       " 2,\n",
       " 336,\n",
       " 385,\n",
       " 39,\n",
       " 4,\n",
       " 172,\n",
       " 4536,\n",
       " 1111,\n",
       " 17,\n",
       " 546,\n",
       " 38,\n",
       " 13,\n",
       " 447,\n",
       " 4,\n",
       " 192,\n",
       " 50,\n",
       " 16,\n",
       " 6,\n",
       " 147,\n",
       " 2025,\n",
       " 19,\n",
       " 14,\n",
       " 22,\n",
       " 4,\n",
       " 1920,\n",
       " 4613,\n",
       " 469,\n",
       " 4,\n",
       " 22,\n",
       " 71,\n",
       " 87,\n",
       " 12,\n",
       " 16,\n",
       " 43,\n",
       " 530,\n",
       " 38,\n",
       " 76,\n",
       " 15,\n",
       " 13,\n",
       " 1247,\n",
       " 4,\n",
       " 22,\n",
       " 17,\n",
       " 515,\n",
       " 17,\n",
       " 12,\n",
       " 16,\n",
       " 626,\n",
       " 18,\n",
       " 2,\n",
       " 5,\n",
       " 62,\n",
       " 386,\n",
       " 12,\n",
       " 8,\n",
       " 316,\n",
       " 8,\n",
       " 106,\n",
       " 5,\n",
       " 4,\n",
       " 2223,\n",
       " 5244,\n",
       " 16,\n",
       " 480,\n",
       " 66,\n",
       " 3785,\n",
       " 33,\n",
       " 4,\n",
       " 130,\n",
       " 12,\n",
       " 16,\n",
       " 38,\n",
       " 619,\n",
       " 5,\n",
       " 25,\n",
       " 124,\n",
       " 51,\n",
       " 36,\n",
       " 135,\n",
       " 48,\n",
       " 25,\n",
       " 1415,\n",
       " 33,\n",
       " 6,\n",
       " 22,\n",
       " 12,\n",
       " 215,\n",
       " 28,\n",
       " 77,\n",
       " 52,\n",
       " 5,\n",
       " 14,\n",
       " 407,\n",
       " 16,\n",
       " 82,\n",
       " 2,\n",
       " 8,\n",
       " 4,\n",
       " 107,\n",
       " 117,\n",
       " 5952,\n",
       " 15,\n",
       " 256,\n",
       " 4,\n",
       " 2,\n",
       " 7,\n",
       " 3766,\n",
       " 5,\n",
       " 723,\n",
       " 36,\n",
       " 71,\n",
       " 43,\n",
       " 530,\n",
       " 476,\n",
       " 26,\n",
       " 400,\n",
       " 317,\n",
       " 46,\n",
       " 7,\n",
       " 4,\n",
       " 2,\n",
       " 1029,\n",
       " 13,\n",
       " 104,\n",
       " 88,\n",
       " 4,\n",
       " 381,\n",
       " 15,\n",
       " 297,\n",
       " 98,\n",
       " 32,\n",
       " 2071,\n",
       " 56,\n",
       " 26,\n",
       " 141,\n",
       " 6,\n",
       " 194,\n",
       " 7486,\n",
       " 18,\n",
       " 4,\n",
       " 226,\n",
       " 22,\n",
       " 21,\n",
       " 134,\n",
       " 476,\n",
       " 26,\n",
       " 480,\n",
       " 5,\n",
       " 144,\n",
       " 30,\n",
       " 5535,\n",
       " 18,\n",
       " 51,\n",
       " 36,\n",
       " 28,\n",
       " 224,\n",
       " 92,\n",
       " 25,\n",
       " 104,\n",
       " 4,\n",
       " 226,\n",
       " 65,\n",
       " 16,\n",
       " 38,\n",
       " 1334,\n",
       " 88,\n",
       " 12,\n",
       " 16,\n",
       " 283,\n",
       " 5,\n",
       " 16,\n",
       " 4472,\n",
       " 113,\n",
       " 103,\n",
       " 32,\n",
       " 15,\n",
       " 16,\n",
       " 5345,\n",
       " 19,\n",
       " 178,\n",
       " 32]"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_data[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_labels[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since we restricted ourselves to the top 10,000 most frequent words, no word index will exceed 10,000:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "9999"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "max([max(sequence) for sequence in train_data])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For kicks, here's how you can quickly decode one of these reviews back to English words:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# word_index is a dictionary mapping words to an integer index\n",
    "word_index = imdb.get_word_index()\n",
    "# We reverse it, mapping integer indices to words\n",
    "reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])\n",
    "# We decode the review; note that our indices were offset by 3\n",
    "# because 0, 1 and 2 are reserved indices for \"padding\", \"start of sequence\", and \"unknown\".\n",
    "decoded_review = ' '.join([reverse_word_index.get(i - 3, '?') for i in train_data[0]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"? this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert ? is an amazing actor and now the same being director ? father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for ? and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also ? to the two little boy's that played the ? of norman and paul they were just brilliant children are often left out of the ? list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all\""
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "decoded_review"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preparing the data\n",
    "\n",
    "\n",
    "We cannot feed lists of integers into a neural network. We have to turn our lists into tensors. There are two ways we could do that:\n",
    "\n",
    "* We could pad our lists so that they all have the same length, and turn them into an integer tensor of shape `(samples, word_indices)`, \n",
    "then use as first layer in our network a layer capable of handling such integer tensors (the `Embedding` layer, which we will cover in \n",
    "detail later in the book).\n",
    "* We could one-hot-encode our lists to turn them into vectors of 0s and 1s. Concretely, this would mean for instance turning the sequence \n",
    "`[3, 5]` into a 10,000-dimensional vector that would be all-zeros except for indices 3 and 5, which would be ones. Then we could use as \n",
    "first layer in our network a `Dense` layer, capable of handling floating point vector data.\n",
    "\n",
    "We will go with the latter solution. Let's vectorize our data, which we will do manually for maximum clarity:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "def vectorize_sequences(sequences, dimension=10000):\n",
    "    # Create an all-zero matrix of shape (len(sequences), dimension)\n",
    "    results = np.zeros((len(sequences), dimension))\n",
    "    for i, sequence in enumerate(sequences):\n",
    "        results[i, sequence] = 1.  # set specific indices of results[i] to 1s\n",
    "    return results\n",
    "\n",
    "# Our vectorized training data\n",
    "x_train = vectorize_sequences(train_data)\n",
    "# Our vectorized test data\n",
    "x_test = vectorize_sequences(test_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's what our samples look like now:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.,  1.,  1., ...,  0.,  0.,  0.])"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x_train[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We should also vectorize our labels, which is straightforward:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Our vectorized labels\n",
    "y_train = np.asarray(train_labels).astype('float32')\n",
    "y_test = np.asarray(test_labels).astype('float32')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now our data is ready to be fed into a neural network."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Building our network\n",
    "\n",
    "\n",
    "Our input data is simply vectors, and our labels are scalars (1s and 0s): this is the easiest setup you will ever encounter. A type of \n",
    "network that performs well on such a problem would be a simple stack of fully-connected (`Dense`) layers with `relu` activations: `Dense(16, \n",
    "activation='relu')`\n",
    "\n",
    "The argument being passed to each `Dense` layer (16) is the number of \"hidden units\" of the layer. What's a hidden unit? It's a dimension \n",
    "in the representation space of the layer. You may remember from the previous chapter that each such `Dense` layer with a `relu` activation implements \n",
    "the following chain of tensor operations:\n",
    "\n",
    "`output = relu(dot(W, input) + b)`\n",
    "\n",
    "Having 16 hidden units means that the weight matrix `W` will have shape `(input_dimension, 16)`, i.e. the dot product with `W` will project the \n",
    "input data onto a 16-dimensional representation space (and then we would add the bias vector `b` and apply the `relu` operation). You can \n",
    "intuitively understand the dimensionality of your representation space as \"how much freedom you are allowing the network to have when \n",
    "learning internal representations\". Having more hidden units (a higher-dimensional representation space) allows your network to learn more \n",
    "complex representations, but it makes your network more computationally expensive and may lead to learning unwanted patterns (patterns that \n",
    "will improve performance on the training data but not on the test data).\n",
    "\n",
    "There are two key architecture decisions to be made about such stack of dense layers:\n",
    "\n",
    "* How many layers to use.\n",
    "* How many \"hidden units\" to chose for each layer.\n",
    "\n",
    "In the next chapter, you will learn formal principles to guide you in making these choices. \n",
    "For the time being, you will have to trust us with the following architecture choice: \n",
    "two intermediate layers with 16 hidden units each, \n",
    "and a third layer which will output the scalar prediction regarding the sentiment of the current review. \n",
    "The intermediate layers will use `relu` as their \"activation function\", \n",
    "and the final layer will use a sigmoid activation so as to output a probability \n",
    "(a score between 0 and 1, indicating how likely the sample is to have the target \"1\", i.e. how likely the review is to be positive). \n",
    "A `relu` (rectified linear unit) is a function meant to zero-out negative values, \n",
    "while a sigmoid \"squashes\" arbitrary values into the `[0, 1]` interval, thus outputting something that can be interpreted as a probability."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's what our network looks like:\n",
    "\n",
    "![3-layer network](https://s3.amazonaws.com/book.keras.io/img/ch3/3_layer_network.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And here's the Keras implementation, very similar to the MNIST example you saw previously:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from keras import models\n",
    "from keras import layers\n",
    "\n",
    "model = models.Sequential()\n",
    "model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))\n",
    "model.add(layers.Dense(16, activation='relu'))\n",
    "model.add(layers.Dense(1, activation='sigmoid'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Lastly, we need to pick a loss function and an optimizer. Since we are facing a binary classification problem and the output of our network \n",
    "is a probability (we end our network with a single-unit layer with a sigmoid activation), is it best to use the `binary_crossentropy` loss. \n",
    "It isn't the only viable choice: you could use, for instance, `mean_squared_error`. But crossentropy is usually the best choice when you \n",
    "are dealing with models that output probabilities. Crossentropy is a quantity from the field of Information Theory, that measures the \"distance\" \n",
    "between probability distributions, or in our case, between the ground-truth distribution and our predictions.\n",
    "\n",
    "Here's the step where we configure our model with the `rmsprop` optimizer and the `binary_crossentropy` loss function. Note that we will \n",
    "also monitor accuracy during training."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "model.compile(optimizer='rmsprop',\n",
    "              loss='binary_crossentropy',\n",
    "              metrics=['accuracy'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We are passing our optimizer, loss function and metrics as strings, which is possible because `rmsprop`, `binary_crossentropy` and \n",
    "`accuracy` are packaged as part of Keras. Sometimes you may want to configure the parameters of your optimizer, or pass a custom loss \n",
    "function or metric function. This former can be done by passing an optimizer class instance as the `optimizer` argument:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from keras import optimizers\n",
    "\n",
    "model.compile(optimizer=optimizers.RMSprop(lr=0.001),\n",
    "              loss='binary_crossentropy',\n",
    "              metrics=['accuracy'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The latter can be done by passing function objects as the `loss` or `metrics` arguments:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from keras import losses\n",
    "from keras import metrics\n",
    "\n",
    "model.compile(optimizer=optimizers.RMSprop(lr=0.001),\n",
    "              loss=losses.binary_crossentropy,\n",
    "              metrics=[metrics.binary_accuracy])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Validating our approach\n",
    "\n",
    "In order to monitor during training the accuracy of the model on data that it has never seen before, we will create a \"validation set\" by \n",
    "setting apart 10,000 samples from the original training data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "x_val = x_train[:10000]\n",
    "partial_x_train = x_train[10000:]\n",
    "\n",
    "y_val = y_train[:10000]\n",
    "partial_y_train = y_train[10000:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "We will now train our model for 20 epochs (20 iterations over all samples in the `x_train` and `y_train` tensors), in mini-batches of 512 \n",
    "samples. At this same time we will monitor loss and accuracy on the 10,000 samples that we set apart. This is done by passing the \n",
    "validation data as the `validation_data` argument:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 15000 samples, validate on 10000 samples\n",
      "Epoch 1/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.5103 - acc: 0.7911 - val_loss: 0.4016 - val_acc: 0.8628\n",
      "Epoch 2/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.3110 - acc: 0.9031 - val_loss: 0.3085 - val_acc: 0.8870\n",
      "Epoch 3/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.2309 - acc: 0.9235 - val_loss: 0.2803 - val_acc: 0.8908\n",
      "Epoch 4/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.1795 - acc: 0.9428 - val_loss: 0.2735 - val_acc: 0.8893\n",
      "Epoch 5/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.1475 - acc: 0.9526 - val_loss: 0.2788 - val_acc: 0.8890\n",
      "Epoch 6/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.1185 - acc: 0.9638 - val_loss: 0.3330 - val_acc: 0.8764\n",
      "Epoch 7/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.1005 - acc: 0.9703 - val_loss: 0.3055 - val_acc: 0.8838\n",
      "Epoch 8/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0818 - acc: 0.9773 - val_loss: 0.3344 - val_acc: 0.8769\n",
      "Epoch 9/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0696 - acc: 0.9814 - val_loss: 0.3607 - val_acc: 0.8800\n",
      "Epoch 10/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0547 - acc: 0.9873 - val_loss: 0.3776 - val_acc: 0.8785\n",
      "Epoch 11/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0453 - acc: 0.9895 - val_loss: 0.4035 - val_acc: 0.8765\n",
      "Epoch 12/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0353 - acc: 0.9930 - val_loss: 0.4437 - val_acc: 0.8766\n",
      "Epoch 13/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0269 - acc: 0.9956 - val_loss: 0.4637 - val_acc: 0.8747\n",
      "Epoch 14/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0212 - acc: 0.9968 - val_loss: 0.4877 - val_acc: 0.8714\n",
      "Epoch 15/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0162 - acc: 0.9977 - val_loss: 0.6080 - val_acc: 0.8625\n",
      "Epoch 16/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0115 - acc: 0.9993 - val_loss: 0.5778 - val_acc: 0.8698\n",
      "Epoch 17/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0116 - acc: 0.9979 - val_loss: 0.5906 - val_acc: 0.8702\n",
      "Epoch 18/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0054 - acc: 0.9998 - val_loss: 0.6204 - val_acc: 0.8639\n",
      "Epoch 19/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0083 - acc: 0.9984 - val_loss: 0.6419 - val_acc: 0.8676\n",
      "Epoch 20/20\n",
      "15000/15000 [==============================] - 1s - loss: 0.0031 - acc: 0.9998 - val_loss: 0.6796 - val_acc: 0.8683\n"
     ]
    }
   ],
   "source": [
    "history = model.fit(partial_x_train,\n",
    "                    partial_y_train,\n",
    "                    epochs=20,\n",
    "                    batch_size=512,\n",
    "                    validation_data=(x_val, y_val))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On CPU, this will take less than two seconds per epoch -- training is over in 20 seconds. At the end of every epoch, there is a slight pause \n",
    "as the model computes its loss and accuracy on the 10,000 samples of the validation data.\n",
    "\n",
    "Note that the call to `model.fit()` returns a `History` object. This object has a member `history`, which is a dictionary containing data \n",
    "about everything that happened during training. Let's take a look at it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dict_keys(['val_acc', 'acc', 'val_loss', 'loss'])"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "history_dict = history.history\n",
    "history_dict.keys()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It contains 4 entries: one per metric that was being monitored, during training and during validation. Let's use Matplotlib to plot the \n",
    "training and validation loss side by side, as well as the training and validation accuracy:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\ncko89mmnnbbTPu+++y5DhgwB4NBDD6Vz585lxvf+++9zzDHH0KpVKxo0aMCwYcOYPXs2BxxwAIsX\nL2bMmDHMnDmTpk2bAtC5c2dGjBjBtGnTNPhMJIlycuDYY+GKK+D442HhwupPCFAHk0JpLfdxtejv\nvvvuhfeXLFnCXXfdxdtvv82CBQsYMGBAif31GzZsWHi/fv365Ofnl3jsXXfdtdx9Kqtly5YsWLCA\nvn37MnnyZC688EIAZs6cyUUXXcScOXPo1asXW7eWOAhdRBLkDg8/DF27wty58NBD8OKL8JOfpCae\nOpcUxo+Hxo133Na4cdget++++44mTZqw5557smLFCmbOnJn0c/Tp04enn34agI8++qjEkkhRvXv3\nZtasWaxdu5b8/HymT59Ov379WL16Ne7OGWecwbhx45g3bx5bt24lNzeXY445hgkTJrBmzRo2Fq+L\nE5GErVoFv/wlnHde6GG0YAGcey6kcihPnWtTKGg3SGbvo0T16NGDTp06cfDBB9O+fXv69OmT9HNc\ncsklnH322XTq1KnwVlD1U5K2bdvypz/9iaOPPhp355RTTuGkk05i3rx5/PrXv8bdMTNuvfVW8vPz\nGTZsGBs2bGDbtm1cddVVNGnSJOnvQaQueP750Mllwwa44w649FIopXNitapxazRnZmZ68UV2Fi1a\nRMeOHVMUUXrJz88nPz+fRo0asWTJEo4//niWLFnCLrukV/7X70zqqnXrQgJ47LFQOnj8cSjW1yQW\nZjbX3TPL2y+9rhRSZXl5eRx77LHk5+fj7tx3331plxBE6qo33wzVQytWwA03wHXXQbr119DVopZp\n1qwZc+fOTXUYIlLEsmUwYQL85S9w0EHwr3/BYYelOqqSKSmIiMRg82aYMQMeeAD+9rewbcwY+POf\nd+7skk6UFEREkuizz0IiePRR+OYbaNsWrr8+9DBq3z7V0ZVPSUFEpIo2bYLnnoP774fZs6F+fTjl\nFLjgAvjFL8LjmkJJQUSkkhYsCIlg6tTQq2j//UP10MiRsPfeqY6uctKgV2zN179//50Gok2cOJGL\nL764zNftscceAHz99dcMHjy4xH2OPvpoinfBLW7ixIk7DCI78cQTkzIv0U033cTtt99e5eOI1CYb\nNoRE0Ls3HHooTJkCJ5wAb70Vqo7Gjq25CQGUFJJi6NChTJ8+fYdt06dPZ+jQoQm9/qc//SnPPvts\n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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7f9e8b6b6588>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "acc = history.history['acc']\n",
    "val_acc = history.history['val_acc']\n",
    "loss = history.history['loss']\n",
    "val_loss = history.history['val_loss']\n",
    "\n",
    "epochs = range(1, len(acc) + 1)\n",
    "\n",
    "# \"bo\" is for \"blue dot\"\n",
    "plt.plot(epochs, loss, 'bo', label='Training loss')\n",
    "# b is for \"solid blue line\"\n",
    "plt.plot(epochs, val_loss, 'b', label='Validation loss')\n",
    "plt.title('Training and validation loss')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\nrFmT61CkCo0bN6aoqIiGDRvWaH0lCxFJWVlZGc2bN6dz586YWa7DkTjuzpIlSygrK6NLly412oaK\noUQkZWvWrKFNmzZKFLWUmdGmTZuUrvyULEQkLZQoardU/z5KFiIikpCShYhk3fDh0Lkz1KsXXocP\nT217S5YsoXv37nTv3p0dd9yRDh06bHy/du3apLZx4YUX8sUXX1S7zIMPPsjwVIPNU6rgFpGsGj4c\nBg2CVavC+9mzw3uA/v1rts02bdowZcoUAG699VaaNWvGtddeu9ky7o67U69e5b+Rn3jiiYT7ufzy\ny2sWYB2gKwsRyaobb9yUKCqsWhWmp9vMmTPp2rUr/fv3Z5999mHBggUMGjSI4uJi9tlnH2677baN\ny/bq1YspU6ZQXl5Oy5Ytue666+jWrRuHHHIIixYtAuCmm27i3nvv3bj8ddddR8+ePdlzzz2ZOHEi\nACtXruTMM8+ka9eu9O3bl+Li4o2JLNYtt9zCz372M/bdd18uvfRS3B2AL7/8kmOOOYZu3brRo0cP\nZs2aBcDvf/979ttvP7p168aNmThYCShZiEhWzZmzddNT9fnnn3P11Vczffp0OnTowB//+EdKSkoo\nLS3ljTfeYPr06Vuss2zZMo488khKS0s55JBDGDZsWKXbdnc+/PBD/vKXv2xMPA888AA77rgj06dP\n57e//S0ff/xxpeteeeWVTJo0iWnTprFs2TJee+01APr168fVV19NaWkpEydOZPvtt2fs2LG8+uqr\nfPjhh5SWlnLNNdek6egkT8lCRLKqY8etm56qXXfdleLi4o3vR4wYQY8ePejRowefffZZpcmiSZMm\nnHTSSQAceOCBG3/dxzvjjDO2WObdd9/lnHPOAaBbt27ss88+la47btw4evbsSbdu3fj3v//Np59+\nytKlS/nuu+847bTTgHAjXdOmTXnzzTcZOHAgTZo0AaB169ZbfyBSpGQhIll1553QtOnm05o2DdMz\nYdttt904PmPGDO677z7eeustpk6dyoknnljpvQeNGjXaOF6/fn3Ky8sr3fY222yTcJnKrFq1isGD\nBzN69GimTp3KwIEDa/3d70oWIpJV/fvD0KHQqROYhdehQ2teub01fvzxR5o3b852223HggULeP31\n19O+j8MOO4xRo0YBMG3atEqvXFavXk29evVo27Yty5cv56WXXgKgVatWtGvXjrFjxwLhZsdVq1Zx\n/PHHM2zYMFavXg3A999/n/a4E1FrKBHJuv79s5Mc4vXo0YOuXbuy11570alTJw477LC072PIkCGc\nf/75dO1WMfpsAAAN5klEQVTadePQokWLzZZp06YNAwYMoGvXrrRv356DDjpo47zhw4dzySWXcOON\nN9KoUSNeeuklTj31VEpLSykuLqZhw4acdtpp3H777WmPvTpWUQOf74qLi72kpCTXYYgUpM8++4y9\n994712HUCuXl5ZSXl9O4cWNmzJjBCSecwIwZM2jQIPe/zSv7O5nZZHcvrmKVjXIfvYhIHbJixQqO\nPfZYysvLcXceeeSRWpEoUpX/n0BEpBZp2bIlkydPznUYaZfRCm4zO9HMvjCzmWZ2XSXzO5rZeDP7\n2MymmtnJMfOuj9b7wsx+nsk4RUSkehm7sjCz+sCDwPFAGTDJzMa4e2zTgJuAUe7+kJl1BV4BOkfj\n5wD7ADsBb5rZHu6+PlPxiohI1TJ5ZdETmOnuX7v7WmAk0CduGQe2i8ZbAPOj8T7ASHf/yd2/AWZG\n2xMRkRzIZLLoAMyNeV8WTYt1K3CemZURriqGbMW6IiKSJbm+Ka8f8KS7FwEnA8+YWdIxmdkgMysx\ns5LFixdnLEgRqd2OPvroLW6wu/fee7nsssuqXa9Zs2YAzJ8/n759+1a6zFFHHUWiZvn33nsvq2J6\nRzz55JP54Ycfkgk9b2QyWcwDdo55XxRNi3URMArA3d8DGgNtk1wXdx/q7sXuXtyuXbs0hi4i+aRf\nv36MHDlys2kjR46kX79+Sa2/00478eKLL9Z4//HJ4pVXXqFly5Y13l5tlMmms5OA3c2sC+FEfw5w\nbtwyc4BjgSfNbG9CslgMjAGeM7N7CBXcuwMfZjBWEUmTq66CSnrkTkn37hD1DF6pvn37ctNNN7F2\n7VoaNWrErFmzmD9/PocffjgrVqygT58+LF26lHXr1nHHHXfQp8/m1aezZs3i1FNP5ZNPPmH16tVc\neOGFlJaWstdee23sYgPgsssuY9KkSaxevZq+ffvyu9/9jvvvv5/58+dz9NFH07ZtW8aPH0/nzp0p\nKSmhbdu23HPPPRt7rb344ou56qqrmDVrFieddBK9evVi4sSJdOjQgX/+858bOwqsMHbsWO644w7W\nrl1LmzZtGD58ODvssAMrVqxgyJAhlJSUYGbccsstnHnmmbz22mvccMMNrF+/nrZt2zJu3Li0/Q0y\nlizcvdzMBgOvA/WBYe7+qZndBpS4+xjgGuBRM7uaUNl9gYdbyj81s1HAdKAcuFwtoUSkKq1bt6Zn\nz568+uqr9OnTh5EjR3LWWWdhZjRu3JjRo0ez3Xbb8d1333HwwQfTu3fvKp9J/dBDD9G0aVM+++wz\npk6dSo8ePTbOu/POO2ndujXr16/n2GOPZerUqVxxxRXcc889jB8/nrZt2262rcmTJ/PEE0/wwQcf\n4O4cdNBBHHnkkbRq1YoZM2YwYsQIHn30Uc466yxeeuklzjvvvM3W79WrF++//z5mxmOPPcaf//xn\n7r77bm6//XZatGjBtGnTAFi6dCmLFy/mV7/6FRMmTKBLly5p7z8qozflufsrhIrr2Gk3x4xPByrt\nnMXd7wQy1A+liGRKdVcAmVRRFFWRLB5//HEgPHPihhtuYMKECdSrV4958+axcOFCdtxxx0q3M2HC\nBK644goA9t9/f/bff/+N80aNGsXQoUMpLy9nwYIFTJ8+fbP58d59911OP/30jT3fnnHGGbzzzjv0\n7t2bLl260L17d6DqbtDLyso4++yzWbBgAWvXrqVLly4AvPnmm5sVu7Vq1YqxY8dyxBFHbFwm3d2Y\n57qCO+fS/SxgEcmNPn36MG7cOD766CNWrVrFgQceCISO+RYvXszkyZOZMmUKO+ywQ426A//mm2+4\n6667GDduHFOnTuWUU05JqVvxiu7NoeouzocMGcLgwYOZNm0ajzzySE67MS/oZFHxLODZs8F907OA\nlTBE8k+zZs04+uijGThw4GYV28uWLWP77benYcOGjB8/ntmzZ1e7nSOOOILnnnsOgE8++YSpU6cC\noXvzbbfdlhYtWrBw4UJeffXVjes0b96c5cuXb7Gtww8/nH/84x+sWrWKlStXMnr0aA4//PCkP9Oy\nZcvo0CHcNfDUU09tnH788cfz4IMPbny/dOlSDj74YCZMmMA333wDpL8b84JOFtl8FrCIZF6/fv0o\nLS3dLFn079+fkpIS9ttvP55++mn22muvardx2WWXsWLFCvbee29uvvnmjVco3bp144ADDmCvvfbi\n3HPP3ax780GDBnHiiSdy9NFHb7atHj16cMEFF9CzZ08OOuggLr74Yg444ICkP8+tt97KL37xCw48\n8MDN6kNuuukmli5dyr777ku3bt0YP3487dq1Y+jQoZxxxhl069aNs88+O+n9JKOguyivVy9cUcQz\n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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7f9e8b6b6080>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.clf()   # clear figure\n",
    "acc_values = history_dict['acc']\n",
    "val_acc_values = history_dict['val_acc']\n",
    "\n",
    "plt.plot(epochs, acc, 'bo', label='Training acc')\n",
    "plt.plot(epochs, val_acc, 'b', label='Validation acc')\n",
    "plt.title('Training and validation accuracy')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('Loss')\n",
    "plt.legend()\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "The dots are the training loss and accuracy, while the solid lines are the validation loss and accuracy. Note that your own results may vary \n",
    "slightly due to a different random initialization of your network.\n",
    "\n",
    "As you can see, the training loss decreases with every epoch and the training accuracy increases with every epoch. That's what you would \n",
    "expect when running gradient descent optimization -- the quantity you are trying to minimize should get lower with every iteration. But that \n",
    "isn't the case for the validation loss and accuracy: they seem to peak at the fourth epoch. This is an example of what we were warning \n",
    "against earlier: a model that performs better on the training data isn't necessarily a model that will do better on data it has never seen \n",
    "before. In precise terms, what you are seeing is \"overfitting\": after the second epoch, we are over-optimizing on the training data, and we \n",
    "ended up learning representations that are specific to the training data and do not generalize to data outside of the training set.\n",
    "\n",
    "In this case, to prevent overfitting, we could simply stop training after three epochs. In general, there is a range of techniques you can \n",
    "leverage to mitigate overfitting, which we will cover in the next chapter.\n",
    "\n",
    "Let's train a new network from scratch for four epochs, then evaluate it on our test data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/4\n",
      "25000/25000 [==============================] - 1s - loss: 0.4738 - acc: 0.8044     \n",
      "Epoch 2/4\n",
      "25000/25000 [==============================] - 1s - loss: 0.2660 - acc: 0.9076     \n",
      "Epoch 3/4\n",
      "25000/25000 [==============================] - 1s - loss: 0.2028 - acc: 0.9277     \n",
      "Epoch 4/4\n",
      "25000/25000 [==============================] - 1s - loss: 0.1700 - acc: 0.9397     \n",
      "24544/25000 [============================>.] - ETA: 0s"
     ]
    }
   ],
   "source": [
    "model = models.Sequential()\n",
    "model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))\n",
    "model.add(layers.Dense(16, activation='relu'))\n",
    "model.add(layers.Dense(1, activation='sigmoid'))\n",
    "\n",
    "model.compile(optimizer='rmsprop',\n",
    "              loss='binary_crossentropy',\n",
    "              metrics=['accuracy'])\n",
    "\n",
    "model.fit(x_train, y_train, epochs=4, batch_size=512)\n",
    "results = model.evaluate(x_test, y_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[0.29184698499679568, 0.88495999999999997]"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our fairly naive approach achieves an accuracy of 88%. With state-of-the-art approaches, one should be able to get close to 95%."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using a trained network to generate predictions on new data\n",
    "\n",
    "After having trained a network, you will want to use it in a practical setting. You can generate the likelihood of reviews being positive \n",
    "by using the `predict` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.91966152],\n",
       "       [ 0.86563045],\n",
       "       [ 0.99936908],\n",
       "       ..., \n",
       "       [ 0.45731062],\n",
       "       [ 0.0038014 ],\n",
       "       [ 0.79525089]], dtype=float32)"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.predict(x_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, the network is very confident for some samples (0.99 or more, or 0.01 or less) but less confident for others (0.6, 0.4). \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Further experiments\n",
    "\n",
    "\n",
    "* We were using 2 hidden layers. Try to use 1 or 3 hidden layers and see how it affects validation and test accuracy.\n",
    "* Try to use layers with more hidden units or less hidden units: 32 units, 64 units...\n",
    "* Try to use the `mse` loss function instead of `binary_crossentropy`.\n",
    "* Try to use the `tanh` activation (an activation that was popular in the early days of neural networks) instead of `relu`.\n",
    "\n",
    "These experiments will help convince you that the architecture choices we have made are all fairly reasonable, although they can still be \n",
    "improved!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusions\n",
    "\n",
    "\n",
    "Here's what you should take away from this example:\n",
    "\n",
    "* There's usually quite a bit of preprocessing you need to do on your raw data in order to be able to feed it -- as tensors -- into a neural \n",
    "network. In the case of sequences of words, they can be encoded as binary vectors -- but there are other encoding options too.\n",
    "* Stacks of `Dense` layers with `relu` activations can solve a wide range of problems (including sentiment classification), and you will \n",
    "likely use them frequently.\n",
    "* In a binary classification problem (two output classes), your network should end with a `Dense` layer with 1 unit and a `sigmoid` activation, \n",
    "i.e. the output of your network should be a scalar between 0 and 1, encoding a probability.\n",
    "* With such a scalar sigmoid output, on a binary classification problem, the loss function you should use is `binary_crossentropy`.\n",
    "* The `rmsprop` optimizer is generally a good enough choice of optimizer, whatever your problem. That's one less thing for you to worry \n",
    "about.\n",
    "* As they get better on their training data, neural networks eventually start _overfitting_ and end up obtaining increasingly worse results on data \n",
    "never-seen-before. Make sure to always monitor performance on data that is outside of the training set.\n"
   ]
  }
 ],
 "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.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
