使用TensorFlow编写识别数字的CNN训练程序详解
2017-01-01 17:09
826 查看
文本代码基于TensorFlow 0.12.0版本,使用tf.contrib.learn封装的方法来实现CNN。
程序中设置的一些参数是:
卷积层1:kernel_size [5, 5], stride=1, 4个卷积窗口
卷积层2:kernel_size [5, 5], stride=1, 6个卷积窗口
池化层: pool_size [2, 2], stride = 2
全连接层1: 1024个特征
现在可以:
通过mnist.train, mnist.test, mnist.validation来获得3个数据集,每个数据集里面的方法有(已train为例):
train.images 图片数据,二维数组 (55000, 784) dtype=float32
train.labels 图片的分类, 一维数组,每个数值表示图片对应的数字
array([7, 3, 4, …, 5, 6, 8], dtype=uint8)
train.num_examples 图片数量 55000
train.next_batch 下一批数据
第一种加载方式,有一个one-hot参数,此时每个样本的label,返回的是一个长度10的vector,其中只有一个位置是1,其他都是0。 第二种方式,没有这个参数,如果需要的话,得直接调用datasets.mnist.read_data_sets
查看learn.datasets的代码,还可以加载其他一些数据:
我们自己写卷积运算时要考虑的因素,基本上都体现在这个方法的参数里面了,禁不住佩服一下google的抽象能力。
Adds an N-D convolution followed by an optional batch_norm layer.It is required that 1 <= N <= 3.
这个函数很强大,1到3维的卷积都支持。(我暂时只用过2维)
inputs: 输入变量,是一个N+2维的Tensor
类型要求是一个Tensor,而我们一般训练的数据都是常量(比如mnist,load以后得到是python的数据类型,不是tf的),所以需要把用tf的方法做一下转换,比如tf.reshape
为什么是N+2维呢,比如图像,除了宽度和高度,实际上还有样本数量和通道数量(如RGB3通道),所以多了2维。
inputs的格式,由date_format这个参数来觉得,比如2维,有NHWC和NCHW两种。N是样本数量,H高度,W宽度,C通道数。
num_outputs: 卷积filter的数量,或者说提取的特征数量,比如5,10
kernel_size: 卷积核的大小,是N个参数的list,比如二维图像,可以时候[10,10],如果参数值相同,用一个整数来表示也可以;
stride: 卷积步长,同样是N个参数的序列,或者都相等的话,用一个整数来表示,默认是1.
padding: 字符串格式,默认SAME,可选’VALID’。(想问:这两个效果上有多大差异?)
data_format: 字符串,指定inputs的格式
一维数据:”NWC” (default) and “NCW”
二维数据:”NHWC” (default) and “NCHW”
三维数据:”NDHWC”
也就是,不指定的话,通道数都是最后一个参数。
rate: a sequence of N positive integers specifying the dilation rate to use for a’trous convolution. Can be a single integer to specify the same value for all spatial dimensions. (暂时没看到其作用)
activation_fn: 激活函数,默认relu
normalizer_fn: normalization function to use instead of biases.(没用过,不知道起作用)
normalizer_params: normalization function parameters.
weights_initializer: 这不用说了,有默认值,估计用默认的就可以了。
weights_regularizer: Optional regularizer for the weights.(没明白为什么需要这个)
biases_initializer: 有默认值,一般也就不用指定。
biases_regularizer: …
reuse: whether or not the layer and its variables should be reused. To be able to reuse the layer scope must be given. 应该都需要reuse吧,所以这个参数默认为True更好,现在是None。
variables_collections: 怎么用暂时不太明白,但应该不用指定也可以;
outputs_collections: 同上;
trainable: If True also add variables to the graph collection GraphKeys.TRAINABLE_VARIABLES,默认是True。 (这个是不是说在fit的时候需要设为True,evaluate和predict的时候为false?)
scope: 也即是variable_scope, 如果用多个卷积层的话,需要设置这个参数,以便把每一次的weight和bias区别出来。
我们在对MNIST做卷积的时候,只要指定inputs, num_outputs, kernel_size, scope这几个参数就可以了,比如:
TODO:卷积的结果是多少维的数据?
max_pool2d(inputs, kernel_size, stride=2, padding=’VALID’, data_format=DATA_FORMAT_NHWC, outputs_collections=None, scope=None)
inputs: 就是卷积的输出了;
kernel_size: 是不是叫pool_size更贴切。[kernel_height, kernel_width]或者是一个整数;
stride: [stride_height, stride_width],不过文档上说目前这两个值必须一样
padding: 这里默认是VALID,和卷积默认不一样,为什么要这样呢?
data_format: 注意和卷积用的一样哦;
outputs_collections: …
scope: pooling的时候没有参数,需要scope吗?
fully_connected(inputs, num_outputs, activation_fn=nn.relu, normalizer_fn=None, normalizer_params=None, weights_initializer=initializers.xavier_initializer(), weights_regularizer=None, biases_initializer=init_ops.zeros_initializer, biases_regularizer=None, reuse=None, variables_collections=None, outputs_collections=None, trainable=True, scope=None)
看这个函数,参数和卷积很多地方是一样的, 我们可以这样用:
唯一需要注意的是这里的inputs参数,一般是二维的形式[batch_size, depth],而前面卷积的结果,一般是[batch_size, height, width, channels]的形式,所以需要做一个flatten操作后再传给fully_connected。
一般在fc之后还会做dropout,可以用如下方法:
dropout(inputs, keep_prob=0.5, noise_shape=None, is_training=True, outputs_collections=None, scope=None)
参数的意义很明显,其中is_training需要注意一下,在训练的时候传True,其他情况下传False。
We don’t apply softmax here because
tf.nn.softmax_cross_entropy_with_logits accepts the unscaled logits
and performs the softmax internally for efficiency.
为什么要这样暂时不太明白,但是依样画葫芦,定义logtis本身很简单,做一个线性变换,把FC的结果映射到分类的数量上:
softmax_cross_entropy(logits, onehot_labels, weights=_WEIGHT_SENTINEL, label_smoothing=0, scope=None)
注意这里的label是onehot格式的, 我们从mnist获取的label要转换成这个格式。
预定义的optimizer有:
所以我们可以这样写:
session到哪儿去了
变量的保存和加载
参数的调优
参考文章:
1. Playing with convolutions in TensorFlow
CNN的结构
从网上借用一张图片来表示一下,是一个有2层hidden layer的CNN。程序中设置的一些参数是:
卷积层1:kernel_size [5, 5], stride=1, 4个卷积窗口
卷积层2:kernel_size [5, 5], stride=1, 6个卷积窗口
池化层: pool_size [2, 2], stride = 2
全连接层1: 1024个特征
MNIST数据的获取
以往我们获取MINIST的方式是:from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
现在可以:
from tensorflow.contrib import learn mnist = learn.datasets.load_dataset('mnist')
通过mnist.train, mnist.test, mnist.validation来获得3个数据集,每个数据集里面的方法有(已train为例):
train.images 图片数据,二维数组 (55000, 784) dtype=float32
train.labels 图片的分类, 一维数组,每个数值表示图片对应的数字
array([7, 3, 4, …, 5, 6, 8], dtype=uint8)
train.num_examples 图片数量 55000
train.next_batch 下一批数据
n = train.next_batch n[0] 是images n[1]是labels
第一次load MNIST数据的时候,会自动从网上下载,放到当前目录的MNIST-data目录下
第一种加载方式,有一个one-hot参数,此时每个样本的label,返回的是一个长度10的vector,其中只有一个位置是1,其他都是0。 第二种方式,没有这个参数,如果需要的话,得直接调用datasets.mnist.read_data_sets
查看learn.datasets的代码,还可以加载其他一些数据:
DATASETS = { # Returns base.Dataset. 'iris': base.load_iris, 'boston': base.load_boston, # Returns base.Datasets (train/validation/test sets). 'mnist': mnist.load_mnist, 'dbpedia': text_datasets.load_dbpedia, }
定义卷积层
在tf.contrib.layers里面有convolution2d,conv2d等方法,其实都是convolution方法的别名convolution(inputs, num_outputs, kernel_size, stride=1, padding='SAME', data_format=None, rate=1, activation_fn=nn.relu, normalizer_fn=None, normalizer_params=None, weights_initializer=initializers.xavier_initializer(), weights_regularizer=None, biases_initializer=init_ops.zeros_initializer, biases_regularizer=None, reuse=None, variables_collections=None, outputs_collections=None, trainable=True, scope=None)
我们自己写卷积运算时要考虑的因素,基本上都体现在这个方法的参数里面了,禁不住佩服一下google的抽象能力。
Adds an N-D convolution followed by an optional batch_norm layer.It is required that 1 <= N <= 3.
这个函数很强大,1到3维的卷积都支持。(我暂时只用过2维)
inputs: 输入变量,是一个N+2维的Tensor
类型要求是一个Tensor,而我们一般训练的数据都是常量(比如mnist,load以后得到是python的数据类型,不是tf的),所以需要把用tf的方法做一下转换,比如tf.reshape
为什么是N+2维呢,比如图像,除了宽度和高度,实际上还有样本数量和通道数量(如RGB3通道),所以多了2维。
inputs的格式,由date_format这个参数来觉得,比如2维,有NHWC和NCHW两种。N是样本数量,H高度,W宽度,C通道数。
num_outputs: 卷积filter的数量,或者说提取的特征数量,比如5,10
kernel_size: 卷积核的大小,是N个参数的list,比如二维图像,可以时候[10,10],如果参数值相同,用一个整数来表示也可以;
stride: 卷积步长,同样是N个参数的序列,或者都相等的话,用一个整数来表示,默认是1.
padding: 字符串格式,默认SAME,可选’VALID’。(想问:这两个效果上有多大差异?)
data_format: 字符串,指定inputs的格式
一维数据:”NWC” (default) and “NCW”
二维数据:”NHWC” (default) and “NCHW”
三维数据:”NDHWC”
也就是,不指定的话,通道数都是最后一个参数。
rate: a sequence of N positive integers specifying the dilation rate to use for a’trous convolution. Can be a single integer to specify the same value for all spatial dimensions. (暂时没看到其作用)
activation_fn: 激活函数,默认relu
normalizer_fn: normalization function to use instead of biases.(没用过,不知道起作用)
normalizer_params: normalization function parameters.
weights_initializer: 这不用说了,有默认值,估计用默认的就可以了。
weights_regularizer: Optional regularizer for the weights.(没明白为什么需要这个)
biases_initializer: 有默认值,一般也就不用指定。
biases_regularizer: …
reuse: whether or not the layer and its variables should be reused. To be able to reuse the layer scope must be given. 应该都需要reuse吧,所以这个参数默认为True更好,现在是None。
variables_collections: 怎么用暂时不太明白,但应该不用指定也可以;
outputs_collections: 同上;
trainable: If True also add variables to the graph collection GraphKeys.TRAINABLE_VARIABLES,默认是True。 (这个是不是说在fit的时候需要设为True,evaluate和predict的时候为false?)
scope: 也即是variable_scope, 如果用多个卷积层的话,需要设置这个参数,以便把每一次的weight和bias区别出来。
我们在对MNIST做卷积的时候,只要指定inputs, num_outputs, kernel_size, scope这几个参数就可以了,比如:
conv1 = tf.contrib.layers.conv2d(inputs, 4, [5, 5], 'conv_layer1') #stride默认1,weights和biases也都是默认的
TODO:卷积的结果是多少维的数据?
定义池化层
可以用 tf.contrib.layers.max_pool2d或者tf.contrib.layers.avg_pool2dmax_pool2d(inputs, kernel_size, stride=2, padding=’VALID’, data_format=DATA_FORMAT_NHWC, outputs_collections=None, scope=None)
inputs: 就是卷积的输出了;
kernel_size: 是不是叫pool_size更贴切。[kernel_height, kernel_width]或者是一个整数;
stride: [stride_height, stride_width],不过文档上说目前这两个值必须一样
padding: 这里默认是VALID,和卷积默认不一样,为什么要这样呢?
data_format: 注意和卷积用的一样哦;
outputs_collections: …
scope: pooling的时候没有参数,需要scope吗?
pool1 = tf.contrib.layers.max_pool2d(conv1, [2, 2], padding='SAME')
定义全连接层
tf.contrib.layers下有可用的全连接方法:fully_connected(inputs, num_outputs, activation_fn=nn.relu, normalizer_fn=None, normalizer_params=None, weights_initializer=initializers.xavier_initializer(), weights_regularizer=None, biases_initializer=init_ops.zeros_initializer, biases_regularizer=None, reuse=None, variables_collections=None, outputs_collections=None, trainable=True, scope=None)
看这个函数,参数和卷积很多地方是一样的, 我们可以这样用:
fc = tf.contrib.layers.fully_connected(inputs, 1024, scope='fc_layer')
唯一需要注意的是这里的inputs参数,一般是二维的形式[batch_size, depth],而前面卷积的结果,一般是[batch_size, height, width, channels]的形式,所以需要做一个flatten操作后再传给fully_connected。
一般在fc之后还会做dropout,可以用如下方法:
dropout(inputs, keep_prob=0.5, noise_shape=None, is_training=True, outputs_collections=None, scope=None)
参数的意义很明显,其中is_training需要注意一下,在训练的时候传True,其他情况下传False。
定义logits
全连接之后,一般就是用softmax做分类,然后定义loss,就可以训练了。但是看官方的例子,softmax前还加了一步,计算叫logits的东西,代码里面的说明是:We don’t apply softmax here because
tf.nn.softmax_cross_entropy_with_logits accepts the unscaled logits
and performs the softmax internally for efficiency.
为什么要这样暂时不太明白,但是依样画葫芦,定义logtis本身很简单,做一个线性变换,把FC的结果映射到分类的数量上:
def inference(x, num_class): with tf.variable_scope('softmax'): dtype = x.dtype.base_dtype # Set up the requested initialization. init_mean = 0.0 init_stddev = 0.0 weights = tf.get_variable('weights', [x.get_shape()[1], num_class], initializer=init_ops.random_normal_initializer(init_mean, init_stddev, dtype=dtype), dtype=dtype) biases = tf.get_variable('bias', [num_class], initializer=init_ops.random_normal_initializer(init_mean, init_stddev, dtype=dtype), dtype=dtype) logits = tf.nn.xw_plus_b(x, weights, biases) return logits
定义loss
在tf.contrib.losses下有一些预定义的loss函数,比如直接用softmax_cross_entropy(logits, onehot_labels, weights=_WEIGHT_SENTINEL, label_smoothing=0, scope=None)
注意这里的label是onehot格式的, 我们从mnist获取的label要转换成这个格式。
定义train_op
可以用tf.contrib.layers.optimize_loss,通过传递不同的参数,就可以调用不同的优化方法。optimize_loss(loss, global_step, learning_rate, optimizer, gradient_noise_scale=None, gradient_multipliers=None, clip_gradients=None, learning_rate_decay_fn=None, update_ops=None, variables=None, name=None, summaries=None, colocate_gradients_with_ops=False):
预定义的optimizer有:
OPTIMIZER_CLS_NAMES = { "Adagrad": train.AdagradOptimizer, "Adam": train.AdamOptimizer, "Ftrl": train.FtrlOptimizer, "Momentum": train.MomentumOptimizer, "RMSProp": train.RMSPropOptimizer, "SGD": train.GradientDescentOptimizer, }
所以我们可以这样写:
train_op = tf.contrib.layers.optimize_loss( loss, tf.contrib.framework.get_global_step(), optimizer='Adagrad', learning_rate=0.1)
model和Estimator
结合上面的内容,就可以定义出model, 从而用Estimator完成训练,预测等功能,完整的程序如下:import numpy as np import sklearn.metrics as metrics import tensorflow as tf from PIL import Image from tensorflow.contrib import learn from tensorflow.contrib.learn import SKCompat from tensorflow.contrib.learn.python.learn.estimators import model_fn as model_fn_lib from tensorflow.python.ops import init_ops IMAGE_SIZE = 28 LOG_DIR = './ops_logs' mnist = learn.datasets.load_dataset('mnist') def inference(x, num_class): with tf.variable_scope('softmax'): dtype = x.dtype.base_dtype init_mean = 0.0 init_stddev = 0.0 weight = tf.get_variable('weights', [x.get_shape()[1], num_class], initializer=init_ops.random_normal_initializer(init_mean, init_stddev, dtype=dtype), dtype=dtype) biases = tf.get_variable('bias', [num_class], initializer=init_ops.random_normal_initializer(init_mean, init_stddev, dtype=dtype), dtype=dtype) logits = tf.nn.xw_plus_b(x, weight, biases) return logits def model(features, labels, mode): if mode != model_fn_lib.ModeKeys.INFER: labels = tf.one_hot(labels, 10, 1, 0) else: labels = None inputs = tf.reshape(features, (-1, IMAGE_SIZE, IMAGE_SIZE, 1)) #conv1 conv1 = tf.contrib.layers.conv2d(inputs, 4, [5, 5], scope='conv_layer1', activation_fn=tf.nn.tanh); pool1 = tf.contrib.layers.max_pool2d(conv1, [2, 2], padding='SAME') #conv2 conv2 = tf.contrib.layers.conv2d(pool1, 6, [5, 5], scope='conv_layer2', activation_fn=tf.nn.tanh); pool2 = tf.contrib.layers.max_pool2d(conv2, [2, 2], padding='SAME') pool2_shape = pool2.get_shape() pool2_in_flat = tf.reshape(pool2, [pool2_shape[0].value or -1, np.prod(pool2_shape[1:]).value]) #fc fc1 = tf.contrib.layers.fully_connected(pool2_in_flat, 1024, scope='fc_layer1', activation_fn=tf.nn.tanh) #dropout is_training = False if mode == model_fn_lib.ModeKeys.TRAIN: is_training = True dropout = tf.contrib.layers.dropout(fc1, keep_prob=0.5, is_training=is_training, scope='dropout') logits = inference(dropout, 10) prediction = tf.nn.softmax(logits) if mode != model_fn_lib.ModeKeys.INFER: loss = tf.contrib.losses.softmax_cross_entropy(logits, labels) train_op = tf.contrib.layers.optimize_loss( loss, tf.contrib.framework.get_global_step(), optimizer='Adagrad', learning_rate=0.1) else: train_op = None loss = None return {'class': tf.argmax(prediction, 1), 'prob': prediction}, loss, train_op classifier = SKCompat(learn.Estimator(model_fn=model, model_dir=LOG_DIR)) classifier.fit(mnist.train.images, mnist.train.labels, steps=1000, batch_size=300) predictions = classifier.predict(mnist.test.images) score = metrics.accuracy_score(mnist.test.labels, predictions['class']) print('Accuracy: {0:f}'.format(score))
待分析的一些问题
global_step的作用session到哪儿去了
变量的保存和加载
参数的调优
参考文章:
1. Playing with convolutions in TensorFlow
相关文章推荐
- TensorFlow - 手写数字识别 (模型训练完成后的使用)
- Tensorflow系列之(二):详解CNN识别MNIST手写数字集
- 深度学习-CNN卷积神经网络使用TensorFlow框架实现MNIST手写数字识别
- 03:一文全解:使用Tensorflow搭建卷积神经网络CNN识别手写数字图片
- 使用Tensorflow实现CNN进行MNIST数字识别
- Deep Learning-TensorFlow (1) CNN卷积神经网络_MNIST手写数字识别代码实现详解
- 3000门徒内部训练绝密视频(泄密版)第8课:彻底实战详解使用IDE开发Spark程序
- 使用c语言编写程序,从键盘上任意输入两个数字,并计算出两个数的最小公倍数
- Tensorflow - Tutorial (4) :基于CNN的手写数字识别
- 在WIN7系统VS2010中使用MSComm控件编写串口程序详解
- 使用OPENCV训练手写数字识别分类器
- Tensorflow中使用CNN实现Mnist手写体识别
- Deep Learning-TensorFlow (1) CNN卷积神经网络_MNIST手写数字识别代码实现
- 使用OPENCV训练手写数字识别分类器
- 使用OPENCV训练手写数字识别分类器
- tensorflow中mnist 使用cnn模型训练的输出层数为7x7的原因
- 这不仅仅是另一个使用TensorFlow来做MNIST数字图像识别的教程
- Tensorflow 训练自己的cnn模型 行人识别
- 分享:基于神经网络的Visual C++阿拉伯数字训练和识别程序
- Android程序签名详解、打包,分别使用keytool工具和Android Studio生成数字证书