因为研究方向为关系抽取,所以在文本的处理方面,一维卷积方法是很有必要掌握的,简单介绍下加深学习印象。
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维卷积 - kernel_size(
int
ortuple
) - 卷积核的尺寸,卷积核的大小为(k,),第二个维度是由in_channels来决定的,所以实际上卷积大小为kernel_size*in_channels - stride(
int
ortuple
,optional
) - 卷积步长 - padding (
int
ortuple
,optional
)- 输入的每一条边补充0的层数 - dilation(
int
ortuple
, `optional``) – 卷积核元素之间的间距 - groups(
int
,optional
) – 从输入通道到输出通道的阻塞连接数 - bias(
bool
,optional
) - 如果bias=True
,添加偏置
有一位博主解释的很清楚,我附上他的内容,阅读连接我会在下方提供。
1 conv1 = nn.Conv1d(in_channels=256,out_channels=100,kernel_size=2) 2 input = torch.randn(32,35,256) 3 # batch_size x text_len x embedding_size -> batch_size x embedding_size x text_len 4 input = input.permute(0,2,1) 5 out = conv1(input) 6 print(out.size())
这里32为batch_size,35为句子最大长度,256为词向量
再输入一维卷积的时候,需要将32*35*256变换为32*256*35,因为一维卷积是在最后维度上扫的,最后out的大小即为:32*100*(35-2+1)=32*100*34
图中输入的词向量维度为5,输入大小为7*5,一维卷积和的大小为2、3、4,每个都有两个,总共6个特征。
对于k=4,见图中红色的大矩阵,卷积核大小为4*5,步长为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
1 class TextCNN(nn.Module): 2 def __init__(self, config): 3 super(TextCNN, self).__init__() 4 self.is_training = True 5 self.dropout_rate = config.dropout_rate 6 self.num_class = config.num_class 7 self.use_element = config.use_element 8 self.config = config 9 10 self.embedding = nn.Embedding(num_embeddings=config.vocab_size, 11 embedding_dim=config.embedding_size) 12 self.convs = nn.ModuleList([ 13 nn.Sequential(nn.Conv1d(in_channels=config.embedding_size, 14 out_channels=config.feature_size, 15 kernel_size=h), 16 # nn.BatchNorm1d(num_features=config.feature_size), 17 nn.ReLU(), 18 nn.MaxPool1d(kernel_size=config.max_text_len-h+1)) 19 for h in config.window_sizes 20 ]) 21 self.fc = nn.Linear(in_features=config.feature_size*len(config.window_sizes), 22 out_features=config.num_class) 23 if os.path.exists(config.embedding_path) and config.is_training and config.is_pretrain: 24 print("Loading pretrain embedding...") 25 self.embedding.weight.data.copy_(torch.from_numpy(np.load(config.embedding_path))) 26 27 def forward(self, x): 28 embed_x = self.embedding(x) 29 30 #print('embed size 1',embed_x.size()) # 32*35*256 31 # batch_size x text_len x embedding_size -> batch_size x embedding_size x text_len 32 embed_x = embed_x.permute(0, 2, 1) 33 #print('embed size 2',embed_x.size()) # 32*256*35 34 out = [conv(embed_x) for conv in self.convs] #out[i]:batch_size x feature_size*1 35 #for o in out: 36 # print('o',o.size()) # 32*100*1 37 out = torch.cat(out, dim=1) # 对应第二个维度(行)拼接起来,比如说5*2*1,5*3*1的拼接变成5*5*1 38 #print(out.size(1)) # 32*400*1 39 out = out.view(-1, out.size(1)) 40 #print(out.size()) # 32*400 41 if not self.use_element: 42 out = F.dropout(input=out, p=self.dropout_rate) 43 out = self.fc(out) 44 return out
embed_x一开始大小为32*35*256,32为batch_size。经过permute,变为32*256*35,输入到自定义的网络后,out中的每一个元素,大小为32*100*1,共有4个元素。在dim=1维度上进行拼接后,变为32*400*1,在经过view,变为32*400,最后通过400*num_class大小的全连接矩阵,变为32*2。
我在关系抽取的论文阅读中,作者使用CNN卷积代码如下:
1 class CNN3(nn.Module): 2 def __init__(self, config): 3 super(CNN3, self).__init__() 4 self.config = config 5 self.word_emb = nn.Embedding(config.data_word_vec.shape[0], config.data_word_vec.shape[1]) 6 self.word_emb.weight.data.copy_(torch.from_numpy(config.data_word_vec)) 7 self.word_emb.weight.requires_grad = False 8 9 # self.char_emb = nn.Embedding(config.data_char_vec.shape[0], config.data_char_vec.shape[1]) 10 # self.char_emb.weight.data.copy_(torch.from_numpy(config.data_char_vec)) 11 # char_dim = config.data_char_vec.shape[1] 12 # char_hidden = 100 13 # self.char_cnn = nn.Conv1d(char_dim, char_hidden, 5) 14 15 self.coref_embed = nn.Embedding(config.max_length, config.coref_size, padding_idx=0) 16 self.ner_emb = nn.Embedding(7, config.entity_type_size, padding_idx=0) 17 input_size = config.data_word_vec.shape[1] + config.coref_size + config.entity_type_size #+ char_hidden 18 #140维 19 self.out_channels = 200 20 self.in_channels = input_size 21 self.kernel_size = 3 22 self.stride = 1 23 self.padding = int((self.kernel_size - 1) / 2) 24 self.cnn_1 = nn.Conv1d(self.in_channels, self.out_channels, self.kernel_size, self.stride, self.padding) 25 self.cnn_2 = nn.Conv1d(self.out_channels, self.out_channels, self.kernel_size, self.stride, self.padding) 26 self.cnn_3 = nn.Conv1d(self.out_channels, self.out_channels, self.kernel_size, self.stride, self.padding) 27 self.max_pooling = nn.MaxPool1d(self.kernel_size, stride=self.stride, padding=self.padding) 28 self.relu = nn.ReLU() 29 self.dropout = nn.Dropout(config.cnn_drop_prob) 30 self.bili = torch.nn.Bilinear(self.out_channels+config.dis_size, self.out_channels+config.dis_size, config.relation_num) 31 self.dis_embed = nn.Embedding(20, config.dis_size, padding_idx=10) 32 33 # model(context_idxs, context_pos, context_ner, context_char_idxs, input_lengths, h_mapping, t_mapping, relation_mask, dis_h_2_t, dis_t_2_h) 34 def forward(self, context_idxs, pos, context_ner, context_char_idxs, context_lens, h_mapping, t_mapping, relation_mask, dis_h_2_t, dis_t_2_h): 35 # para_size, char_size, bsz = context_idxs.size(1), context_char_idxs.size(2), context_idxs.size(0) 36 # context_ch = self.char_emb(context_char_idxs.contiguous().view(-1, char_size)).view(bsz * para_size, char_size, -1) 37 # context_ch = self.char_cnn(context_ch.permute(0, 2, 1).contiguous()).max(dim=-1)[0].view(bsz, para_size, -1) 38 39 # self.word_emb(context_idxs).shape = [40,512,config.data_word_vec.shape[1]] 40 # self.coref_embed(pos) = [40,512,config.coref_size] 41 # self.coref_embed(pos) = [40,512,config.coref_size] 42 sent = torch.cat([self.word_emb(context_idxs), self.coref_embed(pos), self.ner_emb(context_ner)], dim=-1) 43 sent = sent.permute(0, 2, 1)#torch.Size([40, 140, 512]) 44 45 # batch * embedding_size * max_len 46 x = self.cnn_1(sent) #(b,140,512)->(b,200,512) 47 x = self.max_pooling(x)#(b,200,512)->(b,200,512) 48 x = self.relu(x) 49 x = self.dropout(x) 50 51 x = self.cnn_2(x)#(b,200,512)->(b,200,512) 52 x = self.max_pooling(x)#(b,200,512)->(b,200,512) 53 x = self.relu(x) 54 x = self.dropout(x) 55 56 x = self.cnn_3(x)#(b,200,512)->(b,200,512) 57 x = self.max_pooling(x)#(b,200,512)->(b,200,512) 58 x = self.relu(x) 59 x = self.dropout(x) 60 context_output = x.permute(0, 2, 1) #(b,512,200) 因为每一步都pading=1加了两列,所以最后输出没有区别 61 start_re_output = torch.matmul(h_mapping, context_output) #(b,1800,512)*(b,512,200) ->(b,1800,200) 62 end_re_output = torch.matmul(t_mapping, context_output) 63 s_rep = torch.cat([start_re_output, self.dis_embed(dis_h_2_t)], dim=-1) #(b,1800,200+20) 64 t_rep = torch.cat([end_re_output, self.dis_embed(dis_t_2_h)], dim=-1) 65 predict_re = self.bili(s_rep, t_rep) #(b,1800,97) 66 return predict_re
作者定义了三层CNN,第一层通过增加通道数并使用大小为3的卷积核来提取特征,为了使句子最大长度维度每次经过卷积层不变而设计了padding
并不是直接通过CNN进行关系分类,先通过第一层提取特征,后经过两层CNN(数据shape未变化,功能?),将通过3层CNN得到的context_ouput,此时context_output已经含有了一些文档级信息,然后将h_mapping中含有的头实体mask的信息与ontext_ouput相乘,将t_mapping中含有的尾实体mask的信息与ontext_ouput相乘。
接着将头实体到尾实体的距离特征与start_re_output的特征维度上进行拼接,将尾实体到头实体的距离特征与end_re_output的特征维度上进行拼接
最后将两个数据均送入与定义好的双线性层进行预测,得到预测的结果。
可以看到35-37行代码作者尝试使用Glove训练好的字符的预训练量,可能遇到问题放弃了。因此效果并不好。
Conv2d
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
in_channels |
Number of channels in the input image |
out_channels |
Number of channels produced by the convolution |
kernel_size |
卷积核尺寸 |
stride |
步长,控制cross-correlation的步长,可以设为1个int型数或者一个(int, int)型的tuple。 |
padding |
(补0):控制zero-padding的数目。 |
dilation |
(扩张):控制kernel点(卷积核点)的间距 |
groups |
(卷积核个数):通常来说,卷积个数唯一,但是对某些情况,可以设置范围在1 —— in_channels中数目的卷积核: |
bias |
adds a learnable bias to the output. |
实例:
1 import torch 2 x = torch.randn(2,1,7,3) 3 conv = torch.nn.Conv2d(1,8,(2,3)) 4 res = conv(x) 5 print(res.shape) # shape = (2, 8, 6, 1)
输入x:
[ batch_size, channels, height_1, width_1 ]
batch_size | 一个batch中样例的个数 | 2 |
channels | 通道数,也就是当前层的深度 | 1 |
height_1 | 图片的高 | 7 |
width_1 | 图片的宽 | 3 |
Conv2d的参数
[ channels, output, height_2, width_2 ]
channels | 通道数,和上面保持一致,也就是当前层的深度 | 1 |
output | 输出的深度 | 8 |
height_2 | 过滤器filter的高 | 2 |
weight_2 | 过滤器filter的宽 | 3 |
输出res
[batch_size,output,height_3,width_3]
batch_size | 一个batch中样例的个数,同上 | 2 |
output | 输出的深度 | 8 |
height_3 | 卷积结果的高度 | h1-h2+1 = 7-2+1 = 6 |
weight_3 | 卷积结果的宽度 | w1-w2+1 = 3-3+1 = 1 |
Shape:
$(N,C_{in},H_{in},W_{in})$
$(N,C_{out},H_{out},W_{out})$
$H_{out} = \left\lfloor\frac{H_{in} + 2 \times \text{padding}[0] - \text{dilation}[0] \times (\text{kernel_size}[0] - 1) - 1}{\text{stride}[0]} + 1\right\rfloor$
$W_{out} = \left\lfloor\frac{W_{in} + 2 \times \text{padding}[1] - \text{dilation}[1] \times (\text{kernel_size}[1] - 1) - 1}{\text{stride}[1]} + 1\right\rfloor$
参考:
pytorch 中nn.MaxPool1d() 和nn.MaxPool2d()对比: https://www.jianshu.com/p/c5b8e02bedbe
pytorch中的nn.Bilinear的计算原理详解: https://blog.csdn.net/nihate/article/details/90480459
pytorch之nn.Conv1d详解:https://blog.csdn.net/sunny_xsc1994/article/details/82969867
pytorch中的matmul: https://blog.csdn.net/yu_1628060739/article/details/102720385
torch.nn.Conv2d()函数详解:https://blog.csdn.net/m0_37586991/article/details/87855342