【論文實現】一篇Sigkdd的彈幕分析論文的python實現【LDA 實踐者】

【論文實現】一篇Sigkdd的彈幕分析論文的python實現 【LDA 實踐者】

Author : Jasper Yang
School : Buptpython

warning : 此篇文章基於較爲熟悉GibbsLDA++的源碼的前提下閱讀。另外,這篇文章是個人一個很不成熟的筆記,裏面的不少東西和我實現的最終版本的TPTM(這篇論文的模型)已經截然不同了,因此這篇文章就當成一篇簡單的記錄吧,我仍是放在個人blog裏,不太想丟棄。-_-git

論文linkgithub

在開始以前,我想提早說明一下,這篇文章是我實現了基於用戶喜愛和評論的先後影響的改進LDA,由於實驗室在作彈幕分析這方面的研究,就找到了這篇A類會議的文章,原本找他們要源碼的,可是他們一做並不理我。。。而後我只好本身實現了。編程

因爲是第一次寫這類文章,不知道在邏輯上表達的清不清楚,~.~,望有看的各位給點建議,哈。服務器

怎麼實現

first of first

from scipy.special import digamma
    import eulerlib
    import numpy as np
    import math
    from scipy.special import gamma
    import jieba
    import pandas as pd
    from scipy import stats

以上是本文所需庫。多線程

let's begin(前期準備工做)

首先是處理數據app

數據來源:bilibili(爬蟲爬取)dom

數據格式以下。ide

<?xml version="1.0" encoding="UTF-8"?><i>   <chatserver>chat.bilibili.com</chatserver><chatid>1007373</chatid><mission>0</mission><maxlimit>8000</maxlimit><source>e-r</source><ds>274694331</ds><de>3034701550</de><max_count>8000</max_count>
                                                                                                    <d p="72.409,1,25,16777215,1375542175,0,7526c714,274694331">我來組成彈幕..................</d>
                                                                                                    
<d p="33.551,1,25,16777215,1375543533,0,925384b2,274711742">大佬系邊 </d>

<d p="117.977,1,25,16777215,1375543631,0,925384b2,274712904">甘嗨假噶 </d>

<d p="134.849,1,25,16777215,1375547487,0,D3dfe4a5,274755463">呢個日文..一個字都聽唔明</d>
...

下面寫了段代碼解析得到上面的用戶id以及每一個用戶的評論。
最後造成一個全部評論的列表以及每一個用戶對應其評論的字典。函數

user = {}
    comments = []
    split_num = 10 # 分割一部電影爲幾個片斷
    
    # 數據和停用詞
    danmu = open('danmu/1007373.xml')
    stopwords = {}.fromkeys([ line.rstrip().decode('gbk') for line in open('stopwords.txt') ])
    # 讀取文件,分析後存儲到 user 和 comments
    for line in danmu.readlines()[:-1]:
        start = line.find('p=')
        stop = line.find('">')
        sub1 = line[start+3:stop]
        time = sub1.split(',')[0]
        sub1 = sub1.split(',')[6]
        start = line.find('">')
        stop = line.find('</d>')
        sub2 = line[start+2:stop].decode('utf-8')
        comments.append((float(time),sub2))
        temp = []
        if not user.has_key(sub1) :
            temp.append(sub2)
            user[str(sub1)] = temp
        else:
            user[str(sub1)].append(sub2)

通過以上處理後,咱們還須要分片,我這裏默認分割成十份。

# 統計user的個數 , 如今統計的是這個文檔裏的user,後期要作成對全部文檔的統計量,還要能支持增量
    user_num = len(user)
    
    # comments的數量
    comments_num = len(comments)
            
    # 排序,分割comments ---> shots
    comments = sorted(comments)
    spli = (comments[-1][0]-comments[0][0])/split_num
    shots = []
    for i in range(10):
        shots.append([x[1] for x in comments if x[0] > i*spli and x[0] <= (i+1)*spli ])

分割以後就是分詞,我這裏用的是python版的jieba,效果很通常,之後再更新。

注意:這裏的切詞我分紅了兩個部分。由於在文中認爲片斷(shot)和片斷之間有時間上的影響,而後每一個片斷裏的每個comment之間有時間上的影響。可是不一樣的片斷之間的comment是沒有關係的。

def cut_word(x):
    words = jieba.cut(x, cut_all=False)
    final = []
    for word in words:
        if word not in stopwords:
            final.append(word)
    return final

