使用例子
使用经典的Movielens 100k dataset数据集作为推荐的例子. 在lightFM中, 可以直接调用函数获取整理成稀疏矩阵格式的数据:
from lightfm.datasets import fetch_movielens
data = fetch_movielens(min_rating=5.0)
但在这里, 我们将从原始的数据开始, 记录从原始数据到可供lightFM模型使用的数据, 即稀疏矩阵形式(coo, csr). 从而将数据处理的方法直接使用在各种推荐数据集上.
读取数据
下载好的数据集目录中包含很多文件, 其中的u.data
包含了所有数据. 文件中每一行为一个交互样本, 格式为user id | item id | rating | timestamp
, 列之间以tab
符分隔. 使用pandas
读取数据.
import codecs
import numpy as np
import pandas as pd
inter_file = "./data/ml-100k/u.data"
data = pd.read_csv(inter_file, delimiter="\t", header=None)
data.columns = ["user_id", "item_id", "rating", "timestamp"]
data.shape
(100000, 4)
共有100000条行为数据.
转为稀疏矩阵
lightFM模型中个各种方法只接受稀疏矩阵的形式, 因此需要把这种行形式的行为数据转换为(user, item)
这种稀疏矩阵的形式. 固然可以使用scipy
中的方法创建可以空的系数矩阵, 然后循环行为样本, 将对应位置填值, 但lightFM中提供了包装好的类来进行处理.
而且由于lightFM中使用user
和item
的特征, 使得整体数据情况比较复杂. 通过lightFM中的Dataset
类就能够很好地进行管理. lightfm.data.Dataset
能够做到:
user
和item
自身与整数索引之间的映射关系user
和item
特征的管理, 包括特征的顺序等生成
(user, item)
对的稀疏矩阵生成
user
或item
的特征数据结构
首先创建一个Dataset
.
from lightfm.data import Dataset
dataset = Dataset()
Dataset
的__init__
函数中有两个参数:
user_identity_features
item_identity_features
这两个参数默认值都为True
. 以第一个参数为例, 作用是: Create a unique feature for every user in addition to other features
. 即将每个用户本身作为一个特征, 追加到现有的user
特征中, 其实就是用户的One-Hot
特征.
如果没有任何user
和item
特征, 则这两个参数必须指定为True
. 每个user
, 每个item
都是一个独特的特征, 只会被该user
或item
使用到, 其他的不会使用. 在这种情况下, lightFM算法也就等价于普通的FM
算法.
在初始化完毕之后, 接下来需要将user列表和item列表为模型指定, 这一步需要调用Dataset
的fit
方法. 需要注意这里的fit
方法投入的不是行为数据, 只是所有user
和item
分别组成的列表.
fit
方法接收下面4个参数:
users: iterable of user ids,
user
列表items: iterable of item ids,
item
列表user_features: iterable of user features, optional,
user feature
的名称列表item_features: iterable of item features, optional,
item feature
的名称列表
这一步的作用是对每个user
和item
指定一个整数ID, 这个id就是传入列表中元素的顺序. ID从0开始, 在lightFM中使用的这种整数索引, 而不是原始的user
, item
名称.
然后就可以通过函数看到user
和item
的交互矩阵的大小了.
num_users, num_items = dataset.interactions_shape()
num_users, num_items
(943, 1682)
共943个user
, 1682个item
.
如果后续需要增加user, item或user feature和item feature的内容, 只需要调用dataset.fit_partial
, 参数形式与fit
方法相同, 因为fit
方法的内部也是调用了fit_partial
完成的.
接下来就是生成行为矩阵(interactions matrix)了. 这就需要用到原始的行形式的行为数据. 使用dataset.build_interactions
函数, 这个函数有以下的重要参数:
data: iterable of (user_id, item_id) or (user_id, item_id, weight).
即是一个迭代器, 每个元素符合上面两种形式, 分别是行为数据与打分数据.
该函数的返回值有两个:
(interactions, weights)
两个都是COO matrix的格式
分别表示是否有交互行为, 和对应的权值. 其中
weights
在评分系统中有用, 在表示是否有交互行为时, 由于传入的data中每个元素的格式为(user_id, item_id)
, 此时生成的weights
中的权值都为1, 因此与interactions
是完全相同的
这一步耗时比较长.
interactions, weights = dataset.build_interactions([tuple(data[["user_id", "item_id", "rating"]].iloc[i, :]) for i in range(data.shape[0])])
interactions, weights
(<943x1682 sparse matrix of type '<class 'numpy.int32'>'
with 100000 stored elements in COOrdinate format>,
<943x1682 sparse matrix of type '<class 'numpy.float32'>'
with 100000 stored elements in COOrdinate format>)
interactions.toarray(), weights.toarray()
(array([[1., 1., 1., ..., 0., 0., 0.],
[1., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[1., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 1., 0., ..., 0., 0., 0.]], dtype=float32),
array([[5., 3., 4., ..., 0., 0., 0.],
[4., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[5., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 5., 0., ..., 0., 0., 0.]], dtype=float32))
拟合模型
在得到了稀疏矩阵形式的交互数据之后, 就可以创建模型, 并进行拟合操作了.
from lightfm import LightFM
LightFM是一种hybrid latent representation recommender model
. 在初始化时, 需要注意一下的参数:
no_components: int, optional
隐向量的长度, 默认为10. the dimensionality of the feature latent embeddings.
learning_schedule: string, optional
学习率的变化策略, one of ('adagrad', 'adadelta'), 默认为adagrad
loss: string, optional
训练时使用的损失函数, 这个参数非常重要, 不同的场景需要指定不同的损失函数
one of ('logistic', 'bpr', 'warp', 'warp-kos')
logistic: useful when both positive (1) and negative (-1) interactions are present
bpr: Bayesian Personalised Ranking. 目标是最大化正样本与随机一个负样本之间差值. 在以下的场景中使用:
only positive interactions are present
需要最优的ROC, AUC
warp: Weighted Approximate-Rank Pairwise. 在以下的场景中使用:
only positive interactions are present
optimising the top of the recommendation list (precision@k) is desired
k-os warp: k-th order statistic loss. warp的一个变种
learning_rate: 初始学习率
item_alpha: L2 penalty on item features, 默认为0.0, 即不使用正则化. 开启正则化会降低训练的速度.
使用时需检查最后输出的embedding weights是否都近似于0, 如果是说明这个值设定的太大了
user_alpha: L2 penalty on user features, 默认为0.0, 关闭
max_sampled: maximum number of negative samples used during WARP fitting.
random_state
然后是调用训练方法fit
需要注意的参数:
fit(interactions, user_features=None, item_features=None, sample_weight=None, epochs=1, num_threads=1, verbose=False):
interactions: np.float32 coo_matrix of shape [n_users, n_items]
交互稀疏矩阵. 如果只有行为, 矩阵内的非0处值都为1; 如果是评分系统, 非0处的值为任意数值.
user_features: np.float32 csr_matrix of shape [n_users, n_user_features], optional
每行代表一个user, 行中的每个值代表这个user对于每个用户特征的权值
item_features: np.float32 csr_matrix of shape [n_items, n_item_features], optional
sample_weight: np.float32 coo_matrix of shape [n_users, n_items], optional
epochs: 训练的epoch次数
num_threads
verbose: bool, optional, 默认为
False
训练好之后的权值可以通过以下四个属性得到:
item_embeddings: np.float32 array of shape [n_item_features, n_components]
user_embeddings: np.float32 array of shape [n_user_features, n_components]
item_biases: np.float32 array of shape [n_item_features,]
user_biases: np.float32 array of shape [n_user_features,]
通常来说, warp
model = LightFM(loss='warp')
model.fit(interactions)
查看item
对应的embedding:
model.item_embeddings
array([[-0.09082373, 0.3682693 , 0.39377818, ..., -0.5586541 ,
0.75429815, 0.7312904 ],
[-0.05811575, 0.4481644 , -0.14007325, ..., -0.35257035,
0.29532075, -0.10209283],
[-0.12324008, 0.14663236, 0.1538505 , ..., -0.09988073,
0.20627348, 0.09036653],
...,
[ 0.14181307, -0.40691146, 0.20176099, ..., 0.33941165,
-0.38429636, -0.24518272],
[ 0.21714945, -0.3173487 , 0.04293495, ..., 0.13233031,
-0.4103705 , -0.2907497 ],
[ 0.20537567, -0.24591468, 0.14914612, ..., 0.23811479,
-0.34415472, -0.13272695]], dtype=float32)
或者使用weights来训练模型:
model_weights = LightFM(loss='warp')
model_weights.fit(weights)
model_weights.item_embeddings
array([[-0.39075807, 0.47386566, 0.68777657, ..., -0.6056915 ,
0.3733116 , 0.60417 ],
[-0.21503039, 0.30669904, 0.34501988, ..., -0.26187918,
0.33937645, 0.44880536],
[-0.04561909, 0.2656086 , 0.06034704, ..., -0.04511829,
0.2777305 , -0.00356603],
...,
[ 0.23444146, -0.30501488, -0.31618324, ..., 0.36489612,
-0.24902612, -0.29090258],
[ 0.18201233, -0.10327398, -0.23778489, ..., 0.20811245,
-0.07368308, -0.25726503],
[ 0.18961608, -0.07346104, -0.17514575, ..., 0.34551695,
-0.04013151, -0.15871607]], dtype=float32)
除了使用fit
方法训练之外, 还可以使用fit_partial
方法增量训练. 常用于多epoch
训练的情景. fit_partial
方法与fit
方法传入的参数相同, 因为在fit
方法中本身就调用了fit_partial
方法.
预测结果
使用模型的predict
方法对指定的user-item
对进行预测(交互概率或预计评分). 需要特别注意数据的形式与转换过程.
predict(user_ids, item_ids, item_features=None, user_features=None, num_threads=1)
user_ids: integer or np.int32 array of shape [n_pairs,]
需要注意这里传入的是整数形式的user, 即模型中对user的编号, 而不是原始的user
item_ids: np.int32 array of shape [n_pairs,]
同理, 也是
item
对应的整数索引
user_features: np.float32 csr_matrix of shape [n_users, n_user_features], optional
item_features: np.float32 csr_matrix of shape [n_items, n_item_features], optional
num_threads
返回的形式为:
np.float32 array of shape [n_pairs,]
Numpy array containing the recommendation scores for pairs defined by the inputs
这里使用lightfm.datasets
中的fetch_movielens
函数获取数据, 原因是将数据分成了train和test, 即训练集和验证集. 注意这里的训练集与验证集是按行为划分的, 并不是按照user划分的.
当然也可以使用Dataset手动生成train和test. 生成的方法为:
首先使用
sklearn
中的train_test_split函数将行为数据打乱后按比例划分成train和test然后分别使用Dataset中的build_interactions方法生成行为稀疏矩阵
from lightfm.datasets import fetch_movielens
movielens = fetch_movielens()
train, test = movielens['train'], movielens['test']
train, test
(<943x1682 sparse matrix of type '<class 'numpy.int32'>'
with 90570 stored elements in COOrdinate format>,
<943x1682 sparse matrix of type '<class 'numpy.int32'>'
with 9430 stored elements in COOrdinate format>)
model = LightFM(no_components=10, loss="warp")
model.fit(train, epochs=10)
item_index0 = np.arange(test.shape[1])[np.not_equal(test.tocsr()[0, :].toarray(), 0).ravel()]
test.tocsr()[0, :].toarray().ravel()[np.not_equal(test.tocsr()[0, :].toarray(), 0).ravel()]
得到用于评测的test
样本中的数值
array([4, 4, 4, 3, 2, 4, 5, 3, 5, 4], dtype=int32)
对相应的item进行评估:
model.predict(0, item_index0)
array([-4.54141998, -3.23370147, -4.54919338, -1.98084712, -3.43415737,
-3.94253993, -4.17489243, -3.95391273, -1.67029822, -2.08529305])
整体还是比较接近的.
不清楚为什么数值前面有个负号, 猜测是为了使用
np.argsort
排序方便
预测时, 传入的user与item需满足以下的关系:
因为预测的是
user-item
对, 因此两者的输入长度应相等如果是预测一个一个
user
对多个item
或一个item
对多个user
, 一个的那项只需要传入一个整数标量
评估方法
lightFM还提供了便捷的用于评价拟合好的模型优劣性的方法.
from lightfm.evaluation import auc_score, precision_at_k
两个指标的意义分别为:
auc_score: Measure the ROC AUC metric for a model
the probability that a randomly chosen positive example has a higher score than a randomly chosen negative example
precision_at_k: Measure the precision at k metric for a model
the fraction of known positives in the first k positions of the ranked list of results. A perfect score is 1.0
其中precision_at_k方法配合warp损失函数, auc_score配合bpr损失函数使用更符合逻辑, 能取得更好的效果.
两个评价函数接收的参数形式是相同的:
precision_at_k(model, test_interactions, train_interactions=None, k=10, user_features=None, item_features=None, preserve_rows=False, num_threads=1, check_intersections=True)
model: LightFM模型, 已经训练好的
test_interactions: np.float32 csr_matrix of shape [n_users, n_items]
Non-zero entries representing known positives in the evaluation set
train_interactions: np.float32 csr_matrix of shape [n_users, n_items], optional
Non-zero entries representing known positives in the train set
These will be omitted from the score calculations to avoid re-recommending known positives
k: topK
user_features: np.float32 csr_matrix of shape [n_users, n_user_features], optional
item_features: np.float32 csr_matrix of shape [n_items, n_item_features], optional
num_threads
model_auc = LightFM(no_components=10, loss="bpr")
model_top = LightFM(no_components=10, loss="warp")
model_auc.fit(train, epochs=10)
model_top.fit(train, epochs=10)
auc_train_precision = auc_score(model_auc, train)
auc_train_precision
array([0.7889878 , 0.9512625 , 0.91909486, 0.88896024, 0.8801184 ,
0.8458213 , 0.855748 , 0.8329105 , 0.7323852 , 0.8610401 ,
0.75433517, 0.8580728 , 0.8176935 , 0.8583111 , 0.92405164,
0.88807505, 0.95826656, 0.9128386 , 0.87326556, 0.9421181 ,
0.7756875 , 0.8509254 , 0.8667348 , 0.8528218 , 0.8565037 ,
0.9438616 , 0.9414917 , 0.88207227, 0.8531365 , 0.82404387,
...
返回的是每个user
计算得到的auc
的值, 因此整体的需要进行平均:
auc_train_precision = auc_score(model_auc, train).mean()
auc_test_precision = auc_score(model_auc, test).mean()
auc_train_precision, auc_test_precision
(0.8963242, 0.8576865)
top_train_precision = precision_at_k(model_top, train, k=10).mean()
top_test_precision = precision_at_k(model_top, test, k=10).mean()
top_train_precision, top_test_precision
(0.60010606, 0.11145282)
最后更新于
这有帮助吗?