第3課: TensorFlow中的線性和邏輯回歸
線性回歸: 根據(jù)出生率預(yù)測(cè)平均壽命
讓我們從一個(gè)簡(jiǎn)單的線性回歸開始,我們將會(huì)建立一個(gè)十分簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò),它只有一層來表示自變量X和因變量Y之間的線性關(guān)系。
問題描述
我最近對(duì)可視化世界各國(guó)的出生率和平均壽命之間的關(guān)系很著迷,基本上你有越多的孩子,你的死亡年齡越小!你可以在這兒查看世界銀行的統(tǒng)計(jì)。

問題是,能否量化這種關(guān)系?換句話說,如果出生率是X平均壽命是Y,我們能否找到線性函數(shù)f使Y=f(X)?如果我們知道這種關(guān)系,那么給定出生率就可以預(yù)測(cè)平均年齡。
關(guān)于這個(gè)問題,我們將會(huì)用世界銀行的世界發(fā)展指標(biāo)數(shù)據(jù)集中的2010年的相關(guān)數(shù)據(jù),你可以從這個(gè)GitHub地址下載它。
數(shù)據(jù)描述
- Name:Birth rate - life expectancy in 2010
- X = 出生率 Type:float
- Y = 平均壽命 Type:float
- 數(shù)據(jù)個(gè)數(shù):190
方法
首先假設(shè)出生率和平均壽命之間的關(guān)系是線性的,即能找到w和b使得Y=wX+b。
為了找到w和b,我們將要在一層的神經(jīng)網(wǎng)絡(luò)上進(jìn)行反向傳播(Back Propagation),損失函數(shù)使用均方誤差。
import tensorflow as tf
import utils
DATA_FILE = "data/birth_life_2010.txt"
# Step 1: read in data from the .txt file
# data is a numpy array of shape (190, 2), each row is a datapoint
data, n_samples = utils.read_birth_life_data(DATA_FILE)
# Step 2: create placeholders for X (birth rate) and Y (life expectancy)
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.get_variable('weights', initializer=tf.constant(0.0))
b = tf.get_variable('bias', initializer=tf.constant(0.0))
# Step 4: construct model to predict Y (life expectancy from birth rate)
Y_predicted = w * X + 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_out, b_out = sess.run([w, b])
在訓(xùn)練了100個(gè)epoch之后,我們得到的均方損失為30.04,這時(shí)的w=-6.07,b=84.93。這說明出生率和平均年齡負(fù)相關(guān),但并不是說多要一個(gè)孩子減壽6年。

你可以在X和Y的關(guān)系上做其他假設(shè),比如加入二次項(xiàng):\(Y_{predicted} = wX^2 + uX + b\)
# Step 3: create variables: weights_1, weights_2, bias. All are initialized to 0
w = tf.get_variable('weights_1', initializer=tf.constant(0.0))
u = tf.get_variable('weights_2', initializer=tf.constant(0.0))
b = tf.get_variable('bias', initializer=tf.constant(0.0))
# Step 4: predict Y (number of theft) from the number of fire
Y_predicted = w * X * X + X * u + b
# Step 5: Profit!
控制流: Huber loss
看看輸出的圖片,我們看到在下方中間的位置有一些離群點(diǎn)(噪聲):它們有低出生率但是也有低平均壽命。這些點(diǎn)將擬合線拉向它們,使模型表現(xiàn)得比較差,一種削弱離群點(diǎn)影響的方法是用Huber損失。直觀上,平方損失的缺點(diǎn)是給離群點(diǎn)過大的權(quán)重,而Huber損失被設(shè)計(jì)為給予利群點(diǎn)更少的權(quán)重:
[圖片上傳失敗...(image-bf4174-1533217266248)]
為了在Tensorflow中實(shí)現(xiàn)它,我們可能想用一些Python語言的東西,比如:
if tf.abs(Y_predicted - Y) <= delta:
# do something
然而,這種方法只在TensorFlow的eager execution(下一節(jié)課會(huì)涉及)開啟時(shí)才奏效。如果我們?cè)诋?dāng)前情況下使用,TensorFlow會(huì)立即報(bào)錯(cuò):“TypeError: Using atf.Tensoras a Pythonboolis not allowed.”。我們需要用TensorFlow定義的控制流運(yùn)算,你可以在這里找到完整的api。
| Control Flow Ops | tf.count_up_to, tf.cond, tf.case, tf.while_loop, tf.group ... |
| Comparison Ops | tf.equal, tf.not_equal, tf.less, tf.greater, tf.where, ... |
| Logical Ops | tf.logical_and, tf.logical_not, tf.logical_or, tf.logical_xor |
| Debugging Ops | tf.is_finite, tf.is_inf, tf.is_nan, tf.Assert, tf.Print, ... |
為了實(shí)現(xiàn)Huber損失,我們可以用tf.greater,tf.less或者tf.cond,這里將會(huì)用tf.cond因?yàn)樗罹咭话阈浴?/p>
tf.cond(
condition,
true_fn=None,
false_fn=None,
...)
基本的意思是如果condition為True就使用true_fn,反之使用false_fn。
def huber_loss(labels, predictions, delta=14.0):
residual = tf.abs(labels - predictions)
def f1(): return 0.5 * tf.square(residual)
def f2(): return delta * residual - 0.5 * tf.square(delta)
return tf.cond(residual < delta, f1, f2)
使用Huber損失,我們得到了w: -5.883589, b: 85.124306,它和平方損失的對(duì)比圖如下:

