
てんどん
ライブブラックジャック完全解析 データサイエンティストが解析した記録です
更新日:2020年6月18日
こんにちは天丼です!
データサイエンティストとして大学で研究している友人にブラックジャックを解析して貰った記事でお話した解析編です。
天丼はマジで文学部なのでさっぱり言ってることわからないけど、わかる人が読んだらめっちゃ楽しいと思う。
ちなみに途中で数字の取違いがあって軌道修正してます。
数学ってすげーーーー
本編
3/18
勝ち負け引き分けの3パターンの分類
それぞれにおける残りカードを箱ひげ図で評価
残りカードと勝敗の相関をとった

3/19
残りカードの出現割合を用いてPCAで特徴量を抽出したものの、あまり良い結果が得られない。
*PCAは不要な情報を削除するための手法です。入力が1率~10率と十個あり、10次元で目で見ることができないため次元をまとめていきます。
ちなみに三次元にしたときはこんな感じになります

赤:収支がプラス、青:収支がマイナス、黄色:引き分け
部分的に赤青を見てみると特徴があるかもしれませんが、あまり特徴が見られないので、目で見るだけでは判別がつかないと判断し、別の分類を試すことにしました。
ナイーブベイズなどによる評価を行ったが、勝ち負けを分類できるものではない。
モデルのパラメーターを微調整しても結果はあまり変わらない
3/22
SVM,Randomforest,線形回帰などを試してみた。
しかし、1番良かった線形回帰でも46%ほどの精度
3/24
試したこと:クラスタリング
いくつのクラスに分けるかを調整しながら、収支と関係ありそうなクラス分類を探していった。
※モデルについては今後説明を追記していきます
3/25
入力を出現確率ではなく各期待値にした上でモデルを試したものの、結果は変わらず。
クラスタリングの続き。
クラスタリングは、各ゲームごとのデータのうち似たものをグループにしてくっつけていき、最終的に全体が似たものどうしのグループに分かれる手法です。

こんな感じでグループ分けされます。
クラスタリングでをもちいて5つほどのグループを作り、それぞれのグループでどのような特性があるのかを見ていきます。
3/25
knn,決定木の実装。
knnは入力データに対して、それに似た性質を持つ集団で収支について多数決を行い、収支を予測するという方法です。
決定木は下の図のように入力データを「○○の値が××以上かどうか」などの
条件で分類していき、収支を分類していく方法です。この方法であれば、「3が出やすく9が出にくければ収支が正になりやすい」といった意味のわかる分類結果が得られるものと思ったのですが、1ゲームでの分類結果は46%にとどまってしまいました

ここで今後のプランをまとめます。
・1ゲームでなくデッキにおける収支を予測する手法に切り替える必要がある
・クラスタリングでグループ分けしたグループの特性を見ていく
この二つで結果が出なければ、まったく別の手法が必要になってくると思います。
3/25
・データ処理の準備
残り枚数により山札が同じものか別のものか分ける
同じ山札を連続して使った時の全体の収支
同じ山札で考えるために、データの前処理をしました。
4/2
山札の情報を直接入力して予測してしまうと、山札の状態の変化を追うことができません。
よって、山札の状態を下の図のように考えるようにします。この図の変化を見ていくことで、収支が正になりやすい形の図を見ることができないかと考えました。

ここで山の形を単純にするために、1率~10率を確率の高い順にソートすることにしました。これにより、例えば収支が-100になりやすい順番を見つけることができます。
10率は10,J,Q,Kを含んでいて、一番確率が高くなるのは当然なので今回は除外します。
それ以降の順番を見ていくことにします。
収支が-100になりやすい場合に注目して順番を見てみたところ、高確率トップ3の一部はこのようになりました。

