Online Meta Learning - Slides of My Presentation @ NTU

This is my slides in group meeting presenting paper Online Meta-Learning [1].

Download Link

Reference: Finn’s oral on ICML 2019 [2].

Reference


  1. 1.Chelsea Finn, Aravind Rajeswaran, Sham Kakade and Sergey Levine. Online Meta-Learning. ICML 2019.
  2. 2.Chelsea Finn, Aravind Rajeswaran, Sham Kakade and Sergey Levine. Online Meta-Learning Oral Presentation. ICML 2019.

A Survey on Meta-Learning - Slides of My Presentation @ NTU

Two weeks after I joined NTU, I presented a survey at our group meeting. Here, I share the slides to all.

Download Link

Reference: Lil’Log [1], Finn’s tutorial on ICML 2019 [2].

Reference

ECCV 2018 Person Re-Identification Paper Reading

Pose-Normalized Generation for Person Re-Identification

[1]

Overview: The author tries to use GAN that takes pose information to transfer (generate from) a real person image to new image with targeted pose. Thus, with the inputs regulated to the same pose, the feature extractor can learn pose-invariant features.

Motivation:

  • Make the learned features pose-insensitive. Since the inputs are of the same pose, the feature extractor can learn discriminative features despite the poses.
  • Use eight canonical poses obtained by K-means for specific dataset to make the normalized pose more representative.

Model:

  • A GAN to generate pose specific image with the same identity as the input. The loss function for generator:
    $$ \mathcal{L}_{G_p} = \mathcal{L}_{GAN} + \lambda_1 \cdot \mathcal{L}_{L_1}, $$
    and
    $$ \mathcal{L}_{GAN} = \mathbb{E}_{\mathbf{I}_j \sim p_{data}( \mathbf{I}_j )}\{log{ D_p(\mathbf{I}_j) + log( 1 - D_p( G_p(\mathbf{I}_j, \mathbf{I}_{P_j}) ) ) }\} $$
    $$ \mathcal{L}_{L_1} = \mathbb{E}_{\mathbf{I}_j \sim p_{data}( \mathbf{I}_j )}\{[\| \mathbf{I}_j - \mathbf{\hat I}_j \|_1]\} $$
    The loss of discriminator is the same as conventional ones.
  • Use K-means algorithm to get the 8 most representative poses, and then train another network to handle person image in these 8 normalized poses.
  • When evaluation, use the fusion of nine features. The fusion is to perform element-wise max among these features.

Question:

  • No explicit loss for discriminator to judge the generated pose. The discriminator just takes the generated image and tell if the image is fake or real (from dataset), which means the generator can just ignore the input pose information and learn as regular generator in GANs.
  • Due to the $\mathcal{L}_{L_1}$ in the loss of generator, the generator can be optimized just generate the same image as the input. And as mentioned in the above item, the pose information can be ignored.
  • Since there are two base networks, and at the end the two features are fused, how to guarantee the two learned features have the same semantic meaning?
  • More evaluations on the components of the model is need. Also more images generated from the PN-GAN.

To Be Continued…

Reference


  1. 1.X. Qian, Y. Fu, T. Xiang, W. Wang, J. Qiu, Y. Wu, Y. G. Jiang, X. Xue. Pose-Normalized Image Generation for Person Re-identification. ECCV 2018.

Adversarial Open-World Person Re-Identification

Adversarial Open-World Person Re-Identification is the my first paper as the first-author during my undergraduate life. The acceptance to ECCV 2018 makes it more exciting and meaningful to me.

Actually it is this work that helps me have a better understanding towards real research, including discussing about it over and over again, doing experiments over and over again and polishing it over and over again until the DDL of paper submission, which gives me the opportunity to see what’s like in Guangzhou at 4:00 am. Prof. Zheng and PhD. Wu helped me a lot, and I really appreciate it.

The paper is available at Arxiv.

How did I get this idea?

In open-world person re-identification (hereinafter called re-id) scenario, the major issue is to distinguish target people from non-target ones. And obviously non-target people who look like (or are close to in feature space) target people are a main threat to our re-id system. A simple solution is that we can find target-like non-target people in the dataset and alert the network about this by designing a loss to push them away. But you can never get enough such samples in a fixed dataset, so here comes the power of Generative Adversarial Networks (GANs) [1].

This work was initially inspired by Zheng’s paper Unlabeled samples generated by gan improve the person re-identification baseline in vitro [2] when Prof. Zheng talked with me saying that maybe we can do something with GAN in open-world person re-id. And Zheng’s work was one of the mere works about applying GAN in person re-id at that time. However Zheng’s work is very limited, or the using of GAN is very limited. Leaving apart the loss design, in my understanding, GAN in this work is just used to generate unlabeled samples to augment the dataset. They trained the GAN in the first place, and used it to get more samples to form a larger dataset. And here is where the job of GAN ends. The following processes are all about how to make use of the enlarged dataset, which contains some unlabeled samples.

