1. 程式人生 > >基於CB,CF,LR演算法的推薦系統實現

基於CB,CF,LR演算法的推薦系統實現

在開篇之前,我們先來說下上次CB,CF演算法實現粗的推薦系統,我們知道,CB,CF演算法只是在召回階段使用,這種推薦出來的item畢竟是粗排的,這篇文章正是對上圖畫上一個圓滿的句號,將CB,CF召回回來的item進行精排,然後選擇分數最高,給使用者推薦出來,那麼,問題來,我們怎麼來做這個精排,這裡就要提出一個新的演算法LR演算法,所以,在說系統實現之前我們先來說說LR邏輯迴歸的知識點,這裡就是簡單的過一下,具體的如果大家想要了解更深入,建議大家再多多看看。

LR邏輯迴歸

在說邏輯迴歸之前,我們先說一下線性迴歸

線性迴歸:

首先我們把迴歸問題抽象成數學中X與Y的問題,X是自變數,Y是因變數,因此我們可以寫成y=f(x,w),這裡W是什麼意思,相當於一個引數,例如y=wx+b,這麼說y代表一篇軍事題材的概率,給定你一篇文章x,是不是可以的到一個y的概率,這個就代表y是軍事題材概率,其中也不乏會遇到y求出不是軍事題材的概率,我們把這個叫做噪音,當噪音大的時候,也就是錯誤樣本多的時候,會影響我們這個預測的y,在生活中,肯定會有噪音,所以我們要儘量避免噪音低點,保證預測的準確性,其中這個y=wx+b這個就是典型的線性迴歸,一個x與對應個y,這x和y組成一對對,有n個x,有n個y,然後就能在座標軸打印出下面的點,每個樣本必須有這兩個元素,缺少一個都不行,打出來的點如下圖,

但是我們希望有一根直線能夠連線這些點,實際情況是不能的,除非彎曲一下,所以我們只能儘量去靠近大部分點,因此我們的目標就是點與直線間的誤差,即直線出來的y與實際點的Y,即Y-y代表誤差,當我們把所有點的誤差相加,肯定是希望所有點的誤差越小越好,當全部分佈到直線上是不是這個誤差為0,這個是我們理想化的目標,這個誤差和就是

這就是一元線性迴歸,但是這一元對應的特徵僅僅是一個特徵x,其實生活中,我們對應的肯定不是一個,肯定有多個特徵,那麼這裡就有多元線性迴歸,自變數x有N個,然後這個方程可以借鑑一元,f(xi,w,b)= wix+b  這裡我們有多少個特徵,就會有多少個w,那麼這裡的w是什麼呢,在一元裡面,這個w是個斜率,但是在多元裡面,這個w不太好描述,我們用一個例子來說明一下:

例如:對於一個人,這個人是個樣本,有很多屬性,我們來判斷這個人是個男還是女

  • 特徵:

  • 身高

  • 體重

  • 頭髮長短

  • 膚色

  • 音色

這五個特徵中,你認為哪個特徵最重要,能夠準確判斷這個人是男還是女,當然,我們會根據每個特徵重要性來進行打分,我們給五個特徵設個權重,這裡w就是代表權重,w權重是在【0-1】越是重要的越靠近1,不重要靠近0

  • 特徵:

  • 身高    0.5

  • 體重    0.4

  • 頭髮長短   0.2

  • 膚色    0.1

  • 音色    0.1

我們首先設定個預值0.6,這個是什麼呢,就是當一個人最後經過上面五個特徵計算得到的分數大於0.6,我們認為男,小於則認為女

好了我們把判斷的依據已經分配好權重,若來了個人(樣本),我們來判斷是男還是女,你報下上面五個特徵,然後我們進行計算,最後我們來打個分數,假如是0.9,那麼我們認為是男的。

 也就是說,我們把上面五個特徵乘以權重再相加得到最後分數,這個分數來判斷分類的問題,所以有了這些權重w,事情就好辦了,那麼w怎麼來,w是什麼,w就是模型,就像我們的NB中一樣,我們有了先驗概率,有了條件概率,我們是不是就能得到分數,進行分類,在迴歸裡面,我們的最後目標就是得到w,但是大家也別忘了這裡還有個b,這裡這個b是個偏置,這個是什麼意思呢,相當於整體的偏差,同樣是做性別識別,我們給個偏差0.4,就是說我們經過計算最終得到的分數需要再加上這個偏置才是最後的結果,這個偏置可正可負,因此偏置就是這麼計算的,是對整體的側重

對於一元模型多元模型,我們的學習目標是最小化誤差平方和,這個是學習目標,不是最後的目標,所以這個多元的學習目標即:

那麼我們通過學習目標是要最後得到w,這裡x不是未知數,x是已知的,不要搞混了,怎麼得到w呢,我們知道這個是個凸函式,相當於對該函式w求導,令導數=0,求極值點,就能得到w

邏輯迴歸:

好了,說了這麼多,其實都是對邏輯迴歸做個鋪墊,我們充分理解剛剛的w和b,接下來就理解邏輯迴歸就很簡單了

首先我們說下邏輯迴歸的思想:

我們做迴歸,最後很多情況是做個分類,比如說我們以分類舉例子,比如這個y因變數是個類別號,要麼等於1要麼等於0,或者說1代表發生,0代表不發生

我們的目標:根據特徵x,預測發生的概率y=1 即p(y=1|x)

那麼這裡面,我們根據剛剛說的y=wx,只用把wx相乘即可,我們這裡求y=1的概率,相當於求f(x)那麼就有f(x)=wx,那麼我們就有

假定P(y=1|x)依賴於線性函式wx,wx可能不在[0-1]內,但是概率肯定是在這個裡面,那麼怎麼辦呢?這裡我們就需要用到指數函式即

exp(wx)的值域[0 ,正無窮),怎麼保證左邊也是該值域呢,這裡我們用對立面考慮,有P(y=1|x),就有P(y=0|x),那麼P(y=0|x) = 1-P(y=1|x)

所以就有P(y=1|x)/1-P(y=1|x) = exp(wx)因此,我們就能夠得到:P(y=1|x) = 1/1+exp(-wx)

因此我們就得到了sigmoid函式,值域就是[0,1],有了這個我們就有類別概率P(y=1|x) = n(wx),P(y=0|x) = 1-n(wx)

我們知道這個了,我們來計算w,這個我們需要到似然函式,什麼是最大似然,我們看下這張圖,就一目瞭然

總結起來,最大似然估計的目的就是:利用已知的樣本結果,反推最有可能(最大概率)導致這樣結果的引數值。

        原理:極大似然估計是建立在極大似然原理的基礎上的一個統計方法,是概率論在統計學中的應用。極大似然估計提供了一種給定觀察資料來評估模型引數的方法,即:“模型已定,引數未知”。通過若干次試驗,觀察其結果,利用試驗結果得到某個引數值能夠使樣本出現的概率為最大,則稱為極大似然估計。

知道最大似然的方法,我們就可以用到邏輯迴歸的方法:首先看下如下公式:公式不全,少個求和的符號sigma,這裡面I代表符號函式,當樣本標籤等於1的時候,I就等於1,等於0的時候,I就等於0,所以公式分為兩個部分,左邊要求所有的都等於1,右邊要求所有的都等於0,基本上會把所有的樣本通過這個公式涵蓋了,log裡面表示是我們給你個樣本,計算標籤屬於1的概率,然後我們在外面求個log結果最大化,不影響結果判斷,,那麼這裡為什麼會有個負數,後面這個式子是個凹函式,相當於把拋物線反轉成凸函式,求最小值,因此這個公式即負對數似然函式,那麼第二個Lw即相當於把剛剛sigmoid函式代入得到

那麼,我們來看如何通過理論來計算w,剛開始我們不知道w,那麼我們隨便定義一個w,然後計算出一個F(w),然後我們來計算梯度,就是上面那個公式,我們有w,有x,有y,是不是能計算出梯度,計算出梯度後,我們嘗試更新,通過步長*梯度再加上w,得到新的w,然後我們就能得到newF(w),新的fw和舊的fw是不是有個落差,若當這個落差非常小的時候,是不是我們就到了凸函式的底部,到此我們就停止訓練,下面就是過程

