还是以TensorFlow游乐场作为例子。假如这是一个区分零件是否合格的实践场景,零件还是以 长度 和 质量 为特征。TensorFlow会通过监督学习的方式更合理的设置参数取值,设置神经网络参数的过程就是神经网络的训练过程。只有经过有效训练的神经网络草可以真正的解决分类或者回归问题。

3a5d086bffe189edea4d01747ab03c88.png
盛景网络训练前

dedc52bb9815b54b9dd1f5ca4b226e70.png
神经网络训练后

使用监督学习的方式试着神经网络参数,需要一个标注好的数据集。本例中就是零件是否合格。这个标注好的训练集就是收集一批不合格零件和一批合格零件。图上的橘色点和蓝色点代表训练数据集。平面上的深色和浅色代表了TensorFlow对分析结果的自信程度,上边的图可以发现,整张图都是浅色调,但是经过训练之后的下图,颜色划分就很明显了。

监督学习的重要思想就是,在已知答案的标注数据集上,模型给出的预测结果要尽量接近真实答案,通过调整神经网络中的参数对训练数据集进行拟合,可以使得模型对未知的样本提供预测能力。

在神经网络优化算法中,最常用的方法就是反向传播算法(backpropagation)。

下图展示了神经网络反向传播优化流程图

4373ce19e47d85827d78a8f0ba5b435d.png
神经网络反向传播优化流程

反向传播算法实现了一个迭代过程。在每次迭代开始首先需要获取一小部分训练数据,这一小部分数据叫做一个batch。然后,这个batch的样例会通过向前传播算法得到神经网络模型的预测结果。因为训练数据集都是有正确标注的,所以可以计算出当前预测答案和正确答案之间的差距。最后,基于预测值和真实值之间的差距,反向传播算法会相应更新神经网络参数的取值,使得这个batch上的神经网络模型的预测值结果和真实答案更加接近。

通过TensorFlow实现反向传播算法的第一步就是使用TensorFlow表达一个batch的数据。之前我们的示例是用常量来做的。

x =tf.constant([[0.7,0.9]])

但是如果每轮迭代的数据都通过常量来表示,那么TensorFlow的计算图将会太大,因为每生成一个常量,TensorFlow都会在计算图中增加一个节点。一般来说一个神经网络训练过程要经过几百万甚至上亿次轮的迭代,这样的计算图就会非常大,而且利用率也很低。TensorFlow提供了placeholder机制用于提供输入数据。

placeholder可以这样理解,他相当于定义一个占位符,但是里边并没有数据,但是制定了数据的类型和数据维度,当需要计算的时候在把值填充进去。

import tensorflow as tf

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=[1, 2], name="input")
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
sess = tf.Session()
# 初始化所有变量
init_op = tf.global_variables_initializer()
sess.run(init_op)
# 现在如果直接输出 sess.run(y) 的话会报错,因为 placeholder并没有实际值
# feed_dict 给x占位符填值
print(sess.run(y, feed_dict={x: [[0.7, 0.9]]}))
sess.close()

在以上程序中,原来通过常量 定义的输入x 用placeholder方式进行定义。在新的程序中计算向前传播结果时,需要提供一个feed_dict 来指定的x的取值,feed_dict 是一个字典类型的参数,在字段中需要给出每个用到的placeholder的取值,那么程序在运行时就会报错。

以上程序计算只计算一个样例的向前传播结果,在训练神经网络时需要每次提供一个batch的训练样本。对于这样的需求,placeholder也能很好的支持。在上面的样例程序中,如果将输入的 1 * 2 矩阵改为 n * 2 的矩阵,那么就可以得到n个向前传播结果了,其中n * 2矩阵的每一行为一个样例数据。

x = tf.placeholder(tf.float32, shape=[3, 2], name="input")
print(sess.run(y1, feed_dict={x: [[0.7, 0.9], [0.1, 0.4], [0.5, 0.8]]}))

向前传播结果:

[[-3.62591028]
[-0.98978221]
[-2.86308026]]

得到一个batch的向前传播结果之后,需要定义一个损失函数来刻画当前预测值和真实答案之间的差距。然后通过反向传播算法来调整神经网络的取值使得差距可以被缩小。

