python|尝试用bert做文本聚类

尝试用bert做文本聚类 以前文本聚类多以TF-IDF构建词权重的方法进行,在本文中尝试用bert提取的向量做文本聚类。对于bert模型,尝试提取不同层的特征,尝试对bert做fun-tune,观察相应特征对应的文本聚类的效果
数据
数据使用的是百度2020语言比赛的数据,该数据是标注并分类好的,所以在聚类的情况下,省去了聚类时对k值的搜索,同时可以可以根据标注好的数据和聚类得到的数据比较,从侧面评价聚类的效果
下载地址
https://pan.baidu.com/s/1hIiGutDm73vo7lw31H-tfw提取码 4gqn

工具和环境
python3 ,bert4keras 0.7.5
强烈推荐苏剑林大佬的bert4keras https://github.com/bojone/bert4keras

使用bert提取特征

from bert4keras.backend import keras from bert4keras.models import build_transformer_model from bert4keras.tokenizers import Tokenizer import numpy as npconfig_path = '/root/kg/bert/chinese_L-12_H-768_A-12/bert_config.json' checkpoint_path = '/root/kg/bert/chinese_L-12_H-768_A-12/bert_model.ckpt' dict_path = '/root/kg/bert/chinese_L-12_H-768_A-12/vocab.txt'tokenizer = Tokenizer(dict_path, do_lower_case=True)# 建立分词器 model = build_transformer_model(config_path, checkpoint_path)# 建立模型,加载权重# 编码测试 token_ids, segment_ids = tokenizer.encode(u'语言模型')print('\n ===== predicting =====\n')print(model.predict([np.array([token_ids]), np.array([segment_ids])]))""" 输出: [[[-0.632510070.20302360.07936534 ...0.49122632 -0.20493352 0.2575253 ] [-0.75883510.096518651.0718756... -0.61096940.04312154 0.03881441] [ 0.5477043-0.7921170.44435206 ...0.424493040.41105673 0.08222899] [-0.29242380.60527220.49968526 ...0.8604137-0.6533166 0.5369075 ] [-0.74734590.494315650.7185162...0.3848612-0.74090636 0.39056838] [-0.8741375-0.216503581.338839...0.5816864-0.4373226 0.56181806]]] """

这段代码是bert4keras的示例代码,可以将 '语言模型' 这句话的特征提取出来,本文中采用下面两种方式作表征句向量
1取特征第一个位置的向量作为句向量,即 model.predict([np.array([token_ids]), np.array([segment_ids])])[0][0]
2对所有特征取平均作为句向量
处理数据,得到数据集对应的词向量
#! -*- coding: utf-8 -*- # 测试代码可用性: 提取特征from bert4keras.backend import keras from bert4keras.models import build_transformer_model from bert4keras.tokenizers import Tokenizer import numpy as np import json from keras.models import Modelfrom bert4keras.backend import keras, K from bert4keras.models import build_transformer_model from bert4keras.tokenizers import Tokenizer from bert4keras.optimizers import Adam from bert4keras.snippets import sequence_padding, DataGenerator from bert4keras.snippets import open from bert4keras.layers import ConditionalRandomField from keras.layers import Dense from keras.models import Model from tqdm import tqdm from keras.layers import Dropout, Densefrom keras_bert import extract_embeddings# 如果需要禁止GPU的话可以使用下面的环境变量 对于不同的系统可能为-1 或者 0 # os.environ['CUDA_VISIBLE_DEVICES'] = '-1' config_path = 'D:/model/chinese_L-12_H-768_A-12/bert_config.json' checkpoint_path = 'D:/model/chinese_L-12_H-768_A-12/bert_model.ckpt' dict_path = 'D:/model/chinese_L-12_H-768_A-12/vocab.txt'def load_data(filename):D = [] with open(filename,encoding='utf-8') as f: for l in f: l = json.loads(l) D.append(l['text']) return Dif __name__ == "__main__":# 词向量获取方法 cls,mean, vector_name = 'cls' tokenizer = Tokenizer(dict_path, do_lower_case=True)# 建立分词器 model = build_transformer_model(config_path, checkpoint_path)# 建立模型,加载权重 maxlen = 70# 读取处理数据 f1 = 'D:/cluster/data/train.json' res = load_data(f1) output = []print('开始提取')# 根据提取特征的方法获得词向量 for r in res: token_ids, segment_ids = tokenizer.encode(r,max_length=maxlen)if vector_name == 'cls': cls_vector = model.predict([np.array([token_ids]), np.array([segment_ids])])[0][0] output.append(cls_vector) elif vector_name == 'mean': new = [] vector = model.predict([np.array([token_ids]), np.array([segment_ids])])[0] for i in range(768): temp = 0 for j in range(len(vector)): temp += vector[j][i] new.append(temp/(len(vector))) output.append(new)print('保存数据') np.savetxt("text_vectors.txt",output)

