web-dev-qa-db-ja.com

pyspark.mlCrossValidatorを介した暗黙的なpyspark.mlALS行列因数分解モデルのパラメーターの調整

暗黙のデータを使用するALSマトリックス因数分解モデルのパラメーターを調整しようとしています。このために、pyspark.ml.tuning.CrossValidatorを使用してパラメーターグリッドを実行し、最適なモデルを選択しようとしています。私の問題は評価者にあると思いますが、理解できません。

次のように、回帰RMSEエバリュエーターを使用した明示的なデータモデルでこれを機能させることができます。

from pyspark import SparkConf, SparkContext
from pyspark.sql import SQLContext
from pyspark.ml.recommendation import ALS
from pyspark.ml.tuning import CrossValidator, ParamGridBuilder
from pyspark.ml.evaluation import BinaryClassificationEvaluator
from pyspark.ml.evaluation import RegressionEvaluator

from pyspark.sql.functions import Rand


conf = SparkConf() \
  .setAppName("MovieLensALS") \
  .set("spark.executor.memory", "2g")
sc = SparkContext(conf=conf)

sqlContext = SQLContext(sc)

dfRatings = sqlContext.createDataFrame([(0, 0, 4.0), (0, 1, 2.0), (1, 1, 3.0), (1, 2, 4.0), (2, 1, 1.0), (2, 2, 5.0)],
                                 ["user", "item", "rating"])
dfRatingsTest = sqlContext.createDataFrame([(0, 0), (0, 1), (1, 1), (1, 2), (2, 1), (2, 2)], ["user", "item"])

alsExplicit = ALS()
defaultModel = alsExplicit.fit(dfRatings)

paramMapExplicit = ParamGridBuilder() \
                    .addGrid(alsExplicit.rank, [8, 12]) \
                    .addGrid(alsExplicit.maxIter, [10, 15]) \
                    .addGrid(alsExplicit.regParam, [1.0, 10.0]) \
                    .build()

evaluatorR = RegressionEvaluator(metricName="rmse", labelCol="rating")

cvExplicit = CrossValidator(estimator=alsExplicit, estimatorParamMaps=paramMapExplicit, evaluator=evaluatorR)
cvModelExplicit = cvExplicit.fit(dfRatings)

predsExplicit = cvModelExplicit.bestModel.transform(dfRatingsTest)
predsExplicit.show()

暗黙のデータ(たとえば、評価ではなくビューの数)に対してこれを実行しようとすると、完全に理解できないエラーが発生します。コードは次のとおりです(上記と非常によく似ています)。

dfCounts = sqlContext.createDataFrame([(0,0,0), (0,1,12), (0,2,3), (1,0,5), (1,1,9), (1,2,0), (2,0,0), (2,1,11), (2,2,25)],
                                 ["user", "item", "rating"])
dfCountsTest = sqlContext.createDataFrame([(0, 0), (0, 1), (1, 1), (1, 2), (2, 1), (2, 2)], ["user", "item"])

alsImplicit = ALS(implicitPrefs=True)
defaultModelImplicit = alsImplicit.fit(dfCounts)

paramMapImplicit = ParamGridBuilder() \
                    .addGrid(alsImplicit.rank, [8, 12]) \
                    .addGrid(alsImplicit.maxIter, [10, 15]) \
                    .addGrid(alsImplicit.regParam, [1.0, 10.0]) \
                    .addGrid(alsImplicit.alpha, [2.0,3.0]) \
                    .build()

evaluatorB = BinaryClassificationEvaluator(metricName="areaUnderROC", labelCol="rating")
evaluatorR = RegressionEvaluator(metricName="rmse", labelCol="rating")

cv = CrossValidator(estimator=alsImplicit, estimatorParamMaps=paramMapImplicit, evaluator=evaluatorR)
cvModel = cv.fit(dfCounts)

predsImplicit = cvModel.bestModel.transform(dfCountsTest)
predsImplicit.show()

RMSEエバリュエーターでこれを実行しようとしましたが、エラーが発生します。私が理解しているように、暗黙の行列因数分解の予測はバイナリ行列p_uiの予測の信頼行列c_uiであるため、バイナリ分類エバリュエーターにAUCメトリックを使用することもできるはずです このペーパーによる 、pysparkALSのドキュメントが引用しています。

