美文网首页人工智能时代(AI)Machine Learning & Recommendation & NLP & DL
无监督语义相似度匹配之Bert抽取文本特征实战

无监督语义相似度匹配之Bert抽取文本特征实战

作者: 王同学死磕技术 | 来源:发表于2020-01-12 18:54 被阅读0次

    今天记一次采用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等是经常被采用的文本向量化技术。接下来进入实战部分。

    实战部分

    准备工作:

    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
    笔者用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

    相关文章

      网友评论

        本文标题:无监督语义相似度匹配之Bert抽取文本特征实战

        本文链接:https://www.haomeiwen.com/subject/njkmactx.html