はじめに
機械学習も Python もまともに触ったことないし、数式にも馴染みがないけど
時代に取り残されないために勉強がてら scikit-learn を使ってロト6 の当選番号を予測してみます
一応この本の 8 章までは読んでます www.oreilly.co.jp
ロト6 を選んだのは、なんとなく実装が簡単そうだったからです
ちなみに「ロト6 機械学習」でググっても予測は難しいと言ってる人がほとんどなので、予測の難易度はかなり高いと思われます
あくまで勉強の一環です
ロト6 概要
一応ロト6 の簡単な概要
- 1〜43 の中から 6 つの数字を選び、抽選結果の数字と一致している数によって当選金が分配される
- 1 等約 2 億円(約と言ってるのは当選人数やキャリーオーバーによって変動するため)
- 年中無休で購入可能
- 抽選日は毎週月曜と木曜
機械学習モデルの選択
回帰モデルで訓練データが少なくてもいけそうなやつがいいかなって思ってましたが、ググってるとサポートベクターマシン(SVM)で予測されてる方が何人かいたので scikit-learn の SVR クラスを使うことにしました
カーネルトリックを使ってみたいと思って、訓練データが少ない場合は、ガウス RBF カーネルが良さそうなのでそれを使います
訓練データは過去のロト6 の当選番号全てです
アウトプットは次回の抽選回の当選番号予測です
また、ボーナス数字の予測はしません
訓練データの作成
本数字 1〜6 をそれぞれ別の訓練データとして分割しています
最初は手っ取り早く過去の当選番号を全て 1 つの訓練データとして訓練させようとしてましたが、やめました
訓練データを作成するプログラム
import pandas as pd url = 'http://sougaku.com/loto6/download/loto6.zip' train = pd.read_csv(url, encoding='cp932') X = train['抽せん回'] X_train = X[:, np.newaxis] y_train = [train['本数字1'].values, train['本数字2'].values, train['本数字3'].values, train['本数字4'].values, train['本数字5'].values, train['本数字6'].values] next_round = X.tail(1).values[0] + 1
X_train は過去の抽選回全て
y_train は本数字ごとの過去の当選番号全て
next_round は予測を行う回(つまり次回の抽選回)
です
学習と予測
以下のコードは本数字 1 のみを学習(fit)して、予測(predict)するプログラムです
本数字は 6 つあるので、ループするなどして計 6 回学習と予測を実行します
from sklearn.svm import SVR svr_rbf = SVR(kernel='rbf', gamma=..., C=..., epsilon=...) svr_rbf.fit(X_train, y_train[0]).predict(np.array([[next_round]]))
γ、C、ε の 3 つのハイパーパラメータを調整して実行します
ハイパーパラメータの最適化
scikit-learn のグリッドサーチを使えば簡単にできます
SVM や SVR のハイパーパラメータの範囲は広いので 2 のべき乗の値をパラメータの候補として設定するそうです
パラメータ | 候補の範囲 | 候補の数 | Python で書くと |
---|---|---|---|
γ | 〜 | 31 | np.logspace(-20, 10, 31, base=2) |
C | 〜 | 16 | np.logspace(-5, 10, 16, base=2) |
ε | 〜 | 11 | np.logspace(-10, 0, 11, base=2) |
だいぶ絞られましたが、それでも 3 つのパラメータの組み合わせは31 x 16 x 11 = 5456
通りもあります
以下のコードは本数字 1 のみを対象にグリッドサーチを使い、5456 通りのパラメータの組み合わせ全てを学習・評価し、精度が良いものを出力する例です
ただし、学習データの量にもよりますが、かなり時間がかかる(むしろ終わらない)ため、ある程度 5456 通りから更に候補を絞る必要は出てきます
param_grid = { 'kernel': ['rbf'], 'gamma': np.logspace(-20, 10, 31, base=2), 'C': np.logspace(-5, 10, 16, base=2), 'epsilon': np.logspace(-10, 0, 11, base=2) } grid_search = GridSearchCV(SVR(), param_grid, iid=False, cv=5) ### かなり時間がかかるため、パラメータの候補を更に絞った方が良い ### grid_search.fit(X_train, y_train[0]) gs = grid_search.best_params_ print('grid-search result: {}'.format(gs)) # grid-search result: {'C': 32.0, 'epsilon': 0.5, 'gamma': 1.0, 'kernel': 'rbf'}
予測と当選結果確認をスケジューリング
毎回プログラムを実行したり当選結果を確認するのは面倒なので、ある程度自動化しておきます
以下のコードは、ロト6 の当選番号が載ってるサイトから抽選回、当選番号、ボーナス番号をスクレイピングしています
import bs4 import re from urllib.request import urlopen url = 'http://www.takarakuji-loto.jp/tousenp.html' html = bs4.BeautifulSoup(urlopen(url).read(), 'html.parser') section = html.find('section', class_='tousenno') tbody = section.find_all('tbody') current_round = re.match('第[0-9]+回', section.find('div').string).group() winning_number = [i.get('alt') for i in tbody[0].find_all('img')] bonus_number = tbody[1].find('img').get('alt')
以下のコードは、CircleCI のスケジューリング機能を使ってロト6 の当選番号を予測するプログラム(svr.py)を呼ぶ predict ジョブと当選結果をスクレイピングするプログラム(res.py)を呼ぶ result ジョブを設定しています
毎週月曜と木曜が抽選日なので、predict ジョブは前日の日曜日と水曜日に実行し、result ジョブは翌日の火曜日と金曜日に実行するようにしています
references: commands: setup-docker: &setup-docker docker: - image: kurosame/circleci-python version: 2 jobs: predict: <<: *setup-docker steps: - checkout - run: name: Predict command: python3 svr.py result: <<: *setup-docker steps: - checkout - run: name: Get result command: python3 res.py workflows: version: 2 nightly-predict: triggers: - schedule: cron: '00 0 * * 0,3' filters: branches: only: - master jobs: - predict nightly-result: triggers: - schedule: cron: '00 0 * * 2,5' filters: branches: only: - master jobs: - result
CircleCI で使っている Docker は以下です
Python3 とプログラム実行に必要なライブラリをインストールしたイメージを使っています
hub.docker.com
また、各プログラムの最後で Slack に通知するように実装しています
import slackweb url = 'https://hooks.slack.com/services/...' # SlackのWebhook URL slackweb.Slack(url).notify(text='ここに予測結果とか当選番号とか色々設定しておく')
流れ的には
- 日曜日と水曜日に predict ジョブが cron 実行され、Slack に予測結果を通知
- ロト6 を購入
- 月曜日と木曜日に抽選が行われる
- 火曜日と金曜日に result ジョブが cron 実行され、Slack に当選結果を通知
さいごに
現在 2 回予測結果でロト6 を購入しましたが外れました(2 回とも 2 個までは数字は的中しました)
とりあえずランダムで買った時とあまり結果が変わらないなーって思ったらやめます
ちなみに次は競馬予測をやってみようかなと思ってます