TensorFlow小白教程:迈向神经的第一步

685 阅读10分钟

第一章 TensorFlow 基础概念

TensorFlow小白教程:深度学习的理解

TensorFlow小白教程:Tensor基础教程

TensorFlow小白教程:Session基础教程

TensorFlow小白教程:Graph计算图教程

从线性回归开始

从今天开始我们终于可以开始做一个神经了,想想还有点小激动。在这篇文章里,我们通过从理解神经网络到一步步自己手撸一个线性回归的预测网络模型。

我们会用Numpyarange()方法生成0到25的数字,然后通过reshape()方法将生成的数字转化为25行1列的矩阵。

x = np.arange(0, 25, 1).reshape(25,1).astype(np.float32)

然后通过线性变化,计算出结果值。为了给TensorFlow增加一点难度,我们给结果值加一些噪点,让他生成的点围绕着线性的周围分布。

# 生成随机数作为噪点
noise = np.random.normal(0,10,x.shape).astype(np.float32)
# 进行x*4+10的线性变化,并给结果加入噪点
y = x*4 + 10 + noise

这样我们的数据就准备完成了,我们将通过一个全连接神经网络来训练模型,看是否TensorFlow能够很好的对我们的数据进行回归。

在真正开始之前,我们先看一下我们最终的成果效果图。

理解一个神经元(节点)是怎么运作的

神经网络图

这是一个最简单的三层全连接神经网络结构,它包涵了:一个输入层、一个隐藏层和一个输出层。所谓的全连接神经神经网络指的是每个节点都互相连接的网络。我们可以看到图中的每个白色的圆圈(节点)都与下一层的节点互相连接,这就是一个最典型的全连接神经网络结构。

为了帮助大家理解全连接网络结构中每个节点都发生了什么,我们就先拿放大镜来看输入层到隐藏层之间发生了什么。

首先我们的输入节点例如是一个2行一列的矩阵:[x1,x2]。我们在用TensorFlow设计全连接神经网络的时候,我们经常可以看到以下的代码:

weight = tf.get_variable('weight', [input_size, output_size], dtype=tf.float32,
        initializer=tf.truncated_normal_initializer(stddev=0.1))

这段代码就是初始化我们的w(权重)的代码,为了方便大家理解。我已经给输入层和隐藏层之间的节点的权重都标注好了。其中w11代表了x1与a1节点之间的权重值、w21代表了x2与a1之间的权重值,以此类推。

我们假设x1的值为1、x2的值为2,其中每个节点间的w的值如图所示。这时候我们的输入层就是一个1行2列的矩阵:[[1,2]]。而我们weight则是一个2行3列的矩阵:[[0.1,0.2,0.3],[0.4,0.5,0.6]]的矩阵。

矩阵

这时候我们就可以通过矩阵的乘法算出对应a1a2a3矩阵的值。

a1 = x1*w11+x2*w21 = 1*0.1 + 2*0.4 = 0.9

a2 = x1*w12+x2*w22 = 1*0.2 + 2*0.5 = 1.2

a3 = x1*w13+x2*w23 = 1*0.3 + 2*0.6 = 1.5

这样我们就得出了隐藏层每个节点的值。最后通过一样的计算过程,我们也能够算出输出层的值,这就是我们输出的结果值。这一个从输入层不断推导至输出层的过程也就是我们的前向传播过程。

接下来我们定义一个方法来生成我们的一层神经层。

def get_layer(x,input_size,output_size):
    # 初始化我们的weight权重
    weight = tf.get_variable('weight', [input_size, output_size], dtype=tf.float32,
        initializer=tf.truncated_normal_initializer(stddev=0.1))
    # 通过tf.matmul进行矩阵乘法,计算得到我们后一层的节点矩阵
    output = tf.matmul(x, weight)
    # 输出我们生层的一层神经层
    return output

# 通过我们定义的方法生成我们的隐藏层
get_layer(x,2,3)

我们定义了一个方法来计算和生成我们的一层神经层,我们接受3个参数,第一个参数是输入的矩阵;第二个参数是上一层的节点数;第三个参数是我们生成的神经层所包涵的节点数。

