YouTube動画視聴回数予測

YouTuberとしておさえるべきポイントとは?

賞金: 100,000 参加ユーザー数: 596 4年弱前に終了
コンペティション概要

本コンペでは、YouTube APIとして公開されているメタデータを用いて、
動画の視聴回数予測にチャレンジしていただきます。


背景課題・目的

YouTubeは、2019年広告売上高150億ドル、月間アクセス 20億人に達するほど、
世界で最も普及した動画マーケティングツールです。

投稿動画の視聴回数を増やすには、動画コンテンツの質向上、SEO対策が重要といわれ、
日々多くのメディアでノウハウが紹介されております。

しかしながら、各取組みによりどれだけ視聴回数を伸ばすことができるのか、
定量データに基づいたノウハウ紹介は少なく、実際のところ何が効果的であるかはよく分かっておりません。

そこで本コンペティションでは、YouTube APIとして公開されているメタデータをインプットに、
視聴回数を予測するアルゴリズムを開発していただきます。

メタデータの中には、
動画コンテンツの質的指標となるlike/dislike・コメント数や、
SEOとして重要とされているタイトル名・説明文・タグ・投稿時間、といった情報が含まれております。

これら情報を分析することで、投稿動画の視聴回数を伸ばし、しいてはYouTuberとして活躍するためには、
どのような指標に心掛ける必要があるか、知見を可視化し、共有できればと思います。

賞金

1位:100,000円

ダウンロード

データをダウンロードするにはログインまたはユーザー登録して下さい

概要

コンペに使用するデータは3つに分けられます。

  • 訓練データ(train_data.csv)
  • テストデータ(test_data.csv)

訓練データセットとテストデータセットにはそれぞれ19720レコードと29582レコードのサンプルが含まれています。
訓練データは、動画と再生回数の値が各レコードごとに記載されています。機械学習モデルの構築に使用してください。

テストデータセットでは、再生回数のデータは存在しません。テストデータセットを使用して、どの程度新しいデータに対してモデルが適合しているかを確認し、各動画の再生回数を予測してください。

また、提出ファイルの例に関しては、評価方法のタブを参照してください。

データの形式

データセットのカラムは以下の通りになっています。

