Paoの技術力を磨くブログ

機械学習やブロックチェーン等の技術を身に付けていくブログです。

【Kaggle】HomeCreditコンペ上位入賞者の手法まとめ(メモ)

KaggleのHomeCreditコンペに参加しました。初めてのKaggleコンペ参加です。
HomeCreditコンペは、ローンの支払が出来たかどうかを予測するもので、Kaggleの中で過去最大の参加者数のコンペでした。

私は、7198チーム中97位(上位1.5%)でした!
目標を上位5%にしてたので結果には満足していますが、今後より上位を目指すためにも、 自分の復習・勉強用に上位入賞者の手法をまとめたので共有します。

自分のコンペ振り返りについては別記事でまとめます。

前提

  • あくまでメモなので、このコンペに参加もしくは、データの内容を理解していないと解読できないところ多いです
    • 特に特徴量とかは参加していないと意味わからないと思います
  • あくまで自分用メモなので出来ていなかったところや気になったところ中心に書いています
  • 流し読みした部分もあり、解法が間違っている可能性もあります。もし何かあればコメントください

詳しい内容を見たい人は下記に上位入賞者毎の解法へのリンクがまとまっているので、こちらを見てください

**COMPETITION WRITEUP INDEX** (UPDATING!!!) | Kaggle

6thを除くTop10の内容を書いてます

上位者の全体傾向

  • モデルはLightGBMが中心だが、NeuralNetwork(NN)も使っているケースが多い
  • 特徴量の作成(Feature Engineering)に力を入れている
  • 存在しないデータ(interest rate)、欠損値(EXT_SOUCE)の推測
  • 過去の支払いについて、時系列を考慮した特徴量つくり
  • Stackingで精度をあげるために、いかに精度が高いながらも多様性のある(相関の低い)モデルを作るかが重要
  • 特徴量選択で工夫、学習手法を増やす、チームメンバーを増やして多様性を増やすなど

1st

特徴量

  • interest rate の予測モデルを作り、特徴量に利用
  • 特徴量の近い500個のデータのTARGET平均値を特徴量にする。
  • 特徴量はEXT_SOURCE_Xとcredit/annuity
  • AMT_ANNUITYと過去最大の分割支払い額の比率
  • EXT_SOURCE_3で割った値が有効だった
  • カテゴリ変数をLabelEncoding
  • previousの中でも最新のpreviousのカテゴリ変数を利用
  • いくつかの観点でaggregate
    • previousの直近N回、最初のN回、直近N日など閾値を変えてaggregate
    • installmentの1~4回目の支払い(NUM_INSTALMENT_NUMBER)のみでaggregate
    • 支払いが遅延したものをaggregate
  • REGION_ID_POPULATIONをカテゴリ変数として扱う
  • 直近のDAYS CREDIT
  • 日齢を年齢に変換して利用 AGE_INT: int(DAYS_BIRTH / -365)
  • 直近1000日のAMT_PAYMENT - AMT_INSTALMENTの平均
  • リッジ回帰による特徴量選択

推定モデル

  • NN:DAEを利用
  • stacking3層
  • 一層目 NN,XGB,LightGBM,
  • 二層目 NN,ExtraTree,Hill Climber.
  • 三層目 average
  • ExtraTreeでは、AMT_INCOME_TOTALも特徴量に追加
  • EXT_xの推測、穴埋めは効果なかった

2nd

チーム構成

推定モデル

  • LGBM,NN,RNN,CNN,RGF,CatBoost
    • チームメンバーで作るモデルが分担

Stacking/Blending

  • Adversarial Stochastic Blending
    • testとtrainを区別するモデルを作成。各特徴量セットで、そのモデルの精度を元に重み付け。使うtrainはランダムで抽出

その他

  • 同じ人が複数のSK_ID_CURRを持ってることが判明。そして、同じ人だと同じ結果になりやすいため、推定結果にたいして重み付け実施

3th

特徴量

  • 特徴量はCVでしぼって250個くらい
    • 一度数を減らすと学習時間も短く試行錯誤もやりやすかった
  • SK_ID_PREV単位での予測を特徴量に利用
  • EXT_SOURCEの推測
    • 推測値と実際の値の差分はいい特徴量に

推定モデル

  • たくさん作ったモデルのなかから、相関の低いモデルを数個選定
  • モデルはLGBM中心で、NNとXGBも
  • Stackingは、LGBM,RandomForest,ExtraTree,LinearRegressionの4つを使いaverageをとる
  • stacking時にはいくつかの元の特徴量も利用
  • stackingで使った特徴量は1つずつ増やして試した。特徴量も多くないから早い
  • ExtraTree,LinearRegressionでは、欠損値は平均でうめた

4th

  • 過去N回のinstallments and pos,bureau featuresでaggregate
  • 大量の特徴量セットから、特徴量を選択して相関の低いモデルをたくさん作る。
    • oof毎にRFEを使って特徴量選択
  • BayesianOptimizationの時の試行毎の予測結果を保存して利用した
  • revolving loan の予測結果を修正した(0.4を越えたら0.8かける)

5th

