Sentiment Analysis with BERT Pytorch【半成品】

文章目录

https://curiousily.com/posts/sentiment-analysis-with-bert-and-hugging-face-using-pytorch-and-python/

导入各种包,设置基本参数

import transformers
from transformers import BertModel, BertTokenizer, AdamW, get_linear_schedule_with_warmup
import torch
import numpy as np
import pandas as pd
import seaborn as sns
from pylab import rcParams
import matplotlib.pyplot as plt
from matplotlib import rc
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report
from collections import defaultdict
from textwrap import wrap
from torch import nn, optim
from torch.utils.data import Dataset, DataLoader
%matplotlib inline
%config InlineBackend.figure_format='retina'
sns.set(style='whitegrid', palette='muted', font_scale=1.2)
HAPPY_COLORS_PALETTE = ["#01BEFE", "#FFDD00", "#FF7D00", "#FF006D", "#ADFF02", "#8F00FF"]
sns.set_palette(sns.color_palette(HAPPY_COLORS_PALETTE))
rcParams['figure.figsize'] = 12, 8
RANDOM_SEED = 42
np.random.seed(RANDOM_SEED)
torch.manual_seed(RANDOM_SEED)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

使用的是Google Play 的评论数据,用下面的程序,从谷歌drive上下载数据集

import gdown
url1 = "https://drive.google.com/uc?id=1S6qMioqPJjyBLpLVz4gmRTnJHnjitnuV"
url2 = 'https://drive.google.com/uc?id=1zdmewp7ayS4js4VtrJEHzAheSW-5NBZv'
gdown.download(url1)  #'apps.csv'
gdown.download(url2)  #'reviews.csv'

读取数据

df = pd.read_csv("reviews.csv")
df.head()

有这些类:

list(df.columns)

['userName',
 'userImage',
 'content',
 'score',
 'thumbsUpCount',
 'reviewCreatedVersion',
 'at',
 'replyContent',
 'repliedAt',
 'sortOrder',
 'appId']

最主要的还是contentscore,分别是评论的内容和打分(1-5分)

查看数据形状

df.shape

(15746, 11)


查看缺失值情况

df.info()

在这里插入图片描述
可以看到content和score没有缺失值,其他的并不影响


查看各个打分的分布情况

sns.countplot(df['score'])
plt.xlabel('review score')

在这里插入图片描述
相当不平衡,所以考虑将其转化为negative,neutral,positive类,进行情感分析


将socre 转化为0,1,2

def to_sentiment(rating):
  rating = int(rating)
  if rating <= 2:
    return 0 # 负面
  elif rating == 3:
    return 1 #中立
  else:
    return 2 # 正面
df['sentiment'] = df.score.apply(to_sentiment)  #sentiment列里面存的也只是[0,1,2]
class_names = ['negative', 'neutral', 'positive']

画出新的分布图

ax = sns.countplot(df["sentiment"])
plt.xlabel('review sentiment')
ax.set_xticklabels(class_names) #将原本的0,1,2设置为['negative', 'neutral', 'positive']

在这里插入图片描述
这样看数据就差不多平衡了


将纯文本数据转化成数字,有些要求如下:

  • 添加special tokens特殊字符来分句,
  • 将句子变成固定长度,补长截短
  • 创建attention mask,也就是对padding补长的空字符标记为0(1,1,1,1,1,1,0,0,0)

Transformer库中有许多训练好的模型

PRE_TRAINED_MODEL_NAME = 'bert-base-cased'

你可以使用cased(有大小写的)模型,作者试验了下,发现有cased模型效果更好,直觉来说,BAD比bad更具有情感。

加载与训练过的tokenizer

tokenizer = BertTokenizer.from_pretrained(PRE_TRAINED_MODEL_NAME)

我们用下面这句话来理解tokenization

sample_txt = 'When was I last outside? I am stuck at home for 2 weeks.'

一些基本的操作将文本转化为token,再将token转化成id(unique integers)

