斯坦福CS20SI:基于Tensorflow的深度学习研究课程笔记,Lecture note3:TensorFlow上的线性回归和逻辑回归

时间:2023-02-02 08:15:10

课程笔记3:TensorFlow上的线性回归和逻辑回归

“CS 20SI: TensorFlow 深度学习研究”(cs20si.stanford.edu)
由Chip Huyen (huyenn@stanford.edu)编译
Danijar Hafner 审核

在前两个讲座中,我们已经学到了很多。这两个讲座涉及到了一些关键的概念性知识。如果这篇笔记中的某些知识你并不了解,可以回到前两个讲座中回顾,确保已经掌握了讲座中涉及到的知识如下:

  • 图表和会话

  • TF ops:常量,变量,函数

  • TensorBoard

  • 延迟加载

我们已经掌握了TensorFlow的基本原理。是的,我们就是这么快!现在让我们把这些东西放在一起看看可以做些什么。

1 TensorFlow的线性回归

我们从一个简单的线性回归实例开始。我希望你们都已经熟悉了线性回归。如果没有,你可以阅读

问题:我们经常听说保险公司使用例如一个社区的火灾和盗贼去衡量一个社区的安全程度,我的问题是,这是不是多余的,火灾和盗贼在一个社区里是否是相关的,如果相关,那么我们能不能找到他们的关系。

换句话说,我们能不能找到一个函数f,如果X是火灾数并且Y是盗贼数,是否存在Y=f(X)?

给出这个关系,如果我们有特定社区的火灾数我们能不能预测这一区域的盗贼数。

我们有美国民权委员会收集的数据集

数据集描述如下:

名称:芝加哥的火灾和盗贼
X=每1000住房单元的火灾数
Y=每1000人口的盗贼数

每对数据取自地区码不同的芝加哥的42个区域

解决办法:首先假设火灾数和盗贼数成线性关系Y=wX+b

我们需要找到参数w和b,通过平方差误差作为损失函数,写出如下程序:

import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
import xlrd

DATA_FILE = "data/fire_theft.xls"

# Step 1: read in data from the .xls file
book = xlrd.open_workbook(DATA_FILE, encoding_override="utf-8")
sheet = book.sheet_by_index(0)
data = np.asarray([sheet.row_values(i) for i in range(1, sheet.nrows)])
n_samples = sheet.nrows - 1

# Step 2: create placeholders for input X (number of fire) and label Y (number of
theft)
X = tf.placeholder(tf.float32, name="X")
Y = tf.placeholder(tf.float32, name="Y")

# Step 3: create weight and bias, initialized to 0
w = tf.Variable(0.0, name="weights")
b = tf.Variable(0.0, name="bias")

# Step 4: construct model to predict Y (number of theft) from the number of fire
Y_predicted = X * w + b

# Step 5: use the square error as the loss function
loss = tf.square(Y - Y_predicted, name="loss")

# Step 6: using gradient descent with learning rate of 0.01 to minimize loss
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001).minimize(loss)

with tf.Session() as sess:
# Step 7: initialize the necessary variables, in this case, w and b
sess.run(tf.global_variables_initializer())
# Step 8: train the model
for i in range(100): # run 100 epochs
for x, y in data:
# Session runs train_op to minimize loss
sess.run(optimizer, feed_dict={X: x, Y:y})
# Step 9: output the values of w and b
w_value, b_value = sess.run([w, b])

训练100遍后我们得到了平均平方误差1372.77701716,w=1.62071,b=16.9162,误差太大了。

斯坦福CS20SI:基于Tensorflow的深度学习研究课程笔记,Lecture note3:TensorFlow上的线性回归和逻辑回归

拟合情况并不好,如果使用二次函数 Y=wXX+uX+b 会不会更好呢?
试一下,我们只需要添加另外一个变量 u ,并且修改Y_predicted的公式。

# Step 3: create variables: weights_1, weights_2, bias. All are initialized to 0
w = tf.Variable(0.0, name="weights_1")
u = tf.Variable(0.0, name="weights_2")
b = tf.Variable(0.0, name="bias")
# Step 4: predict Y (number of theft) from the number of fire
Y_predicted = X * X * w + X * u + b
# Step 5: Profit!

10遍训练后,我们得到了平方平均损797.335975976。w,u,b=[0.0713430.010234 0.00143057]

