Python 智能项目:1~5(2)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Python 智能项目:1~5(2)

Python 智能项目:1~5(1)https://developer.aliyun.com/article/1426929

Softmax 将是表示输出层中不同类别的概率的最佳激活函数,而每个数据点的分类交叉熵损失之和将是优化的最佳损失。 对于具有输出标签向量yp的预测概率的单个数据点,交叉熵损失由以下公式给出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vaGBbUxv-1681653992504)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8058232c-897d-4cec-9a2d-768e01646985.png)]

这里是y = [y1, ..., y5]^Tp = [p1, ..., p5]^T

同样,M训练数据点上的平均损失可以表示为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2EnyCsTA-1681653992504)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/79a945ed-f72e-486f-9b76-d6a9a3ec21f2.png)]

在训练过程中,小批量的梯度基于(2)给出的平均对数损失,其中M是所选的批量大小。 对于我们将结合验证准确率进行监视的验证日志损失,M是验证集数据点的数量。 由于我们将在每个折叠中进行 K 折交叉验证,因此在每个折叠中我们将具有不同的验证数据集。

现在我们已经定义了训练方法,损失函数和验证指标,让我们继续进行数据探索和建模。

请注意,输出类别中的分类具有序数性质,因为严重性随类别而增加。 因此,回归可能会派上用场。 我们还将尝试通过回归来代替分类,以了解运气如何。 回归的挑战之一是将原始分数转换为类。 我们将使用一个简单的方案,将分数散列到最接近的整数严重性类别。

考虑类别失衡

在分类方面,类别失衡是一个主要问题。 下图描述了五个严重性类别的类别密度:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pRsVpACt-1681653992504)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/5969eeb1-ca28-4fbb-b96c-12d2f4088362.png)]

图 2.4:五个严重性类别的类别密度

从上图可以看出,将近 73% 的训练数据属于 0 类,这代表没有糖尿病性视网膜病。 因此,如果我们碰巧将所有数据点标记为 0 类,那么我们将具有 73% 的准确率。 在患者健康状况下这是不希望的。 我们宁愿有一个测试说患者没有时有某种健康状况(假阳性),而有一项测试却漏诊时没有发现某种健康状况(假阴性)。 如果模型学会将所有点分类为属于 0 类,则 73% 的准确率可能毫无意义。

在严重性类别 0 上,检测较高的严重性类别比做好工作更为重要。 使用对数损失或交叉熵成本函数的分类模型的问题在于它偏爱多数类。 这是因为交叉熵误差是从最大似然原理中得出的,该似然原理倾向于将较高的概率分配给多数类别。 我们可以做两件事:

  • 丢弃具有更多样本的类别中的数据,或者丢弃低频类别的上样本,以保持样本在各个类别之间的分布均匀。
  • 在损失函数中,权重与类别的密度成反比。 这将确保当模型无法对低频类别进行分类时,对成本函数施加更高的惩罚。

我们将使用方案二,因为它不需要生成更多数据或丢弃现有数据。 如果我们将类别权重与类别频率的倒数成正比,则会得到以下类别权重:

严重类别 类权重
Class 0 0.0120353863
Class 1 0.1271350558
Class 2 0.0586961973
Class 3 0.3640234214
Class 4 0.4381974727

我们将在训练分类网络时使用这些权重。

预处理图像

不同类别的图像将存储在不同的文件夹中,因此可以轻松标记其类别。 我们将使用Opencv函数读取图像,并将其调整为不同的大小,例如224 x 224 x3。我们将基于 ImageNet 数据集从每个图像的通道方向上减去平均像素强度。 这意味着减法将使糖尿病性视网膜病变图像达到与在其上训练了预训练模型的处理过的 ImageNet 图像相同的强度范围。 提出每个图像后,它们将存储在numpy数组中。 图像预处理函数可以定义如下:

def get_im_cv2(path,dim=224):
     img = cv2.imread(path)
     resized = cv2.resize(img, (dim,dim), cv2.INTER_LINEAR)
     return resized
 def pre_process(img):
     img[:,:,0] =     img[:,:,0] - 103.939
     img[:,:,1] =     img[:,:,0] - 116.779
     img[:,:,2] =     img[:,:,0] - 123.68
     return img

通过opencv函数imread读取图像,然后使用线性插值方法将其调整为(224,224,3)或任何给定大小。 ImageNet 图像的红色,绿色和蓝色通道中的平均像素强度分别为103.939116.779123.68; 从图像中减去这些平均值之后,对预训练模型进行训练。 均值减法的这种活动用于使数据居中。 将数据定为零附近有助于解决消失和爆炸的梯度问题,进而帮助模型收敛更快。 同样,对每个通道进行归一化有助于保持梯度均匀地流入每个通道。 由于我们将为此项目使用预训练的模型,因此在将图像馈入预训练的网络之前,有必要根据通道平均像素值校正图像。 但是,并非必须要根据预训练网络所基于的 ImageNet 的平均值来校正图像。 您可以通过该项目的训练语料库的平均像素强度很好地进行归一化。

同样,您可以选择对整个图像进行均值归一化,而不是对通道进行均值归一化。 这需要从自身中减去每个图像的平均值。 想象一下 CNN 识别的对象在不同的光照条件下(例如白天和晚上)曝光的场景。 无论光照条件如何,我们都希望对物体进行正确的分类,但是不同的像素强度将以不同方式激活神经网络的神经元,从而导致错误分类物体的可能性。 但是,如果我们从自身中减去每个图像的平均值,则对象将不再受到不同光照条件的影响。 因此,根据我们使用的图像的性质,我们可以为自己选择最佳的图像标准化方案。 但是,任何默认的标准化方法都倾向于提供合理的表现。

