[自然语言处理]文本主题相关(TF-IDF/LDA/Sentence-BERT)

[机器学习]文本主题相关

    • 1 TF-IDF
    • 2 LDA
    • 3 BERTopic
      • 3.1 Seq2Seq
      • 3.2 Seq2Seq & Attention
      • 3.3 Transformer
      • 3.4 BERT
      • BERTopic实例
    • 4 参考
      • 4.1 论文原文
      • 4.2 开源实现
      • 4.3 补充理解

1 TF-IDF

常用于挖掘文本关键词:

  • TF(词频) = 词在本文的出现次数/文章的总词数
  • IDF(逆文档频率) = log(语料库的文档总数/包含该词的文档数+1)
  • 对于某一篇文章,计算出文档的每个词的TF-IDF值,然后按降序排列,取排在最前面的几个词,即为本文关键词
import jieba
import numpy as np

texts=[
    '...',
    '...',
    '...',
    '...'
]

# sklearn
# 对于中文文档,需要提前使用jieba进行分词
x_train = [" ".join(jieba.cut(text)) for text in texts[:3]]
x_test = [" ".join(jieba.cut(text)) for text in texts[3:]]

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

vectorizer = CountVectorizer()
tf_idf_transformer = TfidfTransformer()

x_train_tf_idf = tf_idf_transformer.fit_transform(vectorizer.fit_transform(x_train))
x_test_tf_idf = tf_idf_transformer.transform(vectorizer.transform(x_test))

x_train_weight = x_train_tf_idf.toarray()
x_test_weight = x_test_tf_idf.toarray()  
words = vectorizer.get_feature_names()

for w in x_train_weight:
    loc = np.argsort(-w)
    for i in range(5):
        print('{}: {} {}'.format(str(i + 1), words[loc[i]], w[loc[i]]))
    print('\n')

# gensim
x_train = [jieba.lcut(text) for text in texts[:3]]
x_test = [jieba.lcut(text) for text in texts[3:]]

from gensim import corpora
from gensim import models

# 建立词表
dic = corpora.Dictionary(x_train)
# 建立id2count
x_train_bow = [dic.doc2bow(sentence) for sentence in x_train]

tfidf = models.TfidfModel(x_train_bow)
tfidf_vec = []
for sentence in x_test:
    word_bow = dic.doc2bow(sentence.lower())
    word_tfidf = tfidf[word_bow]
    tfidf_vec.append(word_tfidf)
# 输出 词语id与词语tfidf值
print(tfidf_vec)

# jieba
import jieba.analyse

# idf使用jieba默认的,也可以自行指定
keywords = jieba.analyse.extract_tags(texts[0], topK=5)

对于大规模文本,还可使用spark实现tf-idf:Spark MLlib TF-IDF – Example

2 LDA

常用于本文主题分析。

词汇的分布来表达主题,主题的分布来表达文章。

[自然语言处理]文本主题相关(TF-IDF/LDA/Sentence-BERT)_第1张图片
LDA生成文档的过程如下:

  1. 从狄利克雷分布 α \alpha α中取样生成文档 i i i的主题分布 θ i \theta_i θi
  2. 从主题的多项式分布 θ i \theta_i θi中取样生成文档 i i i j j j个词的主题 z i , j z_{i,j} zi,j
  3. 从狄利克雷分布 β \beta β中取样生成主题 z i , j z_{i,j} zi,j对应的词语分布 ϕ z i , j \phi_{z_{i,j}} ϕzi,j
  4. 从词语的多项式分布 ϕ z i , j \phi_{z_{i,j}} ϕzi,j中采样最终生成词语 w i , j w_{i,j} wi,j

进行LDA的参数估计时,常用变分EM算法(MAP)或gibbs采样(贝叶斯估计)

from gensim import corpora, models, similarities
import jieba

docs = [
    '...',
    '...',
    ...,
    '...'
]

documents = [' '.join(jieba.lcut(i)) for i in docs]

with open('stop_words.txt',encoding='UTF-8')) as f:
	stoplist = [i.strip() for i in f.readlines()]
texts = [[word for word in doc.lower().split() if word not in stoplist] for doc in documents]

dictionary = corpora.Dictionary(texts)
corpus = [dictionary.doc2bow(text) for text in texts]


num_topics = 10

# model = models.LsiModel(corpus, id2word = dictionary, num_topics = num_topics)
model = models.LdaModel(corpus, id2word = dictionary, num_topics = num_topics)
# model = models.RpModel(corpus, num_topics = num_topics)
# model = models.HdpModel(corpus, id2word = dictionary)