どちらかのエバリュエーターを使用するとエラーが発生し、暗黙のALSモデルをオンラインで相互検証することについての有益な議論を見つけることができません。 CrossValidatorのソースコードを調べて何が問題なのかを突き止めようとしていますが、問題が発生しています。私の考えの1つは、プロセスが暗黙のデータ行列r_uiをバイナリ行列p_uiと信頼行列c_uiに変換した後、評価段階で予測されたc_ui行列を何と比較しているかわからないということです。

エラーは次のとおりです。

Traceback (most recent call last):

  File "<ipython-input-16-6c43b997005e>", line 1, in <module>
    cvModel = cv.fit(dfCounts)

  File "C:/spark-1.6.1-bin-hadoop2.6/python\pyspark\ml\pipeline.py", line 69, in fit
    return self._fit(dataset)

  File "C:/spark-1.6.1-bin-hadoop2.6/python\pyspark\ml\tuning.py", line 239, in _fit
    model = est.fit(train, epm[j])

  File "C:/spark-1.6.1-bin-hadoop2.6/python\pyspark\ml\pipeline.py", line 67, in fit
    return self.copy(params)._fit(dataset)

  File "C:/spark-1.6.1-bin-hadoop2.6/python\pyspark\ml\wrapper.py", line 133, in _fit
    Java_model = self._fit_Java(dataset)

  File "C:/spark-1.6.1-bin-hadoop2.6/python\pyspark\ml\wrapper.py", line 130, in _fit_Java
    return self._Java_obj.fit(dataset._jdf)

  File "C:\spark-1.6.1-bin-hadoop2.6\python\lib\py4j-0.9-src.Zip\py4j\Java_gateway.py", line 813, in __call__
    answer, self.gateway_client, self.target_id, self.name)

  File "C:/spark-1.6.1-bin-hadoop2.6/python\pyspark\sql\utils.py", line 45, in deco
    return f(*a, **kw)

  File "C:\spark-1.6.1-bin-hadoop2.6\python\lib\py4j-0.9-src.Zip\py4j\protocol.py", line 308, in get_return_value
    format(target_id, ".", name), value)

etc.......