特徴量

  • 行ごとの推定
    • installment,bureau,pos,creditそれぞれ、行毎にtargetの推定を実施
    • 推定結果をSK_ID_CURR毎にaggregateして、min,max,mean等を特徴量に
  • interest_rateの予測
    • 予測interest_rateは現実的な範囲の価にしぼって利用
  • 一般的な金融リスクスコアの計算方法を調べ、それを特徴量とした
  • 一部のカテゴリ変数を数値化。例えば、NAME_EDUCATION_TYPE
  • 色んな特徴量でaggregate
    • 例 CREDIT_TYPE+CREDIT_ACTIVE

推定モデル

  • ニューラルネット
    • ユーザの過去の支払い状況を2次元マトリクスにして、1dCNN+LSTMを使ったNNを作成
    • NNの予測結果をLGBMにいれる

7th

8th

9th

推定モデル

  • xgb, lgbm, catboost, logistic regression, random forest, extra trees
  • 結果、CVとLBの関係がかなり安定
  • LightGBMについて、gbdtよりdartのほうがよかった。恐らく特徴量が多いから
    • dartで特徴量が多いときはfeature_fractionを減らすと良い
  • NN
    • Entity Embedded Neural Networksがよかった。
      • 詳細はEddy's kernel from Porto Seguroを見るとよい
    • バッチサイズは小さいほうが精度よかった
    • RankGauss Normalizationには小さいepochが必要
    • MinMaxScalerは必要
    • 層の数を増やしたほうが精度がいい
  • stackingよりaverage belendingのほうがよかった

特徴量

  • 時系列の情報に対して、速度・加速度のような値を算出。速度は前月との差分、加速度は差分の差分
  • CNT_INSTALMENT_FUTUREの遷移の仕方(基本1つずつ減るが一気に減るとまとめて返したことになる)
  • posなどで、(1-(DAYS_VALUE)/(minimum_value))のような割合を各特徴量にかけた

10th

特徴量

  • instalmentの(DAYS_INSTALMENT-DAYS_ENTRY_PAYMENT)は有効な特徴量であり、さらに直近1年のものに絞ると有効だった
  • creditの上限に対して利用した割合を特徴量に
    • 特に直近のものは有効
  • interest rateの推測を特徴量に
  • NAME_CONTRACT_TYPE別にモデルを作成
  • previousの直近N回、最初のN回、直近N日など閾値を変えてaggregate
  • EXT_SOURCE_Xの欠損値を推測モデルで埋める

画像以外へCNN(畳み込みニューラルネット)を利用した研究を調べてみた

麻雀AIでCNN(Convolutional Neural Network: 畳み込みニューラルネットワーク)の構成を考えるにあたって、そもそも画像以外のCNNってどんなのあるんだっけ?と思い、簡単に調べた。

ゲーム関連

盤面を使ったゲーム(囲碁・将棋・チェスなど)

盤面が9x9や19x19などの二次元座標で表現できて、CNNと親和性が非常に高い。
DeepMindが開発したAlphaGO*1, AlphaZero*2が特に有名。
囲碁であれば、黒石・白石の有無(one-hot)、数手前の状況等をそれぞれ19x19の特徴ベクトルで表現し、複数チャンネルあるものとして、CNNを行う。CNNとしてはResNetを利用。
将棋・チェスの場合、自分・相手のコマの種類ごとに一つのチャンネルを用意する。
位置の厳密性のため、プーリング層を設けていないのも特徴。
また、「手番が自分と相手どちらか?」「(将棋で)どの持ち駒を持っているか?」といった2値(True/False)情報は、盤面全部を0か1のチャンネルとして表現しているのも面白い。そういった情報は、畳み込みせずに特徴量ベクトルとして合成してもいい気がするが、構成をシンプルにしたいからなのか、計算速度の影響か、このほうが精度がいいからなのかよく分からない。

盤面を使ったゲームとして、正方形型の盤面以外にも、Hex*3と呼ばれるひし形をした盤面のゲームでもCNNを使った研究*4があった。 いわゆる3x3といった正方形型とは違うが、隣接するマス目を畳み込みフィルタで畳み込んでいるみたい。

トランプ(ポーカー)

ポーカーは不完全情報ゲームとして世界中で特にポピュラーなゲームのため、AI研究もよくされている。

トランプは、4(ハート等の色) x 13 (A~K)の二次元情報に落とし込むことが可能であり、それを活用している。*5
特にポーカーでは、2ペアのような数字と、フラッシュのような色を使うもの両方があり、二次元ベクトルを畳み込むことでうまく特徴量抽出できるらしい。
ただし、方法の一つとしてCNNが提案されているだけで、他のポーカーAIで全結合のネットワーク構造を採用しているものも多く、CNNの効果や効率的なアーキテクチャ等はまだまだ分からない印象。

麻雀

麻雀でも昨年のゲームプログラミングワークショップで論文が出ている。 *6 ちょうど私も麻雀AIで昨年にCNNを少し試したことがあって、最初は先に越された思いや、やっぱり有効だったといった思いが混ざった複雑な気持ちになった。
ただ、いろんなアーキテクチャを試していて、非常に参考になっていて、助かっているのは間違いない。

