使用神经网络解决分类问题主要可以分为4个步骤:
- 提取问题中的实体的特征向量作为神经网络的输入;
- 定义神经网络的结构,并定义如何从神经网络的输入得到输出。即前向传播算法;
- 通过训练数据来调整神经网络中的参数取值;
- 使用训练好的神经网络来预测未知的数据。
1. 前向传播算法
不同的神经网络前向的传播方式不同,这里介绍最简单的全连接网络结构的前向传播算法。
一个神经元有多个输入和一个输出,每个神经元的输出既可以是其他神经元的输入,也可以是整个神经网络的输出。一个简单神经元的输出是所有输入的加权和,而不同输入的权重就是神经元的参数。
前向传播需要三部分的信息,一是神经网络的输入,而是神经网络的连接结构,三是神经网络的参数。通过输入与参数的加权和,逐层向前传播,直至输出层。TensorFlow的神经网络前向传播过程如下:
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
其中,tf.matmul实现了矩阵乘法的功能。
2. 神经网络参数与TensorFlow变量
神经网络的参数是实现分类或者回归的重要部分,TensorFlow定义矩阵变量的方法如下:
weights = tf.Variable(tf.random_normal([2,3],stddev=2))
这段代码调用了TensorFlow变量的声明函数tf.Variable。tf.random_normal([2,3],stddev=2)会产生一个2行3列的矩阵,矩阵中元素是均值为0,标准差为2的随机数。TensorFlow还提出一些其他的随机数生成器:
- tf.random_normal:正态分布
- tf.truncated_normal:正态分布,但如果随机出来的值偏离平均值超过2个标准差,那么将被重新随机
- tf.random_uniform:平均分布
- tf.random_gamma:Gamma分布
TensorFlow中常用的常量声明方法:
- tf.zeros:产生全零数组
- tf.ones:产生全1数组
- tf.fill:产生一个全部为给定数字的数组
- tf.constant:产生一个给定值的常量
神经网络的偏置通常会使用常数来初始化:
biases = tf.Variable(tf.zeros([3]))
也可以用其他变量来初始化新的变量:
w2 = tf.Variable(weights.initialized_value())
w3 = tf.Variable(weights.initialized_value()*2.0)
下面介绍如何实现神经网络参数并实现前向传播的过程:
import tensorflow as tf
# 神经网络参数
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()
init_op = tf.initialize_all_variables()
sess.run(init_op)
print(sess.run(y))
sess.close()
3. 训练神经网络模型
神经网络训练的核心是通过反向传播算法对参数进行调整。反向传播是一个迭代的过程,每次迭代都要选取一部分训练数据,这部分数据叫做一个batch。然后,这个batch的样例会通过前向传播算法得到神经网络模型的预测结果,因为训练数据都有正确答案标注,所以可以计算当前神经网络模型的预测答案与正确答案之间的差距。最后,反向传播算法会相应更新神经网络参数的取值,使得在这个batch上神经网络模型的预测结果和真实答案更加接近。
TensorFLow提供了placeholder机制用于提供输入数据,placeholder相当于定义了一个位置,这个位置中的数据在程序运行时再指定,这样就不需要生成大量常量来提供输入数据,只需将数据通过placeholder传入TensorFlow计算图。
import tensorflow as tf
# 神经网络参数
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.placeholder(tf.float32, shape=(1,2), name="input")
a = tf.matmul(x,w1)
y = tf.matmul(a,w2)
sess = tf.Session()
init_op = tf.initialize_all_variables()
sess.run(init_op)
print(sess.run(y, feed_dict={x:[[0.7,0.9]]}))
sess.close()
在得到一个batch的前向传播结果之后,需要定义一个损失函数来刻画当前的预测值和真实答案之间的差距。然后通过反向传播算法来调整神经网络参数的取值,使得差距可以被缩小。
# 定义交叉熵来刻画预测值与真实值之间的差距
cross_entropy = -tf.reduce_mean(y_*tf.log(tf.clip_by_value(y,1e-10,1.0)))
# 学习率
learning_rate = 0.001
# 定义反向传播算法优化神经网络参数
train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)
交叉熵是分类问题中常用的损失函数,train_step定义了反向传播的优化方法,tensorflow目前支持7种不同的优化器,比较常用的优化方法有三种:tf.train.GradientDescentOptimizer、tf.train.AdamOptimizer和tf.train.MomentumOptimizer。
import tensorflow as tf
# 模拟数据集
from numpy.random import RandomState
batch_size = 8 # 训练数据batch的大小
# 神经网络参数
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.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)
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y,1e-10,1.0)))
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)
rdm = RandomState(1)
dataset_size = 128
X = rdm.rand(dataset_size,2)
Y = [[int(x1+x2<1)] for (x1,x2) in X]
with tf.Session() as sess:
init_op = tf.initialize_all_variables()
sess.run(init_op)
print(sess.run(w1))
print(sess.run(w2))
iter = 5000
for i in range(iter):
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]})
if i % 1000 == 0:
total_cross_entropy = sess.run(cross_entropy, feed_dict={x:X, y_:Y})
print("After %d training steps, cross entropy all data is %g" % (i, total_cross_entropy))
print(sess.run(w1))
print(sess.run(w2))
训练神经网络的过程可以分为3个步骤:
- 定义神经网络的结构和前向传播的输出结果
- 定义损失函数以及选择反向传播优化的算法
- 生成会话,并且在训练数据上反复运行反向传播优化算法。
无论神经网络的结构如何变化,这3个步骤是不会改变的。