最近看了机器之心的对bert源码的解读,做如下笔记:
句子情感分类
首先是整个流程,主要分为两部分:
- 对句子进行处理,我理解的类似于embedding,类似于Word2Vec。
- 外接模型,后续可以加Logistics模型,LSTM模型等。
首先对模型进行词嵌入:
之后用Scikit Learn库进行训练集、测试集的划分
每个预测值是怎么计算出来的?
假设我们有句子[ a visually stunning rumination on love ] ,要对这个句子进行情感分类,第一步就是用BERT分词器tokenizer将单词word分成词token(这里好像对中文而言?),然后在开始和结束加入**[CLS]** 和 [SEP]
这个过程只需要一行代码就能完成
tokenizer.encode("a visually stunning rumination on love", add_special_tokens=True)
??? 那么这个tokenizer是哪里来的呢?这个我们下面再说
然后我们得到的数据就可以传给BERT了
最后整个模型可以想象成这个过程:
下面讨论代码的实现:
import numpy as np
import pandas as pd
import torch
import transformers as ppb # pytorch transformers
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split
1.导入数据
数据集的链接如下:https://github.com/clairett/pytorch-sentiment-classification/。我们可以直接将其导入为一个 pandas 数据帧。
df = pd.read_csv('https://github.com/clairett/pytorch-sentiment-classification/raw/master/data/SST2/train.tsv', delimiter='\t', header=None)
我们看一下数据的格式
我们在看一下数据的分布:
2. 加载pre_trained BERT model
# For DistilBERT:
model_class, tokenizer_class, pretrained_weights = (ppb.DistilBertModel, ppb.DistilBertTokenizer, 'distilbert-base-uncased')
## Want BERT instead of distilBERT? Uncomment the following line:
#model_class, tokenizer_class, pretrained_weights = (ppb.BertModel, ppb.BertTokenizer, 'bert-base-uncased')
# Load pretrained model/tokenizer
tokenizer = tokenizer_class.from_pretrained(pretrained_weights)
model = model_class.from_pretrained(pretrained_weights)
从这里我们可以看出: tokenizer是直接从tokenizer_class 中拿出来的,而tokenizer_class 是transformers的一个工具类,model也是transformers中的工具类。
在colab中由于算力的限制,我们只取了前2000个样本。即batch_1
下面重点
- Tokenization
把句子分成BERT的输入格式——token、CLS、SEP,(还有个mask机制,这里需要再详细了解一下)tokenized = batch[0]_1.apply((lambda x: tokenizer.encode(x, add_special_tokens=True)
- Padding
分词后,我们有了一个list格式的数据,但是长短不一,我们需要对数据进行一下padding,把不够的补0.
具体代码:
max_len = 0
for i in tokenized.values:
if len(i) > max_len:
max_len = len(i)
padded = np.array([i + [0]*(max_len-len(i)) for i in tokenized.values])
3. Mask
还需要对数据进行一个Mask操作,因为否则的话会对模型造成一定的混淆,具体操作就是新建一个矩阵attention_mask,值为0或1,在padded值不为的地方为1,为0的地方为0;
attention_mask = np.where(padded != 0, 1, 0)
attention_mask.shape
Mode#1
现在我们有的padded矩阵,就是对原始的token矩阵进行补齐,还有attention_mask矩阵。,把他们转换成torch.tensor的形式。
input_ids = torch.tensor(padded)
attention_mask = torch.tensor(attention_mask)
直接输入给model,获得经过BERT之后的矩阵!!
with torch.no_grad():
last_hidden_states = model(input_ids, attention_mask=attention_mask)
结果如图:
提取第0个position的数据
features = last_hidden_states[0][:,0,:].numpy()
labels = batch_1[1]
Model#2 Train/Test Split
train_features, test_features, train_labels, test_labels = train_test_split(features, labels)
lr_clf = LogisticRegression()
lr_clf.fit(train_features, train_labels)
lr_clf.score(test_features, test_labels)
最后得分0.856