1.1 文本表示——词袋法:one-hotTF-IDFn-gram神经概率语言模型
Posted 炫云云
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了1.1 文本表示——词袋法:one-hotTF-IDFn-gram神经概率语言模型相关的知识,希望对你有一定的参考价值。
文章目录
一、文本表示
文本表示的意思是把字词处理成向量或矩阵,以便计算机能进行处理。文本表示是自然语言处理的开始环节。
文本表示分为离散表示和分布式表示。离散表示的代表就是词袋模型,one-hot(也叫独热编码)、TF-IDF、n-gram都可以看作是词袋模型。分布式表示也叫做词嵌入(word embedding),经典模型是word2vec,还包括后来的Glove、ELMO、GPT和最近很火的BERT。
这篇文章介绍一下文本的离散表示。
(一)one-hot
看一个简单的例子。有两句话“杨幂太可爱了,我爱杨幂”,“我要看杨幂的电视剧”,把这两句话拆成一个个的字,整理得到14个不重复的字,这14个字决定了在文本表示时向量的长度为14。
下面这个表格的第一行是第一句话构成的一个词袋(或者说词典),有8个字。要对第一句话进行数值表示,那么先构造一个11×8的零矩阵,然后找到第一句话中每个字在词典中出现的位置,把该位置的0替换为1,第二句话也这样处理。只管字出现了没有(出现了就填入1,不然就是0),而不管这个字在句子中出现了几次。
下面表格中就是“杨幂太可爱了,我爱杨幂”这话的one-hot表示。
右边是字典: | 杨 | 幂 | 太 | 可 | 爱 | 了 | 我 | , |
---|---|---|---|---|---|---|---|---|
杨 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
幂 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
太 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
可 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
爱 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
了 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
, | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
我 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
爱 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
杨 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
幂 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
1、为什么需要one-hot编码?
one hot编码是将类别变量转换为机器学习算法易于利用的一种形式的过程。
上面的第一句话one hot相当于多分类的问题,每个样本只对应于一个类别(即只在对应的特征处值为1,其余地方值为0),而我们的分类结果,得到的往往是隶属于某个类别的概率,这样在进行损失函数(例如交叉熵损失)或准确率计算时,变得非常方便
2、one-hot编码的缺陷
第一个问题是数据稀疏和维度灾难。数据稀疏也就是向量的大部分元素为0,如果词袋中的字词达数百万个,那么由每篇文档转换成的向量的维度是数百万维,由于每篇文档去重后字数较少,因此向量中大部分的元素是0。而且对数百万维的向量进行计算是一件比较蛋疼的事。 但是这样进行文本表示有几个问题。可见,尽管两个句子的长度不一样,但是one-hot编码后长度都一样了,方便进行矩阵运算。
第二个问题是没有考虑句中字的顺序性,假定字之间相互独立。这意味着意思不同的句子可能得到一样的向量。比如“我太可爱了,杨幂爱我”,“杨幂要看我的演唱会”,得到的one-hot编码和上面两句话的是一样的。
第三个问题是没有考虑字的相对重要性。这种表示只管字出现没有,而不管出现的频率,但显然一个字出现的次数越多,一般而言越重要(除了一些没有实际意义的停用词)。
代码实现
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.text import Tokenizer
text1 = "杨幂太可爱了,我爱杨幂"
text2 = "我要看杨幂的电视剧"
text = [ text1,text2 ]
def seg_cut(s):
a =[]
for i in s:
for j in i:
a.append(j)
return a
text = seg_cut(text)
text = [' '.join(text)]
print(text)
tokenizer = Tokenizer(num_words=100)
tokenizer.fit_on_texts(text)
word_index = tokenizer.word_index
print(word_index)
text_index = tokenizer.texts_to_sequences(text)
print(text_index)
one_hot= tf.one_hot(text_index,depth = len(word_index)+1)#由于默认0也是一个类别,这里词典的维度加1
print(one_hot)
one_hot = one_hot.numpy()[ :,:,1:]
print(one_hot) # ['杨 幂 太 可 爱 了 , 我 爱 杨 幂 我 要 看 杨 幂 的 电 视 剧'] 的one_hot
['杨 幂 太 可 爱 了 , 我 爱 杨 幂 我 要 看 杨 幂 的 电 视 剧']
{'杨': 1, '幂': 2, '爱': 3, '我': 4, '太': 5, '可': 6, '了': 7, ',': 8, '要': 9, '看': 10, '的': 11, '电': 12, '视': 13, '剧': 14}
[[1, 2, 5, 6, 3, 7, 8, 4, 3, 1, 2, 4, 9, 10, 1, 2, 11, 12, 13, 14]]
tf.Tensor(
[[[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]], shape=(1, 20, 15), dtype=float32)
[[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]]
自定义实现任意label起始的onehot:
import numpy as np
def one_hot(labels,depth,begain = 1):
"""从1开始的one_hot"""
one_hot_label = np.array([[int(i == int(labels[j])) for i in range( begain,depth+1)] for j in range(len(labels))])
return one_hot_label
text_index = tf.reshape(text_index,shape=[-1]).numpy()
one_hot =one_hot(text_index,depth = len(word_index))
print(one_hot)
[[1 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 1 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 1 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0 0 0]
[1 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 0 0 0 0]
[1 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 1 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 1 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 1 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 1]]
(二)词袋法(Bag of word,简称BOW)
词袋,是一种只统计词频的手段。
该模型忽略了文本的语法和语序,用一组无序的单词(words)来表示一段文字或一个文档(我们也可以认为一段文字也是一个文档,下文均用文档来表示),词袋法的核心是使用单词在文档中出现的**次数(频数)**来表示文档。
例如给出以下两个文档以及基于两个文档构建的一个词典dict:
- s1: this is a sample is just a sample
- s2: this is another example is another example
- dict: this sample another example
上面的字典中包含4个单词,所以可以用4维的向量去表示每一个词典中的单词,
this:[1,0,0,0] ,sample:[0,1,0,0] ,another:[0,0,1,0], example:[0,0,0,1]
那么两个文档用词袋法分别可以表示为
s1:[1,2,0,0], s2:[1,0,2,2]
由此可以看出,词袋法其实相当于文档内各个单词(必须出现在给定的词典中哦)哑编码的累加。
词集法(Set of word,简称SOW):
是词袋法的的一个变种,和词袋法原理一样,也是以文档中的单词来表示文档的一种模型,区别在于:词袋法使用的是单词的频数,而在词集法中使用的是单词是否出现,出现赋值为1,否则赋值为0 同样对于上例来说,用词集法表示两个文档的话,分别为:
s1:[1,1,0,0] s2:[1,0,1,1]
(三)词频–逆文档频率(TF-IDF):
在词袋法或词集法中,使用的是单词的词频或者是否存在来进行文档的表征,但是不同的单词在不同的文档中出现的次数不同,而且有些单词仅仅在某一些文档中出现(比如专业名词等),也就是说不同单词对于文本而言具有不同的重要性,那么,如何评估一个单词对于一个文本的重要性呢?在讨论之前,我们有如下的假设:
-
单词的重要性随着它在文本中出现的次数成正比,也就是单词出现的次数越多,该单词对于文档越重要。
-
与此同时,单词的重要性会随着在语料库中出现的频率成反比下降,也就是单词在语料库中出现的频率越高,表示该单词越常见,也就是该单词对于特定文本的重要性越低。
TF-IDF用来评估字词对于文档集合中某一篇文档的重要程度。字词的重要性与它在某篇文档中出现的次数成正比,与它在所有文档中出现的次数成反比。TF-IDF的计算公式为:
下面一步步来理解。
1 TF——词频
TF(term frequency),即词频,用来衡量字在一篇文档中的重要性,计算公式为:
2 IDF——逆文档频率
IDF((inverse document frequency),叫做逆文档频率,衡量某个字在所有文档集合中的常见程度。当包含某个字的文档的篇数越多时,这个字也就烂大街了,重要性越低。计算公式为:
假设单词用t表示,文档用
d
d
d表示,语料库用
D
D
D表示,那么
N
(
t
,
D
)
N(t,D)
N(t,D)表示包含单词
t
t
t的文档数量,
∣
D
∣
|D|
∣D∣表示文档数量,
∣
d
∣
|d|
∣d∣表示文档d中所有单词数量。
N
(
t
,
d
)
N(t,d)
N(t,d)表示在文档
d
d
d中单词
t
t
t出现的次数。则有:
T
F
−
I
D
F
(
t
,
d
,
D
)
=
T
F
(
t
,
d
)
∗
I
D
F
(
t
,
D
)
=
N
(
t
,
d
)
∣
d
∣
×
log
(
∣
D
∣
+
0.00001
N
(
t
,
D
)
+
0.00001
)
TF-IDF(t,d,D) = TF(t,d)*IDF(t,D) = \\frac{N(t,d)}{|d|} \\times \\log(\\frac{|D|+0.00001}{N(t,D)+0.00001} )
TF−IDF(t,d,D)=TF(t,d)∗IDF(t,D)=∣d∣N(t,d)×log(N(t,D)+0.00001∣D∣+0.00001)
(此处分子分母分别加0.00001是为了防止出现为零的情况)
TF(t,d)和IDF(t,D) 除了使用默认的公式外,还可以使用一些扩展的公式
python 实现TF-IDF算法
collections模块 :namedtuple、deque、defaultdict、OrderedDict、ChainMap、Counter
from collections import defaultdict ,Counter
import math
import operator
import string
stopwords_path =
#设置三段文本
text_1 = "In information retrieval, tf–idf or TFIDF, short for term frequency–inverse document frequency, is a numerical statistic that is intended to reflect how important a word is to a document in a collection or corpus. It is often used as a weighting factor in searches of information retrieval, text mining, and user modeling. The tf–idf value increases proportionally to the number of times a word appears in the document and is offset by the number of documents in the corpus that contain the word, which helps to adjust for the fact that some words appear more frequently in general. Tf–idf is one of the most popular term-weighting schemes today; 83% of text-based recommender systems in digital libraries use tf–idf."
text_2 = "Variations of the tf–idf weighting scheme are often used by search engines as a central tool in scoring and ranking a document's relevance given a user query. tf–idf can be successfully used for stop-words filtering in various subject fields, including text summarization and classification."
text_3 = "One of the simplest ranking functions is computed by summing the tf–idf for each query term; many more sophisticated ranking functions are variants of this simple model."
punctuation_map = dict((ord(char), None) for char in string.punctuation)
#引入标点符号,为下步去除标点做准备
def stop_words(stopwords_path=stopwords_path):
# 加载停用词列表
with open(stopwords_path, 'r', encoding='utf-8') as swf:
return [word.strip() for word in swf.readlines()]
def create_vocab(text):
l_text = text.lower() #全部转化为小写以方便处理
without_punctuation = l_text.translate(punctuation_map) #去除文章标点符号
tokens = without_punctuation.strip().split()
without_stopwords = [w for w in tokens if not w in stop_words()] #去除文章的停用词
count = Counter(without_stopwords) #实现计数功能
return count
def TF_IdF(token, token_words , documents_word):
def D_con(token, documents_word ):
# 语料库中的文档总数
D_con = 0
for counts in documents_word:
if token in counts:
D_con += 1
return D_con
tf = token_words[token] / sum(token_words.values())
idf = math.log( (len(documents_word)+0.00001) / (0.00001 + D_con(token, documents_word)))
return tf * idf
def word_select(documents):
count_list = []
for text in documents:
count_list.append(create_vocab(text)) #填入清洗好后的文本
tf_idfs = {}
for i in range(len(count_list)): #每篇
tf_idf = {}
for word in count_list[i]: # 每篇的word_count
tf_idf[word] = TF_IdF(word, count_list[i], count_list)
sort = sorted(tf_idf.items(), key = lambda x: x[1], reverse=True) #将集合按照TF-IDF值从大到小排列
tf_idfs[i] = sort
return tf_idfs
if __name__=='__main__':
texts = [text_1, text_2, text_3]
q = word_select(texts)
for i ,word_count in q.items() :
print('For document {}'.format(i+1))
for word, tf_idf in word_count[:7]:
print("\\tWord: {} : {}".format(word, round(tf_idf, 6)))
For document 1
Word: document : 0.052315
Word: word : 0.052315
Word: a : 0.038616
Word: information : 0.034876
Word: retrieval : 0.034876
Word: corpus : 0.034876
Word: number : 0.034876
For document 2
Word: a : 0.045051
Word: variations : 0.040689
Word: scheme : 0.040689
Word: search : 0.040689
Word: engines : 0.040689
Word: central : 0.040689
Word: tool : 0.040689
For document 3
Word: functions : 0.156944
Word: simplest : 0.078472
Word: computed : 0.078472
Word: summing : 0.078472
Word: sophisticated : 0.078472
Word: variants : 0.078472
Word: simple : 0.078472
Jieba实现TF-IDF算法
意境级讲解 jieba分词和词云、关键词抽取:TextRank、TF-IDF
import jieba.analyse
text='关键词是能够表达文档中心内容的词语,常用于计算机系统标引论文内容特征、
信息检索、系统汇集以供读者检阅。关键词提取是文本挖掘领域的一个分支,是文本检索、
文档比较、摘要生成、文档分类和聚类等文本挖掘研究的基础性工作'
keywords=jieba.analyse.extract_tags(text, topK=5, withWeight=False, allowPOS=())
print(keywords)
['文档', '文本', '关键词', '挖掘', '文本检索']
注:
jieba.analyse.extract_tags(sentence, topK=20, withWeight=False, allowPOS=())
sentence
为待提取的文本topK
为返回几个 TF/IDF 权重最大的关键词,默认值为 20withWeight
为是否一并返回关键词权重值,默认值为 FalseallowPOS
仅包括指定词性的词,默认值为空,即不筛选
(三)n-gram
上面词袋模型假设字与字之间是相互独立的,没有考虑它们之间的顺序。于是引入语言模型。
语言模型
一个语言模型通常表现为构建字符串 T T T的概率分布 P ( T ) P(T) P(T),这里 P ( T ) P(T) P(T)试图反映的是字符串 T T T作为一个句子出现的频率,即是组成字符串的这个组合在训练语料库中出现的似然。需要注意的是,与语言学中不同,语言模型与句子是否合乎语法逻辑无关,即使一个句子组合完全合乎语法,但这个组合在语料库中出现的似然极小,我们仍然可以认为它出现的概率接近为零。
- 对于一个由 k k k个基元(“基元”可以为字、词或短语等)构成的句子 T = W 1 W 2 … W k T= W_{1} W_{2} \\ldots W_{\\mathrm{k}} T=W1W2…Wk,其概率计算公式可以表示为:
P ( T ) = P ( W 1 , W 2 , W 3 , … W k ) = P ( W 1 ) P ( W 2 ∣ W 1 ) P ( W 3 ∣ W 1 W 2 ) … P ( W k ∣ W 1 W 2 … W k − 1 ) P(T)=P\\left(W_{1}, W_{2}, W_{3}, \\ldots W_{k}\\right)=P\\left(W_{1}\\right) P\\left(W_{2} \\mid W_{1}\\right) P\\left(W_{3} \\mid W_{1} W_{2}\\right) \\ldots P\\left(W_{\\mathrm{k}} \\mid W_{1} W_{2} \\ldots W_{\\mathrm{k}-1}\\right) P(T)=P(W1,W2,W3,…Wk)=P(W1以上是关于1.1 文本表示——词袋法:one-hotTF-IDFn-gram神经概率语言模型的主要内容,如果未能解决你的问题,请参考以下文章
机器学习之路:python 文本特征提取 CountVectorizer, TfidfVectorizer