在上一章已经了解了tensorflow的工作原理,那么今天来学习一下神经网络。
(一)首先看一下最简单的神经网络:
和图中表示的一样,最左边是我们的数据集,然后中间使我们的神经网络,包括输入层、隐层、和输出层。
输入层是特征向量(这个特征向量需要我们一个特征提取的过程,书上说用于描述实体的数字的组合成为一个实体的特征向量,比如一个零件可用【0.5,1.6】来描述他0.5m长和1.6kg重的特征)。这个部分通过图中可以看到他可以简单的输入x1、x2两个数据,也可以输入他们的平方,相乘等多种输入形式。我认为神经网路就是把输入的数据进行一个函数拟合的过程,说白了就是建立一个函数,根据输入的值得到一个输出,这里的输入和输出都为一个多维数组,当然也可以是单一的一个数字。所以只要能包含输入特征的运算过程都可以作为输入,当然这只是我自己的理解。
接下来就是神经元了。神经元到底是什么!这个其实很重要,面试的时候有可能会问。神经元,说白了我认为就是一个函数!左面是输入、右边是输出。他的具体结构如下图:
OK~就是一堆输入的线性组合加上一个非线性的函数,某软面试官居然特么跟我说神经元里没有非线性函数,真是各位选好公司很重要!那么一个神经元的输出只能是一个值,但是可以输出到多个不同的神经元。也就是说,神经元的输出可以有多条线,但是每条线输出的值是一样的!
经过了多层隐层以后便得到了输出层,这个输出层输出的不是上图中那个图,而就是简简单单的一个值。这个值如果大于0就是蓝色,小于0就是橘黄色,所以根据不同的输入值就将两种颜色的点点分离开来!真神奇。
(二)前向传播算法
看完这部分我也是服了,这也叫算法呀---所谓前向传播,就是输入通过权重和神经元的函数后,计算得出结果的这一个过程。如下图所示:
而由于在TensorFlow中矩阵的乘法是非常容易实现的,所以前向传播的过程常表示为矩阵乘法。这里主要是矩阵之间的乘法,行向量乘以列,然后再相加的这个过程。
(三)神经网络参数与TensorFlow变量
在TensorFlow中,tf.Variable的作用就是保存和更新神经网络的参数。
weight = tf.Variable(tf.random_normal([2,3],stddev=2))
上面一行代码就是参数初始化的过程,初始化一个2*3的参数矩阵,均值为0,标准差为2.当然,此外还有很多初始化变量的方法。
OK,接下来我们看一下如何用代码实现前向传播算法:
import tensorflowas tf
w1 = tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2 = tf.Variable(tf.random_normal([3,1],stddev=1,seed=2))
x = tf.constant([[0.7,0.9]])
a = tf.matmul(x,w1)
y = tf.matmul(a,w2)
sess = tf.Session()
init_op = tf.global_variables_initializer()
sess.run(init_op)
print(sess.run(y))
sess.close()
要写一段tf代码,我认为就是需要写出每个神经元和每条边。首先定义变量(Variable),也就是边(权重)。然后定义每次层的神经元,第一层为输入神经元,所以定义为常量输入,第二层和第三层分别进行了矩阵的乘法,也就是简单的线性组合。以上便定义好了张量,和计算图,然后我们定义会话,首先需要初始化所有的张量,而后如下这两句便完成了所有张量的初始化
init_op = tf.global_variables_initializer()
sess.run(init_op)
最后打印了y,然后关闭会话!
(四)训练网络模型
那么,在一个神经网络里我们有大量的输入(比如训练集),那么如果我们用之前定义constant的方式,那么需要定义大量的计算节点,这样是不可取的。那么tensor里定义了 一个placeholder,他作为一个计算节点,可以源源不断的帮我们传入输入值。
import tensorflowas tf
w1 = tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2 = tf.Variable(tf.random_normal([3,1],stddev=1,seed=2))
#x = tf.constant([[0.7,0.9]])
x = tf.placeholder(tf.float32,shape=(3,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)
print(sess.run(y,feed_dict={x:[[0.7,0.9],[0.1,0.4],[0.5,0.8]]}))
sess.close()
上图中我们定义了placeholder,然后再run里的feed_dict中输入我们的x。
那么接下来就是反向传播的调参过程。
反向传播的整体思路是这样的:我们根据设置的参数得到的输出结果与真实结果的差,我们称之为损失函数(代表的就是我们的输出与真实的差距),反向传播的过程就是想办法使损失函数降低到一个最小值。
Loss = (w1*x1 + w2*x2 + b) - y 括号里的当然不只是线性的组合,还有各种非线性的组合。x1,x2等是我们的输入,y是标签的值,所以这些都是常数,所以Loss函数就是关于各种w和b的多元函数。
OK,那么现在这个调参的过程变成了一个多元函数的求最小值的问题。那么为了让这个损失函数变小的最快,这里使用了“梯度下降法”(又称最速下降法)。 我们知道多元函数关于在任何方向上都有变化,那么这个就叫做方向导数。然而这个函数的值变化最快的方向,就叫做梯度!!!!!(这个稍微有些难理解)那么这个梯度的求解方法就是各个方向的偏导数乘当前方向的单位向量再相加。
所以就可以把所有的参数都从他们的偏导数方向上减少:
其中n就是他们的学习率。而求偏导的过程就使用了链式规则:
当然,这个过程在tf中不需要这么复杂,只需要使用封装好的代码就可以。具体的代码明天上。