当前位置:   article > 正文

迁移学习(Transfer Learning)概述及代码实现(full version)

迁移学习

基于PaddlePaddle的李宏毅机器学习——迁移学习

大噶好,我是黄波波。希望能和大家共进步,错误之处恳请指出!
百度AI Studio个人主页, 我在AI Studio上获得白银等级,点亮2个徽章,来互关呀~

本项目是在飞桨深度学习学院提供的李宏毅-机器学习特训营课程。

Abstract

本文共分为两大部分:第一部分介绍迁移学习的主要概念以及类型,第二部分是实现迁移学习布置的作业——领域对抗性训练(Domain Adversarial Training)并进行了三次不同epoch的训练。
项目传送门

第一部分:迁移学习介绍

1 迁移学习:Transfer Learning

1.1 什么是迁移学习呢?

假设现在要做猫和狗的分类器,我们需要一样标签数据告诉机器哪些是猫,哪些是狗。
同时,假设现在有一些与猫和狗没有直接关系的数据,这里说是没有直接关系,并不是说是完全没有关系。就是说有一些关系,但又不是直接相关的。

假设现在有自然界真实存在的老虎和大象的图片,那老虎和大象对分辨猫和狗会有帮助吗。

或者说我们有一些卡通动画中的猫和狗图像,但不是真实存在的,有没有帮助呢。

迁移学习把任务A开发的模型作为初始点,重新使用在为任务B开发模型的过程中。迁移学习是通过从已学习的相关任务中转移知识来改进学习的新任务。

1.2 为什么用迁移学习

这三个说的是,第一个是做闽南语(台湾腔)的语音识别,但是没有太多的训练数据,只有很多无直接关系的英文、普通话数据;第二是做医疗方面的图像识别,同样样本不多,但有很多其他真实动物的图像;第三个说的是在特定领域,这里是法律方面的文本分析,缺少数据,但是可以找到很多不相关的网页数据。

这时候迁移学习就会很有用,因为可能实际情况就是这样,我们无法收集太多想要的数据,但是存在很多不直接相关的其他数据。

其实在现实生活中我们会做迁移学习(有点像类比的思想)。

这里用漫画家的生活对应到研究生的生活。漫画家要画漫画,研究生要跑实验等。

1.3 迁移学习的概述

我们主要把迁移学习分为四大类。
在迁移学习中,有一些target data,就是和你的任务有直接关系的数据;
还有很多source data,是和你现在的任务没有直接关系的数据。

现在有一个我们想要做的task,有一些跟这个task有关的数据叫做target data,有一些跟这个task无关的data,这个data叫做source data。这个target data有可能是有label的,也有可能是没有label的,这个source data有可能是有label的,也有可能是没有label的,所以现在我们就有四种可能,所以之后我们会分这四类来讨论。

1.3.1 第一类迁移学习

我们先看下target data和source data都是有标签的情况。

这种情况下我们可以做什么事情呢,一件事情是模型的微调(Fine-tuning),另一件事情是多任务学习(Multitask Learning)。

  • 1) 模型微调

那现在我们假设target data跟source data都同时有label的情况下,可以的做的事情是:最常见的事情就是:fine-tuning你的model。

在现在的task里面,target data( x t , y t x^t,y^t xt,yt)和source data( x s , y s x^s,y^s xs,ys)都是有label的,但是我们通常是假设说:现在target data的数据量是非常少的(如果target data量是很多的话,你就当做一般的machine learning 来train你的model就好了,你也不需要做什么迁移学习),source data是很多的。虽然source data跟我们现在考虑的task没有关系,但我们想知道说:在target data很少的情况下,有一大推不相关的source data到底有么有可能会有帮助。

如果你今天的target data的量很少,少到只有几个example而已,这个就叫做one-shot learning。这样的task的例子是:在语音上最典型的例子就是speaker adaption,target data是某一个人的声音,但是这个人的声音你不太有可能有太多的label data(可能对你的machine 说了三句话),但是source data有一大堆的audio data,它是来自于不同人的。你当然不可能直接去用target data去train一个语音辨识系统,这样一定会坏掉的。所以你会希望说:这个有好几w小时的source data在这个task里面有什么帮助。

处理方式是非常直觉的,那你的source data直接去train一个model,然后接下来fine tune这个model通过target data。可能会遇到的challenge:source data非常的少,所以你在target data train出一个好的model,然后在source data上做train,可能就坏掉了。

这里面的问题是target data数据量很少,所以我们需要特殊的处理方法。一个比较常见的方法叫保守训练(conservative training)。

  • 保守训练

有一个技巧叫做:conservative training,你现在有大量的source data,(比如说:在语音辨识里面就是很多不同speaker的声音),那你拿来做neural network。target data是某个speaker的声音,如果你直接拿这些去train的话就坏掉了。你可以在training的时候加一些constraint(regularization),让新的model跟旧的model不要差太多。你会希望新的model的output跟旧的model的output在看同一笔data的时候越接近越好。或者说新的model跟旧的model L2-Norm差距越小越好(防止overfitting的情形)

  • 层迁移

另外的一个方法是layer transfer,你现在用source data train好了一个model,把这个model的某几个layer拿出来copy到新的model里面
。接下来用source data只去用没有copy的layer(可能你只保留一个layer没有copy),这样的好处就是source data只需要考虑非常少的参数,这样就可以避免overfitting的情形。当然之后你的source data够多了,那之后可能还是要fine-tune整个model。

哪些layer应该被transfer,哪些layer不应该去transfer呢?有趣的是在不同的task上面需要被transfer的layer往往是不一样的。比如说在语音辨识上面,我们通常是copy the last few layers(最后几层)。同样的发音方式,因为口腔结果略有差异,得到的声音是不一样的。neural network前几层做的事情是从这个声音讯号里面得知现在说话人的发音方式,根据发音方式就可以得到说的词汇。所以从这个角度来看,从发音方式到辨识结果,也就是neural network后面几层是跟语者是每一关系的,所以它是可以被copy的。不一样的是从声音讯号到发音方式这一段可能每个人都是不一样的。

所以在做语音辨识的时候,常见的做法是把neural network的后几层是copy。但是在image的时候发现是不一样的,在image的时候是copy前面几层,只train最后几层。

在image的时候你会发现数说,当你source domain上learn了一network,你learn到CNN通常前几层做的就是deceide最简单的事情(比如前几层做的就是decide有么有直线,有么有简单的几何图形)。所以在image上面前几层learn的东西,它是可以被transfer到其他的task上面。而最后几层learn的东西往往是没有办法transfer到其他的东西上面去。所以在做影像处理的时候反而是会copy前面几层。

这是一个image在layer transfer上的实验,
120多wimage分成source跟target,分法是按照class来分的(500 class归为source data,500classes归为target data)。横轴的意思是:我们在做迁移学习的时候copy了几个layer(copy 0个layer,就是说完全没有做迁移学习),纵轴时候top-1 accuracy,越高越好。

假设source跟target是没关系的,把这个Imagenet分为source data跟target data的时候,把自然界的东西通通当成source,target都是人造的东西,这样的迁移学习会有什么样的影响。如果source data跟target data是差很多的,那在做迁移学习的时候,你的性能会掉的非常多(如果只是copy前面几个layer的话,性能仍然跟没有跟copy是持平的)。这意味着说:即使source domain跟target domain是非常不一样的,在neural network的第一个layer,他们仍然做的事情仍然可能是一样的。绿色的这条线:假设我前面几个layer的参数random会坏掉了。

  • 2) 多任务学习

接下来我们介绍下多任务学习(Multitask Learning),多任务学习跟fine tuning不同是:在fine tuning里面我们care target domain做的好不好,那在多任务学习里面我们同时care target domain跟source domain做的好不好。

我们现在有多个不同的任务,我们希望机器能同时学会做好这几个不同的任务。

比如说你要训练某个人打篮球,同时要训练他唱、跳、Rap。

我们希望NN也能做到这件事情。

其实我们今天用deep learning base方法的话,它特别适合拿来做这种多任务学习,因为你可以说:假设有两个不同的task用的同样的feature(都做影像辨识),我learn一个neural network,中间会分叉出来一部分network去处理taskA,一部分network去处理taskB。这么做的好处是:你的taskA跟taskB他们在前面几个layer会是共用的(有比较多的data,会有比较好的性能)。这样做的前提是:这两个task有没有共通性,是不是可以共用前面几个layer。

在这种神经网络的架构设计上可以是像上面这种。这里假设任务A和任务B可以共用同一组输入特征。就是这两个NN,它们前面几层是共用的,但是在某个隐藏层会产生两个分支,一条产生的是任务A的分支,另一条是任务B的。

那如果这两个任务的输入特征都不能共用呢,我们就可以采用上面的设计,在这两个NN中对不同的输入特征做一些转换,然后丢到共用的网络层中去,再从共用的层中分两个分支出来。

如果可以选择适当的不同的任务合在一起的话,是可以有帮助的。
什么样的任务可能有帮助呢,举例来说,现在在做语音识别的时候,我们不仅让机器学会某国语言的语音识别,我们让机器学会多国语言的。

多任务学习一个很成功的例子就是多语言的语音辨识,假设你现在手上有一大堆不同语言的data(法文,中文,英文等),那你在train你的model的时候,同时可以辨识这五种不同的语言。这个model前面几个layer他们会共用参数,后面几个layer每一个语言可能会有自己的参数,这样做是合理的。虽然是不同的语言,但是都是人类所说的,所以前面几个layer它们可能是share同样的咨询,共用同样的参数。