至此我們邏輯迴歸的基本知識就完成了。

推薦系統

我們接下來回歸正題,進行推薦引擎實現,從文章的開頭的推薦引擎的架構圖中我們可以看到總的過程,一共有這麼幾個部分:

  1. web頁面,使用者點選物品,我們獲取userid,itemid

  2. 當我們拿到使用者行為資訊後,進行檢索引擎的搜尋,這裡搜尋即召回階段,這裡用到兩個演算法:CB演算法  CF演算法
  3. 召回完成之後,我們需要對召回的物品進行精排,怎麼精排呢,這裡我們需要用到LR邏輯迴歸演算法,需要做一個排序模型,這個模型肯定是一個個性化模型,比如說我們有100個item,張三來了和李四來了,我們給這兩個人推薦的肯定是不一樣的,那麼這個模型該怎麼構建,這裡就需要用到使用者的特徵,物品的元資料資訊。

  4. 精排完成之後,我們需要過濾一下,取出排名前五的返回給頁面

上面是總的推薦過程整體思路,這個比較粗,我們下來進行一一解析。

我們來說一下接下來程式碼的思路:

  1. 元資料準備,包括使用者資料,物品資料,使用者行為資料

  2. Nosql中CB演算法資料準備

  3. Nosql中CF演算法資料準備

  4. LR邏輯迴歸訓練模型的資料準備,即使用者特徵資料,物品特徵資料

  5. 模型準備即通過LR演算法訓練模型資料得到W,B

  6. 推薦系統實現

              解析請求

              載入模型,使用者特徵模型,物品特徵模型

              進行召回,檢索候選池,這裡召回我們同時使用CB,CF

              獲取使用者特徵

              獲取物品特徵

              將召回的物品結合模型進行打分計算sigmoid

              精排序

              過濾即得到TOPN

              封裝打包返回給頁面

元資料

我們做系統推薦,剛開始我們要通過各種渠道去收集使用者特徵資料,使用者行為資料,這裡我們直接把已經收集好並處理好的資料給大家,給大家展示一部分

物品的元資料:第一列是itemid,第二列是itemName,第三列是描述,第四列是總時長,第五列是地域,可能出現是空的情況,第六列是標籤,給這打的標籤

使用者的元資料:第一列:使用者id,第二列性別,第三列年齡段,第四列薪資,第五列:地域

使用者行為資料:第一列:使用者id,第二列:物品id,第三列:觀看收聽市場,第四列:哪個時刻觀看或是收聽的

以上就是三個最基本的元資料,使用者特徵,物品特徵,以及連線使用者和物品的使用者行為資料,好了基本的資料型別已經準備好,我們開始進行我們的推薦系統第一步,元資料準備工作,我們需要把三個元資料拼接到一起,這麼做的目的為了後面CB,CF,LR演算法資料準備工作,好了,主要的思路就是把三個元資料放到一個檔案裡面,這個應該不難,我們看下程式碼:這塊程式碼沒有必要說明了,很簡單,大家看下注釋:

#!usr/bin/python
# -*- coding: UTF-8 -*-
'''
    總體思路:處理原始的資料:1、物品元資料 2、使用者元資料 3、使用者行為資料
            把三類資料統一到一個檔案裡面,供後面cb、cf演算法進行計算權重
'''

import sys

#找到三類原始資料檔案,物品元資料,使用者元資料,使用者行為資料
music_data = '/home/python_test/pyCharm/rankModel/initData/music_meta'
user_data = '/home/python_test/pyCharm/rankModel/initData/user_profile.data'
user_music_data = '/home/python_test/pyCharm/rankModel/initData/user_watch_pref.sml'

#將三類處理後的元資料放到新的檔案裡面,這裡我們需要定一個檔名,路徑
output_file = '../resultData/merge_base.data'

#用寫的方式開啟檔案,為下文寫入做準備
ofile = open(output_file,'w')

#step1 處理物品元資料,將處理後的結果放入字典裡面,key是itemid,value為物品對應的資訊為最後寫入做準備
item_dict = {}
with open(music_data,'r') as fd:
    for line in fd:
        ss = line.strip().split('\001')
        if len(ss) != 6:
            continue
        itemid , name ,desc, totalTime,location ,tags = ss
        item_dict[itemid] = '\001'.join([name ,desc, totalTime,location ,tags])

#step2 處理使用者元資料,將處理後的結果放入字典裡面,key是使用者id,value是使用者資訊
user_dict = {}
with open(user_data,'r') as fd :
    for line in fd:
        ss = line.strip().split(',')
        if len(ss) != 5:
            continue
        userid,gender,age,salary,location = ss
        user_dict[userid] = '\001'.join([gender,age,salary,location])

#step3 寫入最後的資訊,將使用者行為資料進行處理,把step1和step2得到的資料一併歸納在檔案裡面
with open(user_music_data,'r') as fd:
    for line in fd:
        ss = line.strip().split('\001')
        if len(ss) != 4:
            continue
        userid,itemid,watchTime,hour = ss

        if userid not in user_dict:
            continue
        if itemid not in item_dict:
            continue
        ofile.write('\001'.join([userid,itemid,watchTime,hour,user_dict[userid],item_dict[itemid]]))
        ofile.write('\n')

ofile.close()

資料準備完畢,我們下看結果:從左往右一次是:使用者ID,物品ID,觀看時長,觀看時間段,性別,年齡,薪資,地域,物品名稱,描述,總時長,地域,標籤

NoSql CB演算法資料準備

我們把基本資料準備完成,接下來進行CB演算法的資料準備,這裡我們就要用到我們以前學的倒排索引的方式,將item資料全部放倒NoSql庫中,這裡我們NoSql使用reids資料庫,我們CB演算法資料準備分這幾個部分:

  1. 對item分詞,計算權重值

  2. 將生成token,itemid,score 檔案我們進行轉換,轉成ii矩陣,進行兩兩配對,包含相同的token的item放在一起,說明這兩個相似,以達到最後召回的目的。

  3. 格式化資料,放入redis資料庫

整體思路說完,我們來一步一步解析下,

  • item分詞,計算權重值

這裡我們需要注意,分詞的時候有name,desc,tags三個方面,但是我們肯定對name分詞更加關注,所以這裡我們要給這三個分詞加權重,然後最後形成token,itemid,score檔案,我們設定三個形式的權重,name權重高點給0.9,描述給0.2,標籤給個0.05,其實這個權重可以自己定義,我們首先把元資料階段準備好的資料進行資料提取,提取我們需要的itemid,name,tags,desc,然後進行itemid去重複,因為相同的itemName沒必要重複新增,分詞,去重複後,我們進行分詞,然後用分詞後的tfidf值乘以權重,在切分描述的時候,我們需要把字典中有的詞的分數進行相加,防止重複了

#!usr/bin/python
# -*- coding: UTF-8 -*-
'''
    總體思路:將初始化好的使用者,物品,使用者行為資料進行處理,目的是為了得到token,itemid,score,我們知道生成的資料裡面的name,將itemName
            進行分詞,得到tfidf權重,同時將desc進行分詞,處理name和desc,我們在元資料中還有已經分類好的tags,tags已經切分好了
            沒必要再次進行切分,只需要用idf詞表查處權重即可,但是對於name、desc、tags這三個分詞結果,我們對name的結果應該更加偏重
            一點,所以分別對這三類得出的分數再次進行分數權重劃分,最後得到cb的初始資料
'''
import sys
sys.path.append('../')
reload(sys)
sys.setdefaultencoding('utf-8')

import jieba
import jieba.posseg
import jieba.analyse

#讀入初始資料
input_file = "../resultData/merge_base.data"