麻雀の牌の種類(34種)と枚数(0~4)を二次元ベクトルとして畳み込みをしている。
いくつかの方法を試しているが、特に精度が高かったのが、マンズ・ピンズ・ソウズそれぞれを別チャンネルとして9(1~9の牌)x5(0~4枚)のベクトルで畳み込む方法。
字牌は前後の牌との関係がほぼないので、全結合の層で結合している。
また、マンズ・ピンズ・ソウズには対称性がある(緑一色を除く)のでチャンネル間のフィルターの重みを共有しているといった工夫もしている。
畳み込みのフィルターサイズは5x9の大きいものを含めたほうが精度がいいらしい。
畳み込み層とプーリング層一つずつであり、あまりDeepではやっていない。
牌譜を使った教師あり学習で全結合よりも牌譜との一致率は少し高くなっていた。

自然言語処理

かなり研究が盛んな分野であるため、論文も多数ある。まだまだこれから新しい発表がありそう。
こちらにまとまっているのが分かりやすかった。
基本的には言語情報をWord2Vecなどのベクトル情報にして、1次元の畳み込みのフィルターを適用している例が多い印象。
あとは、単語の前後情報を抽出するためにRNN(Recurrent Neural Network)と組み合わせる例も多い。
言語情報をベクトルにする部分のほうが重要だったりもするが、自然言語処理系は深すぎて追いきれていない。

時系列データ

時系列の信号データをスカラー値と時系列情報でグラフのような形にして、そこにCNNを適用させる例も多くある。 *7 *8 *9

例えば、モーションセンサーからの行動認識とかであれば、センサーから取れる複数の情報(センサーの場所、加速度・位置など)をチャンネルとして扱う。
あまり読めてないが、時系列といえばLSTMなどのRNN系も効果的であり、RNNとの組み合わせの例も多そう。

見つけた例としては、モーションセンサーからの行動認識、Wi-Fi信号を使った室内空間の認識、株価の予測など。
個人的には、投資系と相性がいいように思えるし、いずれやってみたい。

全体を通して

恐らく他にもたくさんありそうだが、探しきれないので、いったんこんなところで。
CNN自体が人間の視覚を参考にしているため、基本は画像系が多いが、他への応用もかなりあった。
ただ、それぞれの分野で何人も研究しているわけではないので、本当にそのアーキテクチャが適切なのかがよく分からない。(画像とかだと競争も進みより制度の高いアーキテクチャが発見されるが)
特に、囲碁・将棋の手番や麻雀の字牌といった情報の特徴量化の方法や、畳込みフィルターのサイズ、プーリング層の必要有無、層の深さなどが研究によって大きく違い、また設定した根拠があまり書かれてないケースも多く、ノウハウ次第みたいなとこが気になった。
誰かノウハウを伝授してください。

目的としていた麻雀のCNN適用については、先行研究を参考にしながら改めて検証していく予定。

深層強化学習を用いた一人麻雀AIを作ってみた

最近はブロックチェーンまわりばかりだったが、 前々からやっていた麻雀AIの強化学習について一旦まとめておく。

麻雀好きの人から機械学習興味ある人まで読めるよう書いたみたけど、どうでもいいとこは飛ばしながら、分からないとこあれば気軽にコメントください。

概要

  • 一人麻雀のAIを深層強化学習で作ってみた
  • 自分が持っている牌すら選択できない状況から、ある程度有望そうな牌を選べるところまで学習した
  • 最終的には、18順の一人麻雀でテンパイ率60%弱までになった
  • ただ、他の手法に比べるとかなり弱く、まだまだこれからといった感じ

背景

麻雀AIでは、「どの牌を切るか?」「鳴くべきか?」「リーチすべきか?」など多くの判断が必要だが、 その中で基本となるのは「どの牌を切るか?」といった部分であり、点棒状況や相手の手を考慮しないシンプルなものを一人麻雀という。

※ここでいう一人麻雀は副露しないものとして以降扱います。

AIというと最近流行りの深層学習(ディープラーニング)のイメージがあるが、 麻雀AIでは研究はされているが、実用的にはあまり使われてない。

理由としては、モンテカルロ法とかで十分強いのと、そもそも教師あり学習自体、教師となる人間を真似するだけで超えられないといったものがある。
(有名な麻雀AI 爆打モンテカルロ法教師あり学習(単純パーセプトロン)を使っているはず。)

後者を解決する方法として、強化学習というものがある。
これは真似すべき正解はなく、AIが自ら試行錯誤して学んでいくという方法である。
そしてこれに深層学習を応用したものを深層強化学習という。
AlphaGOでも使われていたり、マリオカートなんかも学習している例もある。

強化学習だと、AIが自分で学んでいくので、人間を超えることも可能になる。
というワクワクする方法なので、深層強化学習の麻雀AIを作ってみた。

手法

深層強化学習の中で、A3Cという手法を用いた。
詳しくは説明しないが、2016年頃に出たもので、強化学習の中ではメジャーな手法。
Actor-Criticという状態価値と方策を同時に学習するところが、AlphaGoZeroとも共通であり、麻雀にも有効と何となく考えたため採用した。

ネットワーク