But I demanded more. I was thinking about how to make GAN part of our end-to-end learning process. Cause in this way, the generated samples are not fixed but becoming more and more efficient since the weights of GAN are changing during this end-to-end learning. In this case, since we use GAN to generate adversarial samples to attack the feature extraction network, with the stronger (robuster) the feature extractor becomes, the GAN learns something new and gets stronger (can produce more efficient samples) too, thus better feature extractor can be obtained by using better imposter samples generated by GAN.

The essence of APN: weight sharing

How can we make the GAN become better with the improvement of the feature extractor? And it would be better if the GAN can always know the instant weakness of the feature extractor. By doing this, the samples generated by GAN are always targeting to attack the extractor. The answer is letting the feature extractor become part of the GAN.

In APN, we modified the GAN, and use two distinct discriminators to provide guidance to the generator, while one of the discriminator, the one used to distinguish target people from non-target ones, shares the same weights with the feature extractor. The benefit is obvious because now the generator is guided by the feature extractor’s ability of discriminating targets and non-targets. So the generated samples are very efficient and specifically targeting to attack the growing discriminating ability of feature extractor. Our goal of letting the feature extractor become robuster to similar target and non-target people is achieved. In every batch, the feature extractor becomes robuster and the generator also becomes more efficient.

Conclusion

In this post, I just talked about the basic idea and my perspective about our work Adversarial Open-World Person Re-Identification, which is coarse and not detailed. For more information, it’s better to explore the paper yourself. :)

Reference


  1. 1.I. J. Goodfellow, J. Pouget-Abadie, M. Mirza, B. Xu, D. Warde-Farley, S. Ozair, A. Courville and Y. Bengio. Generative Adversarial Networks. NIPS 2014.
  2. 2.Z. Zheng, L. Zheng and Y. Yang. Unlabeled Samples Generated by GAN Improve the Person Re-Identification Baseline in Vitro. CoRR 2017.

关于CNN(卷积神经网络)的理解

对于从事计算机相关工作的人来说,机器学习、深度学习等词汇一定不会陌生而且。这也是属于AI(人工智能)这个大范畴的,现在的科技公司,不与AI沾点边好像都不好意思叫科技公司。之前有人说21世纪是生物的世纪,现在看来,21世纪早已被AI占领了。

AI的迅速火爆与CNN(卷积神经网络)的提出有很大关系。我最早在杂志上面看到一篇关于神经网络的科普文,当时还没有从事这方面的研究,认为可以用计算机模拟人的神经了,太牛逼了。现在看来还远远达不到生物的神经水平,而神经网络仅仅是参照生物神经激活的一种高维非线性表达方式。

与CNN有关的文章实在太多了,也有讲得很详细的,所以我不希望记录一些重复的工作。本文希望从一些方面来诠释CNN,并且包括一些比较新的研究成果以及自己的思考,作为自己学习的一个总结。如有错误或者疏漏,欢迎提出。在阅读这篇文章之前,希望你已经对深度学习以及CNN有一定的了解。当然本文涉及的内容也是相对简单的一些知识,可能会比较杂乱,都是我觉得值得记录的东西。

从CNN的组成出发

CNN虽然很早就提出来了,但是真正让CNN火起来的是其在图像分类上面取得的巨大成功,而这个成功自然离不开Hinton在2012年的[1],直接在ILSVRC-2012比赛中图像分类正确率超过了第二名26.2%,可谓效果相当惊人。[1]中给出的一种网络结构AlexNet也可谓非常经典。

来自[2]的AlexNet图片,较[1]原文中的图更加简洁。

从AlexNet中我们可以看出早期的CNN结构主要由卷积层(图中Conv)、激活层(图中并没有画出)、池化层(图中未画出)、全连接层(图中FC)以及Dropout层(图中未画出)组成。除了以上提到的几个层之外,本文也将讨论一个在2015年提出的非常重要的层——Batch Normalization层[3]

卷积层

卷积核的意义

毫无疑问卷积层是CNN中最重要的一个层。而卷积层实际的操作也就是卷积核在图像上的操作。卷积核我更喜欢称为滤波器(filter),因为filter更加能够体现卷积的性质。实际上filter做的事情更加像是一种匹配,这种匹配是二维空间上的。更加通用一点来说,卷积是一种二维空间上的模式匹配(pattern recognition),卷积核可以看作“模式”,也就是需要匹配的模板。如下图所示。

最右侧矩阵为filter的值,该filter是希望匹配一个弧形的图像。而最左侧图像的像素值矩阵(中间)与该filter的矩阵值分布类似,所以最后计算结果数值很大(即成功匹配了)。

这就类似于字符串匹配,如果我们只考虑最简单的遍历字符串的$O(n*m)$算法,那么将需要匹配的模板(模式,比如正则表达式)在输入串上面“游走”一边,看能不能匹配到。而卷积就是filter作为模式在输入上面“游走”一遍。不同的是,filter游走的结果不是匹配到一个特定的东西就结束, 而是对于每一个位置的匹配给出一个“打分”,这个“打分”暗示了该区域与filter的匹配程度,也影响到是否被激活。还有一点不同的是,字符串是一维的,所以匹配只需要逐字试就可以了。但是卷积的输入是二维的,所以我们要“游走”图片上所有可能的位置,也就有了卷积这种操作。其实卷积之后的各层跟之前层在空间上是对应的。