tf.data
根據(jù)Derek Murray對(duì)tf.data的介紹,使用placeholder和feed_dicts的好處是可以將數(shù)據(jù)處理放在TensorFlow外面,這樣可以簡(jiǎn)單的用Python打亂,分批和生成隨機(jī)數(shù)據(jù)等等。壞處是這種機(jī)制可能潛在的拖慢你的程序,用戶常常在一個(gè)線程中處理他們的數(shù)據(jù),從而導(dǎo)致數(shù)據(jù)瓶頸,從而降低執(zhí)行速度。
TensorFlow也提供了隊(duì)列作為另一種處理數(shù)據(jù)的方式。它允許你處理數(shù)據(jù)流水化、線程化并減少加載數(shù)據(jù)到placeholder的時(shí)間來提高性能。然而,隊(duì)列以難以使用并容易奔潰而聞名。(譯者:如果學(xué)過操作系統(tǒng)的知識(shí)會(huì)好一些)
注意在我們的線性回歸中,我們的輸入數(shù)據(jù)存儲(chǔ)在一個(gè)名叫data的numpy數(shù)組中,每一行是一個(gè)數(shù)值對(duì)(x,y),對(duì)應(yīng)一個(gè)樣本點(diǎn)。為了將data灌入我們的TensorFlow模型,我們創(chuàng)建了兩個(gè)placeholder名叫x和y,然后在一個(gè)for循環(huán)中將數(shù)據(jù)灌入。我們當(dāng)然可以用分批數(shù)據(jù)代替單個(gè)數(shù)據(jù),但是關(guān)鍵是這種灌數(shù)據(jù)到TensorFlow的方式很慢,而且可能妨礙其它運(yùn)算的執(zhí)行。
# Step 1: read in data from the .txt file
# data is a numpy array of shape (190, 2), each row is a datapoint
data, n_samples = utils.read_birth_life_data(DATA_FILE)
# Step 2: create placeholders for X (birth rate) and Y (life expectancy)
X = tf.placeholder(tf.float32, name='X')
Y = tf.placeholder(tf.float32, name='Y')
...
with tf.Session() as sess:
...
# 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})
使用tf.data代替在非TensorFlow對(duì)象中存儲(chǔ)數(shù)據(jù),我們可以使用數(shù)據(jù)創(chuàng)建一個(gè)Dataset:
tf.data.Dataset.from_tensor_slices((x, y))
x,y都應(yīng)該是tensor,但是記住這是因?yàn)門ensorFlow和Numpy是無縫集成的,它們可以是Numpy數(shù)組。
dataset = tf.data.Dataset.from_tensor_slices((data[:,0], data[:,1]))
將dataset輸入的類型和形狀打印出來:
print(dataset.output_types) # >> (tf.float32, tf.float32)
print(dataset.output_shapes) # >> (TensorShape([]), TensorShape([]))
你也可以用TensorFlow的文件格式分析器將數(shù)據(jù)從文件灌入一個(gè)tf.data.Dataset,它們?nèi)慷己屠系腄ataReader有驚人的相似性。
-
tf.data.TextLineDataset(filenames):文件中的每一行作為一個(gè)輸入。(csv) -
tf.data.FixedLengthRecordDataset(filenames):dataset中的每條數(shù)據(jù)都相同的長(zhǎng)度。(CIFAR,ImageNet) -
tf.data.TFRecordDataset(filenames):如果你的數(shù)據(jù)以tfrecord格式存儲(chǔ)可以用這個(gè)。
例子:
dataset = tf.data.FixedLengthRecordDataset([file1, file2, file3, ...])
在我們將數(shù)據(jù)導(dǎo)入神奇的Dataset對(duì)象后,可以通過一個(gè)迭代器遍歷Dataset中的樣本,可以在這里了解迭代器。
iterator = dataset.make_one_shot_iterator()
X, Y = iterator.get_next() # X is the birth rate, Y is the life expectancy
每一次我們執(zhí)行運(yùn)算X,Y,我們會(huì)得到一個(gè)新的樣本數(shù)據(jù)。
with tf.Session() as sess:
print(sess.run([X, Y])) # >> [1.822, 74.82825]
print(sess.run([X, Y])) # >> [3.869, 70.81949]
print(sess.run([X, Y])) # >> [3.911, 72.15066]
現(xiàn)在我們可以像你之前用placeholder做的那樣用X和Y計(jì)算Y_predicted和損失。不同的是當(dāng)你執(zhí)行計(jì)算圖時(shí)不再需要向feed_dict中灌數(shù)據(jù)。
for i in range(100): # train the model 100 epochs
total_loss = 0
try:
while True:
sess.run([optimizer])
except tf.errors.OutOfRangeError:
pass
我們不得不捕捉OutOfRangeError異常是因?yàn)門ensorFlow竟然沒有自動(dòng)為我們處理它。如果我們運(yùn)行這個(gè)代碼,我們會(huì)在第一個(gè)epoch獲得非0的loss而在后面的epoch中l(wèi)oss總是為0。這是因?yàn)?code>dataset.make_one_shot_iterator()只能使用一次,在一個(gè)epoch之后迭代器到達(dá)了數(shù)據(jù)的最后,你不能重新初始化它來進(jìn)行下一個(gè)epoch。
為了在多個(gè)epoch上使用迭代器,我們使用dataset.make_initializable_iterator()創(chuàng)建迭代器,然后在每個(gè)epoch開始時(shí)重新初始化迭代器。
iterator = dataset.make_initializable_iterator()
...
for i in range(100):
sess.run(iterator.initializer)
total_loss = 0
try:
while True:
sess.run([optimizer])
except tf.errors.OutOfRangeError:
pass
使用tf.data.Dataset你分別只需要一條命令就可以對(duì)數(shù)據(jù)進(jìn)行分批、打亂、重復(fù)等操作。你也可以映射你dataset中的每個(gè)元素來將它們用指定的方法進(jìn)行變形從而創(chuàng)建新的dataset。
dataset = dataset.shuffle(1000)
dataset = dataset.repeat(100)
dataset = dataset.batch(128)
dataset = dataset.map(lambda x: tf.one_hot(x, 10))
# convert each element of dataset to one_hot vector
tf.data真的表現(xiàn)更好嗎?
為了比較tf.data和placeholder的性能,我將每個(gè)模型跑了100次然后計(jì)算每個(gè)模型的平均用時(shí)。在我的Macbook Pro 2.7Ghz Intel Core I5 cpu上,placeholder平均用時(shí)為9.0527秒,tf.data平均用時(shí)為6.1228秒。tf.data比placeholder的性能提高了32.4%
優(yōu)化器(Optimizers)
在前面的代碼中,還有兩行沒有解釋。
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001).minimize(loss)
sess.run([optimizer])
我記得第一次運(yùn)行類似代碼時(shí)非常困惑:
- 為什么optimizer在
tf.Session.run()中? - TensorFlow怎樣知道哪些參數(shù)需要更新?
optimizer是一個(gè)用來最小化loss的運(yùn)算,為了執(zhí)行這個(gè)運(yùn)算我們需要將它傳入tf.Session.run()中。當(dāng)TensorFlow執(zhí)行optimizer時(shí)它會(huì)執(zhí)行在計(jì)算圖中optimizer運(yùn)算依賴的部分,而我們可以看到optimizer依賴loss,然后loss依賴輸入X和Y以及兩個(gè)變量w和b。

