(keras+VGG16+linux)迁移学习初识——猫狗大战数据集训练预测

目录

  • 一.数据集
  • 二.项目环境
  • 三.项目内容
    • 1.数据集预处理 data_pre.py
      • 代码
      • 学习笔记
    • 2.训练代码 training.py
      • 代码
      • 学习笔记
    • 3.测试代码 test.py
      • 代码
      • 学习笔记
    • 4.预测结果
  • 四.训练过程问题记录

本博客代码,划分好的数据集,自己训练的模型:
github:https://github.com/AlannahYYL/keras_vgg16_dogs-vs-cats
数据集:https://download.csdn.net/download/qq_31681523/16699667
模型:https://download.csdn.net/download/qq_31681523/16709305

一.数据集

数据集:Cats vs. Dogs
官网下载地址:https://www.kaggle.com/c/dogs-vs-cats
博主只下载了train.zip,包含猫狗各12500张,在数据集预处理文件中将生成数据集。

二.项目环境

keras==2.1.5 tensorflow==1.15 python==3.6 numpy==1.19.2

三.项目内容

部分配置参数详见工程代码文件config.py

1.数据集预处理 data_pre.py

解压train.zip,本文件将猫狗各12500张图片进行数据集划分,猫和狗前11000张分为训练集,后1500张分为验证集。划分后文件结构和代码如下:
文件结构:
(keras+VGG16+linux)迁移学习初识——猫狗大战数据集训练预测_第1张图片

代码

'''
数据预处理,将数据分为训练集、测试集
原train文件中共有猫狗图片各12500,将其中前10000用作训练集,2500用作测试集
'''
from config import *
import os
import shutil
def div_dataset(ori_path,val_path,train_path):
    '''
    划分数据集
    :param ori_path:
    :param test_path:
    :param train_path:
    :return:
    '''
    cat_num=0
    dog_num=0
    train_num=0
    test_num=0
    cat_val_path =os.path.join(val_path,CAT_CATE)
    dog_val_path =os.path.join(val_path,DOG_CATE)
    cat_train_path =os.path.join(train_path,CAT_CATE)
    dog_train_path =os.path.join(train_path,DOG_CATE)
    if not os.path.exists(cat_val_path):
        os.makedirs(cat_val_path)
    if not os.path.exists(dog_val_path):
        os.makedirs(dog_val_path)
    if not os.path.exists(cat_train_path):
        os.makedirs(cat_train_path)
    if not os.path.exists(dog_train_path):
        os.makedirs(dog_train_path)
    for root, dirs, files in os.walk(ori_path):
        for filename in files:
            # print(filename)
            ori_file = os.path.join(ori_path, filename)
            filename_l = filename.split('.')
            cate = filename_l[0]
            if cate==CAT_CATE:
                cat_num+=1
            else:
                dog_num+=1
            nums = filename_l[1]
            if int(nums)<11000:
                train_num+=1
                train_file = os.path.join(train_path, cate, filename)
                print(train_file)
                shutil.copy(ori_file,train_file)
            else:
                test_num+=1
                val_file = os.path.join(val_path, cate, filename)
                print(val_file)
                shutil.copy(ori_file,val_file)
    print('cat总共{}张'.format(cat_num))
    print('dog总共{}张'.format(dog_num))
    print('train总共{}张'.format(train_num))
    print('val总共{}张'.format(test_num))
    print('Done')


if __name__ == '__main__':
    if not os.path.exists(valdata_dir):
        os.makedirs(valdata_dir)
    if not os.path.exists(traindata_dir):
        os.makedirs(traindata_dir)
    div_dataset(ori_data_path,valdata_dir,traindata_dir)

学习笔记

  • 数据集文件名-label

数据集的标签数据label,若未在训练时特定指定,keras将由文件夹的名字分类而来。如本例中的cat,dog。

2.训练代码 training.py

代码

import os
os.environ["CUDA_VISIBLE_DEVICES"] = '0'
from keras import optimizers
from keras import applications
from keras.models import Sequential, Model
from keras.callbacks import ModelCheckpoint
from keras.layers import Dropout, Flatten, Dense
from keras.preprocessing.image import ImageDataGenerator
from keras.losses import binary_crossentropy
from config import *

opt=optimizers.SGD(lr=lr, momentum=momentum)