tokens = tokenizer.tokenize(sample_txt)
token_ids = tokenizer.convert_tokens_to_ids(tokens)
print(f' Sentence: {sample_txt}')   #以 f开头表示在字符串内支持大括号内的python 表达式
print(f' Tokens: {tokens}')
print(f'Token IDs: {token_ids}')
Sentence: When was I last outside? I am stuck at home for 2 weeks.
  Tokens: ['When', 'was', 'I', 'last', 'outside', '?', 'I', 'am', 'stuck', 'at', 'home', 'for', '2', 'weeks', '.']
Token IDs: [1332, 1108, 146, 1314, 1796, 136, 146, 1821, 5342, 1120, 1313, 1111, 123, 2277, 119]

Special tokens

1.[SEP],句子结尾的标志【marker for ending of a sentence】

tokenizer.sep_token

返回的是’[SEP]’ 字符串

tokenizer.sep_token_id

返回的是整形:102(SEP的token id 是102)
在这里插入图片描述
2.[CLS] 要将这个特殊token加在每个句子的开头,这样BERT才知道我们在做分类任务
(we must add this token to the start of each sentence, so BERT knows we’re doing classification)

tokenizer.cls_token, tokenizer.cls_token_id

生成一个元组,cls的id是101
在这里插入图片描述
3.’[PAD]’ 用于句子补长,id是0

tokenizer.pad_token, tokenizer.pad_token_id

在这里插入图片描述
4.[UNK],BERT知道训练集中的所有token,其他的可以使用Unknown token来进行编码

tokenizer.unk_token, tokenizer.unk_token_id

以上这些都可以使用 encode_plus() 方法来实现

encoding = tokenizer.encode_plus(
  sample_txt,
  max_length=32,
  add_special_tokens=True, # Add '[CLS]' and '[SEP]'
  return_token_type_ids=False, # 如果不是做sentence pair任务,所有的token_type_id都是0,
  pad_to_max_length=True,
  truncation = True,
  return_attention_mask=True,
  return_tensors='pt',  # Return PyTorch tensors
)
encoding.keys()

dict_keys(['input_ids', 'attention_mask'])
print(encoding)
{'input_ids': tensor([[ 101, 1332, 1108,  146, 1314, 1796,  136,  146, 1821, 5342, 1120, 1313, 1111,  123, 2277,  119,  102,    0,    0,    0,    0,    0,    0,    0, 0,    0,    0,    0,    0,    0,    0,    0]]), 
 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])}

将tokenizaiton逆转(将token id还原成句子),就能看到special token

tokenizer.convert_ids_to_tokens(encoding['input_ids'][0])
['[CLS]',
 'When',
 'was',
 'I',
 'last',
 'outside',
 '?',
 'I',
 'am',
 'stuck',
 'at',
 'home',
 'for',
 '2',
 'weeks',
 '.',
 '[SEP]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]',
 '[PAD]']

选择句子得长度Choosing Sequence Length
BERT只接受固定长度的文本输入,我们会用一个简单的方法来选择max_length。
首先存储每个评论(句子)tokenize后的token长度

token_lens = []

for txt in df.content:
  tokens = tokenizer.encode(txt, max_length=512)
  token_lens.append(len(tokens))
sns.distplot(token_lens)
plt.xlim([0, 256]); # x轴长度
plt.xlabel('Token count')

在这里插入图片描述
可以看到大部分句子的长度都小于128,但安全起见,选择160

MAX_LEN = 160

我们有创建PyTorch数据集所需的所有构造模块。 我们开始做吧:

class GPReviewDataset(Dataset):
  def __init__(self, reviews, targets, tokenizer, max_len):
    self.reviews = reviews
    self.targets = targets
    self.tokenizer = tokenizer
    self.max_len = max_len
    
  def __len__(self):
    return len(self.reviews)
    
  def __getitem__(self, item):
    review = str(self.reviews[item])
    target = self.targets[item]
    encoding = self.tokenizer.encode_plus(
      review,
      add_special_tokens=True,
      max_length=self.max_len,
      return_token_type_ids=False,
      pad_to_max_length=True,
      truncate = True,
      return_attention_mask=True,
      return_tensors='pt',
    )
    return {
      'review_text': review,
      'input_ids': encoding['input_ids'].flatten(), #flatten()将张量平铺成一行
      'attention_mask': encoding['attention_mask'].flatten(),
      'targets': torch.tensor(target, dtype=torch.long)
    }

