这里给出了一个简单的 TensorFlow 的 Hello World 的程序,实现了一个简单的向量加法的操作。首先 import 了 TensorFlow ,然后定义了 a、b 两个向量,包括 name 属性,接着把它们加起来。这里 TensorFlow 采用了这样的一个惰性计算模型,输出的并不是加法的结果,而是结果的一个引用。另外,要运行整个 result 运算,就必须定义一个 session ,session 会掌握所有 TensorFlow 的运算资源,然后通过 session 运行相关操作。
这里只是简单介绍了一个 TensorFlow 的简单用法,由于时间有限,也无法深入地去详细介绍。我们关注的是如何用 TensorFlow 实现一个神经网络的全连接,也就是加权和,加上激活函数的模型。
加权和可以通过矩阵乘法的方式实现。如上图代码所示,这里通过 placeholder 的方法定义了一个 input ,其中类型是必须的,其他的诸如 shape 等参数则可以等使用的时候再赋值。之后定义了权重 w 和偏移量 b,这里是通过 Variable 方法定义的,这样等最后优化的时候,TensorFlow 会针对这些 Variable 展开优化。最后通过乘法和加法操作,也就是通过 output = tf.nn.relu(tf.matmul(x, w) + b) 这一行代码就实现了神经网络的基本结构。
这里是通过基础的 TensorFlow API 实现的,如果通过 Keras 等更高层的封装来实现会更加简单。这里我们是从基础的 API 入手来进行讲解,如果大家对高层封装感兴趣,可以自己学习。需要指出的是,其实高层封装和基础 API 的主要区别是实现上的区别,他们整体上的过程是基本一样的。
下面我们来看一下如何用 TensorFlow 实现一个具体的图像识别模块,即从 MNIST 数据集中识别手写数字。(完整代码见下文链接)
可以看到,TensorFlow 通过 read_data_sets 方法对引用数据进行了一个非常好的封装,后续可以通过非常简单的方式读取这些划分好的数据集,例如通过 train、validation、test 等关键词就可以读取训练和测试数据集等。
如下图所示,然后是通过 next_batch 来获取一小批的训练数据。我们刚刚提到,在利用梯度下降算法时需要在所有的训练数据上计算梯度,但是计算量太大了,因此这里通过 next_batch 方法,相当于我们在所有的训练数据集中筛选一部分,随机选取一部分训练数据集,提供到神经网络的输入层,然后通过反向迭代方法去优化这个神经网络。
这里 xx 设置等于 100,也就是我们得到了 xs 和 ys 两个矩阵,xs 代表输入数组,相当于把一个 28×28 的手写图像展开成一个长度为 748 的一维数组。ys 相当于我们的结果,也就是 0-9 这 10 种可能值。
如上图,完了之后是一个前向传播的一个大致过程的程序截图。这个过程就相当于是定义一层一层的神经网络结构,这里 inference 函数中的 input_tensor 相当于输入矩阵,后面的 reqularizer 就相当于一个正则化的东西。我们可以看到,当输入来了之后,程序开始进行一层一层的推导,定义一层一层的权重和偏移量,算出每一层的结果,传入下一层,进入下一层的计算。
其实通过这个前项传播的定义我们可以看到,无论是全连接层还是卷积神经网络,甚至是循环神经网络,它的大致流程都是一样的,给定输入,然后通过一层一层的传递就可以得到最后的输出。
如上图,下面我们看一下主程序。其实主程序就是调用了 train 的过程,这个 train 的过程其实也是比较简单的。第一部分是定义输入,也就是怎样来提供这个训练数据的接口,也就是通过 placeholder 的方式导入,输入这里是 x ,输出是 y_ 。然后通过调用 inference 函数来进行一个前向传播的计算。然后定义了滑动平均和损失函数。
这里的过程其实是就相当于是:我通过输入一个训练数据集,然后得到在当前数据集下的推导结果,然后再通过这个推导结果和正确答案对比,就知道跟正确答案的差别在哪。下一步可以看到我们定义了 loss ,它相当于评估当前模型好坏的一个指标。这里其实就相当于是评价 cross_entropy 加上正则化,这里正则化是为了避免过耦合的。
完了之后,下一行是通过 GradientDescentOptimizer 函数优化。TensorFlow 提供了大概 5-7 中不同的优化函数可供选择,它们针对不同的应用场景,各具特点,大家可以灵活选择。这里我认为,对于那些不搞学术研究的同学,其实没有必要去从数学的角度推导每一个优化函数具体是怎么优化的,从应用层的角度来看,大部分用户只需要提供学习率和目标函数,并且了解这些优化函数的优劣就可以了,这个相对来说还是比较方便。
在把所有的这些计算方式都定义好了之后,下面就是生成 TensorFlow 的计算图,以及生成 session。定义好 session 之后,下面训练的过程就比较简单了,其实就是写了一个循环,每次选取一小部分训练数据,然后去做训练,隔一段时间再打印一下训练结果,整个过程就完成了。
所以说整个用 Tensorflow 来实现一个神经网络的过程,相对还是比较简单的。需要注意的是,这里我介绍的只是原生态的 TensorFlow,如果大家要去使用 TFLearn,或者 Keras 这些高级封装去实现 MNIST 问题的话,可能会更加简单,大概只需要 2-3 行代码就可以解决了。
这里我们基本上把通过原生态的 Tensorflow 生成神经网络的过程为大家介绍了一下。其实后面还有个 evaluate 评估的部分(代码如上图所示),因为时间关系我就不对着代码详细讲了,感兴趣的同学可以自己下去研究(源码见下文链接)。
下面我再跟大家再介绍一下循环卷积神经网络。
刚刚大家看到的这个结构是一个全链接的神经网络,在图像处理的过程中,使用全连接神经网络最大的一个问题就是它的参数太多了,这个问题可能会导致模型最终训练不好。
例如,经常发生的,当你的训练数据不足的时候,参数又太多,你就可能训练不出来。一个非常简单的例子,大家可以想象 N 元的一个方程组,然后我们假设只有 N 个数据,并且这些数据是完全可分的,也就是我们是可以完全求解。但完全求解可能会导致过拟合,因为训练数据在真实环境下都是有噪音的,也就是没有办法做到完全避免随机因素的影响。在这种情况下如果你过于贴合训练数据,那么就有可能没有办法去收敛到未知的数据。
所以这就是参数过多可能引发的问题,即过拟合和训练不出来。那怎样去解决这两个问题呢?卷积神经网络就是一个很好的方法。
卷积神经网络就相当于是采用一个内核,即一个规模较小的矩阵,去处理一个比较小的区域,然后去通过移动这个小矩阵,去处理不同的这个块。这种方式一个直观的一个意义就是:一般的图像上相邻区域的内容是相似的。然后通过这样的一个潜在的东西,就可以去把一个浅层的表达变成一个更深层的表达。也就是相当于自动从图像中去提取特征。
例如上图所示,第一层可能可以从图像中提取一些线条和斑点,第二层提取一些更复杂的性状。第三层和第四层,层数越多,提取的特征就会越复杂。然后通过最后提取出来的这样一些特征,我们再去做一个全连接的分类,最后这个分类的效果也就会更好。
然后这里给出了一个简单的 LeNet5 的模型,我们可以看到他的大致结构就是从输入层,经过不断地卷积池化,再经过 1 到 3 层不等的全连接层,最后得到输出层。其实现在很多的卷积神经网络基本上也都保留了这样的一种结构。
除了这种模型之外,另一种比较特殊的模型是 Google Inception 模型,这里因为时间关系我也不去做过多的介绍了。
然后我在这里给出了一个简单的用 TensorFlow 的程序来实现卷积层。通过代码其实大家也可以看到,在这个框架里面,无论是全连接的神经网络也好,还是卷积神经网络也好,甚至循环神经网络也好。它们的训练过程,以及前面的准备过程其实基本上都是差不多的,你基本上只要去修改,怎么样去从输入得到输出就可以了。
从代码也可以看到,开始是定义这个卷积层中的权重和偏移量,完了之后 TensorFlow 会对这个卷积层有一个封装,然后通过 conv2d 函数得到一个 2D 的卷积层,然后再把偏移量、激活函数加上去。大家可以看到整个的过程还是比较简单的,同样的,如果用 Keras 等更高级的封装来实现的化会更加简单。
最后我要推荐一下《TensorFlow:实战Google深度学习框架》这本书,今天讲的内容,包括一些略去的内容,基本上全部在这本书里都有非常详细的介绍。
另外,前面提到的代码地址如下:
https://github.com/caicloud/tensorflow-tutorial
代码库里包含了书中所有的样例代码,此外还包括了才云科技提供的 TensorFlow as a Service (TaaS) 深度学习平台的一些教程和代码,包括后面我们做的一些图像分类和图像识别的样例代码,后面也都会陆陆续续添加进去。大家有兴趣的化可以关注一下这个代码库。
网友评论