#定義輸出檔案
output_file = "../resultData/cb_init.data"
ofile = open(output_file,'w')
#定義三類的權重分數
RATIO_FOR_NAME = 0.9
RATIO_FOR_DESC = 0.1
RATIO_FOR_TAGS = 0.05
#為tags讀入idf權重值
idf_file = "../resultData/idf.txt"
idf_dict = {}
with open(idf_file,'r') as fd:
    for line in fd:
        token, idf_score = line.strip().split(' ')
        idf_dict[token] = idf_score
#開始處理初始資料
item_set = set()
with open(input_file,'r') as fd:
    for line in fd:
        ss = line.strip().split('\001')
        if len(ss) != 13:
            continue
        #使用者行為
        userid = ss[0].strip()
        itemid = ss[1].strip()
        watchTime = ss[2].strip()
        hour = ss[3].strip()
        #使用者元資料
        gender = ss[4].strip()
        age = ss[5].strip()
        salary = ss[6].strip()
        userLocation = ss[7].strip()
        #物品元資料
        name = ss[8].strip()
        desc = ss[9].strip()
        totalTime = ss[10].strip()
        itemLocation = ss[11].strip()
        tags = ss[12].strip()

        #對item去重,相同的itemid不用再計算,因為都一樣,這裡用到continue特性,當不同的時候才繼續執行下面的程式碼
        if itemid not in item_set:
            item_set.add(itemid)
        else:
            continue

        #去掉重複後的itemID,然後我們進行分詞,計算權重,放到字典裡面
        token_dict = {}
        #對name統計
        for a in jieba.analyse.extract_tags(name, withWeight=True):
            token = a[0]
            score = a[1]
            token_dict[token] = float(score) * RATIO_FOR_NAME
        #對desc進行分詞,這裡需要注意的是描述一般會含有name中的詞,這裡我們把有的詞的分數進行相加,沒有的放入
        for a in jieba.analyse.extract_tags(desc,withWeight=True):
            token = a[0]
            score = float(a[1])

            if token not in token_dict:
                token_dict[token] = score * RATIO_FOR_DESC
            else:
                token_dict[token] += score * RATIO_FOR_DESC

        #對tags 進行分數計算
        for a in tags.strip().split(','):
            if a.strip() not in idf_dict:
                continue

            if a not in token_dict:
                token_dict[a] = (float(idf_dict[a]) * RATIO_FOR_TAGS)
            else:
                token_dict[a] += (float(idf_dict[a]) * RATIO_FOR_TAGS)

        #迴圈遍歷token_dict,輸出toke,itemid,score
        for key,val in token_dict.items():
            token = key.strip()
            score = str(val)

            ofile.write(','.join([token, itemid, score]))
            ofile.write("\n")

ofile.close()
  • 相似的item配對,II矩陣的形成

相似度計算,我們要用到MapReduce的框架來進行,只要是用到shuffle階段,對map出來的結果排序,reduce進行兩兩配對,這裡就是主要的wordcount邏輯,主要說下注意的部分:我們需要把兩兩分數的過濾掉,或是把itemA和itemB相同的item過濾掉,因為這部分資料沒有任何意義

map階段:

#!usr/bin/python
# -*- coding: UTF-8 -*-

'''
    總體思路:這裡需要把初始化後的結果進行map排序,為了後續兩兩取pair對,所以這裡我們需要進行map,其實什麼也不用操作輸出即可
'''
import sys
import re

for line in sys.stdin:
    ss = line.strip().split(',')
    if len(ss) != 3:
        continue

    r1 = u'[a-zA-Z0-9’!"#$%&\'()*+,-./:;<=>[email protected],。?★、…【】《》?“”‘’![\\]^_`{|}~]+'
    ss[0] = re.sub(r1,'',ss[0])
    if len(ss[0]) == 0:
        continue
    print ','.join([ss[0], ss[1], ss[2]])

reduce階段:

#!usr/bin/python
# -*- coding: UTF-8 -*
'''
    我們前面已經在pair reduce之前我們做過map操作,輸出以token,item,score輸出,所以排序是token排好的序
    這裡我們相當於求的是II矩陣,所以是根相同的token的item進行相似度計算
    思路:
        1、進行user統計,若相同,把相同的user的item和score放入list裡面
        2、不相同,開始進行兩兩配對,迴圈該list,進行兩兩配對,求出相似度
'''

import  sys
import  math

cur_token = None
item_score_list = []
for line in sys.stdin:
    ss = line.strip().split(',')
    itemid = ss[1]
    score = float(ss[2])
    if len(ss) != 3:
        continue
    if cur_token == None:
        cur_token = ss[0]

    if cur_token != ss[0]:

        #這裡需要注意的是range的區間前閉後開,同時注意range中即使前閉後開,剛開始是從0即列表裡面的第一個,迴圈到列表最後一個的前一個
        for i in range(0,len(item_score_list)-1):
            for j in range(i+1,len(item_score_list)):
                item_a,score_a = item_score_list[i]
                item_b,score_b = item_score_list[j]
                #score = float(score_a * score_b)/float(math.sqrt(pow(score_a,2))*math.sqrt(pow(score_b,2)))
                #輸出兩遍的目的是為了形成II矩陣的對稱
                score = float(score_a*score_b)
                if item_a == item_b:
                    continue
                if score < 0.08:
                    continue

                print "%s\t%s\t%s" % (item_a, item_b, score)
                print "%s\t%s\t%s" % (item_b, item_a, score)
        cur_token = ss[0]
        item_score_list = []

    item_score_list.append((itemid,float(score)))

for i in range(0, len(item_score_list) - 1):
    for j in range(i + 1, len(item_score_list)):
        item_a, score_a = item_score_list[i]
        item_b, score_b = item_score_list[j]
        #score = (score_a * score_b) / (math.sqrt(pow(score_a, 2)) * math.sqrt(pow(score_b, 2))
        # 輸出兩遍的目的是為了形成II矩陣的對稱
        score = float(score_a * score_b)
        if item_a == item_b:
            continue
        if score < 0.08:
            continue

        print "%s\t%s\t%s" % (item_a, item_b, score)
        print "%s\t%s\t%s" % (item_b, item_a, score)
  • 格式化資料放入redis庫,我們需要將itemA設定為key ,itemB和score組成 itemB:score這種方式,利用字典,將與itemA配對的都放在一起,最後組成redis的key為itemA,value為與A有關聯的其他item,生成資料資料後,利用redis的管道形式批量插入redis庫,再插入前格式化一下資料

#!usr/bin/python
# -*- coding: UTF-8 -*-

'''
    思路:我們已經通過CB演算法得到itemA,itemB,score,然後我們需要把放入到redis庫,存入的方法,我們以itemA為key
         與itemA有相似度的itemB,和分數,以value的形式存入記憶體庫
         1、建立一個字典,將key放入itemA,value 放入與A對應的不同b和分數
         2、迴圈遍歷字典,將key加上字首CB,value以從大到小的分數進行排序,並且相同的item以——分割,item和score間用:分割
'''
import sys

#讀入原始資料
input_file = "../resultData/cb.result"

#輸出的檔案
output_file = "../resultData/cbRedis.data"
ofile = open(output_file, 'w')
MAX_RECLIST_SIZE = 50
PREFIX = 'CB_'

rec_dict = {}
with open(input_file,'r') as fd:
    for line in fd:
        itemid_A, itemid_B, score = line.strip().split('\t')


        #判斷itemA在不在該字典裡面,若不在,建立一個key為itemA的列表,把與itemA相關聯的itemB和score新增進去
        if itemid_A not in rec_dict:
            rec_dict[itemid_A] = []
        rec_dict[itemid_A].append((itemid_B, score))
    print itemid_A, itemid_B, score
#迴圈遍歷字典,格式化資料,把itemB和score中間以:分割,不同的itemB以_分割
for k,v in rec_dict.items():
    key = PREFIX+k

    #接下來格式化資料,將資料以從大到小排列後再格式化
    #排序,由於資料量大,我們只取100個
    #拍好序後,我們來格式化資料
    result = '_'.join([':'.join([tu[0], str(round(float(tu[1]), 6))]) \
              for tu in sorted(v, key=lambda x: x[1], reverse=True)[:MAX_RECLIST_SIZE]])

    ofile.write(' '.join(['SET',key,result]))
    ofile.write("\n")

