3

您是否见过 Netflix 如何根据您之前看过并喜欢观看的电影向您推荐某些电影?我正在尝试做同样的事情,但只是为了一套书。

我有 53 本书和 32 个用户。32 位用户对每本书的评分从 5 到 -5,其中 5 分是我喜欢的。用于计算两本书之间“相似”程度的公式如下:

相似函数

x1*y1表示用户一个对书 x 和书 y 的评分,x2*y2表示第二个用户对同一 2 本书的评分,对所有用户都继续。

传递给此方法的数组是主数组。主数组的每一个元素对应一个用户,用户数组的每一个元素对应一本书。(32 个用户数组,每个数组本身就是一个 53 元素的数组)

保存每个用户评分的数组是按顺序排列的,compValuehold[0][0]代表第一个用户对第一本书的评分,compValuehold[0][2]代表第一个用户对第二本书的评分,等等。

 public static void DisplayRatings(double[][] compValuehold)
        {

            double eachUserProduct = 0;
            double denominatorXSum = 0;
            double denominatorYSum = 0;
            double Score = 0;
            int counterForScore = 0;
            double[] calculatedValues = new double[52];



            //this for loop should calculate each book's ratings and store it
            //in an array
            for (int i = 0; i < 52; i++)
            {

                for (int j = 0; j < 32; j++)
                {
                    eachUserProduct += compValuehold[j][i] * compValuehold[j][i + 1];
                    denominatorXSum += compValuehold[j][i] * compValuehold[j][i];
                    denominatorYSum += compValuehold[j][i + 1] * compValuehold[j][i + 1];

                }

                denominatorXSum = Math.Sqrt(denominatorXSum);
                denominatorYSum = Math.Sqrt(denominatorYSum);
                Score = eachUserProduct / (denominatorXSum * denominatorYSum);
                calculatedValues[counterForScore] = Score;
                counterForScore += 1;
                denominatorXSum = 0;
                denominatorYSum = 0;
                eachUserProduct = 0;

            }

        }

我能够编写代码来比较第一本书和其他书。我的问题是我需要找出每本书最相似的书。这意味着要多次计算该公式。我不知道如何为所有的书做到这一点。

4

3 回答 3

3

似乎您正在做的是确定“书籍向量”的余弦相似度,其中每个向量由每个用户对特定书籍的评分组成。

试图在一个函数中完成这一切可能会让您在调试时头疼;我建议将您的问题分解为更易于管理的部分:

  • 编写一个函数,为特定书籍创建书籍向量。
    • 在您的情况下,这将从您的compValuehold矩阵中提取给定的列
  • 编写一个计算两本书向量之间相似度的函数。
  • 循环遍历所有书籍对,计算每对书籍的相似度。
    • (请注意similarity(a, b) == similarity(b, a)

如果您想出更好的比较书籍的方法,这种方法还可以更轻松地更改相似度函数。

这是前两个子问题的示例实现(请记住,它们并不是特别有效):

static int[] GetBookVector(int[][] ratingMatrix, int bookIndex)
{
    int[] book = new int[ratingMatrix.Length];
    for (int i = 0; i < ratingMatrix.Length; i++)
    {
        book[i] = ratingMatrix[i][bookIndex];
    }

    return book;
}

static double Similarity(int[] v1, int[] v2)
{
    if (v1.Length != v2.Length)
    {
        throw new ArgumentException("Vectors must be of the same length.");
    }

    int numerator = 0;
    double v1Norm = 0;
    double v2Norm = 0;
    for (int i = 0; i < v1.Length; i++)
    {
        numerator += v1[i] * v2[i];
        v1Norm += v1[i] * v1[i];
        v2Norm += v2[i] * v2[i]; 
    }

    v1Norm = Math.Sqrt(v1Norm);
    v2Norm = Math.Sqrt(v2Norm);

    return (numerator / (v1Norm * v2Norm));
}
于 2013-03-28T00:36:01.760 回答
2

正如@dckrooney 指出的那样,您正在计算两个向量之间的余弦相似度,每个向量代表所有用户的“评级概况”。从头开始编写该函数很好,但您可以考虑为此使用线性代数库,这将简化您的工作。例如,使用 Math.NET 之类的库,您可以将数组表示为矩阵,例如 Ratings,然后您可以提取列并以更直接的方式执行计算,如下所示:

public double Similarity(DenseMatrix matrix, int col1, int col2)
{
    var column1 = matrix.Column(col1);
    var column2 = matrix.Column(col2);
    var similarity = column1.DotProduct(column2) / (column1.Norm(2)+column2.Norm(2));
    return similarity;
}

您可能会从处理原始数组中获得一些轻微的性能优势,但可以说,代码更具可读性并且易于维护。此外,Math.NET 允许您使用本地提供程序并直接在 CPU 上使用线性代数运行计算,这可以为您带来不错的性能提升。

除此之外,是的,您必须对每一列重复该计算,这可能会变得非常昂贵,特别是如果您有一个大矩阵。解决此问题的一种方法是使用奇异值分解,它可以帮助您减小数据集的大小。

于 2013-03-28T02:21:44.303 回答
0

这是一个错误吗?

    denominatorYSum += compValuehold[j][i + 1] * compValuehold[j][i + 1];
..
...
..
denominatorYSum = Math.Sqrt(denominatorYSum);

如果不是,只需将代码更改为

denominatorYSum += compValuehold[j][i + 1];

Sqrt 非常昂贵,它本质上是一个循环。

假设以上是一个错误,我会完全摆脱两个 Sqrt 计算。或者把它移到这一行,

Score = eachUserProduct / Math.Sqrt(denominatorXSum * denominatorYSum);

Math.Sqrt(25) * Math.Sqrt(25) 是 25。Math.Sqrt(25*25) 是 25。此外,更高的值具有更高的平方根。因此,您可以完全摆脱 Math.Sqrt() 调用,并且就距离排序(相似性)而言,计算仍然相同。

这更像是一道数学题而不是编程题。. . 我希望我没有做你的功课。

于 2013-03-27T23:38:18.873 回答