doc = "..."

vec_bow = dictionary.doc2bow(jieba.lcut(doc))
vec_lsi = model[vec_bow]

index = similarities.MatrixSimilarity(model[corpus])
# index = similarities.Similarity(output_prefix='Similarity',corpus=model[corpus],num_features=500)  

sims = index[vec_lsi]
# sims = sorted(enumerate(sims),key=lambda item:-item[1])
# print(sims)
result = [(docs[i[0]],i[1]) for i in enumerate(sims)]
result = sorted(result ,key = lambda x: -x[1])
print(result)

同样地,Spark MLlib也提供了LDA的支持,Topic modelling using Latent Dirichlet Condition in Apache Spark MLlib

3 BERTopic

GitHub - UKPLab/sentence-transformers

GitHub - MaartenGr/BERTopic

BERTopic是一个基于sentence-BERT实现的主题模型工具,可以实现主题的抽取、计算,同时可以替换不同的预训练模型。

sbert.net - Pretrained Models

Sentence-BERT使用了孪生神经网络结构,将两个不同的句子输入BERT,然后进行pooling,得到输出向量,最后计算向量相似度,得到句子相似度。

而BERT需要参考如下的学习路线

Seq2Seq -> Attention -> Transformer -> BERT

3.1 Seq2Seq

有一些NLP任务的输入输出序列都是长度可变的,例如机器翻译、对话机器人等,为了解决这类问题,常见的解决方案是使用编码器-解码器架构,利用编码器将不定长的输入序列编码成一个向量,称为context,解码器对context进行解码,得到输出序列。
[自然语言处理]文本主题相关(TF-IDF/LDA/Sentence-BERT)_第2张图片
编码器、解码器常用多层循环神经网络来实现。

DIVE INTO DEEPLEARNING - 9.7. 序列到序列学习(seq2seq)给出了一个Seq2Seq的TensorFlow Keras实现。

Seq2Seq常用的训练技巧有;

  • Scheduled Sampling:在训练解码器时,按概率组合Teacher Forcing与Free running两种训练方式,既避免了Free running积累较大的误差,又避免了Teacher forcing过于依赖ground truth,导致训练和预测时解码器产生偏差,效果较差
  • Beam Search:在预测时,解码器每个时间步的输出都保留得分最大的k个作为下一个时间步的输入,最后选择序列得分最高的作为最终结果,避免每个时间步选取局部最优,错过全局最优

3.2 Seq2Seq & Attention

编码器-解码器架构存在一些问题:

  • 输入序列很长时,通过循环网络产生的context向量很难表达整句的信息,而是偏向于表达离序列结尾近的信息
  • 解码器仅依赖编码器最后一个隐藏层状态,信息利用率较低

因此一个可行的优化就是利用编码器所有的隐藏层状态:Bahdanau与Luong等人分别提出了在Seq2Seq中使用注意力机制,来利用编码器所有输出的方法。注意力的本质是加权平均,在Seq2Seq中,编码器每个时间步的输出同时作为键和值,通过计算向量与编码器输入序列每个位置之间的“相关度”,从而利用编码器所有输出计算出解码器当前时间步的输出。
[自然语言处理]文本主题相关(TF-IDF/LDA/Sentence-BERT)_第3张图片
其中:

  • Luong注意力:当前时间步输出前,使用当前时间步的解码器隐向量与编码器每一个时间步的输出进行Attention操作
  • Bahdanau注意力:当前时间步输出前,使用上一个时间步的解码器输出与编码器每一个时间步的输出进行Attention操作

Luong注意力在对齐上更符合语言习惯,因此后续的Transformer也使用了Luong的模式。
[自然语言处理]文本主题相关(TF-IDF/LDA/Sentence-BERT)_第4张图片
在Seq2Seq中的实现如下:

# https://github.com/wavewangyue/tensorflow_seq2seq/blob/master/model_seq2seq.py
# Luong Attention Mechanism
def attn(self, hidden, encoder_outputs):
	attn_weights = tf.matmul(encoder_outputs, tf.expand_dims(hidden, 2))
	attn_weights = tf.nn.softmax(attn_weights, axis=1)
	context = tf.squeeze(tf.matmul(tf.transpose(encoder_outputs, [0,2,1]), attn_weights))
	return context

# ...
if useAttention:
	input = tf.concat([input, self.attn(previous_state, encoder_outputs)], 1)
# ...

3.3 Transformer