從計(jì)算圖上你可以看到一個(gè)巨大的節(jié)點(diǎn)GradientDescentOptimizer,它依賴三個(gè)節(jié)點(diǎn):weights,bias和gradients。
GradientDescentOptimizer的意思是我們的參數(shù)更新策略為梯度下降,TensorFlow自動(dòng)的為我們計(jì)算梯度并更新w和b的值來最小化loss。
默認(rèn)情況下,optimizer訓(xùn)練它的目標(biāo)函數(shù)依賴的所有可訓(xùn)練的變量,如果有些變量你不想訓(xùn)練,你可以在聲明變量時(shí)設(shè)置關(guān)鍵字trainable=False。一個(gè)不需要訓(xùn)練變量的例子是global_step,它是一個(gè)在很多TensorFlow模型中用來跟蹤模型運(yùn)行了多少次的常見變量。
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.train.GradientDescentOptimizer(learning_rate) # learning rate can be a tensor
你也可以讓你的optimizer計(jì)算指定變量的梯度,你也可以修改optimizer計(jì)算的梯度,然后讓optimizer用修改過的梯度進(jìn)行優(yōu)化。
# create an optimizer.
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.1)
# compute the gradients for a list of variables.
grads_and_vars = optimizer.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.stop_gradient來阻止特定的tensor對(duì)關(guān)于特定loss的導(dǎo)數(shù)計(jì)算的貢獻(xiàn)。
stop_gradient( input, name=None )
在訓(xùn)練中需要凍結(jié)指定參數(shù)的時(shí)候這個(gè)方法十分有用,這里有一些TensorFlow官方文檔中的例子。
- 當(dāng)你訓(xùn)練一個(gè)GAN(生成對(duì)抗網(wǎng)絡(luò),Generative Adversarial Network)時(shí)對(duì)抗樣本生成過程中沒有BP發(fā)生。
- EM算法中M階段不應(yīng)該對(duì)E階段的輸出進(jìn)行BP。
optimizer類自動(dòng)的計(jì)算你的計(jì)算圖中的梯度,但是你也可以用tf.gradients顯式的計(jì)算特定的梯度。
tf.gradients(
ys,
xs,
grad_ys=None,
name='gradients',
colocate_gradients_with_ops=False,
gate_gradients=False,
aggregation_method=None,
stop_gradients=None
)
這個(gè)方法計(jì)算xs中ys相對(duì)于每個(gè)x的偏導(dǎo)數(shù)的和。ys和xs分別是一個(gè)tensor或一組tensor,grad_ys是一組持有ys接受到的梯度的tensor,長(zhǎng)度必須和ys一致。
技術(shù)細(xì)節(jié):這個(gè)方法在只訓(xùn)練模型的一部分時(shí)非常有用,例如我們可以用tf.gradients()來計(jì)算loss相對(duì)于中間層的導(dǎo)數(shù)G。然后我們用一個(gè)optimizer去最小化中間層輸出M和M+G之間的差異,這樣只更新網(wǎng)絡(luò)的前半部分。
optimizer列表
TensorFlow支持的optimizer列表在這里查看。
- tf.train.Optimizer
- tf.train.GradientDescentOptimizer
- tf.train.AdadeltaOptimizer
- tf.train.AdagradOptimizer
- tf.train.AdagradDAOptimizer
- tf.train.MomentumOptimizer
- tf.train.AdamOptimizer
- tf.train.FtrlOptimizer
- tf.train.ProximalGradientDescentOptimizer
- tf.train.ProximalAdagradOptimizer
- tf.train.RMSPropOptimizer
這里有一篇對(duì)比這些優(yōu)化算法的博客以及墻內(nèi)翻譯。
TL;DR:使用AdamOptimizer
邏輯回歸(Logistic Regression)和MNIST
讓我們?cè)赥ensorFlow中構(gòu)建一個(gè)邏輯回歸模型來解決MNIST數(shù)據(jù)分類。
MNIST(Mixed National Institute of Standards and Technology database)是一個(gè)用來訓(xùn)練大量圖像處理的流行的數(shù)據(jù)集,它是一個(gè)手寫數(shù)字的數(shù)據(jù)集。

