一. LSA
1. LSA原理



2. LSA的優點
3. LSA的缺點
二. pLSA

pLSA的建模思路分為兩種。
1. 第一種思路

以的概率從文檔集合
中選擇一個文檔
以的概率從主題集合
中選擇一個主題
以的概率從詞集
中選擇一個詞
有幾點說明:
- 以上變量有兩種狀態:observed (
&
) 和 latent (
)
來自文檔,但同時是集合(元素不重復),相當於一個詞匯表
直接的,針對observed variables做建立likelihood function:

其中,為
pair出現的次數。為加以區分,之后使用
與
標識對應文檔與詞匯數量。兩邊取
,得:

其中,倒數第二步旨在將暴露出來。由於likelihood function中
與
存在latent variable,難以直接使用MLE求解,很自然想到用E-M算法求解。E-M算法主要分為Expectation與Maximization兩步。
假設已知與
,求latent variable
的后驗概率

Step 2: Maximization
求關於參數和
的Complete data對數似然函數期望的極大值,得到最優解。帶入E步迭代循環。
由式可得:

此式后部分為常量。故令:


建立以下目標函數與約束條件:

只有等式約束,使用Lagrange乘子法解決:

對與
求駐點,得:


令,得:
,故有:

同理,有:

將與
回代Expectation:
,循環迭代。
pLSA的建模思想較為簡單,對於observed variables建立likelihood function,將latent variable暴露出來,並使用E-M算法求解。其中M步的標准做法是引入Lagrange乘子求解后回代到E步。
總結一下使用EM算法求解pLSA的基本實現方法:
2. 第二種思路
這個思路和上面思路的區別就在於對P(d,w)的展開公式使用的不同,思路二使用的是3個概率來展開的,如下:
這樣子我們后面的EM算法的大致思路都是相同的,就是表達形式變化了,最后得到的EM步驟的更新公式也變化了。當然,思路二得到的是3個參數的更新公式。如下:
你會發現,其實多了一個參數是P(z),參數P(d|z)變化了(之前是P(z|d)),然后P(w|z)是不變的,計算公式也相同。
給定一個文檔d,我們可以將其分類到一些主題詞類別下。
PLSA算法可以通過訓練樣本的學習得到三個概率,而對於一個測試樣本,其中P(w|z)概率是不變的,但是P(z)和P(d|z)都是需要重新更新的,我們也可以使用上面的EM算法,假如測試樣本d的數據,我們可以得到新學習的P(z)和P(d|z)參數。這樣我們就可以計算:
為什么要計算P(z|d)呢?因為給定了一個測試樣本d,要判斷它是屬於那些主題的,我們就需要計算P(z|d),就是給定d,其在主題z下成立的概率是多少,不就是要計算嗎。這樣我們就可以計算文檔d在所有主題下的概率了。
這樣既可以把一個測試文檔划歸到一個主題下,也可以給訓練文檔打上主題的標記,因為我們也是可以計算訓練文檔它們的的。如果從這個應用思路來說,思路一說似乎更加直接,因為其直接計算出來了
。
3. pLSA的優勢
4. pLSA的不足
三. pLSA的Python代碼實現
#!/usr/bin/env python # -*- coding: utf-8 -*- import numpy as np class Preprocess: def __init__(self, fname, fsw): self.fname = fname # doc info self.docs = [] self.doc_size = 0 # stop word info self.sws = [] # word info self.w2id = {} self.id2w = {} self.w_size = 0 # stop word list init with open(fsw, 'r') as f: for line in f: self.sws.append(line.strip()) def __work(self): with open(self.fname, 'r') as f: for line in f: line_strip = line.strip() self.doc_size += 1 self.docs.append(line_strip) items = line_strip.split() for it in items: if it not in self.sws: if it not in self.w2id: self.w2id[it] = self.w_size self.id2w[self.w_size] = it self.w_size += 1 self.w_d = np.zeros([self.w_size, self.doc_size], dtype=np.int) for did, doc in enumerate(self.docs): ws = doc.split() for w in ws: if w in self.w2id: self.w_d[self.w2id[w]][did] += 1 def get_w_d(self): self.__work() return self.w_d def get_word(self, wid): return self.id2w[wid] if __name__ == '__main__': fname = './data.txt' fsw = './stopwords.txt' pp = Preprocess(fname, fsw)
2. plsa.py
#!/usr/bin/env python # -*- coding: utf-8 -*- import numpy as np import time import logging def normalize(vec): s = sum(vec) for i in range(len(vec)): vec[i] = vec[i] * 1.0 / s def llhood(w_d, p_z, p_w_z, p_d_z): V, D = w_d.shape ret = 0.0 for w, d in zip(*w_d.nonzero()): p_d_w = np.sum(p_z * p_w_z[w,:] * p_d_z[d,:]) if p_d_w > 0: ret += w_d[w][d] * np.log(p_d_w) return ret class PLSA: def __init__(self): pass def train(self, w_d, Z, eps): V, D = w_d.shape # create prob array, p(d|z), p(w|z), p(z) p_d_z = np.zeros([D, Z], dtype=np.float) p_w_z = np.zeros([D, Z], dtype=np.float) p_z = np.zeros([Z], dtype=np.float) # initialize p_d_z = np.random.random([D, Z]) for d_idx in range(D): normalize(p_d_z[d_idx]) p_w_z = np.random.random([V, Z]) for w_idx in range(V): normalize(p_w_z[w_idx]) p_z = np.random.random([Z]) normalize(p_z) # iteration until converge step = 1 pp_d_z = p_d_z.copy() pp_w_z = p_w_z.copy() pp_z = p_z.copy() while True: logging.info('[ iteration ] step %d' % step) step += 1 p_d_z *= 0.0 p_w_z *= 0.0 p_z *= 0.0 # run EM algorithm for w_idx, d_idx in zip(*w_d.nonzero()): #print '[ EM ] >>>>>> E step : ' p_z_d_w = pp_z * pp_d_z[d_idx,:] * pp_w_z[w_idx,:] normalize(p_z_d_w) #print '[ EM ] >>>>>> M step : ' tt = w_d[w_idx, d_idx] * p_z_d_w p_w_z[w_idx,:] += tt p_d_z[d_idx,:] += tt p_z += tt normalize(p_w_z) normalize(p_d_z) p_z = p_z / w_d.sum() # check converge l1 = llhood(w_d, pp_z, pp_w_z, pp_d_z) l2 = llhood(w_d, p_z, p_w_z, p_d_z) diff = l2 - l1 logging.info('[ iteration ] l2-l1 %.3f - %.3f = %.3f ' % (l2, l1, diff)) if abs(diff) < eps: logging.info('[ iteration ] End EM ') return (l2, p_d_z, p_w_z, p_z) pp_d_z = p_d_z.copy() pp_w_z = p_w_z.copy() pp_z = p_z.copy()
3. main.py
#!/usr/bin/env python # -*- coding: utf-8 -*- from preprocess import Preprocess as PP from plsa import PLSA import numpy as np import logging import time def main(): # setup logging -------------------------- logging.basicConfig(filename='plsa.log', level=logging.INFO, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S') #console = logging.StreamHandler() #console.setLevel(logging.INFO) #logging.getLogger('').addHandler(console) # some basic configuration --------------- fname = './data.txt' fsw = './stopwords.txt' eps = 20.0 key_word_size = 10 # preprocess ----------------------------- pp = PP(fname, fsw) w_d = pp.get_w_d() V, D = w_d.shape logging.info('V = %d, D = %d' % (V, D)) # train model and get result ------------- pmodel = PLSA() for z in range(3, (D+1), 10): t1 = time.clock() (l, p_d_z, p_w_z, p_z) = pmodel.train(w_d, z, eps) t2 = time.clock() logging.info('z = %d, eps = %f, time = %f' % (z, l, t2-t1)) for itz in range(z): logging.info('Topic %d' % itz) data = [(p_w_z[i][itz], i) for i in range(len(p_w_z[:,itz]))] data.sort(key=lambda tup:tup[0], reverse=True) for i in range(key_word_size): logging.info('%s : %.6f ' % (pp.get_word(data[i][1]), data[i][0])) if __name__ == '__main__': main()
版權聲明:
本文由笨兔勿應所有,發布於http://www.cnblogs.com/bentuwuying。如果轉載,請注明出處,在未經作者同意下將本文用於商業用途,將追究其法律責任。