IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> 卷积神经网络原理与实战 -> 正文阅读

[人工智能]卷积神经网络原理与实战

卷积神经网络原理与实战

Time:2021-07-16
Author:雾雨霜星
个人网站:雾雨霜星 | DA☆ZE (shuangxing.top)
转载请注明出处!!!

前言

开始学习CNN,试着跑了一个分类MNIST手写数据的卷积神经网络模型,跑了一个猫狗图片分类的demo,经过测试与总结后写下笔记。

内容包括:卷积神经网络的原理、各个卷积神经网络对于数据格式、相应的keras接口、实战全过程源码记录及其分析

重点记录了使用Keras完成一次卷积神经网络训练的代码思路,其中关键记录了GPU内存分配设置和使用图像数据生成器,以及两个训练中遇到的错误及其解决方法。

卷积神经网络的原理

卷积运算

连续函数间的卷积运算公式如下:
f ( x ) ? g ( x ) = ∫ f ( τ ) g ( x ? τ ) d τ f(x) \ast g(x)=\int f(\tau)g(x-\tau)d\tau f(x)?g(x)=f(τ)g(x?τ)dτ
矩阵间的卷积运算:首先将卷积核矩阵翻转180度(上下对换,不是倒置),然后卷积核对应在被卷积的矩阵上,每个元素对应相乘,将各个元素对应计算结果相加即得到某次运算的值。然后按照步幅沿着相应维度移动,再次重复上述计算。

特点:局部数据的总体计算

卷积神经网络的维度

卷积神经网络的维度是指卷积核的移动有几个维度。对于一维卷积神经网络,卷积核只沿着一个维度进行移动。二维卷积神经网络中卷积核将会沿着两个不同的维度移动(先沿着行移动再沿着列移动)。

运作的本质

通过使用卷积运算,来发掘一个完整的整体数据各个局部区域的细节特征。

通过使用不同的滤波器(不同的卷积核)来提取得到不同的特征。

每一层卷积神经网络计算后得到内容的本质是样本特征。比如对于图像,使用二维卷积神经网络就是在发掘图像每个局部区域的特征,得到一些列的特征图,再进行下次的卷积,在每个特征图上进一步划分特征细节,最终得到许多的细节特征图,通过组合细节特征图,来进行决策。

Keras搭建的卷积神经网络训练前的数据处理

  1. 数据向量化

    即把数据转化为网络输入要求的张量形式。

    一般对数据使用numpy.reshape()方法。不是为了改变大小,而是为了设置深度维度(通常对二维卷积神经网络的数据需要)。因为图片输入时,被看做为(M * N)的二维数据,而实际上应该是M * N * 1 或 M * N * 3的数据。

    (train_data,train_label),(test_data,test_label)=mnist.load_data()
    train_image = train_data.reshape((60000,28,28,1))
    test_image = test_data.reshape((10000,28,28,1))
    
  2. 数据归一化

    • 对于图像数据,转化为float类型并且全部值除以255

      train_image = train_image.astype(np.float32)/255
      test_image = test_image.astype(np.float32)/255
      

      经过测试,如果不除以255,那么训练结果会较早出现过拟合且最终效果明显下降。

    • 对于序列数据,

      还未具体进行相关实践,按照之前的经验,应该是对各个特征分别进行数据的标准化。

  3. 数据集打乱

    对于序列数据集或者图像数据集,容易出现相关性较强的两个数据在附件,为了让验证集和训练集的状况更加接近,应该进行数据集打乱。

    打乱数据集的同时要保证对于目标值/标签值按照相同的顺序打乱。

    可以采用如下方法:

    # 数据集(样本与标签)对应序号打乱
    permutation = np.random.permutation(train_label.shape[0])
    train_image_shuffled = train_image[permutation]
    train_labels_shuffled = train_label[permutation]
    

    np.random.permutation方法,输入一个数字,则生成一个该数字范围内的数字随机排列。

    然后样本和标签分别按照这个顺序进行排列即可。

  4. 标签处理

    比如标签向量化:

    train_label = keras.utils.to_categorical(train_label)
    test_label = keras.utils.to_categorical(test_label)
    

    keras.utils.to_categorical方法即转化为one-hot编码标签,通常对分类任务的标签进行处理。

    或者其他提取标签特征,以更好方式表现标签特征的方法。

一维卷积神经网络

时间序列数据与序列数据

一维卷积神经网络通常是对 时间序列数据 或者 序列数据 进行使用的。

时间序列数据:每一行代表一个特征,每一列代表一个时刻(看作一帧)。每个矩阵是一个样本。

时间序列数据的一个样本的shape为:(feature_dimension,total_time_step,batch_size)。