此时,多任务学习就会有帮助。

还有一种是input没有办法确定,两个不同task的input都用不同的neural network把它transfer到同一个domain上去,在同一个domain上你在apply不同的neural network,一条路去做taskA,一条路去做taskB。如果在这样的task下你也迁移学习,就算tasKA跟taskB的input完全不一样,如果你觉得中间几个layer有共同的地方,你还是可以用这样的model架构来处理。

在translation你也可以拥同样的事情,假设你今天要做中翻英,也要做中翻日,你也把这两个model一起train。在一起train的时候无论是中翻英还是中翻日,你都要把中文的data先做process,那一部分neural network就可以是两种不同语言的data。

在过去收集了十几种语言,把它们两两之间互相做transfer,做了一个很大N*N的tabel,每一个task都有进步。所以目前发现大部分task,不同人类的语言就算你觉得它们不是非常像,但是它们之间都是可以transfer。

这边举得例子是从欧洲语言去transfer中文,横轴是中文的data,纵轴是character error rate。假设你一开始用中文train一个model,data很少,error rate很大,随着data越来越多,error rate就可以压到30以下。但是今天如果你有一大堆的欧洲语言,你把这些欧洲语言跟中文一起去做multitask train,用这个欧洲语言的data来帮助中文model前面几层让它train更好。你会发现说:在中文data很少的情况下,你有做迁移学习,你就可以得到比较好的性能。随着中文data越多的时候,中文本身性能越好,就算是中文100小时借用一些从欧洲语言对这个变化也是有微幅帮助的。所以这边的好处是说:假设你做多任务学习的时候,你会发现你有100多个小时跟有50小时以内,如果你有做迁移学习的话,你只需要1/2以下的data就可以跟有两倍的data做的一样好

这里是文献上的实验的例子,纵轴是错误率,横轴是中文语言识别训练的数据量。
从实验结果看到,如果仅让机器学中文的话,就是蓝色的线,它达到红线交点处的错误率需要的中文数据量会超过同时与欧洲语言一起学习的数据量。并且可以看到橙色的曲线是在蓝色曲线的下方,说明效果更加好。

  • 渐进神经网络

常常有人会担心说:迁移学习会不会有负面的效应,这是会有可能,如果两个task不像的话,你的transfer 就是negative的。但是有人说:总是思考两个task到底之间能不能transfer,这样很浪费时间。所以就会有progressive neural networks。

progressive network neural其实是很新的做法(2016年的paper)。我先train一个task1,train好以后它的参数就fix住,那现在我们要做task2,但是task2它的每一个hidden layer都会去接前一个task1的某一个hidden layer的output。所以在train的时候好处就是:task1跟task2非常不像,首先task1的data不会去动到task2的model,所以task1一定不会比原来更差。task2去借用task1的参数,但是它可以把这些参数直接设为0,这样也不会影响task2的性能。task3也是做一样的事情,task3会同时从task1和task2的hidden layer得到information。

1.3.2 第二类迁移学习

上面介绍的都是source data和target data有标签的情况,那如果只是source data有标签,target data无标签呢。这种类型也有两种情况,第一种是领域对抗性训练(Domain Adversarial Training),第二种是零次学习(Zero-shot Learning)。第二种情况是第二部分代码实现的内容。

  • 1)领域对抗性训练(Domain Adversarial Training)

这种情况的前提是他们有相同的任务,在概念上你可以把有标签的source data当成训练数据,把无标签的target data当成测试数据,但是这样的效果肯定是很差的,因为它们的分布不同。

假设今天要做手写数字识别,你有有标签的MNIST的数据,但是你要识别的对象是无标签的来自MNIST-M的数据,在MNIST-M中的数字甚至是彩色的,它的数据样本分布和原来的MNIST分布不一样。

所以需要特别的处理。Domain-adversarial training就是干这件事的。Domain-adversarial training可以看成GAN的一种。它想要把source data和target data转换到同样的领域上,让它们有同样的分布。

如果我们没有对数据做任何处理,单纯的拿source data来训练一个分类器,它输入是一个图像,输出是该图形的类别。那今天得到的特征分布可能是下面这样子。

MNIST的数据它是蓝色的点,确实可以看到它们分成一群一群的,把几群数据的点拿出来看的话,得到的结果可能是左边的样子,能区分出4,0和1。 但是把和MNIST分布不同的MNIST-M手写数字的图片丢到这个分类器中去,这些不一样的图片,它们的特征分布可能像红点一样。可以看到,红点和蓝点根本没有交集。
如果今天这个NN无法用同样的特征表示这两种数据,那么就会无法得到好的分类结果。

怎么办呢

我们希望在一个NN中,前面几个网络层做的事是特征抽取,如图1所示,也就是说,希望这个特征抽取器能把不同领域的source data和target data都转成同样的特征。

图1 Feature Extractor:特征提取器

也就是我们希望说,红点和蓝点的分布不是上面这样,而是像下面混合在一起。

那怎么让我们这个特征抽取器做到这件事情呢。

这里需要引入一个领域的分类器(domain classifier),如图2所示,就像我们做GAN的时候引入的鉴别器。它也是一个神经网络。

图2 Domain Classifier领域的分类器

Domain-adversarial training可以看成GAN的一种。它想要把source data和target data转换到同样的领域上,让它们有同样的分布。

这个领域分类器的作用是,要侦测出现在特征抽取器输出的特征是属于哪个领域的(来自哪个分布的)。现在特征抽取器要做的事情是尽量骗过这个领域分类器,而后者是尽量防止被骗。

特征抽取器要做的是去除source 领域和target 领域不一样的地方,让提取出来的特征分布是很接近的,可以骗过领域分类器。

但是如果只有这两个神经网络是不够的。因为绿色的特征抽取器可以轻易的骗过红色的分类器,只要它不管输入是什么,只把所有的输出都变成0就可以了。

所以需要引入另外一个东西叫标签预测器(Label predictor)的东西。

图3 Label predictor:标签预测器

现在特征抽取器不仅要骗过分类器,还要让预测器尽量有准确的预测结果。这是一个很大的神经网络,但是这三个不同的部分有不同的目标。

预测器想要正确的分类输入的图片,分类器想要正确分别输入是来自哪个分布。它们都只能看到特征抽取器抽取后的特征。

抽取器一方面希望可以促使预测器做的好,另一方面要防止分类器做的好。

那么要怎么做呢?

一样用梯度下降来训练,红色的分类器部分要调整参数,去让分辨领域的结果越正确越好;蓝色的预测器需要调参数,让标签的预测正确率越高越好;如图4所示梯度反向传播过程。

这两者不一样的地方在于,当分类器要求绿色的抽取器去调整参数以满足以及的目标时,绿色的抽取器会尽量满足它的要求;还当红色的神经网络要求绿色的神经网络调整参数的时候,红色的网络会故意乘以− 1 -1−1,以防止分类器做的好。

最后红色的神经网路会无法做好分类,但是它必须要努力挣扎,它需要从绿色的NN给的不好的特征里面尽量去区分它们的领域。这样才能迫使绿色的NN产生红色的NN无法分辨的特征。难点就在于让红色的NN努力挣扎而不是很快放弃。

图4 Domain Adversarial Training梯度反向传播过程

  • 2)零样本学习(Zero-shot Learning)

零样本学习(Zero-shot Learning)说的是source data和target data它们的任务都不相同。
在zero-shot learning里面,它的difine又更加严格一点。它的difine是:今天在source data和target data里面,它的task是不一样的。

比如说在影像上面(你可能要分辨猫跟狗),你的source data可能有猫的class,也有狗的class。但是你的target data里面image是草泥马的样子,在source data里面是从来没有出现过草泥马的,如果machine看到草泥马,就未免有点强人所难了吧。但是这个task在语音上很早就有solution了,其实语音是常常会遇到zero-shot learning的问题。

target data中需要正确找出草泥马,但是source data中都没出现过草泥马,那要怎么做这件事情呢
我们先看下语音识别里面是怎么做的,语音识别一直都有训练数据(source data)和测试数据(target data)是不同任务的问题。 很有可能在测试数据中出现的词汇,在训练数据中从来没有出现过。语音识别在处理这个问题的时候,做法是找出比词汇更小的单位。通常语音识别都是拿音位(phoneme,可以理解为音标)做为单位。

如果把词汇都转成音位,在识别的时候只去识别音位,然后再把音位转换为词汇的话就可以解决训练数据和测试数据不一样的问题。
假如我们把不同的word都当做一个class的话,那本来在training的时候跟testing的时候就有可能看到不同的词汇。你的testing data本来就有一些词汇是在training的时候是没有看过的。

在影像上我们可以把每一个class用它的attribute来表示,也就是说:你有一个database,这个database里面会有所以不同可能的class跟它的特性。假设你要辨识的是动物,但是你training data跟testing data他们的动物是不一样的。但是你有一个database,这个database告诉你说:每一种动物它是有什么样的特性。比如狗就是毛茸茸,四只脚,有尾巴;鱼是有尾巴但不是毛茸茸,没有脚。

这个attribute要更丰富,每一个class都要有不一样的attribute(如果两个class有相同的attribute的话,方法会fail)。那在training的时候,我们不直接辨识说:每一张image是属于哪一个class,而是去辨识说:每一张image里面它具备什么样的attribute。所以你的neural network target就是说:看到猩猩的图,就要说:这是一个毛茸茸的动物,没有四只脚,没有尾巴。看到狗的图就要说:这是毛茸茸的动物,有四只脚,有尾巴。

