在傳統的文本信息處理中,以單詞向量表示文本的語義內容,以單詞向量空間的度量來表示文本之間的語義近似度。這種方法不能準確表示語義。
潛在語義分析試圖從大量的文本數據中發現潛在的話題,以話題向量來表示文本的語義內容,以話題向量的空間度量更準確地表示文本之間的語義相似度。
潛在語義分析使用的是非概率的話題分析模型,具體來說,就是將文本集合表示爲單詞-文本矩陣,對單詞-文本矩陣進行奇異值分解,從而得到話題向量空間,以及文本在話題向量空間的表示。可採用的矩陣分解方法有:奇異值分解、非負矩陣分解。
給定一個含有個文本的集合,以及在所有文本中出現的個單詞,則將單詞在文本中出現的數據用一個單詞-文本表示,記作。
其中,元素表示單詞在文本中出現的頻數或權值。該矩陣是一個稀疏矩陣。
權值通常用單詞頻率-逆文本頻率(TF-IDF)表示,其定義是:
式中是單詞出現在文本中的頻數, 是文本中出現的所有單詞的頻數之和,是含有單詞的文本數,是文本集合的全部文本數。直觀的,一個單詞在一個文本中出現的次數越高,這個單詞在這個文本中的重要度就越高;一個單詞在整個文本集合中出現的文本越少,這個單詞就越能表示其所在文本的特點,重要程度就越高。
單詞向量空間模型直接使用單詞-文本矩陣信息。單詞文本矩陣的第列向量表示文本:
其中是單詞在文本的權值,,權值越大,該單詞在該文本中的重要度就越高。
之後就可以採用兩個單詞向量的內積或標準化內積來表示對應的文本之間的語義相似度。與之間的相似度爲:
這種簡單的單詞向量空間模型不能處理一詞多義的問題,也無法處理同義的問題,需要更加精確的方法。
話題向量空間
假設所有文本共含有個話題,假設每個話題由一個定義在單詞集合上的爲向量表示,稱爲話題向量,即:
其中是單詞在話題的權值,,權值越大,該單詞在該話題中的重要度就越高。這個話題向量張成一個話題向量空間,維數爲。
現在考慮將文本集合中的文本,在單詞向量空間中由一個向量表示,將投影到話題向量空間,得到在話題向量空間的一個向量,是一個維向量,其表達式爲:
其中是話題在文本的權值,權值越大,該話題在該文本中的重要度就越高。
矩陣表示話題在文本中出現的情況,稱爲話題-文本矩陣。
從單詞向量空間到話題向量空間的線性變換
這樣一來,在單詞向量空間的文本向量可以通過它在話題空間的向量近似表示,具體的由個話題向量爲係數的線性組合近似表示。
所以,單詞文本矩陣可以近似的表示爲單詞-話題矩陣與話題-文本矩陣的乘積形式。這就是潛在語義分析。
要盡心潛在語義分析,需要同時決定兩部分內容:話題向量T與文本在話題空間的表示Y,使兩者的乘積近似等於原始矩陣。這些完全從話題-文本矩陣的信息中獲得。
潛在語義分析算法
潛在語義分析利用矩陣奇異值分解,具體的,對單詞-文本矩陣進行奇異值分解,將其左矩陣作爲話題向量空間,將其對角矩陣與右矩陣的乘積作爲文本在話題向量空間的表示。
算法:
1、給定文本集合和單詞集合,首先構造一個文本-單詞矩陣,矩陣中的每個元素表示單詞在文本中出現的頻數或權值。
2、截斷奇異值分解
根據給定的話題數目k進行階段奇異值分解:
3、話題向量空間
矩陣的每一列向量表示一個話題,稱爲話題向量。由這k話題張成一個子空間:
4、文本的話題向量表示
矩陣的每一個列向量是一個文本在話題向量空間的表示。
用代碼表示:
import numpy as np
import pandas as pd
import jieba
df = pd.read_excel(r'D:\BaiduNetdiskDownload\最終data.xlsx')
data = np.array(df['abstract'])
dic = []
for i in data:
cutdata = jieba.cut(i)
for j in cutdata:
if j not in dic and len(j)>=2:
dic.append(j)
a = len(dic)
X = np.zeros((a, len(data)))
for i in range(len(data)):
cutdata = jieba.cut(data[i])
p = []
for j in cutdata:
p.append(j)
for k in range(len(p)):
if p[k] in dic:
index = dic.index(p[k])
X[index][i] += 1
U, O, V = np.linalg.svd(X)
print(U[:, :3])
a = np.diag(O[:3])
b = V[:3, :]
c = np.dot(a, b)
print(c.shape)
最終輸出結果就是話題向量空間與文本在話題空間的線性表示。
非負矩陣分解算法
另外一種對單詞文本矩陣分解的方法是非負矩陣分解。
若是非負的,記作,則分別找到兩個非負矩陣和,使得
非負矩陣分解是爲了用較少的基向量、係數向量來表示較大的數據矩陣。
我們採用最優化問題求解的方式來求
首先定義損失函數
1、平方損失
2、散度
因此,最優化問題轉變爲:
s.t.
或者爲:
s.t.
算法實現
定理:
平方損失對下列乘法更新規則
是非增的,當且僅當和是平方損失函數的穩定點時函數的更新不變。
散度損失對下列乘法更新規則
是非增的,當且僅當和是散度損失函數的穩定點時函數的更新不變。
其實這種乘法更新是將梯度下降法的步長取特殊值,從而使收斂速率加快!
代碼如下:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed()
X = np.random.randint(0, 3, (5, 4))
class LSA:
def __init__(self, x, k): # k爲話題個數
self.k = k
self.X = x
self.m = x.shape[0]
self.n = x.shape[1]
self.W = np.random.uniform(0, 1, (self.m, k))
self.H = np.random.uniform(0, 1, (k, self.n))
def standard(self):
t = self.W**2
T = np.sqrt(np.sum(t, axis=0))
for i in range(self.W.shape[0]):
self.W[i] = self.W[i]/T
def update(self):
up1 = np.dot(self.X, self.H.T)
t1 = np.dot(self.W, self.H)
down1 = np.dot(t1, self.H.T)
up2 = np.dot(self.W.T, self.X)
t2 = np.dot(self.W.T, self.W)
down2 = np.dot(t2, self.H)
for i in range(self.m):
for l in range(self.k):
self.W[i][l] = self.W[i][l]*(up1[i][l]/down1[i][l])
for l in range(self.k):
for j in range(self.n):
self.H[l][j] = self.H[l][j]*(up2[l][j]/down2[l][j])
def cost(self):
X = np.dot(self.W, self.H)
S = (self.X - X)**2
score = np.sum(S)
return score
L = LSA(X, 3)
x = []
score = []
for i in range(50):
L.standard()
L.update()
x.append(i)
score.append(L.cost())
print(X)
print(np.dot(L.W, L.H))
plt.scatter(x, score)
plt.show()
最終的運行結果爲:
原始矩陣與近似矩陣,可見差異已經相當小。
平方損失的圖像:
在迭代到第50步時已經收斂。