实例:交易市场某只股票价格数据

数据特征:该时刻股票价格、上一时刻股票最高价格、上一时刻股票最低价格
单位时间长度:1min(即每一帧之间的时间间隔)
每个样本总时间长度:1天(24*60min=1440min)

故一个样本有3行,1440列。一个样本集中的样本数就是有几天这样的数据。

数据集的样子:
每个样本:[[...],[...],[...]]

序列数据:每一行代表一个特征,每一列代表一个单位间隔,每个样本是矩阵。

时间序列数据是序列数据的一种,只是时间序列数据中每个单位间隔是时间。

序列输出常有的一种是字符串(文本类数据):每个字符的编码号是一个特征,每个单位间隔是这个字符串(文本)中的每个字符。

实例:文章内容数据集

数据特征:假设定义字符编码形式500种,即文章中只有这500个字可能出现。
         (即出现该字符则相应该位置为1,其余位置为0)
单位间隔:文章每个字符
每个样本的间隔总数:假设限制文章最多1000字,则一篇文章最多只有1000个字符。

故一个样本共有500行,1000列。每个样本代表一篇这样的文章。一个数据集中的样本数就是这样的文章的数量。

数据集的样子:
每个样本[[...],[...],[...],[...],[...],...]

一维卷积运算:

卷积核是一个矩阵(features * kernel_size),kernel_size即卷积核的长度(卷积核每次卷积所包括的时间长度),features即样本特征数(样本行数)。其中矩阵的每个元素是一个整数。

对于被卷积的数据是一个矩阵或者行向量。

一维卷积运算就是卷积核沿着一个维度进行卷积,一般默认这个维度就是描述“time_step/单位间隔”的维度。

每个卷积核进行卷积运算的最终的输出结果大小取决于采用“窄卷积”或者“宽卷积”的模式。

  • 注意不是一个行向量每次一行一行地进行卷积,而是一个矩阵,直接对一个时刻的所有特征,沿着时间步骤的维度进行卷积。

Keras一维卷积神经网络API

参考:Conv1D layer (keras.io)

import numpy as np
import tensrflow as tf
import tensorflow.keras as keras

model = keras.models.Sequential()
model.add(keras.layers.Conv1D(filters=100, kernel_size=10, padding="valid", input_shape=(1440,3)))
model.add(keras.layers.Activation(keras.activations.relu))
model.add(keras.layers.MaxPooling1D(3))
model.add(karas.layers.Flatten())
model.add(karas.layers.Dense(1))

此处需要注意的是Conv1D方法的input_shape,输入的是(time_period, features),time_period代表时间区间,即所输入数据每个样本的时间序列长度(列数)。而features是特征维度,即每个样本有几个特征(行数)。

滤波器(filter),在给定参数时filters参数即设置滤波器的个数,每个滤波器会使用不同的卷积核进行卷积运算,从而实现不同细节特征的提取。

每个滤波器使用一个卷积核进行卷积运算得到一个行向量。因此对于一个样本,经过每一层得到的数据是一个2D张量,其shape为(新的时间步骤长度,滤波器的个数),而每一行则代表了一个滤波器的卷积核运算后所得某种特征的时间序列。

kernel_size参数用于指定窗体的宽度(把样本行数看做窗体的高度),即每次卷积运算中包括数据的时间范围大小。

如果使用的是keras.Input作为输入尺寸的指定,那么我猜测应是这样的:

model.add(keras.Input(shape=(1440,3,)))

因为没有试过所以我也不太清楚对不对了。。。建议还是采用上述把input_shape写到卷积层里的官方文档的写法。

代码中的MaxPooling1D是一维池化层,池化层的作用都是一样的,这里是用一个矩阵沿着一个维度进行池化。

官方文档中说明了,相应输入下,最终的输出为:

Input shape
3+D tensor with shape: batch_shape + (steps, input_dim)

Output shape
3+D tensor with shape: batch_shape + (new_steps, filters) steps value might have changed due to padding or strides.

Returns
A tensor of rank 3 representing activation(conv1d(inputs, kernel) + bias).

二维卷积神经网络

图像数据

应用:二维卷积神经网络的使用对象通常是图像数据,即每个样本代表了一张图像。

图像在计算机中的表示:使用矩阵(或者三维矩阵)进行描述的,每个矩阵的点代表一个像素点,矩阵的行列代表了图像的长宽。

图像的分辨率:图像中存储的信息量,是每英寸图像内有多少个像素点。单位:PPI。即相应的长宽下如何切分图像为等大小的方块。

图像分辨率_百度百科 (baidu.com)