ofile.close()

NoSql CF演算法資料準備

和CB演算法一樣,準備NoSql資料庫的資料,基本的邏輯一致,分為下面這幾個步驟:

  1. Item 喜好程度的分數計算

  2. CF演算法資料準備,形成II矩陣,歸一化,兩兩取pair,總和分數

  3. redis資料格式化

  • Item喜好程度的分數計算

這裡說明一下,CF演算法和CB演算法不同的是,我們的資料不一樣,CB是用item的屬性,例如名稱分詞,然後權重,但是CF是使用者對item打分,那麼這個打分我們在這裡怎麼表示,我們知道使用者收聽的時長,也知道item的總時長,是不是這個喜好程度可以用收聽時長比上總時長表示,這樣我們就得到這個分數了,那麼分數計算就是做這個事情,但是相同的我們需要加起來才能把使用者對同一個item的喜愛程度,最後只需要輸出,itemid,userid,score

#!usr/bin/python
# -*- coding: UTF-8 -*-
'''
    總體思路:首先和cb一樣,對處理完的使用者元資料,物品元資料,行為資料進行cf資料準備工作,我們的目的事輸出:user,item score
            其中主要是的到使用者對item的score,這裡score怎麼算呢,當然是使用者收聽的音樂的時常和總的時長相除的到
'''
import  sys

#讀入初始資料
input_file = "../resultData/merge_base.data"

#定義輸出檔案
output_file = "../resultData/cf_init.data"
ofile = open(output_file,'w')

key_dict = {}
with open(input_file,'r') as fd:
    for line in fd:
        ss = line.strip().split('\001')
        if len(ss) != 13:
            continue
        #使用者行為
        userid = ss[0].strip()
        itemid = ss[1].strip()
        watchTime = ss[2].strip()
        hour = ss[3].strip()
        #使用者元資料
        gender = ss[4].strip()
        age = ss[5].strip()
        salary = ss[6].strip()
        userLocation = ss[7].strip()
        #物品元資料
        name = ss[8].strip()
        desc = ss[9].strip()
        totalTime = ss[10].strip()
        itemLocation = ss[11].strip()
        tags = ss[12].strip()

        #拼接key,為了將同一個使用者對相同物品的時長全部得到需要做個聚類
        key = '_'.join([userid,itemid])

        if key not in key_dict:
            key_dict[key] = []
        key_dict[key].append((float(watchTime),float(totalTime)))

#迴圈處理相同使用者對相同item的分數
for key ,val in key_dict.items():
    wathAll = 0
    totalAll = 0

    for v in val:
        wathAll += v[0]
        totalAll += v[1]

    #得到userid對item的最終分數
    score = float(wathAll) / float(totalAll)
    userid, itemid = key.strip().split('_')

    ofile.write(','.join([userid, itemid, str(score)]))
    ofile.write("\n")

ofile.close()
  • II矩陣資料準備,歸一化,取pair對,計算總和

這裡我們準備redis資料分為這麼幾個部分,我們來一一解析一下,當然這部分的資料需要利用到MapReduce框架,進行map和reduce排序,

  • 歸一化

       歸一化階段我們主要是將相同的item進行單位模計算,因為後續我們要用到cos相似度計算公式,將相同的item的分數進行平方和再開根號,最後進行單位化,這裡再貼一下,這部分在以前的部落格裡面講的很明確,就少囉嗦了,不會可以看看CF演算法實現,連結https://blog.csdn.net/Jameslvt/article/details/81280557

map階段,只要將轉資料換成item,user,score ,因為我們要在reduce階段進行相同item單位化,要充分用到shuffle階段的排序

#!usr/bin/python
# -*- coding: UTF-8 -*-
'''
    思路:轉換成i,u,s的矩陣
'''
import  sys

for line in sys.stdin:
    ss = line.strip().split(',')
    if len(ss) != 3:
        continue
    u , i , s = ss
    print '\t'.join([i,u,s])

reduce階段,我們需要將相同item平方和相加開根號,然後再單位化計算,最後輸出

#!usr/bin/python
# -*- coding: UTF-8 -*-
'''
    在map的基礎上將每個item進行歸一化,map已經將相同的item排好序,這裡我們根據map的結果進行給先平方再開根號:
    思路 :
        1、擷取字串,取出item,user,socre
        2、在for迴圈中進行判斷,當前的item和下一個是否相同,要是相同,將相同的放到列表(user,score)列表裡面,否則往下執行
        3、若不相同,迴圈user和score列表,計算模計算,然後再次迴圈,進行單位化計算
'''

import sys
import math

cur_item = None
user_score_list = []
for line in sys.stdin:
    ss = line.strip().split('\t')
    if len(ss) != 3:
        continue

    item = ss[0]
    userid = ss[1]
    score = ss[2]

    #wordcount判斷,當前和下一個是否相同,相同新增到列表,不相同進行歸一化計算
    if cur_item == None:
        cur_item = item
    if cur_item != item:
        #定義sum
        sum = 0.0
        #迴圈列表進行模向量計算
        for ss in user_score_list:
            user,s = ss
            sum += pow(s,2)
        sum = math.sqrt(sum)

        #單位化計算
        for touple in user_score_list:
            u,s = touple
            # 進行單位化完成後,我們輸出重置成原來的user-item-score輸出
            print "%s\t%s\t%s" % (u, cur_item, float(s / sum))

        #初始化這兩個變數
        cur_item = item
        user_score_list = []

    user_score_list.append((userid,float(score)))

#定義sum
sum = 0.0
#迴圈列表進行模向量計算
for ss in user_score_list:
    user,s = ss
    sum += pow(s,2)
sum = math.sqrt(sum)
#單位化計算
for touple in user_score_list:
    u,s = touple
    # 進行單位化完成後,我們輸出重置成原來的user-item-score輸出
    print "%s\t%s\t%s" % (u, cur_item, float(s / sum))
  • 兩兩取pair對

        兩兩取pair對,我們在map階段,其實什麼都不用做,保證輸出user,itemid,score即可

map階段

#!usr/bin/python
# -*- coding: UTF-8 -*-

#在進行pair取對之前,什麼都不需要做,輸出就行

import  sys

for line in sys.stdin:
    u, i, s = line.strip().split('\t')
    print "%s\t%s\t%s" % (u, i, s)

reduce階段,我們需要將同一個使用者下面的item進行兩兩取對,因為我們要形成II矩陣,就必須以user為參考單位,相反形成uu矩陣,就必須以Item參考,所以將同一個使用者下的item進行兩兩取對,並將分數相乘,就得到臨時這個相似度,因為還沒有對相同pair對的分數相加,這個是最後一步要做的。

#!usr/bin/python
# -*- coding: UTF-8 -*-

'''
    思路:進行map排好序之後,我們的會得到相同user對應的不同item和score,這裡我們主要的思路是進行相同使用者兩兩取pair
         1、進行判斷,當前使用者和下一個使用者是不是一樣,若是不一樣,我們進行兩兩取對,形成ii矩陣
         2、若是相同,我們將不同的item和score放入list裡面
'''

import  sys

cur_user = None
item_score_list = []
for line in sys.stdin:
    user,item,score = line.strip().split('\t')

    if cur_user == None:
        cur_user= user

    if cur_user != user:

        #進行兩兩pair,利用range函式
        for i in range(0,len(item_score_list)-1):
            for j in range(i+1,len(item_score_list)):
                item_a, score_a = item_score_list[i]
                item_b, score_b = item_score_list[j]
                # 輸出兩遍的目的是為了形成II矩陣的對稱
                print "%s\t%s\t%s" % (item_a, item_b, score_a * score_b)
                print "%s\t%s\t%s" % (item_b, item_a, score_a * score_b)

        cur_user = user
        item_score_list = []

    item_score_list.append((item,float(score)))

