LFM算法核心思想是通过隐含特征(latent factor)联系用户兴趣和物品,找出潜在的主题和分类。LFM(latent factor model)通过如下公式计算用户u对物品i的兴趣:

\[Preference(u,i) = r_{ui} = {p_u}^T q_i = \sum_{f=1}^F p_{u,k} q_{i,k}
\]

定义\(P\)矩阵是user-class矩阵,矩阵值\(P_{ij}\)表示的是user \(i\)对class \(j\)的兴趣度;\(Q\)矩阵式class-item矩阵,矩阵值\(Q_{ij}\)表示的是item \(j\)在class \(i\)中的权重,权重越高越能作为该类的代表。那么,用户\(U\)对物品\(I\)的兴趣度为:

\[R_{UI} = P_U Q_I = \sum_{k=1}^K P_{U,K} Q_{K,I}
\]

整个程序框架分为了train和test两个部分。

在训练中,初始化需要所有的user_id与item_id,然后指定初始化参数:主题数量(class_count),迭代次数(iter_count),学习率(lr),正则项参数(lambd)。

之后调用了两个方法:

  • _init_data

    这里假设用户观看的电影评分3分以上为正样本,所以需要对其他电影采样作为负样本。self.items_dict存储了对于每一个用户而言的正样本和负样本字典。若为正,则value为1,否则,value为0。

实际上,对于评分数据而言,可以直接采用rating作为label,这里区分正负样本是为了更具普遍性。

  • _init_model

    初始化向量\(p\)和\(q\)后,使用LFM算法迭代计算。具体的:

\[predict = sigmoid(\sum P_{U,K} Q_{K,I})
\]

令损失函数为:

\[J = 1/2 * (y - predict)^2 + \lambda ||p_u||^2 + \lambda ||q_i||^2
\]

根据梯度下降,可得:

\[p_u = p_u - lr * [-(y - predict) * q_i + 2 \lambda ||p_u||] \\
q_i = q_i - lr * [-(y - predict) * p_u + 2 \lambda ||q_i||]
\]

全部代码如下所示:

#-*-coding:utf-8-*-
"""
author:jamest
date:20190306
LFM function
"""
import math
import pandas as pd
import random
import numpy as np
import pickle class LFM:
def __init__(self, user_ids, item_ids):
self.class_count = 5
self.iter_count = 5
self.lr = 0.02
self.lambd = 0.01
self._init_data(user_ids, item_ids)
self._init_model() # 下采样
def _randomSelectNegativeSample(self,user_id,user_ids,item_ids):
items = [x[1] for x in zip(user_ids,item_ids) if x[0]==user_id]
res = dict()
for i in items:
res[i] = 1
n = 0
for i in range(len(items) * 3):
item = item_ids[random.randint(0, len(item_ids) - 1)]
if item in res:
continue
res[item] = 0
n += 1
if n > len(items):
break
return res def _get_dic(self,user_ids,item_ids):
items_dict = {}
for user_id in self.user_ids_set:
items_dict[user_id] = self._randomSelectNegativeSample(user_id,user_ids,item_ids)
return items_dict def _init_data(self,user_ids,item_ids):
self.user_ids_set = set(user_ids)
self.item_ids_set = set(item_ids)
self.items_dict = self._get_dic(user_ids,item_ids) def _init_model(self):
"""
Get corpus and initialize model params.
"""
array_p = np.random.randn(len(self.user_ids_set), self.class_count)
array_q = np.random.randn(len(self.item_ids_set), self.class_count)
self.p = pd.DataFrame(array_p, columns=range(0, self.class_count), index=list(self.user_ids_set))
self.q = pd.DataFrame(array_q, columns=range(0, self.class_count), index=list(self.item_ids_set)) def _predict(self, user_id, item_id):
"""
Calculate interest between user_id and item_id.
p is the look-up-table for user's interest of each class.
q means the probability of each item being classified as each class.
"""
p = np.mat(self.p.ix[user_id].values)
q = np.mat(self.q.ix[item_id].values).T
r = (p * q).sum()
# logit = 1.0 / (1 + math.exp(-r))
logit = self._sigmoid(r)
return logit def _sigmoid(self,z):
return 1./(1 + np.exp(-z)) def _loss(self, user_id, item_id, y, step):
"""
Loss Function define as MSE, the code write here not that formula you think.
"""
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 def _optimize(self, user_id, item_id, e):
"""
Use SGD as optimizer, with L2 p, q square regular.
e.g: E = 1/2 * (y - predict)^2, predict = matrix_p * matrix_q
derivation(E, p) = -matrix_q*(y - predict), derivation(E, q) = -matrix_p*(y - predict),
derivation(l2_square,p) = lam * p, derivation(l2_square, q) = lam * q
delta_p = lr * (derivation(E, p) + derivation(l2_square,p))
delta_q = lr * (derivation(E, q) + derivation(l2_square, q))
"""
gradient_p = -e * self.q.ix[item_id].values
l2_p = 2 * self.lambd * self.p.ix[user_id].values
delta_p = self.lr * (gradient_p + l2_p) gradient_q = -e * self.p.ix[user_id].values
l2_q = 2 * self.lambd * self.q.ix[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 def train(self):
for step in range(self.iter_count):
for user_id, item_dict in self.items_dict.items():
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
self.save() def predict(self, user_id, items,top_n=10):
"""
Calculate all item user have not meet before and return the top n interest items.
"""
self.load()
user_item_ids = set(items)
other_item_ids = self.item_ids_set ^ 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):
"""
Save model params.
"""
f = open('../data/lfm.model', 'wb')
pickle.dump((self.p, self.q), f)
f.close() def load(self):
"""
Load model params.
"""
f = open('../data/lfm.model', 'rb')
self.p, self.q = pickle.load(f)
f.close() if __name__ == '__main__':
moviesPath = '../data/ml-1m/movies.dat'
ratingsPath = '../data/ml-1m/ratings.dat'
usersPath = '../data/ml-1m/users.dat' ratingsDF = pd.read_csv(ratingsPath, index_col=None, sep='::', header=None,names=['user_id', 'movie_id', 'rating', 'timestamp']) ratingsDF = ratingsDF[ratingsDF['rating']>3]
X=ratingsDF['user_id'][:1000]
Y=ratingsDF['movie_id'][:1000] # LFM(X,Y).train()
items = ratingsDF[ratingsDF['user_id']==1]['movie_id'].values
rank = LFM(X,Y).predict(1,items)
print('LFM result',rank)

