AlexNet是2012年ImageNet竞赛冠军获得者Hinton和他的学生Alex Krizhevsky设计的。也是在那年之后,更多的更深的神经网络被提出,比如优秀的vgg,GoogLeNet。 这对于传统的机器学习分类算法而言,已经相当的出色。
结构呢现在看来还是很清晰的不复杂,不过对于当时2012年来说,这个网络的结构可谓是巨兽,参数超级多。结构如下:
**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、全连接层,4096神经元;
11、全连接层,4096神经元;
12、最后得到1000个分类。
可以看出它的结构其实和vgg特别像,基本上就是卷积池化,只是参数不同,这也导致了最后的训练参数太大,特别是第一个全连接层,最大的消耗在第一个4096的全连接层。
首先附上数据集,猫和狗各4000张图片。
链接:https://pan.baidu.com/s/1_DIrQJCjNqGYnnc-TL4vRw
提取码:al2i
首先导入库,这些库都是比较常见的,如果有特殊用处的下面会解释。
import os
import keras
from keras.preprocessing.image import img_to_array, load_img
import numpy as np
from sklearn.preprocessing import LabelBinarizer
from keras.models import Sequential
import matplotlib.pyplot as plt
from matplotlib.image import imread
from keras.models import load_model
from PIL import Image
from keras.layers import Dense,Activation,Conv2D,MaxPooling2D,Flatten,Dropout,BatchNormalization
from keras.optimizers import Adam
from keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau, TensorBoard
from keras.utils.np_utils import to_categorical
这里就是先导入我们的数据集了,一些常规操作就不细讲了,如果阅读吃力可以先看博主最早的卷积网络实战,那里面有很详细的说明。
这里all_path就是所有图片地址了,最后shuffle打乱一下。避免出现验证集正确率反复横跳的情况。
all_path = []
father_path ='D:/桌面/机器学习+深度学习/源码/Artificial_Intelligence-\
master/artificial_intelligence(更多IT课程公众号:某课联盟 )/week8/dataset/training_set'
class_folders = os.listdir(father_path) # 读取爷爷路径下的所有文件名,也就是5个分类标签
for class_folder in class_folders:
img_names = os.listdir(father_path + '/' + class_folder)
for img_name in img_names:
path = father_path + '/' + class_folder + '/' + img_name
all_path.append(path)
np.random.shuffle(all_path)
这里定义这个网络的结构,不过博主用原来网络的参数进行训练的时候发现效果不太好,反复尝试,查阅资料后,认为应该是数据集过小,才8000张,可能不太适用这个大网络。将filters全部缩减一半,在全连接层改变神经元。这样子训练完效果好得多。具体就不说了,阅读吃力地小伙伴可以看看前面的文章,有更详细的关于卷积神经网络的实战。
def AlexNet(input_shape=(224,224,3),output_shape=2):
# AlexNet
model = Sequential()
# 使用步长为4x4,大小为11的卷积核对图像进行卷积,输出的特征层为96层,输出的shape为(55,55,96);
# 所建模型后输出为48特征层
model.add(Conv2D(
filters=48,
kernel_size=(11,11),
strides=(4,4),
padding='valid',
input_shape=input_shape,
activation='relu'
)
)
model.add(BatchNormalization())
# 使用步长为2的最大池化层进行池化,此时输出的shape为(27,27,96)
model.add(MaxPooling2D(
pool_size=(3,3),
strides=(2,2),
padding='valid'
)
)
# 使用步长为1x1,大小为5的卷积核对图像进行卷积,输出的特征层为256层,输出的shape为(27,27,256);
# 所建模型后输出为128特征层
model.add(
Conv2D(
filters=128,
kernel_size=(5,5),
strides=(1,1),
padding='same',
activation='relu'
)
)
model.add(BatchNormalization())
# 使用步长为2的最大池化层进行池化,此时输出的shape为(13,13,256);
model.add(
MaxPooling2D(
pool_size=(3,3),
strides=(2,2),
padding='valid'
)
)
# 使用步长为1x1,大小为3的卷积核对图像进行卷积,输出的特征层为384层,输出的shape为(13,13,384);
# 所建模型后输出为192特征层
model.add(
Conv2D(
filters=192,
kernel_size=(3,3),
strides=(1,1),
padding='same',
activation='relu'
)
)
# 使用步长为1x1,大小为3的卷积核对图像进行卷积,输出的特征层为384层,输出的shape为(13,13,384);
# 所建模型后输出为192特征层
model.add(
Conv2D(
filters=192,
kernel_size=(3,3),
strides=(1,1),
padding='same',
activation='relu'
)
)
# 使用步长为1x1,大小为3的卷积核对图像进行卷积,输出的特征层为256层,输出的shape为(13,13,256);
# 所建模型后输出为128特征层
model.add(
Conv2D(
filters=128,
kernel_size=(3,3),
strides=(1,1),
padding='same',
activation='relu'
)
)
# 使用步长为2的最大池化层进行池化,此时输出的shape为(6,6,256);
model.add(
MaxPooling2D(
pool_size=(3,3),
strides=(2,2),
padding='valid'
)
)
# 两个全连接层,最后输出为1000类,这里改为2类
# 缩减为1024
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(output_shape, activation='softmax'))
return model
像vgg,Alexnet这种网络,都是比较大,参数多。一次性fit容易爆内存。所以定义一个生成器,不断生成训练数据是很有必要的。这里详细说明以下。首先我们每次返回的数量是一个batchsize,这里是64,也就是每次返回64个数据到model训练。根据路径我们分割出图片路径和图片标签,存于Xtrain和Ytrain,不过这里要注意Ytrain返回的是独热编码后的。也就是to_categorical(np.array(Y_train), num_classes=2),numclasses代表种类,猫和狗就是2类。to_categorical是keras处理编码的一个函数。其实就是独热编码了。要注意,这个必须从0开始,就是2类的话Ytrain就是0和1,如果3类就是0,1,2.不然报错。循环生成数据训练。可以做到边生成数据边训练,这样大大节约了内存。
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):
# 从文件中读取图像
img = load_img(lines[i], target_size=(224, 224)) # 读取图片途径,裁成224,224
img = img_to_array(img) # 转换成图像数组
img = img/255
X_train.append(img)
if lines[i].split('.')[-3][-3:]=='cat':
Y_train.append(0)
else:
Y_train.append(1)
# 读完一个周期后重新开始
i = (i+1) % n
# 处理图像
yield (np.array(X_train).reshape(-1,224,224,3), to_categorical(np.array(Y_train), num_classes=2))
最后这里的优化呢,是加上了学习率优化,和早停设置,保存训练好的权重。
3个函数的monitor='acc’代表的意思是评判的标准,都是以准确率来判断的。ReduceLROnPlateau和EarlyStopping的参数很相似,patience表示几个epoch。拿学习率来说,如果3个epoch的正确率都不下降,那么当前学习率乘factor就是新的学习率。这个factor就是学习率下降的比例了。最后这里个函数要在fitgenerator里面的callback以列表方式写入。最后保存我们训练好的权重到目录下。
# 80%用于训练,10%用于估计。
num_val = int(len(all_path)*0.2)
num_train = len(all_path) - num_val
# 建立AlexNet模型
model = AlexNet()
# 保存的方式,10epoch保存一次
checkpoint_period1 = ModelCheckpoint(
'./ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',
monitor='acc',
save_weights_only=False,
save_best_only=True,
period=10
)
# 学习率下降的方式,acc三次不下降就下降学习率继续训练
reduce_lr = ReduceLROnPlateau(
monitor='acc',
factor=0.5,
patience=3,
verbose=1
)
# 是否需要早停,当val_loss一直不下降的时候意味着模型基本训练完毕,可以停止
early_stopping = EarlyStopping(
monitor='val_loss',
min_delta=0,
patience=3,
verbose=1
)
# 交叉熵
model.compile(loss = 'categorical_crossentropy',
optimizer = Adam(0.001),
metrics = ['accuracy'])
# 一次的训练集大小
batch_size = 64
print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
# 开始训练
model.fit_generator(generate_arrays_from_file(all_path[:num_train], batch_size),
steps_per_epoch=max(1, num_train//batch_size),
validation_data=generate_arrays_from_file(all_path[num_train:], batch_size),
validation_steps=max(1, num_val//batch_size),
epochs=150,
initial_epoch=0,
callbacks=[checkpoint_period1, reduce_lr])
model.save_weights('./last_catdog.h5')
训练的结果还是很不错的,训练集在50epoch左右达到了100%的ac,验证集也有快80%,博主偷个懒没写如何测试,感兴趣的小伙伴可以参考之前的博文,自己动手写写如何测试吧!