#進行兩兩pair,利用range函式
for i in range(0,len(item_score_list)-1):
    for j in range(i+1,len(item_score_list)):
        item_a, score_a = item_score_list[i]
        item_b, score_b = item_score_list[j]
        # 輸出兩遍的目的是為了形成II矩陣的對稱
        print "%s\t%s\t%s" % (item_a, item_b, score_a * score_b)
        print "%s\t%s\t%s" % (item_b, item_a, score_a * score_b)
  • 進行最終分數求和

       我們最後的階段是要將相同pair的分數相加才能得到兩個item的相似度

map階段,這裡我們因為要將相同item對排序到一起,就要將pair組成一個key進行排序,將同一個partition後資料放倒一個reduce桶中,再說一下MapReduce框架中國年shuffle階段,key只是做排序,partition只是做分割槽,不要搞混了。

#!usr/bin/python
# -*- coding: UTF-8 -*-

'''
    sum的map中,我們需要把相同的itemA,itemB組成key,為了使相同的key能夠在shuffle階段分配到同一個reduce中,
    因為是計算item的相似度,要把相同的相加
'''

import  sys

for line in sys.stdin:
    item_a,item_b,score = line.strip().split('\t')
    key = '#'.join([item_a,item_b])
    print '%s\t%s' %(key,score)

reduce階段主要任務就是將相同的item的pair對相加

#!usr/bin/python
# -*- coding: UTF-8 -*-

'''
    思路:將相同的item的分數進行相加,得到最後的相似度
'''

import  sys

cur_item = None
score = 0.0
for line in sys.stdin:
    item, s = line.strip().split('\t')
    if not cur_item:
        cur_item = item
    if cur_item != item:
        ss = item.split("#")
        if len(ss) != 2:
            continue
        item_a, item_b = ss
        print "%s\t%s\t%s" % (item_a, item_b, score)

        cur_item = item
        score = 0.0

    score += float(s)

ss = item.split("#")
if len(ss) != 2:
    sys.exit()
item_a, item_b = ss
print "%s\t%s\t%s" % (item_a, item_b, score)
  • redis資料格式化

這個階段和CB階段的格式化完全一樣,不再過多的解釋

#!usr/bin/python
# -*- coding: UTF-8 -*-
'''
    思路:這個處理的邏輯和CB中完全一樣,不一樣的是redis的key是CF開頭
'''
import sys

#讀入原始資料
input_file = "../resultData/cf.result"

#輸出的檔案
output_file = "../resultData/cfRedis.data"
ofile = open(output_file, 'w')
MAX_RECLIST_SIZE = 100
PREFIX = 'CF_'

rec_dict = {}
with open(input_file,'r') as fd:
    for line in fd:
        itemid_A, itemid_B, score = line.strip().split('\t')

        #判斷itemA在不在該字典裡面,若不在,建立一個key為itemA的列表,把與itemA相關聯的itemB和score新增進去
        if itemid_A not in rec_dict:
            rec_dict[itemid_A] = []
        rec_dict[itemid_A].append((itemid_B, score))

#迴圈遍歷字典,格式化資料,把itemB和score中間以:分割,不同的itemB以_分割
for k,v in rec_dict.items():
    key = PREFIX+k
    #接下來格式化資料,將資料以從大到小排列後再格式化
    #排序,由於資料量大,我們只取100個
    list = sorted(v,key=lambda x:x[1],reverse=True)[:MAX_RECLIST_SIZE]
    #拍好序後,我們來格式化資料
    result = '_'.join([':'.join([str(val[0]),str(round(float(val[1]),6))]) for val in list])

    ofile.write(' '.join(['SET',key,result]))
    ofile.write("\n")

ofile.close()

LR邏輯迴歸訓練模型的資料準備,即使用者特徵資料,物品特徵資料

上面都是我們以前用到過的,接下來我們說下主要精排利用的LR邏輯迴歸用到的資料,一般邏輯迴歸進行lr訓練模型資料一般格式都是如下:

第一列,一般都是代表標籤,也就是我們邏輯迴歸中的y,以後的都代表特徵,冒號之前代表一個特徵,例如身高,或是年齡,冒號之後是權重,後面的都代表不同的特徵組成的訓練測試資料

那麼,我們該如何將我們的資料轉換成這樣的格式,我們來看下,我們要處理的肯定是第一步元資料處理成功的資料

思路:

  1. 得到使用者畫像資料,物品資料,標籤資料

    我們首先確定label,那麼這個label怎麼確定,我們以實際的wathTime和totalTime除一下,得到一個值,我們設定一個閥值,相當於收聽比例大的話,肯定會喜歡,我們閥值定個0.7和0.2,大於0.7我們就認為是喜歡給個1,小於0.2就認為不喜歡給個0,然後我們確定一個字典item的name和id的對應字典,這個字典在做引擎的時候需要用到,進行召回後,展現的是itemName而不是id,讓使用者看明白,這裡順便提前生成,然後我們輸出一個列表,包含標籤,使用者畫像資訊,物品資訊,我們做個性化推進肯定需要使用者畫像資訊,同時物品資訊,除此之外,我們還要輸出一個使用者去重後的畫像資訊,和itemName,接下來我們抽特徵的時候只需要在兩個裡面抽取特徵就可以了
  2. 抽取使用者畫像資訊,將使用者的資訊轉換成上面我們需要的那種格式做模型訓練

    我們如何來抽取特徵呢,首先對性別進行抽取,將使用者是女的 預設給個index 0,若是男的給個1,然後將這個拼接起來,然後開始對年齡抽取,將年齡以元資料分開,0-18給個2,19-25給個3以此類推,我們就的到年齡的使用者特徵表達,最後我們給每個特徵一個權重值1,最後將所有的權重值放到字典裡面,方便全部資料替換時候查詢
  3. 抽取物品特徵,和使用者一樣,我們將第一步去重後的itemName取出來,進行分詞,然後把分好的詞放到set裡面去重,這麼做是為了我們將token轉換成id,把id當作物品的特徵來進行模型計算,這裡需要注意,我們前面使用者畫像的特徵是從1開始的,這裡我們做個偏移量+10,防止與使用者畫像衝突了

  4. 將我們第一步出來的第一個列表,也就是label,使用者資訊,物品資訊列表中的資料進行替換,字典就查詢第2步和第3步的字典,取出userid,物品name,然後輸出到檔案中,這樣我們就得到最終的訓練測試資料

  5. 為了能夠實時使用使用者特徵,實時使用物品特徵,將第二部和第三部的字典輸出到檔案中,因為我們在線上,假如使用者點了某首音樂,我們要進行精排,只知道使用者id和itemid,肯定是不能排序的,所以必須要知道itemid和userid 對應的物品特徵和使用者特徵,然後進行計算打分,精排

  6. 最後輸出一個item對應的字典,供返回前臺頁面使用

那麼我們看下程式碼,結合上面的思路很容易就理解程式碼了

#!usr/bin/python
# -*- coding: UTF-8 -*-

'''
    思路:這裡我們經過cb,cf演算法,將資料已經放到記憶體庫,召回部分已經完成,接下來我們需要做排序模型,為邏輯迴歸準備樣本資料
         1、處理第一次將使用者元資料,物品元資料,使用者行為資料一起歸併的資料,也就是merge_base.data,我們在這裡需要得到使用者畫像
            資料,使用者資訊資料,標籤資料
         2、收取樣本,標籤,使用者畫像資訊,物品資訊
         3、抽取使用者畫像資訊,對性別和年齡生成樣本資料
         4、抽取item特徵資訊,分詞獲得token,score,做樣本資料
         5、拼接樣本,生成最終的樣本資訊,作為模型進行訓練
'''

import sys
sys.path.append('../')
reload(sys)
sys.setdefaultencoding('utf-8')

import jieba
import jieba.analyse
import jieba.posseg

input_file = '../resultData/merge_base.data'
#最終生成的樣本資料
output_file = '../resultData/samples.data'