例如上面输出和隐藏层的例子,第一个参数是输入层的矩阵[[1,2]];第二个参数是输入层的节点数2;第三个参数是我们生成隐藏层的节点数3。

首先,我们通过tf.get_variable()方法生成变量的方法,通过正态随机分布来初始化我们weight的值,然后通过tf.matmul()方法计算矩阵的乘法运算,得到我们隐藏层的节点矩阵,这就是我们新生成的神经层,并将它返回。

别忘了偏置项

我们知道线性方程 y = wx,那么它必定是通过(0,0)点的直线。而很多时候我们现实生活中获取的数据,肯定不会围绕着 y = wx的函数进行分布。所以这时候我们会给y =wx + b,这样我们的回归直线就能够更加的灵活运动,去回归我们的数据,这其中的b就是我们所说的偏置项。

总结的来说,我们一定要给全连接神经网络增加一个偏执项,这样就能让我们的神经层能够更加的灵活。

我们来改造以下我们生成神经层的方法:

def get_layer(x,input_size,output_size):
    # 初始化我们的weight权重
    weight = tf.get_variable('weight', [input_size, output_size], dtype=tf.float32,
        initializer=tf.truncated_normal_initializer(stddev=0.1))
     # 初始化我们的bias偏置项
    bias = tf.get_variable('bias', [1, output_size], dtype=tf.float32, initializer=tf.constant_initializer(0.1))
    # 通过tf.matmul进行矩阵乘法,计算得到我们后一层的节点矩阵
    output = tf.matmul(x, weight) + bias
    # 输出我们生层的一层神经层
    return output

# 通过我们定义的方法生成我们的隐藏层
get_layer(x,2,3)

我们给生成神经层的方法中,增加了初始化bias偏置项的变量的代码,我们通常只需要将偏执项初始化为一个1行n列的矩阵,其中的n就是我们生成神经层的节点数。通常我们直接初始化值为0.1就好了,然后我们在output的计算时,给生成的神经层矩阵中都加入我们的偏置项即可。

损失函数是什么

损失函数是神经网络中重要的一环,损失函数指的是通过定义函数计算模型对样本之间的偏差。我们训练模型的过程中,会尽量模型能够很好的学习到样本中的数据,从而降低损失值的大小。

例如本例子中的损失函数我们运用了均方误差损失函数,我们通过计算模型预测的值和实际值之间的平方差,而我们模型优化的目标也就是为了降低这个损失值。

例如黑色的线是我们模型拟合的直线,蓝色的点是实际我们样本的y值,我们通过y值减去我们直线上预测的值,之间的差就是我们预测的值与样本之间的距离。通过平方来抹去正负之间的影响。然后我们将所有的距离相加并除以平均值,得到平均的一个距离。这就是我们的模型和样本真实值的误差。

回到文章最开头的数据,我们的x是一个0-24之间的数字,我们通过定义一个隐藏层和输出层来计算我们预测的结果。

并通过预测的结果和实际的y值做均方误差计算,得到我们预测的值与损失值之间的距离。

# 隐藏层
l1 = get_layer(x,1,25,'l1')
# 输出层
_y = get_layer(l1,25,1,'output')
# 定义损失函数
loss = tf.reduce_mean(tf.square(y-_y))

而接下来我们的目标就是不断的缩小这个损失函数,来优化我们的权重值,来达到最优的模型。

不过值得注意的是,均方误差适合线性回归的模型,但不同的实际问题需要采取的损失函数也各不相同,在之后的教程中,我们会结合不同的问题,来学习不同的损失函数。

学习率是什么

在了解什么叫学习率之前,我们先来熟悉一下下面这个图。这是一个损失函数和weight的值,形成的一个损失值与weight权重的关系图,假设图中的曲线是一个w ²。而我们神经网络的最终目标就是找到一个weight权重,使得损失值达到最小。

在微积分中,我们可以通过函数的导数等于0来寻找极值。在这个例子中,参数x的梯度为:f'(w) = 2w

而学习率η用来定义每次参数更新的幅度。从直观上理解,可以认为学习率定义的就是每次参数移动的幅度 。其公式为:

