前面的博客介绍了如何把图像数据转换成tfrecords格式并读取,本篇博客介绍如何用自己的tfrecords格式的数据训练CNN模型,采用的模型是VGG-16。现有的教程都是在mnist或者cifar-10数据集上做的训练,如何用自己的数据集进行训练相关的资料比较少,即使有相关的也是不完整,所以做一个完成流程的学习笔记,以供以后查阅使用。首先要做的是把自己的数据转换成tfrecords文件。
tfrecords文件制作教程:https://blog.csdn.net/gybheroin/article/details/79800679
很久很久以前,有一个年轻小伙子觉得实现一个现成的网络结构是一件非常复杂的事情,直到有一天他发现在caffe平台下修改网络结构就是修改参数文件,他忽然发现,我天,原来这么随意,后来有一天他用了tensorflow,发现原来实现起来是那么任性。。。。。。主要参考代码是tensorflow的源码实现:
#定义卷积层函数
def conv_op(input_op,name,kh,kw,n_out,dh,dw,p): #分别表示输入张量,卷积层名字,
#卷积核高和宽,输出卷积核数量,步长以及步长的高和宽,以及参数列表p
n_in=input_op.get_shape()[-1].value #获取输入的通道数量,即输入的卷积核的数量或者图像通道
with tf.name_scope(name) as scope:
kernel=tf.get_variable(scope+"w",shape=[kh,kw,n_in,n_out],dtype=tf.float32,initializer=tf.contrib.layers.xavier_initializer_conv2d())
conv=tf.nn.conv2d(input_op,kernel,(1,dh,dw,1),padding='SAME')
bias_init_val=tf.constant(0.0,shape=[n_out],dtype=tf.float32)
biases=tf.Variable(bias_init_val,trainable=True,name='b')
z=tf.nn.bias_add(conv,biases)
activation=tf.nn.relu(z,name=scope)
p+=[kernel,biases]
return activation
#定义全连接层函数
def fc_op(input_op,name,n_out,p):
n_in=input_op.get_shape()[-1].value
with tf.name_scope(name) as scope:
kernel=tf.get_variable(scope+"w",shape=[n_in,n_out],dtype=tf.float32,initializer=tf.contrib.layers.xavier_initializer())
biases=tf.Variable(tf.constant(0.1,shape=[n_out],dtype=tf.float32),name='b')
activation=tf.nn.relu_layer(input_op,kernel,biases,name=scope)
p+=[kernel,biases]
return activation
#创建最大池化层函数
def mpool_op(input_op,name,kh,kw,dh,dw):
return tf.nn.max_pool(input_op,ksize=[1,kh,kw,1],strides=[1,dh,dw,1],padding='SAME',name=name)
#开始定义VGG-16
def inference_op(input_op,keep_prob):
p=[]
#第一段卷积,两个卷积层和一个池化层
conv1_1=conv_op(input_op,name="conv1_1",kh=3,kw=3,n_out=64,dh=1,dw=1,p=p)
conv1_2=conv_op(conv1_1,name="conv1_2",kh=3,kw=3,n_out=64,dh=1,dw=1,p=p)
pool1=mpool_op(conv1_2,name="pool1",kh=2,kw=2,dw=2,dh=2)
#第二段卷积
conv2_1=conv_op(pool1,name="conv2_1",kh=3,kw=3,n_out=128,dh=1,dw=1,p=p)
conv2_2=conv_op(conv2_1,name="conv2_2",kh=3,kw=3,n_out=128,dh=1,dw=1,p=p)
pool2=mpool_op(conv2_2,name="pool2",kh=2,kw=2,dw=2,dh=2)
#第三段卷积
conv3_1=conv_op(pool2,name="conv3_1",kh=3,kw=3,n_out=256,dh=1,dw=1,p=p)
conv3_2=conv_op(conv3_1,name="conv3_2",kh=3,kw=3,n_out=256,dh=1,dw=1,p=p)
conv3_3=conv_op(conv3_2,name="conv3_3",kh=3,kw=3,n_out=256,dh=1,dw=1,p=p)
pool3=mpool_op(conv3_3,name="pool3",kh=2,kw=2,dw=2,dh=2)
#第四段卷积
conv4_1=conv_op(pool3,name="conv4_1",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
conv4_2=conv_op(conv4_1,name="conv4_2",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
conv4_3=conv_op(conv4_2,name="conv4_3",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
pool4=mpool_op(conv4_3,name="pool4",kh=2,kw=2,dw=2,dh=2)
#第五段卷积操作
conv5_1=conv_op(pool4,name="conv5_1",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
conv5_2=conv_op(conv5_1,name="conv5_2",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
conv5_3=conv_op(conv5_2,name="conv5_3",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
pool5=mpool_op(conv5_3,name="pool5",kh=2,kw=2,dw=2,dh=2)
#对pool5的结果扁平化
shp=pool5.get_shape()
flattened_shape=shp[1].value*shp[2].value*shp[3].value
resh1=tf.reshape(pool5,[-1,flattened_shape],name="resh1")
#全连接
fc6=fc_op(resh1,name="fc6",n_out=4096,p=p)
fc6_drop=tf.nn.dropout(fc6,keep_prob,name="fc6_drop")
fc7=fc_op(fc6_drop,name="fc7",n_out=4096,p=p)
fc7_drop=tf.nn.dropout(fc7,keep_prob,name="fc7_drop")
fc8=fc_op(fc7_drop,name="fc8",n_out=2,p=p)
#softmax_result=tf.nn.softmax(fc8) #我把这两行代码放在别的位置了,因为我要做精度测试,需要fc8的输出结果
# predictions=tf.argmax(softmax,1)
return fc8
需要注意的地方:返回值是fc8输出结果,用于softmax的分类,我把softmax和损失函数部分放在网络结果之外了。
还记得上一篇博客的内容吗?完全一样的。需要注意的地方,代码中我都做了注释。
#读取文件
def read_and_decode(filename,batch_size):
#根据文件名生成一个队列
filename_queue = tf.train.string_input_producer([filename])
reader = tf.TFRecordReader()
_, 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),
})
img = tf.decode_raw(features['img_raw'], tf.uint8)
img = tf.reshape(img, [300, 300, 3]) #图像归一化大小
# img = tf.cast(img, tf.float32) * (1. / 255) - 0.5 #图像减去均值处理,根据需要自行添加修改
label = tf.cast(features['label'], tf.int32)
#特殊处理
#返回的batch操作,大小可以自行设定。
img_batch, label_batch = tf.train.shuffle_batch([img, label],
batch_size= batch_size,
num_threads=64,
capacity=200,
min_after_dequeue=150)
return img_batch, tf.reshape(label_batch,[batch_size])
这部分坑比较多,博主前两天主要卡在这个地方了,占位符的shape一直调整不好,总是shape不匹配。需要注意的地方我都做了注释
batch_size=4
x_holder=tf.placeholder(tf.float32,[batch_size,300,300,3],name='x_holder') #要有名字,因为后续保存模型再使用,需要读取出来重新给占位符赋值
y_holder=tf.placeholder(tf.int32,[batch_size]) #标签数据不是独热编码,如果标签数据是独热编码shape=[batch_size,ClassesNumber]
keep_prob=tf.placeholder(tf.float32,name='keep_prob') #要有名字,保存模型之后再使用需要把它调用出来使用的
logits=inference_op(x_holder,keep_prob) #将数据放进去网络中
cost=tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,labels=y_holder) #损失函数
optimizer = tf.train.AdamOptimizer(learning_rate = 0.001).minimize(cost) #优化器
dropout=0.8 #dropout比率
top_k_op=tf.nn.in_top_k(logits,y_holder,1) #在测试过程中用于计算有多少个正确的
#读取文件
tfrecords_file = 'train.tfrecords' #已经存下的tfrecords文件,就在同目录下,否则会找不到
BATCH_SIZE = 4 #batchsize大小
image_batch, label_batch = read_and_decode(tfrecords_file,BATCH_SIZE)
需要注意的地方:
x_holder=tf.placeholder(tf.float32,[batch_size,300,300,3],name='x_holder') #要有名字,因为后续保存模型再使用,需要读取出来重新给占位符赋值
y_holder=tf.placeholder(tf.int32,[batch_size]) #标签数据不是独热编码,如果标签数据是独热编码shape=[batch_size,ClassesNumber]
[batch_size,300,300,3] #如果是batch投入训练,则第一维大小可以写成batch_size,第一维也可以写成None。y_holder同样
y_holder=tf.placeholder(tf.int32,[batch_size]) #如果不是独热编码,标签shape就写成batch_size或者None,如果是独热编码就写成[None,类别个数]
保存模型用的是Saver类,博客主要介绍实现,不做原理讲解,可以自行查阅相关内容。代码如下:
#用于保存模型
saver_path = './model/checkpoint/model.ckpt' #保存路径
saver = tf.train.Saver()
sess=tf.Session()
sess.run(tf.global_variables_initializer())
coord = tf.train.Coordinator() #线程管理
threads = tf.train.start_queue_runners(sess = sess,coord = coord)
for i in range(2): #迭代次数
image, label = sess.run([image_batch, label_batch]) #将batch转换成tensor
print(label)
sess.run(optimizer,feed_dict={x_holder:image,y_holder:label,keep_prob:dropout}) #训练模型
print("training finish!")
saver.save(sess,saver_path) #将训练好的模型保存
模型保存成功,会在相应的目录下出现4个文件,如图所示:
这样模型就保存成功了!
测试模型分为两种情况,本篇博客介绍第一种情况,就是不使用保存的模型,直接训练完直接测试,测试模型代码如下:
#测试使用
image,label=sess.run([image_batch,label_batch]) #读取数据
true_count=0
prediction=sess.run([top_k_op],feed_dict={x_holder:image,y_holder:label,keep_prob:1.0}) #计算预测和真值有多少个相同的
print(logits)
print(prediction)
true_count+=np.sum(prediction)
print(true_count/total_number) #total_number是batch_size的大小,因为每次测试都是投进去一个batch的
这样就完成了用自己的数据训练VGG-16了,并做了测试和模型保存,那么下一次想使用自己保存的模型如何使用呢,下一篇博客介绍如何使用保存的模型提取特征!