缩放/旋转不变性

从上面的解释可以看出,卷积核的这种匹配,这种空间上element-wise相乘来看是否相似的匹配是有局限性的。很容易想到的就是如果图片旋转90度,结果就会很不理想。由于filter并不会自动旋转,所以在空间上element-wise的匹配是死板的,这也使得卷积基本没有旋转不变性[4](Max pooling具有一定的旋转不变性,在后面会说)。例如上图中的例子,如果将图片的像素矩阵顺时针旋转90度之后与filter做element-wise的乘法,结果为3000,是原结果的一半不到。

针对这个问题,一种方法是Data augmentation,也就是在数据预处理的时候生成一些旋转之后的样本用于训练。这种方法实际上并没有弥补filter对于旋转之后图片感知力的不足,我认为CNN本身在这个问题上泛化能力是不足的。当然也有人从网络结构上解决这个事情,例如[5]提出的Spacial Transformer Networks,但是这类网路也只是在层间(对feature map)进行一个空间上的变换,无法克服卷积本质上对于仿射变换支持不足的弱点。

Spacial Transformer Networks

卷积核的大小

在目前主流网络中,如ResNet[6]、 DenseNet[7]以及MobileNet[8]都大量使用$3 \times 3$或者$1 \times 1$的卷积核。为什么会用这样大小的卷积核呢?

首先卷积核的边长应该是奇数的。奇数的边长使得卷积核能有一个中心像素,这个中心像素对应输入中的像素点来在原输入上滑动是非常方便的。对于padding来说,奇数边长的卷积核使得两边的padding是对称的。

为什么会使用$3 \times 3$的卷积核呢?根据卷积核在原输入上滑动的性质,如果我希望匹配一个猫的耳朵的形状,那么卷积核大小难道不应该跟猫耳的区域大小差不多吗?$3 \times 3$的像素能匹配到什么东西呢?首先我们要知道仅用$3 \times 3$的卷积核感受野(输出中一个像素对应输入的区域大小)只有$3 \times 3$,也就是输出一个像素所感知的区域在输入中只有很小的区域。但是神经网络是多层的,如果第$i+1$层对第$i$层的感受野为$3 \times 3$,那么在下一层继续使用一个$3 \times 3$的卷积核,则第$i+2$层对第$i$层的感受野是$5 \times 5$。这样多层下来是可以感知很宽广的区域的。但是为什么不直接用$5 \times 5$的卷积核呢?答案是节约参数。同样的感受野的情况下使用两个$3 \times 3$的卷积核(假如通道数为1,不考虑偏置)参数为$2 \times 3 \times 3=18$,而$5 \times 5$的卷积核参数为$5 \times 5=25$。很显然多个小卷积核参数个数小于一个大卷积核,这种情况在3个$3 \times 3$和1个$7 \times 7$下差距更加明显。

多个$3 \times 3$的卷积核与$5 \times 5$以及$7 \times 7$的感受野相同,图源自[9]

而$1 \times 1$的卷积核有什么作用呢?$1 \times 1$的卷积核可以进行通道间交互,将在下一节通道里讨论。

通道

卷积的通道(channel)到底有什么意思?这个在很多关于CNN的教程中都没有提到,只说了怎么计算下一层。首先,我们知道的是,每一层的卷积核的通道数对应输入的通道数,而卷积核的个数对应输出的通道数。例如我们用$K^i_j$来表示第$i$个卷积核的第$j$个通道,那么有多少个$i$就对应多少个输出通道,而有多少个$j$就对应多少个输入通道(输入图片的通道数)。对于第$i$个卷积核,$K^i_j$中不同的$j$在输入对应的通道上做卷积,然后求和。如果把不同的$j$对应的卷积核看作不同的权重,那么$K^i$在输入上做的就是对不同的通道的卷积的加权求和。加权求和是不是很熟悉?其实这跟全连接层做的事情是一样的。也就是说卷积核关于通道的操作就是对不同的通道进行全连接,进行加权求和,如下图。

卷积与全连接的类比

这样我们也就不难理解$1 \times 1$卷积核的作用了。就是用来做通道间的交互的。其实,就是通道之间的加权求和,跟全连接层作用一样。在[8]中提出的MobileNets中,为了节省参数(从名字就可以看出是为了移动设备,要减少计算量),直接提出了只在通道内部做卷积$3 \times 3$的卷积,然后用$1 \times 1$的卷积做通道之间的交互。什么意思呢,就是对于输入,一个通道对应一个卷积核,卷积核只作用于这一个通道,输出的也只是一个通道的二维矩阵,这个过程中只在每个通道自己内部做卷积,而没有通道间的交互,这个过程被称为depthwise convolution。而为了通道间的交互,再使用$1 \times 1$的卷积来做通道间的全连接,这个过程称为pointwise convolution。将普通的卷积层分为depthwise convolution和pointwise convolution两层,可以大大减少参数的数目。

