0. 前言
本文主要写给自己,记录下这几天做的CNN模型。虽然这个模型最后的准确率只有53%,但我觉得还是很有必要记录下整个模型的流程的。
一方面是为了以后再复习CNN时可以快速想起来如何构建模型,另一方面是等以后等哪天突然知道我这个模型存在什么缺陷时,可以回过头来快速回顾一下模型然后再进行修改以提高准确率。
这个CNN的目的是对图片进行分类,训练集共9866张食物图片,共有11个种类。
1. 正文
1.1 先引入需要的第三方库
#import 需要的第三方库
import numpy as np
import os
import cv2
import tensorflow as tf
1.2 如何将图片数据处理成能够输入CNN的数据
def readfile(path, label):
#label是bool,表示是否返回y
image_dir = os.listdir(path) #image_dir是个list
# x是个4维的numpy数组[图片个数,图片长,图片宽,色彩通道],例如,training里有9866张彩色图片,假设分辨率为64*64,x的维度为[9866, 64, 64, 3]
# y是个向量,长度为图片个数,表示图片类别标签,从0-10共有11个类别
# 注意x、y的数据类型
x = np.zeros((len(image_dir), 64, 64, 3), dtype=np.float32)
y = np.zeros((len(image_dir)), dtype=np.int32)
for i, file in enumerate(image_dir):
img = cv2.imread(path+"\\"+file) #得到图片的地址信息
x[i] = cv2.resize(img,(64, 64)) #把第i张图片大小统一为64*64
if label:
y[i] = int(file.split("_")[0])
#training要x和y,testing只要x
if label:
return x, y
else:
return x
1.3 定义get_batch函数
def get_batch(x, y, train_num, batch_size):
# 每次从训练集中挑选batch_size=165个训练数据,随机小批量梯度下降
# 为了均匀的选择(即每个类别都选15个,而不至于选的这165个全是一个类别的)
# 我们从每个类别中随机挑选15个出来,具体做法是:
index = np.zeros((11, 15), dtype=np.int32)
index[0] = np.random.randint(0, 994, 15) #从第0类中随机挑10个(0-993)
index[10] = np.random.randint(994, 1703, 15) #从第10类中随机挑10个(994-1702)
index[1] = np.random.randint(1703, 2132, 15) #从第1类中随机挑10个(1703-2131)
index[2] = np.random.randint(2132, 3632, 15) #从第2类中随机挑10个(2132-3631)
index[3] = np.random.randint(3632, 4618, 15) #从第3类中随机挑10个(3632-4617)
index[4] = np.random.randint(4618, 5466, 15) #从第4类中随机挑10个(4618-5465)
index[5] = np.random.randint(5466, 6791, 15) #从第5类中随机挑10个(5466-6790)
index[6] = np.random.randint(6791, 7231, 15) #从第6类中随机挑10个(6791-7230)
index[7] = np.random.randint(7231, 7511, 15) #从第7类中随机挑10个(7231-7510)
index[8] = np.random.randint(7511, 8366, 15) #从第8类中随机挑10个(7511-8365)
index[9] = np.random.randint(8366, 9866, 15) #从第9类中随机挑10个(8366-9865)
x0 = np.zeros((batch_size,64,64,3),dtype=np.float32)
y0 = np.zeros((batch_size), dtype=np.int32)
i = 0
for j in range(11):
for k in range(15):
x0[i, :] = x[index[j][k], :]
y0[i] = y[index[j][k]]
i = i + 1
return x0, y0
get_batch函数的作用如下:
因为我们在训练CNN时要用小批量梯度下降,所以我们需要定义一个函数,使得我们每次可以得到一小批的图片数据。在上面的这个函数中,我们每次从每个类型的图片中随机挑15张出来,也就是说一个batch有11*15=165张图片。
1.4 读取训练数据
#得到training、validation、testing数据
CNN_path = "E:\\Machine Learning\\data\\data1\\hw3\\food-11"
print(10*"="+"Reading Data"+10*"=")
train_x, train_y = readfile(CNN_path+"\\training", True)
train_x = train_x.astype(np.float32)/255.0
print("Size of training data = {}".format(len(train_x)))
val_x, val_y = readfile(CNN_path+"\\validation", True)
val_x = val_x.astype(np.float32)/255.0
print("Size of validation data = {}".format(len(val_x)))
print("train_x shape:{}".format(train_x.shape))
print("train_y shape:{}".format(train_y.shape))
首先对图片数据进行一些说明
一张图片数据用cv2.imread()读入后得到的是一个numpy数组,维度为:[长, 宽, 颜色通道数],比如我们读了一张6464的彩色图片,这张图片用cv2.imread()读入后就是一个[64, 64, 3]维度的numpy数组,即共有6464*3个元素,每个元素是从0-255的整数。
对数据进行归一化处理
我们上面的代码有这样一行:
train_x = train_x.astype(np.float32)/255.0
这一行的目的就是对图片进行归一化处理,让图片数据的每个元素都在0-1之间,而不是0-255之间,这样有利于模型的训练。
1.5 搭建CNN模型
class CNN(tf.keras.Model):
def __init__(self):
super().__init__()
self.conv1 = tf.keras.layers.Conv2D(
filters = 64,
kernel_size = [3, 3],
padding = 'same',
activation = tf.nn.relu
)
self.pool1 = tf.keras.layers.MaxPool2D(pool_size=[2,2], strides=2)
self.conv2 = tf.keras.layers.Conv2D(
filters = 128,
kernel_size=[3, 3],
padding = 'same',
activation = tf.nn.relu
)
self.pool2 = tf.keras.layers.MaxPool2D(pool_size=[2,2], strides=2)
self.conv3 = tf.keras.layers.Conv2D(
filters = 256,
kernel_size=[3, 3],
padding = 'same',
activation = tf.nn.relu
)
self.pool3 = tf.keras.layers.MaxPool2D(pool_size=[2,2], strides=2)
self.conv4 = tf.keras.layers.Conv2D(
filters = 512,
kernel_size=[3, 3],
padding = 'same',
activation = tf.nn.relu
)
self.pool4 = tf.keras.layers.MaxPool2D(pool_size=[2,2], strides=2)
self.conv5 = tf.keras.layers.Conv2D(
filters = 512,
kernel_size=[3, 3],
padding = 'same',
activation = tf.nn.relu
)
self.pool5 = tf.keras.layers.MaxPool2D(pool_size=[2,2], strides=2)
self.flatten = tf.keras.layers.Reshape(target_shape=(2*2*512,))
self.dropout1 = tf.keras.layers.Dropout(0.5)
self.dense1 = tf.keras.layers.Dense(units=1024, activation=tf.nn.relu)
self.dropout2 = tf.keras.layers.Dropout(0.5)
self.dense2 = tf.keras.layers.Dense(units=512, activation=tf.nn.relu)
self.dense3 = tf.keras.layers.Dense(units=11)
def call(self, inputs):
x = self.conv1(inputs)
x = self.pool1(x)
x = self.conv2(x)
x = self.pool2(x)
x = self.conv3(x)
x = self.pool3(x)
x = self.conv4(x)
x = self.pool4(x)
x = self.conv5(x)
x = self.pool5(x)
x = self.flatten(x)
x = self.dropout1(x)
x = self.dense1(x)
x = self.dropout2(x)
x = self.dense2(x)
x = self.dense3(x)
output = tf.nn.softmax(x)
return output
模型定义了5个卷积+池化层,打平之后又加了两层全连接层,为了减少全连接层出现过拟合的可能我又在每层间加了dropout。
如果忘了怎么定义CNN模型,参考
1.6 开始训练模型
#train
num_iter = 1200
batch_size = 165
learning_rate = 0.001
model = CNN()
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
for epoch in range(num_iter):
X, y = get_batch(train_x, train_y, len(train_x), batch_size)
with tf.GradientTape() as tape:
y_pred = model(X)
loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
loss = tf.reduce_mean(loss)
print("batch %d: loss %f" % (epoch, loss.numpy()))
grads = tape.gradient(loss, model.variables)
optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))
以上代码哪里有问题,都可以在这里找到答案
1.7 模型评估
#模型评估
sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy() #交叉熵
num_batches = int(len(val_x) // batch_size)
for batch_index in range(num_batches):
start_index, end_index = batch_index * batch_size, (batch_index + 1) * batch_size
y_pred = model.predict(val_x[start_index: end_index])
sparse_categorical_accuracy.update_state(y_true=val_y[start_index: end_index], y_pred=y_pred)
print("test accuracy: %f" % sparse_categorical_accuracy.result())
1.8 最后的结果
搞了这么久,最后的结果只有:
2. 总结
毕竟是我第一次做CNN,很多东西都不了解,我问了学长也尝试了很多方法,精度都没有提高。不想花太久在这个上面了,不管是学什么很难一次都学得特别好,就比如我这次花了这么久还是没找到准确率低的原因。但这里的坑不能不补,现在没法解决不代表就把问题永远搁置,这也是我写这篇博客的一个原因,以后有能力了再拐回来解决这个问题吧。