尝试用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 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-meansBirch
bert-cls0.380.32
bert-mean 0.520.41
bert倒数第二层cls0.470.42
bert倒数第二层mean0.520.41
bert- fun-tune-mean0.930.93
bert- fun-tune倒数第二层mean0.910.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

欢迎大家交流

 

 

 

 

 

 

 

  • 18
    点赞
  • 106
    收藏
    觉得还不错? 一键收藏
  • 39
    评论
评论 39
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值