连公式都没看懂?!学渣谨碰这个「神经网络」

原创 2017年10月24日 00:00:00

640.gif?wxfrom=5&wx_lazy=1

本文来自作者 沧夜 在 GitChat 上分享「TensorFlow 优化实践」,阅读原文」查看交流实录

文末高能

编辑 | 乌索普

写在前面的话

在前面一章中说到了 TensorFlow 的基础用法(http://gitbook.cn/m/mazi/activity/599d3d2839e4774d0db015cc),这一章作为一个进阶来聊聊神经网络的具体的结构和参数问题,包括:

  1. 前馈神经网络

  2. 循环神经网络

  3. 神经网络参数

大概就这三个中心内容。当然每个部分展开又是很多东西,但是好在这里只是聊聊工具的使用。所以大概还是一个章节的内容。

前馈神经网络

前馈神经网络包括全链接网络与卷积神经网络,这两者其实并没有大的区别,一般情况下可以将卷积神经网络看做是一个特例。

全链接网络与卷积网络

霍金说过科普书里加一个公式就会少一半读者,所以这里用公式来直观的说明二者的共同点:

0?wx_fmt=png

前面说到全链接网络是一个矩阵相乘的过程:

0?wx_fmt=png

这是一个全链接网络,x是一个一维向量,全链接网络的一个问题就是参数量有点大,设想一下,如果x,y的维度均为1000的话那么显然所需矩阵A的参数数量轻而易举的就到达了个,对于序列数据(比如一维的声音数据、二维的图形数据)而言显然其特征很大程度上可能只与其临近的数据有关,这就使得权值向量在计算的时候可以将用不到的部分省略:

0?wx_fmt=png

上面的矩阵代表在隐藏层计算过程中只计算与其临近的三个点,其他部分不进行计算,这样使得矩阵A的参数数量得到了有效的减少,另外由于序列数据或图像数据在计算过程中特征的相似性,使得各个部分的特征具有重复性,这样在计算中就可以利用同一套权值:

0?wx_fmt=png

这就是所谓的权值共享,通过这个过程,就可以使得全连接层中的参数量极大的减少。

这个就是全连接网络和卷积网络的共同点。提一下,上面那个卷积核心可以计算的特征是“滑动平均”。

接下来看下函数的傅里叶展开:

0?wx_fmt=png

这是函数的傅里叶展开,这里隐藏了卷积神经网络的一个思想,如果将序列x看成一个函数的话那么其特征可以用一系列的简单函数去表示。

而相应的权值就是三角函数与原函数的积分,这个积分的过程就可以看做是卷积神经网络的卷积的过程。

前面矩阵的例子中卷积核心只有一个,能拾取的特征为“均值”,而卷积核心可以有多个,每个卷积核心与原有序列x均做一次卷积的话所得到的就是对应每个特征的权值序列。

傅里叶展开的一个特点就是n越大越接近于原函数,那么到卷积神经网络这里就可以将期描述为卷积的特征越多越可以更好的描述数据的特征。

用 tensorflow 来直观的理解一下卷积的过程:

import tensorflow as tfimport numpy as np kn=np.ones([3,1,2],dtype=np.float32) kn[:,:,0]=1/3kernel = tf.Variable(kn) x = tf.Variable(np.ones([1,1000,1],dtype=np.float32)) y = tf.nn.conv1d(x,kernel,stride=1,padding="SAME") sess=tf.Session() sess.run(tf.global_variables_initializer()) print("Out:",sess.run(y)) print("Shape:",np.shape(sess.run(y)))

这是一个长为1000的一维向量,其特征只有一个最前面的1代表输入数据个数为1,卷积核心维度为 [3,1,2],3代表卷积核心大小,也就是前面所说的能覆盖多少数据点,1代表输入数据特征数量,2是输出数据特征数量,可以看到,对于第0个特征其卷积核心的权值为也就是求取均值,第二个卷积核心权值为1,也就是对三个数据进行相加,可以看下最终结果:

Out: [[[ 0.66666669  2.        ]  [ 1.          3.        ]  [ 1.          3.        ]  ...,  [ 1.          3.        ]  [ 1.          3.        ]  [ 0.66666669  2.        ]]] Shape: (1, 1000, 2)

可以看到,输出数据特征并没有什么违反直觉的地方,输出为两个特征,第一个特征为均值,第二个为相加,由于 padding 边界设置为“SAME”,也就是从第一个点开始作为输出点,这就使得超出部分的权值、数据赋为0。

当然在神经网络之中前面Variable是需要自适应的变化调整以提取特定数据的特征。

tensorflow 构建前馈神经网络

用 tensorflow 构建前馈神经网络总共分为三种方式:

第一种:直接构建

import tensorflow as tf#卷积层def conv1d_layer(input_tensor, kernel_size, feature=2, active_function="relu", name='conv1d'):    activ={"relu":tf.nn.relu,"sigmoid":tf.nn.sigmoid,"tanh":tf.nn.tanh}    with tf.variable_scope(name):        shape = input_tensor.get_shape().as_list()        kernel = tf.get_variable('kernel',                                 (kernel_size, shape[-1], feature),                                 dtype=tf.float32,                                 initializer=tf.constant_initializer(0))        b = tf.get_variable('b',                            [feature],                            dtype=tf.float32,                            initializer=tf.constant_initializer(0))        out = tf.nn.conv1d(input_tensor,                            kernel,                            stride=1,                            padding='SAME') + b    return activ[active_function](out)#全链接层def full_layer(input_tensor, out_dim=2, active_function="relu", name='full'):    activ={"relu":tf.nn.relu,"sigmoid":tf.nn.sigmoid,"tanh":tf.nn.tanh}    with tf.variable_scope(name):        shape = input_tensor.get_shape().as_list()        W = tf.get_variable('W',                            (shape[1], out_dim),                            dtype=tf.float32,                            initializer=tf.constant_initializer(0))        b = tf.get_variable('b',                            [out_dim],                            dtype=tf.float32,                            initializer=tf.constant_initializer(0))        out = tf.matmul(input_tensor,W) + b    return activ[active_function](out)

上面是用 tensorflow 函数构建的全链接网络与卷积神经网络。函数调用过程可以使用:

net=full_layer(xx, featrue=2, active_function="relu", name="full_connect_1") net=conv1d_layer(net,3, feature=2, active_function="relu", name="conv1d_1")

这样就构成了单一层神经网络的构建。

对于多层神经网络而言可以将输出数据进行进行卷积和全链接操作:

net = conv1d_layer(net, 3, feature=2, active_function="relu", name="conv1d_1") net = conv1d_layer(net, 3, feature=2, active_function="relu", name="conv1d_2") net=conv1d_layer(net, 3, feature=2, active_function="relu", name="conv1d_3")

当然每层的 name 要不同,否则会报错。

第二种:通过 contrib 高层次 API

前面用一些基本的 API 构建过程需要自行的去写一些函数,但是这个函数是可以通过 contrib 里面的函数去进行简单的构建的:

import tensorflow.contrib.slim as slim net = slim.conv2d(net, 3, 1, scope='conv1d_1') net = slim.flatten(net) net = slim.flatten(net) net = slim.fully_connected(net, 2,                           activation_fn=tf.nn.relu,                           scope='outes',                           reuse=False)

可以看到通过 slim 高层次 api 实际上可以简化整个网络的构建过程。这里有个 reuse 参数,是复用参数。

通过 keras 构建神经网络

keras 其实是一个构建神经网络比较方便的函数库,其底层计算可以放到 tensorflow 之中:

from keras.layers import Input, Dense, Conv1D net = Input(shape=(1000,1)) net = Conv1D(kernel_size=3,filters=1,padding="same")(net) net=Activation("relu")(net)

当然本章以天 tensorflow 为主,所以这里keras仅供了解。其实现起来比 tensorflow 相对简单。

循环神经网络

循环神经网络构建起来相对于前馈神经网络来说比较麻烦,当然最麻烦的是其训练过程。

前面说到全链接网络的过程为:

0?wx_fmt=png

而循环神经网络与上一步的输入相关,其实只是乘以了一个上一步的矩阵:

0?wx_fmt=png

这就是一个最简单的循环神经网络,在此之上可以构建多层的神经网络,定义y_tx_t之间的关系为:

0?wx_fmt=png

这是构建的多层的循环神经网络。其他的循环神经网络结构是在其上进行的修改。只是参数更多,比如 LSTM。LSTM 输入中加入了一个C矩阵用于保存记忆。

0?wx_fmt=png

tensorflow 构建循环神经网络

上面说到一个循环神经网络的层可以通过如下的函数构建:

cell = tf.contrib.rnn.BasicLSTMCell(           hidden_size, forget_bias=0.0, state_is_tuple=True,           reuse=not is_training)

多层卷积神经网络构建:

cell = tf.contrib.rnn.MultiRNNCell(        [cell for _ in range(num_layers)], state_is_tuple=True)

前面说到循环神经网络的数据输入是一步一步输入的,因此这里在进行数据输入的过程中也需要一步一步的进行数据的输入:

for time_step in range(num_steps):    if time_step > 0: tf.get_variable_scope().reuse_variables()    (cell_output, state) = cell(inputs[:, time_step, :], state)

这里有两个地方需要解释,第一个是 reuse 的问题,因为在第0步之后的变量与前面的变量都是复用的,因此这里需要将其改变为复用模式。

第二个是 state,这个 state 是 LSTM 神经网络结构所特有的,其目的在于保存“记忆”,而记忆量的多少则随着神经网络任务变化而自适应的变化,因此称之为“长短记忆”,这时 cell_output 作为输出就可以用于后续处理了。

可以看到RNN的构建过程相比于前馈神经网络而言是比较复杂的,这其中最关键的是因为其输入是“一步一步”的进行输入的。

神经网络参数选取

这一节内容的话就比较多了,而且有很多“玄学”的感觉,因为参数的选取都带有一定的主观的因素。

自由参数数量与过拟合问题

第一个问题就是参数数量,参数数量的增多会引起一个较大的问题就是过拟合问题,用曲线拟合的方式来了解:

import tensorflow as tfimport numpy as #定义函数展开阶数N = 6x = tf.placeholder(dtype=tf.float32,shape=[1,None]) y = tf.placeholder(dtype=tf.float32,shape=[1,None]) comp = []#函数展开项for itr in range(N):    comp.append(tf.pow(x, itr)) x_v = tf.concat(comp,axis=0)#定义展开系数A = tf.Variable(tf.zeros([1, N])) y_new = tf.matmul(A, x_v)#定义loss函数loss = tf.reduce_sum(tf.square(y-y_new))#用梯度迭代法求解train_step = tf.train.GradientDescentOptimizer(0.0005).minimize(loss) sess = tf.Session() sess.run(tf.global_variables_initializer())#迭代9000次for itr in range(90000):    sess.run(train_step,             feed_dict={x:np.array([[-1, 0, 0.5773502691896258, 1, 1.5, 2]]),                        y:np.array([[0, 0, -0.3849, 0, 1.875, 4]])}) print(sess.run(A.value()))#图形绘制import matplotlib.pyplot as pltimport matplotlib as mpl mpl.style.use('seaborn-darkgrid') lin = np.array([np.linspace(-1, 2, 100)]) ly =sess.run(y_new, feed_dict={x: lin}) plt.plot(lin[0], ly[0]) plt.scatter([-1, 0, 0.5773502691896258, 1, 1.5, 2],            [0, 0, -0.3849, 0, 1.875, 4]) plt.show()

来看下输出图形:

0?wx_fmt=png

可以看到曲线完美的通过了每一个点,但是这种情况可能并非是一个“好”的结果,因为其对于训练数据拟合过于好了,对于训练集以外的数据可能预测效果并不理想。将函数取值范围从-5到5再来绘制图形:

import matplotlib.pyplot as pltimport matplotlib as mpl mpl.style.use('seaborn-darkgrid') lin = np.array([np.linspace(-5, 5, 100)]) ly =sess.run(y_new, feed_dict={x: lin}) plt.plot(lin[0], ly[0]) plt.scatter([-1, 0, 0.5773502691896258, 1, 1.5, 2],            [0, 0, -0.3849, 0, 1.875, 4]) plt.show()

0?wx_fmt=png

在训练数据之外曲线变形比较大,这会影响对于曲线的拟合效果。另一方面来说也就是对于数据的预测效果不理想。在这里如果减少参数数量:

N=3

0?wx_fmt=png

此时可能并未通过每一个点,但是所用的自由参数个数只有三个,但是对于可能的数据分布形式:

0?wx_fmt=png

现在用‘二次曲线’可以对数据进行更好的预测,因此解决过拟合问题最根本的问题就是增加训练集数量

BATCHSIZE

这个是一次输入多少数据用于训练的问题,下面的梯度方向是基于二维曲面拟合的 LMS 方法所言的,可以看到:

0?wx_fmt=png

随机梯度带有一定的随机性,虽然按照概率角度来说是可以收敛的,但是显然通过60个数据进行梯度的估计所带来的方向预测更加准确,因此 BATCHSIZE 存在的一个意义就是进行更好的梯度方向的预测。

当然如果用整个训练集来估计梯度方向的话是更好的。但是这里面临一个效率和内存的问题。

这里用17层 Inception 网络来做个表格,实验机器就是E3非 GPU 版本:

0?wx_fmt=png

梯度参数我想这个表格已经能说明问题了,虽然可以更好的估计梯度方向,但是计算时间和内存的消耗是呈线性增长的。

梯度参数

梯度参数的选取总的来说对于迭代的影响是比大的,过大的梯度参数会使得迭代发散:

0?wx_fmt=png

那个蓝线,在计算过程中不仅没有逐渐减少反而越来越大,想理解发散过程比较简单:

train_step = tf.train.GradientDescentOptimizer(500).minimize(loss)

把曲线拟合的梯度参数选为500,我这里选择的是0.5来观察 loss 函数:

0?wx_fmt=png

这个发散是呈指数增加的,很快就超出机器的数值边界。

比较大的梯度参数可以参考上面那个图的红线,在两个点之间不停震荡。而过小的梯度参数会使得收敛缓慢。比如狠心一些将梯度参数选为

tf.train.GradientDescentOptimizer(5e-15)

这时:

0?wx_fmt=png

收敛过程变得极为缓慢,或者说基本没有变化。

理智的做法梯度步长随着迭代不断的减少。

BATCHNORM 层

前面说过 BP 算法中一个过程就是乘以矩阵:有兴趣参考文章(https://zhuanlan.zhihu.com/p/26352342)

但随着层数增多所乘的矩阵也在不断的增多,那么就会遇到梯度消失的问题,就像鸡汤说的每天忘记百分之一,一百天后就剩下37%了,这是一个指数的问题,BP 算法也是如此,因此引入了 BATCHNORM 层,以解决上述问题。 tensorflow 中有相应的代码:

tf.contrib.layers.batch_norm

形象化

一般在讨论纯理论问题的时候很多人都有个很不好的习惯就是找一个物理映射,这种物理映射可以帮助理解问题,但是可能会在之后的学习中并不如直接的数学理解更有帮助。

那么再来看下整个的循环神经网络的结构:

0?wx_fmt=png

或者

0?wx_fmt=png

这种形式的问题在包含乘法操作的情况下称之为非线性问题。可能用一个比较广为人知的词就是混沌,RNN 和 CNN 网络均可以对混沌吸引子进行重建。
下面这个是一个典型的混沌吸引子:

0?wx_fmt=png

方程描述:

0?wx_fmt=png

那么数值求解上述方程的过程实际上就是类似于 RNN 的过程。

那么三体运动方程实际上也是一个非线性方程的问题,并且三体运动并非是完全不可预测的。

0?wx_fmt=gif

很多时候其表现出概率的特征,这是可以通过统计分析得到的。

有兴趣的,可以参考数值模拟过程用神经网络重建吸引子。

近期热文

当我说要做大数据工程师时他们都笑我,直到三个月后……

当年校招时,我就死在了这个问题上...

如何成为一个 IT 界的女装大佬?

技术学到多厉害,才能顺利进入BAT?

细化到每一步操作的 Jenkins + Django 完整实战

彩蛋

0?wx_fmt=png

阅读原文」了解更多知识

版权声明:本文为GitChat作者的原创文章,未经 GitChat 允许不得转载。

tensorflow中slim模块api介绍

最近需要使用slim模块,先把slim的介绍放在这,后续会进行整理 github:https://github.com/tensorflow/tensorflow/edit/master/tensor...
  • guvcolie
  • guvcolie
  • 2017年08月29日 20:13
  • 6941

TensorFlow学习笔记(二十六)CNN的9大模型之LeNet5的原理讲解

在机器视觉,图像处理领域,卷积神经网络取得了巨大的成功。本文将参考UFLDL和DEEPLEARNING.NET的教程,结合自己的理解,梳理一下卷积神经网络的构成以及其BP算法的求解。虽然利用thean...
  • qq_36330643
  • qq_36330643
  • 2017年08月18日 15:39
  • 460

多层前馈神经网络的后向传播算法推导

多层前馈神经网络的后向传播算法推导。
  • zhangjunthu
  • zhangjunthu
  • 2017年01月07日 16:36
  • 126

多层前馈神经网络及BP算法

1. 多层前馈神经网络 首先说下多层前馈神经网络,BP算法,BP神经网络之间的关系。多层前馈(multilayer feed-forward)神经网络由一个输入层、一个或多个隐藏层和一个输出层组成,后...
  • shengshengwang
  • shengshengwang
  • 2016年08月16日 18:19
  • 4348

前馈神经网络的简单实现

神经网络是机器学习的一个重要方法,就像人类学习的本质是建立神经元之间的联系一样,机器学习也可以模拟相应的“神经元”并建立这些“神经元”之间的联系,人的认知可以看做是神经网络的训练,如识别图片,大脑一旦...
  • zqxnum1
  • zqxnum1
  • 2015年03月06日 09:17
  • 1795

递归神经网络(RNN)简介

在此之前,我们已经学习了前馈网络的两种结构——多层感知器和卷积神经网络,这两种结构有一个特点,就是假设输入是一个独立的没有上下文联系的单位,比如输入是一张图片,网络识别是狗还是猫。但是对于一些有明显的...
  • aws3217150
  • aws3217150
  • 2016年03月02日 17:27
  • 22536

前馈神经网络与深度学习概述

参考: 1、神经网络浅讲:从神经元到深度学习 - 推酷 2、【转】脉络清晰的BP神经网络讲解,赞 - 编程小翁 - 博客园...
  • liuyuemaicha
  • liuyuemaicha
  • 2016年08月27日 15:05
  • 3925

人工神经网络之前馈神经网络

1.基本概念   深度前馈神经网络也叫作多层感知机,是深度学习中最常用的模型。它包含输入层,隐含层和输出层三个部分。它的目的是为了实现输入到输出的映射。它定义了一个函数 y = f(x,theta)...
  • qq_36330643
  • qq_36330643
  • 2017年07月08日 20:14
  • 197

Tensorflow - Tutorial (3) : 前馈神经网络(多层感知机)

前馈神经网络一个简单的3层神经网络模型例子如下,圆圈表示神经网络的输入,“+1”的圆圈被称为偏置节点。神经网络最左边的一层叫做输入层,包含3个输入单元,最右的一层叫做输出层。中间所有节点组成的一层叫做...
  • u010089444
  • u010089444
  • 2016年09月17日 15:57
  • 4706

机器学习(八)前馈神经网络

前馈神经网络用于解决逻辑回归来处理太复杂的情况。提高灵活性的同时,又不太会有过拟合的情况。 神经网络的结点内部是Sigmoid函数,外部有输入、Theta加权值,最后一级网络层如果只有一个输出结点...
  • lonelyrains
  • lonelyrains
  • 2015年10月19日 13:07
  • 7885
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:连公式都没看懂?!学渣谨碰这个「神经网络」
举报原因:
原因补充:

(最多只允许输入30个字)