機(jī)器學(xué)習(xí)推薦系統(tǒng)實(shí)戰(zhàn)之電影推薦系統(tǒng)

推薦算法在互聯(lián)網(wǎng)行業(yè)的應(yīng)用非常廣泛,今日頭條、美團(tuán)點(diǎn)評(píng)等都有個(gè)性化推薦,推薦算法抽象來(lái)講,是一種對(duì)于內(nèi)容滿(mǎn)意度的擬合函數(shù),涉及到用戶(hù)特征和內(nèi)容特征,作為模型訓(xùn)練所需維度的兩大來(lái)源,而點(diǎn)擊率,頁(yè)面停留時(shí)間,評(píng)論或下單等都可以作為一個(gè)量化的 Y 值,這樣就可以進(jìn)行特征工程,構(gòu)建出一個(gè)數(shù)據(jù)集,然后選擇一個(gè)合適的監(jiān)督學(xué)習(xí)算法進(jìn)行訓(xùn)練,得到模型后,為客戶(hù)推薦偏好的內(nèi)容,如頭條的話(huà),就是咨詢(xún)和文章,美團(tuán)的就是生活服務(wù)內(nèi)容。

可選擇的模型很多,如協(xié)同過(guò)濾,邏輯斯蒂回歸,基于DNN的模型,F(xiàn)M等。我們使用的方式是,基于內(nèi)容相似度計(jì)算進(jìn)行召回,之后通過(guò)FM模型和邏輯斯蒂回歸模型進(jìn)行精排推薦,下面就分別說(shuō)一下,我們做這個(gè)電影推薦系統(tǒng)過(guò)程中,從數(shù)據(jù)準(zhǔn)備,特征工程,到模型訓(xùn)練和應(yīng)用的整個(gè)過(guò)程。

我們實(shí)現(xiàn)的這個(gè)電影推薦系統(tǒng),爬取的數(shù)據(jù)實(shí)際上維度是相對(duì)少的,特別是用戶(hù)這一側(cè)的維度,正常推薦系統(tǒng)涉及的維度,諸如頁(yè)面停留時(shí)間,點(diǎn)擊頻次,收藏等這些維度都是沒(méi)有的,以及用戶(hù)本身的維度也相對(duì)要少,沒(méi)有地址、年齡、性別等這些基本的維度,這樣我們爬取的數(shù)據(jù)只有打分和評(píng)論這些信息,所以之后我們又從這些信息里再拿出一些統(tǒng)計(jì)維度來(lái)用。我們爬取的電影數(shù)據(jù)(除電影詳情和圖片信息外)是如下這樣的形式:

image

這里的數(shù)據(jù)是有冗余的,又通過(guò)如下的代碼,對(duì)數(shù)據(jù)進(jìn)行按維度合并,去除冗余數(shù)據(jù)條目:

處理主函數(shù),負(fù)責(zé)將多個(gè)冗余數(shù)據(jù)合并為一條電影數(shù)據(jù),將地區(qū),導(dǎo)演,主演,類(lèi)型,特色等維度數(shù)據(jù)合并

    def mainfunc():
        try:
            unable_list = []
        with connection.cursor() as cursor:
            sql='select id,name from movie'
            cout=cursor.execute(sql)
            print("數(shù)量: "+str(cout))

            for row in cursor.fetchall():
                #print(row[1])
                movieinfo = df[df['電影名'] == row[1]]
                if movieinfo.shape[0] == 0:
                    disable_movie(row[0])
                    print('disable movie ' + str(row[1]))
                else:
                    g = lambda x:movieinfo[x].iloc[0]
                    types = movieinfo['類(lèi)型'].tolist()
                    types = reduce(lambda x,y:x+'|'+y,list(set(types)))
                    traits = movieinfo['特色'].tolist()
                    traits = reduce(lambda x,y:x+'|'+y,list(set(traits)))
                    update_one_movie_info(type_=types, actors=g('主演'), region=g('地區(qū)'), director=g('導(dǎo)演'), trait=traits, rat=g('評(píng)分'), id_=row[0])

        connection.commit()
    finally:
        connection.close()

