{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Introduction to Machine Learning with TensorFlow" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "#conda install -c conda-forge tensorflow=1.0\n", "# API guide is at https://www.tensorflow.org/api_guides/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## You might think of TensorFlow Core programs as consisting of two discrete sections:\n", "\n", "
Building the computational graph.\n", "
Running the computational graph."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tensor(\"A:0\", shape=(2,), dtype=float32)\n",
"Tensor(\"mul:0\", shape=(2,), dtype=float32)\n",
"(2,)\n",
"[ 2. 6.]\n",
"[ 2. 6.]\n",
"[ 1. 9.]\n",
"[ 9. 81.]\n",
"b'Hello, TensorFlow!'\n",
"[ 2. 6.]\n"
]
}
],
"source": [
"import tensorflow as tf\n",
"sess = tf.InteractiveSession()\n",
"\n",
"# Some tensor we want to print the value of\n",
"a = tf.constant([1.0, 3.0],name = \"A\")\n",
"b = a*3\n",
"hello = tf.constant('Hello, TensorFlow!')\n",
"\n",
"print(a)\n",
"print(b)\n",
"#Explicitly call the shape function\n",
"print(a.get_shape())\n",
"\n",
"#Execute the statements above\n",
"print(sess.run(a+a))\n",
"print(sess.run(a*2))\n",
"print(sess.run(a**2))\n",
"print(sess.run(b**2))\n",
"print(sess.run(hello))\n",
"# ANother way to do the same above\n",
"print((a*2).eval())\n",
"\n",
"sess.close() # Because it is an interactive session we have to close it"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"3 # a rank 0 tensor; this is a scalar with shape []\n",
"[1. ,2., 3.] # a rank 1 tensor; this is a vector with shape [3]\n",
"[[1., 2., 3.], [4., 5., 6.]] # a rank 2 tensor; a matrix with shape [2, 3]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Assign type"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tensor(\"Const_1:0\", shape=(), dtype=float64) Tensor(\"Const_2:0\", shape=(), dtype=float32)\n"
]
}
],
"source": [
"node1 = tf.constant(3.0, tf.float64) # contant values once assigned cannot be changed\n",
"node2 = tf.constant(4.0) # also tf.float32 implicitly\n",
"print(node1, node2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Notice that printing the nodes does not output the values 3.0 and 4.0 as you might expect. Instead, they are nodes that, when evaluated, would produce 3.0 and 4.0, respectively. To actually evaluate the nodes, we must run the computational graph within a session. A session encapsulates the control and state of the TensorFlow runtime."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[3.0, 4.0]\n",
"[3.0, 4.0]\n"
]
}
],
"source": [
"# Evaluate multiple values with one sess.run call\n",
"sess = tf.InteractiveSession()\n",
"print(sess.run([node1, node2]))\n",
"sess.close()\n",
"\n",
"# Another way to create sessions\n",
"\n",
"with tf.Session() as sess:\n",
" print(sess.run([node1,node2]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Let us create our first real program, a linear model"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Add scalar a and b\n",
"7.5\n",
"Add vector a and b\n",
"[ 3. 7.]\n",
"Add and multiply by 3\n",
"22.5\n",
"Linear model results with vector inputs\n",
"[ 0. 0.30000001 0.60000002 0.90000004]\n",
"Print the op for linear_model name: \"add_1\"\n",
"op: \"Add\"\n",
"input: \"mul_1\"\n",
"input: \"Variable_1/read\"\n",
"attr {\n",
" key: \"T\"\n",
" value {\n",
" type: DT_FLOAT\n",
" }\n",
"}\n",
"\n"
]
}
],
"source": [
"# Reset the graph, used mostly when working in Jupyter notebook environments\n",
"tf.reset_default_graph()\n",
"\n",
"#Tensorflow can be parameterized to accept external inputs, known as placeholders. A placeholder is a promise to \n",
"#provide a value later.\n",
"\n",
"sess = tf.InteractiveSession()\n",
"a = tf.placeholder(tf.float32)\n",
"b = tf.placeholder(tf.float32)\n",
"adder_node = a + b # + provides a shortcut for tf.add(a, b)\n",
"\n",
"#We can evaluate this graph with multiple inputs by using the feed_dict parameter to specify Tensors that provide \n",
"#concrete values to these placeholders:\n",
"print(\"Add scalar a and b\")\n",
"print(sess.run(adder_node, feed_dict={a: 3, b:4.5}))\n",
"print(\"Add vector a and b\")\n",
"print(sess.run(adder_node, {a: [1,3], b: [2, 4]}))\n",
"\n",
"# Let us make this a little more involved and add another operation\n",
"add_and_triple = adder_node * 3.\n",
"print(\"Add and multiply by 3\")\n",
"print(sess.run(add_and_triple, {a: 3, b:4.5}))\n",
"\n",
"#In machine learning we will typically want a model that can take arbitrary inputs, such as the one above. To make the \n",
"#model trainable, we need to be able to modify the graph to get new outputs with the same input. Variables allow us to \n",
"#add trainable parameters to a graph. They are constructed with a type and initial value:\n",
"\n",
"W = tf.Variable([.3], tf.float32)\n",
"b = tf.Variable([-.3], tf.float32)\n",
"x = tf.placeholder(tf.float32)\n",
"linear_model = W * x + b\n",
"\n",
"#Constants are initialized when you call tf.constant, and their value can never change. By contrast, variables are \n",
"#not initialized when you call tf.Variable. To initialize all the variables in a TensorFlow program, you must explicitly \n",
"#call a special operation as follows:\n",
"\n",
"init = tf.global_variables_initializer()\n",
"sess.run(init)\n",
"\n",
"#Since x is a placeholder, we can evaluate linear_model for several values of x simultaneously as follows:\n",
"print(\"Linear model results with vector inputs\")\n",
"print(sess.run(linear_model, {x:[1,2,3,4]}))\n",
"print(\"Print the op for linear_model\",linear_model.op)\n",
"#show_graph(add_and_triple)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Determine the error - L2 norm squared"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"23.66\n"
]
}
],
"source": [
"# Let us determine the error now, given the correct values are in a variable or 'placeholder' y\n",
"y = tf.placeholder(tf.float32)\n",
"squared_deltas = tf.square(linear_model - y)\n",
"loss = tf.reduce_sum(squared_deltas)\n",
"print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Reassign values to variables W and b"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[array([-1.], dtype=float32), array([ 1.], dtype=float32)]\n",
"0.0\n"
]
}
],
"source": [
"# Now that is pretty high, so let us reassign the values for our parameters to W = -1 and b = 1\n",
"fixW = tf.assign(W, [-1.]) # used to reassign values to variables\n",
"fixb = tf.assign(b, [1.])\n",
"print(sess.run([fixW, fixb]))\n",
"print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Given inputs and outputs, let us train our model to obtain weights and biases"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Weights and biases in iteration 0 -0.22 -0.456\n",
"Weights and biases in iteration 50 -0.712702 0.155309\n",
"Weights and biases in iteration 100 -0.842705 0.537533\n",
"Weights and biases in iteration 150 -0.913881 0.7468\n",
"Weights and biases in iteration 200 -0.95285 0.861373\n",
"Weights and biases in iteration 250 -0.974185 0.924102\n",
"Weights and biases in iteration 300 -0.985867 0.958446\n",
"Weights and biases in iteration 350 -0.992262 0.977249\n",
"Weights and biases in iteration 400 -0.995763 0.987544\n",
"Weights and biases in iteration 450 -0.99768 0.99318\n",
"Final Weights and biases\n",
"[array([-0.99871475], dtype=float32), array([ 0.99622124], dtype=float32)]\n"
]
}
],
"source": [
"# In the real world we don't have the answers, so now let us train this to find the right weights and biases\n",
"optimizer = tf.train.GradientDescentOptimizer(0.01)\n",
"train = optimizer.minimize(loss)\n",
"sess.run(init) # reset values to incorrect defaults.\n",
"# Iterate to find the minimum\n",
"for i in range(500):\n",
" return_val = sess.run([train,W,b], {x:[1,2,3,4], y:[0,-1,-2,-3]})\n",
" if(not(i%50)):\n",
" print(\"Weights and biases in iteration \",i,return_val[1][0],return_val[2][0]) # Use this to see how the optimzer progresses\n",
"\n",
"print(\"Final Weights and biases\")\n",
"print(sess.run([W, b]))\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Let us summarize the code for a linear regression model here"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Weights and biases in iteration 0 -0.22 -0.456\n",
"Weights and biases in iteration 50 -0.712702 0.155309\n",
"Weights and biases in iteration 100 -0.842705 0.537533\n",
"Weights and biases in iteration 150 -0.913881 0.7468\n",
"Weights and biases in iteration 200 -0.95285 0.861373\n",
"Weights and biases in iteration 250 -0.974185 0.924102\n",
"Weights and biases in iteration 300 -0.985867 0.958446\n",
"Weights and biases in iteration 350 -0.992262 0.977249\n",
"Weights and biases in iteration 400 -0.995763 0.987544\n",
"Weights and biases in iteration 450 -0.99768 0.99318\n",
"Weights and biases in iteration 500 -0.99873 0.996266\n",
"Weights and biases in iteration 550 -0.999305 0.997956\n",
"Weights and biases in iteration 600 -0.999619 0.998881\n",
"Weights and biases in iteration 650 -0.999792 0.999387\n",
"Weights and biases in iteration 700 -0.999886 0.999665\n",
"Weights and biases in iteration 750 -0.999938 0.999816\n",
"Weights and biases in iteration 800 -0.999966 0.999899\n",
"Weights and biases in iteration 850 -0.999981 0.999945\n",
"Weights and biases in iteration 900 -0.99999 0.99997\n",
"Weights and biases in iteration 950 -0.999994 0.999983\n",
"W: [-0.9999969] b: [ 0.99999082] loss: 5.69997e-11\n"
]
}
],
"source": [
"#To summarize and evaluate accuracy\n",
"import numpy as np\n",
"import tensorflow as tf\n",
"\n",
"# Model parameters\n",
"W = tf.Variable([.3], tf.float32)\n",
"b = tf.Variable([-.3], tf.float32)\n",
"# Model input and output\n",
"x = tf.placeholder(tf.float32)\n",
"linear_model = W * x + b\n",
"y = tf.placeholder(tf.float32)\n",
"# loss\n",
"loss = tf.reduce_sum(tf.square(linear_model - y)) # sum of the squares\n",
"# optimizer\n",
"optimizer = tf.train.GradientDescentOptimizer(0.01)\n",
"train = optimizer.minimize(loss)\n",
"# training data\n",
"x_train = [1,2,3,4]\n",
"y_train = [0,-1,-2,-3]\n",
"# training loop\n",
"init = tf.global_variables_initializer()\n",
"sess = tf.InteractiveSession()\n",
"sess.run(init) # reset values to wrong\n",
"for i in range(1000):\n",
" return_val = sess.run([train,W,b], {x:x_train, y:y_train})\n",
" if(not(i%50)):\n",
" print(\"Weights and biases in iteration \",i,return_val[1][0],return_val[2][0]) # Use this to see how the optimzer progresses\n",
"\n",
"# evaluate training accuracy\n",
"curr_W, curr_b, curr_loss = sess.run([W, b, loss], {x:x_train, y:y_train})\n",
"print(\"W: %s b: %s loss: %s\"%(curr_W, curr_b, curr_loss))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Let us see tf.contrib.learn makes all of this a lot easier (Older way). Estimators allow you to work at a higher level, without having to deal with sessions."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"INFO:tensorflow:Using default config.\n",
"WARNING:tensorflow:Using temporary folder as model directory: /var/folders/qh/fr09lp897f7360g0yvw8dsl40000gn/T/tmp9dvbw1p5\n",
"INFO:tensorflow:Using config: {'_task_type': None, '_task_id': 0, '_cluster_spec': Learn about the MNIST data and softmax regressions\n",
" Create a function that is a model for recognizing digits, based on looking at every pixel in the image\n",
" Use TensorFlow to train the model to recognize digits by having it \"look\" at thousands of examples (and run our first TensorFlow session to do so)\n",
" Check the model's accuracy with our test data\n",
"\n",
"The MNIST Data\n",
"\n",
"The MNIST data is split into three parts: 55,000 data points of training data (mnist.train), 10,000 points of test data (mnist.test), and 5,000 points of validation data (mnist.validation). This split is very important: it's essential in machine learning that we have separate data which we don't learn from so that we can make sure that what we've learned actually generalizes!\n",
"\n",
"![title](https://www.tensorflow.org/images/mnist-train-xs.png)\n",
"\n",
"As mentioned earlier, every MNIST data point has two parts: an image of a handwritten digit and a corresponding label. We'll call the images \"x\" and the labels \"y\". Both the training set and test set contain images and their corresponding labels; for example the training images are mnist.train.images and the training labels are mnist.train.labels.\n",
"\n",
"Each image is 28 pixels by 28 pixels. We can interpret this as a big array of numbers:\n",
"\n",
"We can flatten this array into a vector of 28x28 = 784 numbers. It doesn't matter how we flatten the array, as long as we're consistent between images. From this perspective, the MNIST images are just a bunch of points in a 784-dimensional vector space, with a very rich structure.\n",
"\n",
"Flattening the data throws away information about the 2D structure of the image. Isn't that bad? Well, the best computer vision methods do exploit this structure, and we will in later tutorials. But the simple method we will be using here, a softmax regression (defined below), won't.\n",
"\n",
"The result is that mnist.train.images is a tensor (an n-dimensional array) with a shape of [55000, 784]. The first dimension is an index into the list of images and the second dimension is the index for each pixel in each image. Each entry in the tensor is a pixel intensity between 0 and 1, for a particular pixel in a particular image.\n",
"\n",
"\n",
"Each image in MNIST has a corresponding label, a number between 0 and 9 representing the digit drawn in the image.\n",
"\n",
"For the purposes of this tutorial, we're going to want our labels as \"one-hot vectors\". A one-hot vector is a vector which is 0 in most dimensions, and 1 in a single dimension. In this case, the nth digit will be represented as a vector which is 1 in the nth dimension. For example, 3 would be [0,0,0,1,0,0,0,0,0,0]. Consequently, mnist.train.labels is a [55000, 10] array of floats.\n",
"\n",
"![title](https://www.tensorflow.org/images/softmax-regression-vectorequation.png)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Cross entropy cost function\n",
"\n",
"Because of the shape of the sigmoid function, extreme values as inputs to the sigmoid function tend to result in smaller partial derivatives for that cost function. This results in slower learning when the inputs are really far away from zero if the objective is to bring the cost function to zero.\n",
"\n",
"It turns out that we can solve the problem by replacing the quadratic cost with a different cost function, known as the cross-entropy.\n",
"\n",
"For a simple neural network output given by 'a'\n",
"\n",
"$a = sigma(z)$\n",
"\n",
"where \n",
"\n",
"$z = w*x + b$\n",
"\n",
"Let the desired output be y, then the Cross entropy cost is given by\n",
"\n",
"$cost = y*ln(a) + (1-y)*ln(1 -a)$\n",
"\n",
"This satisfies all the properties of a cost function: when y = 1 and a close to 1, cost = 0\n",
"and when y = 0 and a is close to 0, cost = 0\n",
"\n",
"Partial derivative of this cost is given by $x * (sigma(z) - y)$\n",
"This is great because now the learning rate is proportional to (a - y), so the farther a is from y, the larger the gradient.\n",
"\n",
"See here for details http://neuralnetworksanddeeplearning.com/chap3.html"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.\n",
"Extracting /tmp/tensorflow/mnist/input_data/train-images-idx3-ubyte.gz\n",
"Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.\n",
"Extracting /tmp/tensorflow/mnist/input_data/train-labels-idx1-ubyte.gz\n",
"Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.\n",
"Extracting /tmp/tensorflow/mnist/input_data/t10k-images-idx3-ubyte.gz\n",
"Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.\n",
"Extracting /tmp/tensorflow/mnist/input_data/t10k-labels-idx1-ubyte.gz\n",
"Cross entropy at step 0 = 2.30259\n",
"Cross entropy at step 100 = 0.633378\n",
"Cross entropy at step 200 = 0.639661\n",
"Cross entropy at step 300 = 0.326474\n",
"Cross entropy at step 400 = 0.372144\n",
"Cross entropy at step 500 = 0.375368\n",
"Cross entropy at step 600 = 0.489319\n",
"Cross entropy at step 700 = 0.262653\n",
"Cross entropy at step 800 = 0.441976\n",
"Cross entropy at step 900 = 0.244338\n",
"Cross entropy at step 999 = 0.186102\n",
"Accuracy is\n",
"0.9187\n"
]
},
{
"data": {
"image/png": "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\nAABJRU5ErkJggg==\n",
"text/plain": [
"