[〜#〜]更新[〜#〜]

入力を0から1の範囲になるようにスケーリングし、RMSEエバリュエーターを使用してみました。 CrossValidatorに挿入しようとするまでは、うまく機能しているようです。

次のコードは機能します。予測を取得し、評価者からRMSE値を取得します。

from pyspark import SparkConf, SparkContext
from pyspark.sql import SQLContext
from pyspark.sql.types import FloatType
import pyspark.sql.functions as F
from pyspark.ml.recommendation import ALS
from pyspark.ml.tuning import CrossValidator, ParamGridBuilder
from pyspark.ml.evaluation import RegressionEvaluator


conf = SparkConf() \
  .setAppName("ALSPractice") \
  .set("spark.executor.memory", "2g")
sc = SparkContext(conf=conf)

sqlContext = SQLContext(sc)

# Users 0, 1, 2, 3 - Items 0, 1, 2, 3, 4, 5 - Ratings 0.0-5.0
dfCounts2 = sqlContext.createDataFrame([(0,0,5.0), (0,1,5.0),            (0,3,0.0), (0,4,0.0), 
                                        (1,0,5.0),            (1,2,4.0), (1,3,0.0), (1,4,0.0),
                                        (2,0,0.0),            (2,2,0.0), (2,3,5.0), (2,4,5.0),
                                        (3,0,0.0), (3,1,0.0),            (3,3,4.0)            ],
                                       ["user", "item", "rating"])

dfCountsTest2 = sqlContext.createDataFrame([(0,0), (0,1), (0,2), (0,3), (0,4),
                                            (1,0), (1,1), (1,2), (1,3), (1,4),
                                            (2,0), (2,1), (2,2), (2,3), (2,4),
                                            (3,0), (3,1), (3,2), (3,3), (3,4)], ["user", "item"])

# Normalize rating data to [0,1] range based on max rating
colmax = dfCounts2.select(F.max('rating')).collect()[0].asDict().values()[0]
normalize = udf(lambda x: x/colmax, FloatType())
dfCountsNorm = dfCounts2.withColumn('ratingNorm', normalize(col('rating')))

alsImplicit = ALS(implicitPrefs=True)
defaultModelImplicit = alsImplicit.fit(dfCountsNorm)
preds = defaultModelImplicit.transform(dfCountsTest2)

evaluatorR2 = RegressionEvaluator(metricName="rmse", labelCol="ratingNorm")
evaluatorR2.evaluate(defaultModelImplicit.transform(dfCountsNorm))

preds = defaultModelImplicit.transform(dfCountsTest2)

私が理解していないのは、なぜ以下が機能しないのかということです。同じ推定量、同じ評価器を使用し、同じデータをフィッティングしています。これらが上記で機能するが、CrossValidator内では機能しないのはなぜですか。

paramMapImplicit = ParamGridBuilder() \
                    .addGrid(alsImplicit.rank, [8, 12]) \
                    .addGrid(alsImplicit.maxIter, [10, 15]) \
                    .addGrid(alsImplicit.regParam, [1.0, 10.0]) \
                    .addGrid(alsImplicit.alpha, [2.0,3.0]) \
                    .build()

cv = CrossValidator(estimator=alsImplicit, estimatorParamMaps=paramMapImplicit, evaluator=evaluatorR2)
cvModel = cv.fit(dfCountsNorm)
11
ilyab

技術的な問題を無視すると、厳密に言えば、暗黙のフィードバックを伴うALSによって生成された入力を考えると、どちらの方法も正しくありません。

  • RegressionEvaluatorを使用することはできません。これは、すでにご存知のとおり、予測は信頼値として解釈でき、範囲[0、1]の浮動小数点数として表され、ラベル列は単なるバインドされていない整数であるためです。これらの値は明らかに比較できません。
  • 予測が確率ラベルとして解釈できる場合でも、バイナリ決定を表さないため、BinaryClassificationEvaluatorを使用することはできません。さらに、予測列のタイプが無効であり、BinaryClassificationEvaluatorと直接使用できませんでした

入力が要件に合うように列の1つを変換することを試みることができますが、これは理論的な観点からは実際には正当化されたアプローチではなく、調整が難しい追加のパラメーターを導入します。

  • ラベル列を[0、1]の範囲にマップし、RMSEを使用します。

  • ラベル列を固定しきい値のバイナリインジケーターに変換し、ALS/ALSModelを拡張して、期待される列タイプを返します。しきい値を1とすると、次のようになります。

    from pyspark.ml.recommendation import *
    from pyspark.sql.functions import udf, col
    from pyspark.mllib.linalg import DenseVector, VectorUDT
    
    class BinaryALS(ALS):
        def fit(self, df):
            assert self.getImplicitPrefs()
            model = super(BinaryALS, self).fit(df)
            return ALSBinaryModel(model._Java_obj)
    
    class ALSBinaryModel(ALSModel):
        def transform(self, df):
            transformed = super(ALSBinaryModel, self).transform(df)
            as_vector = udf(lambda x: DenseVector([1 - x, x]), VectorUDT())
            return transformed.withColumn(
                "rawPrediction", as_vector(col("prediction")))
    
    # Add binary label column
    with_binary = dfCounts.withColumn(
        "label_binary", (col("rating") > 0).cast("double"))
    
    als_binary_model = BinaryALS(implicitPrefs=True).fit(with_binary)
    
    evaluatorB = BinaryClassificationEvaluator(
        metricName="areaUnderROC", labelCol="label_binary")
    
    evaluatorB.evaluate(als_binary_model.transform(with_binary))
    ## 1.0
    

一般的に言って、暗黙のフィードバックを使用したレコメンダーシステムの評価に関する資料は、教科書にはありません。これらの種類のレコメンダーの評価については、 eliasah 's answer を読むことをお勧めします。

9
zero323

暗黙のフィードバックがあるため、推奨事項に対するユーザーの反応はありません。したがって、精度ベースのメトリックを使用することはできません。

すでに 引用された論文 では、代わりに予想されるパーセンタイルランク付けメトリックが使用されます。

Spark ML libの同様のメトリックに基づいてエバリュエーターを実装し、それを相互検証パイプラインで使用することができます。

0
Danilo Ascione