(本文所使用的Python库和版本号: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2, Keras 2.1.6, Tensorflow 1.9.0)html
不少文章和教材都是用MNIST数据集做为深度学习届的“Hello World”程序,可是这个数据集有一个很大的特色:它是一个典型的多分类问题(一共有10个分类),在咱们刚刚开始接触深度学习时,我却是以为应该从最简单的二分类问题着手。git
在深度学习框架方面,目前比较流行的是Tensorflow,Keras,PyTorch,Theano等,可是我建议新手入门,能够从Keras入手,而后进阶时转移到Tensorflow上,实际上,Keras的后端是能够支持Tensorflow和Theano,能够说,Keras是在Tensorflow和Theano的基础上进一步封装,更加的简单实用,更容易入门,一般几行简单的代码就能够解决一个小型的项目问题。github
我这篇博文主要参考了:keras系列︱图像多分类训练与利用bottleneck features进行微调(三),这篇博文也是参考的Building powerful image classification models using very little data,但我发现这两篇博文有不少地方的代码跑不起来,主要缘由多是Keras或Tensorflow升级形成的,因此我作了一些必要的修改。后端
最经典的二分类数据集就是Kaggle竞赛中的“猫狗大战”数据集(train set有25K张图片,test set: 12.5K),此处按照原始博文的作法,我从train_set中选取1000张Dog的照片+1000张Cat照片做为咱们新的train set,选取400张Dog+400张Cat照片做为新的test set。因此train和test两个文件夹下都有两个子文件夹(cats和dogs子文件夹)。固然,选取是随机的,也是用代码来实现的,准备小数据集的代码以下:网络
def dataset_prepare(raw_set_folder,dst_folder,train_num_per_class=1000,test_num_per_class=400):
''' 准备小数据集,从原始的raw_set_folder数据集中提取train_num_per_class(每一个类别)的照片放入train中, 提取val_num_per_class(每一个类别)放入到validation文件夹中 :param raw_set_folder: 含有猫狗的照片,这些照片的名称必须为cat.101.jpg或dog.102.jpg形式 :param dst_folder: 将选取以后的图片放置到这个文件夹中 :param train_num_per_class: :param test_num_per_class: :return: '''
all_imgs=glob(os.path.join(raw_set_folder,'*.jpg'))
img_len = len(all_imgs)
assert img_len > 0, '{} has no jpg image file'.format(raw_set_folder)
cat_imgs=[]
dog_imgs=[]
for img_path in all_imgs:
img_name=os.path.split(img_path)[1]
if img_name.startswith('cat'):
cat_imgs.append(img_path)
elif img_name.startswith('dog'):
dog_imgs.append(img_path)
random.shuffle(cat_imgs)
random.shuffle(dog_imgs)
[ensure_folder_exists(os.path.join(dst_folder,type_folder,class_folder)) for type_folder in ['train','test']
for class_folder in ['dogs','cats']]
# 下面的代码能够进一步优化。。。。
for cat_img_path in cat_imgs[:train_num_per_class]: # 最开始的N个图片做为train
_, fname = os.path.split(cat_img_path) # 获取文件名和路径
shutil.copyfile(cat_img_path, os.path.join(dst_folder, 'train', 'cats',fname))
print('imgs saved to train/cats folder')
for dog_img_path in dog_imgs[:train_num_per_class]:
_, fname = os.path.split(dog_img_path) # 获取文件名和路径
shutil.copyfile(dog_img_path, os.path.join(dst_folder, 'train', 'dogs',fname))
print('imgs saved to train/dogs folder')
for cat_img_path in cat_imgs[-test_num_per_class:]: # 最末的M个图片做为test
_, fname = os.path.split(cat_img_path) # 获取文件名和路径
shutil.copyfile(cat_img_path, os.path.join(dst_folder, 'test', 'cats',fname))
print('imgs saved to test/cats folder')
for dog_img_path in dog_imgs[-test_num_per_class:]: # 最末的M个图片做为test
_, fname = os.path.split(dog_img_path) # 获取文件名和路径
shutil.copyfile(dog_img_path, os.path.join(dst_folder, 'test', 'dogs',fname))
print('imgs saved to test/dogs folder')
print('finished...')
复制代码
运行该函数便可完成小数据集的构建,下面为Keras建立图片数据流,为模型的构建作准备。app
# 2,准备训练集,keras有不少Generator能够直接处理图片的加载,加强等操做,封装的很是好
from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator( # 单张图片的处理方式,train时通常都会进行图片加强
rescale=1. / 255, # 图片像素值为0-255,此处都乘以1/255,调整到0-1之间
shear_range=0.2, # 斜切
zoom_range=0.2, # 放大缩小范围
horizontal_flip=True) # 水平翻转
train_generator = train_datagen.flow_from_directory(# 从文件夹中产生数据流
train_data_dir, # 训练集图片的文件夹
target_size=(IMG_W, IMG_H), # 调整后每张图片的大小
batch_size=batch_size,
class_mode='binary') # 此处是二分类问题,故而mode是binary
# 3,一样的方式准备测试集
val_datagen = ImageDataGenerator(rescale=1. / 255) # 只须要和trainset一样的scale便可,不需加强
val_generator = val_datagen.flow_from_directory(
val_data_dir,
target_size=(IMG_W, IMG_H),
batch_size=batch_size,
class_mode='binary')
复制代码
上面构建的generator就是keras须要的数据流,该数据流使用flow_from_directory首先从图片文件夹(好比train_data_dir)中加载图片到内存中,而后使用train_datagen来对图片进行预处理和加强,最终获得处理完成以后的batch size大小的数据流,这个数据流会无限循环的产生,直到达到必定的训练epoch数量为止。框架
上面用到了ImageDataGenerator来进行图片加强,里面的参数说明为:(能够参考Keras的官方文档)dom
rotation_range是一个0~180的度数,用来指定随机选择图片的角度。函数
width_shift和height_shift用来指定水平和竖直方向随机移动的程度,这是两个0~1之间的比例。学习
rescale值将在执行其余处理前乘到整个图像上,咱们的图像在RGB通道都是0~255的整数,这样的操做可能使图像的值太高或太低,因此咱们将这个值定为0~1之间的数。
shear_range是用来进行剪切变换的程度
zoom_range用来进行随机的放大
horizontal_flip随机的对图片进行水平翻转,这个参数适用于水平翻转不影响图片语义的时候
fill_mode用来指定当须要进行像素填充,如旋转,水平和竖直位移时,如何填充新出现的像素
因为Keras已经封装了不少Tensorflow的函数,因此在使用上更加简单容易,固然,若是想调整里面的结构和参数等,也比较麻烦一些,因此对于高手,想要调整模型的结构和自定义一些函数,能够直接用Tensorflow.
不论是Keras模型仍是Tensorflow模型,我我的认为其构建都包括两个部分:模型的搭建和模型的配置,因此能够从这两个方面来创建一个小型的模型。代码以下:
# 4,创建Keras模型:模型的创建主要包括模型的搭建,模型的配置
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras import optimizers
def build_model(input_shape):
# 模型的搭建:此处构建三个CNN层+2个全链接层的结构
model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=input_shape))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5)) # Dropout防止过拟合
model.add(Dense(1)) # 此处虽然是二分类,可是不能用Dense(2),由于后面的activation是sigmoid,这个函数只能输出一个值,即class_0的几率
model.add(Activation('sigmoid')) #二分类问题用sigmoid做为activation function
# 模型的配置
model.compile(loss='binary_crossentropy', # 定义模型的loss func,optimizer,
optimizer=optimizers.RMSprop(lr=0.0001),
metrics=['accuracy'])# 主要优化accuracy
# 二分类问题的loss function使用binary_crossentropy,此处使用准确率做为优化目标
return model # 返回构建好的模型
复制代码
这个函数就搭建了模型的结构,对模型进行了配置,主要配置了loss function, optimzer, 优化目标等,固然能够作更多其余配置。
此处,为了简单说明,只是创建了三层卷积层+两层全链接层的小型网络结构,固然,对于一些比较简单的图像问题,这个小型模型也能解决。若是须要构建更为复杂的模型,只须要自定义这个函数,修改里面的模型构建和配置方法便可。
因为此处咱们使用generator来产生数据流,故而训练时要使用fit_generator函数。代码以下:
model=build_model(input_shape=(IMG_W,IMG_H,IMG_CH)) # 输入的图片维度
# 模型的训练
model.fit_generator(train_generator, # 数据流
steps_per_epoch=train_samples_num // batch_size,
epochs=epochs,
validation_data=val_generator,
validation_steps=val_samples_num // batch_size)
复制代码
因为我在本身的笔记本上训练,没有独立显卡,更没有英伟达那么NB的显卡,故而速度很慢,可是的确能运行下去。运行的具体结果能够去个人github上看。
-------------------------------------输---------出--------------------------------
Epoch 1/20 62/62 [==============================] - 136s 2s/step - loss: 0.6976 - acc: 0.5015 - val_loss: 0.6937 - val_acc: 0.5000 Epoch 2/20 62/62 [==============================] - 137s 2s/step - loss: 0.6926 - acc: 0.5131 - val_loss: 0.6846 - val_acc: 0.5813 Epoch 3/20 62/62 [==============================] - 152s 2s/step - loss: 0.6821 - acc: 0.5544 - val_loss: 0.6735 - val_acc: 0.6100
。。。
Epoch 18/20 62/62 [==============================] - 140s 2s/step - loss: 0.5776 - acc: 0.6880 - val_loss: 0.5615 - val_acc: 0.7262 Epoch 19/20 62/62 [==============================] - 143s 2s/step - loss: 0.5766 - acc: 0.6971 - val_loss: 0.5852 - val_acc: 0.6800 Epoch 20/20 62/62 [==============================] - 140s 2s/step - loss: 0.5654 - acc: 0.7117 - val_loss: 0.5374 - val_acc: 0.7450
--------------------------------------------完-------------------------------------
从训练后的loss和acc上能够大体看出,loss在不断减少,acc也不断增大,趋势比较平稳。
此处咱们能够将训练过程当中的loss和acc绘图,看看他们的变化趋势。
# 画图,将训练时的acc和loss都绘制到图上
import matplotlib.pyplot as plt
%matplotlib inline
def plot_training(history):
plt.figure(12)
plt.subplot(121)
train_acc = history.history['acc']
val_acc = history.history['val_acc']
epochs = range(len(train_acc))
plt.plot(epochs, train_acc, 'b',label='train_acc')
plt.plot(epochs, val_acc, 'r',label='test_acc')
plt.title('Train and Test accuracy')
plt.legend()
plt.subplot(122)
train_loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(train_loss))
plt.plot(epochs, train_loss, 'b',label='train_loss')
plt.plot(epochs, val_loss, 'r',label='test_loss')
plt.title('Train and Test loss')
plt.legend()
plt.show()
复制代码
很明显,因为epoch次数太少,acc和loss都没有达到平台期,后续能够增大epoch次数来达到一个比较好的结果。在原始博文中,做者在50个epoch以后达到了约80%左右的准确率,此处我20个epoch后的准确率为74%。
单张图片的预测
模型训练好以后,就须要用来预测新的图片,看看它能不能准确的给出结果。预测函数为:
# 用训练好的模型来预测新样本
from PIL import Image
from keras.preprocessing import image
def predict(model, img_path, target_size):
img=Image.open(img_path) # 加载图片
if img.size != target_size:
img = img.resize(target_size)
x = image.img_to_array(img)
x *=1./255 # 至关于ImageDataGenerator(rescale=1. / 255)
x = np.expand_dims(x, axis=0) # 调整图片维度
preds = model.predict(x) # 预测
return preds[0]
复制代码
用这个函数能够预测单张图片:
predict(model,'E:\PyProjects\DataSet\FireAI\DeepLearning/FireAI005/cat11.jpg',(IMG_W,IMG_H))
predict(model,'E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/dog4.jpg',(IMG_W,IMG_H))
复制代码
-------------------------------------输---------出--------------------------------
array([0.14361556], dtype=float32)
array([0.9942463], dtype=float32)
--------------------------------------------完-------------------------------------
能够看出,对于单张图片cat11.jpg获得的几率为0.14,而dog4.jpg的几率为0.99,能够看出第0个类别是dog,第1个类别是cat,模型可以很好的区分开来。
多张图片的预测
若是想用这个模型来预测一个文件夹中的全部图片,那么该怎么办了?
# 预测一个文件夹中的全部图片
new_sample_gen=ImageDataGenerator(rescale=1. / 255)
newsample_generator=new_sample_gen.flow_from_directory(
'E:\PyProjects\DataSet\FireAI\DeepLearning',
target_size=(IMG_W, IMG_H),
batch_size=16,
class_mode=None,
shuffle=False)
predicted=model.predict_generator(newsample_generator)
print(predicted)
复制代码
-------------------------------------输---------出--------------------------------
Found 4 images belonging to 2 classes. [[0.14361556] [0.5149474 ] [0.71455824] [0.9942463 ]]
--------------------------------------------完-------------------------------------
上面的结果中第二个0.5149对应的应该是cat,应该小于0.5,这个预测是错误的,不过粗略估计正确率有3/4=75%。
模型通常要及时保存到硬盘上,防止数据丢失,下面是保存的代码:
# 模型保存
# model.save_weights('E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/FireAI005_Model.h5') # 这个只保存weights,不保存模型的结构
model.save('E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/FireAI005_Model2.h5') # 对于一个完整的模型,应该要保存这个
复制代码
# 模型的加载,预测
from keras.models import load_model
saved_model=load_model('E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/FireAI005_Model2.h5')
predicted=saved_model.predict_generator(newsample_generator)
print(predicted) # saved_model的结果和前面的model结果一致,表面模型正确保存和加载
复制代码
此处获得的结果和上面model预测的结果如出一辙,代表模型被正确保存和加载。
########################小**********结###############################
1,本篇文章讲解了:准备一个简单的小数据集,从数据集中创建数据流,将该数据流引入到Keras的模型中进行训练,并使用训练后的模型进行新图片的预测,而后将模型进行保存,加载保存好的模型到内存中。
2,此处使用的模型是咱们本身搭建的,结构比较简单,只有三层卷积层和两层全链接层,故而模型的准确率不过高,并且此处因为时间关系,我只训练了20个epoch,训练并无达到平台期。
#################################################################
注:本部分代码已经所有上传到(个人github)上,欢迎下载。