(3)TextCNN和TextRNN

终于到了现代一点的模型了,为了整明白这些折腾了很久。很多大佬都是基于图像领域讲解的,但是对于我这种没整过图像的就很难受了。接下来就谈一谈自己对两个模型的理解

TextCNN

这是很著名的cs231n给出的模型图,可以看到CNN有四个层组成

conv:卷积层

就是用一个称之为卷积核的矩阵(滤波器(filter))做内积,就是逐个元素相乘再求和。每一步求出来的是一个值!具体如图

具体看算式。这样就把输入抽象化了

这个矩阵有几个参数,常用的就是深度,步长和填充值。

深度:上图中的conv层是深度=1的特殊情况,深度=2就两个二维矩阵,就两个滤波器,就两个filter。 

 步长:一次卷积滑动几步

填充值:为了总长能被步长整除,所以可能要加几圈0.

这里depth(深度)=2,stride(步长)=2,padding(填充值)=1

这里有几个特殊的创新点

局部感知:每次滤波器只对一个局部进行卷积,就像人一次只能看到固定长度的文字

权重共享:滤波器的权重不变

RELU,激励函数

Relu的全称是线性整流函数,作用是:1、克服梯度消失问题2、加快训练速度

Pooled,池化层

 取某个区域内的平均,极值等,主要是压缩数据,减少过拟合。

 平铺层

这里将特征最终输出为最终结果,采用一个全连接。具体可以参考全连接网络

下面来聊一聊代码,代码来自https://github.com/graykode/nlp-tutorial/blob/master/2-1.TextCNN/TextCNN.ipynb

里面代码写的真的很好,强推强推。

哦对,代码之前先把论文内的模型图放出来

这里输入采用Ont-hot编码,输入层的尺寸是[word_len,dic_len] ,其中word_len是一次输入单词的个数,dic_len是词典的单词量。因为是用Ont-hot嘛。但是这样作者认为这样不够,太稀疏了怎么训练?因此你看到的输入其实是经过Embedding过来的结果!经过一层Embedding层后我们才得到了[word_len,em_len]的输入,其中em_len是经过Embedding层后输出的维度。

红色的框框是一个卷积核一次卷积的大小,也就是卷积核的大小。可以看到这里的卷积核是不规则的。虽然论文里叫他卷积核,但我认为也有一些N-grams的意思。图片中有两个这样的图所以batch size=2。这样一个三维的输入就做成了!

接下来得到卷积后的结果再池化,全连接就结束了,还是比较简单的。下面再看一下代码

    embedding_size = 2 # embedding size
    sequence_length = 3 # sequence length
    num_classes = 2 # number of classes
    filter_sizes = [2, 2, 2] # n-gram windows
    num_filters = 3 # number of filters
class TextCNN(nn.Module):
    def __init__(self):
        super(TextCNN, self).__init__()
        self.num_filters_total = num_filters * len(filter_sizes)
        self.W = nn.Embedding(vocab_size, embedding_size)
        self.Weight = nn.Linear(self.num_filters_total, num_classes, bias=False)
        self.Bias = nn.Parameter(torch.ones([num_classes]))
        self.filter_list = nn.ModuleList([nn.Conv2d(1, num_filters, (size, embedding_size)) for size in filter_sizes])

    def forward(self, X):
        embedded_chars = self.W(X) # [batch_size, sequence_length, sequence_length]
        embedded_chars = embedded_chars.unsqueeze(1) # add channel(=1) [batch, channel(=1), sequence_length, embedding_size]

        pooled_outputs = []
        for i, conv in enumerate(self.filter_list):
            # conv : [input_channel(=1), output_channel(=3), (filter_height, filter_width), bias_option]
            h = F.relu(conv(embedded_chars))
            # mp : ((filter_height, filter_width))
            mp = nn.MaxPool2d((sequence_length - filter_sizes[i] + 1, 1))
            # pooled : [batch_size(=6), output_height(=1), output_width(=1), output_channel(=3)]
            pooled = mp(h).permute(0, 3, 2, 1)
            pooled_outputs.append(pooled)

        h_pool = torch.cat(pooled_outputs, len(filter_sizes)) # [batch_size(=6), output_height(=1), output_width(=1), output_channel(=3) * 3]
        h_pool_flat = torch.reshape(h_pool, [-1, self.num_filters_total]) # [batch_size(=6), output_height * output_width * (output_channel * 3)]
        model = self.Weight(h_pool_flat) + self.Bias # [batch_size, num_classes]
        return model

分开来看一下

        self.num_filters_total = num_filters * len(filter_sizes)
        self.W = nn.Embedding(vocab_size, embedding_size)
        self.Weight = nn.Linear(self.num_filters_total, num_classes, bias=False)
        self.Bias = nn.Parameter(torch.ones([num_classes]))
        self.filter_list = nn.ModuleList([nn.Conv2d(1, num_filters, (size, embedding_size)) for size in filter_sizes])
num_filters_total定义了卷积核的数量,我的理解是作者想尝试不同尺寸的卷积核的效果。其实一个的话也可以实现。

self.W定义了Embedding层,大小是[词典长度,想变成的长度]。作者把One-hot编码变成了一个2维的embedding变量

Weight和Bias为线性层的参数

filter_list就是卷积层的集合啦

embedded_chars = self.W(X) # [batch_size, sequence_length, sequence_length]
        embedded_chars = embedded_chars.unsqueeze(1) # add channel(=1) [batch, channel(=1), sequence_length, embedding_size]

首先将模型放入embedding层生成词向量,此时的词向量是三维的,但是torch的CNN输入要是四维,缺一个通道。所以第二行使用unsqueeze加了一个通道,这样就能愉快的训练了

pooled_outputs = []
        for i, conv in enumerate(self.filter_list):
            # conv : [input_channel(=1), output_channel(=3), (filter_height, filter_width), bias_option]
            h = F.relu(conv(embedded_chars))
            # mp : ((filter_height, filter_width))
            mp = nn.MaxPool2d((sequence_length - filter_sizes[i] + 1, 1))
            # pooled : [batch_size(=6), output_height(=1), output_width(=1), output_channel(=3)]
            pooled = mp(h).permute(0, 3, 2, 1)
            pooled_outputs.append(pooled)

这里是使用不同的卷积层进行操作,这里使用了一个小小的技巧

F.relu(conv(embedded_chars))因为conv层不需要更改输出就可以进入激活层,所以可以嵌套使用

但是进入池化层的话需要更改一下输入的量,并使用permute函数对输出的值交换一下次序,方便下一次继续操作。

h_pool = torch.cat(pooled_outputs, len(filter_sizes)) # [batch_size(=6), output_height(=1), output_width(=1), output_channel(=3) * 3]
        h_pool_flat = torch.reshape(h_pool, [-1, self.num_filters_total]) # [batch_size(=6), output_height * output_width * (output_channel * 3)]
        model = self.Weight(h_pool_flat) + self.Bias # [batch_size, num_classes]
        return model

这里将所有结果拼接之后输入到线性层,线性层得到最后的分类结果并输出

具体的训练过程大家可以看上面的链接,这里就不加赘述了

TextRNN

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值