{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Yield Oracles: Predicting Soybean Yields in Brazil Using Satellite and Weather Data\n",
"\n",
"This code was written as part of a project to satisfy the [Capstone Course](https://datascience.berkeley.edu/academics/curriculum/synthetic-capstone-course/) requirement of the [UC Berkeley Master of Information and Data Science](https://datascience.berkeley.edu) program. Our project was completed in April 2016. For more details and to see our results, please visit our [website](http://amitavadas.github.io/capstone/index.html). We look forward to your feedback!\n",
"\n",
"For questions and comments about this code, please contact the author, Marguerite Oneto, by email at marguerite.oneto@ischool.berkeley.edu."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Run A Gated Recurrent Units (GRU) Model Using Theano\n",
"This code is based on an excellent tutorial by Denny Britz giving an introduction to Gated Recurrent Units. Please see the links below for more information. \n",
"Reference: http://www.wildml.com/2015/10/recurrent-neural-network-tutorial-part-4-implementing-a-grulstm-rnn-with-python-and-theano/ \n",
"Reference: https://github.com/dennybritz/rnn-tutorial-gru-lstm/blob/master/gru_theano.py \n",
"Data for Unit Test: https://github.com/maoneto/W210/blob/master/Code/data/reddit-comments-2015-trunc.csv \n",
"Data for Yield Prediction Test: https://github.com/maoneto/W210/blob/master/Code/data/train_trajectories_11_images_max_mv_trunc10000.csv"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## GRU Unit Test"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define GRUTheano Class for Unit Test"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Overwriting gru_theano_ut.py\n"
]
}
],
"source": [
"%%writefile gru_theano_ut.py\n",
"\n",
"import numpy as np\n",
"import theano as theano\n",
"import theano.tensor as T\n",
"from theano.gradient import grad_clip\n",
"import time\n",
"import operator\n",
"\n",
"class GRUTheanoUT:\n",
" \n",
" def __init__(self, word_dim, hidden_dim=128, bptt_truncate=-1):\n",
" # Assign instance variables\n",
" self.word_dim = word_dim\n",
" self.hidden_dim = hidden_dim\n",
" self.bptt_truncate = bptt_truncate\n",
" # Initialize the network parameters\n",
" E = np.random.uniform(-np.sqrt(1./word_dim), np.sqrt(1./word_dim), (hidden_dim, word_dim))\n",
" U = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (6, hidden_dim, hidden_dim))\n",
" W = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (6, hidden_dim, hidden_dim))\n",
" V = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (word_dim, hidden_dim))\n",
" b = np.zeros((6, hidden_dim))\n",
" c = np.zeros(word_dim)\n",
" # Theano: Created shared variables\n",
" self.E = theano.shared(name='E', value=E.astype(theano.config.floatX))\n",
" self.U = theano.shared(name='U', value=U.astype(theano.config.floatX))\n",
" self.W = theano.shared(name='W', value=W.astype(theano.config.floatX))\n",
" self.V = theano.shared(name='V', value=V.astype(theano.config.floatX))\n",
" self.b = theano.shared(name='b', value=b.astype(theano.config.floatX))\n",
" self.c = theano.shared(name='c', value=c.astype(theano.config.floatX))\n",
" # SGD / rmsprop: Initialize parameters\n",
" self.mE = theano.shared(name='mE', value=np.zeros(E.shape).astype(theano.config.floatX))\n",
" self.mU = theano.shared(name='mU', value=np.zeros(U.shape).astype(theano.config.floatX))\n",
" self.mV = theano.shared(name='mV', value=np.zeros(V.shape).astype(theano.config.floatX))\n",
" self.mW = theano.shared(name='mW', value=np.zeros(W.shape).astype(theano.config.floatX))\n",
" self.mb = theano.shared(name='mb', value=np.zeros(b.shape).astype(theano.config.floatX))\n",
" self.mc = theano.shared(name='mc', value=np.zeros(c.shape).astype(theano.config.floatX))\n",
" # We store the Theano graph here\n",
" self.theano = {}\n",
" self.__theano_build__()\n",
" \n",
" def __theano_build__(self):\n",
" E, V, U, W, b, c = self.E, self.V, self.U, self.W, self.b, self.c\n",
" \n",
" x = T.ivector('x')\n",
" y = T.ivector('y')\n",
" \n",
" def forward_prop_step(x_t, s_t1_prev, s_t2_prev):\n",
" # This is how we calculated the hidden state in a simple RNN. No longer!\n",
" # s_t = T.tanh(U[:,x_t] + W.dot(s_t1_prev))\n",
" \n",
" # Word embedding layer\n",
" x_e = E[:,x_t]\n",
" \n",
" # GRU Layer 1\n",
" z_t1 = T.nnet.hard_sigmoid(U[0].dot(x_e) + W[0].dot(s_t1_prev) + b[0])\n",
" r_t1 = T.nnet.hard_sigmoid(U[1].dot(x_e) + W[1].dot(s_t1_prev) + b[1])\n",
" c_t1 = T.tanh(U[2].dot(x_e) + W[2].dot(s_t1_prev * r_t1) + b[2])\n",
" s_t1 = (T.ones_like(z_t1) - z_t1) * c_t1 + z_t1 * s_t1_prev\n",
" \n",
" # GRU Layer 2\n",
" z_t2 = T.nnet.hard_sigmoid(U[3].dot(s_t1) + W[3].dot(s_t2_prev) + b[3])\n",
" r_t2 = T.nnet.hard_sigmoid(U[4].dot(s_t1) + W[4].dot(s_t2_prev) + b[4])\n",
" c_t2 = T.tanh(U[5].dot(s_t1) + W[5].dot(s_t2_prev * r_t2) + b[5])\n",
" s_t2 = (T.ones_like(z_t2) - z_t2) * c_t2 + z_t2 * s_t2_prev\n",
" \n",
" # Final output calculation\n",
" # Theano's softmax returns a matrix with one row, we only need the row\n",
" o_t = T.nnet.softmax(V.dot(s_t2) + c)[0]\n",
"\n",
" return [o_t, s_t1, s_t2]\n",
" \n",
" [o, s, s2], updates = theano.scan(\n",
" forward_prop_step,\n",
" sequences=x,\n",
" truncate_gradient=self.bptt_truncate,\n",
" outputs_info=[None, \n",
" dict(initial=T.zeros(self.hidden_dim)),\n",
" dict(initial=T.zeros(self.hidden_dim))])\n",
" \n",
" prediction = T.argmax(o, axis=1)\n",
" o_error = T.sum(T.nnet.categorical_crossentropy(o, y))\n",
" \n",
" # Total cost (could add regularization here)\n",
" cost = o_error\n",
" \n",
" # Gradients\n",
" dE = T.grad(cost, E)\n",
" dU = T.grad(cost, U)\n",
" dW = T.grad(cost, W)\n",
" db = T.grad(cost, b)\n",
" dV = T.grad(cost, V)\n",
" dc = T.grad(cost, c)\n",
" \n",
" # Assign functions\n",
" self.predict = theano.function([x], o)\n",
" self.predict_class = theano.function([x], prediction)\n",
" self.ce_error = theano.function([x, y], cost)\n",
" self.bptt = theano.function([x, y], [dE, dU, dW, db, dV, dc])\n",
" \n",
" # SGD parameters\n",
" learning_rate = T.scalar('learning_rate')\n",
" decay = T.scalar('decay')\n",
" \n",
" # rmsprop cache updates\n",
" mE = decay * self.mE + (1 - decay) * dE ** 2\n",
" mU = decay * self.mU + (1 - decay) * dU ** 2\n",
" mW = decay * self.mW + (1 - decay) * dW ** 2\n",
" mV = decay * self.mV + (1 - decay) * dV ** 2\n",
" mb = decay * self.mb + (1 - decay) * db ** 2\n",
" mc = decay * self.mc + (1 - decay) * dc ** 2\n",
" \n",
" self.sgd_step = theano.function(\n",
" [x, y, learning_rate, theano.Param(decay, default=0.9)],\n",
" [], \n",
" updates=[(E, E - learning_rate * dE / T.sqrt(mE + 1e-6)),\n",
" (U, U - learning_rate * dU / T.sqrt(mU + 1e-6)),\n",
" (W, W - learning_rate * dW / T.sqrt(mW + 1e-6)),\n",
" (V, V - learning_rate * dV / T.sqrt(mV + 1e-6)),\n",
" (b, b - learning_rate * db / T.sqrt(mb + 1e-6)),\n",
" (c, c - learning_rate * dc / T.sqrt(mc + 1e-6)),\n",
" (self.mE, mE),\n",
" (self.mU, mU),\n",
" (self.mW, mW),\n",
" (self.mV, mV),\n",
" (self.mb, mb),\n",
" (self.mc, mc)\n",
" ])\n",
" \n",
" \n",
" def calculate_total_loss(self, X, Y):\n",
" return np.sum([self.ce_error(x,y) for x,y in zip(X,Y)])\n",
" \n",
" def calculate_loss(self, X, Y):\n",
" # Divide calculate_loss by the number of words\n",
" num_words = np.sum([len(y) for y in Y])\n",
" return self.calculate_total_loss(X,Y)/float(num_words) \n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define Theano Utility Functions from utils.py for Unit Test"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Overwriting utils_ut.py\n"
]
}
],
"source": [
"%%writefile utils_ut.py\n",
"\n",
"import csv\n",
"import itertools\n",
"import numpy as np\n",
"import nltk\n",
"import time\n",
"import sys\n",
"import operator\n",
"import io\n",
"import array\n",
"from datetime import datetime\n",
"from gru_theano_ut import GRUTheanoUT\n",
"\n",
"SENTENCE_START_TOKEN = \"SENTENCE_START\"\n",
"SENTENCE_END_TOKEN = \"SENTENCE_END\"\n",
"UNKNOWN_TOKEN = \"UNKNOWN_TOKEN\"\n",
"\n",
"def load_data(filename, vocabulary_size=2000, min_sent_characters=0):\n",
"\n",
" word_to_index = []\n",
" index_to_word = []\n",
"\n",
" # Read the data and append SENTENCE_START and SENTENCE_END tokens\n",
" print(\"Reading CSV file...\")\n",
" with open(filename, 'rt') as f:\n",
" reader = csv.reader(f, skipinitialspace=True)\n",
" reader.next()\n",
" # Split full comments into sentences\n",
" sentences = itertools.chain(*[nltk.sent_tokenize(x[0].decode(\"utf-8\").lower()) for x in reader])\n",
" # Filter sentences\n",
" sentences = [s for s in sentences if len(s) >= min_sent_characters]\n",
" sentences = [s for s in sentences if \"http\" not in s]\n",
" # Append SENTENCE_START and SENTENCE_END\n",
" sentences = [\"%s %s %s\" % (SENTENCE_START_TOKEN, x, SENTENCE_END_TOKEN) for x in sentences]\n",
" print(\"Parsed %d sentences.\" % (len(sentences)))\n",
"\n",
" # Tokenize the sentences into words\n",
" tokenized_sentences = [nltk.word_tokenize(sent) for sent in sentences]\n",
"\n",
" # Count the word frequencies\n",
" word_freq = nltk.FreqDist(itertools.chain(*tokenized_sentences))\n",
" print(\"Found %d unique words tokens.\" % len(word_freq.items()))\n",
"\n",
" # Get the most common words and build index_to_word and word_to_index vectors\n",
" vocab = sorted(word_freq.items(), key=lambda x: (x[1], x[0]), reverse=True)[:vocabulary_size-2]\n",
" print(\"Using vocabulary size %d.\" % vocabulary_size)\n",
" print(\"The least frequent word in our vocabulary is '%s' and appeared %d times.\" % (vocab[-1][0], vocab[-1][1]))\n",
"\n",
" sorted_vocab = sorted(vocab, key=operator.itemgetter(1))\n",
" index_to_word = [\"\", UNKNOWN_TOKEN] + [x[0] for x in sorted_vocab]\n",
" word_to_index = dict([(w, i) for i, w in enumerate(index_to_word)])\n",
"\n",
" # Replace all words not in our vocabulary with the unknown token\n",
" for i, sent in enumerate(tokenized_sentences):\n",
" tokenized_sentences[i] = [w if w in word_to_index else UNKNOWN_TOKEN for w in sent]\n",
"\n",
" # Create the training data\n",
" X_train = np.asarray([[word_to_index[w] for w in sent[:-1]] for sent in tokenized_sentences])\n",
" y_train = np.asarray([[word_to_index[w] for w in sent[1:]] for sent in tokenized_sentences])\n",
"\n",
" return X_train, y_train, word_to_index, index_to_word\n",
"\n",
"\n",
"def train_with_sgd(model, X_train, y_train, learning_rate=0.001, nepoch=20, decay=0.9,\n",
" callback_every=10000, callback=None):\n",
" num_examples_seen = 0\n",
" for epoch in range(nepoch):\n",
" # For each training example...\n",
" for i in np.random.permutation(len(y_train)):\n",
" # One SGD step\n",
" model.sgd_step(X_train[i], y_train[i], learning_rate, decay)\n",
" num_examples_seen += 1\n",
" # Optionally do callback\n",
" if (callback and callback_every and num_examples_seen % callback_every == 0):\n",
" callback(model, num_examples_seen) \n",
" return model\n",
"\n",
"def save_model_parameters_theano(model, outfile):\n",
" np.savez(outfile,\n",
" E=model.E.get_value(),\n",
" U=model.U.get_value(),\n",
" W=model.W.get_value(),\n",
" V=model.V.get_value(),\n",
" b=model.b.get_value(),\n",
" c=model.c.get_value())\n",
" print \"Saved model parameters to %s.\" % outfile\n",
"\n",
"def load_model_parameters_theano(path, modelClass=GRUTheanoUT):\n",
" npzfile = np.load(path)\n",
" E, U, W, V, b, c = npzfile[\"E\"], npzfile[\"U\"], npzfile[\"W\"], npzfile[\"V\"], npzfile[\"b\"], npzfile[\"c\"]\n",
" hidden_dim, word_dim = E.shape[0], E.shape[1]\n",
" print \"Building model model from %s with hidden_dim=%d word_dim=%d\" % (path, hidden_dim, word_dim)\n",
" sys.stdout.flush()\n",
" model = modelClass(word_dim, hidden_dim=hidden_dim)\n",
" model.E.set_value(E)\n",
" model.U.set_value(U)\n",
" model.W.set_value(W)\n",
" model.V.set_value(V)\n",
" model.b.set_value(b)\n",
" model.c.set_value(c)\n",
" return model \n",
"\n",
"def gradient_check_theano(model, x, y, h=0.001, error_threshold=0.01):\n",
" # Overwrite the bptt attribute. We need to backpropagate all the way to get the correct gradient\n",
" model.bptt_truncate = 1000\n",
" # Calculate the gradients using backprop\n",
" bptt_gradients = model.bptt(x, y)\n",
" # List of all parameters we want to chec.\n",
" model_parameters = ['E', 'U', 'W', 'b', 'V', 'c']\n",
" # Gradient check for each parameter\n",
" for pidx, pname in enumerate(model_parameters):\n",
" # Get the actual parameter value from the mode, e.g. model.W\n",
" parameter_T = operator.attrgetter(pname)(model)\n",
" parameter = parameter_T.get_value()\n",
" print \"Performing gradient check for parameter %s with size %d.\" % (pname, np.prod(parameter.shape))\n",
" # Iterate over each element of the parameter matrix, e.g. (0,0), (0,1), ...\n",
" it = np.nditer(parameter, flags=['multi_index'], op_flags=['readwrite'])\n",
" while not it.finished:\n",
" ix = it.multi_index\n",
" # Save the original value so we can reset it later\n",
" original_value = parameter[ix]\n",
" # Estimate the gradient using (f(x+h) - f(x-h))/(2*h)\n",
" parameter[ix] = original_value + h\n",
" parameter_T.set_value(parameter)\n",
" gradplus = model.calculate_total_loss([x],[y])\n",
" parameter[ix] = original_value - h\n",
" parameter_T.set_value(parameter)\n",
" gradminus = model.calculate_total_loss([x],[y])\n",
" estimated_gradient = (gradplus - gradminus)/(2*h)\n",
" parameter[ix] = original_value\n",
" parameter_T.set_value(parameter)\n",
" # The gradient for this parameter calculated using backpropagation\n",
" backprop_gradient = bptt_gradients[pidx][ix]\n",
" # calculate The relative error: (|x - y|/(|x| + |y|))\n",
" relative_error = np.abs(backprop_gradient - estimated_gradient)/(np.abs(backprop_gradient) + np.abs(estimated_gradient))\n",
" # If the error is to large fail the gradient check\n",
" if relative_error > error_threshold:\n",
" print \"Gradient Check ERROR: parameter=%s ix=%s\" % (pname, ix)\n",
" print \"+h Loss: %f\" % gradplus\n",
" print \"-h Loss: %f\" % gradminus\n",
" print \"Estimated_gradient: %f\" % estimated_gradient\n",
" print \"Backpropagation gradient: %f\" % backprop_gradient\n",
" print \"Relative Error: %f\" % relative_error\n",
" return \n",
" it.iternext()\n",
" print \"Gradient check for parameter %s passed.\" % (pname)\n",
"\n",
"\n",
"def print_sentence(s, index_to_word):\n",
" sentence_str = [index_to_word[x] for x in s[1:-1]]\n",
" print(\" \".join(sentence_str))\n",
" sys.stdout.flush()\n",
"\n",
"def generate_sentence(model, index_to_word, word_to_index, min_length=5):\n",
" # We start the sentence with the start token\n",
" new_sentence = [word_to_index[SENTENCE_START_TOKEN]]\n",
" # Repeat until we get an end token\n",
" while not new_sentence[-1] == word_to_index[SENTENCE_END_TOKEN]:\n",
" next_word_probs = model.predict(new_sentence)[-1]\n",
" samples = np.random.multinomial(1, next_word_probs)\n",
" sampled_word = np.argmax(samples)\n",
" new_sentence.append(sampled_word)\n",
" # Seomtimes we get stuck if the sentence becomes too long, e.g. \"........\" :(\n",
" # And: We don't want sentences with UNKNOWN_TOKEN's\n",
" if len(new_sentence) > 100 or sampled_word == word_to_index[UNKNOWN_TOKEN]:\n",
" return None\n",
" if len(new_sentence) < min_length:\n",
" return None\n",
" return new_sentence\n",
"\n",
"def generate_sentences(model, n, index_to_word, word_to_index):\n",
" for i in range(n):\n",
" sent = None\n",
" while not sent:\n",
" sent = generate_sentence(model, index_to_word, word_to_index)\n",
" print_sentence(sent, index_to_word)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Train GRU for Unit Test"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false,
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Reading CSV file...\n",
"Parsed 94 sentences.\n",
"Found 632 unique words tokens.\n",
"Using vocabulary size 200.\n",
"The least frequent word in our vocabulary is 'we' and appeared 1 times.\n",
"SGD Step time: 58.140039 milliseconds\n",
"\n",
"2016-04-25T18:34:47.266062 (25)\n",
"--------------------------------------------------\n",
"Loss: 4.135619\n",
"myself SENTENCE_START not while\n",
"much in fact now that there similar % second\n",
"in too all 're anyone however connections think someone tone overlap to\n",
"when SENTENCE_START cost que more connections in the with with\n",
"que é from who .\n",
"s : from his right wrong n't anyone can did n't same\n",
"no lot be its so in\n",
". it back % character . work .\n",
"then of each much welcome down see ? by , leia season\n",
"n't like last $ . 's a $ the\n",
"Saved model parameters to GRU-2016-04-25-18-33-200-48-128.dat.\n",
"\n",
"\n",
"\n",
"2016-04-25T18:34:48.804555 (50)\n",
"--------------------------------------------------\n",
"Loss: 3.976999\n",
"for have n't é )\n",
"from yeah leia a how where with get\n",
"from : lidstrom multiply they\n",
"right with award\n",
". so though v.\n",
"still is yt good on\n",
"writing but up on stream\n",
"was season tone 2 guys on be fact . result but\n",
"while riven that the similar with have or\n",
"leia guys other , with end someone\n",
"Saved model parameters to GRU-2016-04-25-18-33-200-48-128.dat.\n",
"\n",
"\n",
"\n",
"2016-04-25T18:34:50.175742 (75)\n",
"--------------------------------------------------\n",
"Loss: 3.921327\n",
"more what just\n",
"the similar happens they\n",
"result to get\n",
"`` have but define but\n",
"seeing connections yellow\n",
"paul s in the\n",
"would for : .\n",
"other happens think it the he if\n",
"SENTENCE_START good leia\n",
"hit have said ?\n",
"Saved model parameters to GRU-2016-04-25-18-33-200-48-128.dat.\n",
"\n",
"\n"
]
}
],
"source": [
"#! /usr/bin/env python\n",
"\n",
"import sys\n",
"import os\n",
"import time\n",
"import numpy as np\n",
"from utils_ut import *\n",
"from datetime import datetime\n",
"from gru_theano_ut import GRUTheanoUT\n",
"\n",
"LEARNING_RATE = float(os.environ.get(\"LEARNING_RATE\", \"0.001\"))\n",
"VOCABULARY_SIZE = int(os.environ.get(\"VOCABULARY_SIZE\", \"200\"))\n",
"EMBEDDING_DIM = int(os.environ.get(\"EMBEDDING_DIM\", \"48\"))\n",
"HIDDEN_DIM = int(os.environ.get(\"HIDDEN_DIM\", \"128\"))\n",
"NEPOCH = int(os.environ.get(\"NEPOCH\", \"1\"))\n",
"MODEL_OUTPUT_FILE = os.environ.get(\"MODEL_OUTPUT_FILE\")\n",
"INPUT_DATA_FILE = os.environ.get(\"INPUT_DATA_FILE\", \"./data/reddit-comments-2015-trunc.csv\")\n",
"PRINT_EVERY = int(os.environ.get(\"PRINT_EVERY\", \"25\"))\n",
"\n",
"if not MODEL_OUTPUT_FILE:\n",
" ts = datetime.now().strftime(\"%Y-%m-%d-%H-%M\")\n",
" MODEL_OUTPUT_FILE = \"GRU-%s-%s-%s-%s.dat\" % (ts, VOCABULARY_SIZE, EMBEDDING_DIM, HIDDEN_DIM)\n",
"\n",
"# Load data\n",
"x_train, y_train, word_to_index, index_to_word = load_data(INPUT_DATA_FILE, VOCABULARY_SIZE)\n",
"\n",
"# Build model\n",
"model = GRUTheanoUT(VOCABULARY_SIZE, hidden_dim=HIDDEN_DIM, bptt_truncate=-1)\n",
"\n",
"# Print SGD step time\n",
"t1 = time.time()\n",
"model.sgd_step(x_train[10], y_train[10], LEARNING_RATE)\n",
"t2 = time.time()\n",
"print \"SGD Step time: %f milliseconds\" % ((t2 - t1) * 1000.)\n",
"sys.stdout.flush()\n",
"\n",
"# We do this every few examples to understand what's going on\n",
"def sgd_callback(model, num_examples_seen):\n",
" dt = datetime.now().isoformat()\n",
" loss = model.calculate_loss(x_train[:10000], y_train[:10000])\n",
" print(\"\\n%s (%d)\" % (dt, num_examples_seen))\n",
" print(\"--------------------------------------------------\")\n",
" print(\"Loss: %f\" % loss)\n",
" generate_sentences(model, 10, index_to_word, word_to_index)\n",
" save_model_parameters_theano(model, MODEL_OUTPUT_FILE)\n",
" print(\"\\n\")\n",
" sys.stdout.flush()\n",
"\n",
"for epoch in range(NEPOCH):\n",
" train_with_sgd(model, x_train, y_train, learning_rate=LEARNING_RATE, nepoch=1, decay=0.9, \n",
" callback_every=PRINT_EVERY, callback=sgd_callback)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## GRU for Yield Prediction"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Create Training, Validation, and Test Datasets for Yield Prediction"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"X_train and X_test Shape:\n",
"(8000, 7) (2000, 7)\n",
"Y_train, Y_test, log_Y_train, log_Y_test, log_Y_bar Shape:\n",
"(8000, 7) (2000, 7) (8000, 7) (2000, 7)\n"
]
}
],
"source": [
"import math\n",
"import csv\n",
"import numpy as np\n",
"import pandas as pd\n",
"from sklearn.cross_validation import train_test_split\n",
"\n",
"X = []\n",
"Y = []\n",
"\n",
"with open('train_trajectories_11_images_max_mv_trunc10000.csv', 'r') as csvfile:\n",
" datareader = csv.reader(csvfile, delimiter=',')\n",
" for row in datareader:\n",
" label = row.pop() # pop the last element in the list which is the label (yield)\n",
" if float(label) != 0.0:\n",
" X.append(row)\n",
" Y.append(len(X[0])*[label]) # output at each t, o_t, is the yield\n",
"X = np.array(X).astype(np.float)\n",
"Y = np.array(Y).astype(np.float)\n",
"\n",
"# Break labeled examples into train and test sets\n",
"X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.20, random_state=22)\n",
"\n",
"log_Y_train = np.log(Y_train)\n",
"log_Y_test = np.log(Y_test)\n",
"\n",
"print \"X_train and X_test Shape:\"\n",
"print X_train.shape, X_test.shape\n",
"\n",
"print \"Y_train, Y_test, log_Y_train, log_Y_test, log_Y_bar Shape:\"\n",
"print Y_train.shape, Y_test.shape, log_Y_train.shape, log_Y_test.shape\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define GRUTheano Class for Yield Prediction"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Overwriting gru_theano_yp.py\n"
]
}
],
"source": [
"%%writefile gru_theano_yp.py\n",
"#!/usr/bin/env python\n",
"\n",
"import numpy as np\n",
"import theano as theano\n",
"import theano.tensor as T\n",
"from theano.gradient import grad_clip\n",
"import time\n",
"import operator\n",
"\n",
"theano.exception_verbosity='high'\n",
"theano.mode='FAST_COMPILE'\n",
"theano.allow_gc=False\n",
"theano.optimizer='fast_compile'\n",
"theano.config.compute_test_value = 'off'\n",
"\n",
"class GRUTheanoYP:\n",
" \n",
" def __init__(self, x_dim, hidden_dim=128, bptt_truncate=-1):\n",
" # Assign instance variables\n",
" self.x_dim = x_dim\n",
" self.hidden_dim = hidden_dim\n",
" self.bptt_truncate = bptt_truncate\n",
" # Initialize the network parameters\n",
" E = np.random.uniform(-np.sqrt(1./x_dim), np.sqrt(1./x_dim), (hidden_dim, x_dim))\n",
" U = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (6, hidden_dim, hidden_dim))\n",
" W = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (6, hidden_dim, hidden_dim))\n",
" V = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (x_dim, hidden_dim))\n",
" b = np.zeros((6, hidden_dim, 1))\n",
" c = np.zeros((x_dim, 1))\n",
" # Theano: Created shared variables\n",
" self.E = theano.shared(name='E', value=E.astype(theano.config.floatX))\n",
" self.U = theano.shared(name='U', value=U.astype(theano.config.floatX))\n",
" self.W = theano.shared(name='W', value=W.astype(theano.config.floatX))\n",
" self.V = theano.shared(name='V', value=V.astype(theano.config.floatX))\n",
" self.b = theano.shared(name='b', value=b.astype(theano.config.floatX))\n",
" self.c = theano.shared(name='c', value=c.astype(theano.config.floatX))\n",
" # SGD / rmsprop: Initialize parameters\n",
" self.mE = theano.shared(name='mE', value=np.zeros(E.shape).astype(theano.config.floatX))\n",
" self.mU = theano.shared(name='mU', value=np.zeros(U.shape).astype(theano.config.floatX))\n",
" self.mV = theano.shared(name='mV', value=np.zeros(V.shape).astype(theano.config.floatX))\n",
" self.mW = theano.shared(name='mW', value=np.zeros(W.shape).astype(theano.config.floatX))\n",
" self.mb = theano.shared(name='mb', value=np.zeros(b.shape).astype(theano.config.floatX))\n",
" self.mc = theano.shared(name='mc', value=np.zeros(c.shape).astype(theano.config.floatX))\n",
" # We store the Theano graph here\n",
" self.theano = {}\n",
" self.__theano_build__()\n",
" \n",
" def __theano_build__(self):\n",
" E, V, U, W, b, c = self.E, self.V, self.U, self.W, self.b, self.c\n",
" \n",
" x = T.vector('x')\n",
" y = T.vector('y')\n",
" \n",
" def forward_prop_step(x_t, s_t1_prev, s_t2_prev):\n",
" # This is how we calculated the hidden state in a simple RNN. No longer!\n",
" # s_t = T.tanh(U[:,x_t] + W.dot(s_t1_prev))\n",
" \n",
" # Embedding layer: x_t is the NDVI value of the pixel\n",
" x_e = T.mul(E, x_t)\n",
" \n",
" # GRU Layer 1\n",
" z_t1 = T.nnet.hard_sigmoid(T.dot(U[0], x_e) + T.dot(W[0], s_t1_prev) + b[0])\n",
" r_t1 = T.nnet.hard_sigmoid(T.dot(U[1], x_e) + T.dot(W[1], s_t1_prev) + b[1])\n",
" c_t1 = T.tanh(T.dot(U[2], x_e) + W[2].dot(s_t1_prev * r_t1) + b[2])\n",
" s_t1 = (T.ones_like(z_t1) - z_t1) * c_t1 + z_t1 * s_t1_prev\n",
" # For debugging ...\n",
" # print T.shape(s_t1).eval({x_t: 0.8888, s_t1_prev: np.asarray([[0], [0], [0], [0], [0]])}) \n",
" # GRU Layer 2\n",
" z_t2 = T.nnet.hard_sigmoid(U[3].dot(s_t1) + W[3].dot(s_t2_prev) + b[3])\n",
" r_t2 = T.nnet.hard_sigmoid(U[4].dot(s_t1) + W[4].dot(s_t2_prev) + b[4])\n",
" c_t2 = T.tanh(U[5].dot(s_t1) + W[5].dot(s_t2_prev * r_t2) + b[5])\n",
" s_t2 = (T.ones_like(z_t2) - z_t2) * c_t2 + z_t2 * s_t2_prev\n",
" \n",
" # Final output calculation\n",
" # Theano's softmax returns a matrix with one row, we only need the row\n",
" # o_t = T.nnet.softmax(V.dot(s_t2) + c)[0]\n",
" # We do not use softmax because we are predicting a continuous variable, not doing classification:\n",
" o_t = (V.dot(s_t2) + c)[0]\n",
"\n",
" return [o_t, s_t1, s_t2]\n",
" \n",
" [o, s, s2], updates = theano.scan(\n",
" forward_prop_step,\n",
" sequences=[x],\n",
" truncate_gradient=self.bptt_truncate,\n",
" outputs_info=[None, \n",
" dict(initial=T.zeros_like(E)),\n",
" dict(initial=T.zeros_like(E))])\n",
" \n",
" # prediction = T.argmax(o, axis=1)\n",
" # o_error = T.sum(T.nnet.categorical_crossentropy(o, y))\n",
" # Again, we are predicting a continuous variable, not doing classification.\n",
" # We use the sum of squared errors (SSE) as our cost function:\n",
" prediction = o\n",
" o_error = T.sum(T.sqr(o - T.reshape(y, [T.shape(y)[0],1,1], ndim=3)))\n",
" \n",
" # Total cost (could add regularization here)\n",
" cost = o_error\n",
" \n",
" # Gradients\n",
" dE = T.grad(cost, E)\n",
" dU = T.grad(cost, U)\n",
" dW = T.grad(cost, W)\n",
" db = T.grad(cost, b)\n",
" dV = T.grad(cost, V)\n",
" dc = T.grad(cost, c)\n",
" \n",
" # Assign functions\n",
" self.predict = theano.function([x], o)\n",
" # We do not need this because we are not doing classification\n",
" # self.predict_class = theano.function([x], prediction)\n",
" self.sse_error = theano.function([x, y], cost)\n",
" self.bptt = theano.function([x, y], [dU, dW, db, dV, dc])\n",
" \n",
" # SGD parameters\n",
" learning_rate = T.scalar('learning_rate')\n",
" decay = T.scalar('decay')\n",
" \n",
" # rmsprop cache updates\n",
" mE = decay * self.mE + (1 - decay) * dE ** 2\n",
" mU = decay * self.mU + (1 - decay) * dU ** 2\n",
" mW = decay * self.mW + (1 - decay) * dW ** 2\n",
" mV = decay * self.mV + (1 - decay) * dV ** 2\n",
" mb = decay * self.mb + (1 - decay) * db ** 2\n",
" mc = decay * self.mc + (1 - decay) * dc ** 2\n",
" \n",
" self.sgd_step = theano.function(\n",
" [x, y, learning_rate, theano.Param(decay, default=0.9)],\n",
" [], \n",
" updates=[(U, U - learning_rate * dU / T.sqrt(mU + 1e-6)),\n",
" (W, W - learning_rate * dW / T.sqrt(mW + 1e-6)),\n",
" (V, V - learning_rate * dV / T.sqrt(mV + 1e-6)),\n",
" (b, b - learning_rate * db / T.sqrt(mb + 1e-6)),\n",
" (c, c - learning_rate * dc / T.sqrt(mc + 1e-6)),\n",
" (self.mU, mU),\n",
" (self.mW, mW),\n",
" (self.mV, mV),\n",
" (self.mb, mb),\n",
" (self.mc, mc)\n",
" ])\n",
" \n",
" \n",
" def calculate_total_loss(self, X, Y):\n",
" return np.sum([self.sse_error(x,y) for x,y in zip(X,Y)])\n",
" \n",
" def calculate_loss(self, X, Y):\n",
" # Divide calculate_loss by the number of examples\n",
" num_examples = np.sum([len(y) for y in Y])\n",
" return self.calculate_total_loss(X,Y)/float(num_examples)\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define Theano Utility Functions from utils.py for Yield Prediction"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Overwriting utils_yp.py\n"
]
}
],
"source": [
"%%writefile utils_yp.py\n",
"\n",
"import csv\n",
"import itertools\n",
"import numpy as np\n",
"import nltk\n",
"import time\n",
"import sys\n",
"import operator\n",
"import io\n",
"import array\n",
"from datetime import datetime\n",
"from gru_theano_yp import GRUTheanoYP\n",
"\n",
"def shuffle_data(p, X, y):\n",
" # shuffle it\n",
" shuffle = np.random.permutation(np.arange(X.shape[0]))\n",
" X, y = X[shuffle], y[shuffle]\n",
" # divide \n",
" n_train = np.round(X.shape[0]*p)\n",
" return X[:n_train], y[:n_train], X[n_train:], y[n_train:]\n",
"\n",
"# Used to shrink the size of the training and test datasets for debugging\n",
"def shuffle_data2(X, y, n_train, n_test):\n",
" # shuffle it\n",
" shuffle = np.random.permutation(np.arange(X.shape[0]))\n",
" X, y = X[shuffle], y[shuffle]\n",
" # divide \n",
" return X[:n_train], y[:n_train], X[n_train:n_train + n_test], y[n_train:n_train + n_test]\n",
"\n",
"def save_model_parameters_theano(model, outfile):\n",
" np.savez(outfile,\n",
" E=model.E.get_value(),\n",
" U=model.U.get_value(),\n",
" W=model.W.get_value(),\n",
" V=model.V.get_value(),\n",
" b=model.b.get_value(),\n",
" c=model.c.get_value())\n",
"# print \"Saved model parameters to %s.\" % outfile\n",
"\n",
"def load_model_parameters_theano(path, modelClass=GRUTheanoYP):\n",
" npzfile = np.load(path)\n",
" E, U, W, V, b, c = npzfile[\"E\"], npzfile[\"U\"], npzfile[\"W\"], npzfile[\"V\"], npzfile[\"b\"], npzfile[\"c\"]\n",
" hidden_dim, x_dim = E.shape[0], E.shape[1]\n",
" print \"Building model from %s with hidden_dim=%d x_dim=%d\" % (path, hidden_dim, x_dim)\n",
" sys.stdout.flush()\n",
" model = modelClass(x_dim, hidden_dim=hidden_dim)\n",
" model.E.set_value(E)\n",
" model.U.set_value(U)\n",
" model.W.set_value(W)\n",
" model.V.set_value(V)\n",
" model.b.set_value(b)\n",
" model.c.set_value(c)\n",
" return model \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Train GRU for Yield Prediction"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false,
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Start Time: 2016-04-25 18:40:06\n",
"SGD Step time: 25.578022 milliseconds\n",
"Epochs: 100, TrainingSamples: 8000, ModelTime: 20160425184114\n",
"1) ExamplesSeen=7200, TrainLoss=0.9803, ValLoss=0.9588, ValR2=-0.6773, ValRMSE=0.3701, TrainTime=2.59 min, EndTime=06:43:57 (saved)\n",
"2) ExamplesSeen=14400, TrainLoss=0.6720, ValLoss=0.6408, ValR2=-0.0658, ValRMSE=0.3026, TrainTime=2.99 min, EndTime=06:47:04 (saved)\n",
"3) ExamplesSeen=21600, TrainLoss=0.6137, ValLoss=0.5375, ValR2=-0.0164, ValRMSE=0.2771, TrainTime=3.05 min, EndTime=06:50:14 (saved)\n",
"4) ExamplesSeen=28800, TrainLoss=0.6190, ValLoss=0.6038, ValR2=-0.0367, ValRMSE=0.2937, TrainTime=2.97 min, EndTime=06:53:19 \n",
"5) ExamplesSeen=36000, TrainLoss=0.6061, ValLoss=0.6145, ValR2=-0.0263, ValRMSE=0.2963, TrainTime=3.05 min, EndTime=06:56:28 \n",
"6) ExamplesSeen=43200, TrainLoss=0.6232, ValLoss=0.6342, ValR2=-0.0574, ValRMSE=0.3010, TrainTime=2.99 min, EndTime=06:59:33 \n",
"7) ExamplesSeen=50400, TrainLoss=0.6122, ValLoss=0.5663, ValR2=-0.0163, ValRMSE=0.2844, TrainTime=3.16 min, EndTime=07:02:49 \n",
"8) ExamplesSeen=57600, TrainLoss=1.3032, ValLoss=1.4270, ValR2=-1.0802, ValRMSE=0.4515, TrainTime=3.20 min, EndTime=07:06:07 \n",
"9) ExamplesSeen=64800, TrainLoss=0.7038, ValLoss=0.7538, ValR2=-0.2333, ValRMSE=0.3282, TrainTime=3.17 min, EndTime=07:09:23 \n",
"10) ExamplesSeen=72000, TrainLoss=2.8250, ValLoss=2.8096, ValR2=-3.7463, ValRMSE=0.6335, TrainTime=3.17 min, EndTime=07:12:39 \n",
"11) ExamplesSeen=79200, TrainLoss=0.6055, ValLoss=0.6124, ValR2=-0.0283, ValRMSE=0.2958, TrainTime=3.16 min, EndTime=07:15:55 \n",
"12) ExamplesSeen=86400, TrainLoss=0.6043, ValLoss=0.6098, ValR2=-0.0069, ValRMSE=0.2951, TrainTime=3.16 min, EndTime=07:19:11 \n",
"13) ExamplesSeen=93600, TrainLoss=0.6268, ValLoss=0.6937, ValR2=-0.0511, ValRMSE=0.3148, TrainTime=3.17 min, EndTime=07:22:27 \n",
"14) ExamplesSeen=100800, TrainLoss=0.6992, ValLoss=0.6789, ValR2=-0.2196, ValRMSE=0.3114, TrainTime=3.16 min, EndTime=07:25:43 \n",
"15) ExamplesSeen=108000, TrainLoss=2.2451, ValLoss=2.2353, ValR2=-2.9077, ValRMSE=0.5651, TrainTime=3.16 min, EndTime=07:28:59 \n",
"16) ExamplesSeen=115200, TrainLoss=1.1983, ValLoss=1.2674, ValR2=-1.0014, ValRMSE=0.4255, TrainTime=3.29 min, EndTime=07:32:23 \n",
"17) ExamplesSeen=122400, TrainLoss=0.6704, ValLoss=0.6520, ValR2=-0.1228, ValRMSE=0.3052, TrainTime=3.40 min, EndTime=07:35:54 \n",
"18) ExamplesSeen=129600, TrainLoss=1.0103, ValLoss=0.9660, ValR2=-0.6862, ValRMSE=0.3715, TrainTime=3.44 min, EndTime=07:39:27 \n",
"19) ExamplesSeen=136800, TrainLoss=1.3847, ValLoss=1.4458, ValR2=-1.4236, ValRMSE=0.4545, TrainTime=3.43 min, EndTime=07:43:00 \n",
"20) ExamplesSeen=144000, TrainLoss=0.7284, ValLoss=0.6904, ValR2=-0.1877, ValRMSE=0.3140, TrainTime=3.47 min, EndTime=07:46:34 \n",
"21) ExamplesSeen=151200, TrainLoss=0.6332, ValLoss=0.6367, ValR2=-0.0754, ValRMSE=0.3016, TrainTime=3.47 min, EndTime=07:50:09 \n",
"22) ExamplesSeen=158400, TrainLoss=0.6326, ValLoss=0.6213, ValR2=-0.0619, ValRMSE=0.2979, TrainTime=3.44 min, EndTime=07:53:43 \n",
"23) ExamplesSeen=165600, TrainLoss=0.6200, ValLoss=0.6160, ValR2=-0.0414, ValRMSE=0.2966, TrainTime=3.38 min, EndTime=07:57:12 \n",
"24) ExamplesSeen=172800, TrainLoss=0.7124, ValLoss=0.7279, ValR2=-0.2801, ValRMSE=0.3225, TrainTime=3.35 min, EndTime=08:00:40 \n",
"25) ExamplesSeen=180000, TrainLoss=0.6362, ValLoss=0.6619, ValR2=-0.0590, ValRMSE=0.3075, TrainTime=3.32 min, EndTime=08:04:05 \n",
"26) ExamplesSeen=187200, TrainLoss=0.7171, ValLoss=0.6968, ValR2=-0.1671, ValRMSE=0.3155, TrainTime=3.37 min, EndTime=08:07:34 \n",
"27) ExamplesSeen=194400, TrainLoss=0.8331, ValLoss=0.8048, ValR2=-0.3041, ValRMSE=0.3391, TrainTime=3.39 min, EndTime=08:11:04 \n",
"28) ExamplesSeen=201600, TrainLoss=0.6768, ValLoss=0.6649, ValR2=-0.1226, ValRMSE=0.3082, TrainTime=3.40 min, EndTime=08:14:35 \n",
"29) ExamplesSeen=208800, TrainLoss=0.7954, ValLoss=0.8067, ValR2=-0.3481, ValRMSE=0.3395, TrainTime=3.27 min, EndTime=08:17:57 \n",
"30) ExamplesSeen=216000, TrainLoss=0.6135, ValLoss=0.6470, ValR2=-0.0390, ValRMSE=0.3040, TrainTime=3.25 min, EndTime=08:21:18 \n",
"31) ExamplesSeen=223200, TrainLoss=0.6334, ValLoss=0.5841, ValR2=-0.0458, ValRMSE=0.2889, TrainTime=3.28 min, EndTime=08:24:41 \n",
"32) ExamplesSeen=230400, TrainLoss=0.6509, ValLoss=0.5885, ValR2=-0.0696, ValRMSE=0.2899, TrainTime=3.28 min, EndTime=08:28:04 \n",
"33) ExamplesSeen=237600, TrainLoss=1.0427, ValLoss=1.0250, ValR2=-0.7392, ValRMSE=0.3827, TrainTime=3.23 min, EndTime=08:31:23 \n",
"34) ExamplesSeen=244800, TrainLoss=0.6964, ValLoss=0.7240, ValR2=-0.1709, ValRMSE=0.3216, TrainTime=3.18 min, EndTime=08:34:40 \n",
"35) ExamplesSeen=252000, TrainLoss=0.6814, ValLoss=0.6839, ValR2=-0.1253, ValRMSE=0.3126, TrainTime=3.18 min, EndTime=08:37:57 \n",
"36) ExamplesSeen=259200, TrainLoss=0.8927, ValLoss=0.8359, ValR2=-0.4416, ValRMSE=0.3456, TrainTime=3.17 min, EndTime=08:41:14 \n",
"37) ExamplesSeen=266400, TrainLoss=0.6187, ValLoss=0.6161, ValR2=-0.0509, ValRMSE=0.2967, TrainTime=3.16 min, EndTime=08:44:29 \n",
"38) ExamplesSeen=273600, TrainLoss=0.6687, ValLoss=0.6684, ValR2=-0.0980, ValRMSE=0.3090, TrainTime=3.19 min, EndTime=08:47:46 \n",
"39) ExamplesSeen=280800, TrainLoss=1.2828, ValLoss=1.2485, ValR2=-1.1082, ValRMSE=0.4223, TrainTime=3.26 min, EndTime=08:51:08 \n",
"40) ExamplesSeen=288000, TrainLoss=0.5997, ValLoss=0.6388, ValR2=-0.0104, ValRMSE=0.3021, TrainTime=3.25 min, EndTime=08:54:29 \n",
"41) ExamplesSeen=295200, TrainLoss=0.6340, ValLoss=0.6536, ValR2=-0.0714, ValRMSE=0.3056, TrainTime=3.26 min, EndTime=08:57:50 \n",
"42) ExamplesSeen=302400, TrainLoss=0.6020, ValLoss=0.6100, ValR2=-0.0068, ValRMSE=0.2952, TrainTime=3.24 min, EndTime=09:01:11 \n",
"43) ExamplesSeen=309600, TrainLoss=0.6860, ValLoss=0.6976, ValR2=-0.2293, ValRMSE=0.3157, TrainTime=3.27 min, EndTime=09:04:33 \n",
"44) ExamplesSeen=316800, TrainLoss=1.0441, ValLoss=1.0488, ValR2=-0.7068, ValRMSE=0.3871, TrainTime=3.21 min, EndTime=09:07:52 \n",
"45) ExamplesSeen=324000, TrainLoss=0.7382, ValLoss=0.7020, ValR2=-0.2234, ValRMSE=0.3167, TrainTime=3.21 min, EndTime=09:11:11 \n",
"46) ExamplesSeen=331200, TrainLoss=0.7713, ValLoss=0.7931, ValR2=-0.3101, ValRMSE=0.3366, TrainTime=3.31 min, EndTime=09:14:35 \n",
"47) ExamplesSeen=338400, TrainLoss=0.6280, ValLoss=0.6366, ValR2=-0.0459, ValRMSE=0.3016, TrainTime=3.26 min, EndTime=09:17:57 \n",
"48) ExamplesSeen=345600, TrainLoss=0.6144, ValLoss=0.6033, ValR2=-0.0279, ValRMSE=0.2936, TrainTime=3.26 min, EndTime=09:21:18 \n",
"49) ExamplesSeen=352800, TrainLoss=0.6724, ValLoss=0.6476, ValR2=-0.1247, ValRMSE=0.3042, TrainTime=3.27 min, EndTime=09:24:40 \n",
"50) ExamplesSeen=360000, TrainLoss=0.6977, ValLoss=0.6236, ValR2=-0.1735, ValRMSE=0.2985, TrainTime=3.27 min, EndTime=09:28:03 \n",
"51) ExamplesSeen=367200, TrainLoss=0.9264, ValLoss=0.9168, ValR2=-0.5670, ValRMSE=0.3619, TrainTime=3.26 min, EndTime=09:31:24 \n",
"52) ExamplesSeen=374400, TrainLoss=0.7697, ValLoss=0.7399, ValR2=-0.2403, ValRMSE=0.3251, TrainTime=3.27 min, EndTime=09:34:46 \n",
"53) ExamplesSeen=381600, TrainLoss=0.6423, ValLoss=0.6454, ValR2=-0.0717, ValRMSE=0.3037, TrainTime=3.24 min, EndTime=09:38:06 \n",
"54) ExamplesSeen=388800, TrainLoss=0.6948, ValLoss=0.6729, ValR2=-0.1693, ValRMSE=0.3100, TrainTime=3.24 min, EndTime=09:41:27 \n",
"55) ExamplesSeen=396000, TrainLoss=0.6071, ValLoss=0.5962, ValR2=-0.0138, ValRMSE=0.2919, TrainTime=3.21 min, EndTime=09:44:45 \n",
"56) ExamplesSeen=403200, TrainLoss=1.0773, ValLoss=1.1789, ValR2=-0.8432, ValRMSE=0.4104, TrainTime=3.24 min, EndTime=09:48:06 \n",
"57) ExamplesSeen=410400, TrainLoss=0.6226, ValLoss=0.6389, ValR2=-0.0411, ValRMSE=0.3021, TrainTime=3.24 min, EndTime=09:51:26 \n",
"58) ExamplesSeen=417600, TrainLoss=0.6177, ValLoss=0.6190, ValR2=-0.0320, ValRMSE=0.2974, TrainTime=3.23 min, EndTime=09:54:46 \n",
"59) ExamplesSeen=424800, TrainLoss=0.6126, ValLoss=0.5951, ValR2=-0.0216, ValRMSE=0.2916, TrainTime=3.27 min, EndTime=09:58:08 \n",
"60) ExamplesSeen=432000, TrainLoss=0.8821, ValLoss=0.8633, ValR2=-0.4670, ValRMSE=0.3512, TrainTime=3.28 min, EndTime=10:01:31 \n",
"61) ExamplesSeen=439200, TrainLoss=0.7365, ValLoss=0.7636, ValR2=-0.2666, ValRMSE=0.3303, TrainTime=3.29 min, EndTime=10:04:54 \n",
"62) ExamplesSeen=446400, TrainLoss=0.6013, ValLoss=0.6393, ValR2=-0.0102, ValRMSE=0.3022, TrainTime=3.29 min, EndTime=10:08:18 \n",
"63) ExamplesSeen=453600, TrainLoss=0.6398, ValLoss=0.6732, ValR2=-0.0812, ValRMSE=0.3101, TrainTime=3.27 min, EndTime=10:11:40 \n",
"64) ExamplesSeen=460800, TrainLoss=0.6538, ValLoss=0.6511, ValR2=-0.0647, ValRMSE=0.3050, TrainTime=3.29 min, EndTime=10:15:03 \n",
"65) ExamplesSeen=468000, TrainLoss=0.6681, ValLoss=0.6425, ValR2=-0.1089, ValRMSE=0.3030, TrainTime=3.30 min, EndTime=10:18:27 \n",
"66) ExamplesSeen=475200, TrainLoss=0.7962, ValLoss=0.8180, ValR2=-0.3240, ValRMSE=0.3418, TrainTime=3.13 min, EndTime=10:21:40 \n",
"67) ExamplesSeen=482400, TrainLoss=1.0521, ValLoss=1.1469, ValR2=-0.6872, ValRMSE=0.4048, TrainTime=3.13 min, EndTime=10:24:54 \n",
"68) ExamplesSeen=489600, TrainLoss=0.6255, ValLoss=0.6121, ValR2=-0.0292, ValRMSE=0.2957, TrainTime=3.24 min, EndTime=10:28:15 \n",
"69) ExamplesSeen=496800, TrainLoss=0.6116, ValLoss=0.6102, ValR2=-0.0250, ValRMSE=0.2953, TrainTime=3.28 min, EndTime=10:31:38 \n",
"70) ExamplesSeen=504000, TrainLoss=0.6812, ValLoss=0.6912, ValR2=-0.1381, ValRMSE=0.3142, TrainTime=3.28 min, EndTime=10:35:00 \n",
"71) ExamplesSeen=511200, TrainLoss=0.6169, ValLoss=0.6332, ValR2=-0.0259, ValRMSE=0.3008, TrainTime=3.23 min, EndTime=10:38:20 \n",
"72) ExamplesSeen=518400, TrainLoss=0.6727, ValLoss=0.6522, ValR2=-0.1497, ValRMSE=0.3052, TrainTime=3.28 min, EndTime=10:41:43 \n",
"73) ExamplesSeen=525600, TrainLoss=0.6210, ValLoss=0.5830, ValR2=-0.0539, ValRMSE=0.2886, TrainTime=3.26 min, EndTime=10:45:04 \n",
"74) ExamplesSeen=532800, TrainLoss=0.7657, ValLoss=0.7392, ValR2=-0.1842, ValRMSE=0.3250, TrainTime=3.27 min, EndTime=10:48:27 \n",
"75) ExamplesSeen=540000, TrainLoss=0.6036, ValLoss=0.6139, ValR2=-0.0132, ValRMSE=0.2961, TrainTime=3.25 min, EndTime=10:51:48 \n",
"76) ExamplesSeen=547200, TrainLoss=0.6061, ValLoss=0.5781, ValR2=-0.0124, ValRMSE=0.2874, TrainTime=3.24 min, EndTime=10:55:08 \n",
"77) ExamplesSeen=554400, TrainLoss=0.6695, ValLoss=0.6484, ValR2=-0.0948, ValRMSE=0.3043, TrainTime=3.23 min, EndTime=10:58:28 \n",
"78) ExamplesSeen=561600, TrainLoss=0.7782, ValLoss=0.7547, ValR2=-0.2326, ValRMSE=0.3283, TrainTime=3.31 min, EndTime=11:01:53 \n",
"79) ExamplesSeen=568800, TrainLoss=0.6107, ValLoss=0.5860, ValR2=-0.0168, ValRMSE=0.2893, TrainTime=3.26 min, EndTime=11:05:15 \n",
"80) ExamplesSeen=576000, TrainLoss=0.7498, ValLoss=0.7782, ValR2=-0.2770, ValRMSE=0.3334, TrainTime=3.27 min, EndTime=11:08:37 \n",
"81) ExamplesSeen=583200, TrainLoss=0.6121, ValLoss=0.6348, ValR2=-0.0235, ValRMSE=0.3011, TrainTime=3.29 min, EndTime=11:12:00 \n",
"82) ExamplesSeen=590400, TrainLoss=0.8285, ValLoss=0.8519, ValR2=-0.4161, ValRMSE=0.3488, TrainTime=3.27 min, EndTime=11:15:23 \n",
"83) ExamplesSeen=597600, TrainLoss=0.6011, ValLoss=0.6011, ValR2=-0.0111, ValRMSE=0.2930, TrainTime=3.27 min, EndTime=11:18:45 \n",
"84) ExamplesSeen=604800, TrainLoss=0.6761, ValLoss=0.6469, ValR2=-0.1276, ValRMSE=0.3040, TrainTime=3.28 min, EndTime=11:22:08 \n",
"85) ExamplesSeen=612000, TrainLoss=0.6434, ValLoss=0.6398, ValR2=-0.0678, ValRMSE=0.3023, TrainTime=3.27 min, EndTime=11:25:30 \n",
"86) ExamplesSeen=619200, TrainLoss=0.6087, ValLoss=0.5794, ValR2=-0.0126, ValRMSE=0.2877, TrainTime=3.29 min, EndTime=11:28:54 \n",
"87) ExamplesSeen=626400, TrainLoss=0.6064, ValLoss=0.6047, ValR2=-0.0084, ValRMSE=0.2939, TrainTime=3.26 min, EndTime=11:32:16 \n",
"88) ExamplesSeen=633600, TrainLoss=0.8032, ValLoss=0.7420, ValR2=-0.2693, ValRMSE=0.3256, TrainTime=3.23 min, EndTime=11:35:36 \n",
"89) ExamplesSeen=640800, TrainLoss=0.6477, ValLoss=0.6773, ValR2=-0.1558, ValRMSE=0.3110, TrainTime=3.27 min, EndTime=11:38:58 \n",
"90) ExamplesSeen=648000, TrainLoss=0.6927, ValLoss=0.7621, ValR2=-0.2232, ValRMSE=0.3300, TrainTime=3.26 min, EndTime=11:42:19 \n",
"91) ExamplesSeen=655200, TrainLoss=0.6946, ValLoss=0.6593, ValR2=-0.1533, ValRMSE=0.3069, TrainTime=3.20 min, EndTime=11:45:37 \n",
"92) ExamplesSeen=662400, TrainLoss=0.6913, ValLoss=0.6071, ValR2=-0.1556, ValRMSE=0.2945, TrainTime=3.27 min, EndTime=11:49:00 \n",
"93) ExamplesSeen=669600, TrainLoss=0.6099, ValLoss=0.6568, ValR2=-0.0314, ValRMSE=0.3063, TrainTime=3.30 min, EndTime=11:52:23 \n",
"94) ExamplesSeen=676800, TrainLoss=0.6166, ValLoss=0.6084, ValR2=-0.0296, ValRMSE=0.2948, TrainTime=3.26 min, EndTime=11:55:45 \n",
"95) ExamplesSeen=684000, TrainLoss=0.6232, ValLoss=0.6107, ValR2=-0.0323, ValRMSE=0.2954, TrainTime=3.26 min, EndTime=11:59:07 \n",
"96) ExamplesSeen=691200, TrainLoss=0.6043, ValLoss=0.5982, ValR2=-0.0079, ValRMSE=0.2923, TrainTime=3.29 min, EndTime=12:02:30 \n",
"97) ExamplesSeen=698400, TrainLoss=0.6155, ValLoss=0.5462, ValR2=-0.0102, ValRMSE=0.2793, TrainTime=3.28 min, EndTime=12:05:53 \n",
"98) ExamplesSeen=705600, TrainLoss=0.6149, ValLoss=0.6126, ValR2=-0.0152, ValRMSE=0.2958, TrainTime=3.28 min, EndTime=12:09:16 \n",
"99) ExamplesSeen=712800, TrainLoss=0.7542, ValLoss=0.7707, ValR2=-0.2946, ValRMSE=0.3318, TrainTime=3.29 min, EndTime=12:12:40 \n",
"100) ExamplesSeen=720000, TrainLoss=0.6116, ValLoss=0.5889, ValR2=-0.0234, ValRMSE=0.2900, TrainTime=3.33 min, EndTime=12:16:06 \n",
"Finished. Total train time = 5.58 hours\n",
"End Time: 2016-04-26 00:16:06\n"
]
}
],
"source": [
"#! /usr/bin/env python\n",
"# Make sure the latest code updates are loaded\n",
"%reload_ext autoreload\n",
"%autoreload 2\n",
"\n",
"import sys\n",
"import os\n",
"import time\n",
"import numpy as np\n",
"from utils_yp import *\n",
"from datetime import datetime\n",
"from gru_theano_yp import GRUTheanoYP\n",
"\n",
"LEARNING_RATE = float(os.environ.get(\"LEARNING_RATE\", \"0.01\"))\n",
"DECAY_RATE = float(os.environ.get(\"DECAY_RATE\", \"0.9\"))\n",
"X_DIM = int(os.environ.get(\"X_DIM\", \"1\")) # number of features in the x_t vector\n",
"HIDDEN_DIM = int(os.environ.get(\"HIDDEN_DIM\", \"128\"))\n",
"NEPOCH = int(os.environ.get(\"NEPOCH\", \"100\"))\n",
"\n",
"def train_with_sgd(model, X_train, y_train, X_test, y_test, nepoch=20, learning_rate=0.001, decay=0.9):\n",
" # Set start of training time\n",
" start_time = time.time()\n",
" # Set model time\n",
" modeltime = datetime.now().strftime(\"%Y%m%d%H%M%S\")\n",
" print 'Epochs: %d, TrainingSamples: %s, ModelTime: %s' %(nepoch, y_train.shape[0], modeltime) \n",
" # Set epoch variables\n",
" min_loss = 100000\n",
" train_losses = []\n",
" val_losses = []\n",
" num_examples_seen = 0\n",
" for epoch in range(nepoch):\n",
" epoch_start = time.time()\n",
" # Divide the data\n",
" train_data, train_labels, val_data, val_labels = shuffle_data(0.9, X_train, y_train) # Use this for full sample training \n",
" # train_data, train_labels, val_data, val_labels = shuffle_data2(X_train, y_train, 10000, 1000) # Use this for testing and debugging \n",
" # For each training example...\n",
" for i in range(len(train_data)):\n",
" # One SGD step\n",
" model.sgd_step(train_data[i], train_labels[i], learning_rate, decay)\n",
" num_examples_seen += 1\n",
" epoch_time = time.time() - epoch_start\n",
" # Calculate training loss\n",
" train_loss = model.calculate_loss(train_data, train_labels)\n",
" train_losses.append((num_examples_seen, train_loss))\n",
" # Calculate validation loss, R-squared, and RMSE\n",
" val_loss = model.calculate_loss(val_data, val_labels)\n",
" val_losses.append((num_examples_seen, val_loss))\n",
" val_predictions = []\n",
" for i in range(len(val_data)):\n",
" val_predictions.append(model.predict(val_data[i]))\n",
" val_predictions = np.reshape(val_predictions, (len(val_predictions), len(val_predictions[0])))\n",
" val_R_squared = 1 - np.sum(np.square(val_predictions - val_labels))/np.sum(np.square(val_labels - np.mean(val_labels)))\n",
" val_rmse = np.sqrt(np.mean(np.square(val_predictions - val_labels)))\n",
" # If validation loss is a new minimum, save predictions and model \n",
" is_saved = ''\n",
" if val_loss < min_loss:\n",
" min_loss = val_loss\n",
" # Make and save predictions\n",
" X_test_predictions = []\n",
" for i in range(len(X_test)):\n",
" X_test_predictions.append(np.append(model.predict(X_test[i]).reshape(len(X_test[i])), float(y_test[i][0])))\n",
" predictions_and_labels = np.asarray(X_test_predictions)\n",
" filename = \"./predictions/GRUs/pred-%s.txt\" %(modeltime)\n",
" np.savetxt(filename, predictions_and_labels, fmt='%.18f', delimiter=',',)\n",
" # Save model parameters\n",
" filename = \"./models/GRUs/GRU-%s.npz\" % (modeltime)\n",
" save_model_parameters_theano(model, filename)\n",
" is_saved = '(saved)'\n",
" # Print epoch stats\n",
" print '%d) ExamplesSeen=%d, TrainLoss=%.4f, ValLoss=%.4f, ValR2=%.4f, ValRMSE=%.4f, TrainTime=%.2f min, EndTime=%s %s' %(epoch+1, num_examples_seen, train_loss, val_loss, val_R_squared, val_rmse, epoch_time/60, time.strftime(\"%I:%M:%S\"), is_saved)\n",
" print 'Finished. Total train time = %.2f hours' %((time.time() - start_time)/3600) \n",
" \n",
" return train_losses, val_losses\n",
"\n",
"# Start Time\n",
"print 'Start Time: %s' %(datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\"))\n",
"\n",
"# Build model\n",
"model = GRUTheanoYP(x_dim=X_DIM, hidden_dim=HIDDEN_DIM, bptt_truncate=-1)\n",
"\n",
"# Measure and Print SGD step time\n",
"t1 = time.time()\n",
"model.sgd_step(X_train[10], Y_train[10], LEARNING_RATE, DECAY_RATE)\n",
"t2 = time.time()\n",
"print \"SGD Step time: %f milliseconds\" % ((t2 - t1) * 1000.)\n",
"\n",
"# Train the model\n",
"train_losses, val_losses = train_with_sgd(model, X_train, log_Y_train, X_test, log_Y_test, nepoch=NEPOCH, learning_rate=LEARNING_RATE, decay=DECAY_RATE)\n",
"\n",
"#End Time\n",
"print 'End Time: %s' %(datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")) \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Baseline Model: The Mean"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"R-Squared: -0.0002, MAE: 0.7594, RMSE: 0.9684, MAPE: 26.20%\n"
]
}
],
"source": [
"Y_train_1d = Y_train[:,0]\n",
"y_hat = len(Y_test)*[np.mean(Y_train_1d)]\n",
"y_test = Y_test[:,0]\n",
"\n",
"y_hat = np.array(y_hat).astype(np.float)\n",
"y_test = np.array(y_test).astype(np.float)\n",
"y_bar = np.mean(y_test)\n",
"\n",
"R_squared = 1 - np.sum(np.square(y_hat - y_test))/np.sum(np.square(y_test - y_bar))\n",
"mae = np.mean(np.abs(y_hat - y_test))\n",
"rmse = np.sqrt(np.mean(np.square(y_hat - y_test)))\n",
"mape = np.mean(abs(np.divide((y_hat - y_test), y_test)))\n",
"\n",
"print 'R-Squared: %.4f, MAE: %.4f, RMSE: %.4f, MAPE: %.2f%s' %(R_squared, mae, rmse, mape*100, '%') "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Calculate Performance Stats on Holdout Sample\n",
"These are the predictions on a holdout sample (X_test above) using the model with the lowest validation loss. \n",
"- t-0 = prediction at harvest time \n",
"- t-6 = prediction three months out from harvest "
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false,
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Log-Transformed Yield Results:\n",
"t-6) R-Squared: 0.0000, MAE: 0.2453, RMSE: 0.2978, MAPE: 26.09%\n",
"t-5) R-Squared: -0.0001, MAE: 0.2454, RMSE: 0.2978, MAPE: 26.00%\n",
"t-4) R-Squared: 0.0000, MAE: 0.2454, RMSE: 0.2978, MAPE: 26.06%\n",
"t-3) R-Squared: -0.0002, MAE: 0.2454, RMSE: 0.2978, MAPE: 26.00%\n",
"t-2) R-Squared: -0.0000, MAE: 0.2454, RMSE: 0.2978, MAPE: 26.06%\n",
"t-1) R-Squared: -0.0002, MAE: 0.2454, RMSE: 0.2978, MAPE: 26.00%\n",
"t-0) R-Squared: -0.0000, MAE: 0.2454, RMSE: 0.2978, MAPE: 26.06%\n",
"\n",
"Values Transformed Back to Original Units Results:\n",
"t-6) R-Squared: -0.0196, MAE: 0.7597, RMSE: 0.9778, MAPE: 25.18%\n",
"t-5) R-Squared: -0.0235, MAE: 0.7600, RMSE: 0.9797, MAPE: 25.08%\n",
"t-4) R-Squared: -0.0209, MAE: 0.7597, RMSE: 0.9784, MAPE: 25.14%\n",
"t-3) R-Squared: -0.0235, MAE: 0.7600, RMSE: 0.9797, MAPE: 25.08%\n",
"t-2) R-Squared: -0.0209, MAE: 0.7597, RMSE: 0.9785, MAPE: 25.14%\n",
"t-1) R-Squared: -0.0235, MAE: 0.7600, RMSE: 0.9797, MAPE: 25.08%\n",
"t-0) R-Squared: -0.0209, MAE: 0.7597, RMSE: 0.9785, MAPE: 25.14%\n"
]
}
],
"source": [
"y_hat = []\n",
"y_test = []\n",
"with open('./predictions/GRUs/pred-20160425154620.txt', 'r') as csvfile:\n",
" datareader = csv.reader(csvfile, delimiter=',')\n",
" for row in datareader:\n",
" label = row.pop()\n",
" y_hat.append(row)\n",
" y_test.append(label)\n",
"\n",
"# Results keeping predictions as logs of yield\n",
"y_hat = np.array(y_hat).astype(np.float)\n",
"y_test = np.array(y_test).astype(np.float)\n",
"y_bar = np.mean(y_test)\n",
"\n",
"R_squared_list_tr = []\n",
"mae_list_tr = []\n",
"rmse_list_tr = []\n",
"mape_list_tr = []\n",
"\n",
"print 'Log-Transformed Yield Results:'\n",
"for j in range(len(y_hat[0])):\n",
" y_hat_last = y_hat[:, j]\n",
" R_squared = 1 - np.sum(np.square(y_hat_last - y_test))/np.sum(np.square(y_test - y_bar))\n",
" mae = np.mean(np.abs(y_hat_last - y_test))\n",
" rmse = np.sqrt(np.mean(np.square(y_hat_last - y_test)))\n",
" count = 0\n",
" sums = 0\n",
" for i in range(len(y_test)):\n",
" if y_test[i] != 0: \n",
" error = np.divide(abs(y_hat_last[i] - y_test[i]), abs(y_test[i]))\n",
" count += 1\n",
" sums += error\n",
" mean_abs_pct_error = sums/count\n",
" R_squared_list_tr.append(R_squared)\n",
" mae_list_tr.append(mae)\n",
" rmse_list_tr.append(rmse)\n",
" mape_list_tr.append(mean_abs_pct_error)\n",
" print 't-%d) R-Squared: %.4f, MAE: %.4f, RMSE: %.4f, MAPE: %.2f%s' %(6-j, R_squared, mae, rmse, mean_abs_pct_error*100, '%') \n",
" \n",
"# Results transforming predictions and yields back to original units\n",
"y_hat = np.array(y_hat).astype(np.float)\n",
"y_test = np.array(y_test).astype(np.float)\n",
"\n",
"y_hat_exp = np.exp(y_hat)\n",
"y_test_exp = np.exp(y_test)\n",
"y_bar_exp = np.mean(y_test_exp)\n",
"\n",
"R_squared_list_exp = []\n",
"mae_list_exp = []\n",
"rmse_list_exp = []\n",
"mape_list_exp = []\n",
"\n",
"print '\\nValues Transformed Back to Original Units Results:'\n",
"for j in range(len(y_hat_exp[0])):\n",
" y_hat_last = y_hat_exp[:, j]\n",
" R_squared = 1 - np.sum(np.square(y_hat_last - y_test_exp))/np.sum(np.square(y_test_exp - y_bar_exp))\n",
" mae = np.mean(np.abs(y_hat_last - y_test_exp))\n",
" rmse = np.sqrt(np.mean(np.square(y_hat_last - y_test_exp)))\n",
" count = 0\n",
" sums = 0\n",
" for i in range(len(y_test_exp)):\n",
" if y_test_exp[i] != 0: \n",
" error = np.divide(abs(y_hat_last[i] - y_test_exp[i]), abs(y_test_exp[i]))\n",
" count += 1\n",
" sums += error\n",
" mean_abs_pct_error = sums/count\n",
" R_squared_list_exp.append(R_squared)\n",
" mae_list_exp.append(mae)\n",
" rmse_list_exp.append(rmse)\n",
" mape_list_exp.append(mean_abs_pct_error)\n",
" print 't-%d) R-Squared: %.4f, MAE: %.4f, RMSE: %.4f, MAPE: %.2f%s' %(len(y_hat_exp[0])-1-j, R_squared, mae, rmse, mean_abs_pct_error*100, '%')\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Graph Losses"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"image/png": "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\nLrvsMuLx+JLKMMANN9zA5ZdfDsArXvEKbr311sX9rlVpfeCBB7j44ot53/veh+M43HrrrVxyySV8\n61vfAooh/dd//dfZvXs30WiUW265hSeeeGJd5+SRRx5hfn6e//7f/zvBYJA3v/nN3HzzzXzta18D\nIBwOs3//fmZnZ2lvb+eqq65afHxsbIwjR44QCAS4/vrr13Xcc6VwXAOLlePSn5iIiIhIDRhTnT8b\ncf3119PT08M//MM/8OKLL/Jv//ZvvPe97wXgwIED3HzzzQwMDNDe3s7v//7vV9SiMDY2tuRCt+3b\nty95/tFHH+XNb34zfX19dHR08IUvfKHi1ofR0dEV+9uxYwejo6OL97ds2bJ4OxaLkUqlKtr32cdY\nfqHejh07GBkZAeDv//7vefDBB9m5cyd79+7lkUceAeDDH/4wu3fv5q1vfSsXXnghn/70p9d13HOl\ncFwD2XTxb1WORURE6sfa6vzZqPe///3ce++93Hfffdx000309vYCxenZLrvsMg4ePMjMzAyf/OQn\nK5rBYmBggKNHjy7eP/s2wHvf+17e/e53c/z4caanp/mt3/qtxf2u1YawdetWXnrppSWPvfTSS2zd\nurWif2sltm7dyrFjx5ZUsV966SWGhoYAuOaaa/jmN7/JqVOnePe7380tt9wCFNtF7r77bl588UW+\n9a1v8cd//Mc89NBDVRvXWhSOa+B0W4UqxyIiIi8f73//+/nnf/5n/vIv/3KxpQIglUrR2tpKPB7n\nueee4/Of//yq+7DWLobJW265hS9/+cs8++yzpNPpFW0VqVSKzs5OwuEwjz32GF/72tcWQ3Fvby+O\n4/Diiy+WPM7b3/52Dhw4wNe//nXy+Tx/+7d/y3PPPcfNN9+8ZCzr4bou2Wx28c+1115LPB7nM5/5\nDLlcjn379nH//fdz6623ksvl+OpXv8rMzAyBQIDW1lYCgQAA999/PwcPHsRaS1tbG4FAYPG5elA4\nroFMxhIxBby8KsciIiIvFzt27OD6668nnU7zrne9a/Hxu+++m6997Wu0tbXxwQ9+kFtvvXVJZXf5\n7dP3b7rpJu644w5uvPFGLrroIn7hF35hybaf+9zn+NjHPkZbWxt/+Id/yK/8yq8sPhePx/n93/99\nrr/+erq6unj00UeX7Lu7u5v777+fe+65h56eHu6++27uv/9+urq61hzXcsYYRkZGiMVixONx4vE4\n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\n",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"\n",
"def plot_losses(train_losses, val_losses, epoch_examples, epochs, min_y, max_y):\n",
" x1 = [a/epoch_examples for (a,b) in train_losses]\n",
" y1 = [b for (a,b) in train_losses]\n",
" x2 = [a/epoch_examples for (a,b) in val_losses]\n",
" y2 = [b for (a,b) in val_losses]\n",
" plt.figure(figsize=(12, 6))\n",
" plt.axis((1, epochs, min_y, max_y))\n",
" plt.plot(x1, y1, \"-m\", x2, y2, \"-b\")\n",
" plt.legend(('Train Loss', 'Validation Loss'))\n",
" xlabel = 'Epochs (%d Examples Seen Per Epoch)' %(epoch_examples)\n",
" plt.xlabel(xlabel)\n",
" plt.show()\n",
"\n",
"plot_losses(train_losses, val_losses, 7200, 100, 0, 3.0)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"### Code for Reloading the Saved Best Model"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": false,
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Building model from ./models/GRUs/GRU-20160425154620.npz with hidden_dim=128 x_dim=1\n"
]
}
],
"source": [
"model = load_model_parameters_theano(\"./models/GRUs/GRU-20160425154620.npz\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"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.10"
}
},
"nbformat": 4,
"nbformat_minor": 0
}