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. MultiDesk远程桌面连接

    MultiDesk 是一个选项卡(TAB标签)方式的远程桌面连接 (Terminal Services Client),可以管理组远程桌面连接,更改连接端口. 功能特性 绿色软件,只有一个很小的可执行 ...

  2. Docker 安装运行MySQL

    1.镜像主页 https://hub.docker.com/_/mysql 2.拉取5.7版本 docker pull mysql:5.7 3.或者拉取最新8.x版本 docker pull mysq ...

  3. SpringBoot部署到Linux上AppserverApplication,访问不到控制层

    放在本地是好好的,可以请求到,放到Linux上去的话就直接404, 解决办法: SpringBoot有个加载类叫AppserverApplication.这个大家应该都知道,我们平常都是如下写: @S ...

  4. Spring Boot制作启动图案

    SpringBoot在启动时会有一个默认图案的,如果不喜欢可以自己制作一个. 在resources的目录下新建banner.txt文件. 制作图案地址:springboot启动图案定制 通过输入字符串 ...

  5. jdbc连接oracle的三种方法

    jdbc连接oracle的三种方法 使用service_name,配置方式:jdbc:oracle:thin:@//<host>:<port>/<service_name ...

  6. WUSTOJ 1315: 杜学霸和谭女神(Java)

    题目链接:

  7. TZOJ1299: 畅通工程

    #include<stdio.h> #include<math.h> #include<string.h> #include<algorithm> #i ...

  8. Scratch编程:游泳的火柴人(四)

    “ 上节课的内容全部掌握了吗?反复练习了没有,编程最好的学习方法就是练习.练习.再练习.一定要记得多动手.多动脑筋哦~~” 01 — 游戏介绍 这是一款简单的小游戏,实现了一个手绘的火柴人在水里游泳. ...

  9. 安装CentOS7服务器

    1. 基本安装 https://www.cnblogs.com/kreo/p/4396825.html 2.安装补充 防火墙 / FTP / Nginx https://www.cnblogs.com ...

  10. Atcoder&CodeForces杂题11.6

    Preface NOIP前突然不知道做什么,感觉思维有点江僵化,就在vjudge上随便组了6道ABC D+CF Div2 C/D做,发现比赛质量还不错,知识点涉及广,难度有梯度,码量稍小,思维较多. ...