迁移学习与代码举例

2022-07-31 13:36:29 浏览数 (2)

大家好,又见面了,我是你们的朋友全栈君。

迁移学习出现背景

在有监督的机器学习和尤其是深度学习的场景应用中,需要大量的标注数据。标注数据是一项枯燥无味且花费巨大的任务,关键是现实场景中,往往无法标注足够的数据。而且模型的训练是极其耗时的。因此迁移学习营运而生。 传统机器学习(主要指监督学习)

  • 基于同分布假设
  • 需要大量标注数据 然而实际使用过程中不同数据集可能存在一些问题,比如
  • 数据分布差异
  • 标注数据过期 训练数据过期,也就是好不容易标定的数据要被丢弃,有些应用中数据是分布随着时间推移会有变化,将以往数据尽可能的利用起来,是迁移学习的思想。

迁移学习

迁移学习中有两个重要概念:

  • 域(Domain):可以理解为某个时刻的某个特定领域,比如动物图片数据和电影海报数据可以认为是是两个域,不同域中的数据特征往往存在比较大的差异。
  • 任务(Task):可以理解为业务场景的目标,例如情感识别和自动问答就是两个不同的Task,不同的Task的数据可以来自同一个域。 迁移学习并不是某一类特定算法,而是一种处理问题的思想。具体迁移学习往往分为以前步骤: 1. 根据超大规模数据对模型的进行预训练 2. 根据具体场景任务进行微调(可以微调权重,还可以调整终端的结构) 根据特征空间和迁移方法可将迁移学习分为不同种类。根据特征空间和标签空间是否相同,可将迁移学习分为异构迁移学习同构迁移学习

根据迁移学习的方法,大体可以将迁移学习分为 基于实例(样本)的迁移学习基于特征的迁移学习基于参数(模型)的迁移学习基于关系的迁移学习

  • 基于实例(样本)的迁移学习 传统机器学习中假设训练数据和测试数据来自同一个领域(Domain),即处于同一个特征空间,服从同样的数据分布。然而实际应用中测试数据跟训练数据可能来自不同的域(Domain)。 其中基于实例(样本)的方法对应的假设如下:

即源领域和目标领域具有很多交叠的特征,源领域和目标领域具有相同或相近的支撑集。(此处支撑可以理解为对任务目标有用的信息) 比如,利用评论分析客户情感的任务中,电子设备的评论和DVD的评论两种评论属于不同领域(Domain),虽然两种数据属于不同的域,但是可能存在一些电子设备的评论适用于DVD评论的情感分类任务。 我们的目标就是从源域的训练数据中找出那些适合目标域的实例,并将这些实例迁移到训练数据的学习中去。典型的算法是Tradaboosting,算法的关键想法是,利用boosting的技术来过滤掉源域数据中那些与目标域训练数据最不像的数据。其中,boosting的作用是建立一种自动调整权重的机制,于是重要的源域练数据的权重将会增加,不重要的源域训练数据的 权重将会减小。调整权重之后,这些带权重的源域训练数据将会作为额外的训练数据,与目标域训练数据一起从来提高分类模型的精度和可靠度。

  • 基于特征的迁移学习 基于特征选择的迁移学习算法,通过将源域和目标域特征变换到相同的空间(或者将其中之一映射到另一个的空间中)并最小化源域和目标域的距离来完成知识迁移。 基于特征映射的迁移学习算法,关注的是如何将源领域和目标领域的数据从原始特征空间映射到新的特征空间中去。在新的特征空间中,源领域数据与的目标领域的数据分布相同,从而可以在新的空间中,更好地利用源领域已有的有标记数据样本进行分类训练,最终对目标领域的数据进行分类测试。

针对source domain的大量数据进行训练的过程中,网络的前面几层可以看作特征抽取器。该特征抽取器抽取两个domain的特征,然后输入对抗网络;对抗网络尝试对特征进行区分。如果对抗网络对特征较难区分,则意味着两个domain的特征区分性较小、具有很好的迁移性,反之亦然。 最近几年,由于其良好的性能和实用性,基于对抗学习的深度迁移学习方法被广泛的研究。

  • 基于参数(模型)的迁移学习 基于参数(模型)的迁移学习主要是假设源域和目标域的学习任务中的相关模型会共享一些相同的参数或者先验分布服,使得源域和目标域的任务之间可以共享部分模型结构和与之对应的模型结构。 比如源域中有大量未标记的的猫和狗的图片,目标域中有少量的标记猫和狗的图片。可以利用DNN对源域中的猫狗图片进行无监督学习。之后将训练好的DNN模型的前几层layer和参数直接带入新的DNN,使前几层layer和参数复用在目标域猫狗分类任务中去。此时源域的DNN模型的前几层layer的输出可以看做对图片特征的提取器,这些特征能有效的代表图片的信息。
  • 基于关系的迁移学习 通过将source domain和target domain映射到一个新的数据空间。在这个新的数据空间中,来自两个domain的实例相似且适用于联合深度神经网络。该方法基于假设,“尽管source domain和target domain不相同,但是在精心设计的新数据空间中,它们可以更相似”。就目前来说,基于关系的迁移学习方法的相关研究工作非常少。

