從零開始的文字TF-IDF向量構造和基於餘弦相似度的文字分類
一、任務需求
1、給定資料庫裡面的N行資料每行代表一篇文章,屬性分別是[id, title, summuary,content] ,從mysql資料庫獲取資料並生成DataFrame格式的資料,有兩列,分別是id 和content。id對應資料表裡的id,content根據 content > summuary > title 重要程度排序,對應資料表相關列的資料。
2、對DataFrame裡面的content進行分詞、去停用詞,構造每篇文章的TF-IDF向量。
3、 對於新的文章,構造TF-IDF向量後和已有文章進行相似度比較,給出最相似的五篇文章的id。
給出文字TFIDF向量的定義:文字的one-hot編碼的升級版,每個編碼由對應詞語的tfidf值代替,本質上依然是一個稀疏向量。
二、任務流程
1、 資料庫內容的獲取、DataFrame資料的構造
import pymysql import pandas as pd df = pd.DataFrame() train = pd.DataFrame() db = pymysql.connect('填寫相關配置') cursor = db.cursor() # SQL 查詢語句 sql = "SELECT * FROM item WHERE item_id < %s" % (1811151105292680200222) #執行SQL語句 cursor.execute(sql) # 獲取所有記錄列表 results = cursor.fetchall() for row in results: item_id = row[0] title = row[2] summuary = row[4] content = row[8] s ={ 'item_id': [item_id], 'title': [title], 'summuary': [summuary], 'content': [content] } s = pd.DataFrame(s) df = df.append(s,ignore_index=True) for index, row in df.iterrows(): if row['content'] is not None and len(row['content'])>10: s1 = { 'item_id': [row['item_id']], 'content': [row['content']] } train = train.append(pd.DataFrame(s1),ignore_index=True) elif row['summuary'] is not None and len(row['summuary'])>5: s2 = { 'item_id': [row['item_id']], 'content': [row['summuary']] } train = train.append(pd.DataFrame(s2), ignore_index=True) else: s3 = { 'item_id': [row['item_id']], 'content': [row['title']] } train = train.append(pd.DataFrame(s3), ignore_index=True) train = train[:4698] train.to_csv('corpora.csv',encoding='utf-8')
此處遇到的問題:1、定義空的DataFrame的時候,不需要自己定義列名。字典的鍵會作為DataFrame的列名。2、append()方法要寫ignore_index = True。3、在構造字典的時候,字典的值需要用以列表的形式存在,這樣在執行train.append(pd.DataFrame(s), ignore_index=True) 的時候才不會報錯。4、DataFrame獲取前n行的操作和列表一樣。
2、TF-IDF生成
這裡,最開始使用了gensim的 Tfidfmodel,但是發現它生成的TF-IDF並不是包含了所有單詞的稀疏矩陣。因為任務要求是得到每個文章TFIDF向量的稀疏矩陣(原因,gensim生成的稀疏向量是BOW向量,不是one-hot向量)。sklearn裡面的 tfidf模組,使用過程發生一個問題,(感覺sklearn對中文文字的處理並不好)。至於jieba 自帶的tfidf,暫時只知道用來求前n個關鍵詞,並不知道如何獲得給定文字的TFIDF向量。故只能手寫。
1、首先生成詞典和詞頻數矩陣
def word_matrix(documents):
'''計算詞頻矩陣'''
docs = [d for d in documents]
docs = [word for word in docs]
# 獲取所有詞
words = list(set(chain(*docs)))
# 詞到ID的對映, 使得每個詞有一個ID
dictionary = dict(zip(words, range(len(words))))
# 建立一個空的矩陣, 行數等於詞數, 列數等於文件數
matrix = np.zeros((len(words), len(docs)),dtype='float32')
# 逐個文件統計詞頻
for col, d in enumerate(docs):
# 統計詞頻
count = Counter(d)
for word in count:
# 用word的id表示word在矩陣中的行數
id = dictionary[word]
# 把詞頻賦值給矩陣
matrix[id, col] = count[word]
return matrix, dictionary
這是整個任務最關鍵的一步,生成了詞典和詞頻矩陣。詞典的作用:使得詞語和其id一一對應。同時,對於新的分詞之後的文字,需要去掉詞典裡不存在的詞。詞頻矩陣的行代表每個單詞在每篇文章裡出現的次數,詞頻矩陣的列代表每篇文章裡包含的詞語及該詞語出現的次數。
2、通過詞頻矩陣計算每個詞語的tf-idf值
#計算tf
def tf(matrix):
# 計算每個文件的總詞數
sm = np.sum(matrix, axis=0)
print(sm)
# 每個詞的詞頻除以每個文件的詞頻
try:
a = matrix/sm
except:
print('error')
print('tf運算結束')
return matrix / sm
#計算idf |D|: 語料庫中的文件總數。 分子: 包含詞ti的文件數
def idf(matrix):
'''計算IDF'''
# 文件總數
D = matrix.shape[1]
# 包含每個詞的文件數
j = np.sum(matrix>0, axis=1)
print(j)
return np.log(D / j)
#計算每個單詞的tf_idf值
def tf_idf(matrix):
return tf(matrix) * idf(matrix).reshape(matrix.shape[0], 1)
涉及到了array的四則運算。值得注意的一點就是:二維array除以一維array,就是二維陣列的每一列裡面的所有數分別除一維陣列的對應列的值。 雖然研究生一直做複雜網路相關的東西,也是一直使用矩陣。在這裡第一次感受到矩陣運算的方便。
最後返回的就是需要的文字的TFIDF向量,以陣列的形式存在。
3、儲存生成的模型
#儲存tfidf_matrix
tfidf_matrix = tf_idf(matrix)
np.save('./doc/numpy.npy',tfidf_matrix)
#儲存dictionary
list3_file = open('./doc/dictionary.pickle', 'wb')
pickle.dump(dictionary, list3_file)
list3_file.close()
使用了npy 和pickle兩種資料儲存格式。對資料的存取方面的問題,未完待續。
遇到的問題:最開始使用了10000篇文章,把matrix存為了txt格式,發現有30G,計算tf的時候電腦直接卡死。後來改成了5000篇文章。
4、下載模型
#載入dictionary
word_dict = open('./doc/dictionary.pickle', 'rb')
word_dict = pickle.load(word_dict)
#載入idf字典
with open('./doc/newidf.txt', 'r', encoding='utf-8')as f:
dic = []
for line in f.readlines():
b = line.strip().split(' ')
dic.append(b)
dic = dict(dic)
#載入matrix
matrix = np.load('./doc/numpy.npy')
5、生成新文章的TF-IDF向量
#生成一個一維向量,長度為字典的長度。然後對詞語所在位置進行賦值。使用了自定義的idf模型。
newdoc = np.zeros(len(word_dict))
data = readfile('./doc/re0.txt')
doc = chinese_word_cut(data)
k = len(doc) # 計算單詞總數
result = {}
for word in doc:
result[word] = doc.count(word)
tf = float(result[word] / k)
if word in list(dic.keys()):
idf = float(dic[word])
else:
idf = 0.0
tfidf = tf * idf
if word in word_dict.keys():
newdoc[word_dict[word]] = tfidf
print(np.sum(newdoc))
自定義idf模型的生成,之前部落格有寫,怪不得最開始實習的時候,讓我自己寫個idf模型。
6、計算文章之間的餘弦相似度
# 計算文章和舊文章的餘弦相似度。
def sim(array1,array2):
num = float(np.matmul(array1,array2))
s = np.linalg.norm(array1)*np.linalg.norm(array2)
return num/s
matrix = np.load('./doc/numpy.npy')
print(matrix.shape[1])
doc2id = []
for i in range(matrix.shape[1]):
doc2id.append(sim(newdoc,matrix[:,i]))
# 返回列表裡面最大的三個數值的索引。
max_num_index_list = map(doc2id.index, heapq.nlargest(6, doc2id))
print(list(max_num_index_list))