#我們這裡需要再生成兩個檔案,一個是使用者樣本和item樣本,因為要對實時推薦的化,必須使用這兩個樣本
output_user_feature_file = '../resultData/user_feature.data'
output_item_feature_file = '../resultData/item_feature.data'
#這裡生成個類似name和id對應的字典資訊
output_itemid_to_name_file = '../resultData/name_id.dict'

#定義函式,來獲取各類資料
def get_base_samples(file):
    #放待處理樣本資料
    ret_samples_list = []
    #放user使用者資料
    user_info_set = set()
    #放物品資料
    item_info_set = set()
    item_name2id = {}
    item_id2name = {}


    with open(file,'r') as fd:
        for line in fd:
            ss = line.strip().split('\001')
            if len(ss) != 13:
                continue

            userid = ss[0].strip()
            itemid = ss[1].strip()
            #這兩個時間為了計算label而使用
            watchTime = ss[2].strip()
            totalTime = ss[10].strip()

            #使用者資料
            gender = ss[4].strip()
            age = ss[5].strip()
            user_feature = '\001'.join([userid,gender,age])

            #物品資料
            name = ss[8].strip()
            item_feature = '\001'.join([itemid,name])

            #計算標籤
            label = float(watchTime)/float(totalTime)
            final_label = 0

            if label >= 0.7:
                final_label = '1'
            elif label <= 0.2:
                final_label = '0'
            else:
                continue

            #接下來裝在資料,並返回結果,首先我們裝在itemid2name和itemname2id
            item_name2id[name] = itemid

            item_id2name[itemid] = name

            #裝在待處理的標籤資料
            ret_samples_list.append([final_label,user_feature,item_feature])

            user_info_set.add(user_feature)
            item_info_set.add(name)

        return ret_samples_list, user_info_set, item_info_set, item_name2id, item_id2name

#step 1 程式的入口,開始呼叫函式,開始處理檔案,得到相應的資料
base_sample_list, user_info_set, item_info_set, item_name2id, item_id2name = get_base_samples(input_file)

#step 2 抽取使用者畫像資訊,使用者標籤轉換,將年齡和age進行轉換,用於樣本使用
user_fea_dict = {}
for ss in user_info_set:
    userid, gender, age = ss.strip().split('\001')

    #設定標籤idx,將男(1)和女(0)用數劇的形式表示,權重都設定為1
    idx = 0

    if gender == '男':
        idx = 1
    #將標籤和權重拼接起來
    gender_fea = ':'.join([str(idx), '1'])

    #性別設定完成,我們接下來設定年齡,將年齡進行劃分,0-18,19-25,26-35,36-45
    if age == '0-18':
        idx = 2
    elif age == '19-25':
        idx = 3
    elif age == '26-35':
        idx = 4
    elif age == '36-45':
        idx = 5
    else:
        idx = 6

    age_fea = ':'.join([str(idx),'1'])

    #最後將性別特徵和年齡特徵放入list中
    user_fea_dict[userid] = ' '.join([gender_fea, age_fea])

#step 3 抽取物品特徵,這裡我們要用到分詞,將name進行分詞,並且把分詞後的token轉換成id,這裡就需要我們來做生成tokenid詞表
token_set = set()
item_fs_dict = {}
for name in item_info_set:
    token_score_list = []
    for x,w in jieba.analyse.extract_tags(name,withWeight=True):
        token_score_list.append((x,w))
        token_set.add(x)
    item_fs_dict[name] = token_score_list

#進行token2id的轉換
token_id_dict = {}
#這裡我們要用到剛剛利用set去重過的token列表,生成tokenid的字典表
for s in enumerate(list(token_set)):
    token_id_dict[s[1]] = s[0]

#接下來,我們需要把第三步生成的item_fs_dict中name對應的token全部替換成id,然後當作字典,為下面的全量替換做準備
item_fea_dict = {}
user_feature_offset = 10
for name ,fea in item_fs_dict.items():
    token_score_list = []
    for (token,score) in fea:
        if token not in token_id_dict:
            continue
        token_id = token_id_dict[token] + user_feature_offset
        token_score_list.append(':'.join([str(token_id),str(score)]))

    #接下來輸出到字典中
    item_fea_dict[name] = ' '.join(token_score_list)

#step 4 將第一步輸出的樣本資料整體替換並且替換user_feature和item_feature,並輸出到檔案中
ofile = open(output_file,'w')
for (label,userfea,itemfea) in base_sample_list:
    userid = userfea.strip().split('\001')[0]
    item_name = itemfea.strip().split('\001')[1]

    if userid not in user_fea_dict:
        continue
    if item_name not in item_fea_dict:
        continue

    ofile.write(' '.join([label,user_fea_dict[userid],item_fea_dict[item_name]]))
    ofile.write('\n')

ofile.close()

#step 5 為了能夠實時使用userfeatre,我們需要輸出一下
out_put_file = open(output_user_feature_file,'w')
for userid,fea in user_fea_dict.items():
    out_put_file.write('\t'.join([userid,fea]))
    out_put_file.write('\n')
out_put_file.close()

#step 6 輸出item_feature
out_file = open(output_item_feature_file,'w')
for name,fea in item_fea_dict.items():
    if name not in item_name2id:
        continue
    itemid = item_name2id[name]
    out_file.write('\t'.join([itemid,fea]))
    out_file.write('\n')

#step 7 輸出id2name的對應的字典
o_file = open(output_itemid_to_name_file,'w')
for id,name in item_id2name.items():
    o_file.write('\t'.join([id,name]))
    o_file.write('\n')
o_file.close()

模型準備即通過LR演算法訓練模型資料得到W,B

我們已經在上面把訓練模型的資料準備好了,我們來看下上一步得到資料:

好,接下來我們來看下訓練LR的程式碼,怎麼能夠得到W,B

首先main方法是主體,先進入main方法,然後我們能看到呼叫了一個load_data()函式,返回了四個值,訓練x,測試x,訓練y,測試y,訓練的x和y是用來做訓練用的,測試的用來做測試用的,然後sklearn裡面已經提供了一個LogisticsRegression方法,這裡面有個引數,就是正則化的L1,或是L2,這裡沒有講正則化,大家可以去看看其他部落格,然後用這個模型擬合訓練集x,y得到model,通過model中的兩個引數得到一個w,一個b,得到w和b,然後我們做一下測試,在測試集上,評估一下這個模型。

接下來我們說下load_data()函式,這個就是整個邏輯迴歸重點,其實希望大家把邏輯迴歸的原始碼也多多看看,明白怎麼計算就好,這個計算就是我們上面說的sklearn的方法,我們還是來回過來看看怎麼載入資料,能夠進行訓練,load_data主要幹這麼幾件事,將label,各類特徵轉換成矩陣的形式,第一我們要申請幾個list,分別用來放label,行號,列特徵,各個列特徵對應的分數,這麼幾個list,這主要是為了提前申請好空間,每一行代表個記錄,每一列代表一個特徵,我們使用csr_matrix將這幾個list轉換何曾表,但是我們不能直接使用list,我們要使用np轉換成np.array,再使用csr_matrix,形成矩陣,最後通過train_test_split得到訓練x,測試x,訓練y,測試y

# -*- coding: UTF-8 -*-
'''
    思路:這裡我們要用到我們的資料,就需要我們自己寫load_data的部分,
         首先定義main,方法入口,然後進行load_data的編寫
         其次呼叫該方法的到x訓練x測試,y訓練,y測試,使用L1正則化或是L2正則化使得到結果更加可靠
         輸出wegiht,和b偏置
'''
import sys
import numpy as np
from scipy.sparse import csr_matrix

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

input_file = sys.argv[1]