今後はこの順番に注目して、規則性を見つけて行こうと考えています
4/08
新規データに移行して分析を行いましたが、結果はあまり変わりませんでした。やはり、数%しか変動しない状態では、一ゲームで収支を予測することは難しいのではないかと思います。全体で相関をとってみたところ、1,2,7が出現する確率が高いほど収支が上昇するという関係がありそうだったので、三つの出現確率の合計値が一定を超えた場合に関して収支の合計をとってみましたが、合計が+1100程度であり、大きなマイナスは回避するもののプラスを予想できるものではありません。
今後は局所的な収支合計パターンの抽出と、山札全体の収支での分析をしていこうと思います。
4/10
様々な収支の値がある中で、ほとんどが100か-100であるため、こちらの判別を優先して行うように学習させるようにします。
データセットと収支が100、-100のみ抽出し、さまざまなパラメーターを調整して100か-100を見抜けるように学習させるようにします。
パラメーターは入力→出力データの判別基準を調整しているものです。
最終的に
予測された+100の数→最大
+100と予測し実際は-100だった数→最小
この条件で最適化する方向でパラメーターを最適なものに調整していきます。
4/11
±200と+150がプレイヤーにとって有利とのことから、
±200と+150の集団を有利ラベル、それ以外を不利ラベルとして規定し、まずは入力確率のデータからうまくラベル分けできる条件があるかどうかを探していきます。
その結果、
テストデータ 7331個に対してラベリングミスが 966個という結果になりました。
これで有利か不利か見分けられていると思ったのですが、実際に学習内容を見てみたら「不利ラベルが多いからとりあえずほとんど不利ラベルにしよう」と学習をさぼっていました。
今後必要となる作業は以下になります
・±200と+150の有利条件で収支が上がるか検証
・先日の最適化を書き換えて、
予測された有利ラベルの数→最大
有利ラベルにおけるラベリングミス→最小
この条件でパラメーターを最適化していきます。
4/13
予測された有利ラベルの数、有利ラベルにおけるラベリングミスをカウントする部分を作りました。
また、パラメーターを調整する部分を作りました。
パラメーター調整部分を試しに適応して見たのですが、調整するパラメーターの数とデータ数が多く、計算に多大な時間がかかりました。
ちなみに今回使っている分類方法はランダムフォレストです。これは以前に説明した決定木が複数集まって判別を行うものなのですが、より高速な方法を用いて高速化するか、調整するパラメーターの数を減らすかの対応をしようと考えています。(現在のパラメーターは木の深さや重みの重要度などたくさんあります)
4/14
パラメーターを減らしても無限に時間がかかっているので、小さなデータで学習してそれを全体に適用するような構造にしようと考えています。
最適化する部分を収支の最大化として組み込む必要があるので、学習を進める上で最適化するスコアを組み込む部分を作りました。腹痛のため断念してしまいましたので、作業時間は短めです。
4/15
有利であるという予測をしたゲームにおいて、それが本当に有利である数を最大化するように学習させる部分を作りました。ここでいう有利とは±200、+150が出現する場合を言います。実際に±200、+150の収支を確認すると、収支が大きく正になっていたことからこちらを有利ラベルとしました。
ただし仮定として、
±200、+150の状態(有利状態)とその他の状態(不利状態)が判別可能である
という条件がつきます。
通常のパラメーター最適化は、正しいラベルがついたかつかなかったかを見て学習しています。しかし、不利状態が多いことから、ほとんどを不利状態判別してしまうという問題がありました。そこで、有利状態の判別結果を最大化するように学習させて、
できるだけ有利状態を多く有利状態と見抜けるか
を見ることによって、多くの有利状態を予測できるのではないかと考えています。

ここまでをまとめると、
1、有利ラベル(±200,150)、不利ラベル(それ以外)を判別できるように学習する
2、さぼらないように有利ラベルを多く予測できるようにする
3、有利ラベルの特徴をみる
4、有利ラベルと予測した結果の実際の収支を見る
5、収支が正なら有利ラベルは実用性がある
部品ができてきたので明日以降は組み立てて2の調整にはいります。
ただ、パラメーターの調整になるのでいろいろと試行錯誤が必要になるかと思われます。
4/19
前述の2にあたるパラメーターを調整していきましたが、なかなか良いものが見つかりませんでした。しかし、わずかですが有利ラベルを見つけ出してくれるものが出てきました。
9163データのうち11データにおいて有利ラベルとして判定してくれたパラメーターを発見したので、とりあえずこちらの11データを分析していきました。こちらの11データをみてみると、すべて収支が(±200,150)だったことから、この学習法による判定は(±200,150)の特徴をとらえていると考えられます。
まずは、可視化してみたのですが、目で見てわかるような特徴は見つかりませんでした。

学習の中身を確認していくことで、有利状態の特徴がわかることと思います。
ランダムフォレストの学習の中身は

こんな感じになっているので、どんな条件でこの分類をしているのか見ていくことができます。
今後:
・有利判定されたデータが11/9163では実用性がないので、分岐する条件を少しだけゆるめることで、もっと多くのデータが有利ラベルに分類されるようにする。
・それでうまくいかなかったら引き続きパラメーターを探していく、またはランダムフォレスト以外を使う
4/21
ランダムフォレストの中身を見ようとしてたのですが、可視化する際にいろいろと環境を整えたりして意外と手間がかかってしまいました。