之后開(kāi)始準(zhǔn)備用戶(hù)數(shù)據(jù),我們從用戶(hù)打分的數(shù)據(jù)中,統(tǒng)計(jì)出每一個(gè)用戶(hù)的打分的最大值,最小值,中位數(shù)值和平均值等,從而作為用戶(hù)的一個(gè)附加屬性,存儲(chǔ)于userproex表中:

   'insert into userproex(userid, rmax, rmin, ravg, rcount, rsum, rmedian) values(\'%s\', %s, %s, %s, %s, %s, %s)' % (userid, rmax, rmin, ravg, rcount, rsum, rmedium)
    'update userproex set rmax=%s, rmin=%s, ravg=%s, rmedian=%s, rcount=%s, rsum=%s where userid=\'%s\'' % (rmax, rmin, ravg, rmedium, rcount, rsum, userid)

以上兩個(gè)SQL是最終插入表的時(shí)候用到的,代表準(zhǔn)備用戶(hù)數(shù)據(jù)的最終步驟,其余細(xì)節(jié)可以參考文末的github倉(cāng)庫(kù),不在此贅述,數(shù)據(jù)處理還用到了一些SQL,以及其他處理細(xì)節(jié)。

系統(tǒng)上線(xiàn)運(yùn)行時(shí),第一次是全量的數(shù)據(jù)處理,之后會(huì)是增量處理過(guò)程,這個(gè)后面還會(huì)提到。

我們目前把用戶(hù)數(shù)據(jù)和電影的數(shù)據(jù)的原始數(shù)據(jù)算是準(zhǔn)備好了,下一步開(kāi)始特征工程。做特征工程的思路是,對(duì)type, actors, director, trait四個(gè)類(lèi)型數(shù)據(jù)分別構(gòu)建一個(gè)頻度統(tǒng)計(jì)字典,用于之后的one-hot編碼,代碼如下:

 def get_dim_dict(df, dim_name):
  type_list = list(map(lambda x:x.split('|') ,df[dim_name]))
  type_list = [x for l in type_list for x in l]
  def reduce_func(x, y):
    for i in x:
      if i[0] == y[0][0]:
        x.remove(i)
        x.append(((i[0],i[1] + 1)))
        return x
    x.append(y[0])
    return x
  l = filter(lambda x:x != None, map(lambda x:[(x, 1)], type_list))
  type_zip = reduce(reduce_func, list(l))
  type_dict = {}
  for i in type_zip:
    type_dict[i[0]] = i[1]
  return type_dict

涉及到的冗余數(shù)據(jù)也要?jiǎng)h除

   df_ = df.drop(['ADD_TIME', 'enable', 'rat', 'id', 'name'], axis=1)

將電影數(shù)據(jù)轉(zhuǎn)換為字典列表,由于演員和導(dǎo)演均過(guò)萬(wàn)維,實(shí)際計(jì)算時(shí)過(guò)于稀疏,當(dāng)演員或?qū)а葜怀霈F(xiàn)一次時(shí),標(biāo)記為冷門(mén)演員或?qū)а?/p>

 movie_dict_list = []
for i in df_.index:
  movie_dict = {}
  #type
  for s_type in df_.iloc[i]['type'].split('|'):
    movie_dict[s_type] = 1
  #actors
  for s_actor in df_.iloc[i]['actors'].split('|'):
    if actors_dict[s_actor] < 2:
      movie_dict['other_actor'] = 1
    else:
      movie_dict[s_actor] = 1
  #regios
  movie_dict[df_.iloc[i]['region']] = 1
  #director
  for s_director in df_.iloc[i]['director'].split('|'):
    if director_dict[s_director] < 2:
      movie_dict['other_director'] = 1
    else:
      movie_dict[s_director] = 1
  #trait
  for s_trait in df_.iloc[i]['trait'].split('|'):
    movie_dict[s_trait] = 1
  movie_dict_list.append(movie_dict)

使用DictVectorizer進(jìn)行向量化,做One-hot編碼

v = DictVectorizer()
X = v.fit_transform(movie_dict_list)

這樣的數(shù)據(jù),下面做余弦相似度已經(jīng)可以了,這是特征工程的基本的一個(gè)處理,模型所使用的數(shù)據(jù),需要將電影,評(píng)分,用戶(hù)做一個(gè)數(shù)據(jù)拼接,構(gòu)建訓(xùn)練樣本,并保存CSV,注意這個(gè)CSV不用每次全量構(gòu)建,而是除第一次外都是增量構(gòu)建,通過(guò)mqlog中類(lèi)型為'c'的消息,增量構(gòu)建以comment(評(píng)分)為主的訓(xùn)練樣本,拼接之后的形式如下:

 USERID cf2349f9c01f9a5cd4050aebd30ab74f
  movieid   10533913
