YJango:TensorFlow高层API Custom Estimator建立CNN+RNN的演示
目录
前言
机器学习
- 两大模块:数据、模型
- 三个阶段:训练、评估、预测
优势
实现
- 数据集:TFRecord+Dataset
- 定义input_fn
-
定义model_fn
-
正向传播
- CNN:二维卷积层
- RNN:循环层(双向循环层)
- CNN+RNN:一维卷积层+循环层
- 预测分支
- 训练分支
- 评估分支
-
- 创建estimator
- 训练
- 评估
- 预测
- 可视化
前言
该文是YJango:TensorFlow中层API Datasets+TFRecord的数据导入的后续。
知道了如何用中层API:Dataset来导入数据后,下面介绍如何接着用高层API:Estimator来用下面四个网络结构来完成mnist手写数字识别。
- 二维卷积神经网络
- 循环神经网络
- 双向循环神经网络
- 一维卷积+循环网络
机器学习
人们常说深度学习是黑箱,我们同样也希望使用深度学习可以像使用黑箱工具那样简单。可事实却并非如此。以监督学习为例:
我们的最终目标是:希望获得一个可以输入问题就能获得答案的算法。
可为了获得该算法,要先搜集数据,然后将数据处理成适应计算机或模型的形式。根据目的分成训练集、验证集、测试集。经过反复的选择、训练、调参、评估后确定最终投入应用的模型。
上述流程可分为训练、评估、预测三个阶段。不同阶段:
- 使用的模型和数据处理和记录操作是相同的。
- 使用的数据集和模型操作不同。
一、训练:
- 模型操作:正向传播+反向传播。遍历整个训练集若干次,用于更新模型权重。
- 数据集:训练集。
二、评估:
- 模型操作:正向传播。遍历每个数据集一次,算出评估指标来衡量模型表现。
- 数据集:会使用多个数据集进行评估,但意义不同。
- 训练集:评估模型能力是否足够,判断是否欠拟合。
- 验证集:其本质也属于训练集的一部分。评估模型的普遍性,和训练集的评估结果一起来判断是否过拟合。因为会根据验证集的结果来调整模型参数,所以模型间接的“见过”验证集的数据。
- 测试集:模型从未见过的数据,用于评估模型的最终表现,决定是否选择新模型。
注:训练集和验证集都无法作为最终表现的考核标准。评估模型的最终表现一定要用模型从未见过的数据。就如同考核高考解题能力的方法是考核我们从没做过的题目一样,因为我们可以记住做过题目的答案。
三、预测:
- 模型操作:正向传播。用训练好的模型算出所有预测值即可。
- 数据集:只有输入的实际应用数据。
优势
一、为什么用Estimator API?
正如先前所说机器学习可分为训练、评估、预测三个阶段,每个阶段又都会用到相同的模型和数据处理方法。
而Tensorflow的高层API:Estimator正是对共用部分使用通用方法,而在不同的阶段实现具体的控制。
共用部分:
- model_fn:构建模型
- input_fn:导入数据 + 数据集的处理(结合中层API:Dataset)
- 其他:帮助控制sessions、graphs、loops、logging
三个阶段:通过调用对应的train(), evaluate(), predict()方法来执行不同的阶段。
实现
一、数据集
这里直接使用上篇文章中所描述的方法(没看过的先看上一篇),将MNIST数据集先写成tfrecord文件,再用dataset API导入,进行batch,shuffle,padding等操作。需要文件:tfrecorder.py
1. 制作TFrecord文件
# 所需库包 import pandas as pd import numpy as np import tensorflow as tf # 需要从我给的github上获得tfrecorder from tfrecorder import TFrecorder from matplotlib import pyplot as plt import matplotlib.image as mpimg %pylab inline # mnist数据 mnist = tf.contrib.learn.datasets.load_dataset("mnist") # 指定如何写成tfrecord文件的信息 # 每一个row是一个feature df = pd.DataFrame({'name':['image','label'], 'type':['float32','int64'], 'shape':[(784,),()], 'isbyte':[False,False], "length_type":['fixed','fixed'], "default":[np.NaN,np.NaN]}) # 实例化该类 tfr = TFrecorder() # 写训练集和测试集的位置 mkdir mnist_tfrecord mnist_tfrecord/train mnist_tfrecord/test
使用tfr.feature_writer
方法创建样本写入字典,一个样本一个样本的写入TFRecord file中。
由于使用tfrecord时往往是拥有大量数据的情况,需要一点点写入。
1.1. 训练集
# 用该方法写训练集的tfrecord文件 dataset = mnist.train path = 'mnist_tfrecord/train/train' # 每个tfrecord文件写多少个样本 num_examples_per_file = 1000 # 当前写的样本数 num_so_far = 0 # 要写入的文件 writer = tf.python_io.TFRecordWriter('%s%s_%s.tfrecord' %(path, num_so_far, num_examples_per_file)) # 写多个样本 for i in np.arange(dataset.num_examples): # 要写到tfrecord文件中的字典 features = {} # 写一个样本的图片信息存到字典features中 tfr.feature_writer(df.iloc[0], dataset.images[i], features) # 写一个样本的标签信息存到字典features中 tfr.feature_writer(df.iloc[1], dataset.labels[i], features) tf_features = tf.train.Features(feature= features) tf_example = tf.train.Example(features = tf_features) tf_serialized = tf_example.SerializeToString() writer.write(tf_serialized) # 每写了num_examples_per_file个样本就令生成一个tfrecord文件 if i%num_examples_per_file ==0 and i!=0: writer.close() num_so_far = i writer = tf.python_io.TFRecordWriter('%s%s_%s.tfrecord' %(path, num_so_far, i+num_examples_per_file)) print('saved %s%s_%s.tfrecord' %(path, num_so_far, i+num_examples_per_file)) writer.close() # 把指定如何写成tfrecord文件的信息保存起来 data_info_path = 'mnist_tfrecord/data_info.csv' df.to_csv(data_info_path,index=False)
1.2. 测试集
# 用该方法写测试集的tfrecord文件 dataset = mnist.test path = 'mnist_tfrecord/test/test' # 每个tfrecord文件写多少个样本 num_examples_per_file = 1000 # 当前写的样本数 num_so_far = 0 # 要写入的文件 writer = tf.python_io.TFRecordWriter('%s%s_%s.tfrecord' %(path, num_so_far, num_examples_per_file)) # 写多个样本 for i in np.arange(dataset.num_examples): # 要写到tfrecord文件中的字典 features = {} # 写一个样本的图片信息存到字典features中 tfr.feature_writer(df.iloc[0], dataset.images[i], features) # 写一个样本的标签信息存到字典features中 tfr.feature_writer(df.iloc[1], dataset.labels[i], features) tf_features = tf.train.Features(feature= features) tf_example = tf.train.Example(features = tf_features) tf_serialized = tf_example.SerializeToString() writer.write(tf_serialized) # 每写了num_examples_per_file个样本就令生成一个tfrecord文件 if i%num_examples_per_file ==0 and i!=0: writer.close() num_so_far = i writer = tf.python_io.TFRecordWriter('%s%s_%s.tfrecord' %(path, num_so_far, i+num_examples_per_file)) print('saved %s%s_%s.tfrecord' %(path, num_so_far, i+num_examples_per_file)) writer.close() # 把指定如何写成tfrecord文件的信息保存起来 data_info_path = 'mnist_tfrecord/data_info.csv' df.to_csv(data_info_path,index=False)
二、生成input_fn
送入到Estimator中的input_fn
需要是一个函数,而不是具体的数据。
所以这里用input_fn_maker
来控制如何生成不同的input_fn
函数。
tfr = TFrecorder() def input_fn_maker(path, data_info_path, shuffle=False, batch_size = 1, epoch = 1, padding = None): def input_fn(): # tfr.get_filenames会返回包含path下的所有tfrecord文件的list # shuffle会让这些文件的顺序打乱 filenames = tfr.get_filenames(path=path, shuffle=shuffle) dataset=tfr.get_dataset(paths=filenames, data_info=data_info_path, shuffle = shuffle, batch_size = batch_size, epoch = epoch, padding =padding) iterator = dataset.make_one_shot_iterator() return iterator.get_next() return input_fn padding_info = ({'image':[784,],'label':[]}) # 生成3个input_fn test_input_fn = input_fn_maker('mnist_tfrecord/test/', 'mnist_tfrecord/data_info.csv', padding = padding_info) train_input_fn = input_fn_maker('mnist_tfrecord/train/', 'mnist_tfrecord/data_info.csv', shuffle=True, batch_size = 512, padding = padding_info) # 用来评估训练集用,不想要shuffle train_eval_fn = input_fn_maker('mnist_tfrecord/train/', 'mnist_tfrecord/data_info.csv', batch_size = 512, padding = padding_info) # input_fn在执行时会返回一个字典,里面的key对应着不同的feature(包括label在内)
完整代码可以在MNIST TFrecord.ipynb找到。
三、model_fn
模型函数必须要有features, mode两个参数,可自己选择加入labels(YJango习惯把label也放进features中)。最后要返回特定的tf.estimator.EstimatorSpec()
。
模型有三个阶段都共用的正向传播部分,和由mode
值来控制返回不同tf.estimator.EstimatorSpec
的三个分支。
def model_fn(features, mode): # reshape 784维的图片到28x28的平面表达,1为channel数 features['image'] = tf.reshape(features['image'],[-1,28,28,1])
1. 正向传播
这里使用tf.layers来搭建模型,
要点:
- 记住tensor在每层前后的shape
- 记住shape上每个dimension上的意义
- 给每一层都赋予name,用于debug和方便后续操作。
注:虽然我循序罗列了不同结构,但请一次只拿一个使用
注:关于下面网络的搭建,不明白的细节可以问我。
1.1. 二维卷积层
定义LeNet模型。
# shape: [None,28,28,1] conv1 = tf.layers.conv2d( inputs=features['image'], filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu, name = 'conv1') # shape: [None,28,28,32] pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2, name= 'pool1') # shape: [None,14,14,32] conv2 = tf.layers.conv2d( inputs=pool1, filters=64, kernel_size=[5, 5], padding="same", activation=tf.nn.relu, name = 'conv2') # shape: [None,14,14,64] pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2, name= 'pool2') # shape: [None,7,7,64] pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64], name= 'pool2_flat') # shape: [None,3136] dense1 = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu, name= 'dense1') # dropout只在当mode为tf.estimator.ModeKeys.TRAIN时才使用 dropout = tf.layers.dropout(inputs=dense1, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN) # shape: [None,1024] logits = tf.layers.dense(inputs=dropout, units=10, name= 'output') # shape: [None,10]
1.2. 循环层
定义两层循环层模型。
# shape: [None,28,28,1] # create RNN cells: rnn_cells = [tf.nn.rnn_cell.GRUCell(dim,kernel_initializer=tf.orthogonal_initializer) for dim in [128,256]] # stack cells for multi-layers RNN multi_rnn_cell = tf.nn.rnn_cell.MultiRNNCell(rnn_cells) # create RNN layers outputs, last_state = tf.nn.dynamic_rnn(cell=multi_rnn_cell, inputs=tf.reshape(features['image'],[-1,28,28]), dtype=tf.float32) # shape: outputs: [None,28,256] # shape: last_state: [None,256] dense1 = tf.layers.dense(inputs=last_state[1], units=1024, activation=tf.nn.relu, name= 'dense1') # shape: [None,1024] dropout = tf.layers.dropout(inputs=dense1, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN) logits = tf.layers.dense(inputs=dense1, units=10, name= 'output') # shape: [None,10]
1.3. 双向循环层
定义两个双向循环层模型。
# shape: [None,28,28,1] # create RNN cells: rnn_fcells = [tf.nn.rnn_cell.GRUCell(dim,kernel_initializer=tf.orthogonal_initializer) for dim in [128,256]] rnn_bcells = [tf.nn.rnn_cell.GRUCell(dim,kernel_initializer=tf.orthogonal_initializer) for dim in [128,256]] # stack cells for multi-layers RNN multi_rnn_fcell = tf.nn.rnn_cell.MultiRNNCell(rnn_fcells) multi_rnn_bcell = tf.nn.rnn_cell.MultiRNNCell(rnn_bcells) # create RNN layers ((outputs_fw, outputs_bw),(last_state_fw, last_state_bw)) = tf.nn.bidirectional_dynamic_rnn( cell_fw=multi_rnn_fcell, cell_bw=multi_rnn_bcell, inputs=tf.reshape(features['image'],[-1,28,28]), dtype=tf.float32) # shape: outputs: [None,28,256] # shape: last_state: [None,256] dense1 = tf.layers.dense(inputs=last_state_fw[1]+last_state_bw[1], units=1024, activation=tf.nn.relu, name= 'dense1') # shape: [None,1024] dropout = tf.layers.dropout(inputs=dense1, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN) logits = tf.layers.dense(inputs=dense1, units=10, name= 'output') # shape: [None,10]
1.4. 一维卷积层+循环层模型
定义一维卷基层+一维maxpool+循环层模型。
# shape: [None,28,28,1] conv1 = tf.layers.conv1d( inputs = tf.reshape(features['image'],[-1,28,28]), filters = 32, kernel_size = 5, padding="same", activation=tf.nn.relu, name = 'conv1') # shape: [None,28,32] pool1 = tf.layers.max_pooling1d(inputs = conv1, pool_size=2, strides=2, name = 'pool1') # shape: [None,14,32] # create RNN cells: rnn_cells = [tf.nn.rnn_cell.GRUCell(dim,kernel_initializer=tf.orthogonal_initializer) for dim in [128,256]] # stack cells for multi-layers RNN multi_rnn_cell = tf.nn.rnn_cell.MultiRNNCell(rnn_cells) # create RNN layers outputs, last_state = tf.nn.dynamic_rnn(cell=multi_rnn_cell, inputs=pool1, dtype=tf.float32) # shape: outputs: [None,14,256] # shape: last_state: [None,256] dense1 = tf.layers.dense(inputs=last_state[1], units=1024, activation=tf.nn.relu, name= 'dense1') # shape: [None,1024] dropout = tf.layers.dropout(inputs=dense1, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN) logits = tf.layers.dense(inputs=dense1, units=10, name= 'output') # shape: [None,10]
想要换成不同的模型,只需改变正向传播部分的代码即可。
注:不同网络结构训练1epoch+评估的结构可以在CNN.ipynb,RNN.ipynb,biRNN.ipynb,CNN_RNN.ipynb中找到。
注:CNN之后的模型并不意味着更好,这里只是为了展示你可以使用任何模型结构来进行学习。还有残差网络的跳层链接,batchnorm层,注意力机制等,至于如何选择,主要是在于你的任务是否具有符合这些特殊层的结构特点。详细的请参考:
2. 预测分支
# 创建predictions字典,里面写进所有你想要在预测值输出的数值 # 隐藏层的数值也可以,这里演示了输出所有隐藏层层结果。 # 字典的key是模型,value给的是对应的tensor predictions = { "image":features['image'], "conv1_out":conv1, "pool1_out":pool1, "conv2_out":conv2, "pool2_out":pool2, "pool2_flat_out":pool2_flat, "dense1_out":dense1, "logits":logits, "classes": tf.argmax(input=logits, axis=1), "labels": features['label'], "probabilities": tf.nn.softmax(logits, name="softmax_tensor") } # 当mode为tf.estimator.ModeKeys.PREDICT时,我们就让模型返回预测的操作 if mode == tf.estimator.ModeKeys.PREDICT: return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)
3. 训练分支
# 训练和评估时都会用到loss loss = tf.losses.sparse_softmax_cross_entropy(labels=features['label'], logits=logits) # 训练分支 if mode == tf.estimator.ModeKeys.TRAIN: optimizer = tf.train.AdamOptimizer(learning_rate=1e-3) train_op = optimizer.minimize( loss=loss, # global_step用于记录训练了多少步 global_step=tf.train.get_global_step()) # 返回的tf.estimator.EstimatorSpec根据 return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)
4. 评估分支
# 注意评估的时候,模型和训练时一样,是一个循环的loop,不断累积计算评估指标。 # 其中有两个局部变量total和count来控制 # 把网络中的某个tensor结果直接作为字典的value是不好用的 # loss的值是始终做记录的,eval_metric_ops中是额外想要知道的评估指标 eval_metric_ops = {"accuracy": tf.metrics.accuracy(labels=features['label'], predictions=predictions["classes"])} # 不好用:eval_metric_ops = {"probabilities": predictions["probabilities"]} return tf.estimator.EstimatorSpec(mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)
四、创建estimator
注意:送入的model_fn
和input_fn
一样是一个函数,不可以是model_fn()
返回结果
# model_dir 表示模型要存到哪里 mnist_classifier = tf.estimator.Estimator( model_fn=model_fn, model_dir="mnist_model_cnn")
训练后的模型参数会保存在model_dir中,随着训练在目录下生成拥有类似下面内容的checkpoint文件。
model_checkpoint_path: "model.ckpt-860" all_model_checkpoint_paths: "model.ckpt-1" all_model_checkpoint_paths: "model.ckpt-430" all_model_checkpoint_paths: "model.ckpt-431" all_model_checkpoint_paths: "model.ckpt-860"
当你再次运行相同model_dir的Estimator时,它默认会读取model_checkpoint_path: "model.ckpt-860"的模型权重。
如果想要改变读取的检查点到之前的某个时刻,可以改变:
model_checkpoint_path: "model.ckpt-431" all_model_checkpoint_paths: "model.ckpt-1" all_model_checkpoint_paths: "model.ckpt-430" all_model_checkpoint_paths: "model.ckpt-431" all_model_checkpoint_paths: "model.ckpt-860"
五、训练
1. 日志
# 在训练或评估的循环中,每50次print出一次字典中的数值 tensors_to_log = {"probabilities": "softmax_tensor"} logging_hook = tf.train.LoggingTensorHook(tensors=tensors_to_log, every_n_iter=50)
2. 训练
- hooks:如果不送值,则训练过程中不会显示字典中的数值
- steps:指定了训练多少次,如果不送值,则训练到dataset API遍历完数据集为止
- max_steps:指定了最大训练次数
mnist_classifier.train(input_fn=train_input_fn, hooks=[logging_hook])
如果再次运行,同时没有限制最大训练次数,那么网络会从最后一个checkpoint读取模型权重接着训练。
六、评估
# 训练集 eval_results = mnist_classifier.evaluate(input_fn=train_eval_fn, checkpoint_path=None) print('train set') print(eval_results) # 测试集 # checkpoint_path是可以指定选择那个时刻保存的权重进行评估 eval_results = mnist_classifier.evaluate(input_fn=test_input_fn, checkpoint_path=None) print('test set') print(eval_results)
七、预测
predicts =list(mnist_classifier.predict(input_fn=test_input_fn))
predicts是一个list,list中的元素是dictionary
predicts[0].keys() # 输出为: dict_keys(['image', 'conv1_out', 'pool1_out', 'conv2_out', 'pool2_out', 'pool2_flat_out', 'dense1_out', 'logits', 'classes', 'labels', 'probabilities'])
如果想要输出4个第一个卷基层的输出,可以
plt.figure(num=4,figsize=(28,28)) for i in range(4): plt.subplot(1,4,i+1) plt.imshow(predicts[0]['conv1_out'][:,:,i],cmap = plt.cm.gray) plt.savefig('conv1_out.png')
八、可视化
tensorboard --logdir=mnist_model_cnn 打开http://localhost:6006

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Tensorflow快餐教程(4) - 矩阵
矩阵 矩阵的初始化 矩阵因为元素更多,所以初始化函数更多了。光靠tf.linspace,tf.range之类的线性生成函数已经不够用了。 可以通过先生成一个线性序列,然后再reshape成一个矩阵的方式来初始化。 例: >>> g1 = tf.linspace(1.0,10.0,16) >>> g1 <tf.Tensor 'LinSpace_6:0' shape=(16,) dtype=float32> >>> g2 = tf.constant(sess.run(tf.reshape(g1,[4,4]))) >>> sess.run(g2) array([[ 1. , 1.6 , 2.2 , 2.8000002],
- 下一篇
浅分析Java volatile关键字
浅分析Java volatile关键字 大家好,前不久看了掘金一篇帖子原贴请点链接,那么今天就来给大家分享一下从这篇帖子中学到的volatile以及线程安全相关的知识点。 Java内存模型 在介绍volatile关键字之前,还是先给大家讲讲Java的内存模型 Java内存模型 Java的内存模型规定所有的变量都存储在主内存中,每条线程中还有属于自己的工作内存,现成的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝过来的),线程对变量的所有操作都必须在工作内存中进行,不同线程之间也无法访问到对方的工作内存中的变量,线程间变量值的传递都需要通过主内存来完成(从主内存读取共享变量到工作内存->在工作内存进行修改->写回主内存供其他线程访问) 并发编程的三大概念 1. 可见性 可见性是一种较为复杂的属性,通常我们的直觉在这一部分很大程度来说都是错的,并且通常我们没有办法保证执行将共享变量读取到工作内存的线程读取的一定是最新的共享变量值,也就是说我们不能保证一个线程在执行读操作的时候能适时看到其他线程刚刚写入的值或者说已经写入的值,有的时候我们的确无法控制,为了确保多...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS8编译安装MySQL8.0.19
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS7安装Docker,走上虚拟化容器引擎之路