斯坦福CS20SI:基于Tensorflow的深度学习研究课程笔记,Lecture note3:TensorFlow上的线性回归和逻辑回归

这比线性函数收敛的时间更短,但由于右边的几个异常值,仍然没有很好的拟合。使用Huber 损失代替MSE或者三次函数作为拟合函数可能会更好,你可以自己试一试。

使用Huber损失二次模型,忽略异常值的情况下我得到的结果更好:

斯坦福CS20SI:基于Tensorflow的深度学习研究课程笔记,Lecture note3:TensorFlow上的线性回归和逻辑回归

那么,我怎么知道我的模型是正确的?

1 使用相关系数R-squared

如果你不知道R-squared是什么,可以看Minitab的博客

下面是R-squared的要点:
R-squared是一种测量数据与拟合回归线的接近程度的统计上的测量方法。

它也被称为拟合优度,或者是多元回归的多元拟合优度。

R-squared 的解释为合理且简单的:它是反应变量与线性模型变化的比。R-squared=解释变异差(Explained variation)/ 总变分(Total variation)

2 在测试集上运行

我们在机器学习课上学到了所有这些都归结于验证和测试。所以第一种方法显然是在测试集上测试我们的模型。

有独立的数据集训练,验证和测试是很好的,但这意味着我们将有更少的训练数据。 有很多文献可以帮助我们解决我们没有大量数据的情况,例如k-fold交叉验证。

3 用伪造数据测试我们的模型

另一种方式是我们可以测试它的伪造数据。例如,在这种情况下,我们可以伪造一些呈已知线性关系的数据去测试模型。让我们伪造100个数据点(X,Y),使得Y~3 * X,看看我们的模型输出是否是w = 3,b = 0。

生成伪造数据:

# each value y is approximately linear but with some random noise
X_input = np.linspace(-1, 1, 100)
Y_input = X_input * 3 + np.random.randn(X_input.shape[0]) * 0.5

我们使用numpy数组为 X_input 和 Y_input 以支持之后的迭代(当我们输入占位符为X和Y时)。

斯坦福CS20SI:基于Tensorflow的深度学习研究课程笔记,Lecture note3:TensorFlow上的线性回归和逻辑回归

它拟合的很好!

启示:伪造的数据比真实的数据更容易处理,因为生成的数据更加匹配我们的模型假设。真实的数据拟合这么好是比较困难的!

分析代码:

我们的模型代码非常简单易懂,除了两行:

optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(loss)
sess.run(optimizer, feed_dict={X: x, Y:y})

我记得第一次遇到了类似于这样的代码很困惑,两个问题:

1 为什么train_op在tf.Session.run()的提取列表中?
2 TensorFlow如何知道要更新哪些变量?

我们实际上可以用tf.session.run()传递feed_dict给任何tf节点。TensorFlow将执行这些操作依赖的图部分。在这种情况下,我们看到train_op的目的是尽可能减少损失,而损失取决于变量w和b。

斯坦福CS20SI:基于Tensorflow的深度学习研究课程笔记,Lecture note3:TensorFlow上的线性回归和逻辑回归

从图中可以看出,巨型节点的梯度下降优化器取决于3个节点:权重,偏差和下降(全部自动处理)。

优化器

GradientDescentOptimizer 意味着我们的更新规则是梯度下降法。TensorFlow自动完成:更新w和b的值以最小化损失。自动求梯度令人震惊!

默认情况下,优化程序会训练所有可以实现目标函数的可修改变量。如果有不想要训练的变量,则可以在声明变量时将关键字trainable设置为False。比如一个常见的不想训练的变量global_step,这个变量可以在许多TensorFlow模型中看到,它以跟踪你运行模型的次数。

global_step = tf.Variable(0, trainable=False, dtype=tf.int32)
learning_rate = 0.01 * 0.99 ** tf.cast(global_step, tf.float32)
increment_step = global_step.assign_add(1)
optimizer = tf.GradientDescentOptimizer(learning_rate) # learning rate can be a tensor

类tf.variable的完整定义:

tf.Variable(initial_value=None, trainable=True, collections=None,
validate_shape=True, caching_device=None, name=None, variable_def=None, dtype=None,
expected_shape=None, import_scope=None)

你还可以用优化器计算特定变量的梯度,还可以自己修改优化器计算的梯度。

