3

以前に見て楽しんだ映画に基づいて、Netflixが特定の映画をどのように推奨するかを見たことがありますか? 私は同じことをしようとしていますが、本のセットです。

53 冊の本と 32 人のユーザーがいます。32 人のユーザーが各本を 5 から -5 のスケールで評価しました。5 はとても気に入りました。2 冊の本がどのように「似ている」かを計算するために使用される式は、次のとおりです。

類似度関数

x1*y1は本 x と本 y に対するユーザー 1 の評価をx2*y2表し、同じ 2 本に対する 2 番目のユーザーの評価を表し、すべてのユーザーについて続きます。

このメソッドに渡される配列はメイン配列です。メイン配列の各要素はユーザーに対応し、ユーザー配列の各要素は本に対応します。(32 個のユーザー配列。それぞれが 53 要素の配列です)

各ユーザーの評価を保持する配列はcompValuehold[0][0]、最初の本に対するcompValuehold[0][2]最初のユーザーの評価を表し、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

あなたがしていることは、「本のベクトル」のコサイン類似度を決定しているようです。各ベクトルは、特定の本に対する各ユーザーの評価で構成されています。

これをすべて 1 つの関数で実行しようとすると、デバッグで頭痛の種になる可能性があります。問題をより扱いやすい部分に分割することをお勧めします。

  • 特定の本の本ベクトルを作成する関数を作成します。
    • あなたの場合、これはcompValueholdマトリックスから特定の列を引き出すことになります
  • 2 つのブック ベクトル間の類似度を計算する関数を作成します。
  • 本のすべてのペアをループし、各ペアの類似度を計算します。
    • (注意similarity(a, b) == similarity(b, a)

また、このアプローチにより、将来どこかで本を比較するより良い方法を思いついた場合に、類似度関数を変更することがはるかに簡単になります。

最初の 2 つのサブ問題の実装例を次に示します (特に効率的ではないことに注意してください)。

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 が指摘したように、2 つのベクトル間のコサイン類似度を計算しています。各ベクトルは、すべてのユーザーの「評価プロファイル」を表しています。その関数をゼロから作成することは問題ありませんが、これには線形代数ライブラリを使用することを検討してください。これにより、作業が簡素化されます。たとえば、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 では、ネイティブ プロバイダーを使用し、線形代数を使用して計算を CPU で直接実行できるため、パフォーマンスが大幅に向上します。

それを超えて、はい、すべての列に対してその計算を繰り返す必要があり、特に大きな行列がある場合は非常に高価になる可能性があります. これを回避する 1 つの方法は、データセットのサイズを小さくするのに役立つ特異値分解を使用することです。

于 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 に答える