一本の条件分岐の木をみてみると、このような感じでした。
本日はあまり詳しく踏み込みませんでしたが、青い部分が有利とみなしたゲームかと思われます。
今後もう少し踏み込んで内容を見ていこうと思います。
4/22
分類していく上で、どの項目を重要とみなしているかを見てみました。
card:1 : 0.088599
card:2 : 0.103579
card:3 : 0.106684
card:4 : 0.100711
card:5 : 0.097112
card:6 : 0.105345
card:7 : 0.091543
card:8 : 0.100563
card:9 : 0.091057
card:10 : 0.114808
これをもとに、8,10の出現率を調整して、収支が最大になるものはないかどうか探していきます。
この時、
できるだけ多くのゲームで有利判定可能であり、できるだけ収支を多くするように探します。
先日の木の分類をもとに、手動で条件をゆるめて探していきます。
card:10,"<0.33"
card:8,"<0.07"
という条件で収支を見てみると、
データ総数 18327
判定数 2903
収支 +4750
こちらは手動で条件を作ったものなので、条件を満たすように確率の範囲を調整するプログラムを組み込むことで、よりよいものができると考えられます。
10以上がの出現率が低い時、という条件になっていますが、こちらは部分的な解を抽出しただけになります。

さらなる条件を加えることで、よりよい解を見つける必要があります。
4/27
以前抽出した条件から、さらに別の条件を引き出します。
前に出した重要度の高いもの(3,6,10)に注目して実行した結果、以下のような条件で収支が正になっていました。総数は前回と同じです。
card:10">0.303" and card:3">0.075"
→
判定数 6820
収支 +8600
card:10<0.303 and card:3<0.074 and card:6<0.081
→
判定数 852
収支 1850
とりあえずは、これらの2条件と前回の1条件を最終的な有利条件とし、これ以降はこの条件がどれほど実用的かを判定していきます。
また、さらなる条件の抽出もできる限り行います。
5/1
前回までに抽出した3条件をもとに、三つのいずれかを満たす場合の収支について合計しました。
総数 18327
有利判定数 9441
有利判定収支 +11100
この結果は、以前の木の構造から手動で条件を見つけた結果になります。
本日作成したのは、条件を与えてその収支を合計するという構造です。
こちらの構造を再利用して、逆に収支が大きくなるような条件を微調整させることができるので、今後はそれによってさらなる条件を見つけていきたいと考えています。
5/9
しばらく時間がたってしまいましたが、ちょくちょく最適化方法を考えていました。
本日実際にコードを書いて見ることにしました。
以前の抽出で、二つのカードの確率で収支を十分絞れているようだったので、二つの確率を領域分割されたマップに写し、各領域の状態をベイズ推定で求めるというものです。
例えば、+100,-100,±200&150の三つの状態を仮定し、区切られた領域内においてどの状態になりやすいかを確率で求めます。
これにより、収支が高くなりそうな領域を見つけることができます。
前回までやっていたランダムフォレストの結果を見て手動で条件を決めるよりも、客観的で高精度な分類が期待できます。

このように、領域を分割し、データをプロットしていきます。
区分された領域内のプロットは、状態のラベル(+100か-100か±200&150か)を持っているので、ある領域内でど状態になりやすいかを確率で決めることができます。
これにより、局所的に収支が増加する部分においても評価できます。
図では2つの確率を見ていますが、この手法では3つ以上でも評価できます。ただ、その分だけ評価する領域の数が多くなるので、計算量がかかってしまいます。
本日は領域分割と各領域のプロット数を求める部分を作ったので、次はその領域の評価を行っていきます。
5/12
各領域での収支の特性を確率で見てみました。
とりあえずcard:1 card:10において、分布を見てみると、+100,±200&150の出現確率が高く-100の出現確率が低い部分がありました。
+100,±200&150になりやすい領域を洗い出し、二つの確率をいろいろと組み合わせて見ていくことで、複雑で精度の高い収支最大化条件を見つけることが可能です。
5/13
前回は領域を分割してそれぞれの確率分布を抽出しました。
本日は、領域を指定するとそこに含まれるデータを取り出す部分を作成しました。
これにより、例えば局所的に100になりやすい領域のデータ等を取り出すことができます。
これにより取り出されたデータの収支を見て、これが大きければ収支の大きい領域を二つの確率から求められたことになります。
あとは、二つの確率の組み合わせを変えて見ていきます。
5/14
10の出現率とそのほかの出現率をそれぞれペアにして、確率的に収支が高くなりそうな領域を探していきます。