如果设置提取方式为cls 取特征第一个位置的向量作为句向量 设置为mean 对所有特征取平均作为句向量 最后会将结果保存到txt文件中
对数据聚类
得到了我们想要的数据,我们就可以对他们聚类了,这里使用sklearn,分别用kmeans 和Birch聚类
from sklearn.cluster import KMeans from sklearn.cluster import Birchfeature = np.loadtxt("text_vectors.txt") clf = KMeans(n_clusters=9) s = clf.fit(feature) kn_pre = clf.predict(feature)birch_pre = Birch(branching_factor=10, n_clusters = 9, threshold=0.5,compute_labels=True).fit_predict(feature

聚类评估
由于我们使用的数据是已经标注好的,我们可以对比聚类后的数据和原来标注数据,类似于从上帝视角去观察聚类的结果,当然也可以使用一些评价聚类效果的参数,比如calinski_harabaz_score
同时为了更深入的测试bert的聚类效果,尝试提取了bert倒数第2层,倒数第3层的特征。并且用了10%的语料进行了简单的fun-tune,即简单的分类微调,提取了fun-tune后的bert的特征 一起进行评估,结果如下
k-means Birch
bert-cls 0.38 0.32
bert-mean 0.52 0.41
bert倒数第二层cls 0.47 0.42
bert倒数第二层mean 0.52 0.41
bert- fun-tune-mean 0.93 0.93
bert- fun-tune倒数第二层mean 0.91 0.91
图中分数为依据标注数据计算得到为聚类准确率,分数越大,表示模型聚类效果越好。
可以看到在这个任务中bert进行微调后得到的句向量求平均得到的特征,可以非常好的聚类,但是没有fun-tune的bert 特征聚类的效果一般,并不能直接使用。

一些思考
为什么bert-fun-tune后的特征会表现的这么好?
我们在用fun-tune之后 虽然fun-tune语料很小,但此时它也是一个能力非常强的分类模型,即使不用聚类的方法 直接让它对文本分类可能结果就非常的好。这时再做聚类似乎多此一举。但是我认为这个实验还是有意义的。依据著名的聚类假设:同类的文档相似度较大,而不同类的文档相似度较小。假如我们拿到一批大量的没有标注的数据,我们认为这个数据是可分类的,我们可以先观察它的小部分样本,简单按照我们期望的方向进行分类,分成比如 ['财经','产品行为','交往','其他'],用这个足够小的样本微调,再进行聚类,可能获得更好聚类效果。
是否bert的最后一层的效果是最好的?
最近看了一篇关于bert的论文 How to Fine-Tune Bert for Text Classification 其中有讨论了使用bert不同层 的分类效果,所以也尝试了一下。对于不同的语料不同的任务,这个问题可能有不同的答案。总体来说不同层的效果出入不大,大家可以都尝试一下。

Github
最后附上项目代码 https://github.com/hgliyuhao/cluster
微调后的bert权重
https://pan.baidu.com/s/1TPIQBUPcsCDMvPXmx8d9sg 提取码 9f63
欢迎大家交流




【python|尝试用bert做文本聚类】


    推荐阅读