状態価値関数と方策関数で前半は共通のネットワークを用いた。
全て全結合のネットワーク。
畳み込みニューラルネットも試していたが、学習に時間がかかるのと、強化学習では全結合することが多いため、この形とした。
ざっくりとした図は下の通り。
特徴量の数は180、共通部分の中間層のユニット数は200、状態価値と方策のユニット数は100、方策の出力数は34(牌の種類数)。

f:id:go5paopao:20180726003912p:plain

特徴量

シンプルに34種類の牌それぞれが0~4枚のうち何枚手牌に含まれているか?を34x5=170。 とする予定だったが、ここ少し間違えた。
字牌も9種類分用意していて、36x5=180の特徴量としてしまった。 (常に0の特徴量が20次元ある)

あとは手動の特徴量(メンツがいくつあるか等)も用意してもよかったが、深層学習でその辺は自動で抽出して欲しかったので入れてない。
それで学習してくれた方が面白いし!

報酬の設計

ここ少し工夫をした。 そもそも報酬というのは、強化学習で試行錯誤していく中で何を成功体験、失敗体験とするかを決めるもの。
ゲームでいうと、相手に勝ったときとか、ステージをクリアした時にプラスの報酬を設定し、ミスをしたときにマイナスの報酬(罰)を設定する。

麻雀でシンプルに考えると、 和了したときにプラスの報酬を与えるべきだが、これだけでは上手くいかない。
強化学習では最初はほぼランダムに行動するが、34種類の牌をランダムに選んでも、天和でもない限り、ほぼ和了にたどり着くことがないためである。
そうすると、いつまで経っても学習が進まない。
例えるなら、歩くこともできない子供に野球でホームランを打ってと課題を与えるような感じ。

そこで、今回はステージを段階的に分けて、学習させる。
野球の例でいうと、まずは歩き方をおぼえる、次にバットの握り方、振り方、ボールの当て方、遠くへの飛ばし方と順に教えるようなイメージ。

今回は、以下のステージに分けて学習を行った。

  • ステージ0: 3巡目まで持っている牌を選択できたら報酬
  • ステージ1: 7巡目まで持っている牌を選択できたら報酬
  • ステージ2: 15巡目まで持っている牌を選択できたら報酬
  • ステージ3: 流局まで持っている牌を選択できたら報酬
  • ステージ4: イーシャンテンまで進めたら報酬
  • ステージ5: テンパイまで進めたら報酬
  • ステージ6: 和了できたら報酬

マイナスの報酬は共通して、持っていない牌を選択したときとした。

各ステージで報酬をもらえる成功率が一定の割合を超えたら次のステージに進むようにした。

本来であれば、和了だけでなく、和了時の点数まで報酬に含めるところまですべきだが、 簡略化のためそこまではしていない。

アカデミックな観点でいうと、報酬の段階を手動で設計するのは、あまり評価されない気がするが、他にいい方法が思いつかなかった。 もしいい方法を知っている方がいたら教えてください。

ソース

強化学習フレームワークとしてChainerrlを利用。
強化学習アルゴリズムの実装とか結構めんどくさいので、かなり便利。

ソースはこちら

github.com

学習結果

結論

ステージ5(テンパイしたら報酬)で、60%くらいまでいったところで学習が収束してきて終了した。

学習過程

ステージ4まで

f:id:go5paopao:20180726003920p:plain

横軸が試行回数、縦軸が成功率(報酬をもらえる割合)になっている。
ステージ0~3の成功とは、規定の巡目まで持っている牌を選択し続けたこと、ステージ4はイーシャンテンになったことを指す。
各ステージで右肩上がりになっていて、ステージが上がるとまた成功率が下がり、そこから右肩上がり、、となっているのがわかる。
ステージ4のクリア成功率を0.7としていたので、約100万回でステージ4までクリアした。

ステージ5

f:id:go5paopao:20180726003924p:plain

成功率、すなわちテンパイ率が最初は上がっているが、徐々に収束しているのがわかる。
試行回数について、ステージ4までよりもかなり大きく最終1000万回まで行った。
これ以上、上がりそうになく、コンピュータの電気代も馬鹿にならないのでここで終了した。

実際にどう打つか

学習したモデルで実際にどういった選択をするかを見てみた。
天鳳鳳凰卓で打っている自分の批評も踏まえながらw

というか、本当にテンパイ率60%なのか疑問なくらい残念な打牌です。
適当なルールベースのほうが全然マシです。

画像作るの大変なのでシミュレーションで打った手を表示します。

2m7m8m3p4p7p8p8p1s南北発中中 dahai:北
南か北切っておけばいいので、これは問題なし


2m7m8m3p4p7p8p8p1s6s南発中中 dahai:6s
???まずは字牌切ってほしいところ。6sならせめて1sでしょ。。


2m2m7m8m3p4p7p8p8p1s南発中中 dahai:発
南のほうがといいたいところだが、点数考慮してないので南でも発でも問題なし。


2m2m7m8m3p4p7p8p8p1s南発中中 dahai:発
裏目ったらしい。どんまい。


2m2m5m7m8m3p4p7p8p8p1s南中中 dahai:2m
これはちょっとないかな。。1sか南切りでしょ。 門前のみってこと考えると、3対子は微妙なので対子落としは悪くないけど。


2m5m5m7m8m3p4p7p8p8p1s南中中 dahai:2m
すでに1枚落としてるし、まぁいいでしょう。