使用仿射变换生成其他数据

我们将在图像像素坐标上使用仿射变换,使用 keras ImageDataGenerator生成其他数据。 我们将主要使用的转换是旋转,平移和缩放。 如果像素空间坐标由x = [x[1] x[2]]^T ∈ R^2定义 ,则像素的新坐标可以通过以下方式给出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eA1GAfKp-1681653992504)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/48a4bf27-bee5-46b1-8b5b-9fe1f96d8a52.png)]

这里, M = R^(2x2)是仿射变换矩阵, b = [b[1], b[2]]^T ∈ R^2是翻译向量。

b[1]指定沿一个空间方向的翻译,而b[2]提供沿另一空间维度的翻译。

这些转换是必需的,因为神经网络通常不是平移不变,旋转不变或尺度不变。 合并操作确实提供了一些平移不变性,但是通常这还不够。 神经网络不会将图像中特定位置的一个对象和另一图像中平移位置的相同对象视为同一事物。 这就是为什么我们需要在不同平移位置的图像的多个实例,以使神经网络更好地学习。 相同的解释适用于旋转和缩放。

旋转

以下是旋转的仿射变换矩阵,其中θ表示旋转角度:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rkPpu0qW-1681653992505)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/1fe912fc-2dba-41b7-abe9-03443e5a0be2.png)]

在这种情况下,转换向量b为零。 通过选择一个非零b,我们可以得到旋转和平移。

例如,下图显示了视网膜的照片,然后是旋转 90 度的同一张照片:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fbVEJZ5Q-1681653992505)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/8e268fb7-87ef-4dc7-b478-4ae01ced9521.png)]

图 2.5:视网膜的旋转照片

平移

对于平移,仿射变换矩阵是单位矩阵,平移向量b具有非零值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-35LTxOEZ-1681653992505)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/53b85765-1bcb-4f23-b8f3-60b223dbf0f7.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4U6ESGbU-1681653992505)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/73d49d1a-f974-40f0-9638-6ee27f574016.png)]

例如,对于垂直方向上五个像素位置和水平方向上三个像素位置的平移,我们可以使用b = [5 3]^TM作为单位矩阵。

以下是沿图像的宽度和高度按 24 个像素位置对视网膜进行的图像平移:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UrNDQy7s-1681653992505)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/d8fe4d81-04d8-481f-afdd-3a8295ab2a1c.png)]

图 2.5:视网膜的图像平移

缩放比例

缩放可以通过对角矩阵M ∈ R^(2x2)执行,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G7kclgrL-1681653992506)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f7892eff-50fc-4cf8-a629-271ad0cd18e3.png)]

这里,S[v]表示沿垂直方向的比例因子,S[h]表示沿水平方向的比例因子(请参见 “图 2.6”为插图)。 我们还可以选择通过具有非零转换向量b来跟随转换的缩放:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fe1dRnnM-1681653992506)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/3b758265-dbb1-48fb-a744-4a08fef9ce08.png)]

图 2.6 视网膜的图像缩放

反射

可以通过变换矩阵T ∈ R^(2x2)获得关于一条线L与水平角度为θ的反射,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W719IUbz-1681653992506)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/a5bd446b-cfaa-46c9-9797-36893efc4d5b.png)]

下图显示了视网膜照片的水平翻转:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HKWKtLIA-1681653992506)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/380a724a-5cef-4a07-948a-f07da269bf10.png)]

图 2.7:视网膜照片的水平翻转

通过仿射变换产生更多图像

keras图像生成器将使用以下类来完成我们的任务:

datagen = ImageDataGenerator(
             horizontal_flip = True,
                             vertical_flip = True,
                             width_shift_range = 0.1,
                             height_shift_range = 0.1,
                             channel_shift_range=0,
                             zoom_range = 0.2,
                             rotation_range = 20)

从定义的生成器中可以看到,我们启用了水平和垂直翻转,这仅是图像沿水平和垂直轴的反射。 同样,我们将沿宽度和高度的图像平移定义为沿这些方向的像素位置的 10% 以内。 旋转范围限制为20度的角度,而比例因子定义为原始图像的0.81.2之内。

网络架构

现在,我们将对经过预训练的ResNet50InceptionV3VGG16网络进行实验,并找出哪种网络效果最好。 每个预训练模型的权重均基于 ImageNet。 我提供了 ResNet,InceptionV3VGG16架构的原始论文的链接,以供参考。 建议读者仔细阅读这些文章,以深入了解这些架构及其之间的细微差别。

VGG 论文链接如下:

《用于大型图像识别的超深度卷积网络》

ResNet 论文链接如下:

《用于图像识别的深度残差学习》

InceptionV3 论文链接如下:

《重新思考计算机视觉的起始架构》

简而言之,VGG16是一个 16 层的 CNN,使用3 x 3过滤器和2 x 2接收场进行卷积。 整个网络使用的激活函数都是 ReLU。 由 Simonyan 和 Zisserman 开发的 VGG 架构是 2014 年 ILSVRC 竞赛的亚军。 VGG16网络由于其简单性而广受欢迎,它是从图像中提取特征的最受欢迎的网络。