カラム名 説明
video_id 動画ごとに割り振られる一意なid
title 動画のタイトル
publishedAt 動画の投稿時間
channelId 動画を投稿したチャンネルのid
channelTitle チャンネルのタイトル
categoryId 動画カテゴリのid
collection_date データレコードの収集日
tags 動画に割り当てられたタグ`
likes 高評価の数
dislikes 低評価の数
comment_count コメント数
thumbnail_link 動画のサムネへのリンク
comments_disabled コメントが許可されない動画であるか? Trueの場合にはcomment_countは0となる
ratings_disabled 高評価と低評価が許可されない動画であるか? Trueの場合にはlikesdislikesは0となる
description 動画の説明文
y 視聴回数
目標

目標は、テストデータセットの動画の情報にもとづき、動画の視聴回数を予測することです。y(視聴回数)を実数値で予測してくだい。

評価指標
  • モデルの予測性能は評価関数RMSLE(Root Mean Squared Log Error)で評価されます。
  (RMSLEの評価値)

$$
\sqrt{\frac{\sum_i( \log(y_{obs,i} + 1) - \log(y_{pred,i} + 1))^2}{n}}
$$

  • 評価値は0以上の値をとり、精度が高いほど小さな値となります。

提出ファイルの形式

回答用のsubmission.csvを用意する(エントリーとヘッダー行を含む) 。
提出されたファイルに余分な行や列(idとy以外)が含まれていた場合はエラーとなります。

提出ファイルは以下の列のみを必ず含んでください:

  • id(テストデータセットと同じ順序)
  • y(予測された視聴回数)

以下は提出ファイルの例です。
id,y
1, 69000000
2, 90000000
3, 4500000
4, 7000000

コンペティションルール

■Open Review Competition

本コンペでは、開催期間終了後 賞金対象者のコードを公開し、ユーザーの皆様にチーティング有無をレビューしていただき順位確定させる、オープンレビュー方式のコンペティションを行います。


賞金対象ユーザー

コンペ終了後1週間以内:
トピックにて、学習過程の分かるコードの公開をお願いいたします。 (簡易解説までつけていただけると助かります。)

コード公開後3週間:
レビュアー(ユーザー)より、チーティングの疑いに関するコメントがある場合は、ご回答をお願いいたします。
※チーティングとは無関係のコメント(ノウハウに関する質疑 等)についてもご回答いただけると幸いですが、順位確定の判断材料とは致しません。


レビュアー(ユーザーの皆様)
コード公開後3週間: 公開コードを確認いただき、チーティングが疑われる場合は、トピックを通して質疑の投稿をお願いいたします。

レビュアーからの質疑と、回答状況をふまえて、最終的に運営側で順位確定を判断します。

■順位決定ロジック
  1. コンペ期間中はPublicリーダーボード(以下LB)により暫定評価を、最終結果についてはPrivate LBにより評価します。
    ※ Private LBはコンペ終了と同時に表示されます
  2. スコアが同値の場合は、早い日時に提出いただいたユーザーが上位となります。
  3. コンペ終了後であっても、不正が発覚の際は、対象ユーザーは失格となり、全体の順位が繰り上がります。
    順位繰上げにより賞金対象者となられた場合は、繰上げ日より一週間以内に、トピックにてコードを公開いただき、「Open Review Competition」と同様のフローにて順位を確定させていただきます。
■タイムライン

開始日 2020/04/28 0:00 JST
終了日 2020/6/29 0:00 JST

エントリー締め切り なし

■システム利用
  • 参加者ごとに1つのアカウントでご参加ください
  • チーム参加の場合は、最大5名までエントリー可能です
  • 1日あたり、最大5回までの提出が可能です
■禁止事項
  • ユーザー間での情報共有
    コンペティションに関連するコード・データを、チーム外のユーザーと共有することはできません。全参加者が利用できる場合に限り、共有可能です。
  • 外部データの使用
    本コンペティションの基本情報/データから取得できるデータのみを用いてチャレンジして下さい。コンペ外データを用いて学習されたモデルの使用も禁止とします。
    ただし、トピック上で、運営より承認された外部データのみ、活用を許可いたします。承認プロセスについては、「外部データの活用申請について」をご確認ください。

※コンペ期間中であっても、不正が疑われる場合は、運営より確認のためメール連絡させていただくことがございます。一週間以内にご回答いただけない場合も、不正と判断させていただきます。

■外部データの使用申請について

原則外部データ使用は禁止としておりますが、正解データの入手につながらない場合は、下記承認プロセスにより使用できることといたします

  1. トピックを作成し、取得元情報とデータ(格納先URLも可)を添付
  2. 運営にて判断・承認

注)ただし、コンペ終了まで10日をきってからの申請は禁止とします

■運営からのお願い

公平性の担保、チーティング等の不正防止のため、予告なくルールの追加・変更を行う場合がございます。
ご不便をおかけすることもあるかと思いますが、サービス向上のためご了承ください。


「YouTube動画視聴回数予測」コンペティション参加規約

コンペティションへの参加に際しては、ProbSpace利用規約(以下、「利用規約」といいます。)に加え、本ProbSpace参加規約(以下「本規約」といいます。)に同意いただく必要があります。利用規約にて定義された用語は、本規約においても同様の意味で用いられるものとします。

第1条(適用)

  1. 参加者(第2条に定義します。)は、コンペティションに参加した時点で、本規約、利用規約、その他ご同意いただいた規約のすべて、及びコンペティションサイトに掲載されているコンペティションに関するルールの一切に同意したものとみなされます。
  2. 本規約、利用規約、その他ご同意いただいた規約のすべて、及びコンペティションサイトに掲載されているコンペティションに関するルールは、コンペティションの終了後も参加者に適用されます。

第2条(定義)
本規約において次の各用語の定義は、それぞれ以下に定めるとおりとします。

  1. 「本コンペ」とは、当社ウェブサイト上で開催されるAI開発又はデータ分析等に関するコンペティションのうち、本規約に紐づく特定のコンペティションを意味します。
  2. 「主催者」とは、当社またはユーザーのうち、本コンペを主催する者を意味します。また、本コンペが、当社の顧客又は提携先の企業、学校その他の団体等がスポンサードするものである場合は、当該団体等も主催者の定義に含まれます。
  3. 「参加者」とは、ユーザーのうち、主催者側以外の立場で本コンペに参加する方を意味します。
  4. 「成果物」とは、本コンペにおいて参加者により開発される学習済みモデル、そのソースコード及び乱数シード等の設定値を意味します。
  5. 「入賞者」とは、当社より本コンペに入賞した旨の通知を受けた参加者を意味します。
  6. 「知的財産権」とは、著作権(著作権法第27条及び第28条に定める権利を含みます。)、特許権、実用新案権、商標権、意匠権、その他のノウハウ及び技術情報等の知的財産権(それらの権利を取得し、又はそれらの権利につき登録等を出願する権利を含みます。)を意味します。

第3条(権利の帰属)

  1. 本コンペで発生した成果物に関する知的財産権は、参加者に帰属します。

第4条(入賞者の義務)

  1. 入賞者は、本コンペで公開した成果物を、MITライセンスを適用し、商用利用の許諾条項及び著作権人格権の包括的不行使条項をライセンス条項に付与した形式で、オープンソースソフトウェアとして公開する義務を負うものとします。その前提として、入賞者は、成果物について本項に基づく方法でオープンソース化する権利を有していることを当社に対して表明保証するものとします。
    ※第三者が、授業・研修・セミナー等で活用できるようにするための規約となります。ご理解のほどよろしくお願いいたします。
  2. 当社は、以下の3点の確認が完了した時点で、本コンペの賞金または商品を、入賞者に対して授与するものとします。
    1. 入賞者が、前項に基づいて成果物のオープンソース化を実施したこと
    2. 入賞者が、本規約、利用規約、その他ご同意いただいた規約のすべて、及びコンペティションサイトに掲載されているコンペティションに関するルールの一切に違反していないこと
    3. 当社が定める方法による本人確認
  3. 当社は、入賞者が第1項に基づいてオープンソース化した成果物を、自由に商用利用することができます。

第5条(禁止事項)

  1. 参加者は、本コンペにおいて、以下の各号のいずれかに該当する行為を行ってはならないものとします。
    1. クラッキングやチート行為、なりすまし、盗用等の不正行為
    2. 第三者の知的財産権その他の権利を侵害する内容ないし態様で、参加者公開事項を公開する行為
    3. 主催者(当社以外の者に限ります。)に対する直接連絡、相談、依頼、勧誘、勧誘対応等の活動(但し、当社を介して当社が認めた方法により行うものは除きます。)
    4. 本コンペにおいて、当該コンペと直接関係のない成果物等を提出すること
    5. 本規約における参加者としての地位又は参加者としての権利義務について、譲渡、移転、担保設定、その他の処分をすること
    6. その他、本規約、参加ルール及び利用規約に違反する行為
  2. 参加者が前項に規定する禁止行為を行ったと当社が認める場合、当社は、当該参加者に事前に通知することなく、当該参加者の本コンペにおける失格処分、当社サービスの全部又は一部の利用停止、ユーザー登録の抹消、その他当社が必要と判断した措置をとることができるものとします。

第6条(本コンペの変更、中断、終了等)

  1. 当社は、参加者に事前の通知をすることなく本規約に基づく本コンペの開催内容の変更、本コンペの一時的な中断又は終了を行うことができます。
  2. 当社は、本条に基づき当社が行った措置により生じた結果及び損害について、一切の責任を負わないものとします。

第7条(損害賠償)

  1. 参加者は、本コンペに関連して、自らの責に帰すべき事由により、当社、主催者その他の第三者に損害を与えた場合には、その一切の損害(逸失利益、弁護士費用を含みます。)を賠償するものとします。
  2. 参加者が本規約の規定に違反したことにより主催者(当社を除きます。)その他の第三者が当社に対して何らかの訴え、異議、請求等がなされた場合において、当社から処理の要請がなされたときは、参加者は自己の責任と費用負担において、当社に代わって当該第三者との紛争を処理するとともに、当社がかかる訴え、異議、請求等により被った一切の損害(逸失利益、弁護士費用を含みます。)を賠償するものとします。

第8条(本規約の変更)
当社は、必要と判断した場合には、参加者に対して事前に通知する(本コンペにかかる当社ウェブサイト上での告知その他当社が適当と認める方法を含みます。)ことにより、いつでも本規約を変更することができるものとします。なお、変更内容の通知後、参加者が当社の定める期間内に本コンペへの参加を取り消す手続をとらなかった場合には、当該参加者は変更後の規約に同意したものとみなされます。当社は、本規約の変更により参加者に生じたすべての損害について一切の責任を負いません。

第9条(その他)
本契約の準拠法は日本法とし、本契約に起因し又は関連する一切の紛争については、当社の本店所在地を管轄する裁判所を第一審の専属的合意管轄裁判所とします。

(制定)2020年4月1日

チームで参加できますか?

可能です。チームページから作成いただけます。

どこでアカウントをつくればいいですか?

こちらから作成いただけます。

コンペティション参加にはアカウント登録が必要となりますのでご注意ください。

コードを提出するにあたって Seed を固定する必要はありますか?

Seed を固定することが推奨です。
ただし、Seed を固定しなくても提出用コードとしては認めています。

概要

このチュートリアルでは、動画の鑑賞情報データに対して

  • データの読み込み
  • データの確認と前処理
  • Scikit-Learnを用いて線形回帰モデルの作成,学習
  • 誤差の大きいデータを確認

を行います。

#環境確認
!python3 --version
print(pd.__version__)
print(np.__version__)
print(sklearn.__version__)
import matplotlib
print(matplotlib.__version__)
Python 3.7.7
0.25.3
1.17.4
0.22
3.1.1

環境

  • python 3.7.5
  • pandas 0.25.3
  • sklearn 0.22
  • numpy 1.17.4
  • matplotlib 3.1.1

データのロード

まずはデータを読み込んで見ましょう。csvデータの読み込みは複数のやり方がありえますが、pandasread_csv関数はその中でも機能が豊富で、扱いやすいためこれを使います。
これを使うと、csvデータを読み込み、pandas.DataFrameにして返してくれます。

#データの読み込みと前処理
import pandas as pd
train_data = pd.read_csv("compe_data/train_data.csv", index_col='id')
print(train_data.shape)
(19720, 16)

また、DataFrameには.head()というメソッドが定義されており、これを呼び出すとDataFrameの先頭の数行を確認できます。

train_data.head()

video_id title publishedAt channelId channelTitle categoryId collection_date tags likes dislikes comment_count thumbnail_link comments_disabled ratings_disabled description y
id
1 GDtyztIThRQ [12] BGM Inazuma Eleven 3 - ~ライオコツト ダンジョン~ 2011-01-09T05:50:33.000Z UCQaNYC3dNvH8FqrEyK7hTJw DjangoShiny 20 20.01.02 Inazuma|Eleven|Super|Once|bgm|ost|イナズマイレブン|Kyo... 114 0 7 https://i.ytimg.com/vi/GDtyztIThRQ/default.jpg False False ~ライオコツト ダンジョン~Inazuma Eleven 3 BGM Complete (R... 29229
2 m4H9s3GtTlQ ねごと - メルシールー [Official Music Video] 2012-07-23T03:00:09.000Z UChMWDi-HBm5aS3jyRSaAWUA ねごと Official Channel 10 20.08.02 ねごと|ネゴト|メルシールー|Re:myend|リマインド|Lightdentity|ライデ... 2885 50 111 https://i.ytimg.com/vi/m4H9s3GtTlQ/default.jpg False False http://www.negoto.com/全員平成生まれ、蒼山幸子(Vo&Key)、沙田瑞... 730280
3 z19zYZuLuEU VF3tb 闇よだれvsちび太 (SEGA) 2007-07-26T13:54:09.000Z UCBdcyoZSt5HBLd_n6we-xIg siropai 24 20.14.01 VF3|VF4|VF5|ちび太|闇よだれ|chibita|virtuafighter|seg... 133 17 14 https://i.ytimg.com/vi/z19zYZuLuEU/default.jpg False False Beat-tribe cup finalhttp://ameblo.jp/siropai/ 80667
4 pmcIOsL7s98 free frosty weekend! 2005-05-15T02:38:43.000Z UC7K5am1UAQEsCRhzXpi9i1g Jones4Carrie 22 19.22.12 frosty 287 51 173 https://i.ytimg.com/vi/pmcIOsL7s98/default.jpg False False I look so bad but look at me! 34826
5 ZuQgsTcuM-4 トップ・オブ・ザ・ワールド 2007-09-09T09:52:47.000Z UCTW1um4R-QWa8iIfITGvlZQ Tatsuya Maruyama 10 20.08.01 ギター|guitar|南澤大介|トップオブザワールド|トップ|オブ|ワールド|カーペンターズ... 178 6 17 https://i.ytimg.com/vi/ZuQgsTcuM-4/default.jpg False False ソロギターのしらべより「トップオブザワールド」です。クラシックギターで弾いてます。Offic... 172727

データの確認と前処理

訓練データにおける視聴回数がどのような分布になっているか見てみましょう。各階級ごとの度数をヒストグラムを用いてプロットするのが以下のコードです。

%matplotlib inline
import matplotlib.pyplot as plt
plt.hist(train_data['y'], bins=20, log=True)
(array([1.9624e+04, 5.4000e+01, 1.3000e+01, 8.0000e+00, 6.0000e+00,
        4.0000e+00, 4.0000e+00, 2.0000e+00, 0.0000e+00, 3.0000e+00,
        0.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 0.0000e+00,
        0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 1.0000e+00]),
 array([2.00000000e+00, 1.07128023e+08, 2.14256043e+08, 3.21384064e+08,
        4.28512085e+08, 5.35640106e+08, 6.42768126e+08, 7.49896147e+08,
        8.57024168e+08, 9.64152188e+08, 1.07128021e+09, 1.17840823e+09,
        1.28553625e+09, 1.39266427e+09, 1.49979229e+09, 1.60692031e+09,
        1.71404833e+09, 1.82117635e+09, 1.92830437e+09, 2.03543240e+09,
        2.14256042e+09]),
 <a list of 20 Patch objects>)

2fb0a225-3018-460e-905d-743004dc5faa.png

ヒストグラムとしては、グラフエリアの左側に1本だけ棒状グラフが出力されました。

ほぼ全てのレコードの視聴回数が50,000,000未満であり、一部の少数のレコードが50,000,000近い値を取っているために、上記のように表示されました。

極端に他のレコードと値の異なるものは'外れ値'と言われ、回帰モデルに悪影響を及ぼす場合もあります。この'外れ値'をどのように扱うかは場合により異なります。

今回は、99%タイル以上のデータを除外します

import numpy as np
np.where(train_data["y"] >= np.percentile(train_data['y'], 99))
(array([   18,   289,   518,   659,   667,   714,   855,  1000,  1040,
         1060,  1215,  1303,  1663,  1730,  1862,  1880,  1958,  2114,
         2251,  2499,  2579,  2590,  2616,  2728,  2855,  2910,  3030,
         3053,  3247,  3543,  3555,  3596,  3623,  3638,  3640,  3667,
         3680,  3690,  3852,  3877,  4045,  4134,  4407,  4485,  4507,
         4554,  4600,  4686,  4713,  4836,  4854,  5180,  5231,  5232,
         5240,  5321,  5479,  5587,  5709,  5894,  6093,  6101,  6162,
         6269,  6287,  6462,  6547,  6648,  6688,  6782,  6793,  6847,
         6881,  6913,  7284,  7308,  7732,  7779,  7782,  7784,  7850,
         7967,  7980,  8029,  8087,  8138,  8362,  8394,  8591,  8674,
         8688,  8724,  8943,  8988,  9127,  9270,  9539,  9626,  9892,
         9984, 10232, 10251, 10287, 10441, 10448, 10485, 10500, 10553,
        10588, 10601, 10684, 10695, 10699, 10857, 11333, 11417, 11445,
        11792, 11854, 11893, 12003, 12069, 12128, 12172, 12216, 12218,
        12371, 12672, 12681, 12891, 12894, 13023, 13170, 13196, 13272,
        13337, 13377, 13401, 13422, 13434, 13469, 13880, 13897, 14044,
        14078, 14271, 14379, 14474, 14538, 15618, 15644, 15806, 15819,
        15868, 15966, 16100, 16178, 16197, 16311, 16366, 16372, 16447,
        16565, 16578, 16579, 16587, 16632, 16701, 16741, 16886, 16915,
        17085, 17220, 17247, 17293, 17401, 17490, 17545, 17558, 17587,
        17773, 17820, 17882, 18026, 18119, 18382, 18745, 18781, 18926,
        18968, 19070, 19109, 19170, 19288, 19359, 19589, 19616, 19637]),)
#外れ値除外
train_data = train_data[train_data["y"] < np.percentile(train_data['y'], 99)]
train_data.reset_index(drop=True)
plt.hist(train_data['y'], bins=20, log=True)
(array([1.684e+04, 1.220e+03, 5.270e+02, 2.360e+02, 1.710e+02, 1.070e+02,
        8.000e+01, 6.300e+01, 6.100e+01, 3.600e+01, 2.700e+01, 3.800e+01,
        2.900e+01, 1.200e+01, 1.900e+01, 1.100e+01, 1.600e+01, 4.000e+00,
        1.100e+01, 1.400e+01]),
 array([2.0000000e+00, 2.8219590e+06, 5.6439160e+06, 8.4658730e+06,
        1.1287830e+07, 1.4109787e+07, 1.6931744e+07, 1.9753701e+07,
        2.2575658e+07, 2.5397615e+07, 2.8219572e+07, 3.1041529e+07,
        3.3863486e+07, 3.6685443e+07, 3.9507400e+07, 4.2329357e+07,
        4.5151314e+07, 4.7973271e+07, 5.0795228e+07, 5.3617185e+07,
        5.6439142e+07]),
 <a list of 20 Patch objects>)

4063c8d0-eedd-498d-a0a0-89ddf12b274c.png

続いて、下記のコードを使用して使用するデータ全体の概観を確認します。
各項目について欠損値以外('non-null')の値がどの程度含まれているか把握できます。また、今回のデータでは欠損値が含まれていないようです.

train_data.isnull().sum(axis=0)
video_id               0
title                  0
publishedAt            0
channelId              0
channelTitle           0
categoryId             0
collection_date        0
tags                   1
likes                  0
dislikes               0
comment_count          0
thumbnail_link         0
comments_disabled      0
ratings_disabled       0
description          312
y                      0
dtype: int64

データの形式についてもここで見てみましょう。

train_data.dtypes
video_id             object
title                object
publishedAt          object
channelId            object
channelTitle         object
categoryId            int64
collection_date      object
tags                 object
likes                 int64
dislikes              int64
comment_count         int64
thumbnail_link       object
comments_disabled      bool
ratings_disabled       bool
description          object
y                     int64
dtype: object

データの形式を見ると、数値についてはint64型になっていることが確認できます。投稿日は視聴回数に関係がありそうな変数ですが、 object型になっています. 投稿日をint64型に変換します。

train_data['publishedAt'] =  pd.to_datetime(train_data['publishedAt']).map(pd.Timestamp.to_julian_date)

続いて、数値データの正規化を行い、平均0、標準偏差1になるように変換を行います。

import numpy as np

l = ['likes', 'dislikes', 'comment_count', 'publishedAt']
for name in l:
    mean = np.nanmean(train_data[name], axis=0)
    std = np.nanstd(train_data[name], axis=0)
    train_data[name] = (train_data[name] - mean)/std
train_data.describe()

publishedAt categoryId likes dislikes comment_count y
count 1.952200e+04 19522.000000 1.952200e+04 1.952200e+04 1.952200e+04 1.952200e+04
mean 7.645996e-14 15.910306 1.455881e-17 1.674263e-17 1.528675e-17 1.803871e+06
std 1.000026e+00 8.684912 1.000026e+00 1.000026e+00 1.000026e+00 4.918672e+06
min -1.605412e+00 1.000000 -2.900779e-01 -2.214770e-01 -2.411631e-01 2.000000e+00
25% -9.378234e-01 10.000000 -2.872672e-01 -2.195250e-01 -2.398220e-01 6.824225e+04
50% -2.145848e-02 17.000000 -2.680869e-01 -2.053736e-01 -2.254058e-01 3.058175e+05
75% 9.248891e-01 24.000000 -1.403091e-01 -1.302243e-01 -1.342151e-01 1.255392e+06
max 1.754030e+00 44.000000 2.605187e+01 3.817687e+01 3.574564e+01 5.643914e+07

.describe()を使用して、基本統計量を確認できます。平均0、標準偏差1に正規化できていることを確認します

予測モデルの作成,学習

モデルに投入する目的変数(y),説明変数(X)を作成します

#使用する説明変数・目的変数を代入
model_input = train_data[['categoryId', 'likes', 'dislikes', 'comment_count', 'publishedAt', 'y']]

#説明変数・目的変数についてnull値を含むレコードを除外
model_input = model_input.dropna(how='any', axis=0) 

# 目的変数と説明変数を代入
X = model_input[['categoryId', 'likes', 'dislikes', 'comment_count', 'publishedAt']]
y = np.log(model_input['y'])

このデータにはいくつかのカテゴリカルデータが含まれているので今のままでは機械学習モデルで扱いづらいです。そこで、カテゴリカルデータに対してget_dummies関数を使いone-hot表現に変換することができます。

今回は、categoryIdのみone-hot表現に変換しました。drop_first=Trueを使用することで、多重共線性を弱めることができます。
categoryIdは数値データのためcolumnsで明示的に指定してone-hot表現に変換する必要があります。

# one hot表現へ変換
X = pd.get_dummies(X, columns=['categoryId'], drop_first=True)

いよいよ予測モデルの作成に入ります。
model.fit('説明変数','目的変数')と記述することでモデルの学習が可能となります。目的変数を説明変数の組み合わせで説明可能な回帰モデルを作成できます。

#scikit-learnライブラリをimport
import sklearn
from sklearn.linear_model import LinearRegression as LR

#線形回帰モデルのインスタンス化
model = LR()

#予測モデルの作成
model.fit(X, y)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

作成したモデルの係数と切片を見てみましょう。
model.coef_で係数の一覧、.intercept_で切片を見ることができます。

#回帰モデルの評価

#切片
model.intercept_
12.623831394358584
#各説明変数の係数対応表
coeff = pd.DataFrame(X.columns)
coeff.columns = ['説明変数']
coeff['係数推定'] = model.coef_
print(coeff)
             説明変数      係数推定
0           likes  0.579694
1        dislikes  0.363648
2   comment_count  0.051812
3     publishedAt  0.409643
4    categoryId_2 -0.512656
5   categoryId_10  0.370962
6   categoryId_15 -0.924757
7   categoryId_17 -0.400887
8   categoryId_19 -1.671840
9   categoryId_20 -0.051330
10  categoryId_22 -0.647365
11  categoryId_23  0.035074
12  categoryId_24 -0.064332
13  categoryId_25 -0.956726
14  categoryId_26 -0.516795
15  categoryId_27 -0.442606
16  categoryId_28 -1.333004
17  categoryId_29 -1.998885
18  categoryId_30 -6.530386
19  categoryId_44  1.004041

各説明変数の係数を比較することで、どの説明変数がどれほどの影響を与えていたか把握することができます。

各説明変数は、係数の値が正に大きいほど視聴回数に対して正の寄与をしており、反対に負に大きいほど視聴回数に対して負の寄与をしていると言えます。

モデルの評価

テストデータに対する回帰モデルの当てはまりの良さの指標としては'決定係数'などの指標が用いられます。
決定係数の値は、.scoreで確認が可能です。
1に近いほど回帰式で予測された値が実際のデータに当てはまることを表します。

#決定係数
model.score(X,y)
0.28001797307587306

今回作成したモデルの決定係数は0.28程度でした。

また、コンペの評価指標となるRMSLEについても出力が可能です。
今回は、sklearn.metricsに含まれるmean_squared_log_errorメソッドを使用します。
RMSLEは平均化された誤差の値を示す指標であり、0に近いほど見積もられる予測誤差が小さく予測精度が高いことを表します。
モデルを元にした予測値は、model.predict('説明変数')で表します。

# RMSE値の出力
from sklearn.metrics import mean_squared_log_error
y_true = model_input['y']
y_pred = np.exp(model.predict(X))
print(np.sqrt(mean_squared_log_error(y_true, y_pred)))
1.9462134377385298

今回作成したモデルのRMSLEは1.946程度でした。

テストデータに対する出力

最後に、作成したモデルを使用してテストデータでの視聴回数を予測します。

テストデータの説明変数については予め、上記でモデルに学習させたデータの説明変数と同様の前処理をする必要があります。

test_data = pd.read_csv("compe_data/test_data.csv", index_col='id')
test_data.head()

id video_id title publishedAt channelId channelTitle categoryId collection_date tags likes dislikes comment_count thumbnail_link comments_disabled ratings_disabled description
1 xU8UcB6RbLE Frightened Rabbit - The Greys 2007-09-26T11:00:07.000Z UCOQ_j8Qg4-p0lGKBpXYENbg Fatcat Records 10 20.08.01 Fatcat|Records|Frightened|Rabbit|The|Greys 471 38 61 https://i.ytimg.com/vi/xU8UcB6RbLE/default.jpg False False Director: Fraser CampbellDate:2007Taken from F...
2 ENuB3qHCp0s Eダンスアカデミー『EXダンス体操(EXILEのダンス体操)』 2016-05-27T15:00:00.000Z UCip8ve30-AoX2y2OtAAmqFA NHK 24 20.09.02 Eダンス|Eアカ|EXILE|EXダンス体操|ダンス体操|EX体操|エクササイズ、GENER... 6797 666 0 https://i.ytimg.com/vi/ENuB3qHCp0s/default.jpg True False EXILEがダンスを楽しく分かりやすくレッスンする「Eダンスアカデミー」毎週金曜日に放送中!...
3 2eIeMPhXhSQ 『Lover Come Back To Me』 大西由希子×川口千里(Senri Kawag... 2012-12-10T08:24:57.000Z UCZyaEXqC4i3ni68duKOg6JA atossinternational 10 20.03.02 大西由希子|岩永真奈|川口千里|教則|長崎祥子|ジャズ|ユッコミラー|atossintern... 3371 111 212 https://i.ytimg.com/vi/2eIeMPhXhSQ/default.jpg False False 教則DVD『アドリブ・サックス・パーフェクト・マスター』収録の究極のギャルバンによるスタジオ...
4 yXfmnY6QIOc konnkonn雛たち大きくなりました☆ 2012-02-27T05:23:46.000Z UCyeDXVF4epn-ed3c3ckyhOQ cpj54293 15 20.02.02 オカメインコひな 508 16 23 https://i.ytimg.com/vi/yXfmnY6QIOc/default.jpg False False オカメインコのひなたち約生後39日です鳴き声が変わりましたジィージィーの中にピィッ!ピィッ!...
5 dGbHHMKYGkw Anouk - Everything, Live cover by RU kiddin me 2007-11-24T15:18:04.000Z UCnOICsYtfygl--dfI9kclVQ Vinzarelli 10 20.10.01 Anouk|Everything|live|cover|tribute 0 1 0 https://i.ytimg.com/vi/dGbHHMKYGkw/default.jpg False False Anouk tribute band RU kiddin me covers Everyth...
test_data['publishedAt'] =  pd.to_datetime(test_data['publishedAt']).map(pd.Timestamp.to_julian_date)
import numpy as np

l = ['likes', 'dislikes', 'comment_count', 'publishedAt']
for name in l:
    mean = np.nanmean(test_data[name], axis=0)
    std = np.nanstd(test_data[name], axis=0)
    test_data[name] = (test_data[name] - mean)/std
test_data.describe()

publishedAt categoryId likes dislikes comment_count
count 2.958200e+04 29582.000000 2.958200e+04 29582.000000 2.958200e+04
mean -1.052510e-13 15.863667 -1.921554e-18 0.000000 6.725440e-18
std 1.000017e+00 8.670842 1.000017e+00 1.000017 1.000017e+00
min -1.611338e+00 1.000000 -1.391489e-01 -0.066808 -1.563839e-01
25% -9.275596e-01 10.000000 -1.382193e-01 -0.066395 -1.557679e-01
50% -2.663144e-02 17.000000 -1.322320e-01 -0.063919 -1.485304e-01
75% 9.177797e-01 24.000000 -9.205591e-02 -0.050053 -1.052589e-01
max 1.750811e+00 44.000000 6.588549e+01 136.986019 7.579106e+01
#使用する説明変数・目的変数を代入
model_input_test = test_data[['categoryId', 'likes', 'dislikes', 'comment_count', 'publishedAt']]

#説明変数・目的変数についてnull値を含むレコードを除外
model_input_test = model_input_test.dropna(how='any', axis=0) 

# 目的変数と説明変数を代入
X_test = model_input_test
# one hot表現へ変換
X_test = pd.get_dummies(X_test, columns=['categoryId'], drop_first=True)

モデルで使うデータとテストデータのカラム数が違うとエラーが発生するので、モデルの説明変数について事前に工夫が必要です。

今回は、学習データとテストデータで重複する説明変数のみを利用して学習と予測を実施します。

また、下記のコードでは訓練データで学習させた説明変数の中で、テストデータに含まれないについても列名を出力しています。

#訓練データとテストデータで共通する列名のみに、モデルの説明変数を絞り込む
col_list = X.columns.tolist() 
col_test_list = X_test.columns.tolist()
col_joint = col_list + col_test_list
col_dup = [x for x in set(col_joint) if col_joint.count(x) > 1]
#訓練データの説明変数の中で、テストデータと共通しない列名を抽出
col_list_p = [x for x in set(col_joint) if col_list.count(x) == 0]
print(col_list_p)
['categoryId_43']

categoryId_43は、視聴回数に影響を及ぼす可能性がありますが、訓練データには含まれません。そのため、今回の予測ではテストデータからcategoryId_43を除外しました。

その上で訓練データについて再度学習させて、モデルを作成します。

#共通の説明変数のみに学習、テストデータのカラムを統一
X_test =X_test.drop('categoryId_43', axis=1)

X_train_fix = X[col_dup]
X_test_fix = X_test[col_dup]

#予測モデルの作成
model.fit(X_train_fix, y)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

先ほど使用したmodel.predict('説明変数')の'説明変数'にテストデータの値を代入することで、テストデータの予測値を算出することができます。

下記のようにして、提出用のsubmission.csvを出力することが可能です。

#テスト結果の出力
test_predicted = np.exp(model.predict(X_test_fix))
submit_df = pd.DataFrame({'y': test_predicted})
submit_df.index.name = 'id'
submit_df.index += 1
submit_df.to_csv('submission.csv')

誤差の大きなデータの確認

plt.hist(np.log(y_pred+1) - np.log(y_true+1), log=True, bins=20)
(array([3.200e+01, 8.820e+02, 5.119e+03, 7.204e+03, 3.960e+03, 1.487e+03,
        5.660e+02, 1.920e+02, 5.900e+01, 1.400e+01, 3.000e+00, 0.000e+00,
        2.000e+00, 0.000e+00, 0.000e+00, 1.000e+00, 0.000e+00, 0.000e+00,
        0.000e+00, 1.000e+00]),
 array([-5.95858436, -4.32620805, -2.69383174, -1.06145543,  0.57092088,
         2.20329719,  3.8356735 ,  5.46804981,  7.10042612,  8.73280243,
        10.36517874, 11.99755505, 13.62993136, 15.26230767, 16.89468398,
        18.52706029, 20.1594366 , 21.79181291, 23.42418922, 25.05656553,
        26.68894184]),
 <a list of 20 Patch objects>)

fd047710-5968-4ad7-87bd-dbc8fd99a997.png

threshold = 14

以下は誤差の絶対値が14を超えるデータの一覧です。

wrong_idx_lower = np.where(np.log(y_pred+1) - np.log(y_true+1) > threshold)[0]
print(wrong_idx_lower)
[ 3361  4658 10206 16918]
train_data.loc[wrong_idx_lower, :]

video_id title publishedAt channelId channelTitle categoryId collection_date tags likes dislikes comment_count thumbnail_link comments_disabled ratings_disabled description y
id
3361 E1Nyg03z0pQ H.R. GIGER -1.429517 UCT0R2PJweieLDNNJhyNx4HA fuzzedoutmama 1 19.30.12 GIGER -0.250120 -0.206837 -0.143602 https://i.ytimg.com/vi/E1Nyg03z0pQ/default.jpg False False giger 349785
4658 b-griRtKHLU スチャダラパーとEGO-WRAPPIN' ミクロボーイとマクロガール(Official Mu... 1.172165 UCQZDh-m6a31o7rXJPuMiFbA SPACE SHOWER MUSIC 10 20.09.02 スチャダラパー|schadaraparr|Bose|ANI|SHINCO|ラップグループ|今... 0.219213 0.091808 -0.023579 https://i.ytimg.com/vi/b-griRtKHLU/default.jpg False False スチャダラパーとEGO-WRAPPIN’によるコラボ曲「ミクロボーイとマクロガール」のミュー... 2383295
10206 PPJBp5naSf8 [びじゅチューン!] 兵馬俑ウエディング | NHK 1.272681 UCip8ve30-AoX2y2OtAAmqFA NHK 24 20.09.02 井上涼|びじゅチューン!|びじゅチューン|アニメ|美術|兵馬俑ウエディング|兵馬俑|始皇帝|... -0.251909 -0.195614 -0.241163 https://i.ytimg.com/vi/PPJBp5naSf8/default.jpg True False NHKサイトで全作公開中!どーがレージ>>https://www.nhk.or.jp/d-g... 382571
16918 Vw-6spTK3Us flumpool 見つめていたい Music Video 0.673300 UCpKZrypRuFJQ2r5b1Zc88mA flumpool 22 20.08.02 flumpool|見つめていたい -0.224728 -0.212693 -0.223059 https://i.ytimg.com/vi/Vw-6spTK3Us/default.jpg False False The Best 2008-2014「MONUNEMT」2014.5.21Release!!... 233291

視聴回数を過大に評価したデータは4つ。 過小に評価したデータはありませんでした。

「YouTube動画視聴回数予測」コンペティション参加規約

コンペティションへの参加に際しては、ProbSpace利用規約(以下、「利用規約」といいます。)に加え、本ProbSpace参加規約(以下「本規約」といいます。)に同意いただく必要があります。利用規約にて定義された用語は、本規約においても同様の意味で用いられるものとします。

第1条(適用)

  1. 参加者(第2条に定義します。)は、コンペティションに参加した時点で、本規約、利用規約、その他ご同意いただいた規約のすべて、及びコンペティションサイトに掲載されているコンペティションに関するルールの一切に同意したものとみなされます。
  2. 本規約、利用規約、その他ご同意いただいた規約のすべて、及びコンペティションサイトに掲載されているコンペティションに関するルールは、コンペティションの終了後も参加者に適用されます。

第2条(定義)
本規約において次の各用語の定義は、それぞれ以下に定めるとおりとします。

  1. 「本コンペ」とは、当社ウェブサイト上で開催されるAI開発又はデータ分析等に関するコンペティションのうち、本規約に紐づく特定のコンペティションを意味します。
  2. 「主催者」とは、当社またはユーザーのうち、本コンペを主催する者を意味します。また、本コンペが、当社の顧客又は提携先の企業、学校その他の団体等がスポンサードするものである場合は、当該団体等も主催者の定義に含まれます。
  3. 「参加者」とは、ユーザーのうち、主催者側以外の立場で本コンペに参加する方を意味します。
  4. 「成果物」とは、本コンペにおいて参加者により開発される学習済みモデル、そのソースコード及び乱数シード等の設定値を意味します。
  5. 「入賞者」とは、当社より本コンペに入賞した旨の通知を受けた参加者を意味します。
  6. 「知的財産権」とは、著作権(著作権法第27条及び第28条に定める権利を含みます。)、特許権、実用新案権、商標権、意匠権、その他のノウハウ及び技術情報等の知的財産権(それらの権利を取得し、又はそれらの権利につき登録等を出願する権利を含みます。)を意味します。

第3条(権利の帰属)

  1. 本コンペで発生した成果物に関する知的財産権は、参加者に帰属します。

第4条(入賞者の義務)

  1. 入賞者は、本コンペで公開した成果物を、MITライセンスを適用し、商用利用の許諾条項及び著作権人格権の包括的不行使条項をライセンス条項に付与した形式で、オープンソースソフトウェアとして公開する義務を負うものとします。その前提として、入賞者は、成果物について本項に基づく方法でオープンソース化する権利を有していることを当社に対して表明保証するものとします。
    ※第三者が、授業・研修・セミナー等で活用できるようにするための規約となります。ご理解のほどよろしくお願いいたします。
  2. 当社は、以下の3点の確認が完了した時点で、本コンペの賞金または商品を、入賞者に対して授与するものとします。
    1. 入賞者が、前項に基づいて成果物のオープンソース化を実施したこと
    2. 入賞者が、本規約、利用規約、その他ご同意いただいた規約のすべて、及びコンペティションサイトに掲載されているコンペティションに関するルールの一切に違反していないこと
    3. 当社が定める方法による本人確認
  3. 当社は、入賞者が第1項に基づいてオープンソース化した成果物を、自由に商用利用することができます。

第5条(禁止事項)

  1. 参加者は、本コンペにおいて、以下の各号のいずれかに該当する行為を行ってはならないものとします。
    1. クラッキングやチート行為、なりすまし、盗用等の不正行為
    2. 第三者の知的財産権その他の権利を侵害する内容ないし態様で、参加者公開事項を公開する行為
    3. 主催者(当社以外の者に限ります。)に対する直接連絡、相談、依頼、勧誘、勧誘対応等の活動(但し、当社を介して当社が認めた方法により行うものは除きます。)
    4. 本コンペにおいて、当該コンペと直接関係のない成果物等を提出すること
    5. 本規約における参加者としての地位又は参加者としての権利義務について、譲渡、移転、担保設定、その他の処分をすること
    6. その他、本規約、参加ルール及び利用規約に違反する行為
  2. 参加者が前項に規定する禁止行為を行ったと当社が認める場合、当社は、当該参加者に事前に通知することなく、当該参加者の本コンペにおける失格処分、当社サービスの全部又は一部の利用停止、ユーザー登録の抹消、その他当社が必要と判断した措置をとることができるものとします。

第6条(本コンペの変更、中断、終了等)

  1. 当社は、参加者に事前の通知をすることなく本規約に基づく本コンペの開催内容の変更、本コンペの一時的な中断又は終了を行うことができます。
  2. 当社は、本条に基づき当社が行った措置により生じた結果及び損害について、一切の責任を負わないものとします。

第7条(損害賠償)

  1. 参加者は、本コンペに関連して、自らの責に帰すべき事由により、当社、主催者その他の第三者に損害を与えた場合には、その一切の損害(逸失利益、弁護士費用を含みます。)を賠償するものとします。
  2. 参加者が本規約の規定に違反したことにより主催者(当社を除きます。)その他の第三者が当社に対して何らかの訴え、異議、請求等がなされた場合において、当社から処理の要請がなされたときは、参加者は自己の責任と費用負担において、当社に代わって当該第三者との紛争を処理するとともに、当社がかかる訴え、異議、請求等により被った一切の損害(逸失利益、弁護士費用を含みます。)を賠償するものとします。

第8条(本規約の変更)
当社は、必要と判断した場合には、参加者に対して事前に通知する(本コンペにかかる当社ウェブサイト上での告知その他当社が適当と認める方法を含みます。)ことにより、いつでも本規約を変更することができるものとします。なお、変更内容の通知後、参加者が当社の定める期間内に本コンペへの参加を取り消す手続をとらなかった場合には、当該参加者は変更後の規約に同意したものとみなされます。当社は、本規約の変更により参加者に生じたすべての損害について一切の責任を負いません。

第9条(その他)
本契約の準拠法は日本法とし、本契約に起因し又は関連する一切の紛争については、当社の本店所在地を管轄する裁判所を第一審の専属的合意管轄裁判所とします。

(制定)2020年4月1日