除此之外,通道还可以看作特征。对于原始的输入图像,一般是RGB三个颜色通道,而不同的颜色就是它的特征。延续刚刚与全连接层的类比,卷积层通过特征(通道)间的全连接(加权求和)来提取更高维、更抽象的特征。例如在很多问题中,都使用最后一个全连接层之前的输出作为特征向量使用。而这个特征向量的每一个元素就对应一个通道,就是各通道内部的二维矩阵通过池化压缩为一个元素而已。所以通道和特征是对应的。

激活层

非线性

激活层其实大家应该已经很熟悉ReLU、Sigmoid以及tanh等激活函数。这些激活函数形式很也很简单。激活层顾名思义是用来激活的,由于神经网络本身的线性性,我们使用激活层来表达高维非线性函数。假设没有激活层的话,那么神经网络的层可以线性表示为$f_i(\vec{x}) = W^i\vec{x}+\vec{b^i}$,其中$i$表示第$i$层。那么最终结果也可以线性表示:
$$f(\vec{x}) = f_N(f_{N-1}(…f_1(\vec{x}))) \\
= W^N(W^{N-1}(…W^1(\vec{x})+\vec{b}^1)+\vec{b}^{N-1})+\vec{b}^N \\
= W^NW^{N-1}…W^1\vec{x} + W^NW^{N-1}…W^2\vec{b}^1 + … + W^N\vec{b}^{N-1}+\vec{b}^N$$

而如果有一个非线性函数加在每个卷积层之后就不一样了,整个函数都会是非线性的。正是激活层向神经网络加入了非线性性质,才使得神经网络能够拟合复杂的多维非线性函数。但是CNN仍然具有一些线性性,这导致对于某些对抗样本无法正确的分类。如[10]所说的,即使是一个很小的噪声加在图片上,人根本看不出区别来,但是CNN会犯很严重的错误。如Goodfellow说的,这不是因为过拟合,而是很小的噪声一层一层的线性累积造成的,所以CNN还是有线性缺点。

梯度消失

梯度消失问题在batch normalization[3]提出之后就基本上已经解决这个问题了,在现在的网络结构中,基本都能够很好的训练。但是在之前,人们可是被这个问题困扰了很久,这个问题也是反向传播算法的一个短板。在反向传播的过程中,传播回某一层梯度的时候,由于链式法则,我们需要乘上激活函数的导数。如$f(\vec{x})=g(W^\vec{x} + \vec{b})$,其中$g$为激活函数。则该层反向传播时对输入的梯度为$\frac{\partial f}{\partial \vec{x}} = \frac{\partial f}{\partial g} \frac{\partial g}{\partial \vec{x}}$其中$\frac{\partial f}{\partial g}$则是激活函数$g$的求导。而Sigmoid的导数为$g’(x) = \frac{e^{-x}}{(1+e^{-x})^2}$的函数图像为下图:

Sigmoid导数的函数图像

可以看出,最大也就才0.25,想一想每层梯度都乘$\frac{1}{4}$,那么最终传回来的将是很小的值。梯度的值在回传的过程中是成指数级下降的。tanh也类似,虽然其导数可以取到1,但是只是在$x=0$的时候,很显然同样也会有梯度消失的问题。

这个时候ReLU被提出来使用了,还是在经典的[1]中,$ReLU(x) = max(0, x)$,这样的形式很简洁,计算也足够快,但是最关键的是它的导数。当$x<0$时,导数为0,而$x>0$时,导数为1。简洁的导数为1可以减轻梯度消失问题。而真正可以说是基本解决了梯度消失问题的还是batch normalization,之后会讨论。

Dying ReLU

对于一个激活函数来说,能够有区分性激活就是它最大的作用,对于某些值(一般来说是高的值)激活,而一些值不激活。然而ReLU这样一个特殊的激活函数在$x<0$的情况下对所有值都是平等的对待了,也就是说不能激活了,因为输出全部是0。而且当$x<0$的话,ReLU的导数也是0,那么根本就更新不了权值。这个时候就会有一个疑惑,既然梯度为0,产生不了回传的梯度,那不是相当于始终更新不了吗?那也就没法学习了?而且初始化使得$x$有一半的概率都会为0。但实际上这个问题并不是特别明显,因为对于一个卷积核来说,不太可能对各种输入都恒输出为负数(从而导致ReLU产生0的结果)。所以当我们在使用mini-batch SGD的时候,一个batch中总有使得该卷积核的结果激活的样本,所以该卷积核仍然能够得到训练。

只有当一个卷积核对于几乎所有样本都输出为负数的时候,才会导致ReLU“死掉”(Dying ReLU),导致该卷积核的参数得不到训练。这个有可能是因为有一个很大的负bias造成的。解决这个问题的一个办法是使用Leaky ReLU[11],公式为$LReLU(x)= \begin{cases} x, x > 0 \\ 0.01x, x \leq 0 \end{cases}$。可以看到在$x<0$的时候并不是直接等于0,而是赋予$x$一个很小的权重,这样$x<0$的时候相当于没激活,因为值很小,但是不会“死去”,因为不为0。

LeakyReLU函数图像

除此之外,还有Parametric ReLU[12],就是将上式LReLU中的0.01替换为一个可训练的参数。虽然看似改动很小,但是会有一些有意思的事情,我们之后讨论。