ResNet50是一种深层的 CNN,它实现了残余块的概念,与VGG16网络的概念完全不同。 经过一系列的卷积激活池操作之后,该块的输入再次被反馈到输出。 ResNet 架构是由 Kaiming He 等人开发的,尽管具有 152 层,但它不如 VGG 网络复杂。 该架构通过实现 3.57% 的前五位错误率赢得了 2015 年 ILSVRC 竞赛,这比该竞赛数据集上的人类水平表现要好。 通过检查目标是否在概率最高的五类预测中来计算前五个错误率。 原则上,ResNet 网络尝试学习残差映射,而不是直接从输出映射到输入,如下面的残差框图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x56964Rx-1681653992507)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/6e52be2c-3618-4411-8f03-b372b21e42c7.png)]

图 2.8:ResNet 模型的残差块

InceptionV3 是 Google 提供的最新的 CNN。 InceptionV3 架构没有在每一层使用固定大小的卷积过滤器,而是使用不同大小的过滤器来提取不同粒度级别的特征。 下图说明了 InceptionV3 层的卷积块:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hrXOlwsz-1681653992507)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/afb2d8e3-f919-49c3-9ede-13e707b6d72e.png)]

图 2.9:InceptionV3 卷积模块

Inception V1(GoogleNet)是 2014 年 ILSVRC 竞赛的获胜者。 它的最高 5% 错误率非常接近人类水平的表现,为 6.67%。

VGG16 迁移学习网络

我们将从预训练的VGG16网络中的最后一个合并层中获取输出,并添加一对全连接层,每个层 512 个单元,然后是输出层。 最终池化层的输出是从全局平均池化操作传递到全连接层之前的。 我们只可以展平池化层的输出,而不是执行全局平均池化-其思想是确保池化的输出不是二维晶格格式,而是一维数组格式, 非常像一个全连接层。 下图说明了基于预训练的VGG16的新VGG16的架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ElY6rDAa-1681653992507)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/c2ef9608-eaff-46fd-990c-75a189d2463b.png)]

图 2.10:VGG16 迁移学习网络

如上图所示,我们将从预训练网络中的最后一个最大池化层提取输出,并在最终输出层之前附加两个全连接层。 基于先前的架构,可以使用keras如下代码块所示定义 VGG 定义函数:

def VGG16_pseudo(dim=224,freeze_layers=10,full_freeze='N'):
   # model_save_dest = {}
    model = VGG16(weights='imagenet',include_top=False)
    x = model.output
    x = GlobalAveragePooling2D()(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    out = Dense(5,activation='softmax')(x)
    model_final = Model(input = model.input,outputs=out)
    if full_freeze != 'N':
        for layer in model.layers[0:freeze_layers]:
         layer.trainable = False
    return model_final

我们将使用 ImageNet 上训练的VGG16的权重作为模型的初始权重,然后对模型进行微调。 我们还冻结了前几层的权重(默认设置为10),因为在 CNN 中,前几层学会了检测通用特征,例如边缘,颜色成分等。 因此,特征在各个域之间不会有太大变化。 冻结层是指不训练该层特定的权重。 我们可以尝试冻结的层数,并选择提供最佳验证分数的层。 由于我们正在执行多类分类,因此已为输出层选择了 softmax 激活函数。

InceptionV3 迁移学习网络

在以下代码块中定义了用于我们任务的InceptionV3网络。 需要注意的一件事是,由于InceptionV3是一个更深的网络,因此我们可以拥有更多的初始层。 在数据可用性方面,不训练每个模型中的所有层的想法还有另一个优势。 如果我们使用较少的数据训练,则整个网络的权重可能会导致过拟合。 冻结层会减少要训练的权重数,因此提供了一种形式的规则化。

由于初始层学习通用特征,而与问题的范围无关,因此它们是冻结的最佳层。 我们还在全连接层中使用了丢弃,以防止过拟合:

def inception_pseudo(dim=224,freeze_layers=30,full_freeze='N'):
    model = InceptionV3(weights='imagenet',include_top=False)
    x = model.output
    x = GlobalAveragePooling2D()(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    out = Dense(5,activation='softmax')(x)
    model_final = Model(input = model.input,outputs=out)
    if full_freeze != 'N':
        for layer in model.layers[0:freeze_layers]:
         layer.trainable = False
    return model_final

ResNet50 迁移学习网络

可以类似于VGG16InceptionV3网络定义用于迁移学习的ResNet50模型,如下所示:

def resnet_pseudo(dim=224,freeze_layers=10,full_freeze='N'):
   # model_save_dest = {}
    model = ResNet50(weights='imagenet',include_top=False)
    x = model.output
    x = GlobalAveragePooling2D()(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    out = Dense(5,activation='softmax')(x)
    model_final = Model(input = model.input,outputs=out)
    if full_freeze != 'N':
        for layer in model.layers[0:freeze_layers]:
       layer.trainable = False
    return model_final

优化器和初始学习率

Adam 优化器(自适应矩估计器)用于实现随机梯度下降高级版本的训练。 Adam 优化器会考虑成本函数中的曲率,同时使用动量来确保朝着良好的局部最小值稳定发展。 对于眼前的问题,由于我们正在使用迁移学习,并且希望使用从预训练的网络中获得的尽可能多的先前学习的特征,因此我们将使用较小的初始学习率0.00001。 这将确保网络不会丢失经过预训练的网络学习到的有用特征,并根据当前问题的新数据将其微调至较不激进的最佳点。 Adam 优化器可以定义如下:

adam = optimizers.Adam(lr=0.00001, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)

beta_1参数控制动量计算中当前梯度的贡献,而beta_2参数控制梯度归一化中梯度平方的贡献,这有助于解决成本函数中的曲率。

交叉验证

由于训练数据集很小,我们将执行五重交叉验证,以更好地了解模型对新数据进行泛化的能力。 我们还将在训练中使用交叉验证不同折中的所有五个模型进行推断。 属于类别标签的测试数据点的概率将是所有五个模型的平均概率预测,其表示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MtrmfUDO-1681653992507)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/f22caa67-e998-4298-96ca-80c28ffa53a8.png)]

