本文基于有一定的卷积神经网络基础之上进行VGG16模型的套用。
对于卷积神经网络来说,博主不太懂得到底几个卷积层,几个池化层才合适,才能最大程度的训练处我们想要的结果。导致前段时间一直去猜,去试。但是VGG16模型给了我们一个框架,比较适合小白了解CNN结构后进行模仿套用。这篇博客的目的就是能够让大家掌握如何去拆分已经训练好的模型结构,实现灵活使用。这样就能够能熟练的运用mlp和其他模型相结合实现更复杂的任务。
环境是python3.6+keras,后端是tensorflow-gpu1.12.0,怎么配环境可以参考博主上一篇博客哦。文末附预测结果及完整代码。
数据集
给大家附上数据集。百度云链接
提取码:i5j0
首先介绍一下数据集,数据集采用博主大数据实验室的考核任务:宝可梦识别。
简单描述一下就是训练集有5类宝可梦精灵,然后分别在对应的文件夹。
训练集每种宝可梦有250左右。
有一个无标签的测试集。vgg16强大的地方就在这,小数据集也能够有很高的准确率。
VGG-16
然后介绍一下VGG16模型。一些基础的CNN知识博主就不再赘述了,咱们简单说下直接开始实战。
VGG16的输入图像:224×224×3的RGB图,3个通道
训练参数:约138,000,000个
特点:
- 所有卷积层filter宽和高都是3,步长为1,padding都使用same convolution;
- 所有池化层的filter宽和高都是2,步长都是2;
- 更多的filter用于提取轮廓信息,更精准。
实战
那我们怎么去用呢。
- 加载VGG16模型,剥除它FC层,对图像进行预处理
- 把预处理完成的数据作为输入,分类结果为输出,建立一个mlp模型
- 开始训练
- 预测结果
首先就是导入库和预加载模型,这里要提一下,加载vgg16博主花了大概6,7小时,不懂是网络问题还是什么,因为当时凌晨了就放着等它下载,第二天早上就好了,如果嫌慢的话大家就提前下载好,或者去查查有没有什么快点的办法。
# load image and preprocess it with vgg16 structure
from keras.preprocessing.image import img_to_array, load_img
from keras.applications.vgg16 import VGG16
from keras.applications.vgg16 import preprocess_input
import numpy as np
import os
from sklearn.preprocessing import LabelBinarizer
from keras.models import Sequential
from keras.layers import Dense
from sklearn.model_selection import train_test_split
from sklearn.metrics import recall_score
import matplotlib as mlp
import matplotlib.pyplot as plt
from matplotlib.image import imread
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array
from keras.models import load_model
from PIL import Image
# 载入VGG16结构(去除全连接层)
model_vgg = VGG16(weights='imagenet', include_top=False)
# imagenet表示加载提取图片特征的结构,然后Flase表示去除全连接层
我们先定义处理一个图片的函数,到时候就可以设置个循环,让他自动处理每张图片
# define a method to load and preprocess the image
def modelProcess(img_path, model):
img = load_img(img_path, target_size=(224, 224)) # 读取图片途径,裁成224,224
img = img_to_array(img) #转换成图像数组
x = np.expand_dims(img, axis=0) # 加一个维度这样能载入VGG
x = preprocess_input(x) # 预处理
x_vgg = model.predict(x) #特征提取,这是全连接层之前的shape
# shape(1,7,7,512)
x_vgg = x_vgg.reshape(1, 25088) # 摊开来进行和全连接层的对接
return x_vgg
前文讲到vgg16每张图片都是224×224×3,实验室狗的很,里面还有4通道,图片格式也不统一,有jpg,jpeg,png,那这个函数就是统一下,让图片能够变成224×224×3
# list file names of the training datasets
def transform_format(path): # 转换格式
folders = os.listdir(path) # 读取爷爷路径下的所有文件名,也就是5个分类标签
for j in range(len(folders)):
dirName = path + '//' + folders[j] + '//' # 每一个爸爸路径
li = os.listdir(dirName) # 每个爸爸路径,也就是那个类别文件夹下的全部图片名字
for filename in li:
newname = filename
newname = newname.split(".") # 文件名以'.'为分隔,
if newname[-1] != "png": # 这里转换格式用的是简单的重命名
newname[-1] = "png"
newname = str.join(".", newname) # 这里要用str.join
filename = dirName + filename
newname = dirName + newname
os.rename(filename, newname) # 重命名
print('reading the images:%s' % (newname)) # 这步前期我是用来调试哪张图的读取出问题了,现在可删可不删
a = np.array(Image.open(newname)) # 读取图片数组
if ((len(a.shape) != 3) or (a.shape[2] != 3)): # 有些图片非RGB,这里进行判断处理
a = np.array(Image.open(newname).convert('RGB')) # 换成RGB
img = Image.fromarray(a.astype('uint8')) # 形成图片
img.save(newname) # 替换原来的图片
print(a.shape) # 用来测试的print
print("全部图片已成功转换为PNG格式")
print("全部图片已成功转换为RGB通道")
前文都是对单张图片的处理,我们这么多图片,要设置一个循环读取每张图片的,那就是这个了函数了。其中呢对于路径的父子关系,可要看清楚,爷爷路径是pokemon,然后pokemon下面有5个文件夹,每个文件夹都是一类,这就是爸爸路径,那儿子路径就是真正的每张图片的路径,也就是分类文件夹里面的了。
def read_data(path):
folders = os.listdir(path) # 读取爷爷路径下的所有文件名,也就是5个分类标签
for j in range(len(folders)): # 5个种类嘛,一共循环5次
folder = path + '//' + folders[j] # 这个就是爸爸路径了
dirs = os.listdir(folder) # 读取爸爸路径下的所有文件名,就是各个图片名字了
# 产生图片的路径
img_path = []
for i in dirs:
if os.path.splitext(i)[1] == ".png": # 已经转换过png了
img_path.append(i)
img_path = [folder + "//" + i for i in img_path]