type    劇情|奇幻|冒險(xiǎn)|喜劇
actors  艾米·波勒|菲利絲·史密斯|理查德·坎德|比爾·哈德?tīng)東劉易斯·布萊克
region  美國(guó)
director    彼特·道格特|羅納爾多·德?tīng)枴たㄩT(mén)
trait   感人|經(jīng)典|勵(lì)志
rat 8.7
rmax    5
rmin    2
ravg    3.85714
rcount  7
rmedian 4
TIME_DIS    15

這個(gè)數(shù)據(jù)的actors等字段和上面的處理是一樣的,為了之后libfm的使用,在這里需要轉(zhuǎn)換為libsvm的數(shù)據(jù)格式

 dump_svmlight_file(train_X_scaling, train_y_, train_file)

有很多細(xì)節(jié)不在這里描述,這樣大概的特征工程工作就做好了,之后使用相似度計(jì)算,F(xiàn)M,LR進(jìn)行推薦模型的訓(xùn)練。 具體訓(xùn)練的過(guò)程不在這里進(jìn)行闡述了,項(xiàng)目地址:

https://github.com/GavinHacker/recsys_core

模型使用上遵循先召回,后精排的策略,先通過(guò)余弦相似度計(jì)算一個(gè)相似度矩陣,然后根據(jù)這個(gè)矩陣,為用戶(hù)推薦相似的M個(gè)電影,在通過(guò)訓(xùn)練好的FM,LR模型,對(duì)這個(gè)M個(gè)電影做偏好預(yù)估,F(xiàn)M會(huì)預(yù)估一個(gè)用戶(hù)打分,LR會(huì)預(yù)估一個(gè)點(diǎn)擊概率,綜合結(jié)果推送給用戶(hù)作為推薦電影。

項(xiàng)目的整體介紹

  • recsys_ui: 前端技術(shù)(html5+JavaScript+jquery+ajax)
  • recsys_web: 后端技術(shù)(Java+SpringBoot+mysql)
  • recsys_spider: 網(wǎng)絡(luò)爬蟲(chóng)(python+BeautifulSoup)
  • recsys_sql: 使用SQL數(shù)據(jù)處理
  • recsys_model: pandas, libFM, sklearn. pandas數(shù)據(jù)分析和數(shù)據(jù)清洗,使用libFM,sklearn對(duì)模型初步搭建
  • recsys_core: 使用pandas, libFM, sklearn完整的數(shù)據(jù)處理和模型構(gòu)建、訓(xùn)練、預(yù)測(cè)、更新的程序
  • recsys_etl:ETL 處理爬蟲(chóng)增量數(shù)據(jù)時(shí)使用kettle ETL便捷處理數(shù)據(jù)

為了能夠輸出一個(gè)可感受的系統(tǒng),我們采購(gòu)了阿里云服務(wù)器作為數(shù)據(jù)庫(kù)服務(wù)器和應(yīng)用服務(wù)器,在線(xiàn)上搭建了電影推薦系統(tǒng)的第一版,地址是:

www.technologyx.cn

可以注冊(cè),也可以使用已有用戶(hù):

用戶(hù)名 密碼
gavin 123
gavin2 123
wuenda 123

歡迎登錄使用感受一下。

image

設(shè)計(jì)思路

image

用簡(jiǎn)單地方式表述一下設(shè)計(jì)思路,