参考:

推荐系统概述(一)

Github

个性化召回算法实践(二)——LFM算法的更多相关文章

  1. 个性化排序算法实践(二)——FFM算法

    场感知分解机(Field-aware Factorization Machine ,简称FFM)在FM的基础上进一步改进,在模型中引入类别的概念,即field.将同一个field的特征单独进行one- ...

  2. 个性化排序算法实践(五)——DCN算法

    wide&deep在个性化排序算法中是影响力比较大的工作了.wide部分是手动特征交叉(负责memorization),deep部分利用mlp来实现高阶特征交叉(负责generalizatio ...

  3. 个性化排序算法实践(三)——deepFM算法

    FM通过对于每一位特征的隐变量内积来提取特征组合,最后的结果也不错,虽然理论上FM可以对高阶特征组合进行建模,但实际上因为计算复杂度原因,一般都只用到了二阶特征组合.对于高阶特征组合来说,我们很自然想 ...

  4. 最短路径算法之二——Dijkstra算法

    Dijkstra算法 Dijkstra算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止. 注意该算法要求图中不存在负权边. 首先我们来定义一个二维数组Edge[MAXN][MAXN]来存储 ...

  5. 个性化召回算法实践(一)——CF算法

    协同过滤推荐(Collaborative Filtering Recommendation)主要包括基于用户的协同过滤算法与基于物品的协同过滤算法. 下面,以movielens数据集为例,分别实践这两 ...

  6. 个性化召回算法实践(三)——PersonalRank算法

    将用户行为表示为二分图模型.假设给用户\(u\)进行个性化推荐,要计算所有节点相对于用户\(u\)的相关度,则PersonalRank从用户\(u\)对应的节点开始游走,每到一个节点都以\(1-d\) ...

  7. 个性化召回算法实践(四)——ContentBased算法

    ContentBased算法的思想非常简单:根据用户过去喜欢的物品(本文统称为 item),为用户推荐和他过去喜欢的物品相似的物品.而关键就在于这里的物品相似性的度量,这才是算法运用过程中的核心. C ...

  8. 个性化排序算法实践(一)——FM算法

    因子分解机(Factorization Machine,简称FM)算法用于解决大规模稀疏数据下的特征组合问题.FM可以看做带特征交叉的LR. 理论部分可参考FM系列,通过将FM的二次项化简,其复杂度可 ...

  9. [迷宫中的算法实践]迷宫生成算法——递归分割算法

    Recursive division method        Mazes can be created with recursive division, an algorithm which wo ...

随机推荐

  1. C++ 顺序容器(vector,list、deque,stack,queue)

    顺序容器的种类有:vector,list.deque 顺序容器适配器: stack     //先进后出   栈 queue   //先进先出   队列 priority_queue   //也优先管 ...

  2. 捕捉AVPlayerViewController 系统原生工具栏的出现、隐藏事件

    需求前提 1. app内轻量级的视频播放功能,故不希望引入“过度开发.过度封装”的第三方控件组,使用原生的AVPlayerViewController 2. 工具栏有新增控件需求,如下载按钮 等 3. ...

  3. jvm误区--动态对象年龄判定

    原文链接:https://blog.csdn.net/u014493323/article/details/82921740 虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThres ...

  4. Deepin 15.11 install nvidia dirver[mei you an zhuang shu ru fa]

    1.firstly, exec: sudo vim /etc/modprobe.d/blacklist-nouveau.conf[create], and input [blacklist nouve ...

  5. 039 Android ShareSDK实现分享功能

    1.参考文献: https://blog.csdn.net/augfun/article/details/86551294 http://wiki.mob.com/sdk-share-android- ...

  6. [转帖]亚马逊彻底去掉 Oracle 数据库:迁移完成

    亚马逊彻底去掉 Oracle 数据库:迁移完成 https://mp.weixin.qq.com/s/KFonq8efDZ5K6x4YzIVbbg 云头条的信息挺不错的.. 2019 年 10 月 1 ...

  7. How to change default root@ email address linux / postfix / centos?

    Change root@hostname to different email address By default, any email sent by system is sent to root ...

  8. RocketMQ之一:RocketMQ整体介绍

    常用MQ介绍及对比--<MQ详解及四大MQ比较> RocketMQ环境搭建--<RocketMQ之三:RocketMQ集群环境搭建> RocketMQ物理部署结构 Rocket ...

  9. Mysql之rpm安装5.7版本遇见的问题

    前言:环境是centos7.5的系统,用rpm方式安装mysql5.7 1.由于是centos7.5 所以需要将默认的mariadb给卸载 rpm -qa | grep mariadb 查看下是否有m ...

  10. SQL——UPDATE(改)

    一.UPDATE语句基本用法 UPADTE语句用于修改表中已存在的记录. UPDATE语句语法: UPDATE 表名 SET 列名1 = 值1,列名2 = 值2... WHERE 条件语句; 演示st ...