今天记一次采用bert抽取句子向量的实战过程,主要是想感受一下bert抽取出来的句子特征向量是否真的具有不错的语义表达。
在此之前,我们来回顾一下,如果我们想拿到一个句子的特征向量可以采用什么样的方式(ps 这些下方总结只是笔者在工作中学到的一些文本向量抽取方式,可能并未收集齐全):
基于词袋模型(Bag of Words)
- Bag of Words : 主要思想是基于对句子中字出现的次数来构建句子向量,向量大小即为词表大小。可以采用的工具是gensim中的doc2bow
- TF-IDF:在BOW的基础上,考虑到每个字的重要程度,向量大小依然等于词表大小。可以采用的工具是gensim中的TfidfModel
基于无监督神经网络模型
- 词向量的平均(mean):直观理解就是将句子中每个词的词向量求平均,向量维度等于词向量维度。可以使用工具是gensim中的word2vec。
- Doc2Vec:主要借鉴了word2vec思想去训练句子向量,向量维度可以自己设置。可以使用工具是gensim中的doc2vec。
- Bert 生成的句子向量:BERT的每一层的输出其实都可以看作句子的向量,你可以有很多种方式去抽取文本向量,比如官方给出的取最后四层的句子向量拼接(输出维度:[maxlen,768*4])或者求平均(输出维度:[maxlen,768]。后续也许你需要在对句子向量求一下平均,这样文本向量的维度就为768。 可以采用的工具是官方开源的代码中的extract_features.py,当然也可以采用一些其他方式,我在实战部分会介绍另外一种。
基于文本主题的模型(这个词不是很严谨)
- LDA 主题模型:在词袋模型的基础上通过文本中主题的分布去表示文本向量,向量维度等于预设主题的个数。可以采用工具是gensim中的LdaModel
- LSI 隐语义模型:也是需要预设文本主题的个数,在词袋模型的基础上采用矩阵分解的方式去表示句子向量,向量维度等于预设主题的个数。可以采用工具是gensim中的LSI
行文至此突然发现gensim真是文本处理这块不可不学习的python包,但更重要的是需要掌握每种方法的原理以及使用场景。由于笔者也在学习过程当中,实战经验也不是很足,还不能够很好的描述各方法的原理和以及了解各方法的适用场景,就不在这误人子弟了。
当然你也可以针对有监督的任务去训练一个词向量,但是由于在工业界,有监督的数据通常很难获取。所以词袋模型,无监督的神经网络模型还有LDA等是经常被采用的文本向量化技术。接下来进入实战部分。
实战部分
准备工作:
- 首先安装 pip install https://github.com/bojone/bert4keras,bert4keras 是苏剑林大神开发基于keras的bert包,其中实现了各种对bert的妙用,很值得大家去学习一番
- 去这个网址下载bert 预训练权重—— https://github.com/ymcui/Chinese-BERT-wwm
import numpy as np
from bert4keras.bert import build_bert_model
from bert4keras.tokenizer import Tokenizer, load_vocab
接下来定义四个函数:
- build_tokenizer : 加载bert的tokenizer,主要是产生句子bert的输入向量。
- build_model:加载预训练的bert模型。
- generate_mask:将句子padding为0的的部分mask掉。
- extract_emb_feature:抽取句子向量,这里笔者实现的是直接将bert最后一层的句子向量取平均。
###加载tokenizer
def build_tokenizer(dict_path):
token_dict = load_vocab(dict_path)
tokenizer = Tokenizer(token_dict, do_lower_case=True)
return tokenizer
###加载 bert 模型
def build_model(config_path,checkpoint_path):
bert_model = build_bert_model(config_path,checkpoint_path)
return bert_model
###生成mask矩阵
def generate_mask(sen_list,max_len):
len_list = [len(i) if len(i)<=max_len else max_len for i in sen_list]
array_mask = np.array([np.hstack((np.ones(j),np.zeros(max_len-j))) for j in len_list])
return np.expand_dims(array_mask,axis=2)
###生成句子向量特征
def extract_emb_feature(model,tokenizer,sentences,max_len,mask_if=False):
mask = generate_mask(sentences,max_len)
token_ids_list = []
segment_ids_list = []
for sen in sentences:
token_ids, segment_ids = tokenizer.encode(sen,first_length=max_len)
token_ids_list.append(token_ids)
segment_ids_list.append(segment_ids)
result = model.predict([np.array(token_ids_list), np.array(segment_ids_list)])
if mask_if:
result = result * mask
return np.mean(result,axis=1)
你需要定义好预训练模型的词库文件,配置文件,模型参数地址,然后加载bert的tokenizer和模型。
"""
chinese_wwm_L-12_H-768_A-12.zip
|- bert_model.ckpt # 模型权重
|- bert_model.meta # 模型meta信息
|- bert_model.index # 模型index信息
|- bert_config.json # 模型参数
|- vocab.txt # 词表
"""
dict_path = "./chinese_wwm_L-12_H-768_A-12/ bert_model"
config_path = "./chinese_wwm_L-12_H-768_A-12/bert_config.json"
checkpoint_path = "./chinese_wwm_L-12_H-768_A-12/vocab.txt"
tokenizer = build_tokenizer(dict_path)
model = build_model(config_path,checkpoint_path)
然后笔者用bert提取了"这台苹果真是好用","这颗苹果真是好吃","苹果电脑很好","这台小米真是好用",四句话的文本向量,计算了一下两两句子间的cosine相似度。
sentences = ["这台苹果真是好用","这颗苹果真是好吃","苹果电脑很好","这台小米真是好用"]
sentence_emb = extract_emb_feature(model,tokenizer,sentences,200)
from sklearn.metrics.pairwise import cosine_similarity
cosine_similarity(sentence_emb)
结果如下:我们发现"这台苹果真是好用"和"这颗苹果真是好吃"的相似度(0.92)就比和"苹果电脑很好"的相似度(0.96)要低。这充分显示出bert对 水果“苹果” 和电脑“苹果”还是做了比较好的区别。
笔者用mask了padding为0的地方后,句子相识度结果排序变化不大,但是相似度数值之间的差距变大了(方差变大),这说明mask还是对文本向量的提取有比较正向的作用。
sentences = ["这台苹果真是好用","这颗苹果真好吃","苹果电脑很好","这台小米真是好用"]
sentence_emb = extract_emb_feature(model,tokenizer,sentences,200,mask_if=True)
from sklearn.metrics.pairwise import cosine_similarity
cosine_similarity(sentence_emb)
含mask
结语
如果熟悉bert的同学会知道,如下图所示:其实句子向量的第一个token[CLS] 的向量经常代表句子向量取做下游分类任务的finetune,这里为啥笔者未直接使用[CLS]的向量呢,这就是深度学习玄学的部分:具笔者了解[CLS]向量在下游任务做finetuning的时候会比较好的文本向量表示。而直接采用bert做特征抽取时[CLS]向量的威力就没那么强了。至于到底采用何种方式用bert抽取文本句子向量是最好的方式,我也不知道,还是留给大家探索去吧。
bert参考文献
https://www.lizenghai.com/archives/28761.html
https://github.com/bojone/bert4keras
网友评论