隐语义模型--推荐算法实战

2021-04-29  本文已影响0人  郭彦超

隐语义模型(LFM)属于机器学习中的推荐算法,其中包含隐式因子部分,类似于深度学习网络的隐藏层(单层神经网络),所以很难解释隐含因子与模型最终输出结果的直接关系;但这并不妨碍我们使用该算法进行模型训练 ,因为目标最优才是我们衡量的效果。

LFM算法理解

计算兴趣度公式:

参数数说明:

举例:
假如有三类电影,分别是[武打片,喜剧片,爱情片],用户A非常喜欢武打片,这里用户A的P矩阵就是[1, 0, 0];有电影如下:

那么用户A对两部电影的偏好计算为:
R(A,战狼2) = P(u,k) * Q(k,i) = [1, 0, 0] * [1, 0, 0] = 1
R(A,你好李焕英) = P(u,k) * Q(k,i) = [1, 0, 0] * [, 1, 0] = 0

根据公式不难发现,其实LFM是在寻找合适的P&Q矩阵,需要注意的是这里的分类不需要用户提前指定,用户只需要设置好K的个数,分类特征是模型在训练的时候通过随机梯度分解得到的

LFM的缺点

代码实现


import random
import pickle
import pandas as pd
import numpy as np
from math import exp
import time

class LFM:

    def __init__(self):
        self.class_count = 25  #K的个数
        self.iter_count = 10  #迭代次数
        self.lr = 0.02  #学习率
        self.lam = 0.01  #惩罚因子
        self._init_model()

    """
        构建P&Q矩阵,初始化参数
            randn: 从标准正态分布中返回n个值
            pd.DataFrame: columns 指定列顺序,index 指定索引
    """
    def _init_model(self):
        file_path = '03O2O优惠券/trains.csv'

        self.uiscores = pd.read_csv(file_path)
        self.user_ids = set(self.uiscores['user_id'].values)  # 6040
        self.item_ids = set(self.uiscores['product_id'].values) # 3706
        #self.items_dict = pickle.load(open(pos_neg_path,'rb'))
        self.user_items = {}
        for _, row in self.uiscores.iterrows():
            self.user_items.setdefault(row['user_id'],{})
            self.user_items[row['user_id']][row['product_id']] = row['score']
        
        array_p = np.random.randn(len(self.user_ids), self.class_count)
        array_q = np.random.randn(len(self.item_ids), self.class_count)
        self.p = pd.DataFrame(array_p, columns=range(0, self.class_count), index=list(self.user_ids))
        self.q = pd.DataFrame(array_q, columns=range(0, self.class_count), index=list(self.item_ids))

    # 使用误差平方和(SSE)作为损失函数
    def _loss(self, user_id, item_id, y, step):
        e = y - self._predict(user_id, item_id)
       # print('Step: {}, user_id: {}, item_id: {}, y: {}, loss: {}'.format(step, user_id, item_id, y, e))
        return e

    """
        计算用户 user_id 对 item_id的兴趣度
            p: 用户对每个类别的兴趣度
            q: 物品属于每个类别的概率
    """
    def _predict(self, user_id, item_id):
#         p = np.mat(self.p.loc[user_id].values)
#         q = np.mat(self.q.loc[item_id].values).T
#         r = (p * q).sum()
#         # 借助sigmoid函数,转化为是否感兴趣
#         loss = 1.0 / (1 + exp(-r))
         
        user_vector = self.p.loc[user_id].values
        item_vector = self.q.loc[item_id].values
        loss = np.dot(user_vector,item_vector)/(np.linalg.norm(user_vector)*np.linalg.norm(item_vector))
        return loss

   
   
    # 训练模型,每次迭代都要降低学习率,刚开始由于离最优值相差较远,因此下降较快,当到达一定程度后,就要减小学习率
    def train(self):
        for step in range(0, self.iter_count):
            
            for user_id, item_dict in self.user_items.items():
                #print('Step: {}, user_id: {}'.format(step, user_id))
                item_ids = list(item_dict.keys())
                random.shuffle(item_ids)
                for item_id in item_ids:
                    e = self._loss(user_id, item_id, item_dict[item_id], step)
                    self._optimize(user_id, item_id, e)
            self.lr *= 0.9
        print('train end...')
        self.save()


    def _optimize(self, user_id, item_id, e):
        gradient_p = -e * self.q.loc[item_id].values
        l2_p = self.lam * self.p.loc[user_id].values
        delta_p = self.lr * (gradient_p + l2_p)

        gradient_q = -e * self.p.loc[user_id].values
        l2_q = self.lam * self.q.loc[item_id].values
        delta_q = self.lr * (gradient_q + l2_q)

        self.p.loc[user_id] -= delta_p
        self.q.loc[item_id] -= delta_q

    

    # 计算用户未评分过的电影,并取top N返回给用户
    def predict(self, user_id, top_n=10):
        self.load()
        user_item_ids = set(self.uiscores[self.uiscores['user_id'] == user_id]['product_id'])
        other_item_ids = self.item_ids ^ user_item_ids # 交集与并集的差集
        interest_list = [self._predict(user_id, item_id) for item_id in other_item_ids]
        candidates = sorted(zip(list(other_item_ids), interest_list), key=lambda x: x[1], reverse=True)
        return candidates[:top_n]

    # 保存模型
    def save(self):
        f = open('data/lfm.model', 'wb')
        pickle.dump((self.p, self.q), f)
        f.close()

    # 加载模型
    def load(self):
        f = open('data/lfm.model', 'rb')
        self.p, self.q = pickle.load(f)
        f.close()

    # 模型效果评估,从所有user中随机选取10个用户进行评估,评估方法为:绝对误差(AE)
    def evaluate(self):
        self.load()
        users=random.sample(self.user_ids,10)
        user_dict={}
        for user in users:
            user_item_ids = set(self.uiscores[self.uiscores['user_id'] == user]['product_id'])
            _sum=0.0
            for item_id in user_item_ids: 
                _r = self._predict(user, item_id) 
                r=self.uiscores[(self.uiscores['user_id'] == user)
                                & (self.uiscores['product_id']==item_id)]["score"].values[0]
                _sum+=abs(r-_r)
            user_dict[user] = _sum/len(user_item_ids)
            print("userID:{},AE:{}".format(user,user_dict[user]))

        return sum(user_dict.values())/len(user_dict.keys())

if __name__=="__main__":
    lfm=LFM()
    lfm.train()
    print(lfm.predict('0000c9e938104b5d9ef2e16bbfb7489b',10))
    print(lfm.evaluate())
上一篇下一篇

猜你喜欢

热点阅读