以前文本聚类多以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 np
config_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.63251007 0.2030236 0.07936534 ... 0.49122632 -0.20493352
0.2575253 ]
[-0.7588351 0.09651865 1.0718756 ... -0.6109694 0.04312154
0.03881441]
[ 0.5477043 -0.792117 0.44435206 ... 0.42449304 0.41105673
0.08222899]
[-0.2924238 0.6052722 0.49968526 ... 0.8604137 -0.6533166
0.5369075 ]
[-0.7473459 0.49431565 0.7185162 ... 0.3848612 -0.74090636
0.39056838]
[-0.8741375 -0.21650358 1.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 Model
from 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, Dense
from 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 D
if __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 Birch
feature = 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
欢迎大家交流