代码引用:https://github.com/ganyc717/LeNet
讲真,研一开始了这么久,我还从未自己从头到尾理解和构建一个神经网络,虽然总是跟别人讨论cnn什么的。
事情总要自己来干,那就从lenet开始吧!
先放上lenet的大图;我们从零开始构建网络。
首先,原来我以为构建一个神经网络就是这个神经网络的问题,但是后来发现,不仅仅是构建网络的问题,首先,你要去适应Tensorflow的规则和机制。这就好像,你进入一个新的领域,不是马上就可以施展手脚的,你要先明白这个地方做事有什么规则,不要觉得禁锢,在规则内行事也并不一定不能实现自己~
封装的思想
哎,代码量真的太少,封装的思想太差了。首先基本的封装思想就是,训练一个文件,模型一个文件。
这个思想贯穿整个编码过程。一定要从一开始就有分割的想法。
训练文件
训练文件其实就是整个训练的一个过程。我们先读取数据,然后在进行训练,进行参数的更替。最后保存训练的参数。
读取文件的方法暂且不表,待会再说。
先说一下tensorflow的运行机制,通过session进行运行。这也就是为什么大家都说tf是通过计算图来构建的。
刚开始接触tf的时候一直有一个疑问,为什么我们不能直接通过python中的机制来进行运算呢?其实这就是我上面所说的规则。
引用一段话:
TensorFlow的运行机制属于"定义"与”运行“相分离。从操作层面可以抽象成两种:构造模型和模型运行。
在讲解构建模型之前,需要讲解几个概念。在一个叫做"图"的容器中包括:
- 张量(tensor):TensorFlow程序使用tensor数据结构来代表所有的数据,计算图中,操作间传递的数据都是tensor,你可以把TensorFlow tensor看做一个n维的数组或者列表。
- 变量(Variable):常用于定义模型中的参数,是通过不断训练得到的值。比如权重和偏置。
- 占位符(placeholder):输入变量的载体。也可以理解成定义函数时的参数。
- 图中的节点操作(op):一个op获得0个或者多个Tensor,执行计算,产生0个或者多个Tensor。op是描述张量中的运算关系,是网络中真正结构。
一个TensorFlow图描述了计算的过程,为了进行计算,图必须在会话里启动,会话将图的op分发到诸如CPU或者GPU的设备上,同时提供执行op的方法,这些方法执行后,将产生的tensor返回,在python语言中,返回的tensor是numpy array对象,在C或者C++语言中,返回的tensor是tensorflow:Tensor实例。
session与图的交互过程中定义了以下两种数据的流向机制。
- 注入机制(feed):通过占位符向模式中传入数据。
- 取回机制(fetch):从模式中取得结果。
理解一下,通常学习的语言中都是写代码的过程即运行的的过程。但tensorflow不是,你写的很多代码,如果不喊开始,数据是不会进入这个框架的;你定义了参数,定义了张量,只是说了要做这样的操作,但,并没有去做,要通过session来进行调用,将所有的参数包裹进来,也就是使用global_variables_intializer()来开始整个过程。
训练的batch
当我们看理论知识的时候,神经网络的更新是一次次更新的;
但训练中我们往往使用mini-batch进行训练,其训练方法是什么呢?
我们看图吧:
t 代表第几个子集,从 1 到 5000,因为划分后,一共有 5000 个子集,
1. 对每个子集,先进行前向计算,从第一层网络到最后一层输出层
因为 batch 梯度下降是对整个数据集进行处理,所以不需要角标,而 mini batch 这里需要对 x 加上角标,代表的是第几个子集。
2. 接下来计算当前子集的损失函数,因为子集中一共有 1000 个样本,所以这里要除以 1000。损失函数也是有上角标,和第几个子集相对应。
3. 然后进行反向传播,计算损失函数 J 的梯度。
4. 最后更新参数。
将 5000 个子集都计算完时,就是进行了一个 epoch 处理 ,一个 epoch 意思是遍历整个数据集,即 5000 个子数据集一次,也就是做了 5000 个梯度下降,
如果需要做多次遍历,就需要对 epoch 进行循环。当数据集很大的时候,这个方法是经常被使用的。
作者:Alice嘟嘟
链接:https://www.imooc.com/article/48566
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作
以上把batch的整个计算过程讲的很清楚了。开始学习的时候我们是一个样本进行更新,用batch的时候,我们把这些数据的loss相加求均值,当然也有一种方法,对这些loss取不同的权重。然后根据整体损失函数的值进行反向传播,更新参数。
注入机制和占位符
tensorflow有很多种建立参数的形式,其中一种是placeholder;它被称为占位符,就是我定义一个变量,定义它的数据类型,但是不赋值,等到用的时候再进行赋值。这就是注入机制,随时用随时注入。
'''
4.注入机制
'''
a = tf.placeholder(dtype=tf.float32)
b = tf.placeholder(dtype=tf.float32)
add = a + b
product = a*b
with tf.Session() as sess:
#启动图后,变量必须先经过'初始化' op
sess.run(tf.global_variables_initializer())
print(' a + b = {0}'.format(sess.run(add,feed_dict={a:3,b:4})))
print(' a * b = {0}'.format(sess.run(product,feed_dict={a:3,b:4})))
#一次取出两个节点值
print(' {0}'.format(sess.run([add,product],feed_dict={a:3,b:4})))
那在我们的代码中使用的原因,是因为每一次的batch不同,所以要进行注入。
模型保存和读取
通过tf.tran.Saver()实现。
本次文章分析的是Train.py文件:
import tensorflow.examples.tutorials.mnist.input_data as input_data
import tensorflow as tf
import config as cfg
import os
import lenet
from lenet import Lenet
def main():
#读取文件
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
#session机制
sess = tf.Session()
#参数读取
batch_size = cfg.BATCH_SIZE
parameter_path = cfg.PARAMETER_FILE
max_iter = cfg.MAX_ITER
#初始化网络
lenet = Lenet()
#参数的保存,使用train.saver
saver = tf.train.Saver()
#如果存在参数,就读取参数
if os.path.exists(parameter_path):
saver.restore(parameter_path)
else:
#建立所有随机变量之后,开始训练要先进行初始化;通过global_variables_intializer()
sess.run(tf.initialize_all_variables())
#经过50000次迭代,每次迭代50个数据
for i in range(max_iter):
# gain the batch
batch = mnist.train.next_batch(50)
# train the batch;其中train.op是训练函数;通过adamoptimizer实现
sess.run(lenet.train_op,feed_dict={lenet.raw_input_image: batch[0],lenet.raw_input_label: batch[1]})
if i % 100 == 0:
train_accuracy = sess.run(lenet.train_accuracy,feed_dict={
lenet.raw_input_image: batch[0],lenet.raw_input_label: batch[1]
})
print("step %d, training accuracy %g" % (i, train_accuracy))
save_path = saver.save(sess, parameter_path)
sess.close()
if __name__ == '__main__':
main()
五点半了,准备准备去吃饭饭咯~回来继续下一个文件的学习~