那在testing的时候,就算今天来了你从来没有见过的image,也是没有关系的。你今天neural network target也不是说:input image它是哪一种动物,而是input这一张image它是具有什么样的attribute。所以input你从来没有见过的动物,你只要把它的attribute长出来,然后你就查表看说:在database里面哪一种动物它的attribute跟你现在model output最接近。有时可能没有一摸一样的也是没有关系的,看谁最接近,那个动物就是你要找的。

那有时候你的attribute可能非常的复杂(attribute dimension非常大),你可以做attribute embedding。也就是说现在有一个embedding space,把training data每一个image都通过一个transform,变成一个embedding space上的一个点。然后把所有的attribute也都变成embedding space上的一个点,这个 g ( ∗ ) g(*) g() f ( ∗ ) f(*) f()都可能是neural network,那training的时候希望f跟g越接近越好。那在testing的时候如果有一张没有看过的image,你就可以说这张image attribute embedding以后跟哪个attribute最像,那你就可以知道它是什么样的image。

image跟attribute都可以描述为vector,要做的事情就是把attribute跟image都投影到同一个空间里面。也就是说:你可以想象成是对image的vector,也就是图中的x,跟attribute的vector,也就是图中的y都做降维,然后都降到同一个dimension。所以你把x通过一个function f都变成embedding space上的vector,把y通过另外一个function g也都变成embedding space上的vector。

但是咋样找这个f跟g呢?你可以说f跟g就是neural network。input一张image它变成一个vector,或者input attribute 变成一个vector。training target你希望说:假设我们已经知道 y 1 y^1 y1 x 1 x^1 x1的attribute, y 2 y^2 y2 x 2 x^2 x2的attribute,那你就希望说找到一个f跟g,它可以让 x 1 x^1 x1 y 1 y^1 y1投影到embedding space以后越接近越好, x 2 x^2 x2 y 2 y^2 y2投影到embedding space以后越接近越好。

那现在把f跟g找出来了,那现在假如有一张你从来没见过的image x 3 x^3 x3在你的testing data里面,它也可以透过这个f变成embedding space上面的一个vector,接下来你就可以说这个embedding vector它跟 y 3 y^3 y3最接近,那 y 3 y^3 y3就是它的attribute

又是你会遇到一个问题,如果我没有database呢?我根本不知道每一个动物的attribute是什么,肿么办呢?那你可以借用word vector。我们知道word vector的每一个dimension就代表了现在word某种attribute。所以你不一定需要一个datbase去告诉你说:每一个动物的attribute是什么。假设你有一组word vector,这组word vector里面你知道每一个动物对应的word vector,那你可以把你的attribute直接换成word vector,再做跟刚才一样的embedding就结束了。

假设我们的train的query是要让 x n x^n xn通过f、跟 y n y^n yn通过g之后的距离越接近越好。这样子的话是有问题的,这样你的model只会learn到说:它把所有不同的x跟所有不同的y都投影同一个点,这样子距离最好。所以你的loss function这样定其实是不行的,所以你要稍微重新设计一下你的loss function。前面这个loss function只有考虑到 x n x^n xn y n y^n yn越接近越好,但没有考虑
x n x^n xn跟另一个 y n y^n yn,它的距离应该被拉大。

max里面两个的element分别是0,k-f( x n x^n xn)跟g( y n y^n yn)的inner product,加上一个max(m不等于n)里面的f( x n x^n xn)跟g( y m y^m ym)的inner product。这个k是自己difine的margin(一个constant,在train的时候自己difine)

这个max的两个element一个是0,一个是max f ( x n ) ∗ g ( y m ) f(x^n)*g(y^m) f(xn)g(ym)。它会从0跟这个式子中选一个最大的,所以这一项的最小值就是0。什么时候会等于0呢?当你另外一项小于0的时候,这个loss就会是0。所以今天 k − f ( x n ) ∗ g ( y n ) k-f(x^n)*g(y^n) kf(xn)g(yn)的inner product 加上 m a x m ≠ n f ( x n ) ∗ g ( y m ) max_{m\neq n}f(x^n)*g(y^m) maxm=nf(xn)g(ym)的inner product小于0的时候,这一项会是zero loss,整理一下得到下面的这个式子 f ( x n ) g ( y n ) − m a x m ≠ n f ( x n ) ∗ g ( y m ) f(x^n)g(y^n)-max_{m\neq n}f(x^n)*g(y^m) f(xn)g(yn)maxm=nf(xn)g(ym)的inner product小于k的时候是zero loss。这一项也和解释为:当 f ( x n ) f(x^n) f(xn) g ( y n ) g(y^n) g(yn)的inner product大于另外一项(y不是 y n y^n yn里面找一个m,这个 y m y^m ym x n x^n xn是最接近的)

如果 x n x^n xn y n y^n yn之间的inner product大过所有其它的 y m y^m ym x n x^n xn之间的inner product,而且要大过一个margin k。

还有另外一个简单的Zero-Shot learning的方法叫做convex combination of semantic embedding。这个方法是说:我们也不要做什么learning,假设我们现在有一个语音辨识系统,有一个word vector,这两个是从网络上下载下来的,就可以做这件事情。

我把一张图丢到neural network里面去,它的output没有办法决定是哪一个class,但它觉得有0.5的几率是lion,有0.5的几率是tiger。接下来你在去找lion跟tiger的word vector,然后把lion跟tiger的word vector得到新的vector(用1:1的比例混合,0.5V(tiger)+0.5V(lion)),那你再看哪一个word的vector跟这个混合之后的结果最接近。假设是liger最接近,那这个东西就是liger(狮虎)

以下是这个的实验结果,也是蛮惊人的。我们来比一下人类跟机器的差别,第一张图,CNN判别说是sea lion(海狮),DeViSE没有得到好的结果,ConSE判别为各种sea lion。

在training的时候,machine看过如何把英文翻译成韩文,知道咋样把韩文翻译为英文,知道咋样把英文翻译为日文,知道咋样把日文翻译为英文。但是它从来没有看过日文翻译韩文的data,但是可以翻,但是它从来没有看过韩文翻译日文的data,但是可以翻。

为什么zero-shot在这个task上是可行的呢?如果你今天用同一个model做了不同语言之间的translation以后,machine可以学到的事情是:对不同语言的input 句子都可以project到同一个space上面

我们现在根据我们learn好得translation,那个translation有一个encoder,它会把你input的句子变成vector,decoder根据这个vector解回一个句子,就是翻译的结果。那今天我们把不同语言都丢到这个encoder里面让它变成vector的话,那这些不同语言的不同句子在这个space上面有什么不一样的关系呢?

它发现说今天有日文、英文、韩文这三个句子,这三个句子讲的是同一件事情,通过encoder embedding以后再space上面其实是差不多的位置。在左边这个图上面不同的颜色代表说:不同语言的用一个意思。所以你这样说:machine发明了一个新语言也是可以接受的,如果你把这个embedding space当做一个新的语言的话。machine做的是:发现可一个sequence language,每一种不同的语言都先要先转成它知道的sequence language,在用这个sequence language转为另外一种语言。

所以今天就算是某一个翻译task ,你的input语言和output语言machine没有看过,它也可以透过这种自己学出来的sequence language来做translation。

一些paper给予参考。
More about Zero-shot learning

  • Mark Palatucci, Dean Pomerleau, Geoffrey E. Hinton, Tom M. Mitchell, “Zero-shot Learning with Semantic Output Codes”, NIPS 2009

  • Zeynep Akata, Florent Perronnin, Zaid Harchaoui and Cordelia Schmid, “Label-Embedding for Attribute-Based Classification”, CVPR 2013

  • Andrea Frome, Greg S. Corrado, Jon Shlens, Samy Bengio, Jeff Dean, Marc’Aurelio Ranzato, Tomas Mikolov, “DeViSE: A Deep Visual-Semantic Embedding Model”, NIPS 2013

  • Mohammad Norouzi, Tomas Mikolov, Samy Bengio, Yoram Singer, Jonathon Shlens, Andrea Frome, Greg S. Corrado, Jeffrey Dean, “Zero-Shot Learning by Convex Combination of Semantic Embeddings”, arXiv preprint 2013

  • Subhashini Venugopalan, Lisa Anne Hendricks, Marcus Rohrbach, Raymond Mooney, Trevor Darrell, Kate Saenko, “Captioning Images with Diverse Objects”, arXiv preprint 2016

1.3.3 第三类迁移学习

  • 自我学习

自我学习(Self-taught learning)其实和半监督学习很像,都是有少量的有标签数据,和非常多的无标签数据。但是与半监督学习有个很大的不同是,有标签数据可能和无标签数据是没有关系的。

1.3.4 第四类迁移学习

  • 自学成簇

如果target data和source data都是无标签的话,可以用Self-taught Clustering来做。
可以用无标签的source data,可以学出一个较好的特征表示,再用这个较好的特征表示用在聚类上,就可以得到较好的结果。

第二部分:领域对抗性训练(Domain Adversarial Training)代码实现

2.1 项目描述

本作业的任务是迁移学习中的领域对抗性训练(Domain Adversarial Training)。

也就是左下角的那一块。

Domain Adaptation是让模型可以在训练时只需要 A dataset label,不需要 B dataset label 的情况下提高 B dataset 的准确率。 (A dataset & task 接近 B dataset & task)也就是给定真实图片 & 标签以及大量的手绘图片,请设计一种方法使得模型可以预测出手绘图片的标签是什么。