每張圖片含有28X28個(gè)像素,你可以將它們拉伸成大小為784的一維tensor,每張圖片有一個(gè)0到9的標(biāo)簽。
TF Learn(TensorFlow的簡(jiǎn)化接口)有一個(gè)腳本讓你從楊立昆(Yann Lecun)的網(wǎng)站上加載MNIST數(shù)據(jù)集,然后劃分成訓(xùn)練集、驗(yàn)證集和測(cè)試集。
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('data/mnist', one_hot=True)
這里input_data.read_data_sets('data/mnist', one_hot=True)返回一個(gè)learn.datasets.base.Datasets,它包含三個(gè)數(shù)據(jù)集:55000條訓(xùn)練集(mnist.train),10000條測(cè)試集(mnist.test)和5000條驗(yàn)證集(mnist.validation)。你可以調(diào)用next_batch(batch_size)來讀取這些數(shù)據(jù)。
然而在現(xiàn)實(shí)生活中我們不可能都用這種現(xiàn)成的數(shù)據(jù)解析,很可能只能自己寫數(shù)據(jù)解析。我已經(jīng)在utils.py中編寫了下載和解析MNIST數(shù)據(jù)到numpy數(shù)組的代碼。
mnist_folder = 'data/mnist'
utils.download_mnist(mnist_folder)
train, val, test = utils.read_mnist(mnist_folder, flatten=True)
我們?cè)O(shè)置flatten=True是因?yàn)槲覀兿M麑D片拉伸為1維tensor,train、val和test中的每條數(shù)據(jù)都是一個(gè)Numpy元組(tuple),第一項(xiàng)是圖片數(shù)組(image),第二項(xiàng)是標(biāo)簽(label)。
train_data = tf.data.Dataset.from_tensor_slices(train)
# train_data = train_data.shuffle(10000) # if you want to shuffle your data
test_data = tf.data.Dataset.from_tensor_slices(test)
邏輯回歸模型的建造和線性回歸模型十分相似,現(xiàn)在我們有一大堆數(shù)據(jù),這里我們使用mini-batch GD:
train_data = train_data.batch(batch_size)
test_data = test_data.batch(batch_size)
下一步是建立迭代器從兩個(gè)數(shù)據(jù)集中獲取樣本,辦法是建立一個(gè)迭代器然后在要拉取數(shù)據(jù)時(shí)用相應(yīng)的數(shù)據(jù)集初始化它。
iterator = tf.data.Iterator.from_structure(train_data.output_types, train_data.output_shapes)
img, label = iterator.get_next()
train_init = iterator.make_initializer(train_data) # initializer for train_data
test_init = iterator.make_initializer(test_data) # initializer for test_data
with tf.Session() as sess:
...
for i in range(n_epochs): # train the model n_epochs times
sess.run(train_init) # drawing samples from train_data
try:
while True:
_, l = sess.run([optimizer, loss])
except tf.errors.OutOfRangeError:
pass
# test the model
sess.run(test_init) # drawing samples from test_data
try:
while True:
sess.run(accuracy)
except tf.errors.OutOfRangeError:
pass
和線性回歸相似,你可以從課程的GitHub地址中的examples/03_logreg_starter.py下載示例代碼。
Note:打亂數(shù)據(jù)可以提高性能。
現(xiàn)在讓我們看看TensorBoard:
