【1】项目背景介绍
软件配置:tensorflow 1.9.0,OpenCV4.0,Alexnet,pycharm.
目的:通过微调Alexnet的全连接层的参数以适应自己的图像分类要求;
(1)下载原始的Alexnet模型文件和权重参数文件(ckpth或者npy),权重文件下载
(2)制造自己的标签数据集。参考1:Tensorflow制作并用CNN训练自己的数据集
参考2:完整实现利用tensorflow训练自己的图片数据集
(3) 调整训练模型的参数,具体包含,
分类的类别数:num_classes,数据总量:total_size; 迭代的轮数:num_epochs
每一次喂入的数量:batch_size; 重新训练的层数:train_layers; (注:上述四个参数均位于FineTuningAlexNet.py)
类别文件:class..py(此文件更改位于类别文件当中,此处可根据自己的分类要求更改)
(4)载入数据集,加载原始的npy文件或者ckpt文件,进行训练保存训练的新的ckpt文件。
(5) 载入训练好的ckpt模型转换为npy模型后加载(因为本文模型是加载npy的,若果可以加载可以不更改),测试结果。
【2】文件介绍
1 AlexNet模型文件配置
(1)搭建完整的AlxeNet模型框架,包含模型文件、测试文件、训练文件、微调项目文件、类别文件,其他文件:cell_class.py(自己的分类,此处是细胞的三分类) caffe_classes.py(原始1000分类);模型文件包含AlxeNet网络的模型:AlexNet_model.py ; 测试文件是模型进行测试:AlexNet_test.py;训练文件则是加入数据库进行训练:FineTuningAlexNet.py;参数文件包含已经训练好的模型参数:bvlc_alexnet.npy;数据制造以及载入文件:Imageprocess.py(图像预处理,批量操作,数据增强); input_selfdata.py(制造自己标签载入数据,路径方式制造);Labeledimage_TFRecord.py(制造自己标签,TFRecord格式,图像转为二进制数据);ReadMyOwnData.py(读入制造完成的TFRecord格式的二进制数据)
(2)npy(ckpt)加载的模式:①加载原始网络的npy和class(类别),进行测试,这个时候的网络分类的输出是原始网络的输出为1000个类别 ; ②根据分类要求设置自己的分类要求,以及需要重新训练和更改的类别,此处为三个类别,需要重新训练所有的全连接层fc6、fc7、fc8三层,在微调项目文件中也就是需要自己设置训练的文件中更改类别数和需要训练的参数。③保存训练的模型结果ckpt至指定的文件,注意此处的模型为适应我们分类要求的模型文件,即最后输出的类别数为我们设置的。④将ckpt转换npy格式进行加载(npy和ckpt文件存储的模型参数),用测试文件进行测试,注意此时的测试文件加载的训练好的模型,不是原始模型。判断准确率,如果满足要求,则停止训练,不满足要求继续重复训练。
2 代码
AlexNet_model.py
#定义AlexNet神经网络结构模型
import tensorflow as tf
import numpy as np
#此文件为AlexNet网络的模型图
#建立模型图
class AlexNet(object):
#keep_prob:dropout概率,num_classes:数据类别数,skip_layer
def __init__(self,x,keep_prob,num_classes,skip_layer,weights_path='DEFAULT'):
self.X=x#输入
self.NUM_CLASSES=num_classes#输出类别
self.KEEP_PROB=keep_prob#剪枝概率
self.SKIP_LAYER=skip_layer
if weights_path=='DEFAULT':#原始模型路径
self.WEIGHTS_PATH='bvlc_alexnet.npy'
print("成功载入原始模型!")
else:
self.WEIGHTS_PATH=weights_path
print("成功加载上一次模型!")
self.create()
def create(self):
#第一层:卷积层-->最大池化层-->LRN
conv1=conv_layer(self.X,11,11,96,4,4,padding='VALID',name='conv1')
self.conv1=conv1
pool1=max_pool(conv1,3,3,2,2,padding='VALID',name='pool1')
norm1=lrn(pool1,2,2e-05,0.75,name='norml')
#第二层:卷积层-->最大池化层-->LRN
conv2=conv_layer(norm1,5,5,256,1,1,groups=2,name='conv2')
self.conv2=conv2
pool2=max_pool(conv2,3,3,2,2,padding='VALID',name='pool2')
norm2=lrn(pool2,2,2e-05,0.75,name='norm2')
#第三层:卷积层
conv3=conv_layer(norm2,3,3,384,1,1,name='conv3')
self.conv3=conv3
#第四层:卷积层
conv4=conv_layer(conv3,3,3,384,1,1,groups=2,name='conv4')
self.conv4=conv4
#第五层:卷积层-->最大池化层
conv5=conv_layer(conv4,3,3,256,1,1,groups=2,name='conv5')
self.conv5=conv5
pool5=max_pool(conv5,3,3,2,2,padding='VALID',name='pool5')
#第六层:全连接层
flattened=tf.reshape(pool5,[-1,6*6*256])
fc6=fc_layer(flattened,6*6*256,4096,name='fc6')
dropout6=dropout(fc6,self.KEEP_PROB)
#第七层:全连接层
fc7=fc_layer(dropout6,4096,4096,name='fc7')
dropout7=dropout(fc7,self.KEEP_PROB)
#第八层:全连接层,不带激活函数
self.fc8=fc_layer(dropout7,4096,self.NUM_CLASSES,relu=False,name='fc8')
#加载神经网络预训练参数,将存储于self.WEIGHTS_PATH的预训练参数赋值给那些没有在self.SKIP_LAYER中指定的网络层的参数
def load_initial_weights(self,session):
#下载权重文件
weights_dict=np.load(self.WEIGHTS_PATH,encoding='bytes').item()
for op_name in weights_dict:
if op_name not in self.SKIP_LAYER:
with tf.variable_scope(op_name,reuse=True):
for data in weights_dict[op_name]:
#偏置项
if len(data.shape)==1:
var=tf.get_variable('biases',trainable=False)
session.run(var.assign(data))
#权重
else:
var=tf.get_variable('weights',trainable=False)
session.run(var.assign(data))
#定义卷积层,当groups=1时,AlexNet网络不拆分;当groups=2时,AlexNet网络拆分成上下两个部分。
def conv_layer(x,filter_height,filter_width,num_filters,stride_y,stride_x,name,padding='SAME',groups=1):
#获得输入图像的通道数
input_channels=int(x.get_shape()[-1])
#创建lambda表达式
convovle=lambda i,k:tf.nn.conv2d(i,k,strides=[1,stride_y,stride_x,1],padding=padding)
with tf.variable_scope(name) as scope:
#创建卷积层所需的权重参数和偏置项参数
weights=tf.get_variable("weights",shape=[filter_height,filter_width,input_channels/groups,num_filters])
biases=tf.get_variable("biases",shape=[num_filters])
if groups==1:
conv=convovle(x,weights)
#当groups不等于1时,拆分输入和权重
else:
input_groups=tf.split(axis=3,num_or_size_splits=groups,value=x)
weight_groups=tf.split(axis=3,num_or_size_splits=groups,value=weights)
output_groups=[convovle(i,k) for i,k in zip(input_groups,weight_groups)]
#单独计算完后,再次根据深度连接两个网络
conv=tf.concat(axis=3,values=output_groups)
#加上偏置项
bias=tf.reshape(tf.nn.bias_add(conv,biases),conv.get_shape().as_list())
#激活函数
relu=tf.nn.relu(bias,name=scope.name)
return relu
#定义全连接层
def fc_layer(x,num_in,num_out,name,relu=True):
with tf.variable_scope(name) as scope:
#创建权重参数和偏置项
weights=tf.get_variable("weights",shape=[num_in,num_out],trainable=True)
biases=tf.get_variable("biases",[num_out],trainable=True)
#计算
act=tf.nn.xw_plus_b(x,weights,biases,name=scope.name)
if relu==True:
relu=tf.nn.relu(act)
return relu
else:
return act
#定义最大池化层
def max_pool(x,filter_height,filter_width,stride_y,stride_x,name,padding='SAME'):
return tf.nn.max_pool(x,ksize=[1,filter_height,filter_width,1],strides=[1,stride_y,stride_x,1],padding=padding,name=name)
#定义局部响应归一化LPN
def lrn(x,radius,alpha,beta,name,bias=1.0):
return tf.nn.local_response_normalization(x,depth_radius=radius,alpha=alpha,beta=beta,bias=bias,name=name)
#定义dropout
def dropout(x,keep_prob):
return tf.nn.dropout(x,keep_prob)
AlexNet_test.py
import tensorflow as tf
import AlexNet_model
from Imageprocess import *
import numpy as np
import cv2
import caffe_classes #原有的类别文件
import cell_class #细胞分类的类别文件
import matplotlib.pyplot as plt
import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='3'
#此文件为AlexNe模型的测试文件
'''
注意点:
【1】session中的全局初始化一定要在循环预测图像之外,即只需要初始化一次就好,若在循环内部则python会报错;
'''
#数剪枝概率
keep_prob=0.5
#数据类别数
#num_classes=1000
#原始为1000个类别,现在改为2个
num_classes=3
#指定的层
skip_layer=[]
#待测试的数据类型,表示1张图片,图片的大小为227x227,通道数为3;
x=tf.placeholder(tf.float32,[1,227,227,3],name='x-input')
#加载npy文件,之前训练保存的模型文件
weights_path="D:\\pythonprocedure\\FineTuningAlexNet02\\npydata\\alexnet_cell.npy"
#ckpt模型文件路径
#weights_path='D:\\pythonprocedure\\FineTuningAlexNet02\\model\\alexnet_model.ckpt.data-00000-of-00001'
#定义神经网络结构,初始化模型
model=AlexNet_model.AlexNet(x,keep_prob,num_classes,skip_layer,weights_path)
#这里实例化conv1和conv5的目的是在session中显示特征图。
conv1=model.conv1
conv5=model.conv5
score=model.fc8
#获得神经网络前向传播的softmax层输出
softmax=tf.nn.softmax(score)
print("gamestart")
#测试图片文件目录
openpath='D:\\pythonprocedure\\FineTuningAlexNet02\\data\\test3\\testori\\'
#测试结果保存目录
#imagepath='D:\\pythonprocedure\\FineTuningAlexNet02\\data\\test3\\testresult\\'
#测试单张图片读取路径
# image = cv2.imread("Lym-0.bmp")
# img_resized = cv2.resize(image, (227, 227))
#文件夹图片测试路径
images=GetImg(openpath)
lenth=len(images)
text5="number"+" "+"lymp"+" "+"Neup"+" "+"Others"
print(text5)
with tf.Session() as sess:
#全局参数初始化
sess.run(tf.global_variables_initializer())
model.load_initial_weights(sess)
#循环遍历文件夹图片
for idex in range(lenth):
image=images[idex]
test=np.reshape(image,(1,227,227,3))
#sess.run()函数运行张量返回的是就是对应的数组
soft,con1,con5=sess.run([softmax,conv1,conv5],feed_dict={x:test})
#获取其中最大值所在的索引
maxx=np.argmax(soft)
#【1】找到目标所属的类别 原始网络的类别
# ress=caffe_classes.class_names[maxx]
# text='Predicted class:'+str(maxx)+'('+ress+')'
#【2】更改类别后的类别
ress=cell_class.class_names[maxx]
text='Predicted cell_class:'+str(maxx)+'('+ress+')'
#显示测试类别
img_resizeds = cv2.resize(image, (700, 500))
cv2.putText(img_resizeds,text, (20,20), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
#显示属于该类别的概率
probabilitys=str(soft[0][maxx])
Cellprob=round((soft[0][maxx]),3)
cv2.putText(img_resizeds,'with probability:'+probabilitys, (20,50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
#保存图像和图像的类别
cv2.imshow('test_image', img_resizeds)
#image_path = imagepath+'%s-%s.jpg' % ('result',str(idex))
#cv2.imwrite(image_path,img_resizeds)
Lymp=0
Neup=0
Other=0
if ress=="Lym":
lymp=Cellprob
Neup=0
Other=0
elif ress=="Neu":
lymp=0
Neup=Cellprob
Other=0
else:
lymp=0
Neup=0
Other=Cellprob
text2=str(idex)+" "+str(lymp)+" "+str(Neup)+" "+str(Other)
print(text2)
#显示10秒
cv2.waitKey(10)
print("end")
FineTuningAlexNet.py
#利用Tensorflow对预训练的AlexNet网络进行微调
import tensorflow as tf
import numpy as np
import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='3'
from AlexNet_model import AlexNet
import input_selfdata
from Ckpt_to_npy import *
"""
此文件是用来进行微调AlexNet模型,并将其结果模型保存为
[1] ckpt的保存路径
[2] npy保存路径和ckpt打开路径
[3] 自己npy打开路径(二次训练的时候需要加载的路径)
[4] 原始npy打开路径(初次训练时加载的路径)
文件包含4个文件步骤
初次训练:如果初次训练只能加载原始的模型参数,因此只需要使用[1][2][4]三个步骤
二次训练:[1][2][3]三个步骤
"""
#[1]训练阶段需要CKPT的保存路径和训练图片的路径
#ckpt模型保存的路径和文件名。
MODEL_SAVE_PATH="D:\\pythonprocedure\\FineTuningAlexNet02\\model\\"
MODEL_NAME="alexnet_model.ckpt"
#训练集图片所在路径
train_dir='D:\\pythonprocedure\\FineTuningAlexNet02\\data\\test3\\trainoi\\train\\'
#[2]需要ckpt的路径和npy的保存路径,将ckpt转换为npy
#ckpt模型文件路径
open_ckpt_path='D:\\pythonprocedure\\FineTuningAlexNet02\\model\\alexnet_model.ckpt'
#npy模型文件保存路径
save_npy_path="D:\\pythonprocedure\\FineTuningAlexNet02\\npydata\\"
#[3]加载npy文件,之前训练保存的模型文件
weights_path="D:\\pythonprocedure\\FineTuningAlexNet02\\npydata\\alexnet_cell.npy"
#[4]加载npy文件,原始网络的模型文件
#weights_path="D:\\pythonprocedure\\FineTuningAlexNet02\\npydata\\alexnet_cell.npy"
'''
total_size=我们训练的图片总数
learning_rate=我们设置的学习率
num_epochs=迭代的轮数,一轮表示所有样本被训练一次
batch_size=每次喂入的数据量
注:每一轮我们需要喂入的数据量次数为train_batches_per_epoch=(total_size/batch_size)
即外圈循环为训练的轮数就是数据集用几次,内层循环为train_batches_per_epoch数量,
一轮表示训练完所有的数据一次。
num_classes=我们需要的分类数,自己根据需要定,二分类为2,五分类为5,模型自己会算
注:需要同时重新命名一个class(分类时候的名称,比如标签0为cat,标签1为dog.
则需要在class中删除原来的,写如自己的)
train_layers=需要重新训练的网络层
余下参数可以不更改
'''
#训练图片的尺寸
image_size=227
#训练集中的图片总数
total_size=63
#学习率
learning_rate=0.001
#训练完整数据集迭代轮数,训练完所有的图片所需要的轮数
num_epochs=7
#数据块大小,每次喂入的数据两
batch_size=9
#执行Dropout操作所需的概率值
dropout_rate=0.5
#类别数目
num_classes=3
#需要重新训练的层
train_layers=['fc8','fc7','fc6']
#读取本地图片,制作自己的训练集,返回image_batch,label_batch
train, train_label = input_selfdata.get_files(train_dir)
x,y=input_selfdata.get_batch(train,train_label,image_size,image_size,batch_size,2000)
keep_prob=tf.placeholder(tf.float32)
#定义神经网络结构,初始化模型
model =AlexNet(x,keep_prob,num_classes,train_layers,weights_path)
#获得神经网络前向传播的输出
score=model.fc8
#获得想要训练的层的可训练变量列表
var_list = [v for v in tf.trainable_variables() if v.name.split('/')[0] in train_layers]
#定义损失函数,获得loss
with tf.name_scope("cross_ent"):
loss=tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=score,labels=y))
#定义反向传播算法(优化算法)
with tf.name_scope("train"):
# 获得所有可训练变量的梯度
gradients = tf.gradients(loss, var_list)
gradients = list(zip(gradients, var_list))
# 选择优化算法,对可训练变量应用梯度下降算法更新变量
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train_op = optimizer.apply_gradients(grads_and_vars=gradients)
#使用前向传播的结果计算正确率 计算百分比
with tf.name_scope("accuracy"):
correct_pred=tf.equal(tf.cast(tf.argmax(score,1),tf.int32),y)
accuracy=tf.reduce_mean(tf.cast(correct_pred,tf.float32))
#Initialize an saver for store model checkpoints 加载模型
saver=tf.train.Saver()
# 每个epoch中验证集/测试集需要训练迭代的轮数
train_batches_per_epoch = int(np.floor(total_size/batch_size))
with tf.Session() as sess:
#变量初始化
tf.global_variables_initializer().run()
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(coord=coord)
try:
for epoch in range(num_epochs):
tst="第"+str(epoch+1)+"轮"
print(tst)
for step in range(train_batches_per_epoch):
#while not coord.should_stop():
if coord.should_stop():
break
_,loss_value,accu=sess.run([train_op,loss,accuracy],feed_dict={keep_prob: 1.})
print("Afetr %d training step(s),loss on training batch is %g,accuracy is %g." % (step,loss_value,accu))
saver.save(sess,os.path.join(MODEL_SAVE_PATH,MODEL_NAME))
ckpttonpy(open_ckpt_path,save_npy_path)
except tf.errors.OutOfRangeError:
print('done!')
finally:
coord.request_stop()
coord.join(threads)