文本的向量表示
1. 為什么需要文本的表示?
文字是人類認知過程中產生的高層認知抽象實體,我們需要將其轉換為數字向量或矩陣作為機器學習算法模型以及神經網絡模型的標准輸入輸出。
2. 詞袋模型(Bag-of-words)
Bag-of-words模型是信息檢索領域常用的文檔表示方法。在文本特征生成過程中,對於一個文檔,詞袋模型忽略其單詞順序和語法、句法等要素,將其僅僅看作是若干個詞匯的集合,文檔中每個單詞的出現都是獨立的,不依賴於其它單詞是否出現。也就是說,文檔中任意一個位置出現的任何單詞,都不受該文檔語意影響而獨立選擇的。主要方法包括one-hot編碼、tf-idf、n-gram模型。
2.1 One-hot 編碼
2.1.1 對於單詞的one-hot表示
假設給定一個詞典為 [今天, 天氣, 真好,他們,去,爬山]。該詞典可以看作一個容量為6的詞袋,每個單詞的表示為:
今天: [1, 0, 0, 0, 0, 0]
天氣: [0, 1, 0, 0, 0, 0]
真好: [0, 0, 1, 0, 0, 0]
......
爬山: [0, 0, 0, 0, 0, 1]
在one-hot表示中,每個單詞的向量長度為詞袋的容量(假設詞袋有10000個單詞,某單詞的向量長度為10000)。並且該單詞在詞袋中的對應位置為1,其余位置為0。 python中sklearn可以生成:
import numpy as np
from sklearn import preprocessing
words = np.array(["今天", "天氣", "真好","他們","去","爬山"]) # 詞袋
words=words.reshape((6,1)) # 轉化成6,1矩陣
enc = preprocessing.OneHotEncoder()
enc.fit(words)
enc.categories_ # fit之后內部會改變順序 用詞語句查看
# [array(['今天', '他們', '去', '天氣', '爬山', '真好'], dtype='<U2')]
result = enc.transform([["天氣"]]).toarray() # 天氣 對應的向量
# array([[0., 0., 0., 1., 0., 0.]])
2.1.2 對於句子的one-hot表示
給定三個句子“小明 今天 爬山”,“小紅 昨天 跑步”,“小紅 今天 又 爬山 又 跑步”。首先根據這些句子構造詞袋(可以用分詞、去重)為 [小明 小紅 爬山 跑步 又 今天 昨天]。各句子的向量表示為:
“小明 今天 爬山”:[1, 0, 1, 0, 0, 1, 0]
“小紅 昨天 跑步”:[0, 1, 0, 1, 0, 0, 1]
“小紅 今天 又 爬山 又 跑步”:[0, 1, 1, 1, 1, 1, 0]
每個句子中所有單詞都會出現在詞袋中,其向量長度為詞袋的容量。以“小明 今天 爬山”為例,“小明”出現在詞袋中,在詞袋對應位置設為1。“今天”出現在詞袋中第6個位置,對應位置為1。“爬山”出現在詞袋中第3個位置,對應位置為1。其余位置為0。由於第三句中“又”出現兩次,其向量也可以表示為 [0, 1, 1, 2, 1, 1, 0]。
當“小紅 今天 又 爬山 又 跑步”表示為 [0, 1, 1, 2, 1, 1, 0] 時,其中“又”出現的頻率為2,一般會認為其比較重要,但是從實際而言,“又”的重要性比不上“爬山”、“跑步”等頻率為1的單詞。所以,並不是出現的次數越多越重要、並不是出現的越少就越不重要。由此可以引出 tf-idf。
2.2 n-gram模型
n-gram模型為了保持詞的順序,做了一個滑窗的操作,這里的n表示的就是滑窗的大小,例如2-gram模型,也就是把2個詞當做一組來處理,然后向后移動一個詞的長度,再次組成另一組詞,把這些生成一個字典,按照詞袋模型的方式進行編碼得到結果。該模型考慮了詞一定范圍內的關聯性。
假設給定句子:
John likes to watch movies. Mary likes too
John also likes to watch football games.
以上兩句可以構造一個詞典,{"John likes”: 1, "likes to”: 2, "to watch”: 3, "watch movies”: 4, "Mary likes”: 5, "likes too”: 6, "John also”: 7, "also likes”: 8, “watch football”: 9, "football games": 10}
那么第一句的向量表示為:[1, 1, 1, 1, 1, 1, 0, 0, 0, 0],其中第一個1表示John likes在該句中出現了1次,依次類推。
缺點: 隨着n的大小增加,詞表會成指數型膨脹,會越來越大。
2.3 tf-idf
TF-IDF(term frequency–inverse document frequency,詞頻-逆向文件頻率)是一種用於信息檢索(information retrieval)與文本挖掘(text mining)的常用加權技術。
TF-IDF是一種統計方法,用以評估字詞對於一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨着它在文件中出現的次數成正比增加,但同時會隨着它在語料庫中出現的頻率成反比下降。
TF-IDF的主要思想是:如果某個單詞在一篇文章中出現的頻率TF高,並且在其他文章中很少出現,則認為此詞或者短語具有很好的類別區分能力,適合用來分類。
(1)TF(Term Frequency)詞頻
詞頻(TF)表示詞條(關鍵詞)在一個文本中出現的頻率。計算公式為:
其中表示單詞
在文檔
中的出現次數,分母表示文檔
中所有單詞出現的次數之和。通俗理解為:
(2)IDF(Inverse Document Frequency)逆向文檔頻率
逆向文件頻率 (IDF) :某一特定詞語的IDF,可以由總文件數目除以包含該詞語的文件的數目,再將得到的商取對數得到。如果包含單詞的文檔越少, IDF越大,則說明詞條具有很好的類別區分能力。計算公式為:
其中表示所有文檔數,分母表示包含單詞
的所有文檔數。通俗理解為(加1防止分母為0):
(3)TF-IDF實際是TF*IDF
對IDF的理解:語料庫的文檔總數實際上是一個詞分布的可能性大小,n篇文檔,有n種可能。包含詞的文檔數m,表示詞
的真實分布有m個“可能”。那么log(n/m) = log(n) - log(m)就可以表示詞
在m篇文檔中的出現,導致的詞
分布可能性的減少(即信息增益),這個值越小,表示詞
分布越散,我們認為一個詞越集中出現在某一類文檔,它對這類文檔的分類越有貢獻,那么當一個詞分布太散了,那他對文檔歸類的作用也不那么大了。**
舉例
給定3篇文檔 ,“今天 上 NLP 課程”,“今天 的 課程 有 意思”,“數據 課程 也 有 意思”。詞袋為[今天 上 NLP 課程 的 有 意思 數據 也],容量為9。以第一句為例,“今天”的tf-idf值為;“上”的tf-idf值為
;“NLP”的tf-idf值為
,“課程”的tf-idf值為
。
則“今天 上 NLP 課程”的向量可以表示為[,
,
,
,0, 0, 0, 0, 0]。以此類推,各句子可以表示為:
“今天 上 NLP 課程”:[,
,
,
,0, 0, 0, 0, 0]
“今天 的 課程 有 意思”:[,0,0,
,
,
,
, 0, 0]
“數據 課程 也 有 意思”:[0,0,0,,0,
,
,
,
]
(4)利用sklearn庫實現tf-idf
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
x_train = ['TF-IDF 主要 思想 是','算法 一個 重要 特點 可以 脫離 語料庫 背景',
'如果 一個 網頁 被 很多 其他 網頁 鏈接 說明 網頁 重要']
x_test=['原始 文本 進行 標記','主要 思想']
#該類會將文本中的詞語轉換為詞頻矩陣,矩陣元素a[i][j] 表示j詞在i類文本下的詞頻
vectorizer = CountVectorizer(max_features=10)
#該類會統計每個詞語的tf-idf權值
tf_idf_transformer = TfidfTransformer()
#將文本轉為詞頻矩陣並計算tf-idf
tf_idf = tf_idf_transformer.fit_transform(vectorizer.fit_transform(x_train))
#將tf-idf矩陣抽取出來,元素a[i][j]表示j詞在i類文本中的tf-idf權重
x_train_weight = tf_idf.toarray()
#對測試集進行tf-idf權重計算
tf_idf = tf_idf_transformer.transform(vectorizer.transform(x_test))
x_test_weight = tf_idf.toarray() # 測試集TF-IDF權重矩陣
print('輸出x_train文本向量:')
print(x_train_weight)
print('輸出x_test文本向量:')
print(x_test_weight)
輸出x_train文本向量:
[[0.70710678 0. 0.70710678 0. 0. 0.
0. 0. 0. 0. ]
[0. 0.3349067 0. 0.44036207 0. 0.44036207
0.44036207 0.44036207 0. 0.3349067 ]
[0. 0.22769009 0. 0. 0.89815533 0.
0. 0. 0.29938511 0.22769009]]
輸出x_test文本向量:
[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]]
(5)自己實現tf-idf
# -*- coding: utf-8 -*-
from collections import defaultdict
import math
import operator
"""
函數說明:創建數據樣本
Returns:
dataset - 實驗樣本切分的詞條
classVec - 類別標簽向量
"""
def loadDataSet():
dataset = [ ['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], # 切分的詞條
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid'] ]
classVec = [0, 1, 0, 1, 0, 1] # 類別標簽向量,1代表好,0代表不好
return dataset, classVec
"""
函數說明:特征選擇TF-IDF算法
Parameters:
list_words:詞列表
Returns:
dict_feature_select:特征選擇詞字典
"""
def feature_select(list_words):
#總詞頻統計
doc_frequency=defaultdict(int)
for word_list in list_words:
for i in word_list:
doc_frequency[i]+=1
#計算每個詞的TF值
word_tf={} #存儲沒個詞的tf值
for i in doc_frequency:
word_tf[i]=doc_frequency[i]/sum(doc_frequency.values())
#計算每個詞的IDF值
doc_num=len(list_words)
word_idf={} #存儲每個詞的idf值
word_doc=defaultdict(int) #存儲包含該詞的文檔數
for i in doc_frequency:
for j in list_words:
if i in j:
word_doc[i]+=1
for i in doc_frequency:
word_idf[i]=math.log(doc_num/(word_doc[i]+1))
#計算每個詞的TF*IDF的值
word_tf_idf={}
for i in doc_frequency:
word_tf_idf[i]=word_tf[i]*word_idf[i]
# 對字典按值由大到小排序
dict_feature_select=sorted(word_tf_idf.items(),key=operator.itemgetter(1),reverse=True)
return dict_feature_select
if __name__=='__main__':
data_list,label_list=loadDataSet() #加載數據
features=feature_select(data_list) #所有詞的TF-IDF值
print(features)
print("詞袋容量:",len(features))
# 結果:
[('to', 0.0322394037469742), ('stop', 0.0322394037469742),
('worthless', 0.0322394037469742),
('my', 0.028288263356383563), ('dog', 0.028288263356383563),
('him', 0.028288263356383563), ('stupid', 0.028288263356383563),
('has', 0.025549122992281622), ('flea', 0.025549122992281622),
('problems', 0.025549122992281622), ('help', 0.025549122992281622),
('please', 0.025549122992281622), ('maybe', 0.025549122992281622),
('not', 0.025549122992281622), ('take', 0.025549122992281622),
('park', 0.025549122992281622), ('dalmation', 0.025549122992281622),
('is', 0.025549122992281622), ('so', 0.025549122992281622),
('cute', 0.025549122992281622), ('I', 0.025549122992281622),
('love', 0.025549122992281622), ('posting', 0.025549122992281622),
('garbage', 0.025549122992281622), ('mr', 0.025549122992281622),
('licks', 0.025549122992281622), ('ate', 0.025549122992281622),
('steak', 0.025549122992281622), ('how', 0.025549122992281622),
('quit', 0.025549122992281622), ('buying', 0.025549122992281622),
('food', 0.025549122992281622)]
詞袋容量: 32
(6)tf-idf的不足
**
TF-IDF算法實現簡單快速,但是仍有許多不足之處:
(1)沒有考慮特征詞的位置因素對文本的區分度,詞條出現在文檔的不同位置時,對區分度的貢獻大小是不一樣的。
(2)按照傳統TF-IDF,往往一些生僻詞的IDF(反文檔頻率)會比較高、因此這些生僻詞常會被誤認為是文檔關鍵詞。
(3)傳統TF-IDF中的IDF部分只考慮了特征詞與它出現的文本數之間的關系,而忽略了特征項在一個類別中不同的類別間的分布情況。
(4)對於文檔中出現次數較少的重要人名、地名信息提取效果不佳。