由于目的是预测实际类别而不是概率,因此我们将选择具有最大概率的类别。 当我们使用基于分类的网络和成本函数时,此方法有效。 如果我们将问题视为回归问题,则该过程会有一些更改,我们将在后面讨论。

根据验证日志损失对检查点进行建模

它始终是一个很好的做法,以保存模型时所选择的评估验证分数提高。 对于我们的项目,我们将跟踪验证日志损失,并随着验证得分在不同周期的提高而保存模型。 这样,在训练之后,我们将保存提供最佳验证分数的模型权重,而不是保存我们停止训练后的最终模型权重。 训练将继续进行,直到达到为训练定义的最大周期数,或者直到连续10个周期的验证日志损失都没有减少为止。 当3周期的验证日志损失没有改善时,我们还将降低学习率。 以下代码块可用于执行学习率降低和检查点操作:

reduce_lr = keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.50,
 patience=3, min_lr=0.000001)
 callbacks = [
 EarlyStopping(monitor='val_loss', patience=10, mode='min', verbose=1),
 CSVLogger('keras-5fold-run-01-v1-epochs_ib.log', separator=',', append=False),reduce_lr,
 ModelCheckpoint(
 'kera1-5fold-run-01-v1-fold-' + str('%02d' % (k + 1)) + '-run-' + str('%02d' % (1 + 1)) + '.check',
 monitor='val_loss', mode='min', # mode must be set to max or keras will be confused
 save_best_only=True,
 verbose=1)
 ]

如您在前面的代码块中所见,如果在3patience=3)周期中验证损失没有改善,则学习率降低到一半(0.50)。 同样,如果在10patience = 10)周期没有减少验证损失,我们将停止训练(通过执行EarlyStopping)。 每当验证日志损失减少时,都会保存模型,如以下代码片段所示:

'kera1-5fold-run-01-v1-fold-' + str('%02d' % (k + 1)) + '-run-' + str('%02d' % (1 + 1)) + '.check'

keras-5fold-run-01-v1-epochs_ib.log 日志文件中跟踪训练过程的每个周期的验证日志损失,如果验证日志损失有所改善,为了保存模型,请参考该文件, 或决定何时降低学习率或停止训练。

通过使用keras save函数在用户定义的路径中保存每个折叠中的模型,而在推理过程中,使用keras.load_model函数将模型加载到内存中。

Python 实现训练过程

以下 Python 代码块显示了训练过程的端到端实现。 它由前面各节中讨论的所有函数块组成。 让我们首先调用所需的所有 Python 包,如下所示:

import numpy as np
np.random.seed(1000)
import os
import glob
import cv2
import datetime
import pandas as pd
import time
import warnings
warnings.filterwarnings("ignore")
from sklearn.model_selection import KFold
from sklearn.metrics import cohen_kappa_score
from keras.models import Sequential,Model
from keras.layers.core import Dense, Dropout, Flatten
from keras.layers.convolutional import Convolution2D, MaxPooling2D, ZeroPadding2D
from keras.layers import GlobalMaxPooling2D,GlobalAveragePooling2D
from keras.optimizers import SGD
from keras.callbacks import EarlyStopping
from keras.utils import np_utils
from sklearn.metrics import log_loss
import keras
from keras import __version__ as keras_version
from keras.applications.inception_v3 import InceptionV3
from keras.applications.resnet50 import ResNet50
from keras.applications.vgg16 import VGG16
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers 
from keras.callbacks import EarlyStopping, ModelCheckpoint, CSVLogger, Callback
from keras.applications.resnet50 import preprocess_input
import h5py
import argparse
from sklearn.externals import joblib
import json

导入所需的库后,我们可以定义TransferLearning类:

