一、案例描述
使用Tensorflow框架搭建卷积神经网络模型,加载水果图片数据集(共77个子文件夹即77类水果),处理后让模型进行学习训练,最终得以预测分类测试集图片。(本次实验是在Jupyter)
图集下载地址:https://www.kaggle.com/moltean/fruits
二、项目步骤
整个项目可分为以下6个步骤
1. 数据采集
2. 数据可视化(每个分类文件夹中的第5张图片的显示)
3. 数据预处理
4. 搭建Tensorflow构建卷积神经网络模型
5. 优化器的选择与效果比较
6. 查看模型在测试集上的准确率
三、实践过程
1、数据的采集
##导包
import os
import skimage
import numpy as np
import matplotlib.pyplot as plt
from skimage import color,data,transform
from sklearn.utils import shuffle
import keras
from keras.utils import np_utils
将当前路径改为图集文件路径
##改变当前路径
os.chdir('D://fruits//fruits-360')
读图函数,方法很多,我是通过os.listdir()这个函数来实现文件夹下的循环读图,返回的数据有所有的图集数据以列表形式存放;其对应的标签,这里的标签由于其文件夹是字符串,不便于后期的独热编码转换,故而存放为外循环的迭代数;每个文件夹下的第5张图片以及其对应的文件夹名作为标签,用于后面实现老师要求的第五张图片的可视化。
##获取对应路径文件夹下的图片数据
def load_data(dir_path):
images=[] ##新建一个空列表用于存放图片数集
labels=[] ##新建一个空列表用于存放标签数集
no5_imgs=[] ##新建一个空列表用于存放每个文件夹下的第五张图片
labels_no5=[] ##新建一个空列表用于存放第五张图片对应的文件夹名
lab=os.listdir(dir_path)
n=0
for l in lab:
img=os.listdir(dir_path+l) ##img为对应路径下的文件夹
for i in img:
img_path=dir_path+l+'/'+i ##是的话获取图片路径
labels.append(int(n)) ##将外循环的迭代数n存于labels中
images.append(skimage.data.imread(img_path)) ##读取对应路径图像存放于datasets中
n+=1
no5_img=format_path(img) ##将图片按正确顺序排列一下,便于取得第5张图片
img5_path=dir_path+l+'/'+no5_img
labels_no5.append(l)
no5_imgs.append(skimage.data.imread(img5_path)) ##将每个类别的第五张图片读出后存放于数据集no5_imgs中
return images,labels,no5_imgs,labels_no5 ##返回的images内的图片存放顺序与实际文件夹中存放的顺序不同
为了让你们更好地看到文件的读取顺序以及以上函数的作业,我输出一下读取的图片路径,输出代码不显示在以上函数中
我们可以看到本来想要的读取顺序是0_100.jpg、10_100.jpg……,但是最终读到的结果不是这样的,如果你们只是做模型训练可以不管读图顺序,但由于我们有要求要显示 文件夹下的第五张图片,故而引申出以下两个函数
调整获取到的图集的顺序的函数,由于部分图片的命名规则不同大致可以分为两类,一类是文件名分割后的第一部分可以转换为整型的和不可以转换为整型的,大致命名格式如下图:
##用于顺序读取文件夹内文件的函数
def format_path(img): ##img为 os.listdir(标签文件夹) 的结果
yes_int=[] ##新建列表用于存放分割后的第一项可转化为整型的文件名
for s in range(len(img)): ##遍历
img[s] = img[s].split('_') ##把文件名以'_'为间隔符分开
if(is_number(img[s][0])): ##判断文件名分割后的第一部分能否转化为整型
img[s][0]=int(img[s][0]) ##把文件名第一部分转换为整数
yes_int.append(img[s]) ##将该文件名放到没有带r的列表中
yes_int.sort() ##可以转化为整型的文件名的列表进行排序
for yi in range(len(yes_int)): ##变量
yes_int[yi][0]=str(yes_int[yi][0]) ##把之前转为整型的部分再转回字符串
yes_int[yi]=yes_int[yi][0]+'_'+yes_int[yi][1] ##进行拼接
no5_img=yes_int[4]
return no5_img
##判断一个一个数据是否能够转换为整形
def is_number(s):
try:
float(s)
return True
except ValueError:
pass
try:
import unicodedata
unicodedata.numeric(s)
return True
except (TypeError, ValueError):
pass
return False
最终获取到的77类水果图片的数据集
##最终数据集与标签
images,labels,no5_imgs,labels_no5=load_data('./Training/')
print(len(images),len(labels),len(no5_imgs))
同理加载测试集
##最终测试数据集与标签
images_test,labels_test,no5_imgs_test=load_data('./Test/')
print(len(images_test),len(labels_test),len(no5_imgs_test),len(labels_no5)
2、数据的可视化
定义一个函数遍历no5_imgs数据集,显示里面的图片以及对应的标签labels_no5
##定义第5张图片可视化函数
def display_no5_img(no5_imgs,labels_no5):
fig = plt.figure(figsize=(15,15)) ##显示的尺寸为15*15
for i in range(len(no5_imgs)):
plt.subplot(11,7,(i+1)) ##显示为11行,每行7个
plt.title("{0}".format(labels_no5[i])) ##显示标题
plt.imshow(no5_imgs[i]) ##显示图片
plt.axis('off') ##不显示坐标轴
plt.show()
##调用显示函数
display_no5_img(no5_imgs,labels_no5)
结果如下所示
3、数据的预处理
由于77类水果的数据集约3万多张图片,我的电脑承受不了这么多图片的训练,故而我只选取数据集中的前20类进行训练,当然如果你们的电脑可以承受,你们可以自己去试一下,或者对图片进行批量裁剪可能可以进行77类的实验。下面是加载小数据集的函数。
##由于数据集过大,先加载小部分进行训练,n代表几个类
def load_small_data(dir_path,m):
images_m=[] ##新建一个空列表用于存放图片数集
labels_m=[] ##新建一个空列表用于存放标签数集
lab=os.listdir(dir_path)
n=0
for l in lab:
if(n>=m):
break
img=os.listdir(dir_path+l) ##img为对应路径下的文件夹
for i in img:
img_path=dir_path+l+'/'+i ##是的话获取图片路径
labels_m.append(int(n)) ##将图片的上层文件夹转换为int类型存于labels中
images_m.append(skimage.data.imread(img_path)) ##读取对应路径图像存放于images_m中
n+=1
return images_m,labels_m ## m类标签以及数据
images_20,labels_20=load_small_data('./Training/',20) ##训练集
images_test_20,labels_test_20=load_small_data('./Test/',20) ##测试集
在图像处理的领域中,有很多种方式,如图像的去噪、平移、反转、灰度化、裁剪等,下面会有一些相关的处理函数,你们可以选择用或者不用,但在本次实验中我并未用的这些上述的处理方式,而是简单的把图像数据集转换为数组形式,并将其乱序,对变迁数据集进行keras的独热编码。我使用过进行过灰度处理后的图像数据集对模型进行训练,结果对测试集的预测的准确率有所降低,我的导师给我的建议是不进行灰度处理,因为颜色也属于水果的特征之一。但我还是将之前写的灰度处理函数和图片裁剪函数贴上来。
##使用列表推导式完成图像的批量裁剪
def cut_image(images,w,h):
new_images=[skimage.transform.resize(I,(w,h)) for I in images]
return new_images
##预处理数据函数(数组化,乱序)
def prepare_data(images,labels,n_classes):
##images64=cut_image(images,64,64) ##裁剪图片大小为64*64
train_x=np.array(images)
train_y=np.array(labels)
##images_gray=color.rgb2gray(images_a) ##转灰度
indx=np.arange(0,train_y.shape[0])
indx=shuffle(indx)
train_x=train_x[indx]
train_y=train_y[indx]
train_y=keras.utils.to_categorical(train_y,n_classes) ##one-hot独热编码
return train_x,train_y
##训练集数据预处理
train_x,train_y=prepare_data(images_20,labels_20,20)
##测试数据集与标签的数组化和乱序
test_x,test_y=prepare_data(images_test_20,labels_test_20,20)
4、Tensorflow卷积神经网络的搭建
终于到我们的重头戏卷积神经网络的搭建了,我相信看我这个实践案例的朋友们都是带着基础来的,关于神经网络的理论知识我就不多说了,或者后面有时间再补文吧。
我的卷积神经网络是采用经典模型LeNet-5模型搭建的两层卷积池化,三层全连接,代码里有详细注释
import tensorflow as tf
## 配置神经网络的参数
n_classes=20 ##数据的类别数
batch_size=128 ##训练块的大小
kernel_h=kernel_w=5 ##卷积核尺寸
dropout=0.8 ##dropout的概率
depth_in=3 ##图片的通道数
depth_out1=64 ##第一层卷积的卷积核个数
depth_out2=128 ##第二层卷积的卷积核个数
image_size=train_x.shape[1] ##图片尺寸
n_sample=train_x.shape[0] ##训练样本个数
t_sample=test_x.shape[0] ##测试样本个数
##feed给神经网络的图像数据类型与shape,四维,第一维训练的数据量,第二、三维图片尺寸,第四维图像通道数
x=tf.placeholder(tf.float32,[None,100,100,3])
y=tf.placeholder(tf.float32,[None,n_classes]) ##feed到神经网络的标签数据的类型和shape
keep_prob=tf.placeholder(tf.float32) ##dropout的placeholder(解决过拟合)
fla=int((image_size*image_size/16)*depth_out2) ##用于扁平化处理的参数经过两层卷积池化后的图像大小*第二层的卷积核个数
##定义各卷积层和全连接层的权重变量
Weights={"con1_w":tf.Variable(tf.random_normal([kernel_h,kernel_w,depth_in,depth_out1])),\
"con2_w":tf.Variable(tf.random_normal([kernel_h,kernel_w,depth_out1,depth_out2])),\
"fc_w1":tf.Variable(tf.random_normal([int((image_size*image_size/16)*depth_out2),1024])),\
"fc_w2":tf.Variable(tf.random_normal([1024,512])),\
"out":tf.Variable(tf.random_normal([512,n_classes]))}
##定义各卷积层和全连接层的偏置变量
bias={"conv1_b":tf.Variable(tf.random_normal([depth_out1])),\
"conv2_b":tf.Variable(tf.random_normal([depth_out2])),\
"fc_b1":tf.Variable(tf.random_normal([1024])),\
"fc_b2":tf.Variable(tf.random_normal([512])),\
"out":tf.Variable(tf.random_normal([n_classes]))}
## 定义卷积层的生成函数
def conv2d(x,W,b,stride=1):
x=tf.nn.conv2d(x,W,strides=[1,stride,stride,1],padding="SAME")
x=tf.nn.bias_add(x,b)
return tf.nn.relu(x)
## 定义池化层的生成函数
def maxpool2d(x,stride=2):
return tf.nn.max_pool(x,ksize=[1,stride,stride,1],strides=[1,stride,stride,1],padding="SAME")
## 定义卷积神经网络生成函数
def conv_net(x,weights,biases,dropout):
## Convolutional layer 1(卷积层1)
conv1 = conv2d(x,Weights['con1_w'],bias['conv1_b']) ##100*100*64
conv1 = maxpool2d(conv1,2) ##经过池化层1 shape:50*50*64
## Convolutional layer 2(卷积层2)
conv2 = conv2d(conv1,Weights['con2_w'],bias['conv2_b']) ##50*50*128
conv2 = maxpool2d(conv2,2) ##经过池化层2 shape:25*25*128
## Fully connected layer 1(全连接层1)
flatten = tf.reshape(conv2,[-1,fla]) ##Flatten层,扁平化处理
fc1 = tf.add(tf.matmul(flatten,Weights['fc_w1']),bias['fc_b1'])
fc1 = tf.nn.relu(fc1) ##经过relu激活函数
print(flatten.get_shape())
## Fully connected layer 2(全连接层2)
fc2 = tf.add(tf.matmul(fc1,Weights['fc_w2']),bias['fc_b2']) ##计算公式:输出参数=输入参数*权值+偏置
fc2 = tf.nn.relu(fc2) ##经过relu激活函数
## Dropout(Dropout层防止预测数据过拟合)
fc2 = tf.nn.dropout(fc2,dropout)
## Output class prediction
prediction = tf.add(tf.matmul(fc2,Weights['out']),bias['out']) ##输出预测参数
return prediction
5、优化器的选择与效果比较
以下是我在实验时记录的一些TensorFlow优化器的特点、使用效果以及最终的准确率记录,当然并不是所有的优化器我都进行过测试,你们如果电脑性能较优且有耐性的可以一一去试验一下,当然实验结果测试集预测的准确率还可你feed到神经网络的块的大小以及训练的次数相关,如果要比较优化器的优差还需考虑其他条件不变。我测试的20类数据集,相同训练次数以及相同学习率的情况下,Adam的准确率更高一点。
当然有不同的优化器也有不同的损失函数,这里不做详解,可以自行了解。我选用的是交叉熵损失函数,在下一期的Tensorboard可视化中会有损失函数的下降曲线图。
## 优化预测准确率
prediction=conv_net(x,Weights,bias,keep_prob) ##生成卷积神经网络
cross_entropy=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=prediction,labels=y)) ##交叉熵损失函数
optimizer=tf.train.AdamOptimizer(0.0009).minimize(cross_entropy) ##选择优化器以及学习率
##optimizer=tf.train.GradientDescentOptimizer(0.1).minimize(cross_entropy)
##optimizer=tf.train.AdagradOptimizer(0.001).minimize(cross_entropy) ##选择优化器以及学习率
## 评估模型
correct_pred=tf.equal(tf.argmax(prediction,1),tf.argmax(y,1))
accuracy=tf.reduce_mean(tf.cast(correct_pred,tf.float32))
这里有一个小函数是用来将一次训练的数据分成n块小数据,feed给神经网络
##训练块数据生成器
def gen_small_data(inputs,batch_size):
i=0
while True:
small_data=inputs[i:(batch_size+i)]
i+=batch_size
yield small_data
6.训练数据并查看模型在测试集上的准确率
## 初始会话并开始训练过程
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(5):
train_x,train_y=prepare_data(images_20,labels_20,20) ##重新预处理数据
train_x=gen_small_data(train_x,batch_size) ##生成图像块数据
train_y=gen_small_data(train_y,batch_size) ##生成标签块数据
for j in range(int(n_sample/batch_size)+1):
x_=next(train_x)
y_=next(train_y)
##准备验证数据
validate_feed={x:x_,y:y_,keep_prob:0.8}
if i % 1 == 0:
sess.run(optimizer, feed_dict=validate_feed)
loss,acc = sess.run([cross_entropy,accuracy],feed_dict={x:x_,y:y_,keep_prob:0.8})
print("Epoch:", '%04d' % (i+1),"cost=", "{:.9f}".format(loss),"Training accuracy","{:.5f}".format(acc))
print('Optimization Completed')
##准备测试数据
test_x=test_x[0:400]
test_y=test_y[0:400]
test_feed={x:test_x,y:test_y,keep_prob: 0.8}
y1 = sess.run(prediction,feed_dict=test_feed)
test_classes = np.argmax(y1,1)
print('Testing Accuracy:',sess.run(accuracy,feed_dict=test_feed))
下面是20类水果训练了5次使用Adam优化器训练后的训练集准确率及测测试集准确率约为0.86