def load_data():
    #由於在計算過程用到矩陣計算,這裡我們需要根據我們的資料設定行,列,和訓練的資料準備
    #標籤列表
    target_list = []
    #行數列表
    fea_row_list = []
    #特徵列表
    fea_col_list = []
    #分數列表
    data_list = []

    #設定行號計數器
    row_idx = 0
    max_col = 0

    with open(input_file,'r') as fd:
        for line in fd:
            ss = line.strip().split(' ')
            #標籤
            label = ss[0]
            #特徵
            fea = ss[1:]

            #將標籤放入標籤列表中
            target_list.append(int(label))

            #開始迴圈處理特徵:
            for fea_score in fea:
                sss = fea_score.strip().split(':')
                if len(sss) != 2:
                    continue
                feature, score = sss
                #增加行
                fea_row_list.append(row_idx)
                #增加列
                fea_col_list.append(int(feature))
                #填充分數
                data_list.append(float(score))
                if int(feature) > max_col:
                    max_col = int(feature)

            row_idx += 1

    row = np.array(fea_row_list)
    col = np.array(fea_col_list)
    data = np.array(data_list)

    fea_datasets = csr_matrix((data, (row, col)), shape=(row_idx, max_col + 1))

    x_train, x_test, y_train, y_test = train_test_split(fea_datasets, s, test_size=0.2, random_state=0)

    return x_train, x_test, y_train, y_test

def main():
    x_train,x_test,y_train,y_test = load_data()
    #用L2正則話防止過擬合
    model = LogisticRegression(penalty='l2')
    #模型訓練
    model.fit(x_train,y_train)

    ff_w = open('model.w', 'w')
    ff_b = open('model.b', 'w')

    #寫入訓練出來的W
    for w_list in model.coef_:
        for w in w_list:
            print >> ff_w, "w: ", w
    # 寫入訓練出來的B
    for b in model.intercept_:
        print >> ff_b, "b: ", b
    print "precision: ", model.score(x_test, y_test)
    print "MSE: ", np.mean((model.predict(x_test) - y_test) ** 2)

if __name__ == '__main__':
    main()

好了,所有的一切都準備好了,我們下來就進行推薦系統的實現

推薦系統實現

  • 解析請求

  • 載入模型,使用者特徵模型,物品特徵模型

  • 進行召回,檢索候選池,這裡召回我們同時使用CB,CF

  • 獲取使用者特徵

  • 獲取物品特徵(將召回的物品結合模型進行打分計算sigmoid)

  • 精排序

  • 過濾即得到TOPN

  • 封裝打包返回給頁面

推薦系統的實現主要就是我們前面說的這幾部分,思路很明確,需要大家細細看下程式碼。

#coding=utf-8
import web
import sys
import redis
import json
import math

sys.path.append("./")
import jieba
import jieba.posseg
import jieba.analyse

urls = (
    '/', 'index',
    '/test', 'test',
)

render = web.template.render('templates/')
web.template.Template.globals['render'] = render

config = web.storage(
            static = '/static',
            resource = '/resource'
            )
web.template.Template.globals['config'] = config

app = web.application(urls, globals())

# 載入user特徵
user_fea_dict = {}
with open('/home/python_test/pyCharm/rankModel/resultData/user_feature.data') as fd:
    for line in fd:
        userid, fea_list_str = line.strip().split('\t')
        user_fea_dict[userid] = fea_list_str

# 載入item特徵
item_fea_dict = {}
with open('/home/python_test/pyCharm/rankModel/resultData/item_feature.data') as fd:
    for line in fd:
        ss = line.strip().split('\t')
        if len(ss) != 2:
            continue
        itemid, fea_list_str = ss
        item_fea_dict[itemid] = fea_list_str

class test:
    def GET(self):
        ret = '111'

        return ret


class index:
    def GET(self):
        return render.index_badou(' ', ' ', ' ', '')


    def POST(self):
        param = web.input()
        print "==============="
        content = param['item_id']
        # step 1 解析請求,上面我們已經得到userid,itemid
        userid ,reqitemid = content.split(',')
        print userid,reqitemid
        r = redis.Redis(host='master', port=6379, db=0)

        #step2 載入模型
        model_w_path = '../model.w'
        model_b_path = '../model.b'

        model_w_list = []
        model_b = 0

        with open(model_w_path,'r') as fd:
            for line in fd:
                ss = line.strip().split(' ')
                if len(ss) !=3:
                    continue
                model_w_list.append(float(ss[2].strip()))

        with open(model_b_path,'r') as fd:
            for line in fd:
                ss = line.strip().split(' ')
                model_b = float(ss[2].strip())

        #step 3 進行召回階段,檢索候選池,這裡我們分兩次,cb,cf
        #將檢索回來的item全部放到recallitem列表裡面
        rec_item_mergeall = []
        # 3.1 cf檢索
        cf_recinfo = 'null'
        key = '_'.join(['CF', reqitemid])
        if r.exists(key):
            cf_recinfo = r.get(key)
        if len(cf_recinfo) > 6:
            for cfinfo in cf_recinfo.strip().split('_'):
                item,score = cfinfo.strip().split(':')
                rec_item_mergeall.append(item)
        #3.2 cb檢索
        cb_recinfo = 'null'
        key = '_'.join(['CB_',reqitemid])
        if r.exists(key):
            cb_recinfo = r.get(key)
        if len(cb_recinfo) > 6:
            for cbinfo in cb_recinfo.strip().split('_'):
                item,score = cbinfo.strip().split(':')
                rec_item_mergeall.append(item)
        #step 4 獲取使用者特徵,將獲取的使用者特徵處理後放到字典裡面,方便後續計算內積
        user_fea = ''
        if userid in user_fea_dict:
            user_fea = user_fea_dict[userid]

        u_fea_dict = {}
        for ss in user_fea.strip().split(' '):
            sss = ss.strip().split(':')
            if len(sss) != 2:
                continue
            idx = int(sss[0].strip())
            score = float(sss[1].strip())
            u_fea_dict[idx] = score

        # step 5 獲取物品的特徵 ,迴圈遍歷剛剛得到itemid,判斷item是否在item特徵中,若在開始進行處理
        rec_list = []
        for itemid in rec_item_mergeall:
            if itemid in item_fea_dict:
                tem_fea = item_fea_dict[itemid]

                i_fea_dict = {}
                #迴圈遍歷item特徵
                for fea_idx in tem_fea.strip().split(' '):
                    ss = fea_idx.strip().split(':')
                    if len(ss) != 2:
                        continue
                    idx = int(ss[0].strip())
                    score = float(ss[1].strip())
                    i_fea_dict[idx] = score

                #我們得到召回item對應的特徵和使用者的特徵,我們接下來根據模型求出來的w,b,進行打分
                wx_score = 0
                #這裡我們求個內積,wx,然後做sigmoid,先將兩個字典拼接起來,然後計算分數
                for index,score in dict(u_fea_dict.items()+i_fea_dict.items()).items():
                    wx_score +=  (score * model_w_list[index])

                #計算sigmoid
                final_rec_score = 1 / (1 + math.exp(-(wx_score + model_b)))
                #將itemID和分數放入列表中,方便後續排序
                rec_list.append((itemid, final_rec_score))

        #step 6 精排
        rec_sort_list = sorted(rec_list, key=lambda x: x[1], reverse=True)

        #step 7 過濾(filter)
        rec_fitler_list = rec_sort_list[:5]
        print rec_fitler_list
        #step 8 進行將itemid轉換成name
        itemid2name = {}
        with open('/home/python_test/pyCharm/rankModel/resultData/name_id.dict','r') as fd:
            for line in fd:
                itemid,name = line.strip().split('\t')
                itemid2name[itemid] = name
        #進行替換
        ret_list = []
        for tup in rec_fitler_list:
            req_item_name = itemid2name[reqitemid]
            item_name = itemid2name[tup[0]]
            item_rank_score = str(tup[1])
            ret_list.append('   ->   '.join([item_name, item_rank_score]))

        # ret = '   '.join(rec_sort_list)
        ret = ret_list

        if len(ret) <= 0:
            return json.dumps({'block_one':' ', 'block_two':' '},ensure_ascii=True, encoding='UTF-8')
        else:
            return json.dumps({'block_one':req_item_name.decode("utf8","ignore"), \
                    'block_two':'<br>'.join(ret).decode("utf8","ignore")},ensure_ascii=True, encoding='UTF-8')