基于编码器-解码器架构与注意力机制,Google提出了Transformer,用于解决机器翻译问题。
[自然语言处理]文本主题相关(TF-IDF/LDA/Sentence-BERT)_第5张图片
编码器-解码器架构

  • 编码器:输入所有的单词embedding,加上位置编码,进入编码器的block,每个block由多头自注意力与前馈神经网络组成,中间交叠残差连接与LayerNorm
  • 解码器:第一次输入开始标记,然后每次输入上次产出的embedding,同样加入位置编码,进入解码器block,每个block由一个多头自注意力,一个交叉注意力和一个前馈神经网络组成,中间也交叠残差连接与LayerNorm,输入的每个block通过cross attention与编码器连接

注意力
[自然语言处理]文本主题相关(TF-IDF/LDA/Sentence-BERT)_第6张图片

  • 自注意力:输入的所有单词进行注意力操作,一个句子内的单词,互相看其他单词对自己的影响力有多大,句子内各单词的注意力,应该关注在该句子内其他单词中的哪些单词上
    Attention ( Q , K , V ) = softmax ( Q K T d k ) V \text{Attention}(Q,K,V) = \text{softmax}(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V

    d k d_k dk很大时, Q K T QK^T QKT的方差就很大,分布会趋于陡峭(分布的方差大,分布就会集中在绝对值大的区域),就会使得softmax之后使得值出现两极分化的状态
    ————————————————
    原文链接:https://blog.csdn.net/panxin801/article/details/120758904

  • 多头注意力:使用多个不同的Q,K,V,将向量映射到不同的语义空间内,学习embedding之间的联系,类似CNN中通道的概念
    MultiHead(Q,K,V) = Concat ( h e a d 1 , . . . , h e a d n ) W O \text{MultiHead(Q,K,V)} = \text{Concat}(head_1,...,head_n)W^O MultiHead(Q,K,V)=Concat(head1,...,headn)WO

位置编码:由于Transformer并行输入,没有使用序列的位置信息,因此采用了位置编码进行补偿

  • 每个位置有一个唯一的positional encoding.
  • 两个位置之间的关系可以通过他们位置编码间的运算来建模

残差连接,LayerNorm

  • 残差连接:为了解决网络结构加深后,误差回传困难影响训练
  • LayerNorm:缓解梯度消失,与BatchNorm的区别在于norm的方向不同,LayerNorm只考虑每个句子自己,避免了不同batch size的影响

GitHub - tensorflow/docs-l10n - 理解语言的 Transformer 模型给出了一个Transformer的TensorFlow Keras实现

3.4 BERT

BERT采用了两阶段模型:语言模型预训练,随后微调解决下游任务

  1. 采用遮蔽语言模型(完形填空),利用上下文信息,以非监督的方式,利用大量未标记的语料,基于multi-layer bidirectional Transformer架构实现(其实就是一个12层的Transformer的Encoder)
  2. 预训练基础模型后,在不同的场景下微调

BERTopic实例

from bertopic import BERTopic
from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd
import jieba

def tokenize_zh(text):
    words = jieba.lcut(text)
    return words

docs = [
    '...',
    '...',
    ...,
    '...'
]

vectorizer = CountVectorizer(tokenizer=tokenize_zh)
# 初始化Bertopic模型
model = BERTopic(language='chinese (simplified)', vectorizer_model=vectorizer)
topics, probs = model.fit_transform(docs)
topic2doc = pd.DataFrame({'topic': topics, 'doc': docs})

print(model.get_topic_info())
print(topic2doc)

4 参考

4.1 论文原文

Bahdanau et.al (2014) Neural Machine Translation by Jointly Learning to Align and Translate

Luong et.al (2015) Effective Approaches to Attention-based Neural Machine Translation

Attention Is All You Need

BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

4.2 开源实现

Spark MLlib TF-IDF – Example

Topic modelling using Latent Dirichlet Condition in Apache Spark MLlib

DIVE INTO DEEPLEARNING - 9.7. 序列到序列学习(seq2seq)

GitHub - CLUEbenchmark/CLUE

GitHub - MaartenGr/BERTopic

GitHub - tensorflow/docs-l10n - 理解语言的 Transformer 模型

GitHub - google-research/bert

4.3 补充理解

通俗理解LDA主题模型

Tensorflow中的Seq2Seq全家桶

Attention Variants

Self-Attention和Transformer

Self-attention中为什么softmax要除d_k

你可能感兴趣的:(机器学习,自然语言处理)