5m5m7m8m3p3p4p7p8p8p1s南中中 dahai:1s
南のほうがいいけど、1sは悪くない


5m5m7m8m3p3p4p7p8p8p9s南中中 dahai:中
早く南を切ってくれ。。南以外であれば中は悪くないけど。


5m5m7m8m3p3p4p7p8p8p3s9s南中 dahai:3s
3sはないですね。。9sか南か中のどれかにしてほしい。


5m5m7m8m3p3p4p4p7p8p8p9s南中 dahai:中
ようやく中切ったか。あとは南。


5m5m7m8m3p3p4p4p7p7p8p8p9s南 dahai:南
待ってました南切り。これで手牌がすっきりした。


5m5m7m8m9m3p3p4p4p7p7p8p8p9s dahai:3p
9sを切ってください。9sが9mだったら3pはかなりあり。


5m5m7m8m9m3p4p4p7p7p8p8p4s9s dahai:4s
4sは切ってくれるのね。あとは9s切ってください。。


5m5m7m8m9m3p4p4p7p7p8p8p9s9s dahai:9s なぜ9s重なったところで9sを切るのか。。。


5m5m7m8m9m3p4p4p6p7p7p8p8p9s dahai:3p この3pはないですね。。9s切らないならせめて4p


5m5m7m8m9m4p4p6p7p7p8p8p9s発 dahai:発
これはOK


5m5m7m8m9m4p4p6p7p7p8p8p9s白 dahai:白
これもOK


5m5m7m8m9m4p4p6p7p7p8p8p7s9s dahai:7s
なぜ9sだけ切らないのか。。。


考察

いくつか今回うまく行かなった原因や改善ポイントについてまとめます。

特徴量設計がうまくいっていない

特徴量で不要な次元数を与えてしまったというミスも含めて、ここを改善すればもう少しましになると思います。
まだメンツやシュンツなどの特徴量を入れなくても、せめて山にいる可能性がある枚数を入れるとかすればましになると思います。

強化学習単体では限度がある

強化学習だけで強いAIを作るのは難しい可能性があります。 AlphaGOでも強化学習結果をそのまま使うのではなく、その結果をモンテカルロ木探索の中で使うといったやり方をしています。
なので、麻雀でも強化学習の結果をモンテカルロに活かすようなことをしたほうがいいかもしれないです。

強化学習部分の改善

強化学習における報酬の設定の仕方、利用する手法など改善の余地はあります。
ゲームによって向き・不向きもあるみたいなので、もう少し強化学習を調査して改善したいと思います。

深層学習部分の改善

今回の深層学習部分のネットワーク構造はかなり適当に全結合をしたものです。
畳み込み層を入れるなどのネットワーク構造の改善やその他の学習改善手法を取り入れることで精度が上る可能性があります。

プログラムにバグが有る可能性

上の例で9sを全然切らなかったことを考えると、報酬を決めるときのシャンテン数計算とか、特徴量作る部分とか我ながら疑いたくなります。
改めてバグがないか見直す予定です。

まとめ

今回は深層強化学習を使った麻雀AIを作ってみました。
まだまだ弱いけれど、教師データもなく、持っている牌すら選択できない状態から考えると、強化学習の可能性は示せたかなと思います。

今後もっと実用的になるように改善していく予定です。

【Ethereum】ブロックチェーンのじゃんけんdAppゲームを作ってみた

ブロックチェーンを使ったジャンケンゲーム(デモ)のdAppを作りました。
ゲーム自体はジャンケンなので大して面白くないですが、 ジャンケンというゲームがブロックチェーンの特性理解およびdappsの勉強になるいい題材と思ったため ジャンケンのゲームを作りました。

ゲームの紹介

ゲームのページ

Ethereum ジャンケンゲーム

※PC用サイトです。
※ジャンプする前にMetaMaskでログインしてください。
※ゲームの具体的な手順はリンク先の下の方に記載されています。

ゲームのポイント

ブロックチェーンのパブリックな特性ゆえに、処理フローを工夫しています。
これをしないと後出しジャンケンが可能になってしまうためです。
詳しくは↓の「ブロックチェーンならではのゲームの特徴」に書いています。

作ったゲームの概要

  • 2人対戦用のジャンケンゲーム
  • HostPlayerが先にゲームを作り、GuestPlayerがそのゲームに参加する
  • HostPlayerが賭け金(Ether)を設定でき、敗者から勝者に賭け金が渡される
    • Hostがゲームを作る時点で賭け金を設定
    • それぞれゲームに参加する時点でコントラクトに賭け金をデポジット
    • 勝負後、コントラクトは勝者のアドレスにデポジットした金額を送金する
    • あいこの場合、それぞれのプレイヤーに賭け金を返す

ゲームの前提

  • HostとGuestを同じユーザがプレイするデモゲーム
  • RopstenNet(イーサリアムのテストネット)上に存在
  • MetaMaskのインストールが必要
    • PCのみ利用可能
  • MetaMaskにRopstenのEtherを持ったアカウントが2つ必要

MetaMaskのインストール及びRopstenのEtherの貰い方は下記サイトを参考にすればと思います。