def training():
    '''
    training
    :return:
    '''
    base_model = applications.VGG16(weights=pweight_path, include_top=False,
                                input_shape=(img_width, img_height, 3))  # 预训练的VGG16网络
    #固定前几层权值
    for layer in base_model.layers[:freeze]: layer.trainable = False
    #自定义网络
    top_model = Sequential()  # 自定义顶层网络
    top_model.add(Flatten(input_shape=base_model.output_shape[1:]))  # 展平
    top_model.add(Dense(4096, activation='relu')) #全连接层
    top_model.add(Dropout(0.5))  # Dropout概率0.5
    top_model.add(Dense(4096, activation='relu'))
    top_model.add(Dropout(0.5))  # Dropout概率0.5
    top_model.add(Dense(OUTPUT_NUM, activation='sigmoid'))  # 二分类
    #组装网络
    model = Model(inputs=base_model.input, outputs=top_model(inputs=base_model.output))
    #损失函数与优化器
    model.compile(loss=binary_crossentropy, optimizer=opt,
                  metrics=['accuracy'])
    print(base_model.summary())
    # 数据预处理器
    train_datagen = ImageDataGenerator(rescale=1. / 255, #归一化
                                       width_shift_range=0.1,
                                       height_shift_range=0.1,
                                       horizontal_flip=True)
    val_datagen = ImageDataGenerator(rescale=1. / 255)
    #数据生成器
    train_generator = train_datagen.flow_from_directory(traindata_dir, target_size=(img_height, img_width),
                                                        batch_size=batch_size, class_mode='binary')
    val_generator = val_datagen.flow_from_directory(valdata_dir, target_size=(img_height, img_width),
                                                            batch_size=batch_size, class_mode='binary',
                                                            shuffle=False)
    # 保存模型
    filepath = os.path.join(saved_models, "model_{epoch:02d}-{val_acc:.2f}.hdf5")
    checkpointer = ModelCheckpoint(filepath=filepath, monitor='val_acc', verbose=1, save_best_only=True)
    #模型训练
    model.fit_generator(train_generator,
                        steps_per_epoch=train_samples // batch_size,
                        epochs=epochs,
                        validation_data=val_generator,
                        validation_steps=validation_samples // batch_size,
                        verbose=2,
                        callbacks=[checkpointer],
                        initial_epoch=start_epoch)

if __name__ == '__main__':
    training()

学习笔记

  • applications.VGG16中include_top参数

是否包含Dense层

  • 迁移训练策略

新的数据集较小,并且和pre-trained model所使用的训练数据集差异很大时,不适合微调,应当单独训练网络中较高的层数,冻结前几层权值。

  • sigmoid和softmax、binary_crossentropy\categorical_crossentropy\sparse_categorical_crossentropy

sigmoid与softmax的区别:sigmoid和softmax是神经网络输出层使用的激活函数,分别用于两类判别和多类判别。
keras中binary_crossentropy与categorical_crossentropy的区别:均为损失函数,其中binary_crossentropy+sigmoid:用于二分类问题;categorical_crossentropy+softmax:用于多分类问题。

binary_crossentropy:见上
categorical_crossentropy:见上
sparse_categorical_crossentropy:categorical_crossentropy要求target为onehot编码,而sparse_categorical_crossentropy要求target为非onehot编码,函数内部进行onehot编码实现。
详见https://blog.csdn.net/weixin_42295205/article/details/107487243

  • 数据预处理器ImageDataGenerator 类

(1)图片生成器,负责生成一个批次一个批次的图片,以生成器的形式给模型训练;
(2)对每一个批次的训练图片,适时地进行数据增强处理(data augmentation);
参数详见keras中文文档https://keras.io/zh/preprocessing/image/#imagedatagenerator

  • 数据生成器flow_from_directory

arget_size:所有的图像将被调整到的尺寸。
class_mode类模式:“分类(“categorical”)”、“二进制(“binary”)”、“稀疏(“sparse”)”、“输入(“input”)”、“无(None)”模式之一。
参数详见keras中文文档https://keras.io/zh/preprocessing/image/#flow_from_directory

  • ModelCheckpoint

verbose:详细信息模式,0或1
verbose = 0 为不在标准输出流输出日志信息
verbose = 1 为输出进度条记录
monitor:监测值,val_loss或者val_acc,也可以自己设定分数函数,例如自定义score函数作为检测值,则monitor参数值为val_score
参数详见keras中文文档https://keras.io/zh/callbacks/#modelcheckpoint

  • filepath填充

若验证损失报错KeyErrors,是因为版本缘故,val_acc参数更新为accuracy,后又更新为val_accuracy。因此需要酌情更换。

KeyError: 'val_accuracy'
  • fit_generator

