一、测试平台
python:3.6.5
tensorflow:1.6.0
keras:2.1.5
二、前期准备
1、数据收集:本例收集了25000张小猫、小狗的图片,二者各占一半,当然,训练集数据多多益善;
2、数据集预处理:
①猫和狗的图片分别重命名,命名格式为:“cat.number”/“dog.number”,其中number=1,2,3,4……n。文件重命名这一块如果有问题请参考:图片批量重命名;
②将猫和狗的图片放在文件夹:“./data/image/pre_train/”这个文件夹下,并在同一目录创建文件夹train(“./data/image/train/”);
③将预训练的数据集中的图片resize成需要的大小(本例中Alexnet模型input的图片大小为(-1,224,224,3)),所以我们将pre_train文件夹下的图片resize成(224,224),并存储在train文件夹下;这一步如果有疑问,请参考文章:resize训练集图片大小并存储的方法
④在data文件夹下新建空的dataset.txt文件,根据train文件夹中已训练好的数据集制作label,格式为"cat.number.jpg;0"/“dog.number.jpg;1”
③、④步代码块如下:
#coding=utf-8
[1]
'''
对收集到的数据进行预处理,以减小训练时间;
1、我们将搜集到的图片存放在与软件脚本同一根目录下的'./data/image/pre_train/'文件夹下;
2、我们使用Image模块从pre_train文件夹读取图片,resize,并存储入'./data/image/train/'文件夹;
'''
from PIL import Image
import os
src_path = './data/image/pre_train/'
dst_path = './data/image/train/'
filelist=os.listdir(src_path)
for img in filelist:
image=Image.open(src_path+img)
image_resize=image.resize((224,224),resample=2)
'''
# image.resize(size,resample=0) #sesam用于表示改变图像过程中的插值方法,0:双线性插值;1:最邻近插值;2:双三次插值;3|面积插值法
# 参考:python: PIL的Image.resize()函数:
'''
image_resize.save(dst_path+img)
[2]
'''
生成标签label
'''
import os
photos=os.listdir('./data/image/train/') #os.listdir:用于返回一个由文件名和目录组成的列表
with open('./data/dataset.txt','w') as f:
for photo in photos:
name=photo.split('.')[0]
if name=='cat':
f.write(photo+';0\n') #\n:换行
elif name=='dog':
f.write(photo+';1\n')
f.close()
三、训练
Alexnet模型如下所示
注释:
1、一张原始图片被resize到(224,224,3);
2、使用步长为4x4,大小为11的卷积核对图像进行卷积,输出的特征层为96层,输出的shape为(55,55,96);
3、使用步长为2的最大池化层进行池化,此时输出的shape为(27,27,96)
4、使用步长为1x1,大小为5的卷积核对图像进行卷积,输出的特征层为256层,输出的shape为(27,27,256);
5、使用步长为2的最大池化层进行池化,此时输出的shape为(13,13,256);
6、使用步长为1x1,大小为3的卷积核对图像进行卷积,输出的特征层为384层,输出的shape为(13,13,384);
7、使用步长为1x1,大小为3的卷积核对图像进行卷积,输出的特征层为384层,输出的shape为(13,13,384);
8、使用步长为1x1,大小为3的卷积核对图像进行卷积,输出的特征层为256层,输出的shape为(13,13,256);
9、使用步长为2的最大池化层进行池化,此时输出的shape为(6,6,256);
10、两个全连接层,最后输出为1000类
代码块:
from keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
from keras.utils import np_utils
from keras.optimizers import Adam
from model.AlexNet import AlexNet
import numpy as np
import utils
import cv2
from keras import backend as K
K.set_image_dim_ordering('tf')
# K.image_data_format()=="channel_first"
def generate_arrays_from_file(lines, batch_size):
# 获取总长度
n = len(lines)
i = 0
while 1:
X_train = []
Y_train = []
# 获取一个batch_size大小的数据
for b in range(batch_size):
if i == 0:
np.random.shuffle(lines) #shuffle:洗牌
name = lines[i].split(';')[0]
# 从文件中读取图像
img = cv2.imread(r".\data\image\train" + '/' + name)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = img / 255
X_train.append(img)
Y_train.append(lines[i].split(';')[1])
# 读完一个周期后重新开始
i = (i + 1) % n #取模,返回除法的余数
# 处理图像
X_train=np.array(X_train)
#X_train = utils.resize_image(X_train, (224, 224))
#X_train = X_train.reshape(-1, 224, 224, 3) #-1是模糊控制的意思,n,h,w,c,n=-1代表取值不固定
Y_train = np_utils.to_categorical(np.array(Y_train), num_classes=2)
#np_utils.to_categorical(y_train,2)将原来标签(sample,label)转化为一行两列的 独热码
yield (X_train, Y_train) #并发编程中完成一个“并发”程序
if __name__ == "__main__":
# 模型保存的位置
log_dir = "./logs/"
# 打开数据集的txt
with open(r".\data\dataset.txt", "r") as f:
lines = f.readlines()
# 打乱行,这个txt主要用于帮助读取数据来训练
# 打乱的数据更有利于训练
np.random.seed(10101) #预先使用random.seed(x)设定好种子之后,其中x可以是任意数字,比如10,先调用它的情况下,使用random生成的随数将会是同一个,设置的seed()值仅有一个有效
np.random.shuffle(lines)
np.random.seed(None)
# 90%用于训练,10%用于估计。
num_val = int(len(lines) * 0.1)
num_train = len(lines) - num_val
# 建立AlexNet模型
model = AlexNet()
# 保存的方式,3世代保存一次
checkpoint_period1 = ModelCheckpoint( #checkpoint:检查站
log_dir + 'ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',
monitor='acc', #monitor:三代保存一次
save_weights_only=False,
save_best_only=True,
period=3 #period:周期
)
# 学习率下降的方式,acc三次不下降就下降学习率继续训练
reduce_lr = ReduceLROnPlateau( #plateau:稳定水平,达到平衡时期
monitor='acc',
factor=0.5, #factor:要素
patience=3, #patience:耐心
verbose=1 #verboss:冗长的
)
# 是否需要早停,当val_loss一直不下降的时候意味着模型基本训练完毕,可以停止
early_stopping = EarlyStopping(
monitor='val_loss',
min_delta=0,
patience=10,
verbose=1
)
# 交叉熵
model.compile(loss='categorical_crossentropy', #compile:编译; categorical_crossentropy:交叉熵
optimizer=Adam(lr=1e-3), #optimizer:优化器
metrics=['accuracy']) #metrics:度量; accuracy:精度,准确度
# 一次的训练集大小
batch_size = 128 #batch:一批
print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
# 开始训练
model.fit_generator(generate_arrays_from_file(lines[:num_train], batch_size),
steps_per_epoch=max(1, num_train // batch_size),
'''
#由于generator()函数循环没有终止条件,fit_generator也不知道一个epoch什么时候结束,所以我们需要动手指定steps_per_epoch参数
一般的数值即为len(y)//batch_size.如果过数据集大小不能整除batch_size,而且你打算使用最后一个batch的数据(该batch比batch_size要小),
此时使用np.ceil(len(y)//batch_size)
'''
validation_data=generate_arrays_from_file(lines[num_train:], batch_size),
validation_steps=max(1, num_val // batch_size),
epochs=50,
initial_epoch=0,
callbacks=[checkpoint_period1, reduce_lr])
model.save_weights(log_dir + 'last1.h5')
训练结束后,“./logs/”文件夹将生成last1.h5文件,预测环节将调用这个文件
四、预测
1、在"./data/model/"文件夹下,新建text文件,并命名为“index_word”,写入:
预测时,可以通过下面四行代码,调用index_word文件。
#coding=utf-8
#读index_word.txt文档,给预测提供结果
def print_answer(argmax):
with open("./data/model/index_word.txt", "r", encoding='utf-8') as f:
synset = [l.split(";")[1][:-1] for l in f.readlines()] #[:-1]从0到位置为-1的数,这里主要是为了过滤换行符“\n”
#print(synset[argmax])
return synset[argmax]
2、预测新图片时,我们不能保证输入图片的尺寸一定是满足要求的,所以还是要对输入的图片resize,
import numpy as np
import utils
import cv2
from keras import backend as K
from model.AlexNet import AlexNet
K.set_image_dim_ordering('tf') #tf1版本的用法,'tf'的意思是;数据按照nhwc分布
#将预测图片resize成(224,224)
def resize_image(image,size):
with tf.name_scpoe('resize image'):
images=[]
for i in image:
i=cv2.resize(i,size)
images.append(i)
images=np.array(images)
return images
if __name__ == "__main__":
model = AlexNet()
model.load_weights("./logs/last1.h5") #调用last1.h5文件
img = cv2.imread("./Test.jpg")
img_RGB = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
img_nor = img_RGB/255
img_nor = np.expand_dims(img_nor,axis = 0)
img_resize = resize_image(img_nor,(224,224))
#utils.print_answer(np.argmax(model.predict(img)))
print(utils.print_answer(np.argmax(model.predict(img_resize))))
cv2.imshow("ooo",img)
cv2.waitKey(0)