ブロックチェーンならではのゲームの特徴

課題:シンプルにやると後出しジャンケンが可能になってしまう

dappsでは一般的にユーザの操作内容やデータをブロックチェーン上に記録します。
ジャンケンの場合、ユーザが出した手(グー、チョキ、パー)はEthereumのブロックチェーン上に記録されます。
その結果、先に出したプレーヤー(ここでいうHost)の手をGuestがブロックチェーンから見た上で勝てる手を出すという、後出しジャンケンが可能になってしまいます。

解決策:ランダム文字列と組み合わせたハッシュ化を活用

後出しジャンケンをさせないために、ランダムな文字列とハッシュ化を使っています。
ハッシュ化とは、元のデータからハッシュ値と呼ばれる規則性のない値を計算することで、 ハッシュ値から元のデータを逆算するのは、ほぼ不可能と言われています。

Hostは、ジャンケンの手とランダム文字列を最初のMakeGameで提出しますが、 それらを結合して事前にハッシュ化したデータのみをブロックチェーン上に記録します。
こうすることで、GuestPlayerはHostが何の手を出したのか分からず、後出しができなくなります。
そしてGuestPlayerが手を提出した後に、Hostハッシュ化する前の手とランダム文字列を提出し、 それをEVM側でハッシュ化し、最初のハッシュ値と同じかどうかをチェックし、改竄していないことを確認します。

実装に関して

以下は開発する人向けの細かい話です。

利用したライブラリ、ツール等

  • Web3js:フロントエンドにほぼ必須ですね。
  • Truffle:テストやデバッグがかなり楽になりますね。
  • Remix:デバッグに使いました。EVM上での処理の流れとかも見れるの便利すぎるとようやく気付きました。
  • MetaMask:PCでの利用として現時点では必須ですね。今後はOperaのようにスマホブラウザでもアクセスできる流れでしょう。

その他実装した機能

賭け金のデポジットおよび移動

賭けれないと面白くないので、HostとGuestが同じ額だけBetして、敗者から勝者に賭け金が移動するようにしました。
よくある方法でゲームに参加する時の関数をpayableにしていて、EtherをBetできるようにしておき、 コントラクトがお金を一時的に預かり、決着が決まったら勝者もしくは引き分けの者に送金するようにしています。

コントラクトのデプロイ

デフォルトで指定しているコントラクトアドレスに複数人が同時アクセスすると、ゲームのStateがぐちゃぐちゃになるので、 各ユーザが自分用のコントラクトをデプロイできるようにしています。
Web3.jsでデプロイ済みのコントラクトオブジェクトと、デプロイ時のbytecode、gasを指定すれば下記のようにデプロイできます。
本当はゲーム管理用のコントラクトと組み合わせた方が良さそうですが。

イベントのウォッチ、トランザクション状況の表示

ユーザの操作によりトランザクション発行するだけでは、ブロックチェーン上で正しく受理されたかどうかは分かりません。
そこで、Solidity側でイベントを定義して、それをフロントエンド(Web3js)側でウォッチしておき、その状況をユーザに伝えることが望ましいです。
今回のジャンケンでは、下記のような対応をしました。

ハッシュ値の確認

上記の通り、ハッシュ値を確認することで手の改ざんなどがないかをチェックしています。
具体的には、「ローカルでハッシュ化してコントラクトに保存されたもの」と「コントラクトに送られた手とランダム文字列からハッシュ化したもの」について 比較しています。 それぞれのハッシュ値を確認できるような機能をページに用意しました。

残っている課題・改善点

別技術との組み合わせによる改善

今回は純粋にパブリックチェーンを使った場合のジャンケンとして実装しましたが、 Hostが一度ハッシュを提出した上で、再度手を提出するとか、ただのジャンケンにしては面倒すぎます。

まだ勉強不足ですが、「zk-snarks」や「Enigma」といった暗号化関連技術や、「state-channel」を使うなどして、 この面倒なフローを改善できるかもしれません。
それでこそブロックチェーンの可能性も広がると思うので、この辺りを追求していきたいです。  

適切なゲームサイクル等の管理

デモプレイ用に作っているため、細かい条件チェックなどは実装していません。 例えば、Hostがゲームを作った後に、Guestが来なかったりHostが再度手の提出をしなかった場合、 最初にベットしたEtherはコントラクトに残ります。
本来であれば、一定時間経ったら資金を返す、再提出しなければペナルティとして没収するなどを実装すべきだと思います。

コントラクトの設計

今回は一つのコントラクトに全てのFunctionやState変数をまとめて実装していますが、 本格的にやるならば、ゲーム毎や、複数ゲームを管理するコントラクト等複数に分けた方が良いかと思います。

セキュリティ対策強化

Consensysのセキュリティプラクティスは読みましたが、 このコントラクトでそれを網羅して、しっかりと試験をしたわけではありません。
メインネットにデプロイするなら、ここはしっかりやりたいですね。
OpenZeppelinの導入もしたいところです。

Gas節約のための最適化

Solidityの書き方によってユーザがゲームするときの手数料GASが変わってきます。
無駄のないコードを書くことでこのGASの節約ができますが、今回はそこまで考えていません。

別ユーザ同士の対戦

