前言
因为学习TensorFlow的内容较多,如果只看API会很无聊,可以结合实例去学习。但是在构建基本的模型之前,需要学一些准备知识:数据读取、预处理、优化器、损失函数
国际惯例,参考网址:
TensorFlow中文社区
TensorFlow官方文档
如何选择优化器 optimizer
TensorFlow-Examples
TensorFlow中的Neural Network
TensorFlow中的Train
深入浅出Tensorflow(三):训练神经网络模型的常用方法
理解tf.train.slice_input_producer()和tf.train.batch()
此外,为了模拟现实数据集,这里将mnist手写数字数据集转换成图片格式存储,放一下网盘链接:链接:https://pan.baidu.com/s/1ugEy85182vjcXQ8VoMJAbg 密码:1o83
处理数据集
从文本文件读取图像数据
按照caffe
的读取习惯,把图片的路径和标签全部用txt
文本文件存储起来,即路径\图片名 标签
的格式:
./mnist/train/5/5_1.png 5
./mnist/train/0/0_2.png 0
./mnist/train/4/4_3.png 4
./mnist/train/1/1_4.png 1
然后参考TensorFlow-Examples中的build_an_image_dataset 方法按照文本文件中存储的路径和标签去按行读取
imagepaths, labels = list(), list()
data = open(dataset_path, 'r').read().splitlines()
for d in data:imagepaths.append(d.split(' ')[0])labels.append(int(d.split(' ')[1]))
在TF中打乱数据以及分批
因为我们要用tensorflow
处理数据,所以必须将这些文本转换成tensorflow
能处理的形式
# 转换为张量
imagepaths = tf.convert_to_tensor(imagepaths, dtype=tf.string)
labels = tf.convert_to_tensor(labels, dtype=tf.int32)
打乱数据
# 建立TF队列,打乱数据
image, label = tf.train.slice_input_producer([imagepaths, labels],shuffle=True)
这里看一下这个slice_input_producer
函数:
tf.train.slice_input_producer(tensor_list,num_epochs=None,shuffle=True,seed=None,capacity=32,shared_name=None,name=None
)
- 作用:为
tensor_list
中的每个Tensor
建立切片 - 部分参数:
tensor_list
:Tensor
形式的列表,第一个维度必须相同
num_epochs
:每个切片创建多少次
shuffle
:打乱顺序
所以在打乱顺序的时候将此函数的shuffle
参数设置成True
即可
图片读取和处理
打乱数据路径以后,就可以读取了,tensorflow
也提供了图像处理相关函数,这里列部分有用的:
调整图像亮度:
tf.image.adjust_brightness(image,delta )
调整图像对比度:
tf.image.adjust_contrast(images,contrast_factor )
伽马校正:
tf.image.adjust_gamma(image,gamma=1,gain=1 )
从中间向边缘裁剪图像:
tf.image.central_crop(image,central_fraction )
裁剪图片,分别是左上角纵坐标、横坐标,裁剪的高度、宽度:
tf.image.crop_to_bounding_box(image,offset_height,offset_width,target_height,target_width )
图像解码:将对应图像解码为
uint8
张量(还有对应编码的encode函数):tf.image.decode_bmp
tf.image.decode_gif
tf.image.decode_image
tf.image.decode_jpeg
tf.image.decode_png
图像翻转:
tf.image.flip_left_right
左右翻转tf.image.flip_up_down
上下翻转tf.image.rot90
翻转90度,可自定义次数tf.image.transpose_image
图像转置
图像转换:
tf.image.grayscale_to_rgb
:灰度图转换为RGBtf.image.hsv_to_rgb
:HSV图转换为RGBtf.image.rgb_to_grayscale
:RGB转灰度图tf.image.rgb_to_hsv
:RGB转HSVtf.image.rgb_to_yiq
:RGB转YIQtf.image.rgb_to_yuv
:RGB转YUVtf.image.yiq_to_rgb
:YIQ转RGBtf.image.yuv_to_rgb
:YUV转RGB
图像填充:
tf.image.pad_to_bounding_box(image,offset_height,offset_width,target_height,target_width )
在图像上面填充
offset_height
高度的0,在图像左边填充offset_width
宽度的0,再按照最终目标宽度和高度在下边和右边填充图像。图像归一化:
tf.image.per_image_standardization(image)
图像大小调整
tf.image.resize_images(images,size,method=ResizeMethod.BILINEAR,align_corners=False )
其中插值方法有:
AREA BICUBIC BILINEAR NEAREST_NEIGHBOR
所以在图像预处理过程中,先按照路径读取图像以及解码,然后调整图像大小,归一化:
# 读取数据image = tf.read_file(image) image = tf.image.decode_jpeg(image, channels=CHANNELS)# 将图像resize成规定大小image = tf.image.resize_images(image, [IMG_HEIGHT, IMG_WIDTH])# 手动归一化image = image * 1.0/127.5 - 1.0
创建批量数据
就一个函数:
tf.train.batch(tensors,batch_size,num_threads=1,capacity=32,enqueue_many=False,shapes=None,dynamic_pad=False,allow_smaller_final_batch=False,shared_name=None,name=None
)
- 作用:利用队列的方法存储批数据
- 部分参数:
tensors
:张量列表或者字典
batch_size
:批大小
num_thread
:并行入队,采用线程数
capacity
:整数,队列里面最多有多少个元素
在制作数据集中,直接这样:
# 创建batch
X, Y = tf.train.batch([image, label], batch_size=batch_size,capacity=batch_size * 8,num_threads=4)
具体可以参考这篇博客的描述:TensorFlow 组合训练数据(batching)、TensorFlow 笔记(九):数据读取
TF中神经网络相关函数
这里需要注意的是tf.layers
和tf.nn
中均有相关层的实现,它们之间的区别我也不太清楚,但是从网上的观点来看,大部分人认为前者是以后者作为后端的,具体分析可看:
tf.nn.conv2d vs tf.layers.conv2d
tensorflow学习:tf.nn.conv2d 和 tf.layers.conv2d
Neural Network
层相关
目前构建神经网络还是使用前者的吧:
average_pooling1d(...): 一维输入平均池化
average_pooling2d(...): 二维输入平均池化(比如图像)
average_pooling3d(...): 三维输入平均池化(比如立体)
batch_normalization(...): 批归一化
conv1d(...): 一维卷积
conv2d(...): 二维卷积
conv2d_transpose(...): 2D反卷积
conv3d(...): 三维卷积
conv3d_transpose(...): 三维转置卷积
dense(...): 全连接
dropout(...): 随机丢失神经元
flatten(...): 保存第一个维度去展平数据
max_pooling1d(...): 一维输入最大池化
max_pooling2d(...): 二维输入最大池化
max_pooling3d(...): 三维输入最大池化
separable_conv1d(...): 按照深度独立方法一维卷积
separable_conv2d(...): 按照深度独立方法二维卷积
激活函数和损失相关
而tf.nn
也不是完全没用,它存储了激活函数以及部分损失函数:
sigmoid(...): sigmoid激活函数
tanh(...): tanh激活函数
softplus(...): 激活函数 log(exp(features) + 1).
relu(...): ReLU激活函数
leaky_relu(...): Leaky ReLU 激活函数max(features,leak*features)
elu(...): ELU激活函数:features if features>0 else alpha*(e^features-1)
selu(...): scaled exponential linear: scale * alpha * (exp(features) - 1)
crelu(...): 关联ReLU激活函数
relu6(...): Rectified Linear 6: min(max(features, 0), 6).
softmax(...): softmax 激活函数(一般用于最后一层)
softsign(...): 激活函数 features / (abs(features) + 1).
dropout(...):神经元抑制ctc_loss(...): CTC (Connectionist Temporal Classification)损失
nce_loss(...):计算noise-contrastive estimation训练损失
l2_loss(...): L2 损失.
log_poisson_loss(...): log Poisson损失.
weighted_cross_entropy_with_logits(...): 加权交叉熵损失(默认已将features用softmax激活)
sparse_softmax_cross_entropy_with_logits(...): 计算logits 和 labels的稀疏交叉熵损失
softmax_cross_entropy_with_logits(...): 计算logits 和 labels的交叉熵损失(默认已将features用softmax激活)
softmax_cross_entropy_with_logits_v2(...): 计算logits 和 labels的交叉熵损失(默认已将features用softmax激活)
sigmoid_cross_entropy_with_logits(...): 经sigmoid 函数激活之后的交叉熵(默认已将features用softmax激活)moments(...): 计算输入数据的均值和方差.
normalize_moments(...): 基于sufficient statistics计算输入数据的均值和方差top_k(...): 返回最后一个维度的k个最大值和索引
in_top_k(...):查询目标值是否在k个预测值中
xw_plus_b(...): 计算 matmul(x, weights) + biases.
relu_layer(...): 计算 Relu(x * weight + biases).
这里几个地方需要注意:
l2_loss
损失函数:没有开方,并且只取一半output=12∑x2output=12∑x2xxx__with_logits
:类似于这样的都是已经将输出用softmax
激活,然后计算误差,相当于做了两步工作:softmax
激活输出、计算其log
值与原始标签的乘积的和,具体介绍戳这里
正则项
两种正则化方法,三种函数:
一范式正则化:
tf.contrib.layers.l1_regularizer(scale,scope=None )
二范式正则化:
tf.contrib.layers.l2_regularizer(scale,scope=None )
两种范式一起用
tf.contrib.layers.l1_l2_regularizer(scale_l1=1.0,scale_l2=1.0,scope=None )
最后要将正则化应用到权重中:
tf.contrib.layers.apply_regularization(regularizer,weights_list=None
)
TF中的训练方法
包含优化器、梯度计算、梯度裁剪、学习率及其衰减,参考这里
优化器
Optimizer
GradientDescentOptimizer
AdadeltaOptimizer
AdagradOptimizer
AdagradDAOptimizer
MomentumOptimizer
AdamOptimizer
FtrlOptimizer
ProximalGradientDescentOptimizer
ProximalAdagradOptimizer
RMSPropOptimizer
各种梯度优化器,注意他们是属于类
,对应有很多函数可调用,比如minimize
梯度计算
gradients
AggregationMethod
stop_gradient
hessians
梯度裁剪
clip_by_value
clip_by_norm
clip_by_average_norm
clip_by_global_norm
global_norm
学习率及其衰减
exponential_decay
inverse_time_decay
natural_exp_decay
piecewise_constant
polynomial_decay
cosine_decay
linear_cosine_decay
noisy_linear_cosine_decay
一般不用搞那么复杂,随便用两个就行:
指数形式的衰减方法
tf.train.exponential_decay(learning_rate,#初始学习率global_step,#非负,衰减指数decay_steps,#正数,衰减周期decay_rate,#衰减率staircase=False,#如果是True,就是离散形式的衰减name=None ) ''' decayed_learning_rate = learning_rate *decay_rate ^ (global_step / decay_steps) staircase是True的时候(global_step / decay_steps)转换为整数 '''
多项式衰减:
tf.train.polynomial_decay(learning_rate,global_step,decay_steps,end_learning_rate=0.0001,power=1.0,cycle=False,name=None )
其中的cycle参数是决定lr是否在下降后重新上升的过程。cycle参数的初衷是为了防止网络后期lr十分小导致一直在某个局部最小值中振荡,突然调大lr可以跳出注定不会继续增长的区域探索其他区域。
没
cycle
时的计算方法:global_step = min(global_step, decay_steps) decayed_learning_rate = (learning_rate - end_learning_rate) *(1 - global_step / decay_steps) ^ (power) +end_learning_rate
有cycle时的计算方法:
decay_steps = decay_steps * ceil(global_step / decay_steps) decayed_learning_rate = (learning_rate - end_learning_rate) *(1 - global_step / decay_steps) ^ (power) +end_learning_rate
一般使用流程
一般是先定义学习率,然后使用优化器最小化损失
比如使用指数衰减:
...
global_step = tf.Variable(0, trainable=False)
starter_learning_rate = 0.1
learning_rate = tf.train.exponential_decay(starter_learning_rate, global_step,100000, 0.96, staircase=True)
# Passing global_step to minimize() will increment it at each step.
learning_step = (tf.train.GradientDescentOptimizer(learning_rate).minimize(...my loss..., global_step=global_step)
)
比如使用多项式衰减
...
global_step = tf.Variable(0, trainable=False)
starter_learning_rate = 0.1
end_learning_rate = 0.01
decay_steps = 10000
learning_rate = tf.train.polynomial_decay(starter_learning_rate, global_step,decay_steps, end_learning_rate,power=0.5)
# Passing global_step to minimize() will increment it at each step.
learning_step = (tf.train.GradientDescentOptimizer(learning_rate).minimize(...my loss..., global_step=global_step)
)
这个global_step
随着训练自增,具体可以看这里
用这段代码可以看出来:
import tensorflow as tf;
import numpy as np;
import matplotlib.pyplot as plt; x = tf.placeholder(tf.float32, shape=[None, 1], name='x')
y = tf.placeholder(tf.float32, shape=[None, 1], name='y')
w = tf.Variable(tf.constant(0.0))global_steps = tf.Variable(0, trainable=False)
learning_rate = tf.train.exponential_decay(0.1, global_steps, 10, 2, staircase=False)
loss = tf.pow(w*x-y, 2)train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_steps)with tf.Session() as sess:sess.run(tf.initialize_all_variables())for i in range(10):sess.run(train_step, feed_dict={x:np.linspace(1,2,10).reshape([10,1]),y:np.linspace(1,2,10).reshape([10,1])})print (sess.run(global_steps))
注意,经常无需人工计算梯度然后apply到相关参数列表上,因为minimize
已经包含这两步骤了。
简单例子
按照这个例子很容易利用CNN实现一个手写数字识别网络
先引入相关包以及定义数据相关信息
import tensorflow as tf
import osDATASET_PATH = './mnist/train_labels.txt' # the dataset file or root folder path.
N_CLASSES = 10 # 类别数
IMG_HEIGHT = 28 # 高
IMG_WIDTH = 28 # 宽
CHANNELS = 1 # 通道数
然后按照标签中定义的图片路径和标签,制作数据集:
def read_images(dataset_path, batch_size):imagepaths, labels = list(), list()data = open(dataset_path, 'r').read().splitlines()for d in data:imagepaths.append(d.split(' ')[0])labels.append(int(d.split(' ')[1]))# 转换为张量imagepaths = tf.convert_to_tensor(imagepaths, dtype=tf.string)labels = tf.convert_to_tensor(labels, dtype=tf.int32)# 建立TF队列,打乱数据image, label = tf.train.slice_input_producer([imagepaths, labels],shuffle=True)# 读取数据image = tf.read_file(image)image = tf.image.decode_jpeg(image, channels=CHANNELS)# 将图像resize成规定大小image = tf.image.resize_images(image, [IMG_HEIGHT, IMG_WIDTH])# 手动归一化image = image * 1.0/127.5 - 1.0# 创建batchX, Y = tf.train.batch([image, label], batch_size=batch_size,capacity=batch_size * 8,num_threads=4)return X, Y
设置网络参数
#网络参数
learning_rate = 0.001#学习率
num_steps = 100#迭代次数
batch_size = 128#每批大小
display_step = 100#显示调试信息
dropout = 0.75 # dropout保留比率
X, Y = read_images(DATASET_PATH, batch_size)#读取数据集
定义网络结构:
# 创建卷积模型
def conv_net(x, n_classes, dropout, reuse, is_training):# Define a scope for reusing the variableswith tf.variable_scope('ConvNet', reuse=reuse):# 第一层卷积conv1 = tf.layers.conv2d(x, 32, 5, activation=tf.nn.relu)# 最大池化conv1 = tf.layers.max_pooling2d(conv1, 2, 2)# 第二层卷积conv2 = tf.layers.conv2d(conv1, 64, 3, activation=tf.nn.relu)# 最大池化conv2 = tf.layers.max_pooling2d(conv2, 2, 2)# 拉成一维向量fc1 = tf.layers.flatten(conv2)# 全连接层fc1 = tf.layers.dense(fc1, 1024)# 应用dropoutfc1 = tf.layers.dropout(fc1, rate=dropout, training=is_training)# 输出out = tf.layers.dense(fc1, n_classes)# softmax输出out = tf.nn.softmax(out) if not is_training else outreturn out
定义训练网络和评估网络,并进行训练
#训练网络
logits_train = conv_net(X, N_CLASSES, dropout, reuse=False, is_training=True)
# 测试网络
logits_test = conv_net(X, N_CLASSES, dropout, reuse=True, is_training=False)# 定义损失和优化器
loss_op = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits_train, labels=Y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
train_op = optimizer.minimize(loss_op)# 评估模型
correct_pred = tf.equal(tf.argmax(logits_test, 1), tf.cast(Y, tf.int64))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))# 初始化变量
init = tf.global_variables_initializer()# 保存模型参数
saver = tf.train.Saver()# 开始训练
with tf.Session() as sess:coord=tf.train.Coordinator()# 初始化参数sess.run(init)# 数据集队列tf.train.start_queue_runners(sess=sess,coord=coord)# 循环训练for step in range(1, num_steps+1):if step % display_step == 0:# Run optimization and calculate batch loss and accuracy_, loss, acc = sess.run([train_op, loss_op, accuracy])print("Step " + str(step) + ", Minibatch Loss= " + \"{:.4f}".format(loss) + ", Training Accuracy= " + \"{:.3f}".format(acc))else: sess.run(train_op)coord.request_stop()#请求线程结束coord.join()#等待线程结束print("Optimization Finished!")# 保存模型saver.save(sess, './cnn_mnist_model/my_tf_model')
结果
Step 100, Minibatch Loss= 0.1098, Training Accuracy= 0.969
Step 200, Minibatch Loss= 0.1070, Training Accuracy= 0.969
Step 300, Minibatch Loss= 0.0393, Training Accuracy= 1.000
Step 400, Minibatch Loss= 0.0688, Training Accuracy= 0.984
Step 500, Minibatch Loss= 0.0559, Training Accuracy= 0.992
Step 600, Minibatch Loss= 0.0433, Training Accuracy= 0.984
Step 700, Minibatch Loss= 0.0341, Training Accuracy= 0.992
Step 800, Minibatch Loss= 0.0309, Training Accuracy= 0.984
Step 900, Minibatch Loss= 0.0825, Training Accuracy= 0.969
Step 1000, Minibatch Loss= 0.0211, Training Accuracy= 1.000
Optimization Finished!
个人感觉相对于theano
使用tensorflow
的好处在于,无需自己写梯度优化了,创建模型貌似不用自己去挨个权重定义和初始化,这里就没针对每个卷积核定义变量和单独初始化,除此之外,没感觉有啥其它便利性了。
还有一个重要问题是,这个模型保存了以后,好像无法拿过来单独测试一张图片,因为这里没有函数提供额外输入,即使想调用logits_test
来测试单张图片,也不好弄,因为X
无法在重载模型后指定单张图片,所以还得改代码,后续再研究研究模型保存与载入。
后记
这一部分主要了解一下在TensorFlow
中构建神经网络可以使用的部分函数。
我嘞个乖乖,感觉好难啊,跟Theano
差不多,好底层,想脱坑转TensorLayer
或者TFLearn
这两个基于tensorflow
的二次封装库,知乎上有对应讨论如何比较Keras, TensorLayer, TFLearn ?,貌似大部分人都推荐TL
,先继续折腾TensorFlow的保存和加载模型,如果实在不行,后续去了解一下对工程实现的支持程度以及模型的保存和载入难度,再选择框架。