【Fine Tune】神经网络调优总结

参考:A Comprehensive guide to Fine-tuning Deep Learning Models in Keras  code: github

目录

1 为什么要调优:

2 什么时候调优:

3 调优技巧

4 调优方法(VGG16 )

 5 利用VGG16调优 分类Cats. vs Dogs

5.1 准备数据

5.2 构建网络 

 

5.3 测试评估

6 Explore



1 为什么要调优:

一个深度神经网络模型,会包含成千上万的参数。参数越多,需要的样本数量也会随之增长。所以当我们在一个小的训练集上建立如此大的神经网络时,模型的泛化能力非常差,就会非常容易发生过拟合。所以实践中,为了在较小的训练集上建立深度神经网络模型并且避免发生过拟合,就会利用调优方法,即:将一个已经在较大的数据集上(例如ImageNet)训练好的网络模型,在自己的小数据集上继续训练。但是要保证,两个数据集不能毫不相关,这样才能保证预训练好的模型所学习到的特征是可以被利用的。

2 什么时候调优:

神经网络的底层学习到的特征,通常是基础特征。通常情况下,当我们的小数据集与预训练所用的大数据集相似时,就可以利用这些基础特征来继续训练网络,即调优。

当我们的数据集具有一些特殊性(医疗,中国书法),我们无法找到在相关领域预训练好的模型,此时就不得不从头开始训练网络了。

当我们的数据集非常小,并且找到的预训练模型的最后几层都是全连接层时,就会非常容易发生过拟合。经验来说,当数据有几千个,那么通过数据增益(翻转、裁剪等),调优后结果还是不错的。

3 调优技巧

  1. 将最后一层(softmax层)去掉,更换为符合当前任务的softmax层。如ImageNet上预训练的网络执行的是有1000个类别的分类任务,而当前数据集有num_classes个类别,那么就更改为Dense(num_classes,activation='softmax')
  2. 使用较小的学习率,一般为原lr的十分之一大小(将预训练网络的权重作为初始化权重,比随机效果好得多,所以尽量避免开始就大幅改变权重 )
  3. 冻结前几层网络的权重(底层学习到的特征都是基础的通用的特征,可以继续使用)                                                                

一般框架里面都会有一些预训练好的模型,方便直接调用——keras :application 

4 调优方法(VGG16 )

Method 1:从头建立、修改、训练模型

from keras.layers import Input, Dense, Conv2D, MaxPooling2D,Flatten,Dropout,\
    Activation,Reshape,merge,ZeroPadding2D,AveragePooling2D