# create an optimizer.
optimizer = GradientDescentOptimizer(learning_rate=0.1)
# compute the gradients for a list of variables.
grads_and_vars = opt.compute_gradients(loss, <list of variables>)
# grads_and_vars is a list of tuples (gradient, variable). Do whatever you
# need to the 'gradient' part, for example, subtract each of them by 1.
subtracted_grads_and_vars = [(gv[0] - 1.0, gv[1]) for gv in grads_and_vars]
# ask the optimizer to apply the subtracted gradients.
optimizer.apply_gradients(subtracted_grads_and_vars)

更多计算梯度的方法:

优化器自动计算图中节点的导数,但是新优化器的创建者或者熟练地使用者使用如下的更低级的函数:

tf.gradients(ys, xs, grad_ys=None, name='gradients',
colocate_gradients_with_ops=False, gate_gradients=False, aggregation_method=None)

这个方法构造当y为ys,和x为xs时各自的偏导列表。ys和xs都是张量列表。grad_ys是Tensor的列表,保存y所接收的梯度。该列表必须与ys的长度相同。

技术细节:这仅在训练模型的一部分时特别有用。例如,我们可以使用tf.gradients()来获取中间层损失的导数G。然后我们使用优化器来最小化中间层输出M和M + G之间的差。这只会更新网络的下半部分。

optimizer列表:

梯度下降优化器不是TensorFlow支持的唯一更新规则。下面是TensorFlow支持的优化器列表,截至1/20/2017。这些名字是不言自明的。您可以访问官方文档了解更多:

tf.train.GradientDescentOptimizer
tf.train.AdadeltaOptimizer
tf.train.AdagradDAOptimizer
tf.train.MomentumOptimizer
tf.train.AdamOptimizer
tf.train.FtrlOptimizer
tf.train.ProximalGradientDescentOptimizer
tf.train.ProximalAdagradOptimizer
tf.train.RMSPropOptimizer

Sebastian Ruder在他的博客
做了这些optimizer的比较,如果你懒得读他的博客,这有一份总结:

“RMSprop是Adagrad的扩展,它解决了学习率急剧下降的问题。它与Adadelta相同,除了Adadelta法在参数更新规则中使用的RMS法的numerator规则。ADAM终于向RMSprop增加了偏差校正和动量。RMSprop,Adadelta和Adam是非常相似的算法,在类似的情况下表现良好。Kingma等人证明,随着梯度变得越来越少,偏差校正方面ADAM略微优于RMSprop,从而达到最终的优化。ADAM可能是最好的整体选择。”

总结:使用adamOptimizer

讨论一个问题:

我们可以使用线性回归解决现实生活中的哪些问题?你可以写一个程序快速实现吗?

2 Tensorflow实现的逻辑回归

提到线性回归就不能不提到逻辑回归,我们可以使用逻辑回归解决一个很老的问题:MNIST数据集下的分类问题。

MNIST数据库(混合国家标准与技术研究所数据库)可能是含数据最多的数据库之一。用于训练各种图像处理系统的流行数据库。它是一个手写数字的数据库。图像看起来像这样:

斯坦福CS20SI:基于Tensorflow的深度学习研究课程笔记,Lecture note3:TensorFlow上的线性回归和逻辑回归

每个图片28*28像素,拉伸为1维张量,长度为784,每一个都有一个标签,比如第一行标签为0,第二行为1……以此类推。数据集在此网站

TFLearn(tf的一个简单接口)有一个让你可以从Yan Lecun个人网站加载MNIST数据集的脚本,并且把它分为训练集,验证集和测试集。

from tensorflow.examples.tutorials.mnist import input_data
MNIST = input_data.read_data_sets("/data/mnist", one_hot=True)
One-hot encoding
In digital circuits, one-hot refers to a group of bits among which the legal combinations of
values are only those with a single high (1) bit and all the others low (0).
In this case, one-hot encoding means that if the output of the image is the digit 7, then the
output will be encoded as a vector of 10 elements with all elements being 0, except for the
element at index 7 which is 1.

MNIST是一个TensorFlow的数据集对象。它有55,000个数据点的训练数据(MNIST.train),10,000个测试数据(MNIST.test)和5,000个数据点的验证数据(MNIST.validation)。

逻辑回归模型的建立与线性回归模型非常相似。但是,现在我们有更多的数据。 我们在CS229中了解到,如果我们在每个数据点之后计算梯度,那么它会很慢。 解决这个问题的一个方法是批量处理。幸运的是,TensorFlow能很好的批处理数据。