通常的迁移学习可以分为两步完成:“预训练”和“微调”

  • 预训练(pre-train):预训练的本质是无监督学习,栈式自编码器和多层神经网络都能得到有效的参数,使用大量数据将其训练之后的参数作为神经网络的参数初始值即预训练。预训练由于是无监督学习,无需对样本进行标记标签,省去大量人工时间,并且预训练后的参数直接带入其他任务模型中,可以使模型更快的收敛。
  • 微调(fine-tuning):任务模型一部分会复用预训练的部分模型结构和参数,根据具体任务,对模型参数进行微调。由于模型绝大部分参数是已经训练好的,因此无需大量数据进行微调,并且由于参数已经是经过训练的,模型收敛很快。

迁移学习应用在猫狗图片识别

我们的任务是对猫狗图片进行识别。

参考已有的成熟方案,此处我们采取Inception_v3结构网络。Inception_v3的具体结构如下:

可以看到,Inception_v3的结构是遍比较复杂的。使用个人PC对其训练是不现实的,因此此处采取基于参数的迁移学习方式对前几层的网络结构和参数进行复用,再利用本地20000张猫和狗的图片进行最后一层的训练。完成猫狗分类的任务。具体代码和解读如下: 引入必要的package

代码语言:javascript复制
# -*- coding: utf-8 -*-
import os
import numpy as np
from keras.utils import plot_model
from keras.applications.inception_v3 import InceptionV3
from keras.layers import Dense,Flatten,GlobalAveragePooling2D
from keras.models import Model,load_model
from keras.optimizers import SGD
from keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt

由于CNN本身并不具有识别图片缩放和图片旋转的能力(CNN pooling具有识别图片平移的能力)。因此需要对样本数据进行数据增强

代码语言:javascript复制
class PowerTransferMode:
   #数据准备
   def DataGen(self, dir_path, img_row, img_col, batch_size, is_train):
       
       """ data enhancement Args: dir_path:dir path of data img_row: image width img_col: image height batch_size: bitch_size of trian is_train: boolean return: data enhancement result """
       #数据增强可以增加训练数据,包括平移,旋转,镜像,加入噪声等
       if is_train:     #如果是训练集,进行数据增强
           datagen = ImageDataGenerator(rescale=1./255,
               zoom_range=0.25, rotation_range=15.,
               channel_shift_range=25., width_shift_range=0.02, height_shift_range=0.02,
               horizontal_flip=True, fill_mode='constant')
       else:   #如果是测试集,输出规范化的原始图片
           datagen = ImageDataGenerator(rescale=1./255)    #若不是训练集,则直接输出

       generator = datagen.flow_from_directory(
           dir_path, target_size=(img_row, img_col),
           batch_size=batch_size,
           #class_mode='binary',
           shuffle=is_train)

       return generator

接下来可以引入别人已经训练好的模型参数

代码语言:javascript复制
    def InceptionV3_model(self, lr=0.005, decay=1e-6, momentum=0.9, nb_classes=2, img_rows=197, img_cols=197, RGB=True,
                    is_plot_model=False):
        
        """ InceptionV3_model Args: ... return: InceptionV3_model """
        color = 3 if RGB else 1
        base_model = InceptionV3(weights='imagenet', include_top=False, pooling=None,
        						input_shape=(img_rows, img_cols, color), classes=nb_classes)     #引入InceptionV3_model模型和权重

        # 迁移学习,复用冻结base_model所有层,直接复用其参数
        for layer in base_model.layers:
            layer.trainable = False

复用前面的结构和参数后,后面可以接上自己定义的结构和损失函数函数,来适用于自己的任务。

代码语言:javascript复制
        x = base_model.output
        # 添加自己的全链接分类层
        x = GlobalAveragePooling2D()(x)       #添加average pooling层
        x = Dense(1024, activation='relu')(x)    #全连接层
        predictions = Dense(nb_classes, activation='softmax')(x)    #softmax输出预测结果

        # 训练模型
        model = Model(inputs=base_model.input, outputs=predictions)
        sgd = SGD(lr=lr, decay=decay, momentum=momentum, nesterov=True)    #优化器
        model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])

        return model

 	#训练模型 
    def train_model(self, model, epochs, train_generator, steps_per_epoch, validation_generator, 
                    validation_steps, model_url, is_load_model=False):  
        """ train model Args: ... return: model trained """
        if is_load_model and os.path.exists(model_url):
            model = load_model(model_url)    #载入模型

        history_ft = model.fit_generator(
            train_generator,
            steps_per_epoch=steps_per_epoch,
            epochs=epochs,
            validation_data=validation_generator,
            validation_steps=validation_steps)     #模型训练
        # 模型保存
        model.save(model_url,overwrite=True)     #模型保存
        return history_ft

