如何计算此方法中几本书的亲密度?

时间:2013-03-27 23:18:39

标签: c# algorithm linear-algebra

你有没有见过Netflix如何根据你以前看过的电影和喜欢看的电影推荐某些电影?我试图做同样的事情,但对于一套书。

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

Similarity function

x1*y1代表用户对书籍x和书籍y的评分,x2*y2代表第2位用户对相同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;

            }

        }

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

3 个答案:

答案 0 :(得分:3)

您正在做的就是确定“书籍向量”的Cosine Similarity,其中每个向量由每个用户对特定书籍的评分组成。

尝试在一个函数中执行此操作可能会让您在调试时遇到麻烦; 我建议将你的问题分解成更容易管理的部分:

  • 编写一个为特定书籍创建书籍矢量的功能。
    • 在您的情况下,这将从您的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));
}

答案 1 :(得分:2)

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

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允许您使用native providers并直接在CPU上使用线性代数运行计算,这可以为您带来不错的性能提升。

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

答案 2 :(得分: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()调用,并且距离排序(相似性)的计算仍然是相同的。

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