2.2 数据集介绍

这次的任务是源数据: 真实照片,目标数据: 手画涂鸦。
我们必须让model看过真实照片以及标签,尝试去预测手画涂鸦的标签为何。
资料位于’data/data58171/real_or_drawing.zip’

  • Training : 5000 张真实图片 + label, 32 x 32 RGB
  • Testing : 100000 张手绘图片,28 x 28 Gray Scale
  • Label: 总共需要预测 10 个 class。
  • 资料下载下来是以 0 ~ 9 作为label
    特别注意一点: 这次的源数据和目标数据的图片都是平衡的,你们可以使用这个资料做其他事情。

项目要求

  • 禁止手动标记label或在网上寻找label
  • 禁止使用pre-trained model

数据准备

项目传送门

3 代码实现

3.1 数据集查看

!unzip -d work data/data75815/real_or_drawing.zip # 解压缩real_or_drawing数据集

  • 1
  • 2
  inflating: work/real_or_drawing/test_data/0/44725.bmp  

IOPub data rate exceeded.
The notebook server will temporarily stop sending output
to the client in order to avoid crashing it.
To change this limit, set the config variable
`--NotebookApp.iopub_data_rate_limit`.

Current values:
NotebookApp.iopub_data_rate_limit=1000000.0 (bytes/sec)
NotebookApp.rate_limit_window=3.0 (secs)



  inflating: work/__MACOSX/real_or_drawing/test_data/0/._06044.bmp  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
# 导入相关库
import os
import cv2
import paddle
import numpy as np
from PIL import Image
import paddle.nn as nn
import matplotlib.pyplot as plt
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

展示一下训练集


def no_axis_show(img, title='', cmap=None):
  # imshow, 縮放模式為nearest。
  fig = plt.imshow(img, interpolation='nearest', cmap=cmap)
  # 不要显示axis
  fig.axes.get_xaxis().set_visible(False)
  fig.axes.get_yaxis().set_visible(False)
  plt.title(title)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

#标签映射
titles = ['horse', 'bed', 'clock', 'apple', 'cat', 'plane', 'television', 'dog', 'dolphin', 'spider']
plt.figure(figsize=(18, 18))
for i in range(10):
  plt.subplot(1, 10, i+1)
  fig = no_axis_show(plt.imread(f'work/real_or_drawing/train_data/{i}/{500*i}.bmp'), title=titles[i])
#  work/real_or_drawing/train_data/1/566.bmp
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这里插入图片描述

展示一下测试集

plt.figure(figsize=(18, 18))
for i in range(10):
    plt.subplot(1, 10, i + 1)
    fig = no_axis_show(plt.imread(f'work/real_or_drawing/test_data/0/0000{i}.bmp'), title='none')

  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

3 Special Domain Knowledge

3.2 Special Domain Knowledge

预处理source data

因为大家涂鸦的时候通常只会画轮廓,我们可以根据这点将source data做点边缘侦测处理,让source data更像target data一点。
Canny Edge Detection
算法这边不赘述,只教大家怎么用。若有兴趣欢迎参考wiki或这里。
cv2.Canny使用非常方便,只需要两个参数: low_threshold, high_threshold。

cv2.Canny(image, low_threshold, high_threshold)

简单来说就是当边缘值超过high_threshold,我们就确定它是edge。如果只有超过low_threshold,那就先判断一下再决定是不是edge。

以下我们直接拿source data做做看。

titles = ['horse', 'bed', 'clock', 'apple', 'cat', 'plane', 'television', 'dog', 'dolphin', 'spider']
plt.figure(figsize=(18, 18))

original_img = plt.imread(f'work/real_or_drawing/train_data/0/464.bmp')
plt.subplot(1, 5, 1)
no_axis_show(original_img, title='original')

gray_img = cv2.cvtColor(original_img, cv2.COLOR_RGB2GRAY)
plt.subplot(1, 5, 2)
no_axis_show(gray_img, title='gray scale', cmap='gray')


canny_50100 = cv2.Canny(gray_img, 50, 100)
plt.subplot(1, 5, 3)
no_axis_show(canny_50100, title='Canny(50, 100)', cmap='gray')

canny_150200 = cv2.Canny(gray_img, 150, 200)
plt.subplot(1, 5, 4)
no_axis_show(canny_150200, title='Canny(150, 200)', cmap='gray')

canny_250300 = cv2.Canny(gray_img, 250, 300)
plt.subplot(1, 5, 5)
no_axis_show(canny_250300, title='Canny(250, 300)', cmap='gray')

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/numpy/lib/type_check.py:546: DeprecationWarning: np.asscalar(a) is deprecated since NumPy v1.16, use a.item() instead
  'a.item() instead', DeprecationWarning, stacklevel=1)
  • 1
  • 2

在这里插入图片描述

3.4 Data Process

在这里因为train_data的格式已经标注好每种图片,可以直接使用paddle.vision.datasets.DatasetFolder。所以只要使用这个API便可以做出一个datasets。在这里要是说明的是用DataFolder读取的时候有两个存放位置,这两个位置分别存放图片和标签。

此外还有数据预处理部分见下面代码:

3.4.1 数据预处理

import paddle.vision.transforms as T
from paddle.vision.datasets import DatasetFolder,ImageFolder

# 训练集预处理
def source_transform(imge):
    # 转灰色: Canny 不吃 RGB。
    img = T.to_grayscale(imge)
    # cv2 不吃 skimage.Image,因此转成np.array后再做cv2.Canny
    img = cv2.Canny(np.array(img), 170, 300)
    # 重新np.array 转回 skimage.Image
    img = Image.fromarray(np.array(img))
    # 随机水平翻转 (Augmentation)
    RHF= T.RandomHorizontalFlip(0.5)
    img = RHF(img)
    # 旋转15度内 (Augmentation),旋转后空的地方补0
    RR = T.RandomRotation(15, fill=(0,))
    img = RR(img)
    # 最后Tensor供model使用。
    tensor = T.ToTensor()

    return tensor(img)

# 测试集预处理
target_transform = T.Compose([
    # 转灰阶:
   T.Grayscale(),
    # 缩放: 因为source data是32x32,我们把target data的28x28放大成32x32。
    T.Resize((32, 32)),
    # 随机水平翻转(Augmentation)
    T.RandomHorizontalFlip(0.5),
    # 旋转15度内 (Augmentation),旋转后空的地方补0
    T.RandomRotation(15, fill=(0,)),
    # 最后Tensor供model使用。
    T.ToTensor(),
])

#调用一下数据预处理函数
original_img = Image.open(f'work/real_or_drawing/train_data/0/464.bmp')
print('原来的照片形状:',np.array(original_img).shape)

process = source_transform(original_img)
print('预处理后的照片形状:',process .shape)
print(process)

plt.subplot(1,2,1)
no_axis_show(process .numpy().squeeze(), title='process image',cmap='gray')

plt.subplot(1,2,2)
no_axis_show(original_img, title='origimal image', cmap='gray')

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
原来的照片形状: (32, 32, 3)
预处理后的照片形状: [1, 32, 32]
Tensor(shape=[1, 32, 32], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[[0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         ...,
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.]]])

/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/cbook/__init__.py:2349: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  if isinstance(obj, collections.Iterator):
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/cbook/__init__.py:2366: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  return list(data) if isinstance(data, collections.MappingView) else data
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/numpy/lib/type_check.py:546: DeprecationWarning: np.asscalar(a) is deprecated since NumPy v1.16, use a.item() instead
  'a.item() instead', DeprecationWarning, stacklevel=1)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在这里插入图片描述

3.4.2 数据加载器定义

# 生成数据集
source_dataset = DatasetFolder('work/real_or_drawing/train_data', transform=source_transform) # DatasetFolder 用于读取训练集,读取的时候图片和标签
target_dataset = DatasetFolder('work/real_or_drawing/test_data', transform=target_transform) # ImageFolder 用于读取测试集,读取的时候只有图片

# 数据加载器定义
source_dataloader = paddle.io.DataLoader(source_dataset, batch_size=50, shuffle=True)
target_dataloader = paddle.io.DataLoader(target_dataset, batch_size=50, shuffle=True)
test_dataloader = paddle.io.DataLoader(target_dataset, batch_size=100, shuffle=False)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
%matplotlib inline
# 展示生成并经过预处理的的source_dataset和source_loader
print('=============source_dataset=============')
#由于使用了DatasetFolder,训练集这里有图片和标签两个参数image,label
for image, label in source_dataset:      
    print('image shape: {}, label: {}'.format(image.shape,label))
    print('训练集数量:',len(source_dataset))
    print('图片:',image)
    print('标签:',label)
    plt.imshow(image.numpy().squeeze(),cmap='gray')
    break

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
=============source_dataset=============
image shape: [1, 32, 32], label: 0
训练集数量: 5000
图片: Tensor(shape=[1, 32, 32], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[[0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         ...,
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.]]])
标签: 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述

#source_loader的信息    
print('=============source_dataloader=============')
for batch_id, (data,label) in enumerate(source_dataloader):
    print('一个batch的图片:',data.shape)    # 索引[0]存放图片
    print('一个batch的标签个数:',label.shape)   #索引[1]存放标签
    print('图片:',data[0].shape) 
    break

# no_axis_show(x_data.numpy().squeeze(),title='process image', cmap='gray')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
=============source_dataloader=============
一个batch的图片: [50, 1, 32, 32]
一个batch的标签个数: [50]
图片: [1, 32, 32]
  • 1
  • 2
  • 3
  • 4