分词器为我们完成了大部分繁重的工作。 我们也返回了评论文本,因此可以更轻松地评估模型中的预测。


将数据分成训练集和测试集,验证集:0.9:0.05:0.05

df_train, df_test = train_test_split(
  df,
  test_size=0.1,
  random_state=RANDOM_SEED
)

df_val, df_test = train_test_split(
  df_test,
  test_size=0.5,
  random_state=RANDOM_SEED
)
df_train.shape, df_val.shape, df_test.shape

在这里插入图片描述


我们还需要创建几个数据加载器。 这是一个辅助函数:

def create_data_loader(df, tokenizer, max_len, batch_size):
  ds = GPReviewDataset(
    reviews=df.content.to_numpy(),
    targets=df.sentiment.to_numpy(),
    tokenizer=tokenizer,
    max_len=max_len
  )
  return DataLoader(
    ds,
    batch_size=batch_size,
    num_workers=4
  )
BATCH_SIZE = 16
train_data_loader = create_data_loader(df_train, tokenizer, MAX_LEN, BATCH_SIZE)
val_data_loader = create_data_loader(df_val, tokenizer, MAX_LEN, BATCH_SIZE)
test_data_loader = create_data_loader(df_test, tokenizer, MAX_LEN, BATCH_SIZE)

让我们看一下训练数据加载器中的示例批次(an example batch):

data = next(iter(train_data_loader)) #next() 返回迭代器的下一个项目。要和生成迭代器的 iter() 函数一起使用。
data.keys()
dict_keys(['review_text', 'input_ids', 'attention_mask', 'targets'])
print(data['input_ids'].shape)
print(data['attention_mask'].shape)
print(data['targets'].shape)

(运行上面那段程序,太耗时了,结果如下)

torch.Size([16, 160])
torch.Size([16, 160])
torch.Size([16])

加载预训练好的基础bert模型

bert_model = BertModel.from_pretrained(PRE_TRAINED_MODEL_NAME)

并尝试将其用于样本文本的编码

#上面有介绍如何对sample_txt进行tokenize,并编码
sample_txt = 'When was I last outside? I am stuck at home for 2 weeks.'

在这里插入图片描述

output = bert_model(
  input_ids=encoding['input_ids'],
  attention_mask=encoding['attention_mask']
)

last_hidden_state是模型最后一层的hidden_state序列。

print(output.last_hidden_state.shape)

得:

torch.Size([1, 32, 768])

从上面这个输出可以看出,我们这32个tokens(样本得长度是32)都有hidden sate(神经元得输出结果叫hidden sate)
但768是代表什么意思?这是前向传播网络中得隐含层单元数量(hidden units)
可以通过

print(bert_model.config.hidden_size) 查询
在这里插入图片描述


print(output.pooler_output.shape)

得:

torch.Size([1, 768])

可以将pooler_output理解为summary of content

Obtaining the pooled_output is done by applying the BertPooler on last_hidden_state


我们可以使用所有的这些知识来创建基于BERT模型的分类器

class SentimentClassifier(nn.Module):
  def __init__(self, n_classes):
    super(SentimentClassifier, self).__init__()
    self.bert = BertModel.from_pretrained(PRE_TRAINED_MODEL_NAME)
    self.drop = nn.Dropout(p=0.3)
    self.out = nn.Linear(self.bert.config.hidden_size, n_classes)
  def forward(self, input_ids, attention_mask):
    _, pooled_output = self.bert(
      input_ids=input_ids,
      attention_mask=attention_mask
    )
    output = self.drop(pooled_output)
    return self.out(output

从这步开始后面的步骤都完成不下去

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值