def null(l):
    return len(l) > 0 or l[0] == ' '

for i in range(split_num):
    shots[i] = map(cut_word,shots[i])
    shots[i] = filter(null,shots[i])

上面的代碼對每個shot裏面的全部comment進行切詞。

準備工做到此爲止~

real work

因爲這是我第一次還原論文的實現內容,一開始也是一頭霧水,無從下手。慢慢多看了幾遍公式,我發現了一些規律。

這裏寫圖片描述

這裏寫圖片描述

瞭解LDA的同窗對上面的一系列定義應該不陌生。
這是一個咱們要實現的全部的矩陣的描述。

從頭開始,對每個用戶生成一個用戶對應主題的矩陣($x_u$)。每個用戶都有一個本身的$sigma_u^2$。可是,因爲咱們是在初步實驗階段,我就寫了個文件,裏面存儲全部的$sigma^2$值而且都爲0.1。

# 製造假的sigma文件
    user_num = len(user)
    f = open('sigma_u_t.csv','w')
    f.write(',user')
    for i in range(split_num*10):
        f.write(',topic'+str(i))
    f.write('\n')
    for key in user.keys():
        f.write(','+key)
        for j in range(split_num*10):
            f.write(',0.1')
        f.write('\n')


    # 每個用戶的user-topic分佈
    # sigma_u_t 是每一個用戶對於每個topic的sigma值
    # 從文件裏面讀取每個用戶的每個topic的sigma值
    # 每一行一個用戶 (順序就是下面生成的 user_ 中的順序)
    user_sigma = pd.read_csv('sigma_u_t.csv')
    user_sigma = user_sigma.drop(['Unnamed: 0'],1)
    user_sigma.fillna(0.1)
    
            
    # 利用上面的用戶對應評論的字典 make 一個 dataframe
    user_ = pd.DataFrame()
    temp1 = []
    temp2 = []
    for key in user.keys():
        for i in range(len(user[key])):
            temp1.append(key)
            temp2.append(user[key][i])
    user_['user'] = temp1
    user_['comment'] = temp2

【後期的作法能夠是去經過標籤統計分析每個用戶對於主題的機率再去肯定每個用戶的$sigma^2$值】

下面咱們須要實現$lambda_s = N(m_{pre_s},sigma_sI_K)$。這裏就會發現咱們要先去實現$m_{pre_s}$

這裏寫圖片描述