別にページを二つに分ければ出来るのですが、 誰かがサイトにアクセスしてくれても、同時にサイトにアクセスしているユーザがいないと遊べないので、 やめました。
あくまでデモであり、過疎ったオンラインゲームを作りたいわけではないので。。

フロントエンド側の改良

Javascript初心者過ぎてフルスクラッチで書きましたが、 よほど腕に自信ない限り、React+Reduxといったフレームワークを活用したほうが分かりやすく質の良いソースになるはず。
Web3+React+Reduxといった組み合わせが主流になりそうな感じもあるので、勉強がてら変えていきたいですね。。 あとデザインも残念な感じですが、そこメインでないので目をつぶる予定です。

(メインネットにデプロイする場合)法律調査など

今回のdAppsはEtherを賭けるゲームです。
メインネットにデプロイすると「賭博罪」や「資金決済法における仮想通貨交換業としての規制」に触れる可能性あります。
メインネットにあげるならそのあたりの調査をした上でのデプロイになります。
(何となくダメな気がしますが。。)

作ってみての感想

作ってみて初めてわかることが沢山

どんなことでもそうですが。。。
Ethereumの基本知識を本とかサイトで勉強して、CryptoZombiesでもやれば、 簡単なdAppsであれば作ることはできるとは思います。
ただ実際作ってみると、MetaMaskを通じてのトランザクションやら、どういうView Functionを用意すべきか、変数の型は何が適切か・・・ とか悩むことはいっぱいあって、細かいところで沢山つまづきました。 でもそうして、作ってみて初めて悩むことやわかることによる学びが沢山あります。
ということで作ってよかったです。

サーバサイドが不要だと個人開発もやりやすい

サーバサイドの開発も必要だと専用のサーバを用意したりとか、サーバ構成、DB設計などやらないといけないことが増えていきます。
デプロイ後の保守運用も大変。。
でもSolidityで記述したEthereum上のコントラクトがサーバのような役目を果たしてくれて、 基本シンプルな内容を書くだけなので、非常に楽です。
個人開発のハードルを下げてくれます。
※もちろんオフチェーン部分をサーバ使ってやるタイプのdAppもあると思うので一概には言えないですが。

非中央集権でやる意味を改めて考えさせられる

上にも書いた通り、パブリックチェーンで完結させたジャンケンは正直ジャンケンにしては面倒です。
手順の複雑さを踏まえても、本当にブロックチェーンで非中央集権的にやる必要があるのでしょうか。
ジャンケンくらいであれば正直、どこかのゲーム会社のサーバに置く中央集権的な方法でも良い気もします。
ただこれがもっと大規模なもの(eスポーツの国際大会とかオリンピックとか?)であれば、サーバ保有者への信用問題が発生しうるので 非中央集権的に実現できるブロックチェーンは魅力的だったりもします。

どういう場面で、オンチェーン上の非中央集権とすべきで、どこは中央集権化すべきなのか改めて考え続けていき 世の中の流れもつかんでいく必要があると思いました。

ソースコード

github.com

もっといい方法があるといったご意見があれば、ぜひお待ちしております。

【Ethereum】Gethで起動したプライベートネットをMetaMaskと連携

開発時にGethから立てたプライベートネットでのテストでもRopstenのようなテストネットやメインネットを想定して、 MetaMaskからアクセスできるようにしておいた方がいいと今更ながら思ったので、その対応のメモ。
特に難しい話ではないけど。

Gethのインストールやプライベートネット、MetaMaskのインストールの方法は割愛。

1. Gethの起動。

まずはGethの起動。このとき、MetaMaskからネットワークにアクセスできるようrpscorsdomain=*などにしてアクセスできるようにしておくこと。 以下は一例。

geth --networkid [genesis.jsonで指定したnetworkID] --rpc --rpccorsdomain "*" --rpcport "8545" --rpcapi web3,eth,net,db,personal --datadir "プライベートネットのブロック格納先パス" --nodiscover --unlock 0,1 console 2>> ./geth.log

2. MetaMaskを起動。

MetaMaskをブラウザから起動。MetaMaskの左上の接続先からLocalHost 8545を選択。 この時点でネットワークには繋がるが、Geth側で作成したアカウントはMetaMaskには表示されない。

3. アカウントのインポート

右上の人+更新マークみたいなのをクリックし、「Import Account」を選択。 f:id:go5paopao:20180711012344p:plain

SELECT TYPEを「JSON File」にして、ファイルを選択。 (もし秘密鍵が分かるのであれば、Private Keyを選択してコピペでも良い)
f:id:go5paopao:20180711012349p:plain

ここで、Gethでプライベートネットを立ち上げたブロック格納先パス(datadirで指定するとこ)の中に「keystore」ディレクトリがあるので その中の該当アカウントのJSONファイルを選択。

f:id:go5paopao:20180711013503p:plain

これでアカウントがインポートされ、MetaMaskにも表示される。 アカウント同士の送金もMetaMaskから普通にできる。

【Ethereum】Solidityでの例外処理(require,revert)でエラーメッセージを出力する

日本語での情報があまりなかったので書いておく。

Solidity0.4.22より、require()revert()による例外処理でエラーメッセージが出力できるようになった。

