はじめに
機械学習も Python もまともに触ったことないし、数式にも馴染みがないけど
時代に取り残されないために勉強がてら scikit-learn を使ってロト6 の当選番号を予測してみます
一応この本の 8 章までは読んでます
www.oreilly.co.jp
ロト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))
予測と当選結果確認をスケジューリング
毎回プログラムを実行したり当選結果を確認するのは面倒なので、ある程度自動化しておきます
以下のコードは、ロト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/...'
slackweb.Slack(url).notify(text='ここに予測結果とか当選番号とか色々設定しておく')
流れ的には
- 日曜日と水曜日に predict ジョブが cron 実行され、Slack に予測結果を通知
- ロト6 を購入
- 月曜日と木曜日に抽選が行われる
- 火曜日と金曜日に result ジョブが cron 実行され、Slack に当選結果を通知
さいごに
現在 2 回予測結果でロト6 を購入しましたが外れました(2 回とも 2 個までは数字は的中しました)
とりあえずランダムで買った時とあまり結果が変わらないなーって思ったらやめます
ちなみに次は競馬予測をやってみようかなと思ってます