能夠從文中讀到這個$m_{pre_s}$就是當前的shot的以前全部的shot對其的影響值相加,具體怎麼影響的公式用到了 exponential decay 。(其中$Delta(s,s^{'})$是兩個shot之間的絕對差,我這裏的實現是用cos,也就是餘弦值)

由於comment的$Delta$和shot的$Delta$計算方式相近,我就在下面的實現中一塊兒實現了。

實現 $Delta$

先講下餘弦值計算的原理:

一個簡單的例子(後面第三部分是相對複雜的例子):

  句子A:我喜歡看電視,不喜歡看電影。

  句子B:我不喜歡看電視,也不喜歡看電影。

請問怎樣才能計算上面兩句話的類似程度?
基本思路是:若是這兩句話的用詞越類似,它們的內容就應該越類似。所以,能夠從詞頻入手,計算它們的類似程度。

第一步,分詞。

  句子A:我/喜歡/看/電視,不/喜歡/看/電影。

  句子B:我/不/喜歡/看/電視,也/不/喜歡/看/電影。

第二步,列出全部的詞。

  我,喜歡,看,電視,電影,不,也。

第三步,計算詞頻。

  句子A:我 1,喜歡 2,看 2,電視 1,電影 1,不 1,也 0。

  句子B:我 1,喜歡 2,看 2,電視 1,電影 1,不 2,也 1。

第四步,寫出詞頻向量。

  句子A:[1, 2, 2, 1, 1, 1, 0]

  句子B:[1, 2, 2, 1, 1, 2, 1]

到這裏,問題就變成了如何計算這兩個向量的類似程度。

使用餘弦這個公式,咱們就能夠獲得,句子A與句子B的夾角的餘弦。

這裏寫圖片描述

餘弦值越接近1,就代表夾角越接近0度,也就是兩個向量越類似,這就叫餘弦類似性。因此,上面的句子A和句子B是很類似的,事實上它們的夾角大約爲20.3度。

從上面能夠看出若是套用到咱們的模型中,分紅兩類:

  1. 每一個shot有一個詞向量矩陣

  2. 每一個comment有一個詞向量矩陣

以下圖。
左邊是單詞,右邊是單詞出現的次數。
map

由於咱們是對一部視頻也就是一篇文檔處理,因此詞向量能夠是這篇文檔裏出現過的詞。所以用下面的代碼找出全部的詞並存儲成一個pandas的dataframe(使用這個的緣由是處理起來很快並且方便)

# 統計關鍵詞及個數 (根據文件)
def CountKey(fileName, resultName):
    try:
        #計算文件行數
        lineNums = len(open(fileName,'rU').readlines())
        # print u'文件行數: ' + str(lineNums)

        #統計格式 格式<Key:Value> <屬性:出現個數>
        i = 0
        table = {}
        source = open(fileName,"r")
        result = open(resultName,"w")
        
        while i < lineNums:
            line = source.readline()
            line = line.rstrip()
            # print line
            
            words = line.split(" ")  #空格分隔
            # print str(words).decode('string_escape') #list顯示中文
            
            #字典插入與賦值
            for word in words:
                if word!="" and table.has_key(word):      #若是存在次數加1
                    num = table[word]
                    table[word] = num + 1
                elif word!="":                            #不然初值爲1
                    table[word] = 1
            i = i + 1

        #鍵值從大到小排序 函數原型:sorted(dic,value,reverse)
        dic = sorted(table.iteritems(), key = lambda asd:asd[1], reverse = True)
        word_fre = pd.DataFrame(dic)
        for i in range(len(dic)):
            #print 'key=%s, value=%s' % (dic[i][0],dic[i][1])
            result.write("<"+dic[i][0]+":"+str(dic[i][1])+">\n")
        return word_fre
        
    except Exception,e:    
        print 'Error:',e
    finally:
        source.close()
        result.close()
        # print 'END\n\n'


f = open('comments.txt','w')
for i in range(split_num):
    for x in shots[i]:
        for word in x:
            f.write(word.encode('utf-8') + ' ')
    f.write('\n')

word_fre = CountKey('comments.txt','comments_map')

最後獲得的 word_fre 就是一個詞頻向量(全集),其實並不須要計算全集的詞頻。

0    1
0    好    120
1    哈哈哈    116
2    哈哈哈哈    72
3    吳媽    50
4    臥槽    48
5    神父    48
6    人    41
7    黑社會    37
8    靚坤    35
9    真的    34
10    死    33
11    叻    31
12    說    30
13    君    28
14    一個    25
15    太    23
16    想    22
17    大佬    20
18    賣    20
19    吳    20
20    坤    20
21    香港    19
22    樽    19
23    爆    19
24    古惑仔    18
25    2333333    17
26    233333    17
27    笑    16
28    可愛    16
29    李麗珍    16
...    ...    ...
1986    額滴    1
1987    痛    1
1988    死於    1
1989    遞紙    1
1990    hahahahhahahah8    1
1991    扭    1
1992    撲    1
1993    卻    1
1994    扛    1
1995    阿公    1
1996    頭子    1
1997    交個    1
1998    對手    1
1999    解構    1
2000    改一改    1
2001    惹不起    1
2002    湖地    1
2003    把持    1
2004    布吉島    1
2005    傻仔    1
2006    莫名    1
2007    ′    1
2008    ‵    1
2009    陸仔    1
2010    興趣    1
2011    祛溼    1
2012    君比靚    1
2013    培養    1
2014    不卡    1
2015    留學    1

個人構思是這樣的。
構建每個shot的詞向量,就去統計每一個shot裏面的每一個詞的詞頻,沒在該shot裏出現過的可是在全集有的爲0,詞向量的順序就和上面的 word_fre 同樣,這樣後面的計算直接就是處理兩個dataframe就能夠了。

同理,對每個comment也是同樣的。每個comment都有一個詞向量dataframe(這裏會形成對內存的大量使用,可是計算起來快)

# 計算每個shot裏面的全部的單詞的詞頻 ------->   缺點:執行速度實在太慢了,後期須要修改
result_s = []
for i in range(split_num):
    shot_word_fre = word_fre.copy()
    shot_word_fre['time'] = 0
    for x in shots[i]:
        for word in x:
            index = shot_word_fre[word_fre[0] == word.encode('utf-8')].index
            shot_word_fre['time'][index] = shot_word_fre['time'][index] + 1
    shot_word_fre = shot_word_fre.drop(1,1)
    result_s.append(shot_word_fre)
    
# 計算每個comment的詞頻向量  -----------> 如今的辦法是每一個 comment 都有一個完整的詞向量,便於後面的計算,問題是這樣很佔內存資源
# 按照每個shot分片後內部的comment之間的delta計算
# result_c = []
# for i in range(split_num):
#     temp = []
#     for j in range(len(shots[i])):
#         shot_word_fre = word_fre.copy()
#         shot_word_fre['time'] = 0
#         for x in shots[i][j]:
#             for word in x:
#                 index = shot_word_fre[word_fre[0] == word.encode('utf-8')].index
#                 shot_word_fre['time'][index] = shot_word_fre['time'][index] + 1
#         shot_word_fre = shot_word_fre.drop(1,1)
#         temp.append(shot_word_fre)
#     result_c.append(temp)


# 計算每個comment的詞頻向量  -----------> 如今的辦法是每一個 comment 都有一個完整的詞向量,便於後面的計算,問題是這樣很佔內存資源
# 不按照每個shot分片後內部的comment之間的delta計算,全部的comment進行計算
result_c = []
for i in range(split_num):
    for j in range(len(shots[i])):
        shot_word_fre = word_fre.copy()
        shot_word_fre['time'] = 0
        for x in shots[i][j]:
            for word in x:
                index = shot_word_fre[word_fre[0] == word.encode('utf-8')].index
                shot_word_fre['time'][index] = shot_word_fre['time'][index] + 1
        shot_word_fre = shot_word_fre.drop(1,1)
        result_c.append(shot_word_fre)

有了詞向量以後就能夠計算$Delta$了
我這裏是將全部的$Delta$都計算出來了,作成了一個下三角矩陣,具體爲何是下三角矩陣仔細想一想就知道了。這樣作的好處是我後面使用$Delta(s,s^{'})$直接就能夠變成delta_s(i,j)或是delta_c(i,j)。

p.s. 我作了修改,以前我理解的是每一個shot裏面的全部的comment之間計算$Delta$值,可是後來我想一想不是這樣的,對於comment應該仍是全部的comment先後進行計算。所以,上面的result_c要改,這裏的delta_c也要改,我把原先的代碼註釋掉了。

# 計算delta<s,_s> : 這裏用的是詞頻向量 餘弦值    -----> 下三角矩陣,後面方便計算
# 從後面的shot往前計算
delta_s = np.zeros((split_num,split_num))
seq = range(split_num)
# 修改 time 的數據類型 to float64
for shot in result_s:
    shot.time = shot.time.astype('float64')

seq.reverse()
for i in seq:
    for j in range(i):
        numerator = np.sum(result_s[i].time*result_s[j].time)
        denominator = pow(np.sum(pow(result_s[i].time,2)),0.5)*pow(np.sum(pow(result_s[j].time,2)),0.5)
        if denominator != 0:
            cos = numerator/denominator
        else:
            cos = 0
        delta_s[i][j] = cos
        

# 計算delta<c,_c> : 這裏用的是詞頻向量 餘弦值    -----> 下三角矩陣,後面方便計算
# 從後往前
# 這裏是按照每一個shot分開而後計算裏面的comment
# seq = range(len(result_c))
# # 修改 time 的數據類型 to float64
# for i in seq:
#     for comment in result_c[i]:
#         comment.time = comment.time.astype('float64')

# # 建立每一個shot的一個矩陣,用list存儲
# delta_c = []
# for i in seq:
#     length = len(result_c[i])
#     delta_c_temp = np.zeros((length,length))
#     delta_c.append(delta_c_temp)

# for i in seq:
#     seq2 = range(len(result_c[i]))
#     seq2.reverse()
#     for j in seq2:
#         for k in range(j):
#             numerator = np.sum(result_c[i][j].time*result_c[i][k].time)
#             denominator = pow(np.sum(pow(result_c[i][j].time,2)),0.5)*pow(np.sum(pow(result_c[i][i].time,2)),0.5)
#             if denominator != 0:
#                 cos = numerator/denominator
#             else:
#                 cos = 0
#             delta_c[i][j][k] = cos
            
    
# 計算delta<c,_c> : 這裏用的是詞頻向量 餘弦值    -----> 下三角矩陣,後面方便計算
# 從後往前
# 這裏是不按照每一個shot分開而後計算裏面的comment
seq = range(len(result_c))
# 修改 time 的數據類型 to float64
for i in seq:
    result_c[i].time = result_c[i].time.astype('float64')

# list存儲
delta_c = np.zeros((len(result_c),len(result_c)))

for i in seq:
    for k in range(i):
        numerator = np.sum(result_c[i].time*result_c[k].time)
        denominator = pow(np.sum(pow(result_c[i].time,2)),0.5)*pow(np.sum(pow(result_c[j].time,2)),0.5)
        if denominator != 0:
            cos = numerator/denominator
        else:
            cos = 0
        delta_c[i][k] = cos

因爲第一個shot沒有在它以前的shot,因此第一個shot的$m_{pre_s}$等於零。
接下來的每一個$m_{pre_s}$都與以前的有關,而且是針對每個topic的,因此$m_{pre_s}$應該是一個矩陣(這點想清楚才能編程)

# 有了上面的矩陣後,計算論文中提到的 M_pre_s 以及 M_pre_c
# 須要兩個衰減參數 gamma_s 以及 gamma_c
# M_pre_s 比較好計算,M_pre_c 比較複雜一點,由於涉及到了每個shot
gamma_s = 0.5 # 我本身設的
gamma_c = 0.3 # 論文中作實驗獲得的最好的值
M_pre_s = np.zeros((split_num,total_topic))  # 行:shot個數    列:topic個數
lambda_s = np.zeros((split_num,total_topic))
sigma_s = 0.1 # 應該是每一個片斷的都不同,可是這裏我認爲其實每一個片斷的topic分佈沒有統計可能性,不合理,都設成同樣的了

# 先初始化 M_pre_s[0] 以及 lambda_s[0]
mu = 0 # 初始的 M_pre_s[0] 都是0
s = np.random.normal(mu,sigma_s,total_topic) # 不知道這個作法對不對,用正態生成x座標,再用pdf去生成y值
lambda_s[0] = st.norm(mu, sigma_s).pdf(s)

# 從 第1的開始
for i in range(1,split_num):
    for topic in range(total_topic): # 先循環topic
        for j in range(i):
            numerator = np.exp(-gamma_s*delta_s[i][j])*lambda_s[j][topic]
            denominator = np.exp(-gamma_s*delta_s[i][j])
        M_pre_s[i][topic] = numerator/denominator
        s = np.random.normal(M_pre_s[i][topic],sigma_s,1)
        lambda_s[i][topic] = st.norm(M_pre_s[i][topic], sigma_s).pdf(s)

須要提一句,我裏面可能會有些變量沒定義就使用了,畢竟這是個人一個心路歷程的總結,不是完整的源代碼,若是須要看源代碼能夠去個人 Github 上看。

接下來就是計算 $m_{pre_c}$和$pi_c$了,處理起來會比較複雜一點,由於裏面涉及了評論的用戶以及用戶對應的topic分佈。這時候若是隻是匹配的話程序會慢到死的,個人作法就是先處理出一張大表(dataframe)以後,每條評論以及對應的user以及對應的topic分佈就能夠很輕鬆快速地查到了。

# 總的topic個數,我在這裏才填了total_topic這個參數,是有點晚了,不過,我以前在這裏還遇到了一些問題,我覺得是每一個shot裏面有固定的topic數,而後總的topic數是相乘的結果,後來通過一番認真思考,我才悔悟到原LDA中的topic數是固定的,而後無論你輸入了多少文檔,這個也應該同樣,只不過文檔變成了shot。
total_topic = 10


# 每個用戶的user-topic分佈
# sigma_u_t 是每一個用戶對於每個topic的sigma值
# 從文件裏面讀取每個用戶的每個topic的sigma值
# 每一行一個用戶 (順序就是下面生成的 user_ 中的順序)
user_sigma = pd.read_csv('sigma_u_t.csv')
user_sigma = user_sigma.drop(['Unnamed: 0'],1)
user_sigma.fillna(0.1)

        
# 利用上面的用戶對應評論的字典 make 一個 dataframe
user_ = pd.DataFrame()
temp1 = []
temp2 = []
for key in user.keys():
    for i in range(len(user[key])):
        temp1.append(key)
        temp2.append(user[key][i])
user_['user'] = temp1
user_['comment'] = temp2


# 處理獲得一個大表,裏面包括全部評論以及評論的人,和每一個人對應的全部的topic的sigma值
# 這裏處理以後好像有點問題,有些用戶沒有,下面我直接就都填充0.1了
comment_per_shot = []
for i in range(split_num):
    temp = pd.DataFrame(com[i])
    u = []
    tem = pd.DataFrame()
    for j in range(len(temp)):
        user_id = user_[user_.comment == temp[0][j]].iloc[0][0]
        u.append(user_id)
        a = user_sigma[user_sigma.user == user_id].iloc[:,1:]
        tem = [tem,a]
        tem = pd.concat(tem)
    tem = tem.reset_index().drop(['index'],1)
    temp['user'] = pd.DataFrame(u)
    temp = temp.join(tem)
    comment_per_shot.append(temp)

# 全部的 comment 的一個 dataframe ,comment-user_id-topic0,1,2...99 ,後面的topic分佈是user_id的
comment_all = pd.concat(comment_per_shot).reset_index().drop('index',1)
# 給那些沒有topic分佈的用戶填充0.1 ----> 缺失值(就是生成用戶的topic分佈表沒有生成全)
comment_all = comment_all.fillna(0.1) # 沒有topic分佈的都填充爲0.1
comment_all = comment_all.rename(columns={0:'comment'})

上面的 comment_all 的結構基本上就是

index - comment - user - user's topic distribution(列數是總的topic個數)

這結構下面我還會更新用做更方便的計算。
而後有個這個 dataframe 以後咱們就能夠計算 $m_{pre_c}$和$pi_c$

# 生成 pi_c 和 M_pre_c 不一樣於上面,由於這裏是對每一個shot的面的comment進行操做
# 先初始化 M_pre_c[0] 和 第0個 shot 裏的第一個 comment 對應的 pi_c[0]
M_pre_c = np.zeros((len(comment_all),total_topic))  # 行:shot個數    列:topic個數
pi_c = np.zeros((len(comment_all),total_topic))
for i in range(total_topic):
    pi_c[0][i] = lambda_s[0][i]*comment_all.iloc[0][i+2] + M_pre_c[0][i]

start = 0 # shot 之間的位移
for q in range(split_num):
    if q == 0:
        for i in range(1,len(com[q])):
            for topic in range(total_topic): # 先循環topic
                numerator = 0
                denominator = 0
                for j in range(i):
                    numerator += np.exp(-gamma_c*delta_c[i][j])*pi_c[j][topic]
                    denominator += np.exp(-gamma_c*delta_c[i][j])
                M_pre_c[i][topic] = numerator/denominator
                pi_c[i][topic] = lambda_s[q][topic]*comment_all.iloc[i][topic+2] + M_pre_c[i][topic]
        start += len(com[q])
    else:
        for i in range(start,start+len(com[q])):
            for topic in range(total_topic): # 先循環topic
                numerator = 0
                denominator = 0
                for j in range(i):
                    numerator += np.exp(-gamma_c*delta_c[i][j])*pi_c[j][topic]
                    denominator += np.exp(-gamma_c*delta_c[i][j])
                M_pre_c[i][topic] = numerator/denominator
                pi_c[i][topic] = lambda_s[q][topic]*comment_all.iloc[i][topic+2] + M_pre_c[i][topic]
        start += len(com[q])

基本任務上面就完成了,下面的是兩個更新公式的實現(下面兩幅圖),看上去只有兩個,可是實現起來特別困難,並且還要考慮時間複雜的問題。

這裏寫圖片描述

這裏寫圖片描述

在我第一次實現出來的版本須要兩個多小時的執行時間(-_-|),後來進行了 dataframe的更新以及採用多個線程池的方式提升了運行的速度。

最後的最後,我終於知道爲何了,我把topic設置的太多了,我認爲一部電影分片10片,每一片裏面有10個topic,這樣一部電影裏面就有了100個topic,計算起來時間好久,這段心路歷程在上面的total_topic這個變量定義的地方我有詳述了,因此在優化以後,我修改了topic的總數

下面我直接貼出我優化後的版本,再去講怎麼優化的。

下面代碼的前半段是生成一些須要的矩陣,固然,我這裏作成了 dataframe。

注意:下面的代碼很長很長,可是,我作了不少的註釋,我相信可以解釋的清楚。

裏面涉及了 GibbsLDApy (我模仿 GibbsLDA++ 實現的python版本)的內容。你們也能夠去看看我實現的這個版本 GibbsLDApy,順便點點贊 :)。後面我會整合全部代碼造成新的 danmuLDA 作成分支。

# 生成 trndocs.dat 文件
# 該文件就是視頻的剪切 -----> 分紅了 split_num 份數,每一份表明一篇文檔
f = open('test_data/trndocs.dat','w')
f.write(str(split_num)+'\n')
for i in range(split_num):
    for j in range(len(shots[i])):
        for k in range(len(shots[i][j])):
            f.write(shots[i][j][k].encode('utf-8')+' ')
    f.write('\n')


import time # 用來記錄代碼執行時間
# 歐拉函數的定義
eur = eulerlib.numtheory.Divisors(10000) # maxnum


# 執行 model 初始化
#  由於如今仍是實驗階段,我沒有和原LDA整合成一個完整的LDA,因此我這裏用了 GibbsLDApy的初始化model的功能
argv = ['-est', '-alpha', '0.5', '-beta', '0.1', '-ntopics', '100', '-niters',
        '1000', '-savestep', '100', '-twords', '20', '-dfile', 'trndocs.dat', '-dir', 'test_data/',
        '-model', 'trnmodel']
pmodel = Model()
pmodel.init(len(argv),argv)


# 將 comment_all 升級成一個新的大表 comment_all_sort 結構爲 {comment,user_id,user_id的topic,該comment屬於的shot的topic分佈},有了這個表,後面的處理會很方便
a1 = pd.concat([comment_all,pd.DataFrame(M_pre_c)],axis=1)
temp = []
for i in range(split_num):
    for j in range(len(shots[i])):
        t = pd.DataFrame(lambda_s)[i:i+1]
        t['shot'] = i
        t['com'] = j
        temp.append(t)
a2 = pd.concat(temp)
a2 = a2.reset_index().drop('index',1)
comment_all_sort = pd.concat([a1,a2],axis=1)
comment_all_sort = comment_all.sort_values('user') # 按照 user 排序


# 生成 user-topic 分佈的 dataframe
x_u_c_t = np.zeros((len(comment_all_sort),total_topic))
for i in range(len(comment_all_sort)):
    for topic in range(total_topic):
        s = np.random.normal(mu,comment_all_sort.icol(topic+2)[i],1)
        x_u_c_t[i][topic] = st.norm(mu, comment_all_sort.icol(topic+2)[i]).pdf(s)
user_id = comment_all_sort.drop_duplicates('user')['user'].reset_index().drop('index',1)
x_u_c_t = user_id.join(pd.DataFrame(x_u_c_t))

        
def lgt(y):
    return math.log(1+math.exp(y))
def dlgt(y):
    return 1/((1+math.exp(y))*np.log(10))

word2id = pd.read_csv('test_data/wordmap.txt',sep=' ') # 讀取單詞對應id的表
column = list(word2id)[0]   # 這個是由於第一行是單詞的個數,會變成index,下面轉換成字典後出現二級索引,因此作了處理
word2id = word2id.to_dict()
yita_lambda_s = lambda_s.copy()

# 線程函數 --> 計算 yita_lambda_s
def calculate_lambda_s(shot,start):
    for topic in range(total_topic):
        result = 0
        lam_s = lambda_s[shot][topic]
        for comment in range(len(shots[shot])):
            u = x_u_c_t[x_u_c_t.user == comment_all.iloc[comment+start][topic+1]]
            x_u = u.iloc[0][topic+1]
            m_pre_c = M_pre_c[comment+start][topic]
            t1 = x_u*dlgt(x_u*lam_s+m_pre_c)
            t2 = 0
            for t in range(total_topic):
                t2 += lgt(comment_all.iloc[comment+start][t+2]*lam_s+M_pre_c[comment+start][t])
            t3 =t2
            t2 = eur.phi(t2)
            t3 = eur.phi(t3+len(shots[shot][comment]))
            n_tc = 0
            for word in shots[shot][comment]:
                word = word.encode('utf-8')
                if word != ' ' :
                    try:
                        num = word2id[column][word]
                        n_tc += pmodel.nw[num][topic]
                    except Exception,e:  
                        print Exception,":",e
            t4 = eur.phi(lgt(x_u*lam_s+m_pre_c) + n_tc)
            t5 = eur.phi(lgt(x_u*lam_s+m_pre_c))
        result += t1 * (t2 - t3 + t4 - t5)
        yita_lambda_s[shot][topic] = -(lam_s+M_pre_s[shot][topic])/(lam_s*lam_s) + result
        
        


# 定義容量比視頻片斷同樣多一些的線程池
pool = threadpool.ThreadPool(split_num+2)     

start_time = time.time() # 下面的多線程開始執行的時間
start = 0 # 初始化,用於控制在哪個shot裏面
for shot in range(len(shots)):
    lst_vars = [shot,start]
    func_var = [(lst_vars, None)]
    start += len(shots[shot]) # start 增長位移,移動一個shot
    requests = threadpool.makeRequests(calculate_lambda_s, func_var)
    [pool.putRequest(req) for req in requests]
pool.wait()
print 'updating lambda_s %d second'% (time.time()-start_time)



# 定義容量爲 total_topic 的一半
pool_cal = threadpool.ThreadPool(total_topic/2)  

# x_u_c_t 的更新代碼
# 注意 :這裏的 comment_all 已經排過序了,和上面的不同
def calculate_x_u_c_t(i,start):
    for topic in range(total_topic):
        result = 0
        for j in range(start,start+user_ct.iloc[i]):
            lambda_s_t = comment_all_sort.iloc[j,topic+total_topic+total_topic+2]
            m_pre_c_t = comment_all_sort.iloc[j,topic+total_topic+2]
            x_u = x_u_c_t.iloc[j,topic+1]
            print(lambda_s_t)
            print(m_pre_c_t)
            print(x_u)
            t1 = lambda_s_t*dlgt(x_u*lambda_s_t + m_pre_c_t)
            t2 = []
            for t in range(total_topic):
                lst_vars = [comment_all_sort.iloc[j,t+2]*comment_all_sort.iloc[j,t+total_topic+total_topic+2]+comment_all_sort.iloc[j,t+total_topic+2],t2]
                func_var = [(lst_vars, None)]
                requests = threadpool.makeRequests(add_t2, func_var)
                [pool_cal.putRequest(req) for req in requests]
            pool_cal.wait()
            t2 = sum(t2)
            print(t2)
            t3 = eur.phi(t2+len(shots[int(comment_all_sort.ix[j,['shot']])][int(comment_all_sort.ix[j,['com']])]))
            t2 = eur.phi(t2)
            n_tc = 0
            for word in shots[int(comment_all_sort.ix[j,['shot']])][int(comment_all_sort.ix[j,['com']])]:
                word = word.encode('utf-8')
                if word != ' ' :
                    try:
                        num = word2id[column][word]
                        n_tc += pmodel.nw[num][topic]
                    except Exception,e:  
                        print Exception,":",e
            t4 = eur.phi(lgt(x_u*lambda_s_t + m_pre_c_t)+ n_tc)
            t5 = eur.phi(lgt(x_u*lambda_s_t + m_pre_c_t))
            result += t1 * (t2 - t3 + t4 - t5)
        x_u_c_t.iloc[j,topic+1] = x_u_c_t.iloc[j,topic+1] - yita*(-x_u/(comment_all_sort.iloc[j,topic+2]*comment_all_sort.iloc[j,topic+2]) + result)
        print(x_u_c_t.iloc[j,topic+1])

# 定義容量比用戶數量十分之一多一些的線程池
pool = threadpool.ThreadPool(len(x_u_c_t)/10+2)     

user_ct = comment_all_sort.groupby('user').count()['topic0']
yita_x_u_c_t = x_u_c_t.copy()
yita = 0.3
start_time = time.time() # 下面的多線程開始執行的時間
start = 0 # 初始化,用於控制在哪個shot裏面
for i in range(len(user_ct)):
    lst_vars = [i,start]
    func_var = [(lst_vars, None)]
    start += user_ct.iloc[i] # start 增長位移,移動一個shot
    requests = threadpool.makeRequests(calculate_x_u_c_t, func_var)
    [pool.putRequest(req) for req in requests]
pool.wait()
print 'updating x_u_c_t %d second'% (time.time()-start_time)

到如今爲止咱們所須要的新功能就都實現啦,後面須要的就是遵循以前的僞代碼圖去階段更新參數~

beta 版本先寫到這,後面我還會補充的,代碼整合過幾天再作,服務器被老師關掉了 -_-||

paper done 2017/05/12