自動推奨:いくつかの理論と実践

1。 エントリー



この記事では、自動推奨の基本的な理論的および実用的な問題について説明します。 トラフィックが多い(1日あたり数百万人)大規模ポータル(Yii 2で記述)でApache Mahoutを使用した経験の話に特に注意が払われます。 PHPおよびJAVAソースコードの例は、読者がMahout統合プロセスをよりよく理解できるように提供されます。



2.堅牢性評価



まず、結果がさまざまな干渉の影響を受けないようにする必要があります。 原則として、特定のエンティティを推奨する最も簡単な方法は、ユーザー評価の平均値によるランキング方法です。 平均スコアが高いほど、オブジェクトを推奨する可能性が高くなります。 ただし、このような単純なアプローチであっても、非常に重要なポイント-精度の干渉があります。 いくつかの例を見てみましょう。 ユーザーが1から10のスケールで製品(または他のオブジェクト)を評価する機会があると仮定します。特定のオブジェクトに対して、5、7、4、8、6、5、10、5、10、9、のユーザー評価の有限セットがあります。 10、10、10、5、2、10。知覚をより快適にするために、図の形式で表示します。







セットのカーディナリティーは16です。この特定のケースでは、中央値(7.5)と算術平均(7.25)の有意差は観察されず、確率変数の分散は7.267のみです。 場合によっては、エミッションを除外する必要がある場合があります(たとえば、変動範囲が大きくなる可能性がある場合)。 当然、堅牢なインジケーターを使用できます。 1、2、1、2、3、1、5、100、4のセットがあります。そのグラフィック表現:







上記の例では、算術平均は実際の状況を明示的に反映していません。 さらに、サンプルの分散は明らかに大きくなっています(この例では、1060をわずかに超えています)。 堅牢なインジケーター(中央値と四分位数)を使用すると、このような問題を回避できます。



3.相関の会計処理



最初に思い浮かぶのは、相関を見つける方法です。 当然、これはカールピアソンの線形相関係数です。 別の小さな例を次に示します。 2人の専門家がサイトの品質を評価する必要があるとします。 それらのそれぞれは、彼自身の評価を与えます。 たとえば、次のように:







グラフを一目見ただけで、2人の専門家の強い類似性を確認できます。 この例では、カールピアソンの線形相関係数は0.9684747092でした。 得られたデータに基づいて、他のサイトでイベントが「同様の評価を設定」する可能性が非常に高くなると予測することができます。 好みを知ることはお勧めしやすいでしょう? しかし、すべてを一列に並べるのではなく、志を同じくするユーザーのみの推定値に頼ることはできますか?



4.自動推奨



興味深い無料のApache Mahoutライブラリーの例を考えてみましょう。 3つのオブジェクトがあると仮定します。 そのうち2つだけを評価しました(最初のオブジェクトは2点のみを評価し、2番目のオブジェクトは5点を評価しました)。 私に加えて、オブジェクトを評価した人がさらに3人います。 しかし、私とは異なり、3つのオブジェクトすべてを評価しました。 すべての評価を含む表を見てみましょう。







実際、そのようなデータをMahoutに渡すと、彼は3番目のオブジェクトを正確に推奨します。 最初の2つのオブジェクトを推奨することには意味がありません。私はもはやそれらについて知っているだけでなく、評価さえしているからです。 さらに、Mahoutは私の意見と他の3人との類似性を考慮することができました。最初のオブジェクト(10など)に非常に異なる評価を与えた場合、Mahoutは何も勧めません。 私は何か混乱していますか? 今すぐ確認します。



MySQLJDBCDataModelクラスは、MySQLデータベースからデータを受信できます(テーブルにはuser_id、item_id、およびpreferenceが含まれている必要があります)。 また、FileDataModelクラスはファイル(CSVファイル。各行は「1,1,2.0」のように見え、空の行は無視されます)からロードできます。 理論的には、Yiiのアプリケーションは、推奨方法についてまったく何も知らず、データベースから必要な情報(テーブルリンク:ユーザー識別子、推奨オブジェクト識別子)を取得し、ユーザーに表示するだけです。



Yiiでは、さまざまな分析システムや検索プラットフォームとの統合を含め、非常に負荷の高いサイト(1日あたり数百万人のトラフィック)で非常に多くのタスクを実行する必要がありました。 当然、多くのJavaプロジェクトを理解する必要がありましたが、Mahoutを初めて接続しました。



もちろん、データを交換するには多くの方法があります。 外部システムからの直接ダウンロード(Hibernateを使用したサイトデータベースへのエクスポート)から、キュー(Gearman、RabbitMQ)の使用まで。 JSOUPを使用してサイトを解析し、データを取得するために非常に遅いPhantomJSを使用した場合、POIを使用してExcelからダウンロードした場合、いくつかの面白いケースを見ました。 しかし、悲しいことについては話しましょう。



ところで、ストレージメソッドも退屈ではありません-MongoDBから検索エンジン(Endeca、Solr、Sphinx、ATGに組み込まれた奇跡まで)まで。 もちろん、そのようなオプションには存在する権利があり、巨大なプロジェクトでめったに使用されませんが、この記事では、より一般的なオプションを検討したいと思います。