(关于dpi与ppi的区别参考:DPI 和 PPI 的区别是什么? - 知乎 (zhihu.com)

图像的像素(像素值):图像分割为相应的小块,每一小块处计算机赋予的值。用于描述该小块平均亮度信息,大小范围是0~255px。

灰度图图像的像素值:范围是0~255px,值越大说明亮度越高(白色),值越小则相应越黑。

RGB图像:在计算机中是一个含有三个矩阵数据元素的数组。每个矩阵记录一个颜色通道的图片各个像素点的像素值。三个通道分别为红绿蓝。

数据形式:

  1. 灰度图:每个样本是一个矩阵(2D张量),矩阵每个值代表图像像素点的像素值。一个样本集中样本数量图片的数量。
  2. RGB图像:每个样本是一个3D张量,格式为:(height,width,color_depth),即三个颜色通道各自一个矩阵进行描述。一个样本集中样本数量图片的数量。

二维卷积运算

卷积核一般是一个矩阵(方阵),即shape为(kernel_size* kernel_size),对图像样本数据进行卷积运算,就是线性代数的矩阵的卷积运算。

实际上,卷积核是多层的矩阵。如果对象是RGB图像,即输入样本的input_depth=3,那么每个滤波器的卷积核是(kernel_size* kernel_size*3)的。最终的计算结果,一般是采用将滤波器每层进行卷积的结果相加。即对于RGB图像,每个滤波器有三层,分别对三个通道进行卷积运算,再把各层卷积运算结果对应相加,得到该滤波器在这个RGB图像样本上的卷积计算结果。

对多通道图像的二维卷积,每次卷积运算公式如下:
f ( x , y , z ) ? g ( x , y , z ) = ∫ ( ∫ ∫ f ( u , v , z ) g ( x ? u , y ? v , z ) d u d v ) d z f(x,y,z)\ast g(x,y,z) = \int(\int\int f(u,v,z)g(x-u,y-v,z)dudv)dz f(x,y,z)?g(x,y,z)=(f(u,v,z)g(x?u,y?v,z)dudv)dz
可见只是沿着两个方向进行卷积,卷积核只有两个移动维度,第三维度(图片通道数/深度)是直接相加的。

RGB图像在CNN中如何进行convolution? - 知乎 (zhihu.com)

注意卷积核会沿着两个维度进行移动,一般默认是,首先沿着行维度进行移动,该行卷积完成后先沿着列维度进行移动到下一行,再继续沿着这一行移动进行卷积运算,如此循环。

Keras二维卷积神经网络API

参考:Conv2D layer (keras.io)

搭建二维卷积层,使用keras的layers接口中提供的Conv2D方法。

例如:

model.add(keras.layers.Conv2D(filters=100, kernel_size=10, padding="valid", input_shape=(28,54,3)))

其中input_shape用于指定每个样本的形状,此处即 28*54 大小的RGB图像。其中28是行宽,54是列宽。

filters同理用于指定滤波器的个数,样本与每个滤波器卷积运算后得到一个2D张量(矩阵),而所有滤波器的结果总体就是一个3D张量,即相应每一层的输出数据形状为:(new_row, new_col, filters)。

官方文档说明的如下:

Input shape
4+D tensor with shape: batch_shape + (channels, rows, cols) if data_format='channels_first' or 4+D tensor with shape: batch_shape + (rows, cols, channels) if data_format='channels_last'.

Output shape
4+D tensor with shape: batch_shape + (filters, new_rows, new_cols) if data_format='channels_first' or 4+D tensor with shape: batch_shape + (new_rows, new_cols, filters) if data_format='channels_last'. rows and cols values might have changed due to padding.

Returns
A tensor of rank 4+ representing activation(conv2d(inputs, kernel) + bias).

三维卷积神经网络

视频数据

应用:三维卷积神经网络的应用对象通常是视频数据。

视频数据:通常每个视频数据样本是一个4D张量,可认为许多图片(帧)组合为一个视频,每个图片时3D张量(灰度图则第三维度为1)。

Keras三维卷积神经网络API

参考:Conv3D layer (keras.io)

使用方法keras.layers.Conv3D搭建三维卷积层。

Keras 卷积神经网络实战记录

环境

Windows10

Anaconda虚拟环境:Python3.7.7、tensorflow2.0.0、tensorflow-gpu2.0.0、Pillow8.3.1

CPU:intel CORE i7 10thGen

显卡:NVIDIA RTX2060

CUDA10.0.0、cudnn7.4.1

关于GPU开发环境的配置(CUDA与cudnn的配置)

参考我的文章:

制作数据集

  1. 下载数据集:到Kaggle官方网站进行注册,然后到:Dogs vs. Cats | Kaggle 验证账户后下载所有的图片集合(不是只下载csv)

  2. 下载得到压缩包,解压即可得到train和test两个文件夹,里面都是关于猫的或者狗的JPG图像

  3. 图片格式:下载得到数据的图片,训练集图片命名格式为:cat/dog.num.jpg ; 测试集图片命名格式为 num.jpg

    其中训练集(train文件夹中)共有猫的图片12500张,狗的图片12500张。测试集(test文件夹中)共有图片12500张。

  4. 使用python的shutil模块进行数据集制作:

    # 确定训练集和测试集图片所在文件位置
    origin_image_train_dir = 'E:/MLTrainingData/dogs-vs-cats/train'
    origin_image_test_dir = 'E:/MLTrainingData/dogs-vs-cats/test'
    # 设置目标训练集、验证集、测试集保存图片的文件位置
    dataset_image_train_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_train'
    dataset_image_test_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_test'
    dataset_image_val_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_val'
    
    # 使用Python的os模块设置各个路径变量
    train_dogs_dir = os.path.join(dataset_image_train_dir,'dogs')
    train_cats_dir = os.path.join(dataset_image_train_dir,'cats')
    test_dogs_dir = os.path.join(dataset_image_test_dir,'dogs')
    test_cats_dir = os.path.join(dataset_image_test_dir,'cats')
    val_dogs_dir = os.path.join(dataset_image_val_dir,'dogs')
    val_cats_dir = os.path.join(dataset_image_val_dir,'cats')
    
    # 制作文件夹(如果该文件夹还未存在则制作,否则注释掉该部分)
    # os.mkdir(train_dogs_dir)
    # os.mkdir(train_cats_dir)
    # os.mkdir(test_dogs_dir)
    # os.mkdir(test_cats_dir)
    # os.mkdir(val_dogs_dir)
    # os.mkdir(val_cats_dir)
    
    # 制作训练集
    fnames = {'dog.{}.jpg'.format(i) for i in range(5000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(train_dogs_dir,fname)
        shutil.copyfile(src,dst)
    fnames = {'cat.{}.jpg'.format(i) for i in range(5000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(train_cats_dir,fname)
        shutil.copyfile(src,dst)
    # 制作测试集
    fnames = {'dog.{}.jpg'.format(i) for i in range(5000,8000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(test_dogs_dir,fname)
        shutil.copyfile(src,dst)  
    fnames = {'cat.{}.jpg'.format(i) for i in range(5000,8000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(test_cats_dir,fname)
        shutil.copyfile(src,dst)
    # 制作验证集
    fnames = {'dog.{}.jpg'.format(i) for i in range(8000,10000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(val_dogs_dir,fname)
        shutil.copyfile(src,dst)  
    fnames = {'cat.{}.jpg'.format(i) for i in range(8000,10000)}
    for fname in fnames:
        src = os.path.join(origin_image_train_dir,fname)
        dst = os.path.join(val_cats_dir,fname)
        shutil.copyfile(src,dst)
    

    基本思路就是,确定需要复制的图像的名字列表,按照列表进行迭代,每次迭代就是使用shutil的copyfile方法,确定一个文件所在的具体路径和文件的目标路径。

使用图像数据生成器

使用keras的数据预处理API中的preprocessing的image类组件:ImageDataGenerator

即:tensorflow.keras.preprocessing.image.ImageDataGenerator()

参考官方文档:Image data preprocessing (keras.io)

使用该数据生成器主要是可以从数据指定位置按照需要的格式将训练数据和标签输入,而不用事先将全部训练数据输入到变量中,从而不用消耗大量的内存。同时,该数据生成器可以使用“数据增强”,从而增加样本数。

使用方法:

  1. 构建ImageDataGenerator实例,设置数据增强和数据输入是的处理方法
  2. 使用ImageDataGenerator类的输入流方法,获取数据输入的迭代器实例
  3. 调用迭代器获取数据

参考代码如下:

# 构建数据生成器的实例
ImageDataGenerator = tensorflow.keras.preprocessing.image.ImageDataGenerator(rescale=1/255)
# 使用目录输入流的方法,获取相应的迭代器
# 训练集的数据生成器迭代器
TrainGen = ImageDataGenerator.flow_from_directory(dataset_image_train_dir,
                                                 target_size=(150,150),
                                                 batch_size=64,
                                                 class_mode='binary')
# 测试集的数据生成器迭代器
TestGen = ImageDataGenerator.flow_from_directory(dataset_image_test_dir,
                                                target_size=(150,150),
                                                batch_size=64,
                                                class_mode='binary')
# 验证集的数据生成器迭代器
ValGen = ImageDataGenerator.flow_from_directory(dataset_image_val_dir,
                                               target_size=(150,150),
                                               batch_size=64,
                                               class_mode='binary')

其中设置ImageDataGenerator的rescale参数是用于图像数据归一化,该指定值会被乘到每个输入图像数据矩阵中。

flow_from_directory方法,返回字典迭代器的实例对象,该对象可用于从指定目录下的数据集中按batch_size输入数据。官方解释为:

Returns
A DirectoryIterator yielding tuples of (x, y) where x is a numpy array containing a batch of images with shape (batch_size, *target_size, channels) and y is a numpy array of corresponding labels.

可见该迭代器每次返回一组数据和标签,标签的形式按照class_mode参数决定。

该输入流方法一般提供四个参数:directory、target size、batch size、class mode。分别用于:指定数据所在目录路径、输入图像调整尺寸、每次迭代输入批量、标签类型。其中该方法提供shuffle参数,默认为true,从目录输入时打乱数据集。

  • 注意,目录路径应该按照官方所确定的如下格式:

    main_directory/
    ...class_a/
    ......a_image_1.jpg
    ......a_image_2.jpg
    ...class_b/
    ......b_image_1.jpg
    ......b_image_2.jpg
    

    也就是说flow_from_directory方法中给定的目录路径下应该是相应所分类别的文件夹,文件夹内才是图片数据。
    例如我给的目录路径为:dataset_image_train_dir = ‘E:/MLTrainingData/dogs-vs-cats/dataset_train’,该路径下只有dogs和cats两个文件夹,这两个文件夹中分别是狗的图片和猫的图片。

    相应的标签会按照该路径下文件夹状况进行生成:

    # 检查各个数据集的标签分类是否一致
    print(TrainGen.class_indices)
    print(TrainGen.class_indices)
    print(TrainGen.class_indices)
    # 输出结果
    # {'cats': 0, 'dogs': 1}
    # {'cats': 0, 'dogs': 1}
    # {'cats': 0, 'dogs': 1}
    

    通过对迭代器实例对象调用class_indices属性可以检查其分类状况。

搭建神经网络模型

使用KerasAPI,按照堆叠式的结构进行搭建:

model = models.Sequential()
model.add(keras.Input(shape=(150,150,3)))
model.add(layers.Conv2D(32,(3,3),activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(64,(3,3),activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128,(3,3),activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128,(3,3),activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512,activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))

对于卷积神经网络,基本结构就是: 卷积层->池化层->…->展平层->Dropout层->全连接层->全连接输出层

卷积层的滤波器个数逐渐增多,而因为这里是分类问题,所以使用一个神经元进行输出,因为是二分类,所以对输出神经元使用sigmoid激活,把输出值映射到0~1的区间。

模型编译:

model.compile(optimizer=keras.optimizers.Adam(lr=1e-4),
             loss='binary_crossentropy',
             metrics=['acc'])

采用Adam优化器:结合Momentum和RMSprop的优点,采用动量的方法:每次更新都考虑前一次梯度计算结果;采用RMSprop的思路:计算衰减率用于改变每一次的学习率,Adam正是使用了动量计算和RMSprop中学习率衰减的计算方法。

对于二分类问题,损失函数可以使用二分类交叉熵,binary_crossentropy。

选择准确率用于评估模型。

GPU显存分配设置初始化

这一个步骤对于使用GPU进行训练应是必要的,缺少这个步骤,会因为对GPU内存不能保证全部数据输入而报错。

只需添加如下代码:

from tensorflow.compat.v1 import ConfigProto
from tensorflow.compat.v1 import InteractiveSession

config = ConfigProto()
config.gpu_options.allow_growth = True
session = InteractiveSession(config=config)

tensorflow.compat.v1具体内容参考官方文档:Module: tf.compat.v1 | TensorFlow Core v2.5.0 (google.cn)

tensorflow.compat模块中提供了TF1与TF2各种API的完整副本,主要是给用户对接TF1与TF2的项目,以实现TF两个版本(版本1和版本2)前后兼容。官方文档还说到,tensorflow.compat里面还包含一组帮助函数,用于编写在以下两种情况下都能工作的代码。

tensorflow.compat.v1即包括了TF1的各种API。

ConfigProto类:在创建Tensorflow的会话session时,用来对session进行参数配置。给出参数配置的实例。

session:Tensorflow架构中,每次训练都会需要会话,会话是程序与GPU打交道的方法,即通过会话告知GPU相应的任务。(很久以前学Tensorflow时似乎看到过相关内容,不记得自己这样的理解对不对了)

config.gpu_options.allow_growth = True 即设置了GPU可以进行动态显存申请。可能原本是给定一定的空间给GPU,而后就去用这部分显存进行训练。这里的设置应该是如果GPU在训练时需要更大的显存,可以动态申请使用。这样在通常状况下,可以有效避免训练时显存不足的问题。

InteractiveSession类:可以用于加载自身默认构建的会话。不需要对session使用with语句再形成结构。即InteractiveSession可看作为带有with结构的session。

官方源码注解中写道,对于session需要使用with语句形式形成默认会话:

Note that a regular session installs itself as the default session when it is created in a `with` statement.  
来源:https://github.com/tensorflow/tensorflow/blob/v2.5.0/tensorflow/python/client/session.py#L1689-L1794
其中的1714行

而InteractiveSession自身即带有了with结构的session,因此以下语句等效:

a = tf.constant(4)
b = tf.constant(7)
c = a + b
with tf.Session() as sess:
    print(c.eval())
##############################
a = tf.constant(4)
b = tf.constant(7)
c = a + b
sess = tf.InteractiveSession()
print(c.eval())

因此在配置GPU的时候,使用session = InteractiveSession(config=config)对会话进行设置。将参数配置实例输入到相应session的设置中。因为这里使用的是InteractiveSession,因此不需要使用with语句形式来写。

参考:

模型训练

与以往不同,我们不是直接给出存储所有数据集合标签的变量用于训练,而是给出可以产生数据集合标签的迭代器,因此,在使用数据生成器的状况下,一般训练函数采用fit_generator,而测试集用于评估模型时则使用evaluate_generator。

fit_generator

输入训练集迭代器,给出总轮数,确定每轮执行迭代器获取数据多少次,同时可以输入验证集迭代器,那么还需要会给出每次验证从验证集迭代器获取数据的次数。例如:

steps_per_epoch=5000//64
validation_steps=2000//64
his = model.fit_generator(TrainGen,epochs=100,steps_per_epoch=steps_per_epoch,
                          validation_data=ValGen,validation_steps=validation_steps)

其中steps_per_epoch即每轮训练需要从迭代器获取数据的次数,而validation_steps是每一轮验证时从验证集迭代去获取数据的次数。

使用steps_per_epoch与validation_steps的意义

因为数据生成器提供的迭代器,可以无限迭代生成给出数据,如果不设置每次迭代器进行数据获取的次数,那么就会无限一直迭代获取。使用普通的fit方法中不使用这个参数是因为那些数据集是有限的,默认提取数据到全部用完就停止。而数据生成器可以按照数据增强的规则无限生成数据。

因此每一轮训练需要指定、每轮验证也需要指定。

一般是当获取数据总数为整个样本的总数,即整个样本的长度除以每次提取数据的batch size,经过这么多轮后实际已经向模型输入了整个数据集数据数量的数据。

evaluate_generator

输入测试集数据生成器的迭代器实例和指定获取数据次数即可。

获取训练过程数据绘制图表

对训练方法返回值,调用history对象,获取数据字典,使用matplotlib的pyplot绘图。

获取数据字典:

# 获取训练方法返回对象
his = model.fit_generator(TrainGen,epochs=100,steps_per_epoch=steps_per_epoch,
                          validation_data=ValGen,validation_steps=validation_steps)
# 获取训练过程数据记录字典
h = his.history
# 检查字典有什么数据记录
print(h.keys())
# 获取具体数据,得到相应数据每轮的值,以列表形式记录
Loss = h['loss']
Acc = h['acc']
Val_Loss = h['val_loss']
Val_Acc = h['val_acc']
# 设置绘图横坐标的数据(轮数),得到一个整数列表
Epochs = range(1,len(Loss)+1)

绘图:

plt.figure(figsize=(12,4))
plt.subplot(1,2,1)
plt.plot(Epochs,Loss,'-',label='train loss')
plt.plot(Epochs,Acc,'--',label='train acc')
plt.xlabel('Epoch')
plt.ylabel('Value')
plt.title('Train Loss and Accuracy')
plt.legend()
plt.subplot(1,2,2)
plt.plot(Epochs,Val_Loss,'-',label='validation loss')
plt.plot(Epochs,Val_Acc,'--',label='validation acc')
plt.xlabel('Epoch')
plt.ylabel('Value')
plt.title('Validation Loss and Accuracy')
plt.legend()
plt.show()

plt.figure(figsize=(12,4))创建图表对象,设置size,在Jupyter中该size可以较为美观的在一行显示两个图。

plt.subplot(1,2,1)设置子图相应编号和总体。

plt.legend()添加曲线符号指示对于曲线含义。

模型测试

使用Keras的model API中的方法:model.evaluate_generator

result = model.evaluate_generator(TestGen,steps=50)
print(result)

模型的保存与调用

参考:Model saving & serialization APIs (keras.io)

错误记录

  • ImportError: Could not import PIL.Image. The use of load_img requires PIL.

    这个错误的原因在于环境中没有安装Pillow库或者Pillow库的版本在7.0以下。

    虽然不需要引用Pillow库,但是使用fit_generator方法训练时,是要提供Pillow库中部分功能才能实现的。

    我在Anaconda中,找到相应虚拟环境,pip安装Pillow,然后重启Anaconda或者重新进入该虚拟环境后生效,错误解决。

  • UnknowError:Failed to get convolution algorithm. This is probably because cuDNN failed to initialize, so try looking to see if a warning log message was printed above. [Op: Conv2D]

    大概意思是在执行Conv2D需要的运算时,无法加载卷积运算算法,可能是cuDNN初始化失败导致。

    但是实际上,并不是我的cuDNN有问题,或者环境驱动什么的没有配置好。而是GPU内存(显存)分配的问题,可能是这里的卷积算法需要载入足够数据才能执行,即给定了数据长度但是还没载入完毕,所以这个算法加载使用失败,没能加载这么多数据是因为GPU没有设置动态分配内存,导致运算中发生内存不足的问题。

    以上理解,不知对不对,仅供参考。

    解决的方法是在整个工程前几行加入显存动态分配的设置:

    from tensorflow.compat.v1 import ConfigProto
    from tensorflow.compat.v1 import InteractiveSession
    config = ConfigProto()
    config.gpu_options.allow_growth = True
    session = InteractiveSession(config=config)
    

    这样就不会再报错了,可以正常训练模型了。

    小插曲

    一开始我百度找到了这个错误的相关说法,然后也添加了以上代码,但是,我没有重新启动这个项目,而是直接加入后run了这段,然后再去run训练部分的代码,报错没有变化,所以我以为自己不是这个问题,就跑去研究驱动了…终于到了第二天早上,闲着试试,加入这段代码后,关掉了整个Anaconda(我用的是Window系统上Anaconda的图形化界面),然后重新打开Anaconda来跑这个工程,然后就发现没有报错了…

    修改配置前必须停下这个GPU在这部分程序已经使用的部分,gpu的配置要在其使用之前进行,才能有效。

    怎么说,Anaconda里面的Jupyter,程序报错不意味着这个项目已经停止运作,需要自己关掉,但是关掉这个页面,会发现Jupyter目录中对应的这个程序那本书的小图片还是绿色的,这是否意味着整个工程已经停止?不太懂了。

    原来是要到Jupyter的Running区(图标下方的导航栏第二个选项卡),找到相应工程点击关闭,以此在Jupyter里面关闭一个工程。

使用数据增强

对于数据生成器ImageDataGenerator,创建实例时指定相应规则可以进行数据增强。

常见的数据增强具体方法:

  • rotation_range:设置图像随机旋转的角度范围
  • width_shift_range:水平方向上的平移范围(输入的值是所占水平尺寸的比例)
  • height_shift_range:垂直方向上的平移范围(输入的值是所占垂直尺寸的比例)
  • shear_range:随机错切变换的角度
  • zoom_range:随机缩放方范围
  • fill_mode:用于指定填充原图像生成新的素材的方法

更多具体的规则参考Keras文档中ImageDataGenerator类部分即可。

  • 一般验证集和测试集不采用数据增强。

实战部分的完整代码

一下为猫狗图片分类的完整代码。其实Keras的书中应该也有很多这样的例子,不过还是有一些细节不同。所有细节均已在上文分析了。

代码如下:

import os
import shutil
import numpy as np
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.models as models
import tensorflow.keras.layers as layers
import tensorflow.keras.preprocessing.image as imagepreprocess
from matplotlib import pyplot as plt

from tensorflow.compat.v1 import ConfigProto
from tensorflow.compat.v1 import InteractiveSession

config = ConfigProto()
config.gpu_options.allow_growth = True
session = InteractiveSession(config=config)

origin_image_train_dir = 'E:/MLTrainingData/dogs-vs-cats/train'
origin_image_test_dir = 'E:/MLTrainingData/dogs-vs-cats/test'
dataset_image_train_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_train'
dataset_image_test_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_test'
dataset_image_val_dir = 'E:/MLTrainingData/dogs-vs-cats/dataset_val'

train_dogs_dir = os.path.join(dataset_image_train_dir,'dogs')
train_cats_dir = os.path.join(dataset_image_train_dir,'cats')
test_dogs_dir = os.path.join(dataset_image_test_dir,'dogs')
test_cats_dir = os.path.join(dataset_image_test_dir,'cats')
val_dogs_dir = os.path.join(dataset_image_val_dir,'dogs')
val_cats_dir = os.path.join(dataset_image_val_dir,'cats')

# 若未创建相应的文件夹,就去掉注释
# os.mkdir(train_dogs_dir)
# os.mkdir(train_cats_dir)
# os.mkdir(test_dogs_dir)
# os.mkdir(test_cats_dir)
# os.mkdir(val_dogs_dir)
# os.mkdir(val_cats_dir)

# 制作训练集
fnames = {'dog.{}.jpg'.format(i) for i in range(5000)}
for fname in fnames:
    src = os.path.join(origin_image_train_dir,fname)
    dst = os.path.join(train_dogs_dir,fname)
    shutil.copyfile(src,dst)
fnames = {'cat.{}.jpg'.format(i) for i in range(5000)}
for fname in fnames:
    src = os.path.join(origin_image_train_dir,fname)
    dst = os.path.join(train_cats_dir,fname)
    shutil.copyfile(src,dst)
# 制作测试集
fnames = {'dog.{}.jpg'.format(i) for i in range(5000,8000)}
for fname in fnames:
    src = os.path.join(origin_image_train_dir,fname)
    dst = os.path.join(test_dogs_dir,fname)
    shutil.copyfile(src,dst)  
fnames = {'cat.{}.jpg'.format(i) for i in range(5000,8000)}
for fname in fnames:
    src = os.path.join(origin_image_train_dir,fname)
    dst = os.path.join(test_cats_dir,fname)
    shutil.copyfile(src,dst)
# 制作验证集
fnames = {'dog.{}.jpg'.format(i) for i in range(8000,10000)}
for fname in fnames:
    src = os.path.join(origin_image_train_dir,fname)
    dst = os.path.join(val_dogs_dir,fname)
    shutil.copyfile(src,dst)  
fnames = {'cat.{}.jpg'.format(i) for i in range(8000,10000)}
for fname in fnames:
    src = os.path.join(origin_image_train_dir,fname)
    dst = os.path.join(val_cats_dir,fname)
    shutil.copyfile(src,dst)
    
ImageDataGenerator = imagepreprocess.ImageDataGenerator(rescale=1/255)

TrainGen = ImageDataGenerator.flow_from_directory(dataset_image_train_dir,
                                                 target_size=(150,150),
                                                 batch_size=64,
                                                 class_mode='binary')
TestGen = ImageDataGenerator.flow_from_directory(dataset_image_test_dir,
                                                target_size=(150,150),
                                                 batch_size=64,
                                                 class_mode='binary')
ValGen = ImageDataGenerator.flow_from_directory(dataset_image_val_dir,
                                               target_size=(150,150),
                                                 batch_size=64,
                                                 class_mode='binary')

# 检查各个数据集的标签分类是否一致
print(TrainGen.class_indices)
print(TrainGen.class_indices)
print(TrainGen.class_indices)

model = models.Sequential()
model.add(keras.Input(shape=(150,150,3)))
model.add(layers.Conv2D(32,(3,3),activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(64,(3,3),activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128,(3,3),activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128,(3,3),activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512,activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))

model.compile(optimizer=keras.optimizers.Adam(lr=1e-4),
             loss='binary_crossentropy',
             metrics=['acc'])

steps_per_epoch=5000//64
validation_steps=2000//64
his = model.fit_generator(TrainGen,epochs=100,validation_data=ValGen, steps_per_epoch=steps_per_epoch,validation_steps=validation_steps)

h = his.history
print(h.keys())

plt.figure(figsize=(12,4))
plt.subplot(1,2,1)
plt.plot(Epochs,Loss,'-',label='train loss')
plt.plot(Epochs,Acc,'--',label='train acc')
plt.xlabel('Epoch')
plt.ylabel('Value')
plt.title('Train Loss and Accuracy')
plt.legend()
plt.subplot(1,2,2)
plt.plot(Epochs,Val_Loss,'-',label='validation loss')
plt.plot(Epochs,Val_Acc,'--',label='validation acc')
plt.xlabel('Epoch')
plt.ylabel('Value')
plt.title('Validation Loss and Accuracy')
plt.legend()
plt.show()

# 测试
result = model.evaluate_generator(TestGen,steps=50)
print(result)

参考学习书籍:Python深度学习 (豆瓣) (douban.com)

转载请注明出处!!!

Author:雾雨霜星
欢迎来我的网站学习:
https://www.shuangxing.top

Thanks!

PS: 毕竟,霜星酱水平有限,如果发现任何错误还请及时邮箱告知我,我会去改哦!

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2021-08-05 17:21:26  更:2021-08-05 17:22:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/7 13:44:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码