過去に、
ユーザーベースでの
参考
以下、
Blog に おける 関心事
ECサイトの
* ECサイトの
ECサイトに
おける 商品購入を、 Blog記事への PageView と 考える。
現在ブログ記事への2分間の ユーザの 滞在を コンバージョンと して 計測していますが、 コンバージョンを 商品購入と 考えた ほうが おもしろいかもしれないですが、 実装が 難しくなるので、 一旦、 Blog記事の PageViewを 商品購入と 考えます。 直帰率は
低くはない。 ほどんどの ユーザが 1ページを 閲覧し 離脱する。
そこまで、ユーザ同士で 見ている ページが 重複する ことは ないのかもしれません。
方針
以下の
* ライブラリと
* アルゴリズムが
手順
実施の
* データセットの
python への
取り込みと、 形式変換 Datasetと
して 読み込み アルゴリズムを
幾つか 試す。 - SVDに
よる 学習と 予測、 評価 - KNNBasic に
よる 学習と 予測、 評価 - BaselineOnly に
よる 学習と 予測、 評価
- SVDに
協調フィルタリング、
アイテムベースレコメンドの 実装
データセットの 準備
clientId を
予め カスタムディメンションと して 送付して おく
データセットは、Google Analytics で 取得した データです。
Google Analytics はデフォルト設定では、 どの ユーザーの アクセスなのかを 識別する ことができません。
このため、 Google Analytics の cookie を クライアントID と して、 カスタムディメンションと して 送信しています。
Google Analytics のcookie を カスタムディメンションと して 送付する 方法は、 以下の 記事が 参考に なりました。
2016年の新定番!ユーザーエクスプローラーを もっと 活用する ための 簡単な 方法 | 株式会社プリンシプル Google Analytics Spreadsheet Add-on で、
データを 取得する
google analytics spreadsheet add-on を使って、 Google Analytics から データを 取得します。
上記の使用方法は、 Googleアナリティクスの 分析は スプレッドシートの アドオンで 全自動化しよう が 参考に なりました。
レコメンドに使用する データセットと して、 以下の Metrics、 Dimensions を 指定しています。
また、データの 取得件数ですが、 デフォルトは 1000件までです。 10000件までは 引き上げられるので、 10000件を 設定しています。 - Metrics
- ga:pageviews
- Dimensions
- ga:dimension8
- ga:pagePath
- Metrics
python への 取り込みと、 形式変換
python への
google スプレッドシートからのgspread
を
事前準備
gspread
、oauth2client
を
python3 -m pip install gspread --user python3 -m pip install oauth2client --user python3 -m pip install scikit-surprise --user python3 -m pip install df2gspread --user
スプレッドシートの データを 取得する
以下の
API に
APIのgspread
の
[Python] Google SpreadSheetを
形式変換
各行が
スプレッドシートから
データ取得、 形式変換までを 行う プログラム
以下に
from oauth2client.service_account import ServiceAccountCredentials def download_as_df(): from df2gspread import gspread2df as g2d # key_file 以下の指定方法だと、notebook と同じディレクトリにあるキーファイルを取得しています。 key_file = "spreadsheet_api_key.json" scope = ['https://spreadsheets.google.com/feeds'] credentials = ServiceAccountCredentials.from_json_keyfile_name(key_file, scope) # 1brCpWvk2uofc3MEt-ASb2cuZ-u8Zmx-ICxSTltlbVBQ はスプレッドシートのID なのでそれぞれ取得対象のスプレッドシートで変わります。 df = g2d.download("1brCpWvk2uofc3MEt-ASb2cuZ-u8Zmx-ICxSTltlbVBQ", wks_name="ユーザーの行動レポート 201710", col_names=True, row_names=False, credentials=credentials, start_cell = 'A15') df = df.sort_values(by='ga:dimension8') return df
df = download_as_df() df
ga:dimension8 | ga:pagePath | ga:pageviews | |
---|---|---|---|
0 | 1001125006 | /blog/cent-os-69-に-memcached-をインストールログの設定まで実施する/ | 1 |
1 | 1001125006 | /blog/rundeck-job-をエクスポートする/ | 1 |
2 | 1001401202 | /blog/rundeck-job-をエクスポートする/ | 1 |
3 | 1001564022 | /blog/Several-queries-implemented-with-QueryDsl/ | 1 |
4 | 100158393.2 | /blog/python-markdown-の出力フォーマット-をsublime-markd... | 1 |
5 | 1002298796 | /blog/pycharm-terminal-からpythonスクリプトを実行できるようにする/ | 1 |
6 | 1003971493 | /blog/macos-el-capitan-に-elasticsearch-を-インストー... | 1 |
7 | 1004220920 | /blog/Try-Japanese-translation-of-rule-of-Sona... | 1 |
8 | 1004368328 | /blog/intellij-ideaでpom-の-依存関係をグラフ表示する/ | 1 |
9 | 1004496170 | /blog/postgresql-connection-to-database-failed... | 1 |
10 | 1004962317 | /blog/jackson-orgcodehausjacksonとcomfasterxmlj... | 1 |
11 | 1005044973 | /blog/spring-boot-での-サブコマンドsubcommandsの実装案/ | 1 |
12 | 1005605181 | /blog/pycharm-terminal-からpythonスクリプトを実行できるようにする/ | 1 |
13 | 1005723713 | /blog/TEMPLATE_DEBUG-on-Djnago1.8/ | 1 |
14 | 1005723713 | /blog/django-18からsettingspyのtemplatesが非推奨になって警... | 1 |
15 | 1005988329 | /blog/python-requests-post-リクエスト送信時にheader-を設定する/ | 1 |
16 | 100608157.2 | /blog/djangomezzanine-template内で-google-tag-ma... | 2 |
17 | 1006325468 | /blog/nonencoded-querystring-on-python-requests/ | 1 |
18 | 1006695896 | /blog/jackson-orgcodehausjacksonとcomfasterxmlj... | 1 |
19 | 1006695896 | /blog/javalangruntimeexception-comfasterxmljac... | 1 |
20 | 1006725276 | /blog/Try-Japanese-translation-of-rule-of-Sona... | 1 |
21 | 100810717.2 | /blog/search-resutls-wicket-models/ | 1 |
22 | 1008449136 | /blog/google-モバイルサイト認定を取得してみました/ | 1 |
23 | 100854833.2 | /blog/java-url文字列からクエリストリングを取得/ | 1 |
24 | 100854833.2 | /blog/pep8wraning-do-not-assign-a-lambda-expre... | 1 |
25 | 100865557.2 | /blog/Try-Japanese-translation-of-rule-of-Sona... | 1 |
26 | 1008987097 | /blog/mezzanine_create_sitemap/ | 1 |
27 | 1009170940 | /blog/python-で-markdownファイルをplain-text-に変換する/ | 1 |
28 | 1011239371 | /blog/google-apps-script-でスプレッドシートの列の値を取得する/ | 1 |
29 | 1011359939 | /blog/Verifying-the-vulnerability-of-blogs-bui... | 1 |
... | ... | ... | ... |
4879 | 994573930.2 | /blog/macos-el-capitan-に-elasticsearch-を-インストー... | 1 |
4880 | 994841234.2 | /blog/top-without-b/ | 1 |
4881 | 995360462.2 | /blog/Try-Japanese-translation-of-rule-of-Sona... | 1 |
4882 | 995370005.2 | /blog/google-search-console-の-キーワードの共起ネットワーク図を... | 4 |
4883 | 995797997.2 | /blog/review-musuinabe/ | 1 |
4884 | 995905549.2 | /blog/spring-boot-での-サブコマンドsubcommandsの実装案/ | 3 |
4885 | 995996577.2 | /blog/mezzanineのpagedownにcodehiliteを設定する/ | 1 |
4886 | 996280209.2 | /blog/jackson-orgcodehausjacksonとcomfasterxmlj... | 3 |
4887 | 996299315.2 | /blog/google-spread-sheet-の-複数のシートのデータをスクリプトで統... | 1 |
4888 | 997078995.2 | /blog/sonarqube-owasp-dependency-check-plugin-... | 1 |
4889 | 997425962.2 | /blog/google-apps-script-でスプレッドシートの列の値を取得する/ | 1 |
4890 | 997953452.2 | /blog/spring-boot-での-サブコマンドsubcommandsの実装案/ | 1 |
4891 | 998219780.2 | /blog/usage-of-upsert-java-mongodb-driver/ | 1 |
4892 | 998284256.2 | /blog/google-analytics-v4-api-java-pageview-pe... | 3 |
4893 | 998433927.2 | /blog/Try-static-analysis-of-Python-using-Sona... | 1 |
4894 | 998528418.2 | /blog/java-url文字列からクエリストリングを取得/ | 1 |
4895 | 998630797.2 | /blog/rundeck-job-をエクスポートする/ | 2 |
4896 | 998803816.2 | /blog/pmd-rulesetの一覧java/ | 1 |
4897 | 998803816.2 | /blog/sonarqube-squids106-標準出力はログ出力に使用すべきではありません/ | 1 |
4898 | 998803816.2 | /blog/sonarqube-web-api-を-python-から実行する/ | 1 |
4899 | 99885062.15 | /blog/Several-queries-implemented-with-QueryDsl/ | 1 |
4900 | 998887381.2 | /blog/get-single-result-on-django-model-filter/ | 1 |
4901 | 999543280.2 | /blog/typeerror-builtin_function_or_method-obj... | 1 |
4902 | 999606506.2 | /blog/jackson-orgcodehausjacksonとcomfasterxmlj... | 5 |
4903 | 999606506.2 | /blog/javalangruntimeexception-comfasterxmljac... | 2 |
4904 | 999899749.2 | /blog/mac-os-siera-に-superset-をインストールする/ | 1 |
4905 | amp-golB4KRZeIM8NHKnbMPeWQ | / | 6 |
4906 | amp-golB4KRZeIM8NHKnbMPeWQ | /?page=2 | 1 |
4907 | amp-golB4KRZeIM8NHKnbMPeWQ | /blog/web-サイトの診断ツール-sonar-のcliを使ってみる/ | 1 |
4908 | amp-golB4KRZeIM8NHKnbMPeWQ | /ja/blog/sitespeedio-coach-の-chrome-plugin-の使い方/ | 1 |
4909 rows × 3 columns
Datasetと して 読み込み
変換した
from surprise import Reader, Dataset reader = Reader(line_format='user item rating', sep=' ') dataset = Dataset.load_from_df(df, reader=reader)
アルゴリズムを 幾つか 試す
Surprise · A Python scikit for recommender systems. には、
* BaselineOnly に
SVDに よる 学習と 予測、 評価
学習と 予測
from surprise import SVD model = SVD() # 全てのデータを使って学習 trainset = dataset.build_full_trainset() model.train(trainset) # 評価値を予測する prediction = model.predict(uid="1001125006", iid="/blog/cent-os-69-に-memcached-をインストールログの設定まで実施する/") print('Predicted rating(User: {0}, Item: {1}): {2:.2f}' .format(prediction.uid, prediction.iid, prediction.est))
Computing the msd similarity matrix... Done computing similarity matrix. Predicted rating(User: 1001125006, Item: /blog/cent-os-69-に-memcached-をインストールログの設定まで実施する/): 1.20
評価
予測のRMSE
、MAE
をRMSE
、MAE
の
from surprise import evaluate # 学習データとテストデータを4分割 dataset.split(n_folds=4) # 平方平均二乗誤差と平均絶対誤差の算出 result = evaluate(model, dataset, measures=['RMSE', 'MAE']) result
Evaluating RMSE, MAE of algorithm SVD. ------------ Fold 1 RMSE: 1.4291 MAE: 0.5485 ------------ Fold 2 RMSE: 1.2848 MAE: 0.5540 ------------ Fold 3 RMSE: 0.8470 MAE: 0.5086 ------------ Fold 4 RMSE: 1.1087 MAE: 0.5223 ------------ ------------ Mean RMSE: 1.1674 Mean MAE : 0.5333 ------------ ------------ CaseInsensitiveDefaultDict(list, {'mae': [0.54846225701436846, 0.55402829594829694, 0.50855895245752036, 0.52225277635798917], 'rmse': [1.4290612954440325, 1.2848327554774843, 0.84697789666245271, 1.1086690225537881]})
あまり、
よくかわって
KNNBasic に よる 学習と 予測、 評価
SVD
> KNNBasic
に
from surprise import KNNBasic model = KNNBasic() # 全てのデータを使って学習 trainset = dataset.build_full_trainset() model.train(trainset) # 評価値を予測する prediction = model.predict(uid="1001125006", iid="/blog/cent-os-69-に-memcached-をインストールログの設定まで実施する/") print('Predicted rating(User: {0}, Item: {1}): {2:.2f}' .format(prediction.uid, prediction.iid, prediction.est))
Computing the msd similarity matrix... Done computing similarity matrix. Computing the msd similarity matrix... Done computing similarity matrix. Predicted rating(User: 1001125006, Item: /blog/cent-os-69-に-memcached-をインストールログの設定まで実施する/): 1.00
評価
from surprise import evaluate # 学習データとテストデータを4分割 dataset.split(n_folds=4) # 平方平均二乗誤差と平均絶対誤差の算出 result = evaluate(model, dataset, measures=['RMSE', 'MAE']) result
Evaluating RMSE, MAE of algorithm KNNBasic. ------------ Fold 1 Computing the msd similarity matrix... Done computing similarity matrix. RMSE: 1.1240 MAE: 0.5434 ------------ Fold 2 Computing the msd similarity matrix... Done computing similarity matrix. RMSE: 1.4066 MAE: 0.5866 ------------ Fold 3 Computing the msd similarity matrix... Done computing similarity matrix. RMSE: 0.9164 MAE: 0.5322 ------------ Fold 4 Computing the msd similarity matrix... Done computing similarity matrix. RMSE: 1.2546 MAE: 0.5644 ------------ ------------ Mean RMSE: 1.1754 Mean MAE : 0.5566 ------------ ------------ CaseInsensitiveDefaultDict(list, {'mae': [0.5433624807357964, 0.58657066818605086, 0.53216262890423915, 0.56439259979812473], 'rmse': [1.1239719739149885, 1.4066485670168258, 0.91640801231542368, 1.2545908278867675]})
SVD
と
BaselineOnly に よる 学習と 予測、 評価
学習と 予測
from surprise import BaselineOnly model = BaselineOnly() # 全てのデータを使って学習 trainset = dataset.build_full_trainset() model.train(trainset) # 評価値を予測する prediction = model.predict(uid="1001125006", iid="/blog/cent-os-69-に-memcached-をインストールログの設定まで実施する/") print('Predicted rating(User: {0}, Item: {1}): {2:.2f}' .format(prediction.uid, prediction.iid, prediction.est))
Estimating biases using als... Computing the msd similarity matrix... Done computing similarity matrix. Predicted rating(User: 1001125006, Item: /blog/cent-os-69-に-memcached-をインストールログの設定まで実施する/): 1.25
評価
from surprise import evaluate # 学習データとテストデータを4分割 dataset.split(n_folds=4) # 平方平均二乗誤差と平均絶対誤差の算出 result = evaluate(model, dataset, measures=['RMSE', 'MAE']) result
Evaluating RMSE, MAE of algorithm BaselineOnly. ------------ Fold 1 Estimating biases using als... RMSE: 1.2686 MAE: 0.5409 ------------ Fold 2 Estimating biases using als... RMSE: 1.3285 MAE: 0.5450 ------------ Fold 3 Estimating biases using als... RMSE: 1.2432 MAE: 0.5573 ------------ Fold 4 Estimating biases using als... RMSE: 0.8520 MAE: 0.5008 ------------ ------------ Mean RMSE: 1.1731 Mean MAE : 0.5360 ------------ ------------ CaseInsensitiveDefaultDict(list, {'mae': [0.54090417491621035, 0.54504647468273115, 0.55726726538797222, 0.50077004102283584], 'rmse': [1.2685778535486407, 1.3284861599445619, 1.2431791543957929, 0.85204905872498649]})
協調フィルタリング、 アイテムベースレコメンドの 実装
ユーザベースよりも、
ユーザー間ではなく、sim_option
に
sim_options = {'name': 'cosine', 'user_based': False # compute similarities between items } algo = KNNBasic(sim_options=sim_options)
SVD
はKNNBasic
を
また、
def create_recommended_data(trainset, similarities): """ データ加工用のfunction PageURL をキー、評価の降順にソートされた Taple のリストをValue として持つ辞書 を作成する """ results = {} for index1, elems in enumerate(similarities): # to_raw_iid で、similarities の配列のインデックスから、item id を取得できる raw_id1 = trainset.to_raw_iid(index1) # blog 記事は url に blog を含むのでそれ以外は除外する if "/blog/" not in raw_id1: continue data = {} for index2, elem in enumerate(elems): # index値が同じデータは、同一記事なので、除外 if index1 == index2: continue raw_id2 = trainset.to_raw_iid(index2) # blog 記事は url に blog を含むのでそれ以外は除外する if "/blog/" not in raw_id2: continue # 評価が0.5以下は除外する if elem <= 0.5: continue data.update({raw_id2 : elem}) results.update({raw_id1 : sorted(data.items(), key=lambda x: -x[1])}) return results; from surprise import KNNBasic sim_options = {'name': 'cosine', 'user_based': False # compute similarities between items } model = KNNBasic(k=5, min_k=1, sim_options=sim_options) # 全てのデータを使って学習 trainset = dataset.build_full_trainset() model.train(trainset) # ITEM間の類似度を計算 similarities = model.compute_similarities() recommends_data = create_recommended_data(trainset, similarities)
Computing the cosine similarity matrix... Done computing similarity matrix. Computing the cosine similarity matrix... Done computing similarity matrix.
recommends_data.get("/blog/usage-of-django-compress-on-mezzanine/")
[('/blog/google-モバイルサイト認定を取得してみました/', 1.0), ('/blog/django-migrate-コマンドでinsert-文を実行する/', 1.0), ('/blog/add-configuration-on-django-compressor/', 1.0)]
recommends_data.get("/blog/404_errorpage_configration_on_wicket_dropwizard/")
[('/blog/search-resutls-wicket-models/', 1.0), ('/blog/control-error-message-by-component-on-wicket/', 1.0), ('/blog/Block-multiple-requests-of-Wicket-Ajax/', 1.0), ('/blog/a-child-with-id-already-exists-でダメージを受ける/', 1.0), ('/blog/delete-version-number-from-url-on-wicket/', 1.0), ('/blog/search-resutls-wicket-forms/', 1.0), ('/blog/apache-wicketでrestapiを使う/', 1.0), ('/blog/rest-api-on-wicket7.3.0/', 1.0), ('/blog/wicket-about-wicketheader-items/', 1.0), ('/blog/using-resource-on-wicket-application/', 1.0), ('/blog/Wicket-AjaxButton-Controls-behavior-when-an-error-occurs-onSubmit-method/', 1.0), ('/blog/wicket-が-使用するjquery-の-version-を-切り替える/', 1.0), ('/blog/wicket-stateless-な-pagenavigator-を作る/', 1.0), ('/blog/wicket-条件でhtml等のリソースファイルの切り替えをする/', 1.0), ('/blog/usage-of-image-class-in-wicket/', 1.0), ('/blog/error-handling-on-wicket/', 0.94868329805051377)]
ページURL で
感覚的には、
あ、
まとめ、 今後実施したいこと
Google Analytics の
以下まとめと、
まとめ
Surprise · A Python scikit for recommender systems. を
使って、 アイテムベースの 協調フィルタリングの 実装イメージを 作る ことができた。 アイテムベースの
レコメンドは、 一部の アルゴリズムで 実施できる。 sim_options
のuser_based
をFalse に すると アイテムベースの レコメンドが 可能。 スプレッッドシートの
データを Pandas の DataFrame に 変換、 DataFrame から レコメンドデータを 作成できる。 この 組み合わせは、 中々実装しやすい。
今後実施したいこと
Batch処理と
して、 ブログへ 組み込む。 ユーザーベースの
協調フィルタリングの 仮実装を してみる。
以上です。
コメント