# 使用例子

使用经典的[Movielens 100k dataset](http://grouplens.org/datasets/movielens/100k/)数据集作为推荐的例子. 在lightFM中, 可以直接调用函数获取整理成稀疏矩阵格式的数据:

```python
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`读取数据.

```python
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`.

```python
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`的交互矩阵的大小了.

```python
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`是完全相同的

这一步耗时比较长.

```python
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))
```

## 拟合模型

在得到了稀疏矩阵形式的交互数据之后, 就可以创建模型, 并进行拟合操作了.

```python
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`需要注意的参数:

```python
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**

```python
model = LightFM(loss='warp')
model.fit(interactions)
```

查看`item`对应的embedding:

```python
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来训练模型:

```python
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`对进行预测(交互概率或预计评分). 需要特别注意数据的形式与转换过程.

```python
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**方法生成行为稀疏矩阵

```python
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>)
```

```python
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进行评估:

```python
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还提供了便捷的用于评价拟合好的模型优劣性的方法.

```python
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**损失函数使用更符合逻辑, 能取得更好的效果.

两个评价函数接收的参数形式是相同的:

```python
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

```python
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`的值, 因此整体的需要进行平均:

```python
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)
```

```python
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)
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://blessbingo.gitbook.io/garnet/tui-jian-suan-fa/matrix-factorization/lightfm/shi-yong-li-zi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