from keras.models import Sequential
from keras.optimizers import SGD
from sklearn.metrics import log_loss
img_rows=224
img_cols=224
channel=3
num_classes=2
batchsize=10
epochs=5
#Method 1
def vgg16_model(img_rows,img_cols,channel=1,num_classes=None):
    model=Sequential()
    # No.1 two conv layers
    model.add(ZeroPadding2D((1,1),input_shape=(img_rows,img_cols,channel)))
    model.add(Conv2D(64,(3,3),activation='relu'))
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(64,(3,3),activation='relu'))
    model.add(MaxPooling2D(pool_size=(2,2)))
    #No.2 two conv layers
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(128,(3,3),activation='relu'))
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(128,(3,3),activation='relu'))
    model.add(MaxPooling2D(pool_size=(2,2)))
    #No.3 three conv layers
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(256,(3,3),activation='relu'))
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(256,(3,3),activation='relu'))
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(256,(3,3),activation='relu'))
    model.add(MaxPooling2D(pool_size=(2,2)))
    #No.4 three conv layers
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(512,(3,3),activation='relu'))
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(512,(3,3),activation='relu'))
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(512,(3,3),activation='relu'))
    model.add(MaxPooling2D(pool_size=(2,2)))
    #No.5 three conv layers
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(512,(3,3),activation='relu'))
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(512,(3,3),activation='relu'))
    model.add(ZeroPadding2D((1,1)))
    model.add(Conv2D(512,(3,3),activation='relu'))
    model.add(MaxPooling2D(pool_size=(2,2)))
    
    #No.6 Fully connected
    model.add(Flatten())
    model.add(Dense(4096,activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(4096,activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(1000,activation='softmax'))
    
    #加载权值(500M)https://drive.google.com/file/d/0Bz7KyqmuGsilT0J5dmRCM0ROVHc/view?usp=sharing
    model.load_weights('vgg16_weights.hs')
    #去掉最后一层全连接层softmax,替换成符合自己任务的softmax
    model.pop()
    model.outputs=[model.layers[-1].output]
    model.layers[-1].outbound_nodes=[]
    model.add(Dense(num_classes,activation='softmax'))
    
    #冻结前10层的权重
    for layer in model.layers[:10]:
        layer.trainable=False
    #调整学习率,缩小十倍
    sgd=SGD(lr=0.001,decay=0.000006,momentum=0.9,nesterov=True)
    model.compile(optimizer=sgd,loss='categorical_crossentropy',\
                  metrics=['accuracy'])
    
    return model
vgg16=vgg16_model(img_rows,img_cols,channel,num_classes)
print(vgg16.summary())
history=vgg16.fit(X_train,Y_train,batch_size=batchsize,epochs=epochs,\
          validation_data=(X_valid,Y_valid),verbose=1) 
#预测
pred_test=vgg16.predict(X_test,batch_size=batchsize,verbose=1)
score=log_loss(Y_test,pred_test)

Method 2: 利用keras application 中现有的模型,读入、修改、训练

from keras.applications.vgg16 import VGG16
VGG16_model=VGG16()
print(type(VGG16_model))#默认Model 函数式类型
print(VGG16_model.summary())    
 #要改为Sequential序贯类型(线性结构,更为简单),逐层复制
model3=Sequential()
for layer in VGG16_model.layers:
    model3.add(layer)  
#将 Model类型的VGG16完全复制转化为 Sequential类型
print(model3.summary())
#删除最后一层
model3.layers.pop() 
#替换最后一层为适合的softmax层
model3.add(Dense(num_classes,activation='softmax')) 

 5 利用VGG16调优 分类Cats. vs Dogs

5.1 准备数据

1 遍历读取目录中的文件方法:for file name in os.listdir(path)

2 openCV读取图片,类型是list,需要转换成array: np.array(data)

3 cv2.resize(image,size),方便转换图像尺寸

4 try ...except...结构,适合 error 处理

5 数据读取后要检查样本分布的均匀性:pd.Series(label).value_counts()  #ndarray类型不能直接用value_counts()

#导入工具包

import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
from sklearn.model_selection import train_test_split
from keras.callbacks import EarlyStopping, ReduceLROnPlateau

#准备数据

path="./kaggle/train"  #./是当前目录 ../是父级目录
img_rows=224
img_cols=224
channel=3
IMAGE_SIZE=(img_rows,img_cols)
channel=3
label=[]
data=[]

i=0
for filename in os.listdir(path):
    
    if i%10==0: # 图像共25000太多,只取十分之一处理
        image_data=cv2.imread(os.path.join(path,filename),1)
    #0 读入灰色,1 彩色,-1 alpha通道即透明度
        image_data=cv2.resize(image_data,IMAGE_SIZE) #固定尺寸读入
        if filename.startswith("dog"):
            label.append(1)
        elif filename.startswith("cat"):
            label.append(0)
        try:
            data.append(image_data/255) #归一化
        except:
            label=label[:len(label)-1]
    i+=1
#处理的所有图像数据,都保证是array类型,非list
type(data) #data类型是list,需要转换为 np.ndarray
image_data.shape #(224,224,3)
data=np.array(data)
data.shape#(2500,224,224,3)
type(data) #numpy.ndarray

type(label) #list
label=np.array(label) 
label.shape #(2500,)
type(label) #numpy.ndarray
sns.countplot(label) # array 没有value_counts(),要先转化成series
pd.Series(label).value_counts() #1250-1,1250-0 样本均衡
#划分训练集和验证集
train_data,valid_data,train_label,valid_label=train_test_split(\
                            data,label,test_size=0.2,random_state=42)

5.2 构建网络 

1 VGG16模型默认是Model函数式,要转换为Sequensial序贯式,新建,遍历原模型逐层复制

2 删除层:model.layers.pop() ;冻结前n层权重:for layer in model.layers[:n]:layer.trainable=False

3 注意最后的分类层:num_classes (如二分类问题,如果不one-hot-encoder,num_classes=1,loss='binary_crossentropy';若one-hot编码,则num_classes为2,loss='categorical_crossentropy')

4 最后分类层的activation:与compile中的loss相关,如果 'categorical_crossentropy'----'softmax';'binary_crossentropy'--'sigmoid'

5 冻结了所有卷积层,每个epoch 20分钟左右;若冻结所有网络,只剩最后一层,训练时间虽快,但效果不好

from keras.layers import Input, Dense, Conv2D, MaxPooling2D,Flatten,Dropout,\
    Activation,Reshape,merge,ZeroPadding2D,AveragePooling2D

from keras.models import Sequential
from keras.optimizers import SGD
from sklearn.metrics import log_loss


#可以避免模型重建和权值加载,利用keras applications中已有的vgg16模型,直接复制,修改    
from keras.applications.vgg16 import VGG16
VGG16_model=VGG16()
print(type(VGG16_model))#默认Model 函数式类型
VGG16_model.summary()
 #要改为Sequential序贯类型(线性结构,更为简单),逐层复制
model3=Sequential()
for layer in VGG16_model.layers:
    model3.add(layer)  
model3.summary()
#删除最后一层
model3.layers.pop() 
model3.summary()
#冻结前面卷积层的权值
for layer in model3.layers[:-3]:
    layer.trainable=False

#替换最后一层为适合的softmax层(这里binary_crossentropy,activation选择sigmoid)
#若label编码,则可以选择category——crossentropy,activation=softmax
#本例中二分类问题,没有编码,所以class number设为1(编码时设为2)
num_classes=1
batchsize=10
epochs=10
model3.add(Dense(num_classes,activation='sigmoid'))  
print(model3.summary())

sgd=SGD(lr=0.001,decay=0.000006,momentum=0.9,nesterov=True)
model3.compile(optimizer=sgd,loss='binary_crossentropy',metrics=['accuracy'])

#查看学习曲线
fig,(ax1,ax2)=plt.subplots(2,1,figsize=(8,8))
ax1.plot(history.history['loss'],color='b',label="Train loss")
ax1.plot(history.history['val_loss'],color='r',label="Valid loss")
ax1.set_xticks(np.arange(0,epochs,1))
ax1.set_yticks(np.arange(0,1,0.1))

ax2.plot(history.history['acc'],color='b',label="Train acc")
ax2.plot(history.history['val_acc'],color='r',label="Valid acc")
ax2.set_xticks(np.arange(0,epochs,1))
lengend=plt.legend(loc='best',shadow=True)
plt.tight_layout()
plt.show()

 

5.3 测试评估

1 生成测试集的时候注意查看样本分布均匀性

2 将概率圆整为0-1,np.round( ) ,注意round()不能操作np.ndarray类型

3 评分方法:from sklearn import metrics (分类任务评分accuracy/presicion/fi/ROC、回归任务评分MSE/RMSE...),也可以自己创建评估函数


#保存模型,方便下次加载
#model3.save('./fine_tune/cat_dog_vgg.h5')

#生成测试集
del data #删除原加载的原数据,释放内存
del label
del train_data
del valid_data

test_data=[]
test_label=[]
i=1
for filename in os.listdir(path):
    if i%3==0 and i%10!=0 and i%2==0: #读取大约3333张数据
        image_data=cv2.imread(os.path.join(path,filename),1)
    #0-读入灰色,1-彩色,-1alpha通道即透明度
        image_data=cv2.resize(image_data,IMAGE_SIZE) #固定尺寸读入
        if filename.startswith("dog"):
            test_label.append(1)
        elif filename.startswith("cat"):
            test_label.append(0)
        try:
            test_data.append(image_data/255) #归一化
        except:
            test_label=test_label[:len(label)-1]
    i+=1
#类型转换:
image_data.shape #(224,224,3)
test_data=np.array(test_data)
test_data.shape#(3333,224,224,3)
test_label=np.array(test_label) #将 list 转化为 array
test_label.shape #(3333,)
#查看样本分布均匀性
sns.countplot(test_label) # array 没有value_counts(),要先转化成series
pd.Series(test_label).value_counts() 
#3333还是多,再次划分,减少测试量,最终取1000
split,test_data1,split_label,test_label1=train_test_split(test_data,test_label,test_size=0.3,random_state=42)
pd.Series(test_label1).value_counts() 

#预测
test_pred=model3.predict(test_data1,batch_size=batchsize)
test_pred1=np.round(test_pred)#将预测的概率值转化为0-1值,此处操作类型为numpy.ndarray,需要用numpy.round进行圆整
#评价测试结果
from sklearn import metrics #准确率,适合二分类评价
score=metrics.accuracy_score(test_pred1,test_label1)
print(score)#0.918
print(metrics.accuracy_score(test_pred1,test_label1,normalize=False))#返回预测正确的个数
#scores=model3.evaluate(test_pred1,test_label1)# evaluate 需要test_data作为输入,直接给出评价

6 Explore

冻结权值范围的影响

冻结前面所有网络层只训练最后一个全连接层,时间会更快一些(4/5),但是准确度(valid_accu=0.854)低于冻结卷积层训练三个全连接层(valid_accu=0.912),在测试集上的准确率为0.814,下降约10%(原0.918)

全连接层的影响还是很大的。

猜你喜欢

转载自blog.csdn.net/qq_43243022/article/details/88624300