卷积神经网络是一类包含卷积计算且具有深度结构的前馈神经网络。基本结构包括输入层、卷积层(convolutional layer)、池化层(pooling layer,也称为取样层)、全连接层及输出层。
- 输入层:用于数据的输入
- 卷积层:使用卷积核进行特征提取和特征映射
- 激励层:由于卷积也是一种线性运算,因此需要增加非线性映射
- 池化层:下采样,对特征图稀疏处理,减少数据运算量。
- 全连接层:通常在CNN的尾部进行重新拟合,减少特征信息的损失
其主要特征有三:局部感知、参数共享和池化。下面分别解释这三个概念:
1.局部感知与全局感知,在传统的神经网络中,神经元之间的连接是全连接的,即n-1层的所有神经元与n层的所有神经元全部连接。但是在卷积神经网络中,n-1层与n 层的部分神经元连接。比如,图像是由一个个像素点构成,每个像素点有三个通道,分别代表RGB颜色,那么,如果一个图像的尺寸是(32,32,1),即代表这个图像的是一个长宽均为32,channel为1的图像(channel也叫depth,此处1代表灰色图像)。如果使用全连接的网络结构,即,网络中的神经与与相邻层上的每个神经元均连接,那就意味着我们的网络有32 * 32个神经元,hidden层采用了15个神经元,那么简单计算一下,参数个数(w和b)就有32* 32*15+15*10+15+10=15535个。怎么算出来的呢?答:图片是由像素点组成的,用矩阵表示的,32*32的矩阵,肯定是没法直接放到神经元里的,我们得把它“拍平”,变成一个32*32 = 1024的一列向量,这一列向量和隐含层的15个神经元连接,就有1024*15=15360个权重w,隐含层和最后的输出层的10个神经元连接,就有15*10=150个权重w,再加上隐含层的偏置项15个和输出层的偏置项10个,结果就是个153625参数了。参数太多了!那局部感知呢?由下图可以看出n-1到n的神经元之间都有边存在,每条边都有参数,由此可见全连接的参数很多。右边为局部连接,仅存在少量的边,参数减少了很多。对比左右两图可以明显看出,相应的参数减少。所谓局部感知是模拟人的视觉机制,拿过来一张画,人的视点按照每种顺序来看这幅画,比如从左到右,由上到下的顺序,这样一点一点的累计视觉停留,形成对整幅画的印象。
2.参数共享。一句话,在同一通道,用同一个卷积核卷积出的结果当然是共享了一套参数啊!解释一下,首先需要搞清楚卷积神经网络的卷积过程。
- Embedding:第一层是图中最左边的7乘5的句子矩阵,每行是词向量,维度dimension=5,这个可以类比为图像中的原始像素点。
- Convolution:然后经过 kernel_sizes=(2,3,4) 的一维卷积层,每个kernel_size 有两个输出 channel。
- MaxPolling:第三层是一个1-max pooling层,这样不同长度句子经过pooling层之后都能变成定长的表示。
- FullConnection and Softmax:最后接一层全连接的 softmax 层,输出每个类别的概率。
再解释一个图像的卷积计算整体流程:
原始输入样本的大小:32 x 32 x 1
- 第一次卷积:使用6个大小为
5 x 5
的卷积核,故卷积核的规模为(5 x 5) x 6
;卷积操作的stride
参数默认值为1 x 1
,32 - 5 + 1 = 28,并且使用ReLU对第一次卷积后的结果进行非线性处理,输出大小为28 x 28 x 6
; - 第一次卷积后池化:
kernel_size
为2 x 2
,输出大小变为14 x 14 x 6
; - 第二次卷积:使用16个卷积核,故卷积核的规模为
(5 x 5 x 6) x 16
;使用ReLU对第二次卷积后的结果进行非线性处理,14 - 5 + 1 = 10,故输出大小为10 x 10 x 16
; - 第二次卷积后池化:
kernel_size
同样为2 x 2
,输出大小变为5 x 5 x 16
; - 第一次全连接:将上一步得到的结果铺平成一维向量形式,5 x 5 x 16 = 400,即输入大小为
400 x 1
,W大小为120 x 400
,输出大小为120 x 1
; - 第二次全连接,W大小为
84 x 120
,输入大小为120 x 1
,输出大小为84 x 1
; - 第三次全连接:W大小为
10 x 84
,输入大小为84 x 1
,输出大小为10 x 1
,即分别预测为10类的概率值。
卷积计算:
#对应项乘积加和
feature_map1(1,1) = 1*1 + 0*(-1) + 1*1 + 1*(-1) = 1
feature_map1(1,2) = 0*1 + 1*(-1) + 1*1 + 1*(-1) = -1
```
feature_map1(3,3) = 1*1 + 0*(-1) + 1*1 + 0*(-1) = 2
卷积后特征矩阵大小计算公式:
- 原矩阵n*n
- 卷积核尺寸:f*f
- padding 大小 p
- 步长 s
新特征图大小= (n+2p-f)/s+1
3.池化。也叫降采样。作用是减少特征数量。最大值池化和平均值池化分别为区域内最大值和区域内的平均值,下图分别作展示:
Code
基于 PyTorch 实现文本 cnn 模型
def __init__(self, embeddings, input_dim, hidden_dim, num_layers, output_dim, max_len=40, dropout=0.5):
super(CNN, self).__init__()#将子类的参数传递给父类
self.emb = nn.Embedding(num_embeddings=embeddings.size(0), #第一个维度是词表长度
embedding_dim=embeddings.size(1), #第二个维度是指嵌入到多少维
padding_idx=0)
#embeds = nn.Embedding(2, 5) 这里的2表示有2个词,5表示5维度,其实也就是一个2x5的矩阵,所以如果你有1000个词,每个词希望是100维,你就可以这样建立一个word embedding,nn.Embedding(1000, 100)。
self.emb.weight = nn.Parameter(embeddings) #embedding 的参数
self.input_dim = input_dim
self.output_dim = output_dim
'''
Convolution
25 * 50, kernel(3, 50), out_map(16) --> (25-3+1) * 16
Max-Pooling
23 * 16 --> 1 * 16
Fully-connected
16 --> 50
'''
self.sen_len = max_len
self.sen_conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=(3, input_dim)) #卷积核尺寸如果不是方阵的话就要写全
#self.sen_conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3) #卷积核尺寸是方阵的话就写一个参数即可
self.sen_fc1 = nn.Linear(16, 50)
self.output = nn.Linear(50, output_dim)
def forward(self, sen_batch, sen_lengths):
"""
:param sen_batch: 一批句子文本
:param sen_lengths: 句子长度
"""
sen_batch = self.emb(sen_batch)
batch_size = len(sen_batch)
sen_batch = sen_batch.view(batch_size, 1, self.sen_len, self.input_dim)
sen_batch = F.relu(self.sen_conv1(sen_batch))
sen_batch = sen_batch.view(batch_size, 16, -1) #把数据变成一列
sen_batch = F.max_pool2d(sen_batch, (1, self.sen_len-3+1))
sen_batch = self.sen_fc1(sen_batch.view(batch_size, -1))
return sen_batch
结语
“世界这么大,我想去看看,去看看祖国的大山大河,去娶一个心地善良的姑娘,勾勒最美的诗意。”
“世界”就是原始embedding,“我”就是卷积核,“大山大河”就是一个卷积核的感受野,相对于“世界”这个大范围来说就是局部的;再“娶一个姑娘”这就是第二个卷积核;勾勒出来的“诗意”就是特征图,勾勒出 1 副诗意,输出通道数就是 1,n 幅诗意,输出通道数就是 n(前提是你得有 n 个姑娘,因为一个姑娘只能勾勒一份诗意,呸)你眼睛看到的只是你一个人看到的部分,看完之后对所见产生心理感受就是你卷积之后的结果。这样说明白了么?
参考资料:
Lucas:机器学习基本概念zhuanlan.zhihu.com代码地址:
https://github.com/zy1996code/nlp_basic_model/blob/master/cnn.py