下は各確率での各領域での収支です。
今回は暫定的に{-100になる確率が30%以下}という条件で領域を決定しました。
その結果、
テストデータ数 18327
領域ヒットデータ数 5245
全合計収支 41600
でした。
これはデータの一部を用いて領域を学習した結果になりますので、この領域を全データに適応して評価する必要があります。このテストで高い識別率が出れば、実際のゲームでも実用性があると言えます。
テストしてみましたが、調整しても収支は+4100程でした。
過学習が起きていると考えられます。
訓練の時は正確に識別できますが、新たなデータが入ってきた時にうまく適応できていないということになります。
今回はcard10とそのほかという組み合わせでしか実装できていないため、次回はそれ以外の組み合わせも含められるようにし、精度を向上しようと考えています。
また、領域の選定方法も変更することで、精度を高められると思います。
5/15
card10以外の二つの組み合わせでも試してみました。
また、訓練に用いるデータを増やしました。
しかし、テストデータ7000程で収支は+3400でした。
モデルをよくする調整できるパラメータとしては、領域での確率の閾値になります。
例えば、100がどれくらい出にくいかというパラメータを自動調整することで、収支がより高くまとめられます。
また、領域内のデータ数が少ない場合にも過学習が起きやすいと考えられるので、ある程度母数の多い領域を抽出していくことも有効と考えられます。
5/17
まず、前回のテストデータの収支の検定方法が甘く、収支+3400の計算方法は適切でありませんでした。

前回までのこの方法では、”良い領域”を抽出するのに訓練データの領域分割のみで判定していました。この考え方を拡張し、領域の指定を判定機を二つ作り、互いに領域を監視するようにしました。
試行錯誤の末、以下のようなモデルを作成しました。

フェーズ1:
訓練データを二つに分割し、領域マップを二つずつ作成しました。
これにより、一つの時と比べてより洗練された領域が抽出されるはずです。
フェーズ2:
フェーズ1で作成した二つのマップの論理積をとります。これにより、二つの判定機に認められなければ、領域として認められなくなりました。
また、作成した領域を、別の訓練データに適用し、領域内での収支を求めます。
これにより出てきた収支が負であれば、テストデータでも負になることが予想されます。
よって、互いに別の領域マップで収支を計算し、互いに大きく負にならない時にのみ、マップを学習機として組み込みます。
例えば、card1とcard2について、別の学習機で算出した収支が(A:+100,B,-2000)だった場合、card1とcard2の組み合わせを真の学習機として取り込むと、収支が負になる危険性が大きいですので、テストデータの学習にはcard1とcard2の情報は使わないことにします・
フェーズ3:
一連の流れで抽出された学習機を使って、テストデータを判別していきます。
これにより
テスト総数: 7442
ヒット数 1067
全合計収支 5250
となりました。
今回の手法により、過学習による結果のばらつきはある程度抑えられたと思われますが、この結果にどれほどの有用性があるのかを、数値により検証したいと思います。
今週末で一区切りさせる約束なので、明日の夜の結果でとりあえずは終了のご報告ができればと思います。
5/18
データ数を増やして学習を再度試してみました。
パラメーター(確率の閾値)の調整を行うと実行時間がかなりとられてしまうので、一度固定して何度か試行してみました。
良好なテストでは、テストと訓練を分け方の確率値を変更しても、結果の変動が少ないのですが、これを変えて見ると結果がかなりばらつき、学習によって収支が正になっていると言い切れない状況でした。
よって、安定的に収支が正になっている二つのcardの組み合わせをいろいろと見ていくことにしました。

前回のこの図の
フェーズ3のテスト結果を、cardの組み合わせごとにひとつづつ見ていく十言うことになります。
その結果、[card1とcard10]での組み合わせにおいては、データの分割方法や、テストデータの数を変えても安定的に収支が正になっていることが確認されました。下が結果の一例です。
テスト総数: 23434
ヒット数or 956
全合計収支or 1250
領域指定方法は、+100,±200&150の出現確率が0.17以上になっています。
この条件ように、+100,±200&150の出現確率とcard1,card10の組み合わせには収支を正にする関係性があると言えます。
そちらの領域条件は取得できているので、mapで提示していこうと思います。
本日作業終了予定でしたが、学習に思いのほか時間がかかってしまったので、mapでの提示等は明日以降にさせていただきたいと思います。
ただ、学習機の実装は完了したので、これ以降はそれほど作業時間はかからないと思います。
5/18
+100,±200&150の出現確率が0.17以上という条件のもと、領域の分布を作成すると以下のようになりました。