池化层

GAP

一般来说,为了在网络最后使用特征图来分类,网络最后会使用全连接层,将特征图连接为一个向量用于分类。而将二维的特征图上的像素点全部使用全连接层连接出一个向量的时候,人们发现,全连接层上的参数实在太多了。一次全连接就相当于所有输入的像素和所有输出的向量元素之间都有一个连线,都有一个权重。这导致一个网络中大多数参数都全连接层里,而且和其他层的参数数目不是一个数量级的。

VGG16网络各层的参数个数

可以看到fc1的参数个数达到了102760448,超出其他层太多。为了节约,[13]中提出了使用Global Average Pooling。包括ResNet也使用了这种思想,直接把最后的feature map在每个通道取平均值,最后每个通道只剩下一个元素值,然后再用全连接层,这样节约了太多参数了。而且这种方法比直接用全连接层更加有效,得到的结果更好。

Global Average Pooling

Max Pooling

如果说池化是为了降维,将高分辨的图降低分辨率,那么average pooling取平均值似乎更加合理,这样能够体现该区域的一个平均状况。但是max pooling似乎更加符合神经网络“激活”的特点。正如卷积那一部分说的,我们做的是对特征或者模式的匹配,那么该区域只要有一块匹配到了,被激活了,那么整个区域都应该被视为匹配到了,都应该视为激活的。如果使用average pooling,那么激活的区域很可能就被周围未激活的区域拖了后腿,从而整块大区域未激活。所以使用max pooling是符合神经网络的特点的。

同时max pooling也使得CNN具有一定的旋转不变性。在旋转角度很小的情况下(比如$5^{\circ}$),这么小的角度,使得max pooling作用的区域大小里面最大的值还是那个值,为CNN提供了一定的旋转不变性。但是这种贡献太小了,根本不能因此而说CNN有旋转不变性。

全连接层

很多时候,全连接层都是用来作为分类器的。其分类的本质可以理解为对特征进行加权。比如在ResNet中,最后一个全连接层之前是GAP将特征图$2048 \times 7 \times 7$变为2048维的特征向量。这个向量我们称为特征向量,它的2048维可以类比为2048个感知到的特征,比如动物的耳朵、颜色等。有了这些特征,我们要得到对应的类别,那么可以根据一定的权重来把这些特征求和。比如兔子,可能它对耳朵长度、牙齿长度特征的权重会大一些,而对爪长这个特征权重会很小(甚至为负),因为更长的耳朵和牙齿、短的爪长才是兔子,当这些特征是对应的期望的数值时,最终加权求和的结果就会很大,那么预测兔子的可能性就更大。而这就是全连接层用于分类做的事情。

如果我们用矩阵$W$来代表全连接层,那么$W\vec{x}$代表分类的结果。$W$的行数就是类别的个数,而$W$的第$i$行向量就是学到的第$i$个类别的代表,它的数值是这个类的代表。通过它与输入特征点积,得到对应这个类别的得分,得分越高,是这个类的可能性越高。沿用兔子的例子,如果第$i$类为兔子,而特征中第$j$个为耳朵长短,那么学出来的$W_{i,j}$的值应该是高的,因为它“期望”一个较高的值与它相乘。所以真正学习到的类别信息是在最后作为分类的全连接层里,之前的层更多的是为了学习一个高维、抽象的特征表示。值得一提的是,最后的全连接层作为分类器是线性的,所以我们理想情况下希望之前的层学习到的特征表示是线性可分的。

BN层

Batch Normalization的思想与Hinton在训练深度信念网络(Deep Belief Networks[14])时用的方法是有一定的联系的。在Deep Belief Networks的训练中,先逐层训练,即每次只训练一个层,这样可以避免一次训练很多个层的时候从后面层传回来的梯度逐渐消失的情况。 而Batch Normalization则是把每一层的输入给归一化,类似于前面Hinton的把层间的联系区分开,假设层间数据分布不变,来解决梯度消失问题。

如果不进行归一化,那么训练的时候可能出现什么情况呢?假设第一层输出的数据分布在[a, b],那么第一次训练的时候,第二层接收的是[a, b]的数据,根据这个数据分布来训练。然而在经过一次梯度下降更新权重之后,第一层由于权重变了,结果对于同样的输入,第一层输出分布变为了[c, d]。这时对第二层是不利的,第二层最开始是假设输入为[a, b]进行训练的,然而这下变为了[c, d],第二层就有点不知所措了。所以Batch Normalization的思想就是把层间数据归一化,使得每层输入的数据分布在每次训练中都是相同的。

网络的结构

ResNet

Identity Mapping

ResNet的提出是令人兴奋的,因为它直接解决了深层网络难以训练的问题。它的形式很优美$G(x) = F(x) + x$。

Residual Block