要进行批量逻辑回归,我们只需要更改X_placeholder和Y_placeholder的维度,以便能够容纳batch_size数据点。

X = tf.placeholder(tf.float32, [batch_size, 784], name="image")
Y = tf.placeholder(tf.float32, [batch_size, 10], name="label")

当你将数据提供给占位符时,不是为每个数据点提供数据,我们可以提供batch_size数据点数。

X_batch, Y_batch = mnist.test.next_batch(batch_size)
sess.run(train_op, feed_dict={X: X_batch, Y:Y_batch})

这里有完整的实现:

import time
import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# Step 1: Read in data
# using TF Learn's built in function to load MNIST data to the folder data/mnist
MNIST = input_data.read_data_sets("/data/mnist", one_hot=True)
# Step 2: Define parameters for the model
learning_rate = 0.01
batch_size = 128
n_epochs = 25
# Step 3: create placeholders for features and labels
# each image in the MNIST data is of shape 28*28 = 784
# therefore, each image is represented with a 1x784 tensor
# there are 10 classes for each image, corresponding to digits 0 - 9.
# each label is one hot vector.
X = tf.placeholder(tf.float32, [batch_size, 784])
Y = tf.placeholder(tf.float32, [batch_size, 10])
# Step 4: create weights and bias
# w is initialized to random variables with mean of 0, stddev of 0.01
# b is initialized to 0
# shape of w depends on the dimension of X and Y so that Y = tf.matmul(X, w)
# shape of b depends on Y
w = tf.Variable(tf.random_normal(shape=[784, 10], stddev=0.01), name="weights")
b = tf.Variable(tf.zeros([1, 10]), name="bias")
# Step 5: predict Y from X and w, b
# the model that returns probability distribution of possible label of the image
# through the softmax layer
# a batch_size x 10 tensor that represents the possibility of the digits
logits = tf.matmul(X, w) + b
# Step 6: define loss function
# use softmax cross entropy with logits as the loss function
# compute mean cross entropy, softmax is applied internally
entropy = tf.nn.softmax_cross_entropy_with_logits(logits, Y)
loss = tf.reduce_mean(entropy) # computes the mean over examples in the batch
# Step 7: define training op
# using gradient descent with learning rate of 0.01 to minimize cost
optimizer =
tf.train.GradientDescentOptimizer(learning_rate=learning_rate).minimize(loss)
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
n_batches = int(MNIST.train.num_examples/batch_size)
for i in range(n_epochs): # train the model n_epochs times
for _ in range(n_batches):
X_batch, Y_batch = MNIST.train.next_batch(batch_size)
sess.run([optimizer, loss], feed_dict={X: X_batch, Y:Y_batch})
# average loss should be around 0.35 after 25 epochs

在我的Mac上运行时,用批处理的批量大小为128的模型运行一次时间在0.5秒内,而非批处理模型运行的时间则需要24秒!但是,请注意,更大的batch需要更小的epoch,因为它的更新步骤变少,详细论述

我们可以测试模型,因为我们有一个测试集。让我们看看TensorFlow如何做到这一点:

# test the model
n_batches = int(MNIST.test.num_examples/batch_size)
total_correct_preds = 0
for i in range(n_batches):
X_batch, Y_batch = MNIST.test.next_batch(batch_size)
_, loss_batch, logits_batch = sess.run([optimizer, loss, logits],
feed_dict={X: X_batch, Y:Y_batch})
preds = tf.nn.softmax(logits_batch)
correct_preds = tf.equal(tf.argmax(preds, 1), tf.argmax(Y_batch, 1))
accuracy = tf.reduce_sum(tf.cast(correct_preds, tf.float32)) # similar
to numpy.count_nonzero(boolarray) :(
total_correct_preds += sess.run(accuracy)
print "Accuracy {0}".format(total_correct_preds/MNIST.test.num_examples)

我们在10个迭代之后达到90%的准确度。我们可以从线性分类器中得到:

注意:TensorFlow具有用于MNIST的feeder(数据集解析器),但不指望具有任何数据。你应该学习如何编写自己的数据解析器。

graph在tensorboard中:

斯坦福CS20SI:基于Tensorflow的深度学习研究课程笔记,Lecture note3:TensorFlow上的线性回归和逻辑回归

下节课我们将学习如何构造自己的模型!