损失值与weight权重的关系图

假设参数的初始值为 5,学习率为 0.3,那么优化weight的优化过程入下表:

轮数 当前轮数weight值 梯度x学习率 更新后weight参数值
1 5 2x5×0.3=3 5-3=2
2 2 2×2x0.3=1.2 2-1.2=0.8
3 0.8 2×0.8×0.3 =0.48 0.8-0.48=0.32

经过三轮后,weight的值就变成了0.32,这个值在不断的向最优的weight权重0靠近。

在神经网络过程中优化过程可以分为两个阶段,第一个阶段先通过前向传播算法计算得到预测值,并通过得到预测值与实际值之间差距进行对比,然后在第二个阶段通过反向传播算法计算损失函数对每一个参数的梯度,再根据梯度和学习率使用梯度下降算法更新每一个参数 。

在tf中,已经替我们封装好了方法。

# 通过梯度下降算法,定义学习率为0.001和最小化损失值
train = tf.train.GradientDescentOptimizer(0.001).minimize(loss)

线性的一小步,神经的一大步

完整的代码:

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf;

# 生成数据
x = np.arange(0, 25, 1).reshape(25,1).astype(np.float32)
# 生成噪点
noise = np.random.normal(0,10,x.shape).astype(np.float32)
y = x*4 + 10+noise

# 通过matplotlib进行绘制
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.scatter(x,y)
plt.ion()#本次运行请注释,全局运行不要注释
plt.show()

def get_layer(x,input_size,output_size,name):
    with tf.variable_scope(name):
        with tf.name_scope('Weight'):
            # 生成weight权重值
            weight = tf.get_variable('weight', [input_size, output_size], dtype=tf.float32,
                                     initializer=tf.truncated_normal_initializer(stddev=0.1))
        with tf.name_scope("Bias"):
            # 生成bias偏置项
            bias = tf.get_variable('bias', [1, output_size], dtype=tf.float32, initializer=tf.constant_initializer(0.1)
        output = tf.matmul(x, weight) + bias
        return output

# 类似占位符,作为存放输入数据的地方。具体的值通过feed_dict进行输入
input_x = tf.placeholder(dtype=tf.float32, shape=[25,1], name='x_input')
input_y = tf.placeholder(dtype=tf.float32, shape=[25,1], name='y_input')

# 生成隐藏层
l1 = get_layer(input_x,1,25,'l1')

# 生成输出层,得到预测结果
_y = get_layer(l1,25,1,'output')

# 定义损失函数
loss = tf.reduce_mean(tf.square(input_y-_y))

# 通过梯度下降算法,训练模型
train = tf.train.GradientDescentOptimizer(0.001).minimize(loss)

with tf.Session() as sess:
    # 初始化变量
    sess.run(tf.global_variables_initializer())
    # 训练10000步
    for i in range(10000):
        # 执行训练,通过feed_dict 将训练样本x和y值输入到input_x和input_y中
        sess.run(train,feed_dict={input_x: x, input_y: y})
        # 每100步重新绘制直线
        if i % 100 == 0:
            try:
                ax.lines.remove(lines[0])
            except Exception:
                pass
            prediction = sess.run(_y,feed_dict={input_x:x,input_y:y})
            lines = ax.plot(x, prediction, 'r-', lw=5)
            plt.pause(0.1)


上面是我们本节完整的代码,我们通过生成一个25个节点的隐藏层,最后输出25个预测值,通过均方误差损失函数计算出与实际值的差距,然后通过梯度下降算法,不断的去更新我们的weight权重值,来缩小与实际值之间的差距。

我们通过for循环,训练了10000步模型,并在每100步的时候去更新一下直线。通过运行代码,我们会发现我们的直线会随着模型步数的上升,会慢慢调整,更好的拟合我们的实际值。

复盘

这一节通过一个最简单的线性回归例子,来理解了神经网络前向传播是如何运作的、什么是损失函数和偏执项,最后理解了一下梯度下降算法和学习率的概念。希望通过这个简单的案例,大家能够对神经网络有一个最直观的感受。