Yiiに1日に数百万人のトラフィックがあるサイトがあるとしましょう。 MySQLクラスターをデータベースとして使用します(memcachedはすべての困難と負荷の奪取を処理します)。 アプリケーションにはデータベースへの書き込み権限がないため、データはAPIを介して(Redisクラスターに)のみ送信され、そこからJavaで作成された分析システム(無料のgoogle-gsonおよびJedisライブラリのおかげ)が取得されます。 Mahoutライブラリーが追加されたのは彼女にとってでした。



しかし、識別子のリストだけでなく、既成の(ウィジェット用の)データを取得したいのです。 何が必要ですか? 画像を表示したいとします。 見出しも必要です。 もちろん、推奨オブジェクト(ユーザーがウィジェットをクリックすると表示されるページ)へのリンクが必要です。 これは普遍的なオプションになります。 アンロードを担当するシステムでは、このテーブルを埋めるために必要なロジックを追加できます。 この場合、テーブル構造は次のようになります。



use yii\db\Schema; use yii\db\Migration; class m150923_110338_recommend extends Migration { public function up() { $this->createTable('recommend', [ 'id' => $this->primaryKey(), 'status' => $this->boolean()->notNull(), 'url' => $this->string(255)->notNull(), 'title' => $this->string(255)->notNull(), 'image' => $this->string(255)->notNull(), 'created_at' => $this->datetime()->notNull(), 'updated_at' => $this->datetime()->notNull(), ]); } public function down() { $this->dropTable('recommend'); } }
      
      