model.fit()需要传递的参数是batch_size,而model.fit_generator()需要传递一个叫steps_per_epoch的参数,而不是指定batch_size。
steps_per_epoch参数:steps_per_epoch=len(x_train)/batch_size
详见keras中文文档https://keras.io/zh/models/model/#fit_generator

  • 指定gpu

keras可以自动识别能否使用gpu训练。gpu训练时,可以通过os.environ["CUDA_VISIBLE_DEVICES"]="1,2"指定gpu

3.测试代码 test.py

代码

import numpy as np
from keras.models import load_model
from keras.preprocessing import image
import cv2 as cv
import os
from config import *

def img_draw(res,img_file=''):
    img = cv.imread(img_file)
    if res[0][0] >= 0.5:
        text = DOG_CATE
    else:
        text = CAT_CATE
    cv.putText(img, text, (40, 60), cv.FONT_HERSHEY_PLAIN, 4.0, (0, 0, 255), 4)
    return img

def test():
    '''
    预测
    :return:
    '''
    #模型加载
    if not os.path.exists(res_data_path):
        os.makedirs(res_data_path)
    model_path = os.path.join(saved_models,'model_07-0.99.hdf5')
    model = load_model(model_path)
    #测试图片加载
    for root, dirs, files in os.walk(test_data_path):
        for filename in files:
            save_path = os.path.join(res_data_path,filename.split('.')[0]+'_res.jpg')
            img_file = os.path.join(test_data_path,filename)
            img_arr = image.load_img(img_file,target_size=(img_height,img_width))
            img_arr = image.img_to_array(img_arr)
            img_arr = img_arr[np.newaxis,:]
            res = model.predict(img_arr)
            # print(res)
            res = img_draw(res,img_file)
            cv.imwrite(save_path,res)
    pass
if __name__ == '__main__':
    test()

学习笔记

  • model.predict()

predict()输出的是概率值而不是类别,多分类的话配合使用numpy.argmax()找到最大概率的类别

  • arr = arr[np.newaxis,:]
    给数组增加一个维度

4.预测结果

(keras+VGG16+linux)迁移学习初识——猫狗大战数据集训练预测_第2张图片
(keras+VGG16+linux)迁移学习初识——猫狗大战数据集训练预测_第3张图片

四.训练过程问题记录

  • 1.ImportError: cannot import name ‘Type’
File "D:\360Downloads\Python35\lib\site-packages\scipy_lib_uarray_backend.py", line 1, in
from typing import (
ImportError: cannot import name 'Type'

尝试:降低scipy的版本到1.2.1 pip install scipy==1.2.1
结果:已解决

  • 2.ValueError: Error when checking target: expected dense_2 to have shape (1,) but got array with shape(2,)

数据维度不符,建议检查网络最后一层输出层的神经元的个数和类别数目是否匹配。
二分类时,使用sigmoid+binary loss,则只有一个神经元输出;使用softmax+交叉熵,则两个神经元输出(实际上也就是二分类和多分类的区别)。
二分类最后一层dense输出一个神经元就够了,改为1即可。

  • 3.无法使用gpu进行训练

分析:os.environ["CUDA_VISIBLE_DEVICES"] = "0"指定gpu后依然使用cpu进行训练;可能是keras版本与服务器上驱动不匹配等,试了好几个版本都不行,总之环境有问题。博主原来环境是py35+keras2.3.1+tensorflow2.0
解决办法:重新装配虚拟环境,环境如文章开头。

  • 4.损失和精度均不变

解决方法:调参
博主最开始学习率lr=0.001,此时从epoch2开始损失和精度均不再变动,且精度为0.5。学习率过大将不收敛,学习率改为0.0001后正常。

  • 5.cv2.error: OpenCV(3.4.2)

cv2.error: OpenCV(3.4.2) C:\projects\opencv-python\opencv\modules\highgui\src\window.cpp:356: error: (-215:Assertion failed) size.width>0 && size.height>0 in function ‘cv::imshow’
解决方法:将读取的图片路径中的\改为/

  • 6.根目录获取

根目录获取:
获取项目路径的根目录:Path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
获取项目路径:Path = os.path.abspath(os.path.dirname(__file__))
文件路径格式:
windows:文件路径使用’’
linux:文件路径使用’/’

青古の每篇一歌
《以父之名》
仁慈的父我已坠入
看不见罪的国度
请原谅我的自负

你可能感兴趣的:(python,ML/DL/数据结构与算法,深度学习,神经网络,迁移学习)