# 展示生成并经过预处理的target_dataset和target_dataloader
print('=============target_dataset=============')

for image_,_ in target_dataset:
    print('image shape: {}'.format(image_.shape))
    print('测试集数量:',len(target_dataset))
    plt.imshow(image_.numpy().squeeze(),cmap='gray')
    print('图片:',image_)
    break

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
=============target_dataset=============
image shape: [1, 32, 32]
测试集数量: 100000
图片: Tensor(shape=[1, 32, 32], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
       [[[0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         ...,
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.]]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述

#target_dataloader的信息    
print('=============target_dataloader=============')
for batch_id, (data_1,label_1) in enumerate(target_dataloader):
    # print('一个batch的图片:',data[0].shape)
    print('一个batch的图片:',data_1.shape)
    print('一张图片的形状:',data_1[0].shape) 
    print(label_1)

    break
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
=============target_dataloader=============
一个batch的图片: [50, 1, 32, 32]
一张图片的形状: [1, 32, 32]
Tensor(shape=[50], dtype=int64, place=CUDAPinnedPlace, stop_gradient=True,
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
  • 1
  • 2
  • 3
  • 4
  • 5

3.5 搭建三个模型

这里的原理参考本文的1.3.2 第二类迁移学习的领域对抗性训练(Domain Adversarial Training)。

  • Feature Extractor: 典型的VGG-like叠法。
  • Label Predictor :MLP到尾
  • Domain Classifier: MLP到尾。

我们希望在一个NN中,前面几个网络层做的事是特征抽取,如图1所示,也就是说,希望这个特征抽取器能把不同领域的source data和target data都转成同样的特征。

图1 Feature Extractor:特征提取器

那怎么让我们这个特征抽取器做到这件事情呢。
这里需要引入一个领域的分类器(domain classifier),如图2所示,就像我们做GAN的时候引入的鉴别器。它也是一个神经网络。

图2 Domain Classifier领域的分类器

Domain-adversarial training可以看成GAN的一种。它想要把source data和target data转换到同样的领域上,让它们有同样的分布。

这个领域分类器的作用是,要侦测出现在特征抽取器输出的特征是属于哪个领域的(来自哪个分布的)。现在特征抽取器要做的事情是尽量骗过这个领域分类器,而后者是尽量防止被骗。

特征抽取器要做的是去除source 领域和target 领域不一样的地方,让提取出来的特征分布是很接近的,可以骗过领域分类器。

但是如果只有这两个神经网络是不够的。因为绿色的特征抽取器可以轻易的骗过红色的分类器,只要它不管输入是什么,只把所有的输出都变成0就可以了。

所以需要引入另外一个东西叫标签预测器(Label predictor)的东西。

图3 Label predictor:标签预测器

现在特征抽取器不仅要骗过分类器,还要让预测器尽量有准确的预测结果。这是一个很大的神经网络,但是这三个不同的部分有不同的目标。

预测器想要正确的分类输入的图片,分类器想要正确分别输入是来自哪个分布。它们都只能看到特征抽取器抽取后的特征。

抽取器一方面希望可以促使预测器做的好,另一方面要防止分类器做的好。

那么要怎么做呢?详见下面的模型训练部分。

3.5.1 搭建模型

class FeatureExtractor(nn.Layer):
    '''
    从图片中抽取特征
    input [batch_size ,1,32,32]
    output [batch_size ,512]
    '''

    def __init__(self):
        super(FeatureExtractor, self).__init__()

        self.conv = nn.Sequential(                               
            nn.Conv2D(in_channels=1, out_channels=64, kernel_size=3, padding=1,  stride=1),  # [batch_size ,64,32,32] (32-3+2*1)/1 + 1
            nn.BatchNorm2D(64),
            nn.ReLU(),
            nn.MaxPool2D(kernel_size=2),  # [batch_size ,64,16,16]

            nn.Conv2D(64, 128, 3, 1, 1),  # [batch_size ,128,16,16]
            nn.BatchNorm2D(128),
            nn.ReLU(),
            nn.MaxPool2D(2),  # [batch_size ,128,8,8]

            nn.Conv2D(128, 256, 3, 1, 1),  # [batch_size ,256,8,8]
            nn.BatchNorm2D(256),
            nn.ReLU(),
            nn.MaxPool2D(2),  # [batch_size ,256,4,4]

            nn.Conv2D(256, 256, 3, 1, 1),  # [batch_size ,256,4,4]
            nn.BatchNorm2D(256),
            nn.ReLU(),
            nn.MaxPool2D(2),  # [batch_size ,256,2,2]

            nn.Conv2D(256, 512, 3, 1, 1),  # [batch_size ,512,2,2]
            nn.BatchNorm2D(512),
            nn.ReLU(),
            nn.MaxPool2D(2),  # [batch_size ,512,1,1]
            nn.Flatten()      # [batch_size ,512]
        )

    def forward(self, x):
        x = self.conv(x) # [batch_size ,256]
        return x

class LabelPredictor(nn.Layer):
    '''
    预测图像是什么动物
    '''
    def __init__(self):
        super(LabelPredictor, self).__init__()

        self.layer = nn.Sequential(
            nn.Linear(512, 512),
            nn.ReLU(),

            nn.Linear(512,512),
            nn.ReLU(),

            nn.Linear(512, 10),
        )

    def forward(self, h):
        c = self.layer(h)
        return c

class DomainClassifier(nn.Layer):
    '''预测时手绘还是真实图片'''
    def __init__(self):
        super(DomainClassifier, self).__init__()

        self.layer = nn.Sequential(
            nn.Linear(512, 512),
            nn.BatchNorm1D(512),
            nn.ReLU(),

            nn.Linear(512, 512),
            nn.BatchNorm1D(512),
            nn.ReLU(),

            nn.Linear(512, 512),
            nn.BatchNorm1D(512),
            nn.ReLU(),

            nn.Linear(512, 512),
            nn.BatchNorm1D(512),
            nn.ReLU(),

            nn.Linear(512, 1),
        )

    def forward(self, h):
        y = self.layer(h)
        return y

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

3.5.2 模型配置

import paddle.optimizer as optim
# 模型实例化
feature_extractor = FeatureExtractor()
label_predictor = LabelPredictor()
domain_classifier = DomainClassifier()
class_criterion = nn.CrossEntropyLoss()
domain_criterion = nn.BCEWithLogitsLoss()
# 定义优化器
optimizer_F = optim.Adam(learning_rate=0.0001, parameters=feature_extractor.parameters())
optimizer_C = optim.Adam(learning_rate=0.0001, parameters=label_predictor.parameters())
optimizer_D = optim.Adam(learning_rate=0.0001, parameters=domain_classifier.parameters())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3.5.3 开始训练

用梯度下降来训练,红色的分类器部分要调整参数,去让分辨领域的结果越正确越好;蓝色的预测器需要调参数,让标签的预测正确率越高越好;

这两者不一样的地方在于,当分类器要求绿色的抽取器去调整参数以满足以及的目标时,绿色的抽取器会尽量满足它的要求;还当红色的神经网络要求绿色的神经网络调整参数的时候,红色的网络会故意乘以-1,以防止分类器做的好。

最后红色的神经网路会无法做好分类,但是它必须要努力挣扎,它需要从绿色的NN给的不好的特征里面尽量去区分它们的领域。这样才能迫使绿色的NN产生红色的NN无法分辨的特征。难点就在于让红色的NN努力挣扎而不是很快放弃。

# 定义训练函数
import paddle
def train_epoch(source_dataloader, target_dataloader, lamb):
    '''
      Args:
        source_dataloader: source data的dataloader
        target_dataloader: target data的dataloader
        lamb: 调控adversarial的loss系数。
    '''
    running_D_loss, running_F_loss = 0.0, 0.0
    total_hit, total_num = 0.0, 0.0

    for i, ((source_data, source_label), (target_data,_)) in enumerate(zip(source_dataloader, target_dataloader)):
        mixed_data = paddle.concat([source_data, target_data], axis=0)
        domain_label = paddle.zeros([source_data.shape[0] + target_data.shape[0], 1]).cuda()
        # 设定source data的label为1
        domain_label[:source_data.shape[0]] = 1

        # Step 1 : 训练Domain Classifier
        feature = feature_extractor(mixed_data)
        # 因为我们在Step 1不需要训练Feature Extractor,所以把feature detach
        #这样可以把特征抽取过程的函数从当前计算图分离,避免loss backprop传递过去。
        domain_logits = domain_classifier(feature.detach())
        loss = domain_criterion(domain_logits, domain_label)
        running_D_loss += loss.numpy().tolist()[0]
        loss.backward()
        optimizer_D.step()

        # Step 2 : 训练Feature Extractor和Domain Classifier
        class_logits = label_predictor(feature[:source_data.shape[0]])
        domain_logits = domain_classifier(feature)
        # loss为原本的class CE - lamb * domain BCE,相減的原因是我们希望特征能够使得domain_classifier分不出来输入的图片属于哪个领域
        loss = class_criterion(class_logits, source_label) - lamb * domain_criterion(domain_logits, domain_label)
        running_F_loss += loss.numpy().tolist()[0]
        loss.backward()
        optimizer_F.step()
        optimizer_C.step()
        #训练了一轮,清空所有梯度信息
        optimizer_D.clear_grad()
        optimizer_F.clear_grad()
        optimizer_C.clear_grad()
        # return class_logits,source_label  #测试
        bool_eq = paddle.argmax(class_logits, axis=1) == source_label.squeeze()
        total_hit += np.sum(bool_eq.numpy()!=0)
        total_num += source_data.shape[0]
        print(i, end='\r')

    return running_D_loss / (i+1), running_F_loss / (i+1), total_hit / total_num
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
# 训练250 epochs
train_D_loss_history,train_F_loss_history,train_acc_history = [], [], []
for epoch in range(250):
    train_D_loss, train_F_loss, train_acc = train_epoch(source_dataloader, target_dataloader, lamb=0.1)

    train_D_loss_history.append(train_D_loss)
    train_F_loss_history.append(train_F_loss)
    train_acc_history.append(train_acc)

    
    epoch = epoch + 1
    if epoch % 50 == 0:
        paddle.save(feature_extractor.state_dict(), "ckp/{}ckp_feature_extractor.pdparams".format(str(epoch)))
        paddle.save(label_predictor.state_dict(), "ckp/{}ckp_label_predictor.pdparams".format(str(epoch)))

    print('epoch {:>3d}: train D loss: {:6.4f}, train F loss: {:6.4f}, acc {:6.4f}'.format(epoch, train_D_loss,
                                                                                           train_F_loss, train_acc))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/nn/layer/norm.py:648: UserWarning: When training, we now always track global mean and variance.
  "When training, we now always track global mean and variance.")


4
epoch   1: train D loss: 0.0602, train F loss: 1.9422, acc 0.3076
epoch   2: train D loss: 0.0049, train F loss: 1.6367, acc 0.4228
epoch   3: train D loss: 0.0018, train F loss: 1.5082, acc 0.4792
epoch   4: train D loss: 0.0014, train F loss: 1.4486, acc 0.4972
epoch   5: train D loss: 0.0008, train F loss: 1.3823, acc 0.5112
epoch   6: train D loss: 0.0009, train F loss: 1.3342, acc 0.5418
epoch   7: train D loss: 0.0032, train F loss: 1.3113, acc 0.5424
epoch   8: train D loss: 0.0009, train F loss: 1.2453, acc 0.5628
epoch   9: train D loss: 0.0005, train F loss: 1.2192, acc 0.5810
epoch  10: train D loss: 0.0015, train F loss: 1.1950, acc 0.5864
epoch  11: train D loss: 0.0006, train F loss: 1.1552, acc 0.5998
epoch  12: train D loss: 0.0008, train F loss: 1.1179, acc 0.6072
epoch  13: train D loss: 0.0007, train F loss: 1.1018, acc 0.6216
epoch  14: train D loss: 0.0015, train F loss: 1.0852, acc 0.6246
epoch  15: train D loss: 0.0021, train F loss: 1.0540, acc 0.6296
epoch  16: train D loss: 0.0010, train F loss: 1.0261, acc 0.6388
epoch  17: train D loss: 0.0003, train F loss: 1.0171, acc 0.6492
epoch  18: train D loss: 0.0015, train F loss: 0.9744, acc 0.6632
epoch  19: train D loss: 0.0014, train F loss: 0.9480, acc 0.6722
epoch  20: train D loss: 0.0014, train F loss: 0.9167, acc 0.6800
epoch  21: train D loss: 0.0002, train F loss: 0.8936, acc 0.6908
epoch  22: train D loss: 0.0009, train F loss: 0.8802, acc 0.6982
epoch  23: train D loss: 0.0019, train F loss: 0.8519, acc 0.7062
epoch  24: train D loss: 0.0010, train F loss: 0.8368, acc 0.7070
epoch  25: train D loss: 0.0005, train F loss: 0.7954, acc 0.7202
epoch  26: train D loss: 0.0003, train F loss: 0.7908, acc 0.7262
epoch  27: train D loss: 0.0010, train F loss: 0.7365, acc 0.7450
epoch  28: train D loss: 0.0016, train F loss: 0.7246, acc 0.7532
epoch  29: train D loss: 0.0008, train F loss: 0.7114, acc 0.7592
epoch  30: train D loss: 0.0007, train F loss: 0.6864, acc 0.7604
epoch  31: train D loss: 0.0006, train F loss: 0.6516, acc 0.7696
epoch  32: train D loss: 0.0010, train F loss: 0.6332, acc 0.7822
epoch  33: train D loss: 0.0001, train F loss: 0.6057, acc 0.7950
epoch  34: train D loss: 0.0005, train F loss: 0.6050, acc 0.7884
epoch  35: train D loss: 0.0006, train F loss: 0.5850, acc 0.7950
epoch  36: train D loss: 0.0016, train F loss: 0.5416, acc 0.8152
epoch  37: train D loss: 0.0017, train F loss: 0.5277, acc 0.8166
epoch  38: train D loss: 0.0006, train F loss: 0.5302, acc 0.8254
epoch  39: train D loss: 0.0013, train F loss: 0.5254, acc 0.8146
epoch  40: train D loss: 0.0004, train F loss: 0.4854, acc 0.8320
epoch  41: train D loss: 0.0013, train F loss: 0.4715, acc 0.8376
epoch  42: train D loss: 0.0014, train F loss: 0.4278, acc 0.8530
epoch  43: train D loss: 0.0002, train F loss: 0.4356, acc 0.8448
epoch  44: train D loss: 0.0001, train F loss: 0.4458, acc 0.8458
epoch  45: train D loss: 0.0044, train F loss: 0.4122, acc 0.8602
epoch  46: train D loss: 0.0014, train F loss: 0.3909, acc 0.8664
epoch  47: train D loss: 0.0013, train F loss: 0.3957, acc 0.8624
epoch  48: train D loss: 0.0008, train F loss: 0.3608, acc 0.8742
epoch  49: train D loss: 0.0010, train F loss: 0.3660, acc 0.8786
epoch  50: train D loss: 0.0007, train F loss: 0.3325, acc 0.8882
epoch  51: train D loss: 0.0019, train F loss: 0.3478, acc 0.8778
epoch  52: train D loss: 0.0009, train F loss: 0.3341, acc 0.8852
epoch  53: train D loss: 0.0006, train F loss: 0.3039, acc 0.9010
epoch  54: train D loss: 0.0012, train F loss: 0.2959, acc 0.9008
epoch  55: train D loss: 0.0002, train F loss: 0.2983, acc 0.8950
epoch  56: train D loss: 0.0005, train F loss: 0.2971, acc 0.8996
epoch  57: train D loss: 0.0009, train F loss: 0.2892, acc 0.8990
epoch  58: train D loss: 0.0023, train F loss: 0.2761, acc 0.9054
epoch  59: train D loss: 0.0004, train F loss: 0.2786, acc 0.9032
epoch  60: train D loss: 0.0019, train F loss: 0.2656, acc 0.9116
epoch  61: train D loss: 0.0003, train F loss: 0.2362, acc 0.9194
epoch  62: train D loss: 0.0010, train F loss: 0.2206, acc 0.9254
epoch  63: train D loss: 0.0003, train F loss: 0.2259, acc 0.9220
epoch  64: train D loss: 0.0017, train F loss: 0.2328, acc 0.9194
epoch  65: train D loss: 0.0013, train F loss: 0.2261, acc 0.9250
epoch  66: train D loss: 0.0012, train F loss: 0.2230, acc 0.9204
epoch  67: train D loss: 0.0008, train F loss: 0.2319, acc 0.9186
epoch  68: train D loss: 0.0006, train F loss: 0.2173, acc 0.9234
epoch  69: train D loss: 0.0012, train F loss: 0.1922, acc 0.9350
epoch  70: train D loss: 0.0009, train F loss: 0.2050, acc 0.9314
epoch  71: train D loss: 0.0007, train F loss: 0.2082, acc 0.9268
epoch  72: train D loss: 0.0020, train F loss: 0.2050, acc 0.9282
epoch  73: train D loss: 0.0004, train F loss: 0.1977, acc 0.9336
epoch  74: train D loss: 0.0011, train F loss: 0.1825, acc 0.9380
epoch  75: train D loss: 0.0009, train F loss: 0.1944, acc 0.9318
epoch  76: train D loss: 0.0005, train F loss: 0.1531, acc 0.9476
epoch  77: train D loss: 0.0020, train F loss: 0.1747, acc 0.9394
epoch  78: train D loss: 0.0011, train F loss: 0.1770, acc 0.9414
epoch  79: train D loss: 0.0013, train F loss: 0.1659, acc 0.9450
epoch  80: train D loss: 0.0005, train F loss: 0.1398, acc 0.9564
epoch  81: train D loss: 0.0016, train F loss: 0.1518, acc 0.9490
epoch  82: train D loss: 0.0009, train F loss: 0.1757, acc 0.9430
epoch  83: train D loss: 0.0008, train F loss: 0.1563, acc 0.9474
epoch  84: train D loss: 0.0013, train F loss: 0.1456, acc 0.9500
epoch  85: train D loss: 0.0009, train F loss: 0.1638, acc 0.9440
epoch  86: train D loss: 0.0009, train F loss: 0.1440, acc 0.9500
epoch  87: train D loss: 0.0019, train F loss: 0.1669, acc 0.9442
epoch  88: train D loss: 0.0015, train F loss: 0.1476, acc 0.9466
epoch  89: train D loss: 0.0010, train F loss: 0.1321, acc 0.9544
epoch  90: train D loss: 0.0004, train F loss: 0.1200, acc 0.9604
epoch  91: train D loss: 0.0002, train F loss: 0.1213, acc 0.9564
epoch  92: train D loss: 0.0023, train F loss: 0.1255, acc 0.9574
epoch  93: train D loss: 0.0019, train F loss: 0.1385, acc 0.9538
epoch  94: train D loss: 0.0012, train F loss: 0.1302, acc 0.9568
epoch  95: train D loss: 0.0003, train F loss: 0.1298, acc 0.9564
epoch  96: train D loss: 0.0006, train F loss: 0.1399, acc 0.9510
epoch  97: train D loss: 0.0012, train F loss: 0.1222, acc 0.9584
epoch  98: train D loss: 0.0004, train F loss: 0.1152, acc 0.9606
epoch  99: train D loss: 0.0013, train F loss: 0.1205, acc 0.9576
epoch 100: train D loss: 0.0007, train F loss: 0.1330, acc 0.9540
epoch 101: train D loss: 0.0009, train F loss: 0.1182, acc 0.9584
epoch 102: train D loss: 0.0004, train F loss: 0.1220, acc 0.9576
epoch 103: train D loss: 0.0018, train F loss: 0.1200, acc 0.9586
epoch 104: train D loss: 0.0020, train F loss: 0.1315, acc 0.9568
epoch 105: train D loss: 0.0015, train F loss: 0.1316, acc 0.9580
epoch 106: train D loss: 0.0017, train F loss: 0.0903, acc 0.9704
epoch 107: train D loss: 0.0012, train F loss: 0.1014, acc 0.9672
epoch 108: train D loss: 0.0014, train F loss: 0.1124, acc 0.9618
epoch 109: train D loss: 0.0004, train F loss: 0.1216, acc 0.9578
epoch 110: train D loss: 0.0004, train F loss: 0.1092, acc 0.9602
epoch 111: train D loss: 0.0008, train F loss: 0.1045, acc 0.9638
epoch 112: train D loss: 0.0012, train F loss: 0.0952, acc 0.9666
epoch 113: train D loss: 0.0005, train F loss: 0.0881, acc 0.9676
epoch 114: train D loss: 0.0002, train F loss: 0.0974, acc 0.9648
epoch 115: train D loss: 0.0008, train F loss: 0.0987, acc 0.9690
epoch 116: train D loss: 0.0011, train F loss: 0.0947, acc 0.9690
epoch 117: train D loss: 0.0016, train F loss: 0.1037, acc 0.9646
epoch 118: train D loss: 0.0011, train F loss: 0.0996, acc 0.9660
epoch 119: train D loss: 0.0014, train F loss: 0.1144, acc 0.9586
epoch 120: train D loss: 0.0025, train F loss: 0.1095, acc 0.9632
epoch 121: train D loss: 0.0007, train F loss: 0.1155, acc 0.9602
epoch 122: train D loss: 0.0009, train F loss: 0.0962, acc 0.9686
epoch 123: train D loss: 0.0007, train F loss: 0.0937, acc 0.9672
epoch 124: train D loss: 0.0005, train F loss: 0.0772, acc 0.9730
epoch 125: train D loss: 0.0007, train F loss: 0.0894, acc 0.9678
epoch 126: train D loss: 0.0006, train F loss: 0.1166, acc 0.9566
epoch 127: train D loss: 0.0018, train F loss: 0.0985, acc 0.9666
epoch 128: train D loss: 0.0006, train F loss: 0.0839, acc 0.9728
epoch 129: train D loss: 0.0006, train F loss: 0.0820, acc 0.9704
epoch 130: train D loss: 0.0007, train F loss: 0.0876, acc 0.9700
epoch 131: train D loss: 0.0018, train F loss: 0.1065, acc 0.9636
epoch 132: train D loss: 0.0004, train F loss: 0.0819, acc 0.9738
epoch 133: train D loss: 0.0019, train F loss: 0.0799, acc 0.9724
epoch 134: train D loss: 0.0004, train F loss: 0.0859, acc 0.9702
epoch 135: train D loss: 0.0010, train F loss: 0.0867, acc 0.9698
epoch 136: train D loss: 0.0016, train F loss: 0.0910, acc 0.9704
epoch 137: train D loss: 0.0002, train F loss: 0.1005, acc 0.9642
epoch 138: train D loss: 0.0007, train F loss: 0.0727, acc 0.9736
epoch 139: train D loss: 0.0009, train F loss: 0.0823, acc 0.9704
epoch 140: train D loss: 0.0015, train F loss: 0.0944, acc 0.9676
epoch 141: train D loss: 0.0003, train F loss: 0.0759, acc 0.9738
epoch 142: train D loss: 0.0001, train F loss: 0.0743, acc 0.9732
epoch 143: train D loss: 0.0015, train F loss: 0.0741, acc 0.9744
epoch 144: train D loss: 0.0002, train F loss: 0.0868, acc 0.9656
epoch 145: train D loss: 0.0006, train F loss: 0.0825, acc 0.9712
epoch 146: train D loss: 0.0018, train F loss: 0.0748, acc 0.9764
epoch 147: train D loss: 0.0006, train F loss: 0.0870, acc 0.9698
epoch 148: train D loss: 0.0002, train F loss: 0.0735, acc 0.9728
epoch 149: train D loss: 0.0011, train F loss: 0.0853, acc 0.9692
epoch 150: train D loss: 0.0003, train F loss: 0.0852, acc 0.9704
epoch 151: train D loss: 0.0001, train F loss: 0.0686, acc 0.9764
epoch 152: train D loss: 0.0001, train F loss: 0.0560, acc 0.9808
epoch 153: train D loss: 0.0005, train F loss: 0.0799, acc 0.9714
epoch 154: train D loss: 0.0005, train F loss: 0.0861, acc 0.9690
epoch 155: train D loss: 0.0009, train F loss: 0.0749, acc 0.9724
epoch 156: train D loss: 0.0001, train F loss: 0.0599, acc 0.9798
epoch 157: train D loss: 0.0004, train F loss: 0.0757, acc 0.9756
epoch 158: train D loss: 0.0003, train F loss: 0.0875, acc 0.9712
epoch 159: train D loss: 0.0000, train F loss: 0.0642, acc 0.9806
epoch 160: train D loss: 0.0005, train F loss: 0.0608, acc 0.9784
epoch 161: train D loss: 0.0011, train F loss: 0.0721, acc 0.9734
epoch 162: train D loss: 0.0006, train F loss: 0.0801, acc 0.9738
epoch 163: train D loss: 0.0006, train F loss: 0.0742, acc 0.9742
epoch 164: train D loss: 0.0009, train F loss: 0.0618, acc 0.9798
epoch 165: train D loss: 0.0017, train F loss: 0.0726, acc 0.9758
epoch 166: train D loss: 0.0003, train F loss: 0.0716, acc 0.9760
epoch 167: train D loss: 0.0013, train F loss: 0.0640, acc 0.9780
epoch 168: train D loss: 0.0002, train F loss: 0.0503, acc 0.9816
epoch 169: train D loss: 0.0010, train F loss: 0.0575, acc 0.9812
epoch 170: train D loss: 0.0001, train F loss: 0.0796, acc 0.9744
epoch 171: train D loss: 0.0004, train F loss: 0.0678, acc 0.9768
epoch 172: train D loss: 0.0004, train F loss: 0.0745, acc 0.9744
epoch 173: train D loss: 0.0014, train F loss: 0.0636, acc 0.9782
epoch 174: train D loss: 0.0009, train F loss: 0.0678, acc 0.9756
epoch 175: train D loss: 0.0001, train F loss: 0.0729, acc 0.9726
epoch 176: train D loss: 0.0003, train F loss: 0.0516, acc 0.9810
epoch 177: train D loss: 0.0021, train F loss: 0.0553, acc 0.9790
epoch 178: train D loss: 0.0010, train F loss: 0.0585, acc 0.9768
epoch 179: train D loss: 0.0007, train F loss: 0.0653, acc 0.9778
epoch 180: train D loss: 0.0008, train F loss: 0.0620, acc 0.9810
epoch 181: train D loss: 0.0007, train F loss: 0.0640, acc 0.9786
epoch 182: train D loss: 0.0004, train F loss: 0.0536, acc 0.9806
epoch 183: train D loss: 0.0010, train F loss: 0.0660, acc 0.9774
epoch 184: train D loss: 0.0001, train F loss: 0.0514, acc 0.9820
epoch 185: train D loss: 0.0000, train F loss: 0.0621, acc 0.9800
epoch 186: train D loss: 0.0013, train F loss: 0.0641, acc 0.9798
epoch 187: train D loss: 0.0001, train F loss: 0.0683, acc 0.9766
epoch 188: train D loss: 0.0007, train F loss: 0.0658, acc 0.9776
epoch 189: train D loss: 0.0018, train F loss: 0.0558, acc 0.9806
epoch 190: train D loss: 0.0014, train F loss: 0.0591, acc 0.9776
epoch 191: train D loss: 0.0005, train F loss: 0.0425, acc 0.9830
epoch 192: train D loss: 0.0011, train F loss: 0.0487, acc 0.9828
epoch 193: train D loss: 0.0008, train F loss: 0.0726, acc 0.9720
epoch 194: train D loss: 0.0002, train F loss: 0.0641, acc 0.9770
epoch 195: train D loss: 0.0008, train F loss: 0.0723, acc 0.9750
epoch 196: train D loss: 0.0001, train F loss: 0.0562, acc 0.9792
epoch 197: train D loss: 0.0007, train F loss: 0.0475, acc 0.9834
epoch 198: train D loss: 0.0001, train F loss: 0.0640, acc 0.9784
epoch 199: train D loss: 0.0000, train F loss: 0.0717, acc 0.9734
epoch 200: train D loss: 0.0002, train F loss: 0.0489, acc 0.9854
epoch 201: train D loss: 0.0007, train F loss: 0.0449, acc 0.9828
epoch 202: train D loss: 0.0008, train F loss: 0.0636, acc 0.9782
epoch 203: train D loss: 0.0004, train F loss: 0.0478, acc 0.9836
epoch 204: train D loss: 0.0001, train F loss: 0.0437, acc 0.9838
epoch 205: train D loss: 0.0004, train F loss: 0.0643, acc 0.9776
epoch 206: train D loss: 0.0009, train F loss: 0.0748, acc 0.9754
epoch 207: train D loss: 0.0002, train F loss: 0.0565, acc 0.9786
epoch 208: train D loss: 0.0007, train F loss: 0.0408, acc 0.9860
epoch 209: train D loss: 0.0005, train F loss: 0.0539, acc 0.9812
epoch 210: train D loss: 0.0002, train F loss: 0.0664, acc 0.9760
epoch 211: train D loss: 0.0015, train F loss: 0.0585, acc 0.9786
epoch 212: train D loss: 0.0001, train F loss: 0.0535, acc 0.9830
epoch 213: train D loss: 0.0007, train F loss: 0.0559, acc 0.9804
epoch 214: train D loss: 0.0001, train F loss: 0.0540, acc 0.9804
epoch 215: train D loss: 0.0000, train F loss: 0.0428, acc 0.9852
epoch 216: train D loss: 0.0001, train F loss: 0.0394, acc 0.9850
epoch 217: train D loss: 0.0003, train F loss: 0.0374, acc 0.9870
epoch 218: train D loss: 0.0001, train F loss: 0.0509, acc 0.9834
epoch 219: train D loss: 0.0003, train F loss: 0.0584, acc 0.9812
epoch 220: train D loss: 0.0016, train F loss: 0.0606, acc 0.9806
epoch 221: train D loss: 0.0002, train F loss: 0.0448, acc 0.9836
epoch 222: train D loss: 0.0001, train F loss: 0.0524, acc 0.9814
epoch 223: train D loss: 0.0002, train F loss: 0.0523, acc 0.9832
epoch 224: train D loss: 0.0002, train F loss: 0.0496, acc 0.9814
epoch 225: train D loss: 0.0016, train F loss: 0.0456, acc 0.9828
epoch 226: train D loss: 0.0001, train F loss: 0.0361, acc 0.9872
epoch 227: train D loss: 0.0002, train F loss: 0.0523, acc 0.9802
epoch 228: train D loss: 0.0013, train F loss: 0.0622, acc 0.9796
epoch 229: train D loss: 0.0001, train F loss: 0.0493, acc 0.9824
epoch 230: train D loss: 0.0003, train F loss: 0.0561, acc 0.9800
epoch 231: train D loss: 0.0003, train F loss: 0.0531, acc 0.9804
epoch 232: train D loss: 0.0000, train F loss: 0.0433, acc 0.9862
epoch 233: train D loss: 0.0000, train F loss: 0.0563, acc 0.9798
epoch 234: train D loss: 0.0012, train F loss: 0.0604, acc 0.9804
epoch 235: train D loss: 0.0011, train F loss: 0.0522, acc 0.9814
epoch 236: train D loss: 0.0001, train F loss: 0.0466, acc 0.9824
epoch 237: train D loss: 0.0001, train F loss: 0.0538, acc 0.9814
epoch 238: train D loss: 0.0002, train F loss: 0.0416, acc 0.9854
epoch 239: train D loss: 0.0009, train F loss: 0.0415, acc 0.9854
epoch 240: train D loss: 0.0015, train F loss: 0.0541, acc 0.9806
epoch 241: train D loss: 0.0007, train F loss: 0.0419, acc 0.9858
epoch 242: train D loss: 0.0004, train F loss: 0.0399, acc 0.9862
epoch 243: train D loss: 0.0005, train F loss: 0.0492, acc 0.9832
epoch 244: train D loss: 0.0017, train F loss: 0.0524, acc 0.9822
epoch 245: train D loss: 0.0001, train F loss: 0.0443, acc 0.9846
epoch 246: train D loss: 0.0014, train F loss: 0.0475, acc 0.9820
epoch 247: train D loss: 0.0007, train F loss: 0.0539, acc 0.9798
epoch 248: train D loss: 0.0001, train F loss: 0.0484, acc 0.9842
epoch 249: train D loss: 0.0007, train F loss: 0.0350, acc 0.9880
epoch 250: train D loss: 0.0003, train F loss: 0.0419, acc 0.9842
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
#保存模型
paddle.save(feature_extractor.state_dict(), "model/feature_extractor_final.pdparams")
paddle.save(label_predictor.state_dict(), "model/label_predictor_final.pdparams")
  • 1
  • 2
  • 3

3.5.4 可视化训练过程

#分开绘制三条曲线
epochs = range(epoch)
# 模型训练可视化
def draw_process(title,color,iters,data,label):
    plt.title(title, fontsize=20)  # 标题
    plt.xlabel("epochs", fontsize=15)  # x轴
    plt.ylabel(label, fontsize=15)  # y轴
    plt.plot(iters, data,color=color,label=label)   # 画图
    plt.legend()
    plt.grid()
    plt.savefig('{}.jpg'.format(title))
    plt.show()

# Domain Classifier train loss
draw_process("train D loss","green",epochs,train_D_loss_history,"loss") 
# Feature Extrator train loss
draw_process("train F loss","green",epochs,train_F_loss_history,"loss") 
# Label Predictor的train accuracy
draw_process("train acc","red",epochs,train_acc_history,"accuracy") 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4 模型预测

在测试集上执行预测

4.1 预测测试集结果

result = []
label_predictor.eval()
feature_extractor.eval()
for i, (test_data, _) in enumerate(test_dataloader):
    test_data = test_data.cuda()

    class_logits = label_predictor(feature_extractor(test_data))

    x = paddle.argmax(class_logits, axis=1).cpu().detach().numpy()
    result.append(x)

import pandas as pd
result = np.concatenate(result)

# Generate your submission
df = pd.DataFrame({'id': np.arange(0,len(result)), 'label': result})
df.to_csv('DaNN_submission.csv',index=False)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
# 统计预测的标签数量,10种图片的预测数量如下:
print(df.iloc[:,1].value_counts())
  • 1
  • 2
3    22983
5    19346
8    18487
1    10888
4     9249
7     7511
2     5101
9     4398
0     1230
6      807
Name: label, dtype: int64
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.2 展示预测结果

展示前一百幅的结果

labels = iter(df['label'][0:100])
def f_names():
    for i in range(100):
        yield 'work/real_or_drawing/test_data/0/{:05}.bmp'.format(i)
        
names = iter(f_names())


for j in range(10):
    plt.figure(figsize=(18, 18))
    for i in range(10):
        plt.subplot(1, 10, i + 1)
        name = next(names)
        label = next(labels)
        fig = no_axis_show(plt.imread(name),title=titles[label])
eld 'work/real_or_drawing/test_data/0/{:05}.bmp'.format(i)
        
names = iter(f_names())


for j in range(10):
    plt.figure(figsize=(18, 18))
    for i in range(10):
        plt.subplot(1, 10, i + 1)
        name = next(names)
        label = next(labels)
        fig = no_axis_show(plt.imread(name),title=titles[label])

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5 总结分析

本次项目共进行了三次训练:第一次训练200个epochs,第二次训练125个epochs,第三次250个epochs。
可以通过以下的曲线对比,模型的训练可视化如下,可以发现:

  • 1) 三次训练中特征抽取器(Feature Extractor)的train F loss曲线都呈现下降趋势。
  • 2) 而epoch=125,和epoch=200时,领域的分类器(Domain Classifier)的train D loss曲线呈现增大的趋势,可能原因是训练不稳定;epoch=250,领域的分类器(Domain Classifier)的train D loss曲线逐渐收敛。
  • 3)三次的训练,标签预测器(Label Lredictor)的acc曲线在上升,最终acc都在0.98左右。