很多有关ResNet的博文或者解释中都提到了ResNet解决了梯度消失的问题,因为梯度可以沿那个额外加上的$x$传递回前面的层。但是很显然梯度消失并不是ResNet真正要解决的问题。[6]中也说到梯度消失已经被Batch Normalization解决了,而ResNet面临的问题作者也没有具体说明是什么,但是他举了一个很有意思的例子。假如有一个浅层的网络和一个深层的网络,深层网络的浅层部分和浅层网络一样。那么我们只要假设深层网络剩余的部分是恒等变换(identity mapping,$f(x) = x$),也就是剩下的部分不改变输入的值,那么深层网络至少应该和浅层网络一样,如下图。

[6]提出的假设

深层网络至少应该能做到和浅层网络一样的效果,然而实际实验中得到的结果是深层网络往往比浅层结果差。这其中主要的原因就是恒等变换的$f(x) = x$在CNN中是不成立的,无论参数为多少,都不能做到。所以ResNet提出$f(x)+x$的真正原因是,CNN能够做到$f(x)=0$(卷积核参数全为0),那么$f(x)+x$就等于$x$了,就做到了identity mapping。但实际上这种跳层的连接从深层网络等效为浅层的角度来说,ResNet做到了自动调节网络深度,能够自动学习跳过其中的一些层,这也是关键的一点。

对于identity mapping来说,我自己也有一些思考。单就要使CNN做到$f(x)=x$来说,最大的问题在于激活函数。卷积层如果不是bottleneck的结构,即通道的数目是在逐层增大的话,那么卷积层是可以做到identity mapping的。例如$W^i_j$代表输出通道为第$j$个,在输入第$i$个通道上作用的卷积核,那么我们令当$i=j$时,卷积核最中心那个点的参数为1,其余参数都为0(包括非中心点或$i\neq j$的情况)。如果输入有$a$个通道,那么得到的输出通道前$a$层是与输入恒等的。所以问题主要出在激活函数上,ReLU不能做到$f(x)=x$在$x$任意取值都相等。这个时候可以联想到我们之前提过一个PReLU[12]对吧,参数化的ReLU,$PReLU(x)= \begin{cases} x, x > 0 \\ \alpha x, x \leq 0 \end{cases}$。而这个$\alpha$是可学习的。那么当它学到$\alpha = 1$的时候,这不就是$f(x)=x$了吗?有意思的是Parameter ReLU这篇文章也是ResNet的作者He Kaiming提出的,这就让人浮想联翩了,他是否最开始也是希望通过这种方式解决identity mapping呢?

当然,identity mapping使得深层网络能够跳层或者与浅层网络等效,那么这样训练出来的深层网络究竟是不是我们想要的深层网络呢?继续深的网络到底还有没有意义呢?这些都还需要进一步研究。但就目前来说,更多的网络开始关注的是网络的宽度了。

各层的连接顺序

关于ResNet的连接顺序,作者He Kaiming专门重新发表了一篇文章讨论[15],提出了修改版的ResNet。

[15]中修改版的ResNet

直观上来看,左侧的大箭头代表网络identity map的走向。可以看到,左边原始的ResNet在$F(x)+x$之后有一个ReLU层,这样identity map只存在相邻的residual block中。但是如果把ReLU放在相加之前。那么可以看到,整个网络的identity map都是连通的,也就是说任意两层之间都存在$f(x)=x$。这篇文章中也说到了这样做确实会使得性能有所提升。

Inception

Google的Inception也是非常流行的网络结构,它进行了对网络宽度的探索。但是我始终觉得没有ResNet那样漂亮简洁。最初的Inception[16]中提出了在同一层分别使用$1 \times 1$、$3 \times 3$、$5 \times 5$的卷积核进行卷积,另外还有一个max pooling,然后结果堆叠在一起。这样有什么好处呢,我们之前说到卷积核的大小其实代表了这一层的感受野,能够感知上一层多大的区域,那么Inception结构就是希望在不确定应该感知多大的区域的时候,干脆就全都感知了,把这些特征一起拿来用。

初版Inception结构

后来的Inception融合了一些比如用多个小卷积核替代大卷积核的思想,又经过了几次改版。由于我对Inception的网络结构理解不深,就不在这里继续讨论了。

