使用Tensorflow创建自己的数据集,并训练
介绍环境
win10 + pycharm + CPU
介绍背景
要求用卷积神经网络对不同水分的玉米进行分类(最后的目标是实现回归,以后研究),神经网络虽然是科研神器,但是在工业上的应用效果远远不如实验室中的好。我们找到的教程无非是mnist,表情识别,等官方的数据集。对于一个小白来说虽然上手容易,但是收获这得有限。这篇博客我希望把每个知识都讲到尽量的通俗易懂,希望这篇处女作可以给小白指导。文中部分代码参考了ywx1832990,在此感谢。受限于水平,有讲解错误的地方,也欢迎留言探讨。
话不多说 直接上代码
step1:建立两个TFrecords
# pycharm中此模块名为genertateds.py import os import tensorflow as tf from PIL import Image # 源数据地址 cwd = r\'C:\Users\pc\Desktop\orig_picture\' # 生成record路径及文件名 train_record_path =r"C:\Users\pc\Desktop\outputdata\train.tfrecords" test_record_path =r"C:\Users\pc\Desktop\outputdata\test.tfrecords" # 分类 classes = {\'11.8\',\'13\',\'14.8\',\'16.5\',\'18\',\'20.6\',\'22.8\',\'26.1\',\'28.7\',\'30.6\'} def _byteslist(value): """二进制属性""" return tf.train.Feature(bytes_list = tf.train.BytesList(value = [value])) def _int64list(value): """整数属性""" return tf.train.Feature(int64_list = tf.train.Int64List(value = [value])) def create_train_record(): """创建训练集tfrecord""" writer = tf.python_io.TFRecordWriter(train_record_path) # 创建一个writer NUM = 1 # 显示创建过程(计数) for index, name in enumerate(classes): class_path = cwd + "/" + name + \'/\' l = int(len(os.listdir(class_path)) * 0.7) # 取前70%创建训练集 for img_name in os.listdir(class_path)[:l]: img_path = class_path + img_name img = Image.open(img_path) img = img.resize((128, 128)) # resize图片大小 img_raw = img.tobytes() # 将图片转化为原生bytes example = tf.train.Example( # 封装到Example中 features=tf.train.Features(feature={ "label":_int64list(index), # label必须为整数类型属性 \'img_raw\':_byteslist(img_raw) # 图片必须为二进制属性 })) writer.write(example.SerializeToString()) print(\'Creating train record in \',NUM) NUM += 1 writer.close() # 关闭writer print("Create train_record successful!") def create_test_record(): """创建测试tfrecord""" writer = tf.python_io.TFRecordWriter(test_record_path) NUM = 1 for index, name in enumerate(classes): class_path = cwd + \'/\' + name + \'/\' l = int(len(os.listdir(class_path)) * 0.7) for img_name in os.listdir(class_path)[l:]: # 剩余30%作为测试集 img_path = class_path + img_name img = Image.open(img_path) img = img.resize((128, 128)) img_raw = img.tobytes() # 将图片转化为原生bytes # print(index,img_raw) example = tf.train.Example( features=tf.train.Features(feature={ "label":_int64list(index), \'img_raw\':_byteslist(img_raw) })) writer.write(example.SerializeToString()) print(\'Creating test record in \',NUM) NUM += 1 writer.close() print("Create test_record successful!") def read_record(filename): """读取tfrecord""" filename_queue = tf.train.string_input_producer([filename]) # 创建文件队列 reader = tf.TFRecordReader() # 创建reader _, serialized_example = reader.read(filename_queue) features = tf.parse_single_example( serialized_example, features={ \'label\': tf.FixedLenFeature([], tf.int64), \'img_raw\': tf.FixedLenFeature([], tf.string) } ) label = features[\'label\'] img = features[\'img_raw\'] img = tf.decode_raw(img, tf.uint8) img = tf.reshape(img, [128, 128, 3]) img = tf.cast(img, tf.float32) * (1. / 255) - 0.5 # 归一化 label = tf.cast(label, tf.int32) return img, label def get_batch_record(filename,batch_size): """获取batch""" image,label = read_record(filename) image_batch,label_batch = tf.train.shuffle_batch([image,label], # 随机抽取batch size个image、label batch_size=batch_size, capacity=2000, min_after_dequeue=1000) return image_batch,label_batch def main(): create_train_record() create_test_record() if __name__ == \'__main__\': main()
注1:这里值得一提的是 from PIL import Image 在jupyter中某次更新后,会出现无法使用的现象。建议使用jupyter的朋友不要更新。如果更新了可以卸载,重新安装之前的版本
注2: windows下 cwd = r’C:\Users\pc\Desktop\orig_picture’的执行可能会产生一些操作系统层面的格式错误,在下面必须严格遵守 class_path = cwd + “/” + name + ‘/’ 这种格式,否则会出现格式错误。
解析:
1. train_record_path =r"C:\Users\pc\Desktop\outputdata\train.tfrecords"
test_record_path =r"C:\Users\pc\Desktop\outputdata\test.tfrecords"
这两句代码的意思是定义了两个路径,因为这个模块的作用就是创建TFrecord(一种Tensorflow中管理数据的格式,只要想使用Tensorflow,就需要用TFrecord。TFRecord内部使用的是二进制编码,它可以很好的把数据一次性通过一个二进制文件读取进来,而不是一张图片一张图片的读取,节省了时间,增加了效率。)所以需要告诉计算机一个保存的路径
2. class 中不同的数字 classes = {‘11.8’,‘13’,‘14.8’,‘16.5’,‘18’,‘20.6’,‘22.8’,‘26.1’,‘28.7’,‘30.6’} 就是不同的水分
3. writer = tf.python_io.TFRecordWriter() 这一部分是一个TFRecord的生成器,一般伴随着writer.write(),使用完之后需要关闭生成器,即:writer.close()
4. tf.train.Example:可以理解为一个包含了Features的内存块,并通过feature将图片的二进制数据和label进行统一封装(把数据和标签统一存储), 然后将example转化为一种字符串的形式, 使用tf.python_io.TFRecordWriter() 写入到TFRecords文件中。
5. def read_record(filename) 意思是把刚刚创建的tfrecord读取进来 ,至于这里面的函数具体定义了什么,不要深究,我们不是造*,会修改其中的关键信息即可,如reshape成128*128的三通道图片。
6. tf.cast() 的意思是把tensorflow中的张量数据做一个类型转换,转换成了float32类型
7. def get_batch_record 获取批的图片数据和标签 。在这里使用了刚刚定义的read_record。tf.train.shuffle_batch是不按照顺序的从队列中读取数据,最后找两个变量image_batch和label_batch接收一下。capacity参数的意义是队列中元素的最大数量,这个无所谓,别设置太小,也别太大,根据你的数据集来决定。
step2:配置图片的参数+定义前向传播过程
#这个模块在pycharm中的名字是forward.py import tensorflow as tf # 配置参数 # 图片size IMAGE_SIZE = 128 NUM_CHANNELS = 3 NUM_LABELS = 10 # 第一层卷积层的尺寸和深度 CONV1_DEEP = 64 CONV1_SIZE = 5 # 第二层卷积层的尺寸和深度 CONV2_DEEP = 128 CONV2_SIZE = 5 # 全连接层的节点个数 FC_SIZE = 10 def get_Weight(shape,regularizer_rate = None): # 定义weight如需正则化需传入zhengzehualv默认值为None Weight = tf.Variable(tf.truncated_normal(shape=shape,stddev=0.1),dtype=tf.float32) # tensorflow API推荐随机初始化 if regularizer_rate != None: regularizer = tf.contrib.layers.l2_regularizer(regularizer_rate) tf.add_to_collection(\'losses\',regularizer(Weight)) return Weight def get_biase(shape): # 定义biase biase = tf.Variable(tf.constant(value=0.1,shape=shape),dtype=tf.float32) # tensorflow API推荐初始化0.1 return biase def create_conv2d(x,w): # 定义卷积层 conv2d = tf.nn.conv2d(x,w,strides=[1,1,1,1],padding=\'SAME\') # 步幅为1、SAME填充 return conv2d def max_pooling(x): # 定义最大值池化 pool = tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding=\'SAME\') # ksize为2、步幅为2、SAME填充 return pool def create_fc(x,w,b): # 定义全连接层 fc = tf.matmul(x,w) + b return fc # 定义前向传播的过程 # 这里添加了一个新的参数train,用于区分训练过程和测试过程。 def inference(input_tensor, train, regularizer_rate): with tf.variable_scope(\'layer1-conv1\'): conv1_Weights = get_Weight([CONV1_SIZE,CONV1_SIZE,NUM_CHANNELS,CONV1_DEEP]) # 5*5*64 conv1_baises = get_biase([CONV1_DEEP]) conv1 = tf.nn.bias_add(create_conv2d(input_tensor,conv1_Weights),conv1_baises) conv1 = tf.nn.relu(conv1) # 使用ReLu激活函数 with tf.name_scope(\'layer2-pool1\'): # 64*64*64 pool1 = max_pooling(conv1) with tf.variable_scope(\'layer3-conv2\'): conv2_Weights = get_Weight([CONV2_SIZE,CONV2_SIZE,CONV1_DEEP,CONV2_DEEP]) # 5*5*128 conv2_biases = get_biase([CONV2_DEEP]) conv2 = tf.nn.bias_add(create_conv2d(pool1,conv2_Weights),conv2_biases) conv2 = tf.nn.relu(conv2) with tf.name_scope(\'layer4-pool2\'): # 32*32*128 pool2 = max_pooling(conv2) pool_shape = pool2.get_shape().as_list() # pool_shape为[batch_size,32,32,128] # 计算将矩阵拉直成向量之后的长度,这个长度就是矩阵长度及深度的乘积。 nodes = pool_shape[1] * pool_shape[2] * pool_shape[3] # 通过tf.reshape函数将第四层的输出变成一个batch的向量 reshaped = tf.reshape(pool2, [pool_shape[0], nodes]) # 声明第五层全连接层的变量并实现前向传播过程 with tf.variable_scope(\'layer5-fc1\'): fc1_Weights = get_Weight([nodes,FC_SIZE],regularizer_rate) fc1_biases = get_biase([FC_SIZE]) fc1 = tf.nn.relu(create_fc(reshaped,fc1_Weights,fc1_biases)) # 训练过程添加dropout防止过拟合 if train: fc1 = tf.nn.dropout(fc1, 0.5) # 声明第六层全连接层的变量并实现前向传播过程 with tf.variable_scope(\'layer6-fc2\'): fc2_Weights = get_Weight([FC_SIZE,NUM_LABELS],regularizer_rate) fc2_biases = get_biase([NUM_LABELS]) logit = create_fc(fc1,fc2_Weights,fc2_biases) # fc2 = tf.nn.relu(fc2) 需要softmax层时使用激活函数 return logit
注1:在写代码的过程中,我们应该把需要配置的参数在开头声明出来(一边写,写到哪个参数,就返回开头定义变量)
1. IMAGE_SIZE = 128 呼应了上一个模块中的reshape
2. NUM_CHANNELS = 3 这里涉及到图像的知识,简单来说,设置为3就是彩色图片,设置为1就是灰度图
3. NUM_LABELS = 10 就是你所提前设置好的标签数(即需要分成多少个类)
4. CONV1_DEEP = 64 这里的conv卷积层,其实就是我们所说的filter。这里深度deep的意思就是一次性使用多少个filter (就是过滤器的个数,使用几个不同的过滤器,就是确定了深度是几)
5. CONV1_SIZE = 5 确定卷积(filter)的size,即使用55的"观察窗口" 这样我们第一层的信息量就变成了了55*64
第二层卷积层同理(不懂的小伙伴可以留言)
6. FC_SIZE = 10 定义全连接层的个数,全连接层的一般是两层,最后一层全连接层就是输出层。全连接层的作用就是特征的加权,最后10个类的所有权值加在一起应该是等于1的。
7. def get_Weight 这里定义的是后面需要的权重值。
8. tf.truncated_normal(shape, mean, stddev) :shape表示生成张量的维度(这一部分后面用到了,注意一下),mean是均值,stddev是标准差。这个函数产生正太分布,均值和标准差自己设定。
9. regularizer_rate的意思是选择一种正则化方式,没有指定的话默认使用l2正则化。l2正则化简单理解就是可以去掉权重值中的超过两次幂的高此项,个人理解是防止过拟合用的。
10. def get_biase(shape): 定义一个偏置
11. tf.constant用来定义一个常量,这里的value一般初始化成一个常数或者一个数组。这里初始化成0.1是没有为什么的,不一定非要使用0.1。shape参数和卷积层的深度有关(即,和使用的filter数量有关,不能像0.1一样写死了,是需要变化的)
12. tf.Variable(initializer,name)用来初始化一个变量。参数initializer是初始化参数,name是可自定义的变量名称
13. def create_conv2d(x,w)定义一个卷积层,这部分也是很核心的部分
14. tf.nn.conv2d(x,w,strides=[1,1,1,1],padding=‘SAME’)
x是输入的张量数据 w是权值
strides=[1,1,1,1] (解释一下这个参数,其实不需要过多的纠结,只需要记住,strides在官方定义中就必须是一个一维且具有四个元素的张量,其规定前后必须为1,所以四个参数直接定死了两个,中间的两个参数一般而言都是相同的,步长为1就写 strides=[1,1,1,1] ,步长为2就写 strides=[1,2,2,1] ,由于步长不能过大,因为会损失大量的信息,所以一般选择步长要么为1,要么为2)
padding = ‘SAME’ 同样不需要纠结,就使用SAME即可。本质上说padding是一种filter进行卷积时的策略,一共只有两种策略可以选择,这部分到后面的调优环节再考虑不迟,通常情况下,直接使用SAME是完全没有问题的
15. def max_pooling(x) 池化层的表面形式来看,十分类似于之前的卷积层。但是二者做的事情是不一样的。池化层的作用是为了避免无关的信息过多,干扰结果,或者影响计算速度所进行的一种降维,是一种特征工程。注意:池化层的输入就是卷积层的输出,这里隐含了一个条件,就是池化层必须在卷积层之后。
16. tf.nn.max_pool(value, ksize, strides, padding, name=None)中 第一个参数value是最需要注意的,这里的value输入的是feature map(在卷积层中,数据是以一维或者三维的形式存在的。RGB图片可以理解为3个二维图片叠在一起,其中每一个称为一个feature map。而灰度图就仅仅只存在一个feature map)
17. ksize是池化窗口的大小,取一个四维向量,一般是[1, 1, 1, 1]或者[1, 2, 2, 1]。如果在这里你还在纠结为什么是[1, 1, 1, 1]或者[1, 2, 2, 1],大可不必。在学习的初期,我们不应该被这种细枝末节的细节去分散走我们大多的精力,耗散我们的耐心,降低我们的成就感。你只需要知道,大家都这么用就好。
18. strides和padding参数的设置同上(没明白的请留言)
19. def create_fc(x,w,b): 定义一个全连接层,全连接层做的事情,前面已经有提过这里不做赘述,还希望深入了解全连接层作用的同学可以自行查找一些资料
20. def inference(input_tensor, train, regularizer_rate): 定义一个函数,构造神经网络:神经网络的结构为:输入→卷积层→池化层→卷积层→池化→全连接→全连接。
21. train这个参数在后面坐了一次if判断,关系到是否要进行dropout
dropout可以理解为在防止过拟合。他的具体过程简要来说,就是不一次性给机器看到所有的数据,每次训练都随机较少一部分数据。
22. with tf.name_scope的意思就是方便你看代码的,类似于命名空间,不理解可以不看这部分,照着写就可以了。无关痛痒。
在代码运行的中间部分位置使用了一种激活函数–relu。relu函数可以理解为一种可以让机器学习到非线性特征的激活函数。因为我们之前做的无非是矩阵运算,这些都是属于线性运算(要不为啥是线性代数呢…)然而真是的世界中,事物往往都不是线性存在的,加入relu之后我们可以让机器学到这种非线性的特征
step3:定义反向传播网络
如果说之前我们在做的的是从前往后推导,模拟的是人类神经元的思维过程。那么反向传播网络在我看来,就已经超出了人类生物模型的范围,而变成了一种数学上的推导。可以说,在从前往后推导中我们已经建立一种可以用的模型,但是这个模型可以说是“惨不忍睹”,准确率低的令人大致,loss高得不忍直视。我们的前辈想到一种方法,提高模型的准确率。就是所谓的反向传播。通俗解释就是,基于现在的准确率,反向优化建模中所有的参数,再用经过优化的参数,重复建模的过程,再得到一个准确率,一般来说,经过一次优化的参数会提高模型的准确率。那么不断重复之前的过程,重复n多次,在不出现过拟合的前提下,我们最终就可以得到一个很优秀的模型。下面上代码
#这个模块命名为backward.py #这里需要注意,import genertateds 和 import forward 导入的是刚才定义好的模块,这里体现出了python的继承特点。对python理解不深的同学需要花时间理解一下什么是继承 import tensorflow as tf import forward import os import genertateds # 定义神经网络相关参数 BACTH_SIZE = 100 LEARNING_RATE_BASE=0.1 LEARNING_RATE_DECAY = 0.99 REGULARAZTION_RATE = 0.0001 TRAINING_STEPS = 150 MOVING_AVERAGE_DECAY = 0.99 train_num_examples = 500 # 模型保存的路径和文件名 MODEL_SAVE_PATH = "LeNet5_model_of_corn1234/" MODEL_NAME = "LeNet5_model_of_corn1234" # 定义训练过程 def train(): # 定义输入输出的placeholder x = tf.placeholder(tf.float32, [ BACTH_SIZE, forward.IMAGE_SIZE, forward.IMAGE_SIZE, forward.NUM_CHANNELS]) y_ = tf.placeholder(tf.int32, [None], name=\'y-input\') # label为int类型 y = forward.inference(x,True,REGULARAZTION_RATE) # 训练过程需要使用正则化 global_step = tf.Variable(0, trainable=False) # 记录step、不可训练的变量 # 定义滑动平均类 variable_average = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step) variable_average_op = variable_average.apply(tf.trainable_variables()) # 定义损失函数 # cross_entropy_mean = tf.reduce_mean(tf.square(y - y_)) # 使用softmax层时的loss函数 cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=y_) cross_entropy_mean = tf.reduce_mean(cross_entropy) loss = cross_entropy_mean + tf.add_n(tf.get_collection(\'losses\')) # 定义指数衰减学习率 learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE, global_step, train_num_examples/BACTH_SIZE, LEARNING_RATE_DECAY, staircase=True) # 使用AdamOptimizer优化器、记录step train_step = tf.train.AdamOptimizer(learning_rate).minimize(loss,global_step=global_step) # 控制计算流程(自己这么理解的...) with tf.control_dependencies([train_step, variable_average_op]): train_op = tf.no_op(name=\'train\') # 初始化TensorFlow持久化类 saver = tf.train.Saver() # 读取训练集 image_batch,label_batch = genertateds.get_batch_record(genertateds.train_record_path,100) with tf.Session() as sess: # 初始化所有变量 init_op = tf.global_variables_initializer() sess.run(init_op) # 断点检查 ckpt = tf.train.get_checkpoint_state(MODEL_SAVE_PATH) # 有checkpoint的话继续上一次的训练 if ckpt and ckpt.model_checkpoint_path: saver.restore(sess,ckpt.model_checkpoint_path) # 创建线程 coord = tf.train.Coordinator() threads = tf.train.start_queue_runners(sess,coord) # 开始训练 for i in range(TRAINING_STEPS): xs, ys = sess.run([image_batch,label_batch]) _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x:xs,y_:ys}) # 每20轮保存一次模型 if i % 20 == 0: # 输出当前的训练情况 print("After %d training step(s),loss on training batch is %g." % (step, loss_value)) # 保存当前模型 saver.save( sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step) # 关闭线程 coord.request_stop() coord.join(threads) def main(): train() if __name__ == \'__main__\': main()
1. BACTH_SIZE = 100
这个参数是批尺寸。这是在说,每次拿全部数据中的的一部分过来训练。减少单次的数量,减少时间。如果本身的数据不是很多的话,完全可以直接把全部数据拿过来训练,不需要BACTH_SIZE 这个参数。其实我本身的数据也不是很多,设置这个参数是为了通用性。需要注意的是BACTH_SIZE的值不能太大,也不能太小。太大内存吃不消,也打不到分批的效果。太小可能会出现由于每次取的batch差异性太大,从而使梯度互相抵消,造成结果不收敛。
2. LEARNING_RATE_BASE=0.1 (学习率的基值)
3. LEARNING_RATE_DECAY = 0.99 (一般设为接近于1的值)
这两个参数可以合在一起说,因为一般而言,在训练模型的初期,学习率是比较大的,随着训练的步数越来越多,再使用很大的学习率会出现无法收敛的现象(步子太大,本来很小的步子就可以达到最优点,可是直接迈过了最优点。所以经验而谈,最开始可以大步慢走,越深入,越接近最优点,就应该使用小步快跑的方式)这两个参数在后面就是tensorflow中一个调整学习率的API需要的参数。所谓的LEARNING_RATE_BASE=0.1就是初始学习率,LEARNING_RATE_DECAY = 0.99就是学习率的衰减率
4. REGULARAZTION_RATE = 0.0001 这个参数可以参看前面我对forward.py的描述
5. TRAINING_STEPS = 150 这个很好理解,就是后面训练的步数
6. MOVING_AVERAGE_DECAY = 0.99
7. train_num_examples = 500
这两个参数我们暂时按下不表,后面马上会说到
8. MODEL_SAVE_PATH = “LeNet5_model_of_corn1234/”
9. MODEL_NAME = “LeNet5_model_of_corn1234”
以上这两步看着不起眼,但是确实是整个系统中十分重要的一步,也是反向传播网络的灵魂。笔者开始也是在这个位置没有深入理解,导致后面的思维偏离正规。我们需要再次确认,我们现在定义的这个神经网络是在干什么?我希望看到这篇文章的人都可以先不要往后看。先思考5分钟。明白神经网络在干什么,这是对一个新手来说是非常非常重要的。
那么我们下面公布答案,最简单的话说,就是建模。
不管我们在神经网络中,把程序写的多么花哨,多么天花乱坠,神经网络做的事情也仅仅是建模。为什么在网上看到神经网络,最后输出的往往是“准确率”? 难道不是应该对我们的输入进行分类,最后给出一个明确的结果吗?为什么会是一个所谓的“准确率”呢?
不是的,在我们这个构建神经网络的过程中,我们在做的是建模,后面的准确率的本质是,你的测试集在对你构建的模型进行评估,评估模型的准确率。在这里其实并不涉及到具体的分类。
那么具体的分类怎么做呢?答案是,把训练好的模型保存下来,然后再写另外一套别的程序(这部分,我自己还没有具体的理性认识,研究明白了后面会写新的博客)然后我们就要说道刚刚的两句代码了,这两句的代码其实就是在声明一个模型保存的路径和文件名,便于我们以后加载自己的模型,对新的输入进行评估。
10. def train():这个 模块整个就是在具体的表现训练的过程
11. x和 y_是两个占位符。所谓占位符,也是不需要太过纠结的细节问题,简单说,就是数据不会是上来一次性全都给你的,而是需要在不同的时间点,分批次给你的。也就是说,输入是在变化的,那么这样,我们在写程序的时候就不可以把程序的输入写死。定义占位符可以完美的解决问题,数据不断的被输入到占位符,程序每次向占位符要数据,然后占位符中的数据更新。
12. y = forward.inference(x,True,REGULARAZTION_RATE) 这里用了forward.py模块中的一个函数,不理解的同学大可以向上翻。
13. global_step = tf.Variable(0, trainable=False) 在这里定义了一个“不可训练的变量” ,这个不可训练的变量需要人为设置,并将该参数传入我们的模型。这个参数在后面马上可以用到。
14. variable_average = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
variable_average_op = variable_average.apply(tf.trainable_variables())
这两句代码定义了一个滑动平均类,什么是滑动平均类呢?简而言之,它也是用来更新参数的一个函数,而这种更新参数的方式就是滑动平均,这仅仅是一个名字而已。这个函数初始化需要提供一个衰减速率(decay),用于控制模型的更新速度。MOVING_AVERAGE_DECAY就是这里的衰减速度。可以看到,第二不到吗,等式左边的参数是可以更新的变量,global不参与这里的更新,所以把它设置为不可更新。
15. cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=y_)
cross_entropy_mean = tf.reduce_mean(cross_entropy)
loss = cross_entropy_mean + tf.add_n(tf.get_collection(‘losses’))
这一部分是在定义交叉熵损失函数,交叉熵softmax是在做什么事呢?其实softmax说白了就是在做回归处理。神经网络的本来输出的并不是概率,而是一些经过了复杂的加权,加偏置,非线性处理之后的一个值而已,如何才能让这样一个值变成我们理解的概率值?这就需要softmax。softmax=一部分/一个整体,最后输出的自然就是一个概率值了。(这里不写公式,想看的话网上到处都有) loss最后就等于经过交叉熵之后对张量取平均
16. learning_rate = tf.train.exponential_decay(LEARNING_RATE_BASE,global_step,train_num_examples/BACTH_SIZE, LEARNING_RATE_DECAY, staircase=True)
这里再定义指数衰减学习率,LEARNING_RATE_BASE是学习率基础值。global_step就是那个手动指定的不能训练的参数
17. train_num_examples/BACTH_SIZE是衰减的速度,即更新学习率的速度(注意区分,不是学习率本身的变化幅度,而是多长时间需要去变一次。即总训练样本/每次的feed的batch)LEARNING_RATE_DECAY前面提过,是学习率的衰减率(即学习率本身变化的幅度)staircase=True的意思就是梯度衰减,False为平滑衰减。这里借用一张图来解释,何为梯度衰减,何为平滑衰减
18. train_step = tf.train.AdamOptimizer(learning_rate).minimize(loss,global_step=global_step)
这里需要说明的是,我们最常使用的优化器有两种:随机梯度下降法(SGD) 与 AdamOptimizer,这里我们仅仅需要站在巨人的肩膀上就好。
随机梯度下降的意思,就是在不断的靠近局部(全局)最优点的过程,不断的求导,求导,来达到导数为0的点。每一步求导,基于当前在随机数据批量上的损失,一点点的对参数进行调节
而我们使用AdamOptimizer优化器、记录step。它属于梯度下降的一个分支。它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。可以更好地控制学习速度,使得每一次迭代的学习率都会在一个范围内,不会过大或者过小。
19. with tf.control_dependencies([train_step, variable_average_op]):
train_op = tf.no_op(name=‘train’)
这里是一个上下文管理器(我自己依旧是按照命名空间来理解的)这部分代码完全不需要背,可以理解为一个固定流程
20. saver = tf.train.Saver()
image_batch,label_batch = genertateds.get_batch_record(genertateds.train_record_path,100)
这两步,对于有基础的人,应该比较好理解。无非是一个保存模型,一个用来读取训练集中的图片和标签。
21. with tf.Session() as sess:
这里面在做的事有:
1.初始化所有变量(必须加,不要问为什么,加就好了。Tensorflow要求你必须加这两步,否则不work)
2.断点检查 这个意思是你可以在上次训练的模型基础上,接着训练,不需要每次都从头开始
3.创建线程 对于线程,进程,携程不理解的同学需要自行google,这里把这个知识点展开了将不现实(如果需要以后开新的文章介绍) 总而言之,就是你不能把所用东西一次性丢给计算机,而是需要建立一个线程,主线程执行你本应该执行的代码,子线程可以去分批次的拿数据,子线程拿过来一点,主线程就训练一点,边拿边训练。(就好像是本来只有一只手干活,这是手是主线程。主线程忙着的时候,子线程就可以去做一些辅助主线程的工作,来配合子线程)
4.训练,每10轮保存一次模型(随时可以中断,再开启时会自动从上次中断的位置来,生成ckpt文件)
step4:输出测试集的测试结果
#这个模块我命名为test.py #导入之前的三个模块,分别是genertateds,forward,backward import time import forward import backward import genertateds import tensorflow as tf # 等待时间 TEST_INTERVAL_SECS = 5 # 总测试集样本数量 test_num_examples = 20 def test(): with tf.Graph().as_default() as g: x = tf.placeholder(tf.float32,[test_num_examples, forward.IMAGE_SIZE, forward.IMAGE_SIZE, forward.NUM_CHANNELS]) y_ = tf.placeholder(tf.int64,[None]) # 测试过程不需要正则化和dropout y = forward.inference(x,False,None) # 还原模型中的滑动平均 variable_average = tf.train.ExponentialMovingAverage(backward.MOVING_AVERAGE_DECAY) variable_average_restore = variable_average.variables_to_restore() saver = tf.train.Saver(variable_average_restore) # 计算准确率 correct_prediction = tf.equal(tf.argmax(y,1),y_) accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32)) image_batch,label_batch = genertateds.get_batch_record(genertateds.test_record_path,20) while True: with tf.Session() as sess: ckpt = tf.train.get_checkpoint_state(backward.MODEL_SAVE_PATH) if ckpt and ckpt.model_checkpoint_path: coord = tf.train.Coordinator() threads = tf.train.start_queue_runners(sess, coord) image, label = sess.run([image_batch, label_batch]) saver.restore(sess,ckpt.model_checkpoint_path) # 从文件名称中读取第几次训练 global_step = ckpt.model_checkpoint_path.split(\'/\')[-1].split(\'-\')[-1] accuracy_score = sess.run(accuracy,feed_dict={x:image,y_:label}) coord.request_stop() coord.join(threads) print("After %s training step(s),test accuray = %g"%(global_step,accuracy_score)) else: time.sleep(TEST_INTERVAL_SECS) def main(): test() if __name__ == \'__main__\': main()
1. #等待时间
TEST_INTERVAL_SECS = 5
2. #总测试集样本数量
test_num_examples = 20
3. 这里再声明两个变量,声明等待时间在后面会用到。time.sleep(TEST_INTERVAL_SECS)的意思是让程序执行到这里,休眠5秒钟,什么都不做。(这个时候cpu可以去处理其他的任何事,只是当前的程序需要休眠)。 test_num_examples = 20是后面的占位符需要的参数
4. def test():
这个函数里做了什么事呢?
首先明确一点,模块名字是test,那么自然,这个函数做的就是验证模型的好坏。怎么评价模型的还坏?自然是对比着验证集来看模型准确率
x和y_是两个占位符,前面已经提过
y由于是在测试,没有正则化处理
5. variable_average = tf.train.ExponentialMovingAverage(backward.MOVING_AVERAGE_DECAY)
variable_average_restore=variable_average.variables_to_restore()
saver = tf.train.Saver(variable_average_restore)
这里做的之前也有讲过,不再赘述
6. tf.argmax(input,axis)用来返回返回每行或者每列最大值的索引
7. tf.equal是一个判等函数
结合起来就是在判断,最大索引值,和测试集中传进来的值是否一致
8. accuracy= tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
image_batch,label_batch=genertateds.get_batch_record(genertateds.test_record_path,20)
这两句代码的细节在上个模块中也提到了,忘记了不妨回看,加深印象
9. while True:循环
不断的从ckpt中找到训练的模型,用来提供给后面,进行test
到此 一个完整的使用数据集搭建神经网络的过程就完成了闭环。