記述方法

require(条件, メッセージ);
revert(メッセージ);

具体例(require)

require(msg.value % 2 == 0, "Even value required.");

具体例(revert)

if (msg.value % 2 != 0){
    revert("Even value required.");
}

デバッグが捗って嬉しい!

例えば、以下のsetNumber()のようなやたら制限したがる関数があったとする。
この関数を呼び出すトランザクションを発行してエラーになったとき、どこでエラーになったのかパッと見て分からない。
そこで、setNumber2()のように書いてあると、エラーが出力されエラーの原因がすぐにわかる!

pragma solidity ^0.4.22;

contract test{
    address  owner;
    uint number;
    constructor () public{
        owner = msg.sender;
    }
    //エラーの理由がわからない
    function setNumber(uint _number) public {
        require(msg.sender == owner);
        require(msg.sender.balance > 100000);
        require(_number > 0);
        require(_number%10 == 0);
        number = _number;
    }
    //エラーの理由がわかる!!!
    function setNumber2(uint _number) public {
        require(msg.sender == owner, "only owner");
        require(msg.sender.balance > 100000, "balance: more than 100000 wei required");
        require(_number > 0, "_number: more than 0 required");
        require(_number%10 == 0, "_number: cannot be devisible by 10");
        number = _number;
    }

}

ちなみに、assert()についてはメッセージ出力は現時点では対応していないっぽい。

以下、参考。

CryptoZombiesを一通りやってみて。。。

EthereumでのdApp開発の学習ツールとして有名な「CryptoZombies」を一通りやってみました。
(今出ているLesson6まで)

cryptozombies.io

「CryptoZombies」はEthereum上に独自ネットワークを開発しているLoom Network社が提供しています。
「CryptoZombies」という名のdAppのゲームをチュートリアルと共に一から作っていきます。

f:id:go5paopao:20180519222754p:plain

キャプチャの通り、右側にエディターがあり、左側にコードの書き方やノウハウ、課題が書かれています。
課題の通り、コーディングをすると次のステップに進めます。

キャプチャは英語ですが、日本語も可能です。
(個人的には分かりやすい英語であり、今後dAppの勉強するなら英語はほぼ必須なので英語推奨です)

今後、章が増えていくと思いますが、1〜5章まではSolidityの書き方、6章はWeb3.jsでのフロントエンド部分の書き方を勉強できます。

また、各章の中でもステップ毎に、テーマが決まっており、コントラクトの宣言の仕方や修飾子の宣言の種類などを体系的に学んでいけます。


一通りやってみた上でエンジニアが勉強として使う上でのいいところや注意点などを書いておこうと思います。


いいとこ:スマートコントラクトによってアプリケーションがどう作られるのかを理解できる

Solidityやスマートコントラクトを調べても、だいたいは「トークンを作ってみよう」「コントラクト間でのメッセージのやり取りをしてみよう」といった基本的な内容が多く、「実際ゲームとかのアプリケーションはどう作るの?」というところが具体的にイメージできないことがよくあります。
(プログラミング初心者あるあるですが)
最終的に具体的なゲームとして動作するところまで進んでいくので、アプリケーションがどう出来ているかまで学習できるのがいいところだと思います。

いいとこ:体系的に学べるので、知らなかった部分の穴埋めをできる

まだEthereumの歴史も浅いので、あまり体系的にSolidityやdAppについて学べるものは少ないです。
結果、いろんなとこで調べながら学んでいくわけですが、部分的に理解に抜け漏れも生まれます。
すでにある程度勉強している人は知っているところを飛ばしながら、そういった抜け漏れがありそうなとこだけを勉強して埋めていくっていうのもありだと思います。
個人的には、GASの消費を抑える書き方についてはあまり勉強できておらず、そのことに気づき勉強になったかと思います。

注意点:課題のクリアを目的とせず、仕組みを理解する

各ステップ毎に出てくる課題をクリアすれば次に進めるわけですが、この課題自体は基本的に難しくありません。
具体的に「どこにどういったコードを書け」という指示があり、その通りやるだけなので。(コピペしろみたいなのもあります)
課題の手前に、書き方や注意点・ノウハウが書いてあるので、そこを必ず読むようにしましょう !!

おすすめ:英語で勉強する

上にも書きましたが、日本語訳もありますが、英語でやることをおすすめします。 利用としては、 - 分かりやすい英語が使われており、苦手な人にも英語の勉強にもなる - Ethereum関連の英単語や熟語に慣れることができる といった感じです。
特に大事なのは2つ目です。
今後dAppやEthereumの勉強をしようとすると、各プロジェクトのGithubやWhitePaper、Ethereum.researchなど、ほぼ間違いなく英語のページが出てきます。
そんな時にEthereumやSolidityなどでよく出てくる単語や熟語があり、独特の意味合いで利用されたりします。
それをこのCryptoZombiesとかで勉強しておくと、分かりやすくなったりするのでオススメです。




すでにある程度勉強していたとはいえ、それでも勉強になりました。
学んだことを忘れないうちに、簡単なdAppを作ってみようと思います!


ちなみにここまでオススメしておきながら、LoomNetworkの関係者でもなければ、LOOMトークンも持っていないです(笑)