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