作业做到这里才真正进入了cnn的范畴。
先用最基本的循环来写forward
1 | def conv_forward_naive(x, w, b, conv_param): |
backward如图:
1 | def conv_backward_naive(dout, cache): |
然后是max pool 层
1 | def max_pool_forward_naive(x, pool_param): |
以上只是尝试最基本的CNN和max pool结构。实际使用不用这个,因为有更高效的版本。
然后用高效的版本定义了三明治层:
1 | def conv_relu_forward(x, w, b, conv_param): |
在cnn.py
中完成了三层的ConvNet
1 |
|
批量归一化相当于在每一层神经网络的激活函数前进行归一化预处理。
先写batchnorm_forward
1 | def batchnorm_forward(x, gamma, beta, bn_param): |
backword
很难,公式看图:
1 | def batchnorm_backward(dout, cache): |
另一种backword
1 | def batchnorm_backward_alt(dout, cache): |
然后把之前的FullyConnectedNet的use_batchnorm补上,之前已经写好了,不再赘述。
定义一个mask,用来生成0-1随机数,然后转化为大于某个数的布尔值,再把输入值乘上这个mask就可以得到一部分失活,一部分没有失活的神经元
1 | def dropout_forward(x, dropout_param): |
吴恩达Coursera(DeepLearning.ai)笔记和作业汇总。
历时一个多月终于把NG的五门课全部学完并且做了作业和笔记了。这里汇总一下:
主要讲了神经网络的基本概念,以及机器学习的梯度下降法,向量化,而后进入了浅层和深层神经网络的实现。
介绍了改善神经网络的方法,如正则化,超参数调节,优化算法等。
第一周:训练集的划分、正则化、dropout
第二周:Mini-batch、Momentum、RMS、Adam、学习率衰减
第三周:超参数的调试、BatchNorm、softmax
主要讲了机器学习中的一些策略。
主要讲了神经网络的在图像上的非常重要的应用,卷积神经网络。
第一周:padding、步长、池化、卷积
第二周:一些重要的神经网络结构,VGG、ResNet、Inception等
第三周:目标检测、Bounding Box、IOU、NMS
第四周:人脸识别和神经风格转换
主要讲了神经网络在语言领域的应用,用RNN模型
这周作业分为了两部分:
你将建立一个将人类可读日期(“2009年6月25日”)转换为机器可读日期(“2009-06-25”)的神经机器翻译(NMT)模型。 你将使用注意力机制来执行此操作,这是模型序列中最尖端的一个序列。
你将创建的模型可用于从一种语言翻译为另一种语言,如从英语翻译为印地安语。 但是,语言翻译需要大量的数据集,并且通常需要几天的GPU训练。 在不使用海量数据的情况下,为了让你有机会尝试使用这些模型,我们使用更简单的“日期转换”任务。
网络以各种可能格式(例如“1958年8月29日”,“03/30/1968”,“1987年6月24日”)写成的日期作为输入,并将它们转换成标准化的机器可读的日期(例如“1958 -08-29“,”1968-03-30“,”1987-06-24“),让网络学习以通用机器可读格式YYYY-MM-DD输出日期。
定义一些layers
1 | # Defined shared layers as global variables |
然后根据a 和 s 得到context
1 | # GRADED FUNCTION: one_step_attention |
实现model()
1 | n_a = 32 |
1 | # GRADED FUNCTION: model |
做触发关键字的检测。
X: 这里把每一段音频分为了10s,而10s内细分为了5511个小的片段,也就是Tx = 5511
Y: Ty = 1375,每个y都是一个布尔值,用来记录有没有收到触发关键字。
这里把样本分为了三种,背景音乐,正向的音频,反向的音频,合成训练示例:
合成后类似这样:
定义一个随机插入片段起始和终点位置的函数:
1 | def get_random_time_segment(segment_ms): |
然后需要判断在别的片段插入的时候,有没有被占用:
1 | # GRADED FUNCTION: is_overlapping |
生成input音频片段:
1 | # GRADED FUNCTION: insert_audio_clip |
生成y标签:
1 | # GRADED FUNCTION: insert_ones |
1 | # GRADED FUNCTION: create_training_example |
实现model()
1 | # GRADED FUNCTION: model |
这里载入预训练好的模型,不需要自己训练那么久了,
1 | model = load_model('./models/tr_model.h5') |
sequence to sequence 模型:
sequence to sequence 模型最为常见的就是机器翻译,假如这里我们要将法语翻译成英文。
对于机器翻译的序列对序列模型,如果我们拥有大量的句子语料,则可以得到一个很有效的机器翻译模型。模型的前部分使用一个编码网络来对输入的法语句子进行编码,后半部分则使用一个解码网络来生成对应的英文翻译。网络结构如下图所示:
还有输入图像,输出描述图片的句子的:
机器翻译:条件语言模型
对于机器翻译来说和之前几节介绍的语言模型有很大的相似性但也有不同之处。
在语言模型中,我们通过估计句子的可能性,来生成新的句子。语言模型总是以零向量开始,也就是其第一个时间步的输入可以直接为零向量;
在机器翻译中,包含了编码网络和解码网络,其中解码网络的结构与语言模型的结构是相似的。机器翻译以句子中每个单词的一系列向量作为输入,所以相比语言模型来说,机器翻译可以称作条件语言模型,其输出的句子概率是相对于输入的条件概率。
Beam search 算法:
这里我们还是以法语翻译成英语的机器翻译为例:
Step 1:对于我们的词汇表,我们将法语句子输入到编码网络中得到句子的编码,通过一个softmax层计算各个单词(词汇表中的所有单词)输出的概率值,通过设置集束宽度(beam width)的大小如3,我们则取前3个最大输出概率的单词,并保存起来。
Step 2:在第一步中得到的集束宽度的单词数,我们分别对第一步得到的每一个单词计算其与单词表中的所有单词组成词对的概率。并与第一步的概率相乘,得到第一和第二两个词对的概率。有3×10000个选择,(这里假设词汇表有10000个单词),最后再通过beam width大小选择前3个概率最大的输出对;
上面的集束搜索有个问题,就是因为每一项的概率都很小,所以句子越长,概率越小,因此会倾向于选择比较短的句子,这样是不太好的。
首先,为了保证不会太小而导致数值下溢,先取对数,把连乘变成求和。
然后在前面加上一个系数
$$\frac{1}{T_{y}^{\alpha}}$$
当$\alpha$ 为 1 时,就表示概率为句子长度的平均;为0时,就表示没有系数;在这里一般取$\alpha = 0.7$
集束搜索讨论:
Beam width:B的选择,B越大考虑的情况越多,但是所需要进行的计算量也就相应的越大。在常见的产品系统中,一般设置B = 10,而更大的值(如100,1000,…)则需要对应用的领域和场景进行选择。
相比于算法范畴中的搜索算法像BFS或者DFS这些精确的搜索算法,Beam Search 算法运行的速度很快,但是不能保证找到目标准确的最大值。
集束搜索算法是一种近似搜索算法,也被称为启发式搜索算法。而不是一种精确的搜索。
如果我们的集束搜素算法出现错误了要怎么办呢?如何确定是算法出现了错误还是模型出现了错误呢?此时集束搜索算法的误差分析就显示出了作用。
模型分为两个部分:
计算人类翻译的概率P(y∗|x)以及模型翻译的概率P(ŷ |x)
P(y∗|x) > P(ŷ |x):Beam search算法选择了ŷ ,但是y∗ 却得到了更高的概率,所以Beam search 算法出错了;
P(y∗|x) <= P(ŷ |x) 的情况:翻译结果y∗相比ŷ 要更好,但是RNN模型却预测P(y∗|x)
PASS
之前我们的翻译模型分为编码网络和解码网络,先记忆整个句子再翻译,这对于较短的句子效果不错,但是对于很长的句子,翻译结果就会变差。
回想当我们人类翻译长句子时,都是一部分一部分的翻译,翻译每个部分的时候也会顾及到该部分周围上下文对其的影响。同理,引入注意力机制,一部分一部分的翻译,每次翻译时给该部分及上下文不同的注意力权重以及已经译出的部分,直至翻译出整个句子。
以一个双向的RNN模型来对法语进行翻译,得到相应的英语句子。其中的每个RNN单元均是LSTM或者GRU单元。
对于双向RNN,通过前向和后向的传播,可以得到每个时间步的前向激活值和反向激活值,我们用一个符号来表示前向和反向激活值的组合。
然后得到每个输入单词的注意力权重:
计算公式为:
这里的$e^{<t,t^{\prime}>}$则是通过一层神经网络来进行计算得到的,其值取决于输出RNN中前一步的激活值$s^{<t-1>}$和输入RNN当前步的激活值$a^{<t^{\prime}>}$。我们可以通过训练这个小的神经网络模型,使用反向传播算法来学习一个对应的关系函数。
语音识别就是将一段音频转化为相应文本。
之前用音位来识别,现在 end-to-end 模型中已经不需要音位了,但是需要大量的数据常见的语音数据大小为300h、3000h或者更大。
另外一种效果较好的就是使用CTC损失函数的语音识别模型(CTC,Connectionist temporal classification)
模型会有很多个输入和输出,对于一个10s的语音片段,我们就能够得到1000个特征的输入片段,而往往我们的输出仅仅是几个单词。
在CTC损失函数中,允许RNN模型输出有重复的字符和插入空白符的方式,强制使得我们的输出和输入的大小保持一致。
触发字检测:关键词语音唤醒。
一种可以简单应用的触发字检测算法,就是使用RNN模型,将音频信号进行声谱图转化得到图像特征或者使用音频特征,输入到RNN中作为我们的输入。而输出的标签,我们可以以触发字前的输出都标记为0,触发字后的输出则标记为1。
一种简单应用的触发字检测算法,就是使用RNN模型,将音频信号进行声谱图转化音频特征,输入到RNN中作为我们的输入。而输出的标签,非触发字的输出都标记为0,触发字的输出则标记为1。
上面方法的缺点就是0、1标签的不均衡,0比1多很多。一种简单粗暴的方法就是在触发字及其之后多个目标标签都标记为1,在一定程度上可以提高系统的精确度。
]]>本周作业分为两部分:
由于词嵌入的训练计算量庞大切耗费时间长,绝大部分机器学习人员都会导入一个预训练的词嵌入模型。
本作业中,我们使用50维的 Glove 向量来表示词。导入数据:
1 | words, word_to_vec_map = read_glove_vecs('data/glove.6B.50d.txt') |
one-hot向量不擅长表示向量相似度(内积为0), Glove 向量包含了单词更多的信息,下面看看如何使用 Glove 向量计算相似度。
$$\text{CosineSimilarity(u, v)} = \frac {u . v} {||u||_2 ||v||_2} = cos(\theta)$$
分子表示两个向量的内积,分母是向量的模的乘积,θθ表示向量夹角,向量越近夹角越小,cos 值越大。
1 | # GRADED FUNCTION: cosine_similarity |
类比推理任务中需要实现”a is to b as c is to __” 比如”man is to woman as king is to queen”。我们需要找到单词 d,使得”e_b−e_a ≈ e_d−e_c”
也就是两组的差向量应该相似(仍然用 cos 来衡量)
1 | # GRADED FUNCTION: complete_analogy |
1 | def neutralize(word, g, word_to_vec_map): |
1 | def equalize(pair, bias_axis, word_to_vec_map): |
你有没有想过让你的短信更具表现力? emojifier APP将帮助你做到这一点。 所以不是写下”Congratulations on the promotion! Lets get coffee and talk. Love you!” emojifier可以自动转换为 “Congratulations on the promotion! ? Lets get coffee and talk. ☕️ Love you! ❤️”
另外,如果你对emojis不感兴趣,但有朋友向你发送了使用太多表情符号的疯狂短信,你还可以使用emojifier来回复他们。
你将实现一个模型,输入一个句子(“Let’s go see the baseball game tonight!”),并找到最适合这个句子的表情符号(⚾️)。 在许多表情符号界面中,您需要记住❤️是”heart”符号而不是”love”符号。 但是使用单词向量,你会发现即使你的训练集只将几个单词明确地与特定的表情符号相关联,你的算法也能够将测试集中相关的单词概括并关联到相同的表情符号上,即使这些词没有出现在训练集中。这使得即使使用小型训练集,你也可以建立从句子到表情符号的精确分类器映射。
在本练习中,您将从使用词嵌入的基本模型(Emojifier-V1)开始,然后构建进一步整合LSTM的更复杂的模型(Emojifier-V2)。
1 | # GRADED FUNCTION: sentence_to_avg |
1 | # GRADED FUNCTION: model |
1 | # GRADED FUNCTION: sentences_to_indices |
1 | # GRADED FUNCTION: pretrained_embedding_layer |
1 | # GRADED FUNCTION: Emojify_V2 |
本周主要讲了NLP和词嵌入的问题。
在前面学习的内容中,我们表征词汇是直接使用英文单词来进行表征的,但是对于计算机来说,是无法直接认识单词的。为了让计算机能够能更好地理解我们的语言,建立更好的语言模型,我们需要将词汇进行表征。下面是几种不同的词汇表征方式:
one-hot 表征:
在前面的一节课程中,已经使用过了one-hot表征的方式对模型字典中的单词进行表征,对应单词的位置用1表示,其余位置用0表示,如下图所示:
one-hot表征的缺点:这种方法将每个词孤立起来,使得模型对相关词的泛化能力不强。每个词向量之间的距离都一样,乘积均为0,所以无法获取词与词之间的相似性和关联性。
特征表征:词嵌入
用不同的特征来对各个词汇进行表征,相对与不同的特征,不同的单词均有不同的值。如下例所示:
这样差不多的词汇就会聚在一起:
Word Embeddings对不同单词进行了实现了特征化的表示,那么如何将这种表示方法应用到自然语言处理的应用中呢?
以下图为例,该图表示的是输入一段话,判断出人名。通过学习判断可以知道orange farmer指的应该是人,所以其对应的主语Sally Johnson就应该是人名了,所以其对应位置输出为1。
那如果把orange换成apple呢?通过词嵌入算法可以知道二者词性类似,而且后面跟着farmer,所以也能确认Robert Lin是人名。
我们继续替换,我们将apple farmer替换成不太常见的durian cultivator(榴莲繁殖员)。此时词嵌入中可能并没有durian这个词,cultivator也是不常用的词汇。这个时候怎么办呢?我们可以用到迁移学习。
学习含有大量文本语料库的词嵌入(一般含有10亿到1000亿单词),或者下载预训练好的词嵌入
将学到的词嵌入迁移到相对较小规模的训练集(例如10万词汇),这个时候就能体现出相比于使> 用one hot表示法,使用词嵌入的优势了。如果是使用one hot,那么每个单词是1×100000表> 示,而用词嵌入后,假设特征维度是300,那么只需要使用 1×300的向量表示即可。
(可选) 这一步骤就是对新的数据进行fine-tune。
词嵌入和人脸编码之间有很奇妙的联系。在人脸识别领域,我们会将人脸图片预编码成不同的编码向量,以表示不同的人脸,进而在识别的过程中使用编码来进行比对识别。词嵌入则和人脸编码有一定的相似性。
但是不同的是,对于人脸识别,我们可以将任意一个没有见过的人脸照片输入到我们构建的网络中,则可输出一个对应的人脸编码。而在词嵌入模型中,所有词汇的编码是在一个固定的词汇表中进行学习单词的编码以及其之间的关系的。
可以得到 man to woman ,正如 King to Queen。
可以通过词嵌入,计算词之间的距离,从而实现类比。
关于词相似度的计算,可以使用余弦公式。
当然也可以使用距离公式:
$$||u - v||^2$$
如下图示,左边是词嵌入矩阵,每一列表示该单词的特征向量,每一行表示所有单词在某一特征上的值的大小,这个矩阵用$E$表示,假设其维度是(300,10000)。
在原来的one-hot中每个词是维度为10000的向量,而现在在嵌入矩阵中,每个词变成了维度为300的向量。
下图展示了预测单词的方法,即给出缺少一个单词的句子:
“I want a glass of orange ___”
计算方法是将已知单词的特征向量都作为输入数据送到神经网络中去,然后经过一系列计算到达 Softmax分类层,在该例中输出节点数为10000个。经过计算juice概率最高,所以预测为
“I want a glass of orange juice”
在这个训练模式中,是通过全部的单词去预测最后一个单词然后反向传播更新词嵌表E
假设要预测的单词为W,词嵌表仍然为E,需要注意的是训练词嵌表和预测W是两个不同的任务。
如果任务是预测W,最佳方案是使用W前面n个单词构建语境。
如果任务是训练E,除了使用W前全部单词还可以通过:前后各4个单词、前面单独的一个词、前面语境中随机的一个词(这个方式也叫做 Skip Gram 算法),这些方法都能提供很好的结果。
“word2vec” 是指将词语word 变成向量vector 的过程,这一过程通常通过浅层的神经网络完成,例如CBOW或者skip gram,这一过程同样可以视为构建词嵌表E的过程”。
下图详细的展示了Skip-grams。即先假设Context(上下文)是orange,而Target(预测词)则是通过设置窗口值得到的,例如设置为紧邻的后一个单词,此时Target则为juice,设置其他窗口值可以得到其他预测词。
注意这个过程是用来构建词嵌表的,而不是为了真正的去预测,所以如果预测效果不好并不用担心。
上面在使用Softmax的时候有一个很明显的问题,那就是计算量过于繁琐,所以为了解决计算量大的问题,提出了如下图所示的方法,即Hierachical Softmax(分层的Softmax)
简单的来说就是通过使用二叉树的形式来减少运算量。
例如一些常见的单词,如the、of等就可以在很浅的层次得到,而像durian这种少用的单词则在较深的层次得到。
对于skip gram model而言,还要解决的一个问题是如何取样(选择)有效的随机词 c 和目标词 t 呢?如果真的按照自然随机分布的方式去选择,可能会大量重复的选择到出现次数频率很高的单词比如说“the, of, a, it, I, …” 重复的训练这样的单词没有特别大的意义。
如何有效的去训练选定的词如 orange 呢?在设置训练集时可以通过“负取样”的方法, 下表中第一行是通过和上面一
样的窗口法得到的“正”(1)结果,其他三行是从字典中随机得到的词语,结果为“负”(0)。通过这样的负取样法
可以更有效地去训练skip gram model.
负取样的个数k由数据量的大小而定,上述例子中为4. 实际中数据量大则 k = 2 ~ 5,数据量小则可以相对大一些k = 5 ~ 20
通过负取样,我们的神经网络训练从softmax预测每个词出现的频率变成了经典binary logistic regression问题,概率公式用 sigmoid 代替 softmax从而大大提高了速度。
选词概率的经验公式:
GloVe(Global vectors for word representation)虽然不想Word2Vec模型那样流行,但是它也有自身的优点,即简单。
这里就不介绍了,看不太懂。
情感分类就是通过一段文本来判断这个文本中的内容是否喜欢其所讨论的内容,这是NLP中最重要的模块之一。
可以看到下图中的模型先将评语中各个单词通过 词嵌表(数据量一般比较大,例如有100Billion的单词数) 转化成对应的特征向量,然后对所有的单词向量做求和或者做平均,然后构建Softmax分类器,最后输出星级评级。
但是上面的模型存在一个问题,一般而言如果评语中有像”good、excellent“这样的单词,一般都是星级评分较高的评语,但是该模型对下面这句评语就显得无能为力了:
“Completely lacking in good taste, good service, and good ambience.”
之所以上面的模型存在那样的缺点,就是因为它没有把单词的时序考虑进去,所以我们可以使用RNN构建模型来解决这种问题。
另外使用RNN模型还有另一个好处,假设测试集中的评语是这样的
“Completely absent of good taste, good service, and good ambience.”
该评语只是将lacking in替换成了absent of,而且我们即使假设absent并没有出现在训练集中,但是因为词嵌表很庞大,所以词嵌表中包含absent,所以算法依旧可以知道absent和lacking有相似之处,最后输出的结果也依然可以保持正确。
现如今机器学习已经被用到了很多领域,例如银行贷款决策,简历筛选。但是因为机器是向人们学习,所以好的坏的都会学到,例如他也会学到一些偏见或者歧视。
如下图示
当说到Man:程序员的时候,算法得出Woman:家庭主妇,这显然存在偏见。
又如Man:Doctor,算法认为Woman:Nurse。这显然也存在其实和偏见。
上面提到的例子都是性别上的歧视,词嵌入也会反映出年龄歧视、性取向歧视以及种族歧视等等。
人类在这方面已经做的不对了,所以机器应当做出相应的调整来减少歧视。
消除偏见的方法:
中和化:对每一个定义不明确的词汇,进行偏见的处理,如像doctor、babysitter这类词;通过减小这些词汇在得到的偏见趋势维度上值的大小;
均衡:将如gradmother和gradfather这种对称词对调整至babysitter这类词汇平衡的位置上,使babysitter这类词汇处于一个中立的位置,进而消除偏见。
第三个作业是用LSTM来生成爵士乐。
我们已经对音乐数据做了预处理,以”values”来表示。可以非正式地将每个”value”看作一个音符,它包含音高和持续时间。 例如,如果您按下特定钢琴键0.5秒,那么您刚刚弹奏了一个音符。 在音乐理论中,”value” 实际上比这更复杂。 特别是,它还捕获了同时播放多个音符所需的信息。 例如,在播放音乐作品时,可以同时按下两个钢琴键(同时播放多个音符生成所谓的“和弦”)。 但是这里我们不需要关系音乐理论的细节。对于这个作业,你需要知道的是,我们获得一个”values”的数据集,并将学习一个RNN模型来生成一个序列的”values”。
我们的音乐生成系统将使用78个独特的值。
模型结构如下:
这里用了3个keras函数来定义:
1 | reshapor = Reshape((1, 78)) # Used in Step 2.B of djmodel(), below |
1 | # GRADED FUNCTION: djmodel |
1 | model = djmodel(Tx = 30 , n_a = 64, n_values = 78) |
1 | opt = Adam(lr=0.01, beta_1=0.9, beta_2=0.999, decay=0.01) |
1 | m = 60 |
1 | model.fit([X, a0, c0], list(Y), epochs=100) |
1 | # GRADED FUNCTION: music_inference_model |
1 | inference_model = music_inference_model(LSTM_cell, densor, n_values = 78, n_a = 64, Ty = 50) |
1 | x_initializer = np.zeros((1, 1, 78)) |
1 | # GRADED FUNCTION: predict_and_sample |
1 | out_stream = generate_music(inference_model) |
作业2搭建了一个字符级的语言模型,来生成恐龙的名字。
模型结构
梯度裁剪
确保不会梯度爆炸
1 | ### GRADED FUNCTION: clip |
采样
现在假设你的模型已经训练好了,你需要以此生成新的字母,过程如下:
1 | # GRADED FUNCTION: sample |
函数都已经给你了
1 | # GRADED FUNCTION: optimize |
1 | # GRADED FUNCTION: model |
本周作业分为三部分:
来构建一个RNN的神经网络。
先来进行前向传播的构建,要构建这个网络,先构建每个RNN的传播单元:
RNN cell
softmax
.We will vectorize over $m$ examples. Thus, $x^{\langle t \rangle}$ will have dimension $(n_x,m)$, and $a^{\langle t \rangle}$ will have dimension $(n_a,m)$.
1 | # GRADED FUNCTION: rnn_cell_forward |
RNN forward pass
思路是:
1 | # GRADED FUNCTION: rnn_forward |
接下来构建一个LSTM的网络
遗忘门:
假设我们正在阅读一段文字中的单词,并且希望使用LSTM来跟踪语法结构,例如主语是单数还是复数。 如果主语从单个单词变成复数单词,我们需要找到一种方法来摆脱先前存储的单数/复数状态的记忆值。
在LSTM中,遗忘门让我们做到这一点:
$$\Gamma_f^{\langle t \rangle} = \sigma(W_f[a^{\langle t-1 \rangle}, x^{\langle t \rangle}] + b_f)$$
更新门:
一旦我们忘记所讨论的主题是单数的,我们需要找到一种方法来更新它,以反映新主题现在是复数。
$$\Gamma_u^{\langle t \rangle} = \sigma(W_u[a^{\langle t-1 \rangle}, x^] + b_u)$$
所以两个门结合起来可以更新单元值:
$$ \tilde{c}^{\langle t \rangle} = \tanh(W_c[a^{\langle t-1 \rangle}, x^{\langle t \rangle}] + b_c) $$
$$ c^{<t>} = \Gamma_f^{<t>} c^{<t-1>} + \Gamma_u ^{<t>} \tilde {c}^{<t>} $$
输出门:
为了决定输出,我们将使用以下两个公式:
$$ \Gamma_o^{\langle t \rangle}= \sigma(W_o[a^{\langle t-1 \rangle}, x^{\langle t \rangle}] + b_o)$$
$$ a^{\langle t \rangle} = \Gamma_o^{\langle t \rangle}* \tanh(c^{\langle t \rangle}) $$
LSTM 单元
1 | # GRADED FUNCTION: lstm_cell_forward |
Forward pass for LSTM
1 | # GRADED FUNCTION: lstm_forward |
接下来是RNN的反向传播,但是一般框架都会帮我们实现,这里看看就好了。公式也比较复杂。
RNN backward pass
1 | def rnn_cell_backward(da_next, cache): |
1 | def rnn_backward(da, caches): |
LSTM backward pass
1 | def lstm_cell_backward(da_next, dc_next, cache): |
1 | def lstm_backward(da, caches): |
第五门课讲的是序列模型,主要是对RNN算法的应用,如GRU,LSTM算法,应用在词嵌入模型,情感分类,语音识别等领域。
第一周讲的是RNN的基本算法。
序列模型用在了很多的地方,如语音识别,音乐生成,情感分类,DNA序列分析,机器翻译,视频内容检测,名字检测等等。
先讲一下NG在课程中主要用到的数学符号。
对于输入一个$x$的句子序列,可以细分为一个个的词,每一个词记为$x^{<t>}$,对应的输出$y$记为$y^{<t>}$
其中,输入x的序列长度为 $T_x$,输出$y$的序列长度为$T_y$
而针对很多个不同的序列,$X^{(i)<t>}$表示第$i$个样本的第t的词。
那么如何用数学的形式表示这个$x^{<t>}$呢?这里用到了one-hot编码,假设词表中一共有10000个词汇,那么$x^{<t>}$就是一个长度为10000的向量,在这之中只有一个维度是1,其他都是0
如果用传统的神经网络,经过一个N层的神经网络得到输出y。
效果并不是很好,因为:
所以循环神经网络采用每一个时间步来计算,输入一个$x^{<t>}$和前面留下来的记忆$a^{<t-1>}$,来得到这一层的输出$y^{<t>}$和下一层的记忆$a^{<t>}$
这里需要注意在零时刻,我们需要编造一个激活值,通常输入一个零向量,有的研究人员会使用随机的方法对该初始激活向量进行初始化。同时,上图中右边的循环神经网络的绘制结构与左边是等价的。
循环神经网络是从左到右扫描数据的,同时共享每个时间步的参数。
RNN的前向传播
前向传播公式如图,这里可以把$W_{aa},W_{ax}$合并成一项,为$W_a$,而后将$[a^{<t-1>},x^{<t>}]$合并成一项。
RNN的反向传播
定义一个loss function,然后倒回去计算。
对于RNN,不同的问题需要不同的输入输出结构。
什么是语言模型?
对于下面的例子,两句话有相似的发音,但是想表达的意义和正确性却不相同,如何让我们的构建的语音识别系统能够输出正确地给出想要的输出。也就是对于语言模型来说,从输入的句子中,评估各个句子中各个单词出现的可能性,进而给出整个句子出现的可能性。
使用RNN构建语言模型:
当我们训练得到了一个模型之后,如果我们想知道这个模型学到了些什么,一个非正式的方法就是对新序列进行采样。具体方法如下:
在每一步输出$y$时,通常使用 softmax 作为激活函数,然后根据输出的分布,随机选择一个值,也就是对应的一个字或者英文单词。
然后将这个值作为下一个单元的x输入进去(即$x^{<t>}=y^{<t−1>}$), 直到我们输出了终结符,或者输出长度超过了提前的预设值n才停止采样。
RNN存在一个梯度消失问题,如:
cat 和 cats要经过很长的一系列词汇后,才对应 was 和 were,但是我们在传递过程中$a^{<t>}$很难记住前面这么多词汇的内容,往往只和前面最近几个词汇有关而已。
当然,也有可能是每一层的梯度都很大,导致的梯度爆炸问题,不过这个问题可以通过设置阈值来解决,关键是要解决梯度消失问题。我们知道一旦神经网络层次很多时,反向传播很难影响前面层次的参数。
那么如何解决梯度消失问题了,使用GRU单元可以有效的捕捉到更深层次的连接,来改善梯度消失问题。
原本的RNN单元如图:
而GRU单元多了一个c(memory cell)变量,用来提供长期的记忆能力。
具体过程为:
完整的GRU还存在另一个门,用来控制$\bar c$和 $c^{<t-1>}$之间的联系强弱:
GRU能够让我们在序列中学习到更深的联系,长短期记忆(long short-term memory, LSTM)对捕捉序列中更深层次的联系要比GRU更加有效。
GRU只有两个门,而LSTM有三个门,分别是更新门、遗忘门、输出门:$\Gamma_u,\Gamma_f, \Gamma_o$
更新门:用来决定是否更新$\bar c^{<t>}$
遗忘门:来决定是否遗忘上一个$c^{<t-1>}$
输出门:来决定是否输出$c^{<t>}$
双向RNN(bidirectional RNNs)模型能够让我们在序列的某处,不仅可以获取之间的信息,还可以获取未来的信息。
对于下图的单向RNN的例子中,无论我们的RNN单元是基本的RNN单元,还是GRU,或者LSTM单元,对于例子中第三个单词”Teddy”很难判断是否是人名,仅仅使用前面的两个单词是不够的,需要后面的信息来进行判断,但是单向RNN就无法实现获取未来的信息。
而双向RNN则可以解决单向RNN存在的弊端。在BRNN中,不仅有从左向右的前向连接层,还存在一个从右向左的反向连接层。
与深层的基本神经网络结构相似,深层RNNs模型具有多层的循环结构,但不同的是,在传统的神经网络中,我们可能会拥有很多层,几十层上百层,但是对与RNN来说,三层的网络结构就已经很多了,因为RNN存在时间的维度,所以其结构已经足够的庞大。如下图所示:
]]>本周作业分为了两个部分:
训练FaceNet很不现实,所以模型已经都训练好了,我们只是学习一下loss函数,然后调用模型来进行简单的识别而已。
先计算triplet_loss函数,分为4步:
1 | # GRADED FUNCTION: triplet_loss |
进行单个人脸验证:
1 | # GRADED FUNCTION: verify |
进行人脸识别:
1 | # GRADED FUNCTION: who_is_it |
模型也都是训练好的了,用的是VGG-19的网络。这里只是体验一下cost function的实现罢了。
计算J_content(C,G)
$$J_{content}(C,G) = \frac{1}{4 \times n_H \times n_W \times n_C}\sum _{ \text{all entries}} (a^{(C)} - a^{(G)})^2 $$
在这过程中需要把三维的矩阵先展开成2维的矩阵进行计算(虽然不展开也是可以计算的,但是风格损失函数需要计算)
1 |
|
计算J_style(S,G)
需要把三维矩阵展开,然后转置,做矩阵乘法,才能得到相关系数矩阵
1 | # GRADED FUNCTION: gram_matrix |
$$J_{style}^{[l]}(S,G) = \frac{1}{4 \times n_{C}^{2} \times (n_H \times n_W)^2} \sum_{i=1}^{n_C} \sum_{j=1}^{n_C} (G^{(S)}_{ij} - G^{(G)} _ {ij})^{2} $$
1 | # GRADED FUNCTION: compute_layer_style_cost |
1 | # GRADED FUNCTION: total_cost |
1 | ### START CODE HERE ### (1 line) |
1 | def model_nn(sess, input_image, num_iterations = 200): |
本周讲了CNN的两个特殊应用:人脸识别和神经风格转换。
人脸识别和人脸验证不一样。
人脸验证是输入一张图片,和这个人的ID或者名字,然后根据输入的图片判断这个人是不是对应这个ID,是个1对1的问题。
人脸识别是有K个人的数据库,然后输入一张人脸的图片,不确定他是哪一位,然后输出在K个人的数据库中对应的那个人,是1对K的问题。
所以人脸识别难度更高,而且精度要求更高,因为如果每张图片都是99%的精度,那么K个人就是K倍了,所以应该有99.9%以上的精度。
人脸识别系统,通常都是只有一个人脸的样例,然后就能够成功的识别是不是这个人。这就是one shot learning,一次学习,单单通过一张照片就能识别这个人。
因此,在只有单个样本的情况下,并不能用之前的方法来实现这个识别系统。这里就需要有一个相似性函数。
similarity函数:
通过$d(img1,img2)$来表示两张图片的差异程度,如果d大于某个阈值,那么就表示差别很大,如果小于某个阈值,则认为是同一个人。
那么如何计算这个$d(img1,img2)$呢?
可以利用Siamese网络来实现。
如图,输入两张图片$x^{(1)},x^{(2)}$,经过一个卷积神经网络,去掉最后的softmax层,可以得到N维的向量,$f(x^{(1)}),f(x^{(2)})$,假设是128维,而N维的向量就相当于是对输入图片的的编码(encoding)。
然后比较这两个向量之间的差值:
$$d(x1,x2) = ||f(x1) - f(x2)||^{2}_{2}$$
如果距离$d$很小,那表示这两张图片很相近,认为是同一个人。
如果距离$d$很大,那么表示这两张图片差别很大,不是同一个人。
那么,我们之前说到,要得到输入图片的向量编码$f(x)$,是需要经过卷积神经网络的,那么卷积神经网络的参数如何确定呢?使用的方法就是Triplet loss损失函数,而后用梯度下降法进行迭代。
我们需要比较两组成对的图像 (Anchor, Positive, Negative),简写(A,P,N)
Anchor:表示要检测的目标图片
Positive:表示与anchor同个人的图片
Negative:表示与anchor不同个人的图片
所以我们希望A和P的距离小,A和N的距离大,因此有了如下不等式:
$$||f(A) - f(P)||^2 - ||f(A) - f(N)||^2 + \alpha \leq 0$$
这里这个公式与SVM的损失函数很类似,$\alpha$是表示margin边界,也就是增加$d(A,P)$和$d(A,N)$之间的差距。
而如果上面的不等式小于0,那说明是符合我们的要求的,如果是大于0,则要计入损失函数中,所以得到了Triplet loss的公式是:
$$L(A,P,N) = max(||f(A) - f(P)||^2 - ||f(A) - f(N)||^2 + \alpha,0)$$
整个网络的代价函数就是把所有的图片损失加起来:
$$J = \sum L(A,P,N)$$
每个三元组的选择是有讲究的,如果你要识别的是一个女人,然后对比的Negative是个老大爷,那么条件就很容易满足,学不到什么东西。所以应该尽量选择那些相似的图片进行每一组的训练,也就是:
$$d(A,P) \approx d(A,N) $$
选择的例子如下图,可以看到,每一个三元组对比的都是一些比较相似的图片:
除了之前说的用Triplet loss进行训练以外,还有别的方法来进行训练,也就是可以把Siamese网络当做一个二分类的问题。
如图,输入两张图片,当计算得到了两个图片的向量编码后,求两张图片的距离,然后通过一个sigmoid函数,把他变成一个二分类问题,如果同个人,输出1,不同个人则输出0。其中,权重$W,b$都可以通过训练来得到。
这个时候,人脸识别问题就变成了一个监督学习的问题,在创建每一对训练集的时候,应该有对应的输出标签y。
神经风格的迁移,就是输入两张图片,一张当做内容图片content,另一张当做风格图片style,输出的图片g兼具有一张的内容,和另一张的风格。
在进行风格迁移前,我们需要了解我们的神经网络到底在学些什么东西,把中间的隐藏单元拎出来看看。
如上图,假设我们有一个卷积神经网络,要看到不同层的隐藏单元计算结果,怎么办?依次对各个层进行如下操作:
对于在第一层的隐藏单元中,其只能看到卷积网络的小部分内容,也就是最后我们找到的那些最大化激活第一层隐层单元的是一些小的图片块。我们可以理解为第一层的神经单元通常会寻找一些简单的特征,如边缘或者颜色阴影等。
而后随着层数的增加,隐藏层单元看到的东西就越来越复杂了:
对于神经风格迁移,我们的目标是由内容图片C和风格图片S,生成最终的风格迁移图片G。所以定义代价函数为:
$$J(G) = \alpha J_{content}(C,G) + \beta J_{style}(S,G)$$
首先随机初始化G的像素,然后进行梯度下降:
对于一个卷积网络中,我们选择网络的中间层$l$, 定义“Style”表示$l$层的各个通道激活项之间的相关性。
那如何计算这个相关性呢?
假设我们在第$l$层有5个通道:
不同的通道之间代表着不同的神经元学习到的特征,如第一个通道(红色)可以表示含有垂直纹理的特征,第二个通道(黄色)表示区域中出现橙色的特征。
那么两个通道的相关性就表示图片中出现垂直纹理又出现橙色的可能性大小。
所以可以得到相关系数的矩阵“Gram Matrix:
$i,j.k$表示神经元所在的高度,宽度和通道。也就是每个通道的神经元分别乘上另一个通道对应位置的神经元再求和即可得到这两个通道$k,k^{\prime}$的相关系数。这个矩阵的维度是$(n_{c}^{[l]},n_{c}^{[l]})$的,也就是第$l$层的通道数乘通道数的大小。
而代价函数即为两张图片中相关系数矩阵的差值求和,再取平均。
图片都是2D的卷积运算,其实还可以推广到1D和3D的情况。
典型的1D情况就是信号处理。
3D情况就像CT的切片,是一层一层叠加起来的。
]]>本周的作业实现了YOLO算法,并用于自动驾驶的目标检测中。
输入: (m, 608, 608, 3)
输出: (m, 19, 19, 5, 85)
IMAGE (m, 608, 608, 3) -> DEEP CNN -> ENCODING (m, 19, 19, 5, 85)
也就是有5个Anchor boxes,一共有80个分类。
所以,每个box的scores也就是等于每个类预测的可能性:
这个时候开始创建一个函数,得到每一个box中scores最大的那个类,分数,以及位置,去掉其他没用的。
1 | # GRADED FUNCTION: yolo_filter_boxes |
找到了这些boxes后,还需要进行筛选过滤掉。先完成一个IOU算法:
1 | # GRADED FUNCTION: iou |
tensorflow已经帮你实现了iou算法了,不用用自己刚才写的了:
思想就是拿掉IOU比较大的那些box
1 | # GRADED FUNCTION: yolo_non_max_suppression |
而后结合刚才的函数,先去掉scores低的,然后运算NMS算法
1 | # GRADED FUNCTION: yolo_eval |
进行预测:
1 | def predict(sess, image_file): |
这一周主要讲了卷积神经网络的进一步应用:目标检测。
主要内容有:目标定位、特征点检测、目标检测、滑动窗口、Bounding Box,IOU,NMS,Anchor Boxes,Yolo算法。
在进行目标检测之前,需要先了解一下目标定位。
因为进行目标检测的时候需要预测出目标的具体位置,所以在训练的时候需要先标定一下这个目标的实际位置。
假设我们需要分类的类别有3个,行人,汽车,自行车,如果什么都没有,那么就是背景。可以看到,y一共有8个参数:
如果$P_c = 0$那么表示没有目标,那么我们就不关心后面的其他参数了。
如果是要检测人脸,那么可以在人脸上标注若干个特征点,假设有64个特征点,那么这个时候就有128个参数了,再加上判断是否有人脸,就有129个参数。
假设要检测的是人体肢体的动作,那么同样也可以标注若干个肢体上的特征点。
注意,这些都是需要人工进行标注的。
滑动窗口
目标检测通常采用的是滑动窗口的方法来检测的。也就是用一定窗口的大小,按照指定的步长,遍历整个图像;而后再选取更大的窗口,再次遍历,依次循环。这样子,每个窗口都相当于一张小图片,对这个小图片进行图像识别,从而得到预测结果。
但是这个方法有个很明显的问题,就是每个窗口都要进行一次图像识别,速度太慢了。因此就有了滑动窗口的卷积实现。在此之前,我们需要知道如何把全连接层变为卷积层。
全连接层转化为卷积层
如图,在经过Max pool后,我们得到了$5 \times 5 \times 16$的图像,经过第一个FC层后变成了400个节点。
而此时我们可以使用400个$5\times5$的卷积核,进行卷积,得到了$1\times1\times400$
而后再使用400个$1\times1$的卷积核,再得到了$1\times1\times400$矩阵,所以我们就将全连接层转化成了卷积层。
卷积滑动窗口的实现
因为之前的滑动窗口每一次都要进行一次计算,太慢了。所以利用上面的全连接层转化为卷积层的做法,可以一次性把滑动窗口的结果都计算出来。
为了方面观察,这里把三维图像画成了平面。
假设滑动的窗口是$14\times14\times3$,原图像大小是$16\times\times16\times3$。
蓝色表示滑动窗口,如果步数是2的话,很容易可以得到$2\times2$的图像,不难看出,在图中最后输出的左上角的蓝色部分就是原图中蓝色部分的计算结果,以此类推。
也就是说,只需要原图进行一次运算,就可以一次性得到多个滑动窗口的输出值。
具体例子如下图:
可以看到,原图为$28\times28$,最后得到了$8\times8 = 64$个滑动窗口。
上面介绍的滑动窗口的方法有一个问题,就是很多情况下并不能检测出窗口的精确位置。
那么如何找到这个准确的边界框呢?有一个很快的算法叫做YOLO(you only look once),只需要计算一次便可以检测出物体的位置。
如图,首先,将图片分为$n \times n$个部分,如图是划分成了$3\times3=9$份,而每一份都由一个向量y来表示。
因此最终得到了$3\times3\times8$的矩阵。
要得到这个$3\times3\times8$的矩阵,只要选择适当的卷积神经网络,让输出矩阵为这个矩阵就行,而每一个小图像都有一个目标标签y,这个时候y中的$b_x,b_y$都是这个小图像的相对位置,在0-1之间,而$b_h,b_w$是可以大于1的,因为整个大目标有可能在框框外。
在实际过程中可以选用更精细的划分,如$19\times19$。
如何判断框框是否正确呢?
如图红色为车身的框,而紫色为检测到的框,那么紫色的框到底算不算有车呢?
这个时候可以用交并比来判断,也就是两个框框的交集和并集之比。
$$IoU = \frac{交集面积}{并集面积}$$
一般来说 $IoU \geq 0.5$,那么说明检测正确,当然,这个阈值可以自己设定。
在实际过程中,很可能很多个框框都检测出同一个物体,那么如何判断这些边界框检测的是同一个对象呢?
如果有多个目标类别的检测,那么对每个类别分别进行上面的NMS算法。
如果一张格子中有多个目标,那怎么办?这时候就需要Anchor Box了,可以同时检测出多个对象。
我们预先定义了两个不同形状的Anchor box,如比较高的来检测人,比较宽的来检测汽车,然后重新定义了目标向量y:
这个时候最后输出的矩阵从原来的$3\times3\times8$变成了$3\times3\times16$,也可以是$3\times3\times2\times8$
在计算的时候就可以根据不同的box输出了,?号表示我们不关系这个值。
问题:
因为这两种情况出现的几率都比较少,所以问题不大。
注意:
假设我们需要检测三种目标:行人、汽车、摩托车,使用两种anchor box
在训练集中:
预测:
输入图片和训练集大小相同,得到$3\times3\times16$的输出结果
这个时候得到了很多个框框,如果是两个Anchor box,那么就有$2\times9=18$个预测框框,那么就需要把没用的框框都去掉,也就用到了上面的NMS非最大值抑制算法。
进行NMS算法:
这里还介绍了其他的目标检测算法,不过貌似都是比较慢的。
R-CNN:
原本的滑动窗口,只有在少部分的区域是可以检测到目标的,很多区域都是背景,所以运算很慢,用R-CNN后,只选择一些候选的窗口,不需要对整个图片进行滑动。
R-CNN使用的是图像分割算法,将图片分割成很多个色块,从而减少了窗口数量。
是对每个候选区域进行分类,输出的标签和bounding box
Fast R-CNN:
候选区域,使用滑动窗口在区分所有的候选区域。
Faster R-CNN:
使用卷积神经网络而不是图像分割来获得候选区域。
]]>本周作业分为两部分,一部分是keras的基本使用,另一部分是ResNet的构建。
Keras是TensorFlow的高层封装,可以更高效的实现神经网络的搭建。
先导入库
1 | import numpy as np |
构建模型
1 | def HappyModel(input_shape): |
然后实例化这个模型
1 | ### START CODE HERE ### (1 line) |
进行优化器和loss的选择
1 | ### START CODE HERE ### (1 line) |
训练
1 | ### START CODE HERE ### (1 line) |
预测:
1 | ### START CODE HERE ### (1 line) |
可以用summary()来看看详细信息:
1 | happyModel.summary() |
1 | _________________________________________________________________ |
用plot_model()来得到详细的graph
1 | plot_model(happyModel, to_file='HappyModel.png') |
主要有两个步骤:
这一部分非常深的神经网络的一些问题,主要是参数会变得很小或者爆炸,这样子训练的时候就会收敛的很慢,因此,用残差网络可以有效的改善这个问题。
根据输入输入的维度不同,分为两种块:
1. identity block(一致块)
可以看到,identity block的前后两端维度是一致的,可以直接相加。
在这里我们实现了一个跳跃三层的块。
基本结构是:
First component of main path:
conv_name_base + '2a'
. Use 0 as the seed for the random initialization.bn_name_base + '2a'
.Second component of main path:
conv_name_base + '2b'
. Use 0 as the seed for the random initialization.bn_name_base + '2b'
.Third component of main path:
conv_name_base + '2c'
. Use 0 as the seed for the random initialization.bn_name_base + '2c'
. Note that there is no ReLU activation function in this component.Final step:
注意在跳跃相加部分要用函数keras的函授Add(),不能用加号,不然会出错。
这里f是卷积核的大小,filters是这三层卷积层的深度的list,stage指的是哪一大层的网络,用来取名字的,后面有用,block是在stage下的某一层的网络,用a,b,c,d等字母表示。
1 | # GRADED FUNCTION: identity_block |
2. The convolutional block(卷积块)
当两端的维度不一致时,可以加一个卷积核来转化维度,这时候没有激活函数。
First component of main path:
conv_name_base + '2a'
.bn_name_base + '2a'
.Second component of main path:
conv_name_base + '2b'
.bn_name_base + '2b'
.Third component of main path:
conv_name_base + '2c'
.bn_name_base + '2c'
. Note that there is no ReLU activation function in this component.Shortcut path:
conv_name_base + '1'
.bn_name_base + '1'
.Final step:
这里参数新增了s是stride每一步数
1 | def convolutional_block(X, f, filters, stage, block, s = 2): |
构建一个50层的网络,分为5块,结构如下:
The details of this ResNet-50 model are:
'fc' + str(classes)
.Exercise: Implement the ResNet with 50 layers described in the figure above. We have implemented Stages 1 and 2. Please implement the rest. (The syntax for implementing Stages 3-5 should be quite similar to that of Stage 2.) Make sure you follow the naming convention in the text above.
You’ll need to use this function:
1 | # GRADED FUNCTION: ResNet50 |
本周主要讲了深度卷积网络的一些模型:LeNet,AlexNet,VGGNet,ResNet,Inception,1×1卷积,迁移学习等。
经典的卷及网络有三种:LeNet、AlexNet、VGGNet。
LeNet-5
LeNet-5主要是单通道的手写字体的识别,这是80年代提出的算法,当时没有用padding,而且pooling用的是average pooling,但是现在大家都用max pooling了。
论文中的最后预测用的是sigmoid和tanh,而现在都用了softmax。
AlexNet
AlexNet是2012年提出的算法。用来对彩色的图片进行处理,其实大致的结构和LeNet-5是很相似的,但是网络更大,参数更多了。
这个时候已经用Relu来作为激活函数了,而且用了多GPU进行计算。
VGG-16
VGG-16是2015的论文,比较简化的是,卷积层和池化层都是用相同的卷积核大小,卷积核都是3×3,stride=1,same padding,池化层用的maxpooling,为2×2,stride=2。只是在卷积的时候改变了每一层的通道数。
网络很大,参数有1.38亿个参数。
建议阅读论文顺序:AlexNet->VGG->LeNet
残差网络是由若干个残差块组成的。
因为在非常深的网络中会存在梯度消失和梯度爆炸的问题,为此,引入了Skip Connection来解决,也就是残差网络的实现。
上图即为一个残差块的基本原理,在原本的传播过程(称为主线)中,加上了$a^{[l]}$到$z^{[l+2]}$的连接,成为’short cut’或者’skip connetction’。
所以输出的表达式变成了:$a^{[l+2]} = g(z^{[l+2]} + a^{[l]})$
残差网络是由多个残差块组成的:
没有残差网络和加上残差网络的效果对比,可以看到,随着layers的增加,ResNet表现的更好:
假设我们已经经过了一个很大的神经网络Big NN,得到了$a^{[l]}$
那么这个时候再经过两层的神经网络得到$a^{[l+2]}$,那么表达式为:
$$a^{[l+2]} = g(z^{[l+2]} + a^{[l]}) = g(W^{[l+2]} a^{[l+2]} + b^{[l+2]} + a^{[l]})$$
如果加上正则化,那么权值就会很小,假设$W^{[l+2]},b^{[l+2]} = 0$, 因为激活函数是Relu,所以
$$a^{[l+2]} = g(a^{[l]}) = a^{[l]}$$
所以可以看到,加上残差块以后,更深的网络最差也只是和前面的效果一样,何况还有可能更好。
如果只是普通的两层网络,那么结果可能更好,也可能更差。
注意的是$a^{[l+2]}$要和$a^{[l]}$的维度一样,可以使用same padding,来保持维度。
用1×1的卷积核可以来减少通道数,从而减少参数个数。
Inception的主要好处就是不需要人工来选择filter的大小和是否要添加池化层的问题。
如图可以一次性把各个卷积核的大小和max pool一起加进去,然后让机器自己学习里面的参数。
但是这样有一个问题,就是计算量太大了,假设是上面的$5 \times 5 \times 192$的卷积核,有32个,这样一共要进行$28\times\28\times32\times5\times5\times192=120M$的乘法次数,运算量是很大的。
如何解决这个问题呢?就需要用到前面的1×1的卷积核了。
可以看到经过维度压缩,计算次数少了十倍。
单个的inception模块如下:
构成的google net如下:
别人已经实现的网络已经很厉害了,我觉得重复造轮子很没有必要,而且浪费时间,何况你水平也没有别人高。。还不如直接用别人的网络,然后稍加改造,这样可以很快的实现你的想法。
在GitHub上找到自己感兴趣的网络结构fork过来,好好研究!
之前已经讲过迁移学习了,也就是用别人训练好的网络,固定他们已经训练好的网络参数,然后套到自己的训练集上,完成训练。
如果你只有很少的数据集,那么,改变已有网络的最后一层softmax就可以了,比如原来别人的模型是有1000个分类,现在你只需要有3个分类。然后freeze冻结前面隐藏层的所有参数不变。这样就好像是你自己在训练一个很浅的神经网络,把隐藏层看做一个函数来映射,只需要训练最后的softmax层就可以了。
如果你有一定量的数据,那么freeze的范围可以减少,你可以训练后面的几层隐藏层,或者自己设计后面的隐藏层。
数据不够的话,进行数据扩充是很有用的。
可以采用
tips:
在数据比赛中
GitHub地址:https://github.com/ZJUFangzh/cs231n
作业2主要是关于搭建卷积神经网络框架,还有tensorflow的基本应用。
首先先搭建一个全连接神经网络的基本框架。
之前搭建的2层神经网络都是比较简单的,但是一旦模型变大了,代码就变得难以复用。因此搭建一个神经网络框架是很有必要的。
一般都会分为两部分forward
和backward
,一层一层来,因此两个函数成对出现就可以了。
1 | def layer_forward(x, w): |
1 | def layer_backward(dout, cache): |
在cs231n/layers.py
中修改affine_forward
函数,也就是简单的全连接层的前向传播。
1 | def affine_forward(x, w, b): |
而后修改affine_backward
函数
1 | def affine_backward(dout, cache): |
然后是relu_foward
和relu_backward
函数。
1 | def relu_forward(x): |
1 | def relu_backward(dout, cache): |
然后它定义了一个三明治层,意思是将affine和relu连接在一起。在layer_utils.py
中。
1 | def affine_relu_forward(x, w, b): |
1 | def affine_relu_backward(dout, cache): |
在完成这些基本的函数之后,就可搭建一个简单的神经网络了。在fc_net.py
中:
先完成初始化,然后在loss中调用这些基本函数,得到loss,然后再计算梯度。
1 | class TwoLayerNet(object): |
回到notebook中,调用了已经为我们写好的solver类,model用的就是TwoLayerNet()
1 | model = TwoLayerNet() |
完成之后,我们就可以类似的,搭建一个多层的神经网络了,同样是在fc_net.py
的FullyConnectedNet
类中。这时候先不要去在意batchnorm和dropout,后面会来实现这些函数。
1 | class FullyConnectedNet(object): |
然后构建了三层的model
1 | # TODO: Use a three-layer Net to overfit 50 training examples. |
五层的:
1 | # TODO: Use a five-layer Net to overfit 50 training examples. |
而后用上了Momentum的优化方法,在optim.py
中
1 | def sgd_momentum(w, dw, config=None): |
然后再尝试另外两种优化的梯度下降法RMSprop和Adam
1 | def rmsprop(x, dx, config=None): |
本周的作业分为了两部分:
主要内容:
创建CNN的主要函数
1. Zero Padding
先创建一个padding函数,用来输入图像X,输出padding后的图像,这里使用的是np.pad()
函数,
1 | a = np.pad(a, ((0,0), (1,1), (0,0), (3,3), (0,0)), 'constant', constant_values = (..,..)) |
1 |
|
2.Single step of convolution
创建一个单步的卷积运算,也就是一次输入一个切片,大小和卷积核相同,对应元素相乘再求和,最后再加个bias项。
1 | # GRADED FUNCTION: conv_single_step |
3.Convolutional Neural Networks - Forward pass
创建一次完整的卷积过程,也就是利用上面的一次卷积,进行for循环。进行切片的时候,注意边界vert_start, vert_end, horiz_start and horiz_end
这一步应该先弄清楚A_prev,A,W,b的维度,超参数项包括了stride和pad
$$ n_H = \lfloor \frac{n_{H_{prev}} - f + 2 \times pad}{stride} \rfloor +1 $$
$$ n_W = \lfloor \frac{n_{W_{prev}} - f + 2 \times pad}{stride} \rfloor +1 $$
$$ n_C = \text{number of filters used in the convolution}$$
1 | # GRADED FUNCTION: conv_forward |
创建池化层,注意得到的维度需要向下取整,用int()对float()进行转换
$$ n_H = \lfloor \frac{n_{H_{prev}} - f}{stride} \rfloor +1 $$
$$ n_W = \lfloor \frac{n_{W_{prev}} - f}{stride} \rfloor +1 $$
$$ n_C = n_{C_{prev}}$$
同样需要先进行切边,而后分为max和average两种,分别用np.max和np.mean
1 | def pool_forward(A_prev, hparameters, mode = "max"): |
卷积神经网络的求导是比较难以理解的,这里有卷积层的求导和池化层的求导。
假设经过卷积层后我们的输出$Z = W \times A +b$
那么反向传播过程中需要求的就是$dA,dW,db$,其中$dA$是原输入的数据,包含了原图像中的每一个像素,
而这个时候假设从后面传过来的$dZ$是已经知道的。
1.计算dA
从公式可以看出,$dA = W \times dZ$,具体一点,$dA$的每一个切片就是$W_c$乘上$dZ$在输出图片的每一个像素的求和结果,从矩阵的角度,每一次$W_c\times dZ_{hw}$得到的就是从单个输出的图片像素到输入图片切片(大小为W)的映射。因此公式为:
$$ dA += \sum _{h=0} ^{n_H} \sum_{w=0} ^{n_W} W_c \times dZ_{hw} $$
1 | da_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :] += W[:,:,:,c] * dZ[i, h, w, c] |
2.计算dW
$dW = A \times dZ$,而更具体一点,因为W对Z的每一个像素都是有作用的,所以就等于每一个输入图片的切片乘以对应输出图片像素的导数,然后再求和!
$$ dW_c += \sum _{h=0} ^{n_H} \sum_{w=0} ^ {n_W} a_{slice} \times dZ_{hw} $$
1 | dW[:,:,:,c] += a_slice * dZ[i, h, w, c] |
3.计算db
$$ db = \sum_h \sum_w dZ_{hw} $$
1 | db[:,:,:,c] += dZ[i, h, w, c] |
所以得到以下:
1 | def conv_backward(dZ, cache): |
这里max pooling和average poolling要分开处理。
1. Max pooling - backward pass
假设pool size是$2 \times 2$的,那么,4个像素中只有1个留下来了,其余的都没有效果了,所以在max pooling中,从后面传递过来的导数值,只作用在max的那个元素,而且继续往前传递,不做任何改动,在其余3个元素的导数都是0。
创建一个mask矩阵,让最大值为1,其余的都为0,这样子就可以作为一个映射矩阵向前映射了。
$$ X = \begin{bmatrix}1 && 3 \\ 4 && 2 \end{bmatrix} \quad \rightarrow \quad M =\begin{bmatrix}
0 && 0 \\
1 && 0
\end{bmatrix}$$
1 | def create_mask_from_window(x): |
2. Average pooling - backward pass
和max不同,average pooling相当于把backward传过来的值分成了$n_H \times n_W$等分。所以要计算的参数就比max pooling多很多了,这也就是为什么一般都用max pooling,不用average pooling
$$ dZ = 1 \quad \rightarrow \quad dZ =\begin{bmatrix}
1/4 && 1/4 \\
1/4 && 1/4
\end{bmatrix}$$
1 | def distribute_value(dz, shape): |
结合两种方法:
1 | def pool_backward(dA, cache, mode = "max"): |
用TensorFlow来搭建卷积神经网络。
先创建placeholders,用来训练中传递X,Y
1 |
|
用来初始化参数,主要是W1,W2,在这里就没有用b了
用W = tf.get_variable("W", [1,2,3,4], initializer = ...)
initializer 用tf.contrib.layers.xavier_initializer
1 | # GRADED FUNCTION: initialize_parameters |
记得这只是创建了图而已,并没有真正的初始化参数,在执行中还需要
init = tf.global_variables_initializer()
sess_test.run(init)
模型为:CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED
1 | - Conv2D: stride 1, padding is "SAME" |
用到的函数:
tf.nn.conv2d(X,W1, strides = [1,s,s,1], padding = ‘SAME’): given an input $X$ and a group of filters $W1$, this function convolves $W1$’s filters on X. The third input ([1,f,f,1]) represents the strides for each dimension of the input (m, n_H_prev, n_W_prev, n_C_prev). You can read the full documentation here
tf.nn.max_pool(A, ksize = [1,f,f,1], strides = [1,s,s,1], padding = ‘SAME’): given an input A, this function uses a window of size (f, f) and strides of size (s, s) to carry out max pooling over each window. You can read the full documentation here
tf.nn.relu(Z1): computes the elementwise ReLU of Z1 (which can be any shape). You can read the full documentation here.
tf.contrib.layers.flatten(P): given an input P, this function flattens each example into a 1D vector it while maintaining the batch-size. It returns a flattened tensor with shape [batch_size, k]. You can read the full documentation here.
tf.contrib.layers.fully_connected(F, num_outputs): given a the flattened input F, it returns the output computed using a fully connected layer. You can read the full documentation here.
In the last function above (tf.contrib.layers.fully_connected
), the fully connected layer automatically initializes weights in the graph and keeps on training them as you train the model. Hence, you did not need to initialize those weights when initializing the parameters.
1 | # GRADED FUNCTION: forward_propagation |
1 | # GRADED FUNCTION: compute_cost |
把前面的函数都结合起来,创建一个完整的模型。
其中random_mini_batches()
已经给我们了,优化器使用了
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(cost)
1 | # GRADED FUNCTION: model |
得到效果如图:
]]>第四门课开始就学习深度学习关于计算机视觉的重要应用—卷积神经网络。
第一周主要是对卷积神经网络的基本构造和原理做了介绍。
计算机视觉是深度学习的一个非常重要的应用。比如图像分类,目标检测,图片风格迁移等。
用传统的深度学习算法,假设你有一张$64×64$的猫片,又有RGB三通道,那么这个时候是$64×64×3=12288$,input layer的维度就是12288,这样其实也还可以,因为图片很小。那么如果你有$1000×1000$的照片呢,你的向量就会有300万!假设有1000个隐藏神经元,那么就是第一层的参数矩阵$W$有30亿个参数!算到地老天荒。所以用传统的深度学习算法是不现实的。
如图,这些边缘检测中,用水平检测和垂直检测会得到不同的结果。
垂直检测如下图,用一个$3×3$的过滤器(filter),也叫卷积核,在原图片$6×6$的对应地方按元素相乘,得到$4×4$的图片。
可以看到,用垂直边缘的filter可以将原图片中间的边缘区分出来,也就是得到了最右图中最亮的部分即为检测到的边缘。
当然,如果左图的亮暗分界线反过来,则输出图片中最暗的部分表示边缘。
也自然有水平的边缘分类器。
还有更复杂的,但是我们不需要进行人工的决定这些filter是什么,因为我们可以通过训练,让机器自己学到这些参数。
padding是填充的意思。
我们可以从之前的例子看到,每经过一次卷积运算,图片的像素都会变小,从$6×6 —> 4×4$,这样子图片就会越来越小,后面就毛都不剩了。
还有一点就是,从卷积的运算方法来看,边缘和角落的位置卷积的次数少,会丢失有用信息。
所以就有padding的想法了,也就是在图片四周填补上像素。
计算方法如下,
原数据是$n \times n$,filter为$f \times f$,padding为$p \times p$,
那么得到的矩阵大小是$(n + 2p -f +1)\times(n + 2p -f +1)$
padding有两种:
卷积的步长也就是每一次运算后平移的距离,之前使用都是stride=1。
假设stride=2,就会得到:
得到的矩阵大小是
$$\lfloor \frac{n+2p-f}{s}+1\rfloor \times \lfloor \frac{n+2p-f}{s}+1\rfloor$$
向下取整: 59/60 = 0
之前都是单通道的图片进行卷积,如果有RGB三种颜色的话,就要使用立体卷积了。
这个时候的卷积核就变成了$3 \times 3 \times 3$的三维卷积核,一共27个参数,每次对应着原图片上的RGB一共27个像素运算,然后求和得到输出图片的一个像素。因为只有一个卷积核,这个时候输出的还是$4 \times 4 \times 1$的图片。
多个卷积核
因为不同的卷积核可以提取不同的图片特征,所以可以有很多个卷积核,同时提取图片的特征,如分别提取图片的水平和垂直边缘特征。
因为有了两个卷积核,这时候输出的图片就是有两通道的图片$4\times 4 \times 2$。
这里要搞清两个概念,卷积核的通道数和个数:
单层卷积网络
如图是单层卷积的基本过程,先经过两个卷积核,然后再加上bias进行relu激活函数。
那么假设某层卷积层有10个$3 \times 3 \times 3$的卷积核,那么一共有$(3\times3\times3+1) \times10=280$个参数,加1是加上了bias
在这里总结了各个参数的表示方法:
简单神经网络
一般卷积神经网络层的类型有:
pooling 的作用就是用来压缩数据,加速运算,提高提取特征的鲁棒性
Max pooling
在范围内取最大值
Average Pooling
取平均值
一般conv后都会进行pooling,所以可以把conv和pooling当做一层。
如上图就是$conv-pool-conv-pool-fc-fc-fc-softmax$的卷积神经网络结构。
各个层的参数是这样的:
可以看到,在卷积层的参数非常少,池化层没有参数,大量的参数在全连接层。
这里给出了两点主要原因: