nn.Conv1d
学习pytorch用于文本分类的时候,用到了一维卷积,花了点时间了解其中的原理,看网上也没有详细解释的博客,所以就记录一下。
Conv1d
class torch.nn.Conv1d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
in_channels(int) – 输入信号的通道。在文本分类中,即为词向量的维度
out_channels(int) – 卷积产生的通道。有多少个out_channels,就需要多少个1维卷积
kerner_size(int or tuple) - 卷积核的尺寸,卷积核的大小为(k,),第二个维度是由in_channels来决定的,所以实际上卷积大小为kerner_size*in_channels
stride(int or tuple, optional) - 卷积步长
padding (int or tuple, optional)- 输入的每一条边补充0的层数
dilation(int or tuple, `optional``) – 卷积核元素之间的间距
groups(int, optional) – 从输入通道到输出通道的阻塞连接数
bias(bool, optional) - 如果bias=True,添加偏置
举个例子:
conv1 = nn.Conv1d(in_channels=256,out_channels=100,kernel_size=2)
input = torch.randn(32,35,256)
# batch_size x text_len x embedding_size -> batch_size x embedding_size x text_len
input = input.permute(0,2,1)
out = conv1(input)
print(out.size())
#这里32为batch_size,35为句子最大长度,256为词向量
再输入一维卷积的时候,需要将32 * 25 * 256变换为32 * 256 * 35,因为一维卷积是在最后维度上扫的,最后out的大小即为:32 * 100 *(35-2+1)=32 * 100 * 34
附上一张图,可以很直观的理解一维卷积是如何用的:
图中输入的词向量维度为5,输入大小为7*5,一维卷积和的大小为2、3、4,每个都有两个,总共6个特征。
对于k=4,见图中红色的大矩阵,卷积核大小为45,步长为1。这里是针对输入从上到下扫一遍,输出的向量大小为((7-4)/1+1)1=4*1,最后经过一个卷积核大小为4的max_pooling,变成1个值。最后获得6个值,进行拼接,在经过一个全连接层,输出2个类别的概率。
附上一个代码来详解:
#其中,embedding_size=256, feature_size=100, window_sizes=[3,4,5,6], max_text_len=35
class TextCNN(nn.Module):
def __init__(self, config):
super(TextCNN, self).__init__()
self.is_training = True
self.dropout_rate = config.dropout_rate
self.num_class = config.num_class
self.use_element = config.use_element
self.config = config
self.embedding = nn.Embedding(num_embeddings=config.vocab_size,
embedding_dim=config.embedding_size)
self.convs = nn.ModuleList([
nn.Sequential(nn.Conv1d(in_channels=config.embedding_size,
out_channels=config.feature_size,
kernel_size=h),
# nn.BatchNorm1d(num_features=config.feature_size),
nn.ReLU(),
nn.MaxPool1d(kernel_size=config.max_text_len-h+1))
for h in config.window_sizes
])
self.fc = nn.Linear(in_features=config.feature_size*len(config.window_sizes),
out_features=config.num_class)
if os.path.exists(config.embedding_path) and config.is_training and config.is_pretrain:
print("Loading pretrain embedding...")
self.embedding.weight.data.copy_(torch.from_numpy(np.load(config.embedding_path)))
def forward(self, x):
embed_x = self.embedding(x)
#print('embed size 1',embed_x.size()) # 32*35*256
# batch_size x text_len x embedding_size -> batch_size x embedding_size x text_len
embed_x = embed_x.permute(0, 2, 1)
#print('embed size 2',embed_x.size()) # 32*256*35
out = [conv(embed_x) for conv in self.convs] #out[i]:batch_size x feature_size*1
#for o in out:
# print('o',o.size()) # 32*100*1
out = torch.cat(out, dim=1) # 对应第二个维度(行)拼接起来,比如说5*2*1,5*3*1的拼接变成5*5*1
#print(out.size(1)) # 32*400*1
out = out.view(-1, out.size(1))
#print(out.size()) # 32*400
if not self.use_element:
out = F.dropout(input=out, p=self.dropout_rate)
out = self.fc(out)
return out
embed_x一开始大小为3235256,32为batch_size。经过permute,变为3225635,输入到自定义的网络后,out中的每一个元素,大小为321001,共有4个元素。在dim=1维度上进行拼接后,变为324001,在经过view,变为32400,最后通过400num_class大小的全连接矩阵,变为32*2。
nn.Conv2d
nn.Conv2d()的使用、形参与隐藏的权重参数
二维卷积应该是最常用的卷积方式了,在Pytorch的nn模块中,封装了nn.Conv2d()类作为二维卷积的实现。使用方法和普通的类一样,先实例化再使用。下面是一个只有一层二维卷积的神经网络,作为nn.Conv2d()方法的使用简介:
class Net(nn.Module):
def __init__(self):
nn.Module.__init__(self)
self.conv2d = nn.Conv2d(in_channels=3,out_channels=64,kernel_size=4,stride=2,padding=1)
def forward(self, x):
print(x.requires_grad)
x = self.conv2d(x)
return x
print(net.conv2d.weight)
print(net.conv2d.bias)
它的形参由Pytorch手册可以查得,前三个参数是必须手动提供的,后面的有默认值。接下来将一一介绍:也许有细心的同学已经发现了,emm…卷积层最重要的可学习参数——权重参数和偏置参数去哪了?在Tensorflow中都是先定义好weight和bias,再去定义卷积层的呀!别担心,在Pytorch的nn模块中,它是不需要你手动定义网络层的权重和偏置的,这也是体现Pytorch使用简便的地方。当然,如果有小伙伴适应不了这种不定义权重和偏置的方法,Pytorch还提供了nn.Functional函数式编程的方法,其中的F.conv2d()就和Tensorflow一样,要先定义好卷积核的权重和偏置,作为F.conv2d()的形参之一。
回到nn.Conv2d上来,我们可以通过实例名.weight和实例名.bias来查看卷积层的权重和偏置,如上图所示。还有小伙伴要问了,那么它们是如何初始化的呢?
首先给结论,在nn模块中,Pytorch对于卷积层的权重和偏置(如果需要偏置)初始化都是采用He初始化的,因为它非常适合于ReLU函数。这一点大家看Pytorch的nn模块中卷积层的源码实现就能清楚地发现了,当然,我们也可以重新对权重等参数进行其他的初始化,可以查看其他教程,此处不再多言。
in_channels
这个很好理解,就是输入的四维张量[N, C, H, W]中的C了,即输入张量的channels数。这个形参是确定权重等可学习参数的shape所必需的。
out_channels
也很好理解,即期望的四维输出张量的channels数,不再多说。
kernel_size
卷积核的大小,一般我们会使用5x5、3x3这种左右两个数相同的卷积核,因此这种情况只需要写kernel_size = 5这样的就行了。如果左右两个数不同,比如3x5的卷积核,那么写作kernel_size = (3, 5),注意需要写一个tuple,而不能写一个列表(list)。
stride = 1
卷积核在图像窗口上每次平移的间隔,即所谓的步长。这个概念和Tensorflow等其他框架没什么区别,不再多言。
padding = 0
Pytorch与Tensorflow在卷积层实现上最大的差别就在于padding上。
Padding即所谓的图像填充,后面的int型常数代表填充的多少(行数、列数),默认为0。需要注意的是这里的填充包括图像的上下左右,以padding = 1为例,若原始图像大小为32x32,那么padding后的图像大小就变成了34x34,而不是33x33。
当然,上面的公式过于复杂难以记忆。大多数情况下的kernel_size、padding左右两数均相同,且不采用空洞卷积(dilation默认为1),因此只需要记 O = (I - K + 2P)/ S +1这种在深度学习课程里学过的公式就好了。
dilation = 1
这个参数决定了是否采用空洞卷积,默认为1(不采用)。从中文上来讲,这个参数的意义从卷积核上的一个参数到另一个参数需要走过的距离,那当然默认是1了,毕竟不可能两个不同的参数占同一个地方吧(为0)。
更形象和直观的图示可以观察Github上的Dilated convolution animations,展示了dilation=2的情况。
groups = 1
决定了是否采用分组卷积,groups参数可以参考groups参数详解
bias = True
即是否要添加偏置参数作为可学习参数的一个,默认为True。
padding_mode = ‘zeros’
即padding的模式,默认采用零填充。