大佬的笔记较好,拷贝留档学习
学习网址
https://aistudio.baidu.com/aistudio/education/group/info/1978
假设现在要做猫和狗的分类器,我们需要一样标签数据告诉机器哪些是猫,哪些是狗。 同时,假设现在有一些与猫和狗没有直接关系的数据,这里说是没有直接关系,并不是说是完全没有关系。就是说有一些关系,但又不是直接相关的。
假设现在有自然界真实存在的老虎和大象的图片,那老虎和大象对分辨猫和狗会有帮助吗。
或者说我们有一些卡通动画中的猫和狗图像,但不是真实存在的,有没有帮助呢。
迁移学习把任务A开发的模型作为初始点,重新使用在为任务B开发模型的过程中。迁移学习是通过从已学习的相关任务中转移知识来改进学习的新任务。
这三个说的是,第一个是做闽南语(台湾腔)的语音识别,但是没有太多的训练数据,只有很多无直接关系的英文、普通话数据;第二是做医疗方面的图像识别,同样样本不多,但有很多其他真实动物的图像;第三个说的是在特定领域,这里是法律方面的文本分析,缺少数据,但是可以找到很多不相关的网页数据。
这时候迁移学习就会很有用,因为可能实际情况就是这样,我们无法收集太多想要的数据,但是存在很多不直接相关的其他数据。
这里用漫画家的生活对应到研究生的生活。漫画家要画漫画,研究生要跑实验等。
我们主要把迁移学习分为四大类。 在迁移学习中,有一些target data,就是和你的任务由直接关系的数据; 还有很多source data,是和你现在的任务没有直接关系的数据。
现在有一个我们想要做的task,有一些跟这个task有关的数据叫做target data,有一些跟这个task无关的data,这个data叫做source data。这个target data有可能是有label的,也有可能是没有label的,这个source data有可能是有label的,也有可能是没有label的,所以现在我们就有四种可能,所以之后我们会分这四类来讨论。
我们先看下target data和source data都是有标签的情况。
这种情况下我们可以做什么事情呢,一件事情是模型的微调(Fine-tuning),另一件事情是多任务学习(Multitask Learning)。
在现在的task里面,target data( x t , y t \ x^{t},y^{t} xt,yt)和source data
( x t , y t \ x^{t},y^{t} xt,yt)都是有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会坏掉了。
接下来我们介绍下多任务学习(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。
上面介绍的都是source data和target data有标签的情况,那如果只是source data有标签,target data无标签呢。这种类型也有两种情况,第一种是领域对抗性训练(Domain Adversarial Training),第二种是零次学习(Zero-shot Learning)。第二种情况是第二部分代码实现的内容。
这种情况的前提是他们有相同的任务,在概念上你可以把有标签的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都转成同样的特征。
在这里插入图片描述
也就是我们希望说,红点和蓝点的分布不是上面这样,而是像下面混合在一起。
那怎么让我们这个特征抽取器做到这件事情呢。
这里需要引入一个领域的分类器(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 , x 1 \ y^{1},x^{1} y1,x1
的attribute, y 2 是 x 2 \ y^2是x^2 y2是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)
还有另外一个简单的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
自我学习
自我学习(Self-taught learning)其实和半监督学习很像,都是有少量的有标签数据,和非常多的无标签数据。但是与半监督学习有个很大的不同是,有标签数据可能和无标签数据是没有关系的。
自学成簇
如果target data和source data都是无标签的话,可以用Self-taught Clustering来做。 可以用无标签的source data,可以学出一个较好的特征表示,再用这个较好的特征表示用在聚类上,就可以得到较好的结果。
链接:
抄袭大佬作业
学习AIstudio网址
飞桨