Skip to content

Commit 20cf886

Browse files
authored
Add PLSA.py
1 parent e5fdf1c commit 20cf886

1 file changed

Lines changed: 163 additions & 0 deletions

File tree

PLSA/PLSA.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#coding=utf-8
2+
#Author:Harold
3+
#Date:2021-1-27
4+
#Email:zenghr_zero@163.com
5+
6+
'''
7+
数据集:bbc_text
8+
数据集数量:2225
9+
-----------------------------
10+
运行结果:
11+
话题数:5
12+
原始话题:'tech', 'business', 'sport', 'entertainment', 'politics'
13+
生成话题:
14+
1:'said year government people mobile last number growth phone market'
15+
2:'said people film could would also technology made make government'
16+
3:'said would could best music also world election labour people'
17+
4:'said first england also time game players wales would team'
18+
5:'said also would company year world sales firm market last'
19+
运行时长:531.13s
20+
'''
21+
22+
import numpy as np
23+
import pandas as pd
24+
import string
25+
from nltk.corpus import stopwords
26+
import time
27+
28+
29+
#定义加载数据的函数
30+
def load_data(file):
31+
'''
32+
INPUT:
33+
file - (str) 数据文件的路径
34+
35+
OUTPUT:
36+
org_topics - (list) 原始话题标签列表
37+
text - (list) 文本列表
38+
words - (list) 单词列表
39+
40+
'''
41+
df = pd.read_csv(file) #读取文件
42+
org_topics = df['category'].unique().tolist() #保存文本原始的话题标签
43+
df.drop('category', axis=1, inplace=True)
44+
n = df.shape[0] #n为文本数量
45+
text = []
46+
words = []
47+
for i in df['text'].values:
48+
t = i.translate(str.maketrans('', '', string.punctuation)) #去除文本中的标点符号
49+
t = [j for j in t.split() if j not in stopwords.words('english')] #去除文本中的停止词
50+
t = [j for j in t if len(j) > 3] #长度小于等于3的单词大多是无意义的,直接去除
51+
text.append(t) #将处理后的文本保存到文本列表中
52+
words.extend(set(t)) #将文本中所包含的单词保存到单词列表中
53+
words = list(set(words)) #去除单词列表中的重复单词
54+
return org_topics, text, words
55+
56+
57+
#定义构建单词-文本矩阵的函数,这里矩阵的每一项表示单词在文本中的出现频次,也可以用TF-IDF来表示
58+
def frequency_counter(text, words):
59+
'''
60+
INPUT:
61+
text - (list) 文本列表
62+
words - (list) 单词列表
63+
64+
OUTPUT:
65+
words - (list) 出现频次为前1000的单词列表
66+
X - (array) 单词-文本矩阵
67+
68+
'''
69+
words_cnt = np.zeros(len(words)) #用来保存单词的出现频次
70+
X = np.zeros((1000, len(text))) #定义m*n的矩阵,其中m为单词列表中的单词个数,为避免运行时间过长,这里只取了出现频次为前1000的单词,因此m为1000,n为文本个数
71+
#循环计算words列表中各单词出现的词频
72+
for i in range(len(text)):
73+
t = text[i] #取出第i条文本
74+
for w in t:
75+
ind = words.index(w) #取出第i条文本中的第t个单词在单词列表中的索引
76+
words_cnt[ind] += 1 #对应位置的单词出现频次加一
77+
sort_inds = np.argsort(words_cnt)[::-1] #对单词出现频次降序排列后取出其索引值
78+
words = [words[ind] for ind in sort_inds[:1000]] #将出现频次前1000的单词保存到words列表
79+
#构建单词-文本矩阵
80+
for i in range(len(text)):
81+
t = text[i] #取出第i条文本
82+
for w in t:
83+
if w in words: #如果文本t中的单词w在单词列表中,则将X矩阵中对应位置加一
84+
ind = words.index(w)
85+
X[ind, i] += 1
86+
return words, X
87+
88+
89+
#定义概率潜在语义分析函数,采用EM算法进行PLSA模型的参数估计
90+
def do_plsa(X, K, words, iters = 10):
91+
'''
92+
INPUT:
93+
X - (array) 单词-文本矩阵
94+
K - (int) 设定的话题数
95+
words - (list) 出现频次为前1000的单词列表
96+
iters - (int) 设定的迭代次数
97+
98+
OUTPUT:
99+
P_wi_zk - (array) 话题zk条件下产生单词wi的概率数组
100+
P_zk_dj - (array) 文本dj条件下属于话题zk的概率数组
101+
102+
'''
103+
M, N = X.shape #M为单词数,N为文本数
104+
#P_wi_zk表示P(wi|zk),是一个K*M的数组,其中每个值表示第k个话题zk条件下产生第i个单词wi的概率,这里将每个值随机初始化为0-1之间的浮点数
105+
P_wi_zk = np.random.rand(K, M)
106+
#对于每个话题zk,保证产生单词wi的概率的总和为1
107+
for k in range(K):
108+
P_wi_zk[k] /= np.sum(P_wi_zk[k])
109+
#P_zk_dj表示P(zk|dj),是一个N*K的数组,其中每个值表示第j个文本dj条件下产生第k个话题zk的概率,这里将每个值随机初始化为0-1之间的浮点数
110+
P_zk_dj = np.random.rand(N, K)
111+
#对于每个文本dj,属于话题zk的概率的总和为1
112+
for n in range(N):
113+
P_zk_dj[n] /= np.sum(P_zk_dj[n])
114+
#P_zk_wi_dj表示P(zk|wi,dj),是一个M*N*K的数组,其中每个值表示在单词-文本对(wi,dj)的条件下属于第k个话题zk的概率,这里设置初始值为0
115+
P_zk_wi_dj = np.zeros((M, N, K))
116+
#迭代执行E步和M步
117+
for i in range(iters):
118+
print('{}/{}'.format(i+1, iters))
119+
#执行E步
120+
for m in range(M):
121+
for n in range(N):
122+
sums = 0
123+
for k in range(K):
124+
P_zk_wi_dj[m, n, k] = P_wi_zk[k, m] * P_zk_dj[n, k] #计算P(zk|wi,dj)的分子部分,即P(wi|zk)*P(zk|dj)
125+
sums += P_zk_wi_dj[m, n, k] #计算P(zk|wi,dj)的分母部分,即P(wi|zk)*P(zk|dj)在K个话题上的总和
126+
P_zk_wi_dj[m, n, :] = P_zk_wi_dj[m, n, :] / sums #得到单词-文本对(wi,dj)条件下的P(zk|wi,dj)
127+
#执行M步,计算P(wi|zk)
128+
for k in range(K):
129+
s1 = 0
130+
for m in range(M):
131+
P_wi_zk[k, m] = 0
132+
for n in range(N):
133+
P_wi_zk[k, m] += X[m, n] * P_zk_wi_dj[m, n, k] #计算P(wi|zk)的分子部分,即n(wi,dj)*P(zk|wi,dj)在N个文本上的总和,其中n(wi,dj)为单词-文本矩阵X在文本对(wi,dj)处的频次
134+
s1 += P_wi_zk[k, m] #计算P(wi|zk)的分母部分,即n(wi,dj)*P(zk|wi,dj)在N个文本和M个单词上的总和
135+
P_wi_zk[k, :] = P_wi_zk[k, :] / s1 #得到话题zk条件下的P(wi|zk)
136+
#执行M步,计算P(zk|dj)
137+
for n in range(N):
138+
for k in range(K):
139+
P_zk_dj[n, k] = 0
140+
for m in range(M):
141+
P_zk_dj[n, k] += X[m, n] * P_zk_wi_dj[m, n, k] #同理计算P(zk|dj)的分子部分,即n(wi,dj)*P(zk|wi,dj)在N个文本上的总和
142+
P_zk_dj[n, k] = P_zk_dj[n, k] / np.sum(X[:, n]) #得到文本dj条件下的P(zk|dj),其中n(dj)为文本dj中的单词个数,由于我们只取了出现频次前1000的单词,所以这里n(dj)计算的是文本dj中在单词列表中的单词数
143+
return P_wi_zk, P_zk_dj
144+
145+
146+
if __name__ == "__main__":
147+
org_topics, text, words = load_data('bbc_text.csv') #加载数据
148+
print('Original Topics:')
149+
print(org_topics) #打印原始的话题标签列表
150+
start = time.time() #保存开始时间
151+
words, X = frequency_counter(text, words) #取频次前1000的单词重新构建单词列表,并构建单词-文本矩阵
152+
K = 5 #设定话题数为5
153+
P_wi_zk, P_zk_dj = do_plsa(X, K, words, iters = 10) #采用EM算法对PLSA模型进行参数估计
154+
#打印出每个话题zk条件下出现概率最大的前10个单词,即P(wi|zk)在话题zk中最大的10个值对应的单词,作为对话题zk的文本描述
155+
for k in range(K):
156+
sort_inds = np.argsort(P_wi_zk[k])[::-1] #对话题zk条件下的P(wi|zk)的值进行降序排列后取出对应的索引值
157+
topic = [] #定义一个空列表用于保存话题zk概率最大的前10个单词
158+
for i in range(10):
159+
topic.append(words[sort_inds[i]])
160+
topic = ' '.join(topic) #将10个单词以空格分隔,构成对话题zk的文本表述
161+
print('Topic {}: {}'.format(k+1, topic)) #打印话题zk
162+
end = time.time()
163+
print('Time:', end-start)

0 commit comments

Comments
 (0)