参考文献


  1. 1.A. Krizhevsky, I. Sutskever, and G. E. Hinton. ImageNet Classification with Deep Convolutional Neural Networks. NIPS 2012.
  2. 2.X. Han, Y. Zhong, L. Cao, and L. Zhang. Pre-Trained AlexNet Architecture with Pyramid Pooling and Supervision for High Spatial Resolution Remote Sensing Image Scene Classification. MDPI 2017.
  3. 3.S. Ioffe and C.Szegedy. Batch Normalization. ICML 2015.
  4. 4.I. Goodfellow, Y. Bengio and A. Courville. Deep Learning.
  5. 5.M. Jaderberg, K. Simonyan, A. Zisserman and K. Kavukcuoglu. Spatial Transformer Networks. NIPS 2015.
  6. 6.K. He, X. Zhang, S. Ren and J. Sun. Deep Residual Learning for Image Recognition. CVPR 2015.
  7. 7.G. Huang, Z. Liu and L.Maaten. Densely Connected Convolutional Networks. CVPR 2017.
  8. 8.A. G. Howard, M. Zhu, B. Chen and D.Kalenichenko. MobileNets: Efficient convolutional neural networks for mobile vision applications. arXiv preprint arXiv:1704.04861.
  9. 9.知乎 留德华叫兽 的回答。https://www.zhihu.com/question/38098038
  10. 10.G. Ian, S. Jonathon and S. Christian. Explaining and Harnessing Adversarial Examples. arXiv preprint arXiv:1412.6572.
  11. 11.A. L. Mass, A. Y. Hannun and A. Y. Ng. Rectifier Nonlinearities Improve Neural Network Acoustic Models. ICML 2013.
  12. 12.K. He, X. Zhang, S. Ren and J. Sun. Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. IEEE international conference on computer vision 2015.
  13. 13.L. Min, C. Qiang and Y. Shuicheng. Network in network. arXiv preprint arXiv:1312.4400.
  14. 14.G. E. Hinton. Deep Belief Networks. Scholarpedia 2009.
  15. 15.K. He, X. Zhang, S. Ren and J. Sun. Identity Mappings in Deep Residual Networks. ECCV 2016.
  16. 16.C. Szegedy, W. Liu, Y. Jia, P. Sermanet, S. Reed, D. Anguelov, D. Erhan, V. Vanhoucke and A. Rabinovich. Going deeper with convolutions. CVPR 2015.

通过Nginx + Uwsgi + Flask来搭建你的小型博客

什么是内网与外网呢?我的理解是,你从电信去连一个宽带,这个时候电信会给你分配一个外网地址,这个地址全世界只有你有,所以别人可以通过这个地址访问到唯一的一个你的服务器,而这个时候你觉得一个网络端口不够,你要用路由器发送wifi来给手机、笔记本电脑等设备用。这个时候路由器就给你的手机、笔记本电脑等设备分配内网地址,不然所有设备都去抢外网地址,外网地址又只有一个,就没有办法用不同的地址来区分这些设备。外网地址是唯一的,你的外网地址是你专享的,可是内网地址不一样,一般内网地址从127.0.0.1开始,所以大家内网地址都有这几个,外面的人根本访问不到。除了之前说的路由器,还有学校(学校哪舍得给你们每人一个外网地址)啊,公司提供的网络提供的都是内网地址。

在学校里的同学,像博主一样,就只能租服务器了,现在也有很多不错的服务器也不贵。

好了,现在我们有一个外网地址了,但是别的怎么来访问你呢?假如你的ip地址为a, 别人访问你的网站,用浏览器输入a,然后浏览器会向你的服务器发送一个http请求,别人给你发请求了,你不能不理别人吧,所以你需要一个服务器程序来处理别人的请求。别人在浏览器输入a,浏览器告诉你,我想要看你的网站,这个时候你的服务器就得回复别人吧,你得把你的网页发送过去,这就是服务器程序干的事情。

而根据本教程的框架,访问流程大概是这样的。

访客 — nginx — uwsgi — flask

本篇教程所用的服务器端程序为flask,我个人觉得很方便。推荐教程:欢迎进入Flask大型教程项目! 讲得非常详细,我这里就不详细讲了。

————————————————————————————————

Nginx设置

其实光使用flask别人就已经可以访问你的博客了,但是我们就直接把flask运行,我们的服务器被暴露在空气中,这个时候我们需要一个反向代理来隐藏我们的服务器。同时万一我们需要用这个服务器搭建多个网站,但是公网ip又只有一个,这时nginx就起作用了

[image not found]
(图片来自网络)

中间的是我们nginx,下面的是我们的服务器,这样就很安全。

我们用 apt-get 命令来安装。

1
apt-get install nginx

然后我们要去配置它。

首先它的配置文件在/etc/nginx/下,我们cd到这个目录。如果我们不动它的配置文件nginx.conf的话,我们看看这个文件写了什么。

1
2
3
4
5
6
##
# Virtual Host Configs
##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

我们看到这样一段,它include了两个目录里的文件。

而第二个目录告诉我们在这个sites-enabled中所有文件都会被读取。

好了,你可以在这里修改包含的目录,如果你不想的话,我们就直接在sites-enabled里面添加我们的网站配置文件吧。

我们在sites-enabled文件夹里面添加一个叫你站点名字的文件(其实随便什么名字,只是为了区分),我就添加了strickerlee.tk这个文件(.tk不是文件后缀,只是我的网址而已…)
然后我们在文件里打入如下代码

1
2
3
4
5
6
7
8
9
server {
listen 80;
server_name strickerlee.tk;

location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:8000;
}
}

好了,首先第一个设置是监听 80 端口。首先你要明白,一个服务器有很多个端口,如0.0.0.0:80 跟 0.0.0.0:81是访问两个不同的地方,而通过浏览器输入地址默认访问的是80端口,所以你需要监听80端口,这样nginx就把别人的请求听到了。而接下来的server_name可以用来区别不同的站点请求,所以你就可以搭建多个网站喽。如果你只搭建这一个网站,甚至不用有这一行。

后面就是跟uwsgi的交互了,127.0.0.1:8000的意思就是收到请求后,nginx就去找127.0.0.1:8000这个端口的uwsgi程序,把接下来的任务交给uwsgi。

