每日学习记录 2019-10-28

1. 添加相似度后的矩阵更新方式研究

1.1 baseline

baseline 模型是 RSVD,也就是 SVD 矩阵分解添加正则化的损失函数如下:
\begin{aligned} L=& \min _{U, V} \frac{1}{2} \sum_{i=1}^{m} \sum_{j=1}^{n} I_{i j}\left(r_{i j}-\mathbf{u}_{i}^{T} \mathbf{v}_{j}\right)^{2} \\ &+\frac{\lambda_{1}}{2}\|U\|_{F}^{2}+\frac{\lambda_{2}}{2}\|V\|_{F}^{2} \end{aligned}
矩阵更新公式如下:
\begin{array}{l}{\mathbf{u}_{i} \leftarrow \mathbf{u}_{i}+\gamma_{1}\left(\Delta_{i j} \mathbf{v}_{j}-\lambda_{1} \mathbf{u}_{i}\right)} \\ {\mathbf{v}_{j} \leftarrow \mathbf{v}_{j}+\gamma_{2}\left(\Delta_{i j} \mathbf{u}_{i}-\lambda_{2} \mathbf{v}_{j}\right)}\end{array}

1.2 添加相似度后的模型

添加相似度后的模型的损失函数:
\begin{aligned} L=& \min _{U, V} \frac{1}{2} \sum_{i=1}^{m} \sum_{j=1}^{n} I_{i j}\left(r_{i j}-\mathbf{u}_{i}^{T} \mathbf{v}_{j}\right)^{2} \\ &+\frac{\alpha}{2} \sum_{i=1}^{m} \sum_{f \in \mathcal{F}+(i)} s_{i f}\left\|\mathbf{u}_{i}-\mathbf{u}_{f}\right\|_{F}^{2} \\ &+\frac{\lambda_{1}}{2}\|U\|_{F}^{2}+\frac{\lambda_{2}}{2}\|V\|_{F}^{2} \end{aligned}
矩阵更新公式如下:
\begin{aligned} \mathbf{u}_{i} \leftarrow \mathbf{u}_{i}+\gamma_{1}\left(\Delta_{i j} \mathbf{v}_{j}-\alpha\right.& \sum_{f \in \mathcal{F}+(i)} s_{if}\left(\mathbf{u}_{i}-\mathbf{u}_{f}\right) \\\left.-\alpha \sum_{g \in \mathcal{F}^{-}(i)} s_{i g}\left(\mathbf{u}_{i}-\mathbf{u}_{g}\right)-\lambda_{1} \mathbf{u}_{i}\right) &\end{aligned}

1.3 代码分析

1.3.1 ALS 库的选择

目前能使用的 ALS 算法的 Python 工具库有两个:

  1. Spark.mllib
  2. implicit

那么先来分析一下 Spark.mllib 库

1.3.1.1 Spark.mllib

  • Spark.mllib

  • 优点如下:

    • 速度快
    • 方便推荐
    • 方便调参
    • 方便输出不同指标的准度,如:MPA@K,P@K,NDCG@K,RMSE等
  • 缺点如下:

    • 目前算法接口只支持两个数据类型输入。分别是:spark.RDD 和 Dataframe
    • 各个算法像一个黑盒子,只有少量参数可以调整,也许用起来简单,但很多时候并不能满足不同用户的使用需求
    • 不方便调整内部的具体算法,如调整损失函数。

1.3.1.2 implicit

  • implicit
  • 优点如下:
    • 速度快,使用 Cython 代码编写
    • 比较方便推荐,有推荐接口
    • 方便调参
  • 缺点如下:
    • 不方便调整内部的具体算法,如调整损失函数,需要一定的 Cython 基础
    • 支持输出的准度指标较少,仅有 NDCG@K,P@K,MAP@K
    • 源代码注释少,不方便二次修改

1.3.2 具体代码分析

由于 Spark.mllib 的源码无法修改,所以不分析了

下面是 implicit 库中的 _calculate_loss() 函数:

  • 使用 Cython 语言编写,改写比较麻烦
  • 源代码没有注释
  • 每次调试只有在重新编译后才能使用,调试比较麻烦
  • 代码中的具体分析在注释里面,目前还未解析完
def _calculate_loss(Cui, integral[:] indptr, integral[:] indices, float[:] data,
                    floating[:, :] X, floating[:, :] Y, float regularization,
                    int num_threads=0):

    # Cui是评分矩阵
    # indptr是每行的非零数据总数
    # indices是每行的非零数据列数
    # data是所有的非零数据
    # 以上3个变量可以组成一个评分矩阵
    # X是ALS的固定矩阵,用来求Y
    # Y是ALS的固定矩阵,用来求X
    # regularization是正则化项
    # num_threads是迭代次数

    # 转换数据类型
    dtype = np.float64 if floating is double else np.float32
    # 确定用户与项目数量
    cdef int users = X.shape[0], N = X.shape[1], items = Y.shape[0], u, i, index, one = 1
    # 暂定
    cdef floating confidence, temp
    cdef floating zero = 0.

    # Y的平方
    cdef floating[:, :] YtY = np.dot(np.transpose(Y), Y)

    # 指针数据
    cdef floating * r

    # 初始化loss等变量
    cdef double loss = 0, total_confidence = 0, item_norm = 0, user_norm = 0

    with nogil, parallel(num_threads=num_threads):
        r =  malloc(sizeof(floating) * N)
        try:
            for u in prange(users, schedule='guided'):
                # calculates (A.dot(Xu) - 2 * b).dot(Xu), without calculating A
                temp = 1.0
                symv("U", &N, &temp, &YtY[0, 0], &N, &X[u, 0], &one, &zero, r, &one)

                # 计算每个用户
                for index in range(indptr[u], indptr[u + 1]):
                    # i表示第几列
                    i = indices[index]
                    # confidence表示该用户的第i个数据
                    confidence = data[index]

                    if confidence > 0:
                        temp = -2 * confidence
                    else:
                        temp = 0
                        confidence = -1 * confidence

                    temp = temp + (confidence - 1) * dot(&N, &Y[i, 0], &one, &X[u, 0], &one)
                    axpy(&N, &temp, &Y[i, 0], &one, r, &one)

                    total_confidence += confidence
                    loss += confidence

                loss += dot(&N, r, &one, &X[u, 0], &one)
                user_norm += dot(&N, &X[u, 0], &one, &X[u, 0], &one)

            for i in prange(items, schedule='guided'):
                item_norm += dot(&N, &Y[i, 0], &one, &Y[i, 0], &one)

        finally:
            free(r)

    loss += regularization * (item_norm + user_norm)
    return loss / (total_confidence + Cui.shape[0] * Cui.shape[1] - Cui.nnz)

1.4 总结

想要添加用户相似度进 ALS 模型中的损失函数还需要不少时间

参考链接:Spark 相关知识

你可能感兴趣的:(每日学习记录 2019-10-28)