1. TensorFlow 入门
介绍:TensorFlow是一个通过计算图的形式来表达计算的编程系统,其中的每个计算是计算图上的一个节点,而节点之间的边描述了计算之间的依赖关系。
1.1 计算模型——图
TensorFlow程序一般的两个阶段:
- 定义计算图中的所有的计算;
- 执行计算。
#计算定义阶段
import tensorflow as tf
a = tf.constant([1.0,2.0],name='a')
b = tf.constant([2.0,3.0],name = 'b')
result = a + b
#查看张量是否属于当前的计算图,因为没有特别指定,
#因此tf.get_default_graph获取的是当前默认的计算图。
print(a.graph is tf.get_default_graph())
生成不同的计算图,这样不同计算图上的张量和运算不会共享。
import tensorflow as tf
g1 = tf.Graph()
with g1.as_default():
#在计算图g1中定义变量'v',并设置初始值为0
v = tf.get_variable("v",initializer = tf.zeros_initializer(shape=[1]))
'''
tf.get_variable():可以进行值共享,是获取一个现有的值,
若这个变量不存在,则新创建一个.不过需要配合variable_scope() 使用,有一定的作用域,同时需要设置其中的参数reuse=True,即可以进行复用。
tf.variable():新建一个变量。
'''
g2 = tf.Graph()
with g2.as_default():
v = tf.get_variable("v",initializer = tf.ones_initializer(shape = [1]))
#在计算图1中读取v的值
with tf.Session(graph = g1) as sess:
tf.initialize_all_variables().run()
with tf.variable_scope("",reuse = True):
print(sess.run(tf.get_variable("v")))
'''
前面进行了图的定义,图的计算就在run中进行,
Session()的打开方式可以分为两种,
一种是如上with的形式,可以到时不用关闭,退出with的范围即可;
另一种是sess = tf.Session() sess.run() sess.close()
'''
with tf.Session(graph = g2)as sess:
tf.initialize_all_variables().run()
with tf.variable_scope("",reuse = True):
print(sess.run(tf.get_variable("v")))
1.2 数据模型——张量
张量:张量可以简单理解为多维数组,零阶张量表示标量,一阶为向量,二阶为二维数组等。但是张量中并没有真正保存数字,保存的是得到这些数字的过程。
张量中保存的三个属性:名字(name),维度(shape),类型(type).
import tensorflow as tf
if __name__ == '__main__':
a = tf.constant([1.0,2.0],name = "a")
b = tf.constant([2.0,3.0],name = "b")
result = tf.add(a,b,name = "add")
print(result)
#输出:Tensor("add:0", shape=(2,), dtype=float32)
使用张量方便获取中间结果,增加代码的可阅读性。
1.3 运行模型——会话
会话:用来执行定义好的运算,拥有并管理TensorFlow程序运行时的所有资源。
两种使用会话的模式:
方法一
#创建一个会话
sess = tf.Session()
#使用这个创建好的会话得到关心的运算的结果
sess.run(...)
#关闭会话使得本次运行中使用到的资源被释放
sess.close()
方法二
#创建一个会话,并通过Python中上下文管理器来管理这个会话
with tf.Session()as sess:
#使用这个创建好的会话计算关心的结果
sess.run(...)
#不需要调用Session.close()就可以释放资源
默认会话的生成
TensorFlow会自动生成一个默认的计算图,如果没有特殊指定,运算会自动加入这个计算图中。会话也有类似的机制,不过不会自动生成默认的会话,而是需要手动指定。
#方法一
sess = tf.Session()
with sess.as_default():
print(result.eval())
#方法二
sess = tf.Session()
print(sess.run(result))
print(result.eval(session=sess))
手动指定会比较麻烦,因此在交互式环境下提供了一种可以直接构建默认会话的函数,为tf.InteractiveSession,使用它会自动将生成的会话注册为默认会话。
sess = tf.InteractiveSession()
print(result.eval())
sess.close()
1.4 神经网络
全连接神经网络是相邻两层之间任意两个节点之间都有连接,这养的网络结构与卷积神经网络和LSTM网络结构不同。
前向传播简单例子
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
#神经网络前向传播的例子
if __name__ == '__main__':
#声明w1,w2两个变量
#seed: 随机数种子,是一个整数,当设置之后,每次生成的随机数都一样
w1 = tf.Variable(tf.random_normal([2,3],stddev = 1,seed = 1))
w2 = tf.Variable(tf.random_normal([3,1],stddev = 1,seed = 1))
#将输入的特征向量定义为一个常量
x = tf.constant([[0.7,0.9]])
#通过前向传播算法得到神经网络的输出
a = tf.matmul(x,w1)
y = tf.matmul(a,w2)
sess = tf.Session()
#对w1和w2进行初始化操作
sess.run(w1.initializer)
sess.run(w2.initializer)
'''
为方便起见,可以将所有值进行统一初始化
init_op = tf.initialize_all_variables()
sess.run(init_op)
'''
print("w1:",sess.run(w1))
print("w2:", sess.run(w2))
print("y:",sess.run(y))
sess.close()
反向传播
在每次迭代的开始,都会选择一部分训练数据作为一个batch输入到神经网络模型中,然后经过层层的计算最终会得到一个输出值,可与实际值进行比较得到损失值,然后反向传播网络根据损失值进行权值的修正使输出结果与实际值不断接近。
这里仍然是一个简单的前向传播结果,不过输入值使用了placeholder用来占位,而不是定义一个常量。
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
#神经网络前向传播的例子
if __name__ == '__main__':
#声明w1,w2两个变量
#seed: 随机数种子,是一个整数,当设置之后,每次生成的随机数都一样
w1 = tf.Variable(tf.random_normal([2,3],stddev = 1))
w2 = tf.Variable(tf.random_normal([3,1],stddev = 1))
#定义placeholder作为存放输入数据的地方,相当于定义了一个位置
#如果维度是确定的,那么给出维度可以降低出错的概率
x = tf.placeholder(tf.float32,shape=(3,2),name = "input")
a = tf.matmul(x,w1)
y = tf.matmul(a,w2)
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
print(sess.run(y,feed_dict={x:[[0.7,0.9],[0.1,0.4],[0.5,0.8]]}))
完整神经网络样例
训练神经网络的步骤:
- 定义神经网络的结构和前向传播的输出结果;
- 定义损失函数以及选择反向传播优化的算法;
- 生成会话(tf.Session)并且在训练数据上反复运行反向传输优化算法。
import tensorflow as tf
from numpy.random import RandomState
import os
from matplotlib import pyplot as plt
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
def plot(xRange,lossValue):
fig = plt.figure(figsize=(20,8),dpi = 80)
x = range(xRange)
plt.plot(x,lossValue)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.savefig("fig.png")
plt.show()
#完整神经网络解决框架
if __name__ == '__main__':
#训练数据batch的大小
batch_size = 8
#定义神经网络参数
w1 = tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2 = tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))
#在shape的一个维度上使用None可以方便使用不大的batch大小。
x = tf.placeholder(tf.float32,shape=(None,2),name = 'x-input')
y_ = tf.placeholder(tf.float32,shape=(None,1),name = 'y-input')
#定义神经网络前向传播的过程
a = tf.matmul(x,w1)
y = tf.matmul(a,w2)
#定义损失函数和反向传播的算法
#tf.clip_by_value()是指将数据y限制在一定范围之内,防止越界没有意义。
#当y小于1e-10时取1e-10,当大于1时取1
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y,1e-10,1.0)))
train_step = tf.train.AdadeltaOptimizer(0.001).minimize(cross_entropy)
#通过随机数生成一个模拟数据集
##1为随机种子,只要随机种子seed相同,产生的随机数序列就相同
rdm = RandomState(1)
dataset_size = 128
#生成128*2的随机数
X = rdm.rand(dataset_size,2)
Y = [[int(x1+x2<1)] for (x1,x2) in X]
loss_value =[]
#创建一个会话来运行TensorFlow程序
with tf.Session() as sess:
init_op = tf.initialize_all_variables()
#初始化变量
sess.run(init_op)
print(sess.run(w1))
print(sess.run(w2))
steps = 3000 #训练的轮数
for i in range(steps):
#每次选取batch_size个样本进行训练
start = (i*batch_size)%dataset_size
end = min(start+batch_size,dataset_size)
#通过选取的样本训练神经网络并更新参数
sess.run(train_step,feed_dict={x:X[start:end],y_:Y[start:end]})
total_cross_entropy = sess.run(cross_entropy, feed_dict={x: X, y_: Y})
loss_value.append(total_cross_entropy)
if i % 500 == 0:
print("After %d training step(s),cross entropy on all data is %g"%(i,total_cross_entropy))
print(sess.run(w1))
print(sess.run(w2))
plot(steps,loss_value)
因为整个网络太线性简单的原因,得到的损失函数的值也是为线性的。
2. 深层神经网络
根据上文的讨论,我们设置的前向传播算法完全符合线性模型的定义,只通过线性变换,任意层的全连接神经网络和单层神经网络模型的表达能力没有任何区别,而且它们都是线性模型。然而线性模型能够解决的问题是非常有限的。
根据图可以得到,由线性参数得到的分类类别的分界线也是为线性的,因此难以做到数据分类。这个模型只能通过直线来划分平面,对于解决以下问题还是很有帮助的,但不是适应于复杂数据。
因此,需要加入非线性元素,加入非线性激活函数后,能得到好的划分效果。
如果将每一个神经元的输出通过一个非线性函数,那么整个神经网络的模型不再是线性的了,整个非线性函数就是激活函数。常用的非线性激活函数如下。
感知机:先将输入进行加权和,然后通过激活函数得到输出,整个结构是一个没有隐藏层的神经网络,但感知机是无法模拟异或运算的。
因此,当我们加入一个隐藏层四个神经元时,就可以看到可以被很好的拟合出来。
因此我们可以得到,深层神经网络实际上有组和特征提取的功能,对于解决不易提取特征向量的问题有很大的帮助。
2.1 损失函数
神经网络的输出为one-hot值,如何评判一个输出向量和期望向量有多接近呢?交叉熵是常用的评判方法之一。
分类问题
交叉熵:刻画了两个概率分布之间的距离,是分类问题中使用比较广的一种损失函数。
给定两个概率分布和,通过来表示的交叉熵为:
SoftMax:Softmax回归本身可以作为一个学习算法来优化分类结果,但在TensorFlow中,Softmax回归的参数被去掉了,它只是一层额外的处理层,将神经网络的输出变成一个概率分布。
假设原始的神经网络输出为,那么经过Softmax回归处理之后的输出为:
原始神经网络的输出被用作置信度来生成新的输出,而新的输出满足概率分布的所有要求,这个新的输出可以理解为经过神经网络的推导,一个样例为不同类别的概率分别是多大。
因此,我们可以得到交叉熵实现代码:
corss_entropy = -tf.redece_mean(y_*tf.log(tf.clip_by_value(y,1e-10,1.0)))
#因为交叉熵一般会与softmax回归一起使用,所以TensorFlow对这两种功能进行了同一封装,并提供了tf.nn.softmax_cross_entropy_with_logits函数
corss_entropy = tf.nn.softmax_cross_entropy_with_logits(y,y_)
#在只有一个正确答案的分类问题中还可以使用tf.nn.sparse_softmax_cross_entropy_with_logits函数来进一步加速计算过程
回归问题
在回归问题中神经网络一般只有一个输出节点,这个节点的输出值就是预测值。对于回归问题,最常用的损失函数是均方误差。
mse = tf.reduce_mean(tf.square(y_-y))
自定义损失函数
在实际应用中,针对不同的问题可以选择不同的损失函数,也可以根据实际问题自定义损失函数以满足实际的需求。
import tensorflow as tf
from numpy.random import RandomState
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_more = 10
loss_less = 1
loss = tf.reduce_sum(tf.where(tf.greater(y,y_),(y-y_)*loss_less,(y_-y)*loss_more
#loss = tf.reduce_mean(tf.square(y-y_))
train_step = tf.train.AdadeltaOptimizer(0.001).minimize(loss)
#生成一个模拟数据集
rdm = RandomState(2)
dataset_size = 128
X = rdm.rand(dataset_size,2)
'''
设置回归的正确值为两个输入的和加上一个随机量,这是为了加入不可预测的噪音,
否则不同损失函数的意义不大,因为不同损失函数都会在能完全预测正确的时候最低。
一般噪音为均值为0的小量,所以这里的噪音设置为-0.05-0.05的随机数。
'''
Y = [[x1+x2+rdm.rand()/10.0-0.05]for (x1,x2) in X]
print(rdm.rand()/10.0-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) % dataset_size
end = min(start+batch_size,dataset_size)
sess.run(train_step,feed_dict={x:X[start:end],y_:Y[start:end]})
print(sess.run(w1))
2.2 神经网络优化算法
梯度下降算法主要用于优化单个参数的取值,而反向传播算法给出了一个搞笑的方式在所有参数上使用梯度下降算法,从而使神经网络模型在训练数据上的损失函数尽可能小。反向传播算法是训练神经网络的核心算法,它可以根据定义好的损失函数优化神经网络中参数的取值,从而使神经网络模型在训练数据集上的损失函数达到一个较小值。
神经网络的优化过程:
- 第一阶段先通过前向传播算法计算得到预测值,并将预测值和真实值做对比得到两者之间的差距;
- 第二阶段通过反向传播算法计算损失函数对每个参数的梯度,再根据梯度和学习率使用梯度下降算法更新每一个参数。
2.3 神经网络进一步优化
学习率
学习率控制参数更新的速度,当学习率过大时可能导致参数在极优值的两侧来回移动,如果学习率过小,虽然能保证收敛性,但会大大降低优化速度。因此,TensorFlow提供一种领过的学习率设置方法——指数衰减法。tf.train.exponential_decay函数实现了指数衰减学习率,实现了以下代码功能:
global_step = tf.Variable(0)
#通过exponential_decay函数生成学习率
learning_rate = tf.train.exponential_decay(0.1,global_step,100,0.96,staircase=True)
#使用指数衰减的学习率,在minimize函数中传入global_step将自动更新
learning_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_function,global_step = global_step)
上述设定了学习率0.1,因为指定了staircase =True,所以每训练100轮后学习率乘以0.96.
过拟合问题
为了避免过拟合问题,一个非常常用的方法是正则化。正则化的思想是在损失函数中加入刻画模型复杂程度的指标。假设用于刻画模型在训练数据上表现的损失函数为,那么在优化时不时直接优化,而是优化。
L_1正则化:(会让参数变得更加稀疏)
L_2正则化:(不会让参数变得稀疏)
采用正则化方式是希望通过限制权重的大小,使得模型不能热议拟合训练数据中的随机噪音。
下面给出一个带L_2正则化的损失函数定义:
w = tf.Variable(tf.random_normal([2,1],stddev=1,seed=1))
y = tf.matmul(x,w)
loss = tf.reduce_mean(tf.square(y_-y))+tf.contrib.layers.l2_regularizer(lambda)(w)
滑动平均模型
抱歉,没有看懂。。。
3. Mnist数字识别问题
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from matplotlib import pyplot as plt
import numpy as np
# Mnist数据集相关参数
INPUT_NODE = 784
OUTPUT_NODE = 10
#神经网络的参数
LAYER1_NODE = 500 # 隐藏层结点数
BATCH_SIZE = 100 #一个训练batch中的训练数据个数
LEARNING_RATE_BASE = 0.8 #基础的学习率
LEARNING_RATE_DECAY = 0.99 #学习率的衰减率
REGULARIZATION_RATE = 0.0001 #描述模型复杂度的正则项在损失函数中的系数
TRAINING_STEPS = 30000 #训练轮数
MOVING_AVERAGE_DECAY = 0.99 #滑动平均衰减率
def plot(valid,loss):
fig = plt.figure(figsize=(10,6),dpi = 80)
print(len(valid))
x = np.linspace(0,len(valid)*100,len(valid))
plt.plot(x,valid,label = "valid_value")
plt.plot(x,loss,label = "loss_value")
plt.xlabel("Epoch")
plt.ylabel("valid_acc/loss_value")
plt.legend()
plt.savefig("mnist_fig.png")
plt.show()
def inference(input_tensor,avg_class,weights1,biases1,weights2,biases2):
#当没有提供滑动平均类时,直接使用参数当前的取值
if avg_class == None:
#计算隐藏层的前向传播结果,这里使用了ReLU激活函数
layer1 = tf.nn.relu(tf.matmul(input_tensor,weights1)+biases1)
return tf.matmul(layer1,weights2)+biases2
else:
#首先使用avg_class.average函数计算得出变量的滑动平均值
#然后再计算相应的神经网络前向传播结果
layer1 = tf.nn.relu(tf.matmul(input_tensor,avg_class.average(weights1))+avg_class.average(biases1))
return tf.matmul(layer1,avg_class.average(weights2))+avg_class.average(biases2)
def train(mnist):
#在网络节点输入的数据,根据具体的训练数据决定
x = tf.placeholder(tf.float32,[None,INPUT_NODE],name = 'x-input')
y_ = tf.placeholder(tf.float32,[None,OUTPUT_NODE],name = 'y-input')
#隐藏层的参数
weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE,LAYER1_NODE],stddev=0.1))
biases1 = tf.Variable(tf.constant(0.1,shape = [LAYER1_NODE]))
#输出层的参数
weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE,OUTPUT_NODE],stddev = 0.1))
biases2 = tf.Variable(tf.constant(0.1,shape = [OUTPUT_NODE]))
# 计算输出值
y = inference(x,None,weights1,biases1,weights2,biases2)
global_step = tf.Variable(0,trainable=False)
# 给定滑动平均衰减率和训练轮数的变量,初始化滑动平均类
variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,global_step)
variable_averages_op = variable_averages.apply(tf.trainable_variables())
average_y = inference(x,variable_averages,weights1,biases1,weights2,biases2)
# 计算交叉熵作为刻画预测值和真实值之间差距的损失函数
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits = y,labels = tf.argmax(y_,1))
# 计算在当前batch中所有样例的交叉熵平均值
cross_entropy_mean = tf.reduce_mean(cross_entropy)
# 计算L2正则化损失函数
regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
# 计算模型的正则化损失,一般只计算神经网络边上权重的正则化损失,而不使用偏置项
regularization = regularizer(weights1)+regularizer(weights2)
loss = cross_entropy_mean + regularization
#设置指数衰减的学习率
learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE,global_step,mnist.train.num_examples,LEARNING_RATE_DECAY)
#参数优化
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step = global_step)
#在训练神经网络模型时,每过一遍数据急需要通过反向传播更新网络参数,又要更新每一个参数的滑动平均值。
with tf.control_dependencies([train_step,variable_averages_op]):
train_op = tf.no_op(name='train')
correct_prediction = tf.equal(tf.argmax(average_y,1),tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
#定义损失值和验证集上的准确率
lossValue = []
validValue = []
#初始化会话并开始训练过程
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
# 初始化变量
sess.run(init_op)
#划分验证数据,一般在神经网络的训练过程中会通过验证数据来大致判断停止的条件和评判训练结果
validate_feed = {x:mnist.validation.images,y_:mnist.validation.labels}
#准备测试数据,作为模型优劣的最后评价标准
test_feed = {x:mnist.test.images,y_:mnist.test.labels}
# 迭代地训练神经网络
for i in range(TRAINING_STEPS):
#每1000轮输出以此在验证集上的测试结果
if i%100 == 0:
'''
为了计算方便,本样例程序没有将验证数据划分为更小的batch。
当神经网络模型比较复杂或者验证数据比较大时,太大的batch会导致计算时间过长甚至发生内存溢出的错误
'''
validate_acc,loss_value = sess.run([accuracy,loss],feed_dict=validate_feed)
lossValue.append(loss_value)
validValue.append(validate_acc)
print("i:",str(i))
#print("After %d training step(s),validation accuracy using average model is %g"%(i,validate_acc))
# 产生这一轮使用的一个batch的训练数据,并运行训练过程
xs,ys = mnist.train.next_batch(BATCH_SIZE)
sess.run(train_op,feed_dict={x:xs,y_:ys})
#在训练结束之后,在测试数据集上检测神经网络模型的最终正确率
test_acc = sess.run(accuracy,feed_dict=test_feed)
print("After %d training step(s),test accuracy using average model is %g" %(TRAINING_STEPS,test_acc))
print("vaildValue:",str(validValue))
print("lossValue:", str(lossValue))
#画出验证集准确率和损失值
plot(validValue,lossValue)
def main(argv =None):
#声明处理MNIST数据集的类,这个类在初始化时自动下载数据
mnist = input_data.read_data_sets("T:\BaiduNetdiskDownload\Dataset\MnistDataset\\",one_hot=True)
train(mnist)
if __name__ == '__main__':
#提供了一个函数入口,默认执行函数main,也可以设置tf.app.run(main=test)
tf.app.run()
运行结果如下,可以得到验证集的准确率和损失值曲线:
最终在测试集上的准确率为:
After 30000 training step(s),test accuracy using average model is 0.984.
最后,再对变量进行管理,模型进行持久化可以得到更好的数字识别代码,详见数字识别
4. 卷积神经网络
卷积神经网络可以很好的用于图像的识别,比如在LeNet-5可以很好的处理数字识别问题,相比一般的神经网络,其准确率可以达到98.4%。下面的正则表达式总结了一些经典的用于图片分类问题的卷积神经网络架构:
输入层\rightarrow(卷积层+\rightarrow池化层?)+\rightarrow全连接层+
LeNet-5可以表示为:
输入层\rightarrow卷积层\rightarrow池化层\rightarrow卷积层\rightarrow池化层\rightarrow全连接层\rightarrow全连接层\rightarrow输出层。
卷积神经网络算法用于手写数字识别
在进行训练时,训练集上的准确率和损失值如图。
4.2 Inception-v3模型
Inception结构是一种和LeNet-5结构完全不同的卷积神经网络结构。在LeNet-5中,不同卷积层通过串联的方式连接在一起,而Inception-v3模型中的Inception结构是将不同的卷积层通过并联的方式结合在一起。
4.3 迁移学习
所谓迁移学习,就是将一个问题上训练好的模型通过简单的调整使其适用于一个新的问题。例如下文介绍利用ImageNet数据集上训练好的Inception-v3模型来解决一个新的图像分类问题。可以保留训练好的Inception-v3模型中所有卷积层的参数,知识替换最后一层全连接层,在最后这一层全连接层之间的网络层称之为瓶颈层。可以认为瓶颈层输出的节点向量可以被作为任何图像的一个更加精简且表达能力更强的特征向量。在新的数据集上,可以直接利用这个训练好的神经网络对图像进行特征提取,然后再将提取到的特征向量作为输入来训练一个新的单层全连接神经网络处理新的分类问题。
使用谷歌训练好的模型进行迁移学习用于花的识别,inception-v3 迁移学习-花卉识别
传送门:
- 花卉数据集
-
Inception-v3模型
在测试集上的损失值和准确率如图。
5. 图像数据处理
5.1 TensorFlow图像数据处理
TensorFlow提供了几类图像处理函数,下面进行介绍。
5.1.1 图像编码处理
一张RGB色彩模式的图像可以看成一个三维矩阵,矩阵中的每一个数表示了图像上不同位置,不同颜色的亮度。然而图像在存储时并不是直接记录这些矩阵中的数字,而是记录经过压缩编码之后的结果。所以要将一张图像还原成一个三维矩阵,需要解码的过程。TensorFlow提供了对JPEG和png格式图像的编码/解码函数。
import matplotlib.pyplot as plt
import tensorflow as tf
image_raw_data = tf.gfile.FastGFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\1.jpeg",'rb').read()
with tf.Session() as sess:
img_data = tf.image.decode_jpeg(image_raw_data)
print(img_data.eval())
plt.imshow(img_data.eval())
plt.show()
#将数据的类型转化成实数方便下面的样例程序对图像进行处理
img_data = tf.image.convert_image_dtype(img_data,dtype = tf.uint8)
#将表示一张图像的三维矩阵重新按照JPEG格式编码存入文件中。
#打开这张图像,可以得到和原始图像一样的图像
encoded_image = tf.image.encode_jpeg(img_data)
with tf.gfile.GFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\output.jpeg",'w') as f:
f.write(encoded_image.eval())
5.1.2 图像大小调整
TensorFlow提供了四种不同的方法用于图像的大小调整,并且将它们封装到了tf.image.resize_images函数。
import matplotlib.pyplot as plt
import tensorflow as tf
image_raw_data = tf.gfile.FastGFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\1.jpeg",'rb').read()
with tf.Session() as sess:
img_data = tf.image.decode_jpeg(image_raw_data)
#将数据的类型转化成实数方便下面的样例程序对图像进行处理
img_data = tf.image.convert_image_dtype(img_data,dtype = tf.uint8)
resized = tf.image.resize_images(img_data,[1000,1000], method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
plt.imshow(resized.eval())
plt.show()
encode_image = tf.image.encode_jpeg(img_data)
with tf.gfile.GFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\Nearest.jpeg",'w') as f:
f.write(encode_image.eval())
图像大小调整算法有多种,分别是双线性插值法、最近邻居法、双三次插值法以及面积插值法,通过调整ResizeMethod的值可进行不同方法的选择。不同算法调整出来的结果会有细微差别,但不会相差太远。
import matplotlib.pyplot as plt
import tensorflow as tf
image_raw_data = tf.gfile.FastGFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\1.jpeg",'rb').read()
with tf.Session() as sess:
img_data = tf.image.decode_jpeg(image_raw_data)
#将数据的类型转化成实数方便下面的样例程序对图像进行处理
img_data = tf.image.convert_image_dtype(img_data,dtype = tf.uint8)
#resized = tf.image.resize_images(img_data,[1000,1000], method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
#进行图片的填充
resize1 = tf.image.resize_image_with_crop_or_pad(img_data,200,200)
resize2 = tf.image.resize_image_with_crop_or_pad(img_data,400,400)
encode_image1 = tf.image.encode_jpeg(resize1)
encode_image2 = tf.image.encode_jpeg(resize2)
with tf.gfile.GFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\resize1.jpeg",'w') as f:
f.write(encode_image1.eval())
with tf.gfile.GFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\resize2.jpeg",'w') as f:
f.write(encode_image2.eval())
5.1.3 图像翻转
import matplotlib.pyplot as plt
import tensorflow as tf
image_raw_data = tf.gfile.FastGFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\1.jpeg",'rb').read()
with tf.Session() as sess:
img_data = tf.image.decode_jpeg(image_raw_data)
#将数据的类型转化成实数方便下面的样例程序对图像进行处理
img_data = tf.image.convert_image_dtype(img_data,dtype = tf.uint8)
#resized = tf.image.resize_images(img_data,[1000,1000], method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
#上下翻转
resize1 = tf.image.flip_up_down(img_data)
#左右翻转
resize2 = tf.image.flip_left_right(img_data)
# 沿对角线翻转
resize3 = tf.image.transpose_image(img_data)
# 以一定概率上下翻转
resize4 = tf.image.random_flip_up_down(img_data)
#以一定概率左右翻转
resize5 = tf.image.random_flip_left_right(img_data)
encode_image1 = tf.image.encode_jpeg(resize1)
encode_image2 = tf.image.encode_jpeg(resize2)
encode_image3 = tf.image.encode_jpeg(resize3)
encode_image4 = tf.image.encode_jpeg(resize4)
encode_image5 = tf.image.encode_jpeg(resize5)
with tf.gfile.GFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\flip1.jpeg",'w') as f:
f.write(encode_image1.eval())
with tf.gfile.GFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\flip2.jpeg",'w') as f:
f.write(encode_image1.eval())
with tf.gfile.GFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\flip3.jpeg",'w') as f:
f.write(encode_image1.eval())
with tf.gfile.GFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\flip4.jpeg",'w') as f:
f.write(encode_image1.eval())
with tf.gfile.GFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\flip5.jpeg",'w') as f:
f.write(encode_image1.eval())
5.1.4 图像色彩调整
import matplotlib.pyplot as plt
import tensorflow as tf
image_raw_data = tf.gfile.FastGFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\1.jpeg",'rb').read()
with tf.Session() as sess:
img_data = tf.image.decode_jpeg(image_raw_data)
#将数据的类型转化成实数方便下面的样例程序对图像进行处理
img_data = tf.image.convert_image_dtype(img_data,dtype = tf.uint8)
# 调整图像亮度
adjusted1 = tf.image.adjust_brightness(img_data,-0.5)
# 调整图像对比度
adjusted2 = tf.image.adjust_contrast(img_data,-5)
# 调整图像的色相
adjusted3 = tf.image.adjust_hue(img_data,0.1)
# 调整图像的饱和度
adjusted4 = tf.image.adjust_saturation(img_data,-5)
# 将图像进行标准化,即将图像上的亮度均值变为0,方差变为1.
adjust = tf.image.per_image_standardization(img_data)
encode_image1 = tf.image.encode_jpeg(adjust.eval())
with tf.gfile.GFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\standlize.jpeg",'w') as f:
f.write(encode_image1.eval())
5.1.5 处理标注框
在很多图像识别的数据集中,图像中需要关注的物体通常会被标注框圈出来,展示以下如何通过函数在图像中加入标注框。
import matplotlib.pyplot as plt
import tensorflow as tf
image_raw_data = tf.gfile.FastGFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\1.jpeg",'rb').read()
with tf.Session() as sess:
img_data = tf.image.decode_jpeg(image_raw_data)
#img_data = tf.image.resize_images(img_data,[600,600],method =tf.image.ResizeMethod.NEAREST_NEIGHBOR)
#将数据的类型转化成实数方便下面的样例程序对图像进行处理.
# 并且tf.image.drow_bounding_boxes要求输入是四维数据,因此转化为四维
batched = tf.expand_dims(tf.image.convert_image_dtype(img_data,dtype = tf.float32),0)
# 这是相对数据,定位的坐标点是左上角和右下角
boxes = tf.constant([[[0.05,0.05,0.9,0.7]]])
adjust = tf.squeeze(tf.image.draw_bounding_boxes(batched,boxes),0)
encode_image1 = tf.image.encode_jpeg(tf.image.convert_image_dtype(adjust,dtype = tf.uint8))
with tf.gfile.GFile("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\box.jpeg",'w') as f:
f.write(encode_image1.eval())
5.2 多线程输入数据处理框架
5.2.1 队列与多线程
展示如何操作一个队列
import tensorflow as tf
#先进先出队列
q = tf.FIFOQueue(2,"int32")
# 初始化队列中的元素
init = q.enqueue_many(([0,10],))
#出队列
x = q.dequeue()
y = x + 1
q_inc = q.enqueue([y])
with tf.Session() as sess:
init.run()
for _ in range(5):
#运行q_inc保证不断有元素进队列,即运行完整个进出队列的流程
v,_ = sess.run([x,q_inc])
print(v)
在TensorFlow中,队列不仅仅是一种数据结构,还是异步计算张量取值的一个重要机制。比如多个线程可以同时向一个队列中写元素,或者同时读取一个队列中的元素。
import tensorflow as tf
import numpy as np
import threading
import time
#tf,Coordinator主要用于协同多个线程一起停止
#线程中运行的程序,整个程序每隔1秒判断是否需要停止并打印自己的ID
def MyLoop(coord,worker_id):
#使用tf.Coordinator类提供的协同工具判断当前线程是否需要停止
while not coord.should_stop():
#随机停止所有的线程
if np.random.rand()<0.1:
print("Stop from id:%d\n"%worker_id)
coord.request_stop()
else:
#打印当前线程的id
print("Working on id:%d\n"%worker_id)
time.sleep(1)
#声明一个tf.trarin.Coordinator类来协同多个线程
coord = tf.train.Coordinator()
#声明创建5个线程
threads = [threading.Thread(target = MyLoop,args = (coord,i,)) for i in range(5)]
#启动所有的线程
for t in threads:
t.start()
#等待所有线程退出
coord.join(threads)
tf.QueueRunner主要用于启动多个线程来操作同一个队列,启动的这些线程可以通过tf.Coordinator类来统一管理。
import tensorflow as tf
#声明一个先进先出队列,最多为100个元素
queue = tf.FIFOQueue(100,"float")
#定义队列中的入队操作
enqueue_op = queue.enqueue([tf.random_normal([1])])
#使用tf.train.QueueRunner来创建多个线程运行队列的入队操作
#tf.train.QueueRunner的第一个参数给出了被操作的队列
#表示需要启动5个线程,每个线程中运行的都是enqueue_op操作
qr = tf.train.QueueRunner(queue,[enqueue_op]*5)
#将定义过的QueueRunner加入TensorFlow计算图上指定默认的集合
tf.train.add_queue_runner(qr)
#定义出队操作
out_tensor = queue.dequeue()
with tf.Session() as sess:
coord = tf.train.Coordinator()
#tf.train.start_queue_runners函数会默认启动tf.GraphKeys.QUEUE_RUNNERS集合中所有的QueueRunner
threads = tf.train.start_queue_runners(sess = sess,coord = coord)
#获取队列中的取值
for _ in range(3):
print(sess.run(out_tensor)[0])
#停止所有的线程
coord.request_stop()
coord.join(threads)
5.2.2 输入数据处理框架
import tensorflow as tf
#创建文件列表,并通过文件列表创建输入文件队列。
#在调用输入数据处理流程前,需要统一所有原始数据的格式并将它们存储到TFRecord中
files = tf.train.match_filenames_once\
("F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\imageProcessing\\fileDequeue\\file_pattern-*")
filename_queue = tf.train.string_input_producer(files,shuffle = False)
#解析TFRecord文件中的数据
reader = tf.TFRecordReader()
_,serialized_example = reader.read(filename_queue)
features = tf.parse_single_example(
serialized_example,
features = {
'image':tf.FixedLenFeature([],tf.string),
'label':tf.FixedLenFeature([],tf.int64),
'height': tf.FixedLenFeature([], tf.int64),
'width': tf.FixedLenFeature([], tf.int64),
'channels': tf.FixedLenFeature([], tf.int64),
}
)
image,label = features['image'],features['label']
height,width = features['height'],features['width']
channels = features['channels']
#从原始图像数据解析出像素矩阵,并根据图像尺寸还原图像
decoded_image = tf.decode_raw(image,tf.uint8)
decoded_image.set_shape([height,width,channels])
#定义神经网络输入层图片的大小
image_size = 299
#preprocess_for_train为图像预处理程序
distorted_image = preprocess_for_train(decoded_image,image_size,image_size,None)
#将处理后的图像和标签数据通过tf.trainshuffle_batch整理成神经网络训练需要的batch
min_after_dequeue = 10000
batch_size = 100
capacity = min_after_dequeue+3*batch_size
image_batch,label_batch = tf.train.shuffle_batch([distorted_image,label],batch_size=batch_size,
capacity = capacity,min_after_dequeue=min_after_dequeue)
#定义神经网络的结构以及优化过程
logit = inference(image_batch)
loss = calc_loss(logit,label_batch)
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)
#声明会话并运行神经网络的优化过程
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess,coord=coord)
#神经网络的训练过程
for i in range(TRAINING_ROUNDS):
sess.run(train_step)
#停止所有线程
coord.request_stop()
coord.join(threads)
6. 循环神经网络
循环神经网络中的状态是通过一个向量来表示的,这个向量的维度也成为循环神经网络隐藏层的大小,假设其为h。循环体中的神经网络的输入有两个部分,一部分为上一时刻的状态,另一部分为当前时刻的输入样本。假设输入向量的维度为x,循环体的全连接层神经网络的大小为。也就是将上一时刻的状态与当前时刻的输入拼接成一个大的向量作为循环体中神经网络的输入。
因为该神经网络的输出为当前时刻的状态,于是输出层的节点个数也为h,循环体中的参数个数为个。为了将当前时刻的状态转化为最终的输出,还需要另外一个全连接神经网络来完成这个过程,这类似于卷积神经网络中最后的全连接层。不同时刻用于输出的全连接神经网络中的参数也是一致的。
在得到循环神经网络的前向传播结果之后,可以和其他神经网络类似地定义损失函数。循环神经网络唯一的区别在于因为它每个时刻都有一个输出,所以循环神经网络的总损失为所有时刻(或者部分时刻)上的损失函数的总和。
6.1 循环神经网络前向传播
下面用一个简单的例子模拟一下循环神经网络的前向传播过程。
import numpy as np
#循环神经网络前向传播的过程
x = [1,2]
state = [0.0,0.0]
#分开定义不同输入部分的权重以方便操作
w_cell_state = np.asarray([[0.1,0.2],[0.3,0.4]])
w_cell_input = np.asarray([0.5,0.6])
b_cell = np.asarray([0.1,-0.1])
#定义用于输出的全连接层参数
w_output = np.asarray([[1.0],[2.0]])
b_output = 0.1
#按照时间顺序执行循环神经网络的前向传播过程
for i in range(len(x)):
# 计算循环体中的全连接层神经网络
before_activation = np.dot(state,w_cell_state)+x[i]*w_cell_input+b_cell
state = np.tanh(before_activation)
#根据当前时刻状态计算最终输出
final_output = np.dot(state,w_output)+b_output
#输出每个时刻的信息
print("before activation:",before_activation)
print("state:",state)
print("output:",final_output)
输出:
before activation: [0.6 0.5]
state: [0.53704957 0.46211716]
output: [1.56128388]
before activation: [1.2923401 1.39225678]
state: [0.85973818 0.88366641]
output: [2.72707101]
理论上循环神经网络可以支持任意长度的序列,然而在实际中,如果序列过长会导致优化时出现梯度消散的问题,所以实际中一般会规定一个最大长度,挡序列长度超过规定长度之后会对序列进行截断。
6.2 长短时记忆网络结构
在一般的场景中,当前预测位置和相关信息之间的文本间隔有可能变得很大,当这个间隔不断增大时,简单循环神经网络可能会丧失学习到距离如此远的信息的能力。或者在复杂语言场景中,有用信息的间隔有大有小、长短不一,循环神经网络的性能也会受到限制。
LSTM通过“遗忘门”和“输入们”,可以更加有效的决定哪些信息应该被遗忘,哪些信息应该得到保留。遗忘门通过上一时刻状态、当前输入以及上一时刻的输出决定那些信息被遗忘,输入们同样根据三种类型的数据决定哪些信息补充到最新的记忆用以更新细胞状态。输出们根据当前状态,上一时刻的输出以及这一时刻的输入来决定该时刻的输出。
注:遗忘与更新是根据Sigmoid函数来实现的。
以下展示LSTM结构的循环神经网络的前向传播过程。
#TensorFlow中实现使用LSTM结构的循环神经网络的前向传播过程
import tensorflow as tf
'''
定义一个LSTM结构,在TensorFlow中通过一句简单的命令就可以实现一个完整的LSTM结构。
LSTM中使用的变量也会在该函数中自动被声明
'''
lstm = tf.nn.rnn_cell.BasicLSTMCell(lstm_hidden_size)
'''
将LSTM中的状态初始化为全0数组。
类似于其他神经网络,在优化循环神经网络时,每次使用一个batch的训练样本。
'''
state = lstm.zero_state(batch_size,tf.float32)
#定义损失函数
loss = 0.0
'''
理论上循环神经网络可以处理任意长度的序列,但是在训练时为了避免梯度消散的问题,
会规定一个最大的序列长度,用num_steps来表示这个长度
'''
for i in range(num_steps):
if i>0:
#在第一个时刻声明LSTM结构中使用的变量,之后的时刻都需要复用
tf.get_variable_scope().reuse_variables()
#每一步处理时间序列中的一个时刻,将当前输入和前一时刻状态传入定义的LSTM结构可以得到
#当前LSTM结构的输出lstm_output和更新后的状态
lstm_output,state = lstm(current_input,state)
#将当前时刻LSTM结构的输出传入一个全连接层得到最后的输出
final_output = fully_connected(lstm_output)
# 计算当前时刻输出的损失
loss+ = calc_loss(final_output,expected_output)
6.3 循环神经网络的变种
双向循环网络:双向循环神经网络的主题结构就是两个单向循环神金网络的结合。在每个时刻t,输入会同时提供给这两个方向相反的循环神经网络,而输出则是由这两个单向循环神经网络共同决定。
深层循环神经网络(deepRNN):为了增强模型的表达能力,可以将每个时刻上的循环体重复多次。深层循环神经网络在每个时刻上将循环体结构复制多次,和卷积神经网络类似,每一层的循环体中的参数是一致的,而不同层中的参数可以不同。
以下展示实现deepRNN
import tensorflow as tf
lstm = tf.nn.rnn_cell.BasicLSTMCell(lstm_size)
#实现深层RNN每一个时刻的前向传播过程
#number_of_layers表示有多少层
stacked_lstm = tf.nn.rnn_cell.MultiRNNCell([lstm]*number_of_layers)
#通过zero_state来获取初始状态
state = stacked_lstm.zero_state(batch_size,tf.float32)
#计算每一个时刻的前向传播结果
for i in range(len(num_steps)):
if i> 0:
tf.get_variable_scope().reuse_variables()
stacked_lstm_output,state = stacked_lstm(current_input,state)
final_output = fully_connected(stacked_lstm_output)
loss += calc_loss(final_output,expected_output)
循环神经网络的dropout
与卷积神经网络只在最后的全连接层中使用dropout类似,循环神经网络也只在不同层循环体结构之间使用dropout,而不在同一层的循环体结构之间使用。
lstm = rnn_cell.BasicLSTMCell(lstm_size)
#使用DropoutWrapper类来实现dropout功能。
#该类通过两个参数来控制dropout的概率,
#一个参数为input_keep_prob,它可以用来控制输入的dropout概率
#另一个为output_keep_prob,用来控制输出的dropout的概率
dropout_lstm = tf.nn.rnn_cell.DropoutWrapper(lstm,output_keep_prob=0.5)
#在使用了dropout的基础上定义
stacked_lstm = rnn_cell.MultiRNNCell([dropout_lstm]*number_of_layers*)
6.4 循环神经网络样例应用
6.4.1 自然语言建模
PTB(Penn Treebank Dataset)是语言模型学习中最广泛使用的数据集,这里使用它利用RNN实现语言模型。
这里选用三个文件,data文件下的:
- ptb.test.txt
- ptb.train.txt
- ptb.valid.txt
import numpy as np
import tensorflow as tf
import reader
data_path = "F:\PythonProject\MachineLearning\MLProject\\NER\\NER-master\\test\RNN\LSTM_NLP\data"
hidden_size = 200 #隐藏层规模
num_layers = 2 #deep RNN中LSTM结构的层数
vocas_size = 10000 #词典规模,10000个单词
learning_rate = 1.0 #学习率
train_batch_size = 20 #训练数据的batch大小
train_num_step = 35 #训练数据的截断长度
#在测试时不需要使用截断,所以可以将测试数据看成一个超长的序列
eval_batch_size = 1 #测试数据batch的大小
eval_num_step = 1 #测试数据截断长度
num_epoch = 2 #使用训练数据的轮数
keep_prob = 0.5 #节点不被dropout 的概率
max_grad_norm =5 #用于控制梯度膨胀的参数
#通过一个PTBModel类来描述模型,这样方便维护RNN中的状态
class PTBMOdel(object):
def __init__(self,is_training,batch_size,num_steps):
#记录使用的batch大小和截断长度
self.batch_size = batch_size
self.num_steps = num_steps
#定义输入层,维度为batch_size*num_steps
self.input_data = tf.placeholder(tf.int32,[batch_size,num_steps])
#定义预期输出
self.targets = tf.placeholder(tf.int32,[batch_size,num_steps])
#定义LSTM结构且使用dropout的deepRNN
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(hidden_size)
if is_training:
lstm_cell = tf.nn.rnn_cell.DropoutWrapper(lstm_cell,output_keep_prob=keep_prob)
cell = tf.nn.rnn_cell.MultiRNNCell([lstm_cell]*num_layers)
#初始化最初的状态
self.initial_state = cell.zero_state(batch_size,tf.float32)
print(self.initial_state)
#讲单词ID转换为词向量
embedding = tf.get_variable("embedding",[vocas_size,hidden_size])
inputs = tf.nn.embedding_lookup(embedding,self.input_data)
#只在训练时使用dropout,这里没有太理解,这里的dropout是在神经网络的哪个位置起作用
if is_training:
inputs = tf.nn.dropout(inputs,keep_prob)
#定义输出列表。将不同时刻LSTM结构的输出收集起来,在通过一个全连接层得到最终的输出
outputs = []
#state存储不同batch中LSTM状态,将其初始化为0
state = self.initial_state
with tf.variable_scope("RNN"):
for time_step in range(num_steps):
if time_step > 0 :
tf.get_variable_scope().reuse_variables()
cell_output,state = cell(inputs[:,time_step,:],state)
outputs.append(cell_output)
print(outputs)
output = tf.reshape(outputs,[-1,hidden_size])
weight = tf.get_variable("weight",[hidden_size,vocas_size])
bias = tf.get_variable("bias",[vocas_size])
logits = tf.matmul(output,weight)+bias
#定义交叉熵损失函数
#该函数可以计算一个序列的交叉熵的和
loss = tf.contrib.legacy_seq2seq.sequence_loss_by_example(
[logits], #预测的结果
[tf.reshape(self.targets,[-1])], #期待的正确答案,压缩为以为数组
#损失的权重,这里设置为1,表示不同batch和不同时刻的重要程度是一样的
[tf.ones([batch_size*num_steps],dtype = tf.float32)]
)
self.cost = tf.reduce_sum(loss)/batch_size
self.final_state = state
#在训练时执行反向传播操作
if not is_training:
return
trainable_variables = tf.trainable_variables()
#通过clip_by_global_norm控制提的的大小,避免剃度膨胀
grads,_ = tf.clip_by_global_norm(tf.gradients(self.cost,trainable_variables),max_grad_norm)
#定义优化方法
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
#定义训练步骤
self.train_op = optimizer.apply_gradients(zip(grads,trainable_variables))
#使用给定的模型model在数据data上运行train_op并返回在全部数据上的perplexity值
def run_epoch(session,model,data_queue,train_op,output_log,epoch_size):
#计算perplexity的辅助变量
total_costs = 0.0
iters = 0
state = session.run(model.initial_state)
for step in range(epoch_size):
#生成输入和答案
feed_dict = {}
x,y = session.run(data_queue)
feed_dict[model.input_data] = x
feed_dict[model.targets] = y
for i,(c,h) in enumerate(model.initial_state):
feed_dict[c] = state[i].c
feed_dict[h] = state[i].h
#计算损失值,交叉熵损失函数计算的是下一个单词为给定单词的概率
cost,state,_ = session.run([model.cost,model.final_state,train_op],feed_dict=feed_dict)
#计算perplexity值
total_costs += cost
iters +=model.num_steps
if output_log and step % 100 ==0:
print("After %d steps,perplexity is %.3f"%(step,np.exp(total_costs/iters)))
#返回给定数据上的perplexity值
return np.exp(total_costs/iters)
def main(_):
train_data,valid_data,test_data,_ = reader.ptb_raw_data(data_path)
train_data_len = len(train_data)
train_batch_len = train_data_len // train_batch_size
train_epoch_size = (train_batch_len-1)//train_num_step
valid_data_len = len(valid_data)
valid_batch_len = valid_data_len // eval_batch_size
valid_epoch_size = (valid_batch_len-1)//eval_num_step
test_data_len = len(test_data)
test_batch_len = test_data_len // eval_batch_size
test_epoch_size = (test_batch_len-1)//eval_num_step
#定义初始化函数
initializer = tf.random_uniform_initializer(-0.05,0.05)
#定义训练用的循环神经网络
with tf.variable_scope("language_model",reuse= None,initializer=initializer):
train_model = PTBMOdel(True,train_batch_size,train_num_step)
#定义用于评测的循环神经网络
with tf.variable_scope("language_model",reuse = True,initializer=initializer):
eval_model = PTBMOdel(False,eval_batch_size,eval_num_step)
#生成数据队列,必须放在开启多线程之前
train_queue = reader.ptb_producer(train_data,train_model.batch_size,train_model.num_steps)
valid_queue = reader.ptb_producer(valid_data,eval_model.batch_size,eval_model.num_steps)
test_queue = reader.ptb_producer(test_data,eval_model.batch_size,eval_model.num_steps)
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess,coord=coord)
# 使用训练数据训练模型
for i in range(num_epoch):
print("In iteration: %d"%(i+1))
run_epoch(sess,train_model,train_queue,train_model.train_op,True,train_epoch_size)
# 使用验证数据评测模型效果
valid_perplexity = run_epoch(sess,eval_model,valid_queue,tf.no_op(),False,valid_epoch_size)
print("Epoch:%d validation perplexity:%.3f"%(i+1,valid_perplexity))
# 最后使用测试数据进行测试模型效果
test_perplexity = run_epoch(sess,eval_model,test_queue,tf.no_op(),False,test_epoch_size)
print("Test perplexity:%.3f"%test_perplexity)
coord.request_stop()
coord.join(threads)
if __name__ == "__main__":
tf.app.run()
运行结果如下:
In iteration: 1
After 0 steps,perplexity is 10020.763
After 100 steps,perplexity is 1440.514
After 200 steps,perplexity is 1132.069
After 300 steps,perplexity is 1016.310
After 400 steps,perplexity is 952.926
After 500 steps,perplexity is 911.750
After 600 steps,perplexity is 885.571
After 700 steps,perplexity is 863.524
After 800 steps,perplexity is 842.879
After 900 steps,perplexity is 827.868
After 1000 steps,perplexity is 816.141
After 1100 steps,perplexity is 804.882
After 1200 steps,perplexity is 796.997
After 1300 steps,perplexity is 789.275
Epoch:1 validation perplexity:721.026
In iteration: 2
After 0 steps,perplexity is 822.759
After 100 steps,perplexity is 698.593
After 200 steps,perplexity is 707.386
After 300 steps,perplexity is 711.898
After 400 steps,perplexity is 712.077
After 500 steps,perplexity is 713.156
After 600 steps,perplexity is 716.350
After 700 steps,perplexity is 715.423
After 800 steps,perplexity is 711.070
After 900 steps,perplexity is 709.253
After 1000 steps,perplexity is 708.356
After 1100 steps,perplexity is 706.327
After 1200 steps,perplexity is 706.014
After 1300 steps,perplexity is 704.674
Epoch:2 validation perplexity:709.844
Test perplexity:666.194
以上结果说明:
在迭代开始时Perplexity的值为10020.763,相当于从一万个单词中随机选择下一个单词。在训练结束后,在训练数据上的perplexity的值降低到了600多个。
完整代码 RNN语言模型.
另外,由于TensorFlow版本的变动,在实现原书的代码中遇到了很多bug,解决方案参见Debug语言模型
6.4.2 sin函数预测
TFLearn对训练TensorFlow模型进行了一些封装。以下通过sin函数预测介绍TFLearn的使用
import numpy as np
import tensorflow as tf
import matplotlib as mpl
mpl.use('Agg')
from matplotlib import pyplot as plt
learn = tf.contrib.learn
from tensorflow.python.ops import rnn
hidden_size = 30
num_layers = 2
timesteps = 10
training_stepe = 10000
batch_size = 32
training_examples = 10000
testing_examples = 1000
sample_gap = 0.01
def generate_data(seq):
x = []
y = []
for i in range(len(seq)-timesteps-1):
x.append([seq[i:i+timesteps]])
y.append([seq[i+timesteps]])
return np.array(x,dtype=np.float32),np.array(x,dtype=np.float32)
def lstmCell():
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(hidden_size)
return lstm_cell
def lstm_model(x,y):
#使用多层的LSTM结构
# hidden_size的意义:输入细胞cell的大小,之后再理解一下
#lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(hidden_size)
cell = tf.nn.rnn_cell.MultiRNNCell([lstmCell() for _ in range(num_layers)])
x_ = tf.unstack(x,axis = 1)
output, _ = rnn.static_rnn(cell,x_,dtype = tf.float32)
output = output[-1]
prediction,loss = learn.models.linear_regression(output,y)
train_op = tf.contrib.layers.optimize_loss(loss,tf.contrib.framework.get_global_step(),
optimizer='Adagrad',learning_rate= 0.1)
return prediction,loss,train_op
regressor = learn.Estimator(model_fn = lstm_model)
test_start = training_examples * sample_gap
test_end = (training_examples + testing_examples) * sample_gap
train_x,train_y = generate_data(np.sin(np.linspace(0,test_start,training_examples,dtype=np.float32)))
test_x,test_y = generate_data(np.sin(np.linspace(test_start,test_end,testing_examples,dtype = np.float32)))
#调用fit 函数训练模型
regressor.fit(train_x,train_y,batch_size = batch_size,steps = training_stepe)
#使用训练好的模型对测试数据进行预测
predicted = [[pred] for pred in regressor.predict(test_x)]
#计算rmse评价指标
rmse = np.sqrt(((predicted-test_y)**2).mean(axis=0))
#print(rmse)
print("Mean Square Error is: ",rmse[0][0])
print(test_y)
predict = []
test = []
for i in range(len(predicted)):
predict.append(predicted[i][0][0])
for i in range(len(test_y)):
test.append(test_y[i,0,0])
#画图
fig = plt.figure()
plot_predicted = plt.plot(predict,label = "predicted")
plot_test = plt.plot(test,label = "real_sin")
plt.legend()
fig.savefig('sin.png')
运行结果如图,可见实际值和预测值相差无几。
完整代码地址参见 sin函数值预测
OK,TensorFlow-深度学习框架这本书就先学到这里,对TensorFlow的应用有了基本的了解,应该能着手开始看代码写代码了。书的最后两张TensorBoard可视化和TensorFlow计算加速因为现在用不到,就先不学了,在用的时候学一下即可。
TensorFlow实战Google深度学习框架-基本代码