if __name__ == "__main__":
    app.run()

我們來看下頁面實現的推薦結果:

至此,單機版的推薦引擎部分就全部實現,中間還會有很多優化的地方,在現實生活中面對幾億的使用者資料不可能使用的是單機模式,至於優化,後續持續更新,程式碼的整體部分可以到資源庫下載,或是直接留言

相關推薦

基於CBCFLR演算法推薦系統實現

在開篇之前,我們先來說下上次CB,CF演算法實現粗的推薦系統,我們知道,CB,CF演算法只是在召回階段使用,這種推薦出來的item畢竟是粗排的,這篇文章正是對上圖畫上一個圓滿的句號,將CB,CF召回回來的item進行精排,然後選擇分數最高,給使用者推薦出來,那麼,問題來,

基於模型融合的推薦系統實現(3):模型融合

基本思路很簡單,最小二乘法就好了: 我們假設兩個演算法得到的結果權重分別是a,b利用最小二乘法和我們分出來的第二部分資料就可以獲取a,b使得誤差最小。其實最小二乘法就是求一個廣義的逆即可。最後的RMSE比起單一的模型有所提高,變成了(0.86~~~~) import numpy

基於模型融合的推薦系統實現(2):迭代式SVD分解

SVD演算法的原理網路上也有很多,不再細說了,關鍵是我們得到的資料是不完整的資料,所以要算SVD就必須做一次矩陣補全。補全的方式有很多,這裡推薦使用均值補全的方法(用每一行均值和每一列均值的平均來代替空白處),然後可以計算SVD,作PCA分析,然後就可以得到預測結果。 但是我們這裡有

基於模型融合的推薦系統實現(1):基於SGD的PMF

(1)PMF演算法 PMF的基本的思路,就是定義兩個基本的引數W,U,然後對於任意一個組合(u,m),利用 Wi∗Uj W^i*U^j,來獲取預測值。這些基本的演算法思路網上很多,就不細說了。簡單說一下程式 [0]:一開始我們要將訓練資料劃分為3部分,第一部

基於神經網絡的embeddding來構建推薦系統

驗證 撰寫 ive single pap dex lai 最終 you   在之前的博客中,我主要介紹了embedding用於處理類別特征的應用,其實,在學術界和工業界上,embedding的應用還有很多,比如在推薦系統中的應用。本篇博客就介紹了如何利用embedding來

JAVA基於權重的抽獎 權重隨機演算法的java實現

  https://www.bbsmax.com/A/LPdo4pk2z3/     https://blog.csdn.net/huyuyang6688/article/details/50480687  

基於C#的多目標進化演算法平臺MOEAPlat實現

多目標進化算法系列 基於C#實現,展示的結果包括近似Pareto前沿,具體的Pareto前沿資料,IGD值的變化曲線等,當然這些都是高度可定製化的。 目前只提供了基於遺傳演算法的多目標進化演算法,交叉方式已提供SBX和DE兩種,變異方式為多項式突變。同時,

基於Retinex的影象去霧演算法(MATLAB實現

在【影象處理中的數學原理】專欄(該專欄中的文章已經結集出版,書名為《影象處理中的數學修煉》)之前的一些文章中,我們已經討論了諸多非常有用的影象增強演算法,例如直方圖均衡演算法以及更加強大的CLAHE。通常影象增強演算法或多或少都有一定的去霧效果,只是這個效果有強

基於Spark平臺的電影推薦系統實現

博主一年前寫過一個這樣的文章,電影推薦也是博主一年前就學習過的,溫故而知新,重新拿出來好好重新研究一番。 這時以前的文章連結:如何使用Spark ALS實現協同過濾http://www.aboutyun.com/forum.PHP?mod=viewthread&

基於使用者的協同過濾來構建推薦系統

1.概述 之前介紹瞭如何構建一個推薦系統,今天給大家介紹如何基於使用者的協同過濾來構建推薦的實戰篇。 2.內容 協同過濾技術在推薦系統中應用的比較廣泛,它是一個快速發展的研究領域。它比較常用的兩種方法是基於記憶體(Memory-Based)和基於模型(Model-Based)。 基於記憶體:主要通過計算近似

文獻綜述四:基於 UML 技術的客戶關系管理系統實現

流程 企業客戶 經理 產品 流程設計 server 處理請求 對數 工作計劃 一、基本信息   標題:基於 UML 技術的客戶關系管理系統實現   時間:2015   出版源:電子設計工程   文件分類:uml技術的研究 二、研究背景    使用UML 建模技術和 B/S

SpringMVC學習系列(12) 完結篇 之 基於Hibernate+Spring+Spring MVC+Bootstrap的管理系統實現

到這裡已經寫到第12篇了,前11篇基本上把Spring MVC主要的內容都講了,現在就直接上一個專案吧,希望能對有需要的朋友有一些幫助。 一、首先看一下專案結構: InfrastructureProjects:是抽取出的基礎專案,主要封裝了一些通用的操作。 SpringMVC3Demo:就是管理系統

基於Hibernate+Spring+Spring MVC+Bootstrap的管理系統實現

由於原文博主:http://www.cnblogs.com/liukemng/p/3754269.html,博主沒有將搭建好的原始碼給我們,好多人不知道該怎麼搭建,特此重新整理了這篇部落格的原始碼,已經能夠正常跑起來,需要的可以去下載,由於該專案所用的jar包比較多,所以分

推薦演算法CBCF演算法

初學推薦演算法,以下是我的一些見解,如有不對請留言,後續還會更新,這個CSDN太坑了,寫了快一個下午的文章,發表了,結果沒儲存,神坑。。。。。 首先我們來明確一下,推薦系統主要是幹什麼用的:毋庸置疑,在這麼一個資訊爆炸的時代,許多資訊過載或是過剩,那麼我們不可能把全部給看一

【備忘】基於HadoopSpark大資料技術的推薦系統演算法實戰教程

課程簡介:                 2017年最新大資料推薦系統演算法實戰視訊教程,共18.1G容量。附講義、程式碼與練習資料,配套齊全,高清不加密。                 課程介紹:                  網際網路行業是大資料應用最前沿的陣地,目前主流的大資料技術,包括 ha

SSE影象演算法優化系列二十三: 基於value-and-criterion structure 系列濾波器(如KuwaharaMLVMCV濾波器)的優化。 SSE影象演算法優化系列十四:區域性均方差及區域性平方差演算法的優化 SSE影象演算法優化系列七:基於SSE實現的極速的矩形核腐蝕和膨脹(

       基於value-and-criterion structure方式的實現的濾波器在原理上其實比較簡單,感覺下面論文中得一段話已經描述的比較清晰了,直接貼英文吧,感覺翻譯過來反而失去了原始的韻味了。        T

基於PyTorch重寫sklearn《現代大資料演算法

HyperLearn是一個基於PyTorch重寫的機器學習工具包Scikit Learn,它的一些模組速度更快、需要記憶體更少,效率提高了一倍。 專為大資料而設計,HyperLearn可以使用50%以下的記憶體,並在某些模組上執行速度提高50%以上。將支援GPU,並且所有模組都是並行化的。 專案作者Dan

keras探索:nlp-基於內容的推薦系統(單標籤不涉及使用者畫像)

open resource :deep learning with python (keras) open code: https://github.com/fchollet/deep-learning-with-python-notebooks/blob/master/3.6-clas

SSE影象演算法優化系列二十三: 基於value-and-criterion structure 系列濾波器(如KuwaharaMLVMCV濾波器)的優化。

int IM_KuwaharaFilter(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride, int Radius) { int Channel = Stride / Width; if

基於求導的快速exp()演算法exp()快速計算exp導數演算法exp函式C語言實現

基於求導的快速exp()演算法 如果需要得到exp(x)的連續數列,那麼常規方法需要一個一個數的運算,運算量會非常大。此時可以使用以下方法,得到連續的exp(x)數列。 我們知道的導數等於本身。設