前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >LSTM-based Sentiment Classification

LSTM-based Sentiment Classification

作者头像
mathor
发布2020-02-15 17:33:45
6810
发布2020-02-15 17:33:45
举报
文章被收录于专栏:mathor

第一次使用需要在实验环境中下载相关的python库

代码语言:javascript
复制
!pip install torch
!pip install torchtext
!python -m spacy download en

我们初步的设想是,首先将一个句子输入到LSTM,这个句子有多少个单词,就有多少个输出,然后将所有输出通过一个Linear Layer,这个Linear Layer的out_size是1,起到Binary Classification的作用

然后对于每个输入,我们需要先要进行Embedding,把每个单词转换成固定长度的vector,再送到LSTM里面去,假设每个单词我们都用一个长度为100的vector来表示,每句话有seq个单词(动态的,每句话的seq长度不一定一样),那么输入的shape就是[seq, b, 100]。最终通过Linear Layer输出的$y$的shape就是[b]

我们使用的数据集是torchtext库里面的IMDB数据集

代码语言:javascript
复制
import torch
from torch import nn, optim
from torchtext import data, datasets

print("GPU:",torch.cuda.is_available())
torch.manual_seed(123)

TEXT = data.Field(tokenize='spacy')
LABEL = data.LabelField(dtype=torch.float)
train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)

print('len of train data:', len(train_data))
print('len of test data:', len(test_data))

print(train_data.examples[15].text)
print(train_data.examples[15].label)

# word2vec, glove
TEXT.build_vocab(train_data, max_size=10000, vectors='glove.6B.100d')
LABEL.build_vocab(train_data)

batch_size = 30
device = torch.device('cuda')
train_iterator, test_iterator = data.BucketIterator.splits(
    (train_data, test_data),
    batch_size = batch_size,
    device = device
)

上面这些代码里面有些参数不懂不要紧,因为只是加载数据集而已,不是很重要。如果想要了解torchtext,可以看这篇文章

接下来比较重要,定义网络结构

代码语言:javascript
复制
class RNN(nn.Module):
  def __init__(self, vocab_size, embedding_dim, hidden_dim):
    super(RNN, self).__init__()
    # [0-10001] => [100]
    self.embedding = nn.Embedding(vocab_size, embedding_dim)
    # [100] => [200]
    self.rnn = nn.LSTM(embedding_dim, hidden_dim, num_layers=2
              ,bidirectional=True, dropout=0.5)
    # [256*2] => [1]
    self.fc = nn.Linear(hidden_dim*2, 1)
    self.dropout = nn.Dropout(0.5)
  def forward(self, x):
    # [seq, b, 1] => [seq, b, 100]
    embedding = self.dropout(self.embedding(x))
    # output: [seq, b, hid_dim*2]
    # hidden/h: [num_layers*2, b, hid_dim]
    # cell/c: [num_layers*2, b, hid_dim]
    output, (hidden, cell) = self.rnn(embedding)
    # [num_layers*2, b, hid_dim] => 2 of [b, hid_dim] => [b, hid_dim*2]
    hidden = torch.cat([hidden[-2], hidden[-1]], dim=1)
    # [b, hid_dim*2] => [b, 1]
    hidden = self.dropout(hidden)
    out = self.fc(hidden)
    return out

nn.embedding(m, n)其中m表示单词的总数目,n表示词嵌入的维度(每个单词编码为长度为n的vector)

然后就是LSTM本身,这里就不做过多解释了,参数介绍可以查看我的这篇文章,其中有一点之前的文章中没有提到,就是这个bidirectional参数,设置为True表示这个LSTM是双向的,很好理解,之前学过的RNN都是单向的,很有局限,例如下面这句话

  • 我今天不舒服,我打算___一天

如果是单向RNN,这个空肯定会填"医院"或者"睡觉"之类的,但是如果是双向的,它就能知道后面跟着"一天",这时"请假","休息"之类的被选择的概率就会更大

最后的Fully Connected Layer可以理解为把所有输出的信息做个综合,转化为一个一维的tensor

代码语言:javascript
复制
rnn = RNN(len(TEXT.vocab), 100, 256)
pretrained_embedding = TEXT.vocab.vectors
print('pretrained_embedding:', pretrained_embedding.shape)
rnn.embedding.weight.data.copy_(pretrained_embedding)
print('embedding layer inited.')

Embedding层如果不初始化,生成的权值是随机的,所以必须要初始化,这个权值是通过下载Glove编码方式得到的,下载得到的其实就是个weight,直接覆盖掉embedding里面的weight,通过rnn.embedding.weight.data.copy_(pretrained_embedding)的方式

然后我们看一下怎么Train这个网络

代码语言:javascript
复制
import numpy as np

def binary_acc(preds, y):
  """
  get accuracy
  """
  preds = torch.round(torch.sigmoid(preds))
  correct = torch.eq(preds, y).float()
  acc = correct.sum() / len(correct)
  return acc

def train(rnn, iterator, optimizer, criteon):
  avg_acc = []
  rnn.train()
  for i, batch in enumerate(iterator):
    # [seq, b] => [b, 1] => [b]
    pred = rnn(batch.text).squeeze()
    loss = criteon(pred, batch.label)
    acc = binary_acc(pred, batch.label).item()
    avg_acc.append(acc)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if i%10 == 0:
      print(i, acc)
  avg_acc = np.array(avg_acc).mean()
  print('avg acc:', avg_acc)

Train其实很简单了,就是把text丢进去,然后返回一个shape为[b, 1]的output,利用squeeze()函数,去掉其中维数为1的维度,shape变成[b],方便与label进行比较

同样的道理,Test也非常简单

代码语言:javascript
复制
def eval(rnn, iterator, criteon):
  avg_acc = []
  rnn.eval()
  
  with torch.no_grad():
    for batch in iterator:
      # [b, 1] => [b]
      pred = rnn(batch.text).squeeze()
      loss = criteon(pred, batch.label)
      acc = binary_acc(pred, batch.label).item()
      avg_acc.append(acc)
  avg_acc = np.array(avg_acc).mean()
  print(">>test:", avg_acc)

最后定义一下loss和optimizer

代码语言:javascript
复制
optimizer = optim.Adam(rnn.parameters(), lr=1e-3)
criteon = nn.BCEWithLogitsLoss().to(device)
rnn.to(device)

其中BCEWithLogitsLoss()主要用于二分类问题。nn.BCELoss()是针对二分类用的交叉熵,这俩都是用于二分类,有什么区别呢?区别在于BCEWithLogitsLoss将Sigmoid层和BCELoss合并在了一起。如果还是觉得不理解,可以看下这篇博客

ipynb版本代码

py版本代码

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档