神经网络完整代码

import tensorflow as tf
from numpy.random import RandomState

# 定义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大小。在训练是需要把数据分成较小的batch
# 在测试时可以一次性使用全部数据。当数据集比较小时这样比较方便测试,但是数据量较大的时候,
# 讲大量数据放到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)

# 定义损失函数(梯度下降算法)和反向传播算法
y = tf.sigmoid(y)  # 把y的结果限定在 0 ~ 1 之间。

cross_entropy = -tf.reduce_mean(
    # 1e-10 表示1 * 10 的 -10 次幂
    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))
)
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)

# 通过一个随机数生成一个模拟数据集
rdm = RandomState(1)

# print(rdm)
dataset_size = 128
# 产生一个 128 * 2 的矩阵
X = rdm.rand(dataset_size, 2)
# print(X)
# 定义规则来给出样本标签。这里所有的x1 + x2 < 1 这样的样本认为是正样本(零件合格)
# 而其他为负样本(零件不合格)。这里使用0来表示负样本,1表示正样本
Y = [[int(x1 + x2 < 1)] for (x1, x2) in X]

with tf.Session() as sess:
    # 初始化计算图中各阶段参数
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    print(sess.run(w1))
    print(sess.run(w2))
    """
    输入 ----> 隐藏层
[[-0.81131822  1.48459876  0.06532937]
 [-2.4427042   0.0992484   0.59122431]]
    隐藏层 ---> 输出
[[-0.81131822][ 1.48459876][ 0.06532937]]
    """
    # 设定训练轮数
    STEP = 5000
    for i in range(STEP):
        # 每次选取batch_size 个样本进行训练,获取一个batch_size长度的创口数据
        start = (i * batch_size) % dataset_size  # 创口起始位置
        end = min(start + batch_size, dataset_size)  # 创口结束位置,如果超出起始位置到结束位置超出数据集,则牺牲窗口长度,只取到数据集最后一条
        # 通过选取的样本训练神经网络并更新参数  train_step :损失函数
        sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start: end]})
        if i % 1000 == 0:
            # 没运行1000次计算所有数据上的交叉熵并输出
            total_cross_entropy = sess.run(cross_entropy, feed_dict={x: X, y_: Y})
            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))


上方代码打印结果
输入层 -> 隐藏层默认参数 [[-0.811318221.484598760.06532937][-2.44270420.09924840.59122431]]
隐藏层 -> 输出层默认参数 [[-0.81131822][1.48459876][0.06532937]]
------------------------------------------------------------------
After 0 training step(s), cross entropy on all data is0.314006
After 1000 training step(s), cross entropy on all data is0.0684551
After 2000 training step(s), cross entropy on all data is0.033715
After 3000 training step(s), cross entropy on all data is0.020558
After 4000 training step(s), cross entropy on all data is0.0136867
交叉熵逐渐变小,说明预估的结果和实际的结果越发接近
--------------------------------------------------------------------------
输入层 -> 隐藏层计算后参数 [[-2.548655033.079308752.89517117][-4.111274721.625907063.3972702]]
隐藏层 -> 输出层计算后参数[[-2.32309365][3.30116868][2.4632082]]

代码中几个函数介绍:

def reduce_mean(input_tensor,
axis=None,
keep_dims=False,
name=None,
reduction_indices=None)
input_tensor : 输入的张量
axis : 计算轴的方向
keep_dims :是否降维度,设置为True,输出的结果保持输入tensor的形状,设置为False,输出结果会降低维度
name : 名字
reduction_indices : 据说已经弃用
# ******* -tf.reduce_mean *******
# 'x' is [[1., 1.]
# [2., 2.]]
# tf.reduce_mean(x) ==> 1.5 如果不指定计算轴,则球总体的平均数
# tf.reduce_mean(x, 0) ==> [1.5, 1.5] 指定纵向 [(1+2)/ 2 , (1+2)/2]
# tf.reduce_mean(x, 1) ==> [1., 2.] 指定横向 [(1+1) / 2 , (2+2)/2]
# ********************************
tf.clip_by_value
输入一个张量A,把A中的每一个元素的值都压缩在min和max之间。小于min的让它等于min,大于max的元素的值等于max。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