完整的训练代码如下:

代码语言:javascript复制
# -*- coding: utf-8 -*-
import os
import numpy as np
from keras.utils import plot_model
from keras.applications.inception_v3 import InceptionV3
from keras.layers import Dense,Flatten,GlobalAveragePooling2D
from keras.models import Model,load_model
from keras.optimizers import SGD
from keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt

class PowerTransferMode:
    #数据准备
    def DataGen(self, dir_path, img_row, img_col, batch_size, is_train):
        
        """ data enhancement Args: dir_path:dir path of data img_row: image width img_col: image height batch_size: bitch_size of trian is_train: boolean return: data enhancement result """
        #数据增强可以增加训练数据,包括平移,旋转,镜像,加入噪声等
        if is_train:
            datagen = ImageDataGenerator(rescale=1./255,
                zoom_range=0.25, rotation_range=15.,
                channel_shift_range=25., width_shift_range=0.02, height_shift_range=0.02,
                horizontal_flip=True, fill_mode='constant')
        else:
            datagen = ImageDataGenerator(rescale=1./255)    #若不是训练集,则直接输出

        generator = datagen.flow_from_directory(
            dir_path, target_size=(img_row, img_col),
            batch_size=batch_size,
            #class_mode='binary',
            shuffle=is_train)

        return generator

    

    # InceptionV3模型
    def InceptionV3_model(self, lr=0.005, decay=1e-6, momentum=0.9, nb_classes=2, img_rows=197, img_cols=197, RGB=True,
                    is_plot_model=False):
        
        """ InceptionV3_model Args: ... return: InceptionV3_model """
        color = 3 if RGB else 1
        base_model = InceptionV3(weights='imagenet', include_top=False, pooling=None,
                           input_shape=(img_rows, img_cols, color),
                           classes=nb_classes)         #迁移学习,InceptionV3_model模型复用

        # 冻结base_model所有层,这样就可以正确获得bottleneck特征
        for layer in base_model.layers:
            layer.trainable = False

        x = base_model.output
        # 添加自己的全链接分类层
        x = GlobalAveragePooling2D()(x)       #添加average pooling层
        x = Dense(1024, activation='relu')(x)    #全连接层
        predictions = Dense(nb_classes, activation='softmax')(x)    #softmax输出预测结果

        # 训练模型
        model = Model(inputs=base_model.input, outputs=predictions)
        sgd = SGD(lr=lr, decay=decay, momentum=momentum, nesterov=True)    #优化器
        model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])

        return model
    
    #训练模型
    def train_model(self, model, epochs, train_generator, steps_per_epoch, validation_generator, 
                    validation_steps, model_url, is_load_model=False):  
        """ train model Args: ... return: model trained """
        if is_load_model and os.path.exists(model_url):
            model = load_model(model_url)    #载入模型

        history_ft = model.fit_generator(
            train_generator,
            steps_per_epoch=steps_per_epoch,
            epochs=epochs,
            validation_data=validation_generator,
            validation_steps=validation_steps)     #模型训练
        # 模型保存
        model.save(model_url,overwrite=True)     #模型保存
        return history_ft

image_size = 197
batch_size = 32
transfer = PowerTransferMode()
train_generator = transfer.DataGen('D:/data/cat_vs_dog/train_set/', image_size, image_size, batch_size, True)
validation_generator = transfer.DataGen('D:/data/cat_vs_dog/validation_set/', image_size, image_size, batch_size, False)
model = transfer.InceptionV3_model(nb_classes=2, img_rows=image_size, img_cols=image_size, is_plot_model=False)
history_ft = transfer.train_model(model, 10, train_generator, 600, validation_generator, 60, 'D:/data/cat_vs_dog/inceptionv3_nbs.model', is_load_model=False)

利用训练好的模型对测试集进行预测:

代码语言:javascript复制
test_model = load_model('D:/data/cat_vs_dog/inceptionv3_nbs.model')
image_size = 197
batch_size = 32
transfer = PowerTransferMode()

test_datagen = ImageDataGenerator(rescale=1./255)
test_generator = test_datagen.flow_from_directory('D:/data/cat_vs_dog/test_set/', target_size=(image_size, image_size),
            batch_size=1,class_mode='binary',shuffle=False)
test_generator.reset()

pred = test_model.predict_generator(test_generator,steps=10, verbose=0)
predicted_list = np.argmax(pred, axis=1)
predicted_class = ["狗" if i==1 else "猫" for i in predicted_list]

具体的预测结果如下:

可见预测的分类准确率还是比较高的。

参考文献 迁移学习概述 迁移学习系列—基于实例方法的迁移学习 薛贵荣:迁移学习( Transfer Learning ) 综述:迁移学习发展现状及未来趋势

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/128443.html原文链接:https://javaforall.cn

0 人点赞