モデルには、このエンティティを推奨するユーザーを理解できるメソッドが必要です。 推奨オブジェクトの一部からMahoutがわかります。 もちろん、最初から、Mahoutが私たちに何も推奨できない状況を予測します(または量が不十分です)。 モデルは次のようになります。



 namespace common\models; use Yii; use common\models\Api; /** * This is the model class for table "recommend". * * @property integer $id * @property integer $status * @property string $url * @property string $title * @property string $image * @property string $created_at * @property string $updated_at */ class Recommend extends \yii\db\ActiveRecord { const STATUS_INACTIVE = 0; const STATUS_ACTIVE = 1; /** * @inheritdoc */ public static function tableName() { return 'recommend'; } /** * @inheritdoc */ public function rules() { return [ [['status', 'url', 'title', 'image', 'created_at', 'updated_at'], 'required'], [['status'], 'integer'], [['created_at', 'updated_at'], 'safe'], [['url', 'title', 'image'], 'string', 'max' => 255] ]; } /** * @inheritdoc */ public function attributeLabels() { return [ 'id' => 'ID', 'status' => '', 'url' => '', 'title' => '', 'image' => '  ', 'created_at' => '', 'updated_at' => '', ]; } /** * @inheritdoc */ public function behaviors() { return [ [ 'class' => \yii\behaviors\TimestampBehavior::className(), 'value' => new \yii\db\Expression('NOW()'), ], ]; } /** * Status list */ public function statusList() { return [ self::STATUS_INACTIVE => '', self::STATUS_ACTIVE => ' ', ]; } /** * @param integer $userId * @param integer $limit */ public static function getItemsByUserId($userId = 1, $limit = 6) { $itemIds = []; //   get  Api   JSON::decode    //   ID  Recommend,      (, , ) $mahout = Api::get('s=mahout&order=value&limit=' . (int)$limit . '&user=' . (int)$userId); if(!empty($mahout['status']) && $mahout['status'] == true) { $itemIds = $mahout['item-ids']; } if(count($itemIds) < $limit) { //        (   , //   ,    ,     ..).  , //    . $limit = $limit - count($itemIds); $recommend = Api::get('s=recommend&limit=' . (int)$limit . '&user=' . (int)$userId); if(!empty($recommend['status']) && $recommend['status'] == true) { $itemIds = array_merge($itemIds, $recommend['item-ids']); } } return static::find()->where(['id' => $itemIds, 'status' => static::STATUS_ACTIVE])->all(); } }
      
      





また、コントローラーもまったくトリッキーではありません。



 namespace frontend\controllers; use Yii; use yii\web\Controller; use common\models\Recommend; class MainController extends Controller { private $_itemsLimit = 6; private $_cacheTime = 120; public function actionIndex() { $userId = Yii::$app->request->cookies->getValue('userId', 1); $recommends = Recommend::getDb()->cache(function ($db) use ($userId) { return Recommend::getItemsByUserId($userId, $this->_itemsLimit); }, $this->_cacheTime); return $this->render('index', ['recommends' => $recommends]); } }
      
      





ビューは次のとおりです(MVCのビュー)。



 <?php use yii\helpers\Html; $this->title = 'Example'; $this->params['breadcrumbs'][] = $this->title; ?> <h3> :</h3> <div class="row"> <?php foreach ($recommends as $recommend) { ?> <div class="col-md-2"> <a href="<?= $recommend->url ?>" target="_blank"> <img src="<?= $recommend->image ?>" class="img-thumbnail" alt="<?= Html::encode($recommend->title) ?>"> </a> </div> <?php } ?> </div>
      
      





プロトタイプの準備ができました。 目的のコードを実際のシステムに転送することは残っています。 月曜日にタスクを開始する必要があり、土曜日に自宅のコンピューターでMahoutを試すことにしました。 たくさんの本を読むのは良いことですが、練習も重要です。 数分で、CSVファイルからデータを取得し、結果をJSON形式で書き込む単純なJavaアプリケーションをスケッチしました。



インターフェイスは、JSONを返すメソッドを1つだけ実装するように要求します。 この特定のケースでは、CSVデータファイルへのリンクと、何かを推奨する必要があるユーザーIDのリストを提供する必要があります。



 package com.api.service; import java.util.List; public interface IService { String run(String datasetFile, List<Integer> userIds); }
      
      





次に、ファクトリを作成します。



 package com.api.service; public class ServiceFactory { /** * Get Service * @param type * @return */ public IService getService(String type) { if (type == null) { return null; } if(type.equalsIgnoreCase("Mahout")) { return new MahoutService(); } return null; } }
      
      





たとえば、識別子のリストに表示される各ユーザーの推奨オブジェクトのリストを取得します。



 package com.api.service; import java.io.IOException; import java.util.List; import org.apache.mahout.cf.taste.common.TasteException; import com.api.model.CustomUserRecommender; import com.api.util.MahoutHelper; import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class MahoutService implements IService { @Override public String run(String datasetFile, List<Integer> userIds) { Gson gson = new GsonBuilder().create(); MahoutHelper mahoutHelper = new MahoutHelper(); List<CustomUserRecommender> customUserRecommenders = null; try { customUserRecommenders = mahoutHelper.customUserRecommender(userIds, datasetFile); } catch (IOException | TasteException e) { e.printStackTrace(); } return gson.toJson(customUserRecommenders); } }
      
      





そして、これが「同じ」クラスです。



 package com.api.util; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.mahout.cf.taste.common.TasteException; import org.apache.mahout.cf.taste.impl.model.file.FileDataModel; import org.apache.mahout.cf.taste.impl.neighborhood.ThresholdUserNeighborhood; import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender; import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity; import org.apache.mahout.cf.taste.model.DataModel; import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood; import org.apache.mahout.cf.taste.recommender.UserBasedRecommender; import org.apache.mahout.cf.taste.similarity.UserSimilarity; import com.api.model.CustomUserRecommender; public class MahoutHelper { /** * @param List<Integer> userIds * @param String datasetFile * @return List<CustomUserRecommender> * @throws IOException * @throws TasteException */ public List<CustomUserRecommender> customUserRecommender(List<Integer> userIds, String datasetFile) throws IOException, TasteException { List<CustomUserRecommender> customUserRecommenders = new ArrayList<CustomUserRecommender>(); DataModel datamodel = new FileDataModel(new File(datasetFile)); UserSimilarity usersimilarity = new PearsonCorrelationSimilarity(datamodel); UserNeighborhood userneighborhood = new ThresholdUserNeighborhood(0.1, usersimilarity, datamodel); UserBasedRecommender recommender = new GenericUserBasedRecommender(datamodel, userneighborhood, usersimilarity); for (Integer userId : userIds) { customUserRecommenders.add(new CustomUserRecommender(userId, recommender.recommend(userId, 10))); } return customUserRecommenders; } }
      
      





実際のプロジェクトでは、Mahoutライブラリが既存のシステムに追加されました(ターンキーソリューション)。 既に述べたように、データ転送の方法としてAPIが選択されました。 実践からわかるように、キーページ(製品カードなど)に推奨事項を追加すると、変換に非常に大きな影響があります。 週に1回など、推奨サイトの個人評価が電子メールで送信されることも少なくありません。



可能であれば、各ページで試して、ユーザーに対する製品の興味と有用性に関するユーザーの調査の小さなフォームを作成します。 少なくとも、2つの文字(「+」と「-」)を作成できます。 二分法による分類は、通常、数値推定によって表されます(違いがより明確になるように、2と10が望ましい)。 人々に評価を与えるように動機付けてみてください-評価が多ければ多いほど、正確な推奨事項を提示しやすくなります。 商品の注文を考慮に入れることができます(一度購入すると、感謝します)。 あらゆる種類の憶測を避けるように注意してください。 一連の実験(A / Bテスト)でデータを常に確認してください。



明らかなことを思い出したくありませんが、ほとんどの人の意見は必ずしも客観的に正しいとは限りません。 たとえば、子供の頃に生じた複合体を心配している25歳の非常に美しい少女がいるかもしれません。 少女を誘惑する方法として、NLPと催眠の有効性を強く信じている人もいます。 優しい老女でも、鮮やかな緑のアルコール溶液で孫に傷を塗ることができますが、ミラミスチンの使用は明らかに合理的です。 リストは延々と続く。 理想的には、品質が悪いと思われる推奨事項の手動フィルタリングを追加するか(他のサイトを評価する場合)、品質管理を厳しくする(サイト上のオブジェクトを評価する場合)必要があります。



All Articles