特征抽取器就是不断抽取一些领域分类器不一样的特征为了能骗过它。并且他们这样相生相克就是为了模型能有很好的预测能力,这在标签预测器的acc曲线充分地表现了出来。因此,这就是迁移学习——Domain-adversarial training的根本所在!(Domain-adversarial training可以看成GAN的一种。它想要把source data和target data转换到同样的领域上,让它们有同样的分布。)

  • 125epochs
    训练过程不稳定



  • 200epochs



  • 250epochs



模型的前100张测试集结果对比:
就前100张预测图片来看,三种预测结果差别还挺大的,因为没有标签,无法得知预测结果好坏。

125epochs:


200epochs:


250epochs:


6 参考文献&文章&代码

[1] 李宏毅机器学习
[2] https://blog.csdn.net/weixin_44673043/article/details/114858094
[3] https://helloai.blog.csdn.net/article/details/104484924
[4]https://datawhalechina.github.io/leeml-notes/#/chapter30/chapter30

作者介绍

百度AI Studio个人主页, 我在AI Studio上获得白银等级,点亮2个徽章,来互关呀~
CSDN:https://i.csdn.net/#/user-center/profile?spm=1011.2124.3001.5111
交流qq:3207820044

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/532163
推荐阅读
相关标签
  

闽ICP备14008679号