对了这样做之后要重新载入我们的配置文件 /etc/init.d/nginx reload 这个可以让nginx重新载入我们的配置文件,这样我们之前的配置就生效了。

————————————————————————————————

uwsgi配置

首先我们要安装uwsgi,注意ubuntu最好不要直接apt-get install uwsgi,这样得到的uwsgi是没有python插件的,如果要继续还需要apt-get install uwsgi-plugin-python 来让它支持python,虽然这样理论上可以,但是博主用这个方法没有试成功。
最好的是 apt-get install python-dev,有了python环境,然后用pip install uwsgi来安装。

不然博主遇到的情况是报 没有 module 跟callable这样的参数这样的错。

我们用uwsgi来启动我们的python程序,使它正常运作。uwsgi的配置文件有很多种,分别有不同的格式,我们这里使用xml文件。

我们在随便一个目录下(但你要记得在哪里)建立一个config.xml(名字任取,但是后缀得是xml)

1
2
3
4
你的python服务端程序地址
run
app
127.0.0.1:81

好了,如上,里面的是模块,也就是你的服务器程序的名字,如你的是run.py那么模块为run,而callable则是你的借口对象,如果用前面贴的flask教程里面教的话就是app,因为里面执行了app.run()这个语句。

后面的就是我说的nginx处理完后把锅扔给uwsgi来处理接下来的事情,所以uwsgi必须去监听这个端口,这样就能跟nginx联系上了。你也可以更改这个ip跟端口,但是两个一定要能对上,也就是一起改。

最后一个是指用master模式来运行。

对了,还要删掉该目录下的default文件(因为它会跟你抢,nginx会导向它,我是这样理解的)。
现在有了这个配置文件,我们要去启动我们的uwsgi。

使用uwsgi -x 配置文件 来启动,注意了配置文件要加上路径,除非该配置文件是放在你现在的工作路径中的。
如 uwsgi -x ~/myBlog/config.xml 这时我的config.xml放在~/myBlog/目录下。
另外还有一个问题,在flask教程中会创建一个独立的python环境,你的flask框架啊什么的都是在这个独立环境中,而uwsgi是直接在默认python环境中运行的,所以会出现报import flask找不到flask模块的错误,博主还没有找到好的方法,所以只能在默认python环境中再安装一次这些框架,如果你有更好的建议可以告诉我。

这个时候就可以通过公网地址来访问了。注意博主提到的文中哪些是公网地址,哪些是私网地址哦。
最后唠叨一句,uwsgi向上述操作后就会一直显示其工作情况,你可以使用nohup 命令 & 来后台执行这些命令。

中山大学本科教务系统公选课网络爬虫实践

这学期开始,学着做了一点爬虫,用中山大学的教务系统来试了一试,希望能给正在学习网络爬虫的人带来一些帮助。因为博主在刚刚接触这个领域,所以如果文中有错误或者不妥的地方,欢迎大家指正。

—————————————————————————-

首先,是我的项目成果

Github项目地址:https://github.com/shawnlixx/sysu_elect_webcrawler

教务选课系统网址:http://uems.sysu.edu.cn/elect

目前只是能爬进去选课,实际意义不大。。。

—————————————————————————-

过程中遇到的问题及解决

在整个过程中首先遇到的是验证码的问题,要使验证码能够让用户看到,我首先想到的是把请求得到的验证码图片保存在本地,然后让用户自己打开图片来获取验证码。

于是有了如下代码
[image not found]

但是这样得到的code.png图片显示会非常模糊(难道是我眼花)
[image not found]
根本看不清内容。

直接将返回的数据写入文件会发生严重的失真,而使用StringIO处理(注:StringIO — Read and write strings as files.)返回的response ,然后用第三方库Image来处理效果会很好(果然不是眼花)
[image not found]
但是问题又来了,request 验证码图片之后,用户填写验证码后发包,根本通不过验证,显示http error 500。

用户名与密码都没有问题,只有可能是验证码的问题。那么网站怎么把发给你的验证码跟你提交的验证码匹配起来呢? 我想通过cookies 就可以。我监视发包后,发现在访问网站时会设置一个cookie。
[image not found]

如果该cookie存在,再次访问的时候不会设置新的值。如果没有请求网站,直接请求验证码,也会设置这个名字的cookie,但是用这个cookie验证时会失败(我之前就是这么干的,一直找不出原因。。。),必须先请求登陆页面,得到一个cookie,使用该cookie请求验证码,这样得到的验证码才是有效的,然后再带着cookie发包登陆,就可以成功了。

还有一个问题就是一定要记住url的参数编码格式是name1=value1&name2=value2…
所以用python的字典写好参数后一定要用urllib库中的urlencode函数将字典转化为url参数的编码格式,这也是为什么要在开头import urllib的原因。

后来发现同样的事情用Request模块会方便很多!比如自动转化为url参数格式,以及创建会话等。博主也是后来才用这个模块,发现非常方便。

—————————————————————————-

最后把要爬网页的源码看一看,用Beautifulsoup就很方便啦。以我的代码为蓝本,修改一下,以后抢课就方便多了!

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×