长文本分类
文本分类是把文本打上对应的类别标签,在互联网中的应用场景很多,如评论、弹幕等。作为比较强大的预训练模型Bert,用来做文本分类有很好的效果。本文介绍pytorch版本的Bert长文本分类,但由于Bert的输入长度有现在,最长只能输入512个字符,但长文本通常有几千或者几万个字,所以本文采用了两种策略来进行长文本分类,一是取文本开头的512字符输入到Bert,二是对文本先采用文本摘要算法,得到重要的部分再取前面512字符输入到Bert。
文本摘要算法Textrank介绍
Textrank算法的思想来自于PageRank,PageRank是谷歌用来进行网页排序的算法,其思想是赋予每个网页一个权值,然后计算每个网页指向其他网页的权值,之后根据计算的权值求和来进行权值更新,一般,最后按权值来进行排名。
在文本中,就将每一个句子看成一个个体,句子与句子间的链接权值就用句子间的相似度来代替,只需要进行句子间相似度的计算就可以更新权值了,假设向量B是句子的rank权值,每一个元素代表一个句子的排名权值,矩阵A是一个二维矩阵,它的维度数是句子的数量,用矩阵A*向量B来进行迭代,当向量B收敛之后就完成了,向量B中元素的值就代表对应句子的排名权值。
句子相似度计算
句子相似度的计算,一般用两个句子的向量来进行运算,比如计算余弦相似度。然而怎么取句子向量比较重要,句子向量要能充分表示句子的特征,这样计算的相似度才准确。现在一般取句子向量的话,会用到词向量,或者Bert。用词向量的话,是查表找到每一个词的向量,在计算平均,得到句子的向量。而Bert则是输入一个句子,用输出的CLS代表整个句子的向量,当然也可以用Bert获取每个字的向量再求平均获得句子向量。
1、使用glove词向量来获取句子向量并进行Textrank
# -*- coding:utf-8 -*-
# 导入所需的库,没有的话用pip install 库名字安装
import numpy as np
import pandas as pd
import nltk
# networkx库内置了常用的图与复杂网络分析算法,可以方便的进行复杂网络数据分析、仿真建模等工作。
import networkx as nx
# 下载一次就行了,第一次下载完再注释掉
#nltk.download('punkt')
# 下载停用词,下载一次就行,同上
#nltk.download('stopwords')
# 加载进来
from nltk.corpus import stopwords
import re
# 将用余弦相似度计算两个句子之间的相似度
from sklearn.metrics.pairwise import cosine_similarity
from bs4 import BeautifulSoup
def get_sentences_list(raw_text: str):
#BeautifulSoup对象,参数 文档字符串,html解析器,文档编码
return [s for s in BeautifulSoup(raw_text, 'html.parser')._all_strings()]
# 这里使用glove中文维基的词向量生成一个word_embeddings查找列表
word_embeddings = {
}
f = open('glove.txt', encoding='utf-8')
# 按行读取
for line in f:
# 按照空格进行分割
values = line.split()
# values 数组的第一个位置是当前的英语单词
word = values[0]
coefs = np.asarray(values[1:], dtype='float32')
word_embeddings[word] = coefs
f.close()
# print("词表:",len(word_embeddings['我']))
# 停用词表
def stopwordslist():
stopwords = [line.strip() for line in open('chineseStopWords.txt',encoding = "utf-8").readlines()]
return stopwords
def text_import(text):
text_extract = ''
sentence_list = []
text = get_sentences_list(text)
for node in text[0].split('。'):
if len(node) != 0:
sentence_list.append(node)
sentence_num = len(sentence_list)
sentences_vectors = []
print(len(sentence_list))
# print(sentence_list[0])
#去除非中文的字符
pre_sentences = pd.Series(sentence_list).str.replace('[^\u4e00-\u9fa5]', ' ')
stop_words = stopwordslist()
# print(stop_words)
# 定义移除停用词函数
def remove_stopwords(str):
# 遍历数组中的每个元素,如果这个元素不在停用词列表,则加入事先准备的字符串中
sen = ' '.join([i for i in str if i not in stop_words])
return sen
# 去除停用词
pre_sentences = [remove_stopwords(r.split()) for r in pre_sentences]
print(pre_sentences)
# 获取句子特征向量,用来计算后面的相似度,这里取每个句子中词向量合并的平均值来作为该句子的特征向量
# 所有句子的词向量表示
sentences_vectors = []
for sen in pre_sentences:
# 如果句子长度不为0
if len(sen) != 0:
v = sum([word_embeddings.get(w, np.zeros((300,))) for w in sen.split()]) / (len(sen.split()) + 1e-2)
else:
v = np.zeros((300,))
sentences_vectors.append(v)
# 用上面获取到的句子的向量计算一个相似度矩阵
# 这里使用余弦相似度来计算每个句子的相似性
# 首先定义一个n乘n的零矩阵,然后用句子间的余弦相似度填充矩阵,这里n是句子的总数。
similarity_matrix = np.zeros((len(pre_sentences), len(pre_sentences)))
print(len(pre_sentences))
for i in range(len(pre_sentences)):
for j in range(len(pre_sentences)):
# 这里的if用于排序自己与自己计算相似度
if i != j:
similarity_matrix[i][j] = cosine_similarity(
sentences_vectors[i].reshape(1, -1), sentences_vectors[j].reshape(1, -1)
)
# 输出相似度矩阵
print(similarity_matrix)
# 将上面获得的相似性矩阵sim_mat转换为图结构。这个图的节点为句子,边用句子之间的相似性分数表示。
# 在这个图上,使用networkx库提供的PageRank算法来得到句子排名,句子排名越高,说明其越重要,就是摘要
nx_graph = nx.from_numpy_array(similarity_matrix)
scores = nx.pagerank(nx_graph)
print(scores)
# 根据排名来选取句子作为摘要
# 遍历sentences数组,i是当前的位置角标,s是当前的句子
# scores[i]:从scores中取出第i个位置的分数与当前句子组成一对
# 将所有的分数,句子信息组成的list赋值给ranked_sentences
# sorted:并排序,reverse=True降序
ranked_sentences = sorted(
((scores[i], s) for i, s in enumerate(sentence_list)), reverse=True
)
# 排序
# for i in range(1):
# print(ranked_sentences[i][1])
# # 打印得分最高的前面几个句子,即为摘要,这里修改句子的数量就可以修改摘要
for i in range(sentence_num):
if len(text_extract) < 512:
# 获取文本的摘要
text_extract += ranked_sentences[i][1] + '。'
else:
break
return text_extract[:512]
text="徐峥,回不到过去徐峥,回不到过去喜剧演员徐峥、爆米花商业片导演徐峥,同时也是文青的徐峥。毒眸《我和我的家乡》的五个单元故事里,若票选最令人印象深刻的角色,《最后一课》单元的中范伟饰演的“范老师”可能是人气最高的。豆瓣高赞短评写道:“范伟把我的泪点掐住了”、“教科书级别的演技”;微博上,范伟雨中奔跑的花絮冲上热搜第一,评论说:“范伟真的厉害,从他跑出教室我就开始哭,一直到单元结束”。也有观众能够指出,范伟动人的表演不止是范伟自己的功劳。作为这个单元的导演,恰好在徐峥的导演方法论里,表演也是重要的话题。“导演对表演,必须达到一种切身的理解,要完全能够站在演员的立场上,为演员的表演提供有帮助的指示。”10月12日,徐峥在丝绸之路国际电影节的大师班上,传递了这样一种观点。对表演的理解深度,或许是徐峥作品里容易诞生“高光演技”的原因之一:从《我和我的祖国》里惊艳的小演员“冬冬”韩昊霖,到《我和我的家乡》里“掐住观众泪点”的范伟,都凭借演技引发了话题。另一方面,叙事功力也是《最后一课》受到好评的另一个原因。豆瓣一则热评写道:“(最后一课)展现出徐峥某个程度的细腻,他很擅长从小的切角去展开一个宏大叙题。”《最后一课》中,村民为帮助身患阿尔兹海默症的范老师,设下“重返1992年”的骗局,最终骗局的败露,也揭示了家乡的发展变化遮掩不住。徐峥在大师班上这般解释如此构思的原因:“因为观众是非常害怕说教的”,所以要通过一个“过去无法重塑”的故事,去展现变化。而毒眸通过徐峥在大师班上的讲述,也感知到《最后一课》与徐峥个人轨迹的某种暗合:从演实验话剧的先锋文艺青年,到家喻户晓的影视演员、商业片导演,近年来的徐峥正在试图回归到更接近个人表达的状态。但过去无法重塑,徐峥要做的不是变回90年代那个文艺青年,而是带着对过去自我的同理心和多年积累的经验,介入到当下青年导演的创作中去——成为青年导演的电影监制。徐峥提到,大部分导演正如当年的自己一样,是因为热爱艺术而投身电影创作的文艺青年,而现在的徐峥和他所成立的真乐道文化,正在通过“监制”这一角色,去服务青年导演,帮助“曾经的徐峥”们缓解创作之外、全产业流程上的压力。某种程度上,范伟饰演的乡村教师,也带着几分徐峥的自我投射:一个人仅凭自己的力量是无法改变“家乡”或者“环境”的,但是把经验分享出来,帮助更多有能力改变的人,环境就会向好的方向变化。文青徐峥“程耳找我拍《犯罪分子》的时候,是在1998年,那时候我也是个文艺青年。”大师班上与青年导演对谈的徐峥,回溯了他的青年时代,但很快又调转话锋:“现在如果让他(程耳)看我的电影,他肯定看不上我,觉得我已经不是一个艺术青年了。”二十年前,徐峥在大众的认知里是《春光灿烂猪八戒》里的猪八戒、《李卫当官》中的李卫。但其实在古装喜剧之外,他还是个演先锋话剧的艺术青年,这一度会让许多观众感到惊诧。但近年来,1999年问世的《犯罪分子》的重新流行,再度将文青时代的徐峥带回观众的视野。这部31分钟的小成本犯罪片拍摄时,导演程耳还在北京电影学院毕业读大四。作品有“北电史上最牛学生作业”之称,豆瓣评分7.4,一条2014年发布的热门短评无不惋惜地评价男主角徐峥:“大脑袋有大智慧,演技真实准确,可惜了现在的被定位。”《 犯罪分子》剧照但彼时观众跨越时空的“惋惜”对徐峥来说,未免来得迟了一些——文青时代的徐峥是不为观众所青睐的。《十三邀》中,徐峥和许知远走进了上海话剧艺术中心——1994年从上海戏剧学院毕业后,徐峥的第一个工作单位。艺术中心进门处的墙上挂满了剧照,徐峥很快从中发现了自己的身影:《拥挤》《艺术》《股票的颜色》......而后者令徐峥在1998年摘得白玉兰戏剧奖最佳男主角。根据徐峥的描述,从戏剧学院毕业后没有人找自己拍戏,所以他一直在演话剧。那时,话剧演员徐峥一度“小有名气”,徐峥还和朋友组建了剧社,并自己担任导演排了两部先锋的实验作品《拥挤》和《母语》。“当时就受到很多质疑,别人说你排的戏看不懂,这那的,当时我就很激动,还会跟人争辩。”徐峥在《十三邀》里说道。但他很快转变了思路,开始反思过于先锋的内容是否有意义,他认为自己应该对作品的传播效果负责任。愿意主动做出这样的改变,是因为徐峥虽然接受过高屋建瓴的戏剧教育、具备做出先锋性表达的能力,但他并不是那类厌弃成功的、忧郁的、纯度很高的文艺青年。徐峥在艺术性和商业化两个方向里同样具有可能性。世纪之交,徐峥开始涉足影视圈,同时也仍然在排话剧,前者所带来的曝光一度让徐峥的话剧事业“沾光”。2000年1月《春光灿烂猪八戒》在各大卫视反复播出,据索福瑞收视数据,该剧在湖南卫视、黑龙江卫视、山东和江西卫视播出的平均收视率均超过20%,最高平均收视达到31%。次年徐峥主演的喜剧话剧《艺术》在宣传不多的情况下,于上海连演15场。尽管徐峥自嘲现在的程耳一定看不上自己,但事实上,2007年程耳和徐峥又再度合作了悬疑惊悚剧《第三个人》。2009年,徐峥主演两部公路片,一部是指向喜剧和商业成功的《人在囧途》,一部是暗黑的《无人区》,后者入围了柏林国际电影节主竞赛单元。《无人区》海报文艺青年的人格从未离开徐峥的躯壳,只是一度被喜剧演员的光环盖住了——面向大众的商业喜剧片、古装电视剧能够辐射的受众,远比话剧和严肃电影来得多。但近年来,随着主演作品《我不是药神》的上映和徐峥个人导演作品中愈发明显的人文表达,他的“前文艺青年”身份不再显得太过违和。《我不是药神》在豆瓣被150万用户打出9.0分,位列豆瓣电影top250第47名——达到了近20年来华语电影鲜少能触碰到的金线。《最后一课》在《家乡》的五个单元之中,口碑亦属于上乘。徐峥延续了执导《我和我的祖国》单元《夺冠》的经验,从小人物的动机和情感出发,反而真实恳切;在叙事上,他在过去与现在的故事线反复切换,用重塑过去来反衬现在;细节也值得咂摸,比如颜料打翻在水里化成了彩虹的颜色、范老师一路走进教室那个调度复杂、明星云集的长镜头。"
# text=" "
print(len(text))
print(text_import(text))
2、使用在语料上预训练过的Bert来获取句子向量
# 导入所需的库,没有的话用pip install 库名字安装
import numpy as np
import pandas as pd
import nltk
import pickle
import json
# networkx库内置了常用的图与复杂网络分析算法,可以方便的进行复杂网络数据分析、仿真建模等工作。
import networkx as nx
# 下载一次就行了,第一次下载完再注释掉
#nltk.download('punkt')
# 下载停用词,下载一次就行,同上
#nltk.download('stopwords')20
# 加载进来
from nltk.corpus import stopwords
import re
# 将用余弦相似度计算两个句子之间的相似度
from sklearn.metrics.pairwise import cosine_similarity
import torch
import tqdm
import bs4
from torch import nn
from tqdm import tqdm
from bs4 import BeautifulSoup
import transformers as tfs
BERT_TOKENZIER_PATH ='./chinese_wwm_ext_L-12_H-768_A-12/'
FINETUNED_BERT_ENCODER_PATH ='./预训练模型/finetuned_bert.bin'
def get_sentences_list(raw_text: str):
#BeautifulSoup对象,参数 文档字符串,html解析器,文档编码
return [s for s in BeautifulSoup(raw_text, 'html.parser')._all_strings()]
#这里用微调的bert来获取文本的句子向量,再用向量来计算相似度
class MyBertEncoder(nn.Module):
"""自定义的Bert编码器"""
def __init__(self, tokenizer_path, finetuned_bert_path):
super(MyBertEncoder, self).__init__()
model_class, tokenizer_class = tfs.BertModel, tfs.BertTokenizer
self.tokenizer = tokenizer_class.from_pretrained(tokenizer_path)
self.bert = torch.load(finetuned_bert_path)
def forward(self, batch_sentences):
batch_tokenized = self.tokenizer.batch_encode_plus(batch_sentences, add_special_tokens=True,
max_length=512, pad_to_max_length=True)
input_ids = torch.tensor(batch_tokenized['input_ids']).cuda()
token_type_ids = torch.tensor(batch_tokenized['token_type_ids']).cuda()
attention_mask = torch.tensor(batch_tokenized['attention_mask']).cuda()
bert_output = self.bert(input_ids=input_ids, token_type_ids=token_type_ids, attention_mask=attention_mask)
bert_cls_hidden_state=bert_output[1]
# bert_cls_hidden_state = bert_output[0][:, 0, :]
return bert_cls_hidden_state
encoder = MyBertEncoder(BERT_TOKENZIER_PATH, FINETUNED_BERT_ENCODER_PATH)
def text_import(text):
#以下操作不影响梯度
with torch.no_grad():
encoder.eval()
print("我是一个小天使!")
text_test = "我是一个小天使!"
text_emb = np.array(encoder(text_test).cpu().detach().numpy())
print(text_emb[0])
text_extract=''
sentence_list=[]
text=get_sentences_list(text)
for node in text[0].split('。'):
if len(node)!=0:
sentence_list.append(node)
sentence_num=len(sentence_list)
sentences_vectors = []