前回のこの図の
フェーズ3のテスト結果を、cardの組み合わせごとにひとつづつ見ていく十言うことになります。
その結果、[card1とcard10]での組み合わせにおいては、データの分割方法や、テストデータの数を変えても安定的に収支が正になっていることが確認されました。下が結果の一例です。
テスト総数: 23434
ヒット数or 956
全合計収支or 1250
領域指定方法は、+100,±200&150の出現確率が0.17以上になっています。
この条件ように、+100,±200&150の出現確率とcard1,card10の組み合わせには収支を正にする関係性があると言えます。
そちらの領域条件は取得できているので、mapで提示していこうと思います。
本日作業終了予定でしたが、学習に思いのほか時間がかかってしまったので、mapでの提示等は明日以降にさせていただきたいと思います。
ただ、学習機の実装は完了したので、これ以降はそれほど作業時間はかからないと思います。
5/18
+100,±200&150の出現確率が0.17以上という条件のもと、領域の分布を作成すると以下のようになりました。

横軸が訓練とテストの選び方の変化、縦軸が収支です。
前回のテスト結果において+1800と出ていましたが、これは横軸を一つに固定して出た結果です。前回の結果の点はこちらです。

モデルとして、card1とcard10の関係において、±200,150の出現確率が0.17以上の空間を学習させています。
この時、収支の期待値は+259で該当データ数の平均値は273でした。
また、収支が正であるプロット数は54/100でした。
つまり、このモデルでは収支が正になる可能性が高いものの、負になるリスクもかなりあるということになります。
ちなみに、±200,150の出現確率が0.17以上という空間学習のモデルにおいて、card率を二つ選ぶときに、適当に3と10のように選んでみると、結果は以下のようになります。

このように、データの選び方を横軸で変えていっても、収支は主に負となります。
この図と比較すると、先ほどのcard1とcard10の結果は、収支が正に傾いていると言えていると思います。
card1とcard10以外に、収支が正になりやすそうな組み合わせをいろいろと探してみると、card7とcard8の空間で学習した際にも、正に傾いていることがわかりました。

ブラックジャックのゲームの性質上、card1とcard10の組み合わせの比率によって、ある程度収支に影響があると思っていたのですが、なぜかcard7とcard8の空間において±200,150の出現確率が0.17以上という条件で学習させると収支が正のパターンが多く出現していました。
こちらは、該当データ数の平均が106で収支の期待値が379でした。また、収支が正であるプロット数は62/100でした。
ちなみに学習された領域はこのような感じでした。

現状としましては、card7とcard8においては、上記の領域での収支の期待値が正になりやすく、card1とcard10におきましては、前述したこちらの領域において収支が正になりやすいという結論になります。

こちらのマップが黄色の領域が収支が正になりやすいので、これを文章化すると、

このようになります。例として一行目は
0.11<card1≦1かつ0<card10≦0.25
という条件になります。
訓練とテストの選び方を変えるとは、下図のようなイメージになります。

また、収支が正になりやすいと判定されたcard7とcard8に関しては以下のグラフになります。


ペイアウト率ですが、収支が正になるという上記の条件を満たすならばゲームを行い、それ以外であればゲームをしないという条件で計算します。
card1&card10、card7&card8の条件が合致した場合に、ゲームを行った時のペイアウト率を計算する部分を作るので、そちらはもうしばらくお待ちください。
6/10
ペイアウト率の計算ですが、今回の分析では残りの山札からのカードの出現割合でのみゲームを行うか判断していおり、こちらがどれだけベットするかという条件は盛り込んでいません。
よって、「山札の割合が学習した領域(±200か+150の出現確率が0.17以上)であれば勝ちやすいと判断し、ゲームを行う。それ以外であればゲームを行わない」という判断でゲームを行っています。
このような判断でゲームを行った場合の収支の期待値をexcelデータで共有します。
データの説明
こちらからわかるように、ヒット数(ゲームを行うと判断した回数)がテスト総数に比べてかなり低いことがわかると思います。
これは、有利な状態になるまでずっと待ち続けなければならず、有利になる場合がごくまれであることを表しています。
今回の分析をまとめますと、
1~10のcard割合を複数考慮し、収支が正になりやすい条件を探した
→条件が複雑になりすぎてしまい、よい結果が得られなかった
cardの二つの組み合わせに注目し、収支が正になりやすい組み合わせと領域を探した
→card1&card10とcard7&card8において、テストデータを変えても収支が正になりやすい領域があった(結論)
→しかし、ヒット数が少ないので、実際にゲームを行うときにこの判断が発動するのはまれ
といった形になります。