1.后端服務(wù)recsys_web依賴(lài)于系統(tǒng)數(shù)據(jù)庫(kù)的推薦表‘recmovie’展示給用戶(hù)推薦內(nèi)容
2.用戶(hù)對(duì)電影打分后(暫時(shí)沒(méi)有對(duì)點(diǎn)擊動(dòng)作進(jìn)行響應(yīng)),后臺(tái)應(yīng)用會(huì)向mqlog表插入一條數(shù)據(jù)(消息)。
3.新用戶(hù)注冊(cè),系統(tǒng)會(huì)插入mqlog中一條新用戶(hù)注冊(cè)消息
4.新電影添加,系統(tǒng)會(huì)插入mqlog中一條新電影添加消息
5.推薦模塊recsys_core會(huì)拉取用戶(hù)的打分消息,并且并行的做以下操作:
a.增量的更新訓(xùn)練樣本
b.快速(因服務(wù)器比較卡,目前設(shè)定了延時(shí))對(duì)用戶(hù)行為進(jìn)行基于內(nèi)容推薦的召回
c.訓(xùn)練樣本更新模型
d.使用FM,LR模型對(duì)Item based所召回的數(shù)據(jù)進(jìn)行精排
e.處理新用戶(hù)注冊(cè)消息,監(jiān)聽(tīng)到用戶(hù)注冊(cè)消息后,對(duì)該用戶(hù)的屬性初始化(統(tǒng)計(jì)值)。
f.處理新電影添加消息,更新基于內(nèi)容相似度而生成的相似度矩陣

注:

  • 由于線(xiàn)上資源匱乏,也不想使系統(tǒng)增加復(fù)雜度,所以沒(méi)有直接使用MQ組件,而是以數(shù)據(jù)庫(kù)表作為代替。
  • recsys_model屬于用notebook進(jìn)行數(shù)據(jù)分析和數(shù)據(jù)處理以及建模的草稿,地址為:https://github.com/GavinHacker/recsys_model
  • 其余的所有項(xiàng)目的地址索引為:https://github.com/GavinHacker/technologyx

模型相關(guān)的模塊介紹

增量的處理用戶(hù)comment,即增量處理評(píng)分模塊

這個(gè)模塊負(fù)責(zé)監(jiān)聽(tīng)來(lái)自mqlog的消息,如果消息類(lèi)型是用戶(hù)的新的comment,則對(duì)消息進(jìn)行拉取,并相應(yīng)的把新的comment合并到總的訓(xùn)練樣本集合,并保存到一個(gè)臨時(shí)目錄
然后更新數(shù)據(jù)庫(kù)的config表,把最新的樣本集合(csv格式)的路徑更新上去

運(yùn)行截圖

image

消息隊(duì)列的截圖

image
把csv處理為libsvm數(shù)據(jù)

這個(gè)模塊負(fù)責(zé)把最新的csv文件,異步的處理成libSVM格式的數(shù)據(jù),以供libFM和LR模型使用,根據(jù)系統(tǒng)的性能確定任務(wù)的間隔時(shí)間

運(yùn)行截圖

image
基于內(nèi)容相似度推薦

當(dāng)監(jiān)聽(tīng)到用戶(hù)有新的comment時(shí),該模塊將進(jìn)行基于內(nèi)容相似度的推薦,并按照電影評(píng)分推薦

運(yùn)行截圖

image
libFM預(yù)測(cè)

http://www.libfm.org/

對(duì)已有的基于內(nèi)容推薦召回的電影進(jìn)行模型預(yù)測(cè)打分,呈現(xiàn)時(shí)按照打分排序

如下圖為打分更新

image
邏輯回歸預(yù)測(cè)

對(duì)樣本集中的打分做0,1處理,根據(jù)正負(fù)樣本平衡,> 3分為喜歡 即1, <=3 為0 即不喜歡,這樣使用邏輯回歸做是否喜歡的點(diǎn)擊概率預(yù)估,根據(jù)概率排序

image

做了這個(gè)電影推薦系統(tǒng)后,感覺(jué)算是對(duì)自己這么長(zhǎng)時(shí)間學(xué)習(xí)機(jī)器學(xué)習(xí)知識(shí)做一個(gè)綜合的實(shí)踐,有不少的感悟,現(xiàn)在有很多學(xué)習(xí)機(jī)器學(xué)習(xí)的同學(xué),建議在大家刷論文的同時(shí),也注重在項(xiàng)目中實(shí)踐,計(jì)算機(jī)科學(xué),雖然叫做科學(xué),實(shí)際卻是一門(mén)實(shí)踐性學(xué)科,一些AI頂級(jí)大牛,他們并不是數(shù)學(xué)家,也不是理論家,大多是從理論和實(shí)踐結(jié)合這條路成就的,和金庸小說(shuō)中的武林絕技是一個(gè)道理。

https://www.cnblogs.com/gavinsp/p/recsys.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容