Tensorflow 学习笔记(二): 深层神经网络
前言
本文是阅读《TensorFlow:实战Google深度学习框架》第四章提炼出来的笔记。
同时我在github找到这本书作者提供的配套源码,发现和书相比多了一些例子(code),推荐结合书一起使用!
深度学习与深层神经网络
维基百科对深度学习的精确定义:“一类通过多层非线性变换对高复杂性数据建模算法的集合”。这句话就突出神经网络的两个重要特性:
- 非线性激活函数:拟合非线性模型
- 多层神经网络:解决异或问题
感兴趣可以通过可视化的神经网络动手测试一下激活函数是线性的神经网络和只有一层的神经网络的局限性。
损失函数
神经网络模型的效果已经优化的目标都是通过损失函数来定义的。
不同问题的损失函数固然不同,适合的损失函数的选择也决定模型准确率。
接下来将用机器学习里最的经典问题回归问题和分类问题的损失函数为例,讲解如何用tensorflow编码损失函数。
关于这些经典损失函数公式可以参考博客和博客,就不啰嗦呢。
分类问题:交叉熵模型
首先实现交叉熵模型
cross_entropy = -tf.reduce_mean(
y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)) +
(1 - y_) * tf.log(tf.clip_by_value(1-y, 1e-10, 1.0))
)
这一行代码包含了四个计算过程:
tf.clip_by_value(a,min,max)
函数可以将一个张量中的数值限定在(min,max)范围。tf.log
可以对张量中所有元素依次求对数。*
乘法,这里不是矩阵的乘法(tf.matmul),而是元素之间一一对应的乘法。tf.reduce_mean
对整个矩阵内求平均值。
因为交叉熵模型一般会和softmax
回归一起使用,所以Tensorflow对这两个功能进行统一封装,提供了tf.nn.softmax_cross_entropy_with_logits()
函数。
通过以下代码实现使用了softmax回归
之后的交叉熵函数。
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels = y_ , logits = y)
其中y_
给出了标准答案,y
代表原始神经网络的输出结果。
回归问题: 均方误差(MSE,mean squared error)
与分类问题不同,回归问题是要解决对具体数值的预测,所以预测的结果往往是一个具体的数值。
所以回归问题的最常用损失函数就是直接将预测值与已有结果做差的平方: 均方误差(MSE)
Tensorflow代码如下
mse = tf.reduce_mean(tf.square(y_ - y))
自定义损失函数
Tensoflow同样也支持自定义任意的损失函数。
先介绍一个类似c++三目运算符?
的操作tf.where()
。
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * loss_more,
(y_ - y) * loss_less))
这里面tf.greater(y,y_)
比较y,y_大小,如果y大返回True,反之False。
书上举了一个预测商品时,预测多一件损失的成本值loss_less
和预测少了一件的损失的利润值loss_more
大小之间的关系对同一个神经网络最后得到参数的影响。
具体的看代码吧(懒),就不解释了。
#!/usr/bin/env python
# encoding: utf-8
'''
@author: MrYx
@email: [email protected]
@github: https://github.com/MrYxJ
@file: 第四章深层神经网络.py
@time: 18-11-13 下午5:48
'''
import tensorflow as tf
import numpy as np
from numpy.random import RandomState
# cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels = y_ , logits = y)
batch_size = 8
x = tf.placeholder(tf.float32, shape=(None, 2), name="x-input")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name='y-input')
w1 = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w1)
# 定义损失函数使得预测少了的损失大,于是模型应该偏向多的方向预测。
loss_less = 10
loss_more = 1
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * loss_more,
(y_ - y) * loss_less))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
#通过随机数生成一个模拟数据集
rdm = RandomState(1)
X = rdm.rand(128, 2)
Y = [[x1 + x2 + (rdm.rand() / 10.0 - 0.05)] for (x1, x2) in X]
#设置回归的正确值为两个输入之和加上一个随机量。之所以要加上一个随机量是为了加入不可预测的噪音
#否则不同的损失函数意义就不大,因为不同损失函数都能在完全预测正确时候最低。
#一般来说噪音为一个均值为0的小量,所以这里的噪音设置为 -0.05~0.05随机数。
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
STEPS = 5000
for i in range(STEPS):
start = (i * batch_size) % 128
end = (i * batch_size) % 128 + batch_size
sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})
if i % 1000 == 0:
print("After %d training step(s), w1 is: " % (i))
print(sess.run(w1), "\n")
print("Final w1 is: \n", sess.run(w1))
# 定义损失函数使得预测少了的损失小,于是模型应该偏向少的方向预测。
loss_less = 1
loss_more = 10
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * loss_more, (y_ - y) * loss_less))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
STEPS = 5000
for i in range(STEPS):
start = (i * batch_size) % 128
end = (i * batch_size) % 128 + batch_size
sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})
if i % 1000 == 0:
print("After %d training step(s), w1 is: " % (i))
print(sess.run(w1), "\n")
print("Final w1 is: \n", sess.run(w1))
# 定义损失函数为均方误差,会让预测结果最接近正确答案,但没有像上面在预测时候考虑一些实际情景的赋予。
loss = tf.reduce_mean(tf.square(y - y_))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
STEPS = 5000
for i in range(STEPS):
start = (i * batch_size) % 128
end = (i * batch_size) % 128 + batch_size
sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})
if i % 1000 == 0:
print("After %d training step(s), w1 is: " % (i))
print(sess.run(w1), "\n")
print("Final w1 is: \n", sess.run(w1))
第一部分loss_less=10,loss_more =1。这里得到w1值 [[1.019347 ],[1.0428089]],比[1,1]要大,因为预测少了的损失(loss_less)比预测多了(loss_more)大。
同理第二部分loss_less=1,loss_more=10,w1 = [[0.9552581],[0.9813394]],因为预测多了的损失比预测少了大。
第三部分使用了均方误差作为损失函数,w1 = [[0.9743756], [1.0243336]],实际上这个是最接近标准答案,但是却没有考虑预测可能的实际意义。
通过这个样例可以感受到,对于相同的神经网络,不同的损失函数会对训练得到的模型产生重要影响。
神经网络优化算法
梯度下降
学习率的设置
Tensorflow提供了一个灵活的学习率设置方法——指数衰减法 tf.train.exponentail_decay
。它实现了以下代码功能:
decayed_learnning_rate = learning_rate * decay_rate ^(global_step / decay_steps)
Tensorlow用法:
learning_rate = tf.train.exponential_decay(0.1 , global_step, 100 ,0.96, staircase = True)
设定初始化学习率为0.1 , 因为指定了staircase = True
,每训练100轮学习率乘以0.96。
TRAINING_STEPS = 100
global_step = tf.Variable(0)
LEARNING_RATE = tf.train.exponential_decay(0.1, global_step, 1, 0.96, staircase=True)
x = tf.Variable(tf.constant(5, dtype=tf.float32), name="x")
y = tf.square(x)
train_op = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(y, global_step=global_step)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for i in range(TRAINING_STEPS):
sess.run(train_op)
if i % 10 == 0:
LEARNING_RATE_value = sess.run(LEARNING_RATE)
x_value = sess.run(x)
print("After %s iteration(s): x%s is %f, learning rate is %f."% (i+1, i+1, x_value, LEARNING_RATE_value))
过拟合
这里可以使用Tensorflow提供的collection
优化带正则项的神经网络损失函数的写法。
下面是书上的例子。
先生成测试数据和画出图像。
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
data = []
label = []
np.random.seed(0)
# 以原点为圆心,半径为1的圆把散点划分成红蓝两部分,并加入随机噪音。
for i in range(150):
x1 = np.random.uniform(-1, 1)
x2 = np.random.uniform(0, 2)
if x1 ** 2 + x2 ** 2 <= 1:
data.append([np.random.normal(x1, 0.1), np.random.normal(x2, 0.1)])
label.append(0)
else:
data.append([np.random.normal(x1, 0.1), np.random.normal(x2, 0.1)])
label.append(1)
data = np.hstack(data).reshape(-1, 2)
label = np.hstack(label).reshape(-1, 1)
plt.scatter(data[:, 0], data[:, 1], c=np.squeeze(label),cmap="RdBu", vmin=-.2,
vmax=1.2, edgecolor="white")
plt.show()
定义一个获取权重,并自动加入正则项到损失的函数。
def get_weight(shape, lambda1):
var = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(lambda1)(var))
return var
定义神经网络。
x = tf.placeholder(tf.float32, shape=(None, 2))
y_ = tf.placeholder(tf.float32, shape=(None, 1))
sample_size = len(data)
# 每层节点的个数
layer_dimension = [2,10,5,3,1]
n_layers = len(layer_dimension)
cur_layer = x
in_dimension = layer_dimension[0]
# 循环生成网络结构
for i in range(1, n_layers):
out_dimension = layer_dimension[i]
weight = get_weight([in_dimension, out_dimension], 0.003)
bias = tf.Variable(tf.constant(0.1, shape=[out_dimension]))
#使用ReLu激活函数
cur_layer = tf.nn.elu(tf.matmul(cur_layer, weight) + bias)
in_dimension = layer_dimension[i]
y= cur_layer
# 损失函数的定义。
mse_loss = tf.reduce_sum(tf.pow(y_ - y, 2)) / sample_size #不带正则项的损失函数
tf.add_to_collection('losses', mse_loss)
loss = tf.add_n(tf.get_collection('losses')) #带正则项的损失函数
训练不带正则项的损失函数mse_loss
# 定义训练的目标函数mse_loss,训练次数及训练模型
train_op = tf.train.AdamOptimizer(0.001).minimize(mse_loss)
TRAINING_STEPS = 40000
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(TRAINING_STEPS):
sess.run(train_op, feed_dict={x: data, y_: label})
if i % 2000 == 0:
print("After %d steps, mse_loss: %f" % (i,sess.run(mse_loss, feed_dict={x: data, y_: label})))
# 画出训练后的分割曲线
xx, yy = np.mgrid[-1.2:1.2:.01, -0.2:2.2:.01]
grid = np.c_[xx.ravel(), yy.ravel()]
probs = sess.run(y, feed_dict={x:grid})
probs = probs.reshape(xx.shape)
plt.scatter(data[:,0], data[:,1], c=np.squeeze(label),
cmap="RdBu", vmin=-.2, vmax=1.2, edgecolor="white")
plt.contour(xx, yy, probs, levels=[.5], cmap="Greys", vmin=0, vmax=.1)
plt.show()
训练带正则项的损失函数loss
# 定义训练的目标函数loss,训练次数及训练模型
train_op = tf.train.AdamOptimizer(0.001).minimize(loss)
TRAINING_STEPS = 40000
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(TRAINING_STEPS):
sess.run(train_op, feed_dict={x: data, y_: label})
if i % 2000 == 0:
print("After %d steps, loss: %f" % (i, sess.run(loss, feed_dict={x: data, y_: label})))
# 画出训练后的分割曲线
xx, yy = np.mgrid[-1:1:.01, 0:2:.01]
grid = np.c_[xx.ravel(), yy.ravel()]
probs = sess.run(y, feed_dict={x:grid})
probs = probs.reshape(xx.shape)
plt.scatter(data[:,0], data[:,1], c=label,
cmap="RdBu", vmin=-.2, vmax=1.2, edgecolor="white")
plt.contour(xx, yy, probs, levels=[.5], cmap="Greys", vmin=0, vmax=.1)
plt.show()
滑动平均模型
滑动平均模型能够让模型模型不被更新得太快,进而在测试集上更健壮。在统计学中,移动平均通过建立整个数据集中的一系列子集的平均值来分析数据点的计算方法。
推荐不懂的的萌新(比如我)看看知乎上这个文章。
Tensorflow提供了tf.train.ExponentialMovingAverage
来实现滑动平均模型。初始化时需要提供一个衰减率(decay)。这个衰减率用来控制模型的更新的速度。
定义变量及滑动平均类
v1 = tf.Variable(0, dtype=tf.float32)
step = tf.Variable(0, trainable=False)
ema = tf.train.ExponentialMovingAverage(0.99, step)
maintain_averages_op = ema.apply([v1])
查看不同迭代中变量取值的变化。
with tf.Session() as sess:
# 初始化
init_op = tf.global_variables_initializer()
sess.run(init_op)
print(sess.run([v1, ema.average(v1)]))
# 更新变量v1的取值
sess.run(tf.assign(v1, 5))
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))
# 更新step和v1的取值
sess.run(tf.assign(step, 10000))
sess.run(tf.assign(v1, 10))
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))
# 更新一次v1的滑动平均值
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))