class TransferLearning:
  def __init__(self):
    parser = argparse.ArgumentParser(description='Process the inputs')
    parser.add_argument('--path',help='image directory')
    parser.add_argument('--class_folders',help='class images folder  
                        names')
    parser.add_argument('--dim',type=int,help='Image dimensions to 
                        process')
    parser.add_argument('--lr',type=float,help='learning 
                        rate',default=1e-4)
    parser.add_argument('--batch_size',type=int,help='batch size')
    parser.add_argument('--epochs',type=int,help='no of epochs to 
                        train')
    parser.add_argument('--initial_layers_to_freeze',type=int,help='the 
                        initial layers to freeze')
    parser.add_argument('--model',help='Standard Model to 
                        load',default='InceptionV3')
    parser.add_argument('--folds',type=int,help='num of cross 
                        validation folds',default=5)
    parser.add_argument('--outdir',help='output directory')
    args = parser.parse_args()
    self.path = args.path
    self.class_folders = json.loads(args.class_folders)
    self.dim = int(args.dim)
    self.lr = float(args.lr)
    self.batch_size = int(args.batch_size)
    self.epochs = int(args.epochs)
    self.initial_layers_to_freeze = int(args.initial_layers_to_freeze)
    self.model = args.model
    self.folds = int(args.folds)
    self.outdir = args.outdir

接下来,让我们定义一个函数,该函数可以读取图像并将它们调整为合适的大小,如下所示:

def get_im_cv2(self,path,dim=224):
    img = cv2.imread(path)
    resized = cv2.resize(img, (dim,dim), cv2.INTER_LINEAR)
    return resized
  # Pre Process the Images based on the ImageNet pre-trained model   
    Image transformation
  def pre_process(self,img):
    img[:,:,0] = img[:,:,0] - 103.939
    img[:,:,1] = img[:,:,0] - 116.779
    img[:,:,2] = img[:,:,0] - 123.68
    return img
  # Function to build X, y in numpy format based on the   
    train/validation datasets
  def read_data(self,class_folders,path,num_class,dim,train_val='train'):
    print(train_val)
    train_X,train_y = [],[] 
    for c in class_folders:
      path_class = path + str(train_val) + '/' + str(c)
      file_list = os.listdir(path_class) 
      for f in file_list:
        img = self.get_im_cv2(path_class + '/' + f)
        img = self.pre_process(img)
        train_X.append(img)
        train_y.append(int(c.split('class')[1]))
    train_y = keras.utils.np_utils.to_categorical(np.array(train_y),num_class) 
    return np.array(train_X),train_y

接下来,我们现在将定义三个用于迁移学习的模型,从InceptionV3开始:

def inception_pseudo(self,dim=224,freeze_layers=30,full_freeze='N'):
    model = InceptionV3(weights='imagenet',include_top=False)
    x = model.output
    x = GlobalAveragePooling2D()(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    out = Dense(5,activation='softmax')(x)
    model_final = Model(input = model.input,outputs=out)
    if full_freeze != 'N':
      for layer in model.layers[0:freeze_layers]:
        layer.trainable = False
    return model_final

然后,我们将定义用于迁移学习的ResNet50模型:

def resnet_pseudo(self,dim=224,freeze_layers=10,full_freeze='N'):
    model = ResNet50(weights='imagenet',include_top=False)
    x = model.output
    x = GlobalAveragePooling2D()(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    out = Dense(5,activation='softmax')(x)
    model_final = Model(input = model.input,outputs=out)
    if full_freeze != 'N':
      for layer in model.layers[0:freeze_layers]:
        layer.trainable = False
    return model_final

最后,我们将定义VGG16模型:

def VGG16_pseudo(self,dim=224,freeze_layers=10,full_freeze='N'):
    model = VGG16(weights='imagenet',include_top=False)
    x = model.output
    x = GlobalAveragePooling2D()(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    x = Dense(512, activation='relu')(x)
    x = Dropout(0.5)(x)
    out = Dense(5,activation='softmax')(x)
    model_final = Model(input = model.input,outputs=out)
    if full_freeze != 'N':
      for layer in model.layers[0:freeze_layers]:
        layer.trainable = False
    return model_final

现在,让我们定义训练函数,如下所示:

def train_model(self,train_X,train_y,n_fold=5,batch_size=16,epochs=40,
dim=224,lr=1e-5,model='ResNet50'):
    model_save_dest = {}
    k = 0
    kf = KFold(n_splits=n_fold, random_state=0, shuffle=True)
    for train_index, test_index in kf.split(train_X):
      k += 1 
      X_train,X_test = train_X[train_index],train_X[test_index]
      y_train, y_test = train_y[train_index],train_y[test_index]
      if model == 'Resnet50':
        model_final =     
        self.resnet_pseudo(dim=224,freeze_layers=10,full_freeze='N')
      if model == 'VGG16':
        model_final = 
        self.VGG16_pseudo(dim=224,freeze_layers=10,full_freeze='N') 
      if model == 'InceptionV3':
        model_final = self.inception_pseudo(dim=224,freeze_layers=10,full_freeze='N')
      datagen = ImageDataGenerator(
          horizontal_flip = True,
          vertical_flip = True,
          width_shift_range = 0.1,
          height_shift_range = 0.1,
          channel_shift_range=0,
          zoom_range = 0.2,
          rotation_range = 20)
      adam = optimizers.Adam(lr=lr, beta_1=0.9, beta_2=0.999, 
        epsilon=1e-08, decay=0.0)
      model_final.compile(optimizer=adam, 
       loss= ["categorical_crossentropy"],metrics=['accuracy'])
      reduce_lr = keras.callbacks.ReduceLROnPlateau(monitor='val_loss',   
        factor=0.50, patience=3, min_lr=0.000001)
      callbacks = [
            EarlyStopping(monitor='val_loss', patience=10, mode='min',   
             verbose=1),
        CSVLogger('keras-5fold-run-01-v1-epochs_ib.log', 
        separator=',', append=False),reduce_lr,
            ModelCheckpoint(
                'kera1-5fold-run-01-v1-fold-' + str('%02d' % (k + 1)) + 
                '-run-' + str('%02d' % (1 + 1)) + '.check',
                monitor='val_loss', mode='min',
                save_best_only=True,
                verbose=1)
          ]
      model_final.fit_generator(datagen.flow(X_train,y_train, 
                                 batch_size=batch_size),
      steps_per_epoch=X_train.shape[0]/batch_size, epochs=epochs,
         verbose=1, validation_data= (X_test,y_test),
         callbacks=callbacks, class_weight=     
         {0:0.012,1:0.12,2:0.058,3:0.36,4:0.43})
      model_name = 'kera1-5fold-run-01-v1-fold-' + str('%02d' % (k + 
                    1)) + '-run-' + str('%02d' % (1 + 1)) + '.check'
      del model_final
      f = h5py.File(model_name, 'r+')
      del f['optimizer_weights']
      f.close()
      model_final = keras.models.load_model(model_name)
      model_name1 = self.outdir + str(model) + '___' + str(k) 
      model_final.save(model_name1)
      model_save_dest[k] = model_name1
    return model_save_dest

我们还将为保持数据集定义一个inference函数,如下所示:

def inference_validation(self,test_X,test_y,model_save_dest,
    n_class=5,folds=5):
    pred = np.zeros((len(test_X),n_class))
    for k in range(1,folds + 1):
      model = keras.models.load_model(model_save_dest[k])
      pred = pred + model.predict(test_X)
    pred = pred/(1.0*folds) 
    pred_class = np.argmax(pred,axis=1) 
    act_class = np.argmax(test_y,axis=1)
    accuracy = np.sum([pred_class == act_class])*1.0/len(test_X)
    kappa = cohen_kappa_score(pred_class,act_class,weights='quadratic')
    return pred_class,accuracy,kappa

现在,让我们调用main函数,以触发训练过程,如下所示:

def main(self):
    start_time = time.time()
    self.num_class = len(self.class_folders)
  if self.mode == 'train':
        print("Data Processing..")
        file_list,labels= 
        self.read_data(self.class_folders,self.path,self.num_class,
                       self.dim,train_val='train')
        print(len(file_list),len(labels))
        print(labels[0],labels[-1])
        self.model_save_dest = 
        self.train_model(file_list,labels,n_fold=self.folds,
                         batch_size=self.batch_size,
                         epochs=self.epochs,dim=self.dim,
                         lr=self.lr,model=self.model)
        joblib.dump(self.model_save_dest,f'{self.outdir}/model_dict.pkl')
        print("Model saved to dest:",self.model_save_dest)
  else:
        model_save_dest = joblib.load(self.model_save_dest)
        print('Models loaded from:',model_save_dest)
            # Do inference/validation
        test_files,test_y = 
        self.read_data(self.class_folders,self.path,self.num_class,
                       self.dim,train_val='validation')
      test_X = []
      for f in test_files:
        img = self.get_im_cv2(f)
        img = self.pre_process(img)
        test_X.append(img)
      test_X = np.array(test_X)
      test_y = np.array(test_y)
      print(test_X.shape)
      print(len(test_y))
      pred_class,accuracy,kappa =     
      self.inference_validation(test_X,test_y,model_save_dest,
                                n_class=self.num_class,folds=self.folds)
      results_df = pd.DataFrame()
      results_df['file_name'] = test_files
      results_df['target'] = test_y
      results_df['prediction'] = pred_class
      results_df.to_csv(f'{self.outdir}/val_resuts_reg.csv',index=False)
      print("-----------------------------------------------------")
      print("Kappa score:", kappa)
      print("accuracy:", accuracy) 
      print("End of training")
      print("-----------------------------------------------------")
      print("Processing Time",time.time() - start_time,' secs')

我们可以更改几个参数,例如学习率,批量大小,图像大小等,并且我们可以进行实验以得出一个不错的模型。 在训练阶段,模型位置保存在model_save_dest字典中,该字典被写入dict_model文件中。

在推理阶段,该模型仅基于训练后的模型对新的测试数据进行预测。

可以如下调用名为TransferLearning.py的用于迁移学习的脚本:

python TransferLearning.py --path '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/book AI/Diabetic Retinopathy/Extra/assignment2_train_dataset/' --class_folders '["class0","class1","class2","class3","class4"]' --dim 224 --lr 1e-4 --batch_size 16 --epochs 20 --initial_layers_to_freeze 10 --model InceptionV3 --folds 5 --outdir '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/'

脚本的输出日志如下:

Model saved to dest: {1: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/categorical/InceptionV3___1', 2: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/categorical/InceptionV3___2', 3: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/categorical/InceptionV3___3', 4: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/categorical/InceptionV3___4', 5: '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/categorical/InceptionV3___5'}
validation
-----------------------------------------------------
Kappa score: 0.42969781637876836
accuracy: 0.5553973227000855
End of training
-----------------------------------------------------
Processing Time 26009.3344039917 secs

从日志中的结果可以看出,我们达到了不错的交叉验证精度,约为56%,二次 Kappa 约为0.43

在此脚本中,我们将所有数据加载到内存中,然后将增强图像从ImageDataGenerator馈送到模型进行训练。 如果训练图像集很少和/或大小适中,那么将数据加载到内存中可能不会引起太大关注。 但是,如果图像语料库很大和/或我们资源有限,那么将所有数据加载到内存中将不是可行的选择。 由于运行这些实验的计算机具有 64 GB RAM,因此我们能够毫无问题地训练这些模型。 即使是 16 GB 的 RAM 计算机也可能不足以通过将所有数据加载到内存中来运行这些实验,并且您可能会遇到内存错误。

问题是,我们是否需要一次将所有数据加载到内存中?

由于神经网络适用于小型批量,因此我们只需要与一个小型批量对应的数据即可一次通过反向传播训练模型。 类似地,对于下一个反向传播,我们可以丢弃与当前批量相对应的数据,然后处理下一个批量。 因此,以某种方式,每个小型批量中的内存需求仅是与该批量相对应的数据。 因此,我们可以在训练时创建动态批量,从而避免在内存较少的机器上训练深度学习模型。 Keras 具有在训练时创建动态批量的良好功能,我们将在下一节中讨论。

训练期间动态创建小批量

仅加载与小批量对应的数据的一种方法是通过从其位置随机处理图像来动态创建小批量。 小批量处理的图像数量将等于我们指定的小批量大小。 当然,由于在训练期间会动态创建小批量生产,因此在训练过程中会遇到一些瓶颈,但是这一瓶颈可以忽略不计。 特殊的包,例如keras,具有有效的动态批量创建机制。 我们将在训练过程中利用 keras 中的flow_from_directory函数动态创建迷你批,以减少训练过程的内存需求。 我们仍将继续使用ImageDataGenerator进行图像增强。 可以如下定义训练生成器和验证生成器。

通过将pre_process函数作为输入输入到ImageDataGeneratorpreprocessing_function中,完成从三个通道中减去平均图像像素强度的图像预处理步骤:

def pre_process(img):
            img[:,:,0] = img[:,:,0] - 103.939
            img[:,:,1] = img[:,:,0] - 116.779
            img[:,:,2] = img[:,:,0] - 123.68
            return img
        train_file_names = glob.glob(f'{train_dir}/*/*')
        val_file_names = glob.glob(f'{val_dir}/*/*')
        train_steps_per_epoch = len(train_file_names)/float(batch_size)
        val_steps_per_epoch = len(val_file_names)/float(batch_size)
        train_datagen = 
        ImageDataGenerator(horizontal_flip =
                           True,vertical_flip =
                           True,width_shift_range =
                           0.1,height_shift_range = 0.1,
                           channel_shift_range=0,zoom_range = 0.2,
                           rotation_range = 20,
                           preprocessing_function=pre_process)
        val_datagen =
        ImageDataGenerator(preprocessing_function=pre_process)
        train_generator =
        train_datagen.flow_from_directory(train_dir,
                                          target_size=(dim,dim),
                                          batch_size=batch_size,
                                          class_mode='categorical')
        val_generator =
        val_datagen.flow_from_directory(val_dir,
                                        target_size=(dim,dim),
                                         batch_size=batch_size,
                                         class_mode='categorical')
        print(train_generator.class_indices)
        joblib.dump(train_generator.class_indices,
        f'{self.outdir}/class_indices.pkl')

flow_from_directory函数将一个图像目录作为输入,并期望一个与该图像目录中的类有关的文件夹。 然后,它从文件夹名称推断类标签。 如果图像目录的图像目录具有以下结构,则将类别推断为01234,与类别文件夹'class0''class1'有关 ,'class2''class3''class4'

flow_from_directory函数的其他重要输入是batch_sizetarget_sizeclass_modetarget_size用于指定要馈送到神经网络的图像的大小,而class_mode用于指定问题的性质。 对于二分类,将class_mode设置为二进制,而对于多分类,将其设置为categorical

接下来,我们将通过创建动态批量来训练同一模型,而不是一次将所有数据加载到内存中。 我们只需要使用flow_from_directory选项创建一个生成器,然后将其绑定到数据扩充对象即可。 数据生成器对象可以如下生成:

# Pre processing for channel wise mean pixel subtraction 
def pre_process(img):
    img[:,:,0] = img[:,:,0] - 103.939
    img[:,:,1] = img[:,:,0] - 116.779
    img[:,:,2] = img[:,:,0] - 123.68
    return img
# Add the pre_process function at the end of the ImageDataGenerator, 
#rest all of the data augmentation options
# remain the same. 
train_datagen = 
ImageDataGenerator(horizontal_flip = True,vertical_flip = True,
                   width_shift_range = 0.1,height_shift_range = 0.1,
                   channel_shift_range=0,zoom_range =                     
                   0.2,rotation_range = 20,
                   preprocessing_function=pre_process)
 # For validation no data augmentation on image mean subtraction preprocessing
val_datagen = ImageDataGenerator(preprocessing_function=pre_process)
# We build the train generator using flow_from_directory
train_generator = train_datagen.flow_from_directory(train_dir,
        target_size=(dim,dim),
        batch_size=batch_size,
        class_mode='categorical')
# We build the validation generator using flow_from_directory
val_generator = val_datagen.flow_from_directory(val_dir,
        target_size=(dim,dim),
        batch_size=batch_size,
        class_mode='categorical')

在前面的代码中,我们将ImageDataGenerator传递给执行均值像素减法的附加任务,因为我们没有控制将图像数据加载到内存中并通过pre_process函数传递的任何控制权。 在preprocessing_function选项中,我们可以为任何特定的预处理任务传递任何所需的自定义函数。

通过train_dirval_dir,我们将训练和验证目录传递给使用flow_with_directory选项创建的训练和验证生成器。 生成器通过查看传递的训练数据目录(此处为train_dir)中的类别文件夹数来识别类别数。 在基于batch_size的训练时间内,图像根据指定的batch_size读入内存

class_mode帮助生成器识别其二分类还是多分类('categotical')。

详细的实现在 GitHub 上的TransferLearning_ffd.py文件夹中列出。

Python 脚本TransferLearning_ffd.py可以按以下方式调用:

python TransferLearning_ffd.py  --path '/media/santanu/9eb9b6dc-b380-486e-b4fd-c424a325b976/book AI/Diabetic Retinopathy/Extra/assignment2_train_dataset/' --class_folders '["class0","class1","class2","class3","class4"]' --dim 224  --lr 1e-4 --batch_size 32 --epochs 50 --initial_layers_to_freeze 10 --model InceptionV3  --outdir '/home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/'

作业运行的输出日志结尾如下:

Validation results saved at : /home/santanu/ML_DS_Catalog-/Transfer_Learning_DR/val_results.csv
[0 0 0 ... 4 2 2]
[0 0 0 ... 4 4 4]
Validation Accuracy: 0.5183708345200797
Validation Quadratic Kappa Score: 0.44422008110380984

如我们所见,通过重用现有网络并在同一网络上执行迁移学习,我们能够实现不错的0.44二次方 Kappa。

分类结果

通过使用所有三个神经网络架构VGG16ResNet50InceptionV3进行分类。 对于该糖尿病性视网膜病用例,使用迁移学习网络的InceptionV3版本可获得最佳结果。 如果是分类分类,我们只是将具有最大预测分类概率的分类转换为预测严重性标签。 但是,由于问题中的类别具有序数意义,因此我们可以利用 softmax 概率的方法之一是针对 softmax 概率对类别严重性进行期望并得出预期分数y_hat如下 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zzLPKsCq-1681653992508)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-proj-py/img/13040376-dbdd-411b-85da-5ae7e4231683.png)]

我们可以对分数进行排序,并确定三个阈值来确定图像属于哪个类别。 这些阈值可以通过将这些预期得分作为特征训练辅助模型来选择。 建议读者按照这些思路进行试验,看看是否有任何好处。

作为该项目的一部分,我们正在使用迁移学习来解决一个难题。 通过在给定的数据集上从头开始训练网络,模型表现可能会更好。

测试时推断

以下代码可用于对未标记的测试数据进行推断:

import keras
import numpy as np
import pandas as pd
import cv2
import os
import time
from sklearn.externals import joblib
import argparse
# Read the Image and resize to the suitable dimension size
def get_im_cv2(path,dim=224):
    img = cv2.imread(path)
    resized = cv2.resize(img, (dim,dim), cv2.INTER_LINEAR)
    return resized
# Pre Process the Images based on the ImageNet pre-trained model Image transformation
def pre_process(img):
    img[:,:,0] = img[:,:,0] - 103.939
    img[:,:,1] = img[:,:,0] - 116.779
    img[:,:,2] = img[:,:,0] - 123.68
    return img
# Function to build test input data
def read_data_test(path,dim):
    test_X = [] 
    test_files = []
    file_list = os.listdir(path) 
    for f in file_list:
        img = get_im_cv2(path + '/' + f)
        img = pre_process(img)
        test_X.append(img)
        f_name = f.split('_')[0]
        test_files.append(f_name)
    return np.array(test_X),test_files

让我们定义推理:

def inference_test(test_X,model_save_dest,n_class):
    folds = len(list(model_save_dest.keys()))
    pred = np.zeros((len(test_X),n_class))
    for k in range(1,folds + 1):
        model = keras.models.load_model(model_save_dest[k])
        pred = pred + model.predict(test_X)
    pred = pred/(1.0*folds) 
    pred_class = np.argmax(pred,axis=1) 
    return pred_class 
def main(path,dim,model_save_dest,outdir,n_class):
    test_X,test_files = read_data_test(path,dim)
    pred_class = inference_test(test_X,model_save_dest,n_class)
    out = pd.DataFrame()
    out['id'] = test_files
    out['class'] = pred_class
    out['class'] = out['class'].apply(lambda x:'class' + str(x))
    out.to_csv(outdir + "results.csv",index=False)
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='arguments')
    parser.add_argument('--path',help='path of images to run inference on')
    parser.add_argument('--dim',type=int,help='Image dimension size to          
                        process',default=224)
    parser.add_argument('--model_save_dest',
                        help='location of the trained models')
    parser.add_argument('--n_class',type=int,help='No of classes')
    parser.add_argument('--outdir',help='Output DIrectory')
    args = parser.parse_args()
    path = args.path
    dim = args.dim
    model_save_dest = joblib.load(args.model_save_dest)
    n_class = args.n_class
    outdir = args.outdir
    main(path,dim,model_save_dest,outdir,n_class)

Python 智能项目:1~5(3)https://developer.aliyun.com/article/1426931

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
12天前
|
机器学习/深度学习 数据采集 供应链
Python实现深度学习模型:智能库存管理系统
【10月更文挑战第5天】 Python实现深度学习模型:智能库存管理系统
62 9
|
9天前
|
机器学习/深度学习 数据采集 数据可视化
Python 数据分析:从零开始构建你的数据科学项目
【10月更文挑战第9天】Python 数据分析:从零开始构建你的数据科学项目
22 2
|
12天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用Python实现深度学习模型:智能数据隐私保护
使用Python实现深度学习模型:智能数据隐私保护 【10月更文挑战第3天】
42 0
|
10天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用Python实现深度学习模型:智能质量检测与控制
使用Python实现深度学习模型:智能质量检测与控制 【10月更文挑战第8天】
104 62
使用Python实现深度学习模型:智能质量检测与控制
|
3天前
|
机器学习/深度学习 传感器 存储
使用 Python 实现智能地震预警系统
使用 Python 实现智能地震预警系统
89 61
|
7天前
|
机器学习/深度学习 TensorFlow 调度
使用Python实现深度学习模型:智能能源消耗预测与管理
使用Python实现深度学习模型:智能能源消耗预测与管理
80 30
|
1天前
|
机器学习/深度学习 数据采集 消息中间件
使用Python实现智能火山活动监测模型
使用Python实现智能火山活动监测模型
12 1
|
3天前
|
JSON 搜索推荐 API
Python的web框架有哪些?小项目比较推荐哪个?
【10月更文挑战第15天】Python的web框架有哪些?小项目比较推荐哪个?
13 1
|
5天前
|
机器学习/深度学习 数据可视化 TensorFlow
使用Python实现深度学习模型:智能天气预测与气候分析
使用Python实现深度学习模型:智能天气预测与气候分析
77 3
|
4天前
|
机器学习/深度学习 数据可视化 TensorFlow
使用Python实现深度学习模型:智能海洋监测与保护
使用Python实现深度学习模型:智能海洋监测与保护
25 1