LoginSignup
6
3

More than 3 years have passed since last update.

MATLABで競艇の解析を始めよう(教師あり学習)

Last updated at Posted at 2019-08-21

MATLABでボートレース解析をする5番目の記事です。

 matlab_jp のツイッターの人から「いいねついた?」「競艇の解析まだ?」「何番目まで続くの?」って煽られるようになってしまった。。

 「自分から投稿しますから!押さえつけないでください!後ろを張りついてきてください!」って返事しておこう。。

(という時期に書いております。)

ここまでの振り返り。

 1番目の記事でダウンロードができて、2番目の記事で出走表を読み込んで、3番目の記事で結果を読み込んで、4番目の記事で配当データを取りました。

大体のデータは取れたので、学習をさせてみよう。

 学習をさせる場合、まず何がほしい結果なのかを明確にしないとダメですね。今回はわかりやすく着順(1,2,3,4,5,6着)にしてみましょう。「今日の出走表を入れるだけで着順が出てきた!」というのが理想なので、理想に向かって進みましょう!

テーブルがバラけている問題

 各記事毎に取ったデータだと、全部バラバラなのでテーブルを連結させちゃいましょう。T_Person っていうテーブルと T_Result の Finish があればいいよね。MATLAB だと innerjoin っていう超便利な関数があります!

今までのを全部ひっくるめたらこんなプログラムになる。

 ファイルがダウンロードされてるんなら、こんなプログラムになるかな。

boat_scan.m
function T = boat_scan(since,to)

addpath('C:\work\boat')
tic

%% テーブルを作ろう
T = table;

%% ループ

for ix = datenum(since,'YYYYmmDD'):datenum(to,'YYYYmmDD')
    %% 出走表の読み込み
    fname = ['B',datestr(ix,'YYmmDD'),'.TXT'];
    if exist(fname,'file') == 2    % 地震で 2011/3/12 から 3/31 あたりがない。
        fid = fopen(fname);
        S = textscan(fid,'%s','delimiter','\n');
        B = S{1};
        fclose(fid);
        B = B(~cellfun(@isempty,(regexp(B,'^\d \d+'))));
        Personal = cellfun(@scanB,B,'Unif',0);
        Personal = unique(cell2mat(Personal),'rows');
        T_Person = array2table(Personal,'VariableNames',{'NameNo','Age','Weight','Class','A_WinRate','A_DoubleWinRate','L_WinRate','L_DoubleWinRate','Motor','M_DoubleWinRate','Boat','B_DoubleWinRate'});

        %% レース結果の読み込み
        fname = ['K',datestr(ix,'YYmmDD'),'.TXT'];
        fid = fopen(fname);
        S = textscan(fid,'%s','delimiter','\n');
        C = S{1};
        fclose(fid);

        %% BGN/ENDを探す
        KBGN = find(contains(C,'BGN'));
        KEND = find(contains(C,'END'));

        %% BGN-END Loop
        for BEloop = 1:length(KBGN)

            T_Result = table;
            %% BGN-ENDで切り取る
            res = C(KBGN(BEloop)+1:KEND(BEloop)-1);
            res(cellfun(@isempty,res)) = [];

            % 場所
            place = res{1}(1:3);
            place(place == 12288) = [];

            % 開催日と今何日目
            ymd = datestr(ix,'YYYYmmDD');
            idx = regexp(res{1},'第 \w日','match');
            if ~isempty(idx)     % 2018/01/16 の 2525行目にダメデータがあるのでスキップ
                day = str2double(idx{1}(end-1));

                start_idx = startsWith(res,'着');
                x = find(start_idx)-1;
                end_x = [x(2:end)-1;length(res)];
                n = length(x);

                % ここからレースのループ
                for Rloop = 1:n
                    % レース番号
                    race = res(x(Rloop):end_x(Rloop));
                    oddsidx = find(contains(race,'単勝'));
                    if ~isempty(oddsidx)
                        race_detail = race(1:oddsidx-1);
                        odds_detail = race(oddsidx:end);
                        txt = textscan(race_detail{1},'%s');
                        R = txt{1}{1};  % レース番号
                        R = str2double(R(1:end-1));
                        R_NAME = txt{1}{2};   % レース名
                        R_NAME(R_NAME == 12288) = [];

                        midx = find(~cellfun(@isempty,regexp(txt{1},'H\d+m')));
                        LENGTH = txt{1}{midx};
                        LENGTH = str2double(LENGTH(2:end-1));    % H1800m の H/m を捨てる
                        WEATHER = txt{1}{midx+1};
                        WEATHER(WEATHER == 12288) = [];
                        midx = find(~cellfun(@isempty,regexp(txt{1},'風')));
                        WINDDIR = txt{1}{midx+1};
                        WINDDIR(WINDDIR == 12288) = [];
                        WINDPOW = txt{1}{midx+2};
                        WINDPOW(WINDPOW == 12288) = [];
                        WINDPOW(end) = [];
                        WINDPOW = str2double(WINDPOW);
                        midx = find(~cellfun(@isempty,regexp(txt{1},'波')));
                        WAVE = txt{1}{midx+1};
                        WAVE(WAVE == 12288) = [];
                        WAVE(end-1:end) = [];
                        WAVE = str2double(WAVE);

                        % 着順
                        FIN_idx = find(~cellfun(@isempty,regexp(race_detail,'^0\d')));
                        for iy = 1:length(FIN_idx)
                            rc = textscan(race_detail{FIN_idx(iy)},'%s');
                            FRAME = str2double(rc{1}{2});
                            NAME = str2double(rc{1}{3});
                            MOTOR = str2double(rc{1}{5});
                            BOAT = str2double(rc{1}{6});
                            EXTIME = str2double(rc{1}{7});
                            ENTRY = str2double(rc{1}{8});   % 進入コース(本番)
                            FINISH = str2double(rc{1}{1});
                            T_Result(end+1,:) = {place,ymd,day,R,R_NAME,LENGTH,WEATHER,WINDDIR,WINDPOW,WAVE,FRAME,NAME,MOTOR,BOAT,EXTIME,ENTRY,FINISH};
                            T_Result.Properties.VariableNames = {'Place','YYYYMMDD','Day','R','RaceName','Distance','Weather','WindDir','WindPow','Wave','Frame','NameNo','Motor','Boat','ExtTime','Entry','Finish',};
                        end
                    end
                end
                if isempty(T_Result)
                elseif isempty(T)
                    T = innerjoin(T_Result,T_Person,'Keys','NameNo');
                else
                    T = [T;innerjoin(T_Result,T_Person,'Keys','NameNo')];
                end
            end
        end
    end
    t_toc = toc;
    fprintf(1,[num2str(height(T)),'行処理済み [',datestr(ix,'YYYY/mm/DD'),']:',num2str(t_toc),'(秒)経過',10]);
end
T.Properties.VariableNames{13} = 'Motor';
T.Properties.VariableNames{14} = 'Boat';
T.Motor_T_Person = [];
T.Boat_T_Person = [];

T.Place = categorical(T.Place);
T.Day = categorical(T.Day);
T.R = categorical(T.R);
T.Weather = categorical(T.Weather);
T.WindDir = categorical(T.WindDir);
T.Frame = categorical(T.Frame);
T.NameNo = categorical(T.NameNo);
T.Motor = categorical(T.Motor);
T.Boat = categorical(T.Boat);
T.Entry = categorical(T.Entry);
T.Finish = categorical(T.Finish);

T.Class = categorical(T.Class);
T.Age = categorical(T.Age);

end

function sc = scanB(txt)

NAME = str2double(txt(3:6));
MOTOR = str2double(txt(41:43));
BOAT = str2double(txt(50:52));
AGE = str2double(txt(11:12));
WEIGHT = str2double(txt(15:16));
if strcmp(txt(17:18),'A1')
    CLASS = 1;
elseif strcmp(txt(17:18),'A2')
    CLASS = 2;
elseif strcmp(txt(17:18),'B1')
    CLASS = 3;
else
    CLASS = 4;
end
A_WINRATE = str2double(txt(19:23));
A_DOUBLEWINRATE = str2double(txt(24:29));
L_WINRATE = str2double(txt(30:34));
L_DOUBLEWINRATE = str2double(txt(35:40));
M_DOUBLEWINRATE = str2double(txt(44:49));
B_DOUBLEWINRATE = str2double(txt(53:58));
sc = [NAME,AGE,WEIGHT,CLASS,...
    A_WINRATE,A_DOUBLEWINRATE,...
    L_WINRATE,L_DOUBLEWINRATE,MOTOR,M_DOUBLEWINRATE,...
    BOAT,B_DOUBLEWINRATE];
end

実行してみよう。

 2019/8/1 から 8/3 までだったら、コマンドウィンドウでこう。

>> T = boat_scan("20190801","20190803");

932行処理済み [2019/08/01]1.3405()経過
1866行処理済み [2019/08/02]2.6928()経過
2854行処理済み [2019/08/03]3.9852()経過

 念のため確認。

>> T


T =

  2854×26 table

    Place     YYYYMMDD     Day    R       RaceName      Distance    Weather    WindDir    WindPow    Wave    Frame    NameNo    Motor    Boat    ExtTime    Entry    Finish    Age    Weight    Class    A_WinRate    A_DoubleWinRate    L_WinRate    L_DoubleWinRate    M_DoubleWinRate    B_DoubleWinRate
    _____    __________    ___    __    ____________    ________    _______    _______    _______    ____    _____    ______    _____    ____    _______    _____    ______    ___    ______    _____    _________    _______________    _________    _______________    _______________    _______________

    唐津     '20190801'     4     7     '一般'             1800                北東           5        5       2       4224      21      59       6.81        2        1       37       48        3        3.95            23.16           4.65            27.03              30.84              27.03     
    唐津     '20190801'     4     10    '準優進出戦'        1800                 北東          4        4       3       4240      68      56       6.78        3        1       35       45        3        4.08            22.62           3.63            15.79              37.02              43.06     
    唐津     '20190801'     4     9     '準優進出戦'        1800                 北東          5        5       4       4286      55      63       6.81        4        4       35       50        3        5.25            31.93           5.73            54.55              33.91              39.73     
    唐津     '20190801'     4     4     '一般'             1800                             4        4       1       4289      34      53       6.79        1        5       34       48        2        5.98            44.65           5.83               50              39.64              26.58     
    唐津     '20190801'     4     11    '準優進出戦'        1800                 北東          4        4       4       4313      49      85       6.85        4        3       35       50        2        5.71            36.62           6.79            51.28              25.96              26.58     
    唐津     '20190801'     4     11    '準優進出戦'        1800                 北東          4        4       6       4338      30      81       6.76        6        6       35       45        4        3.59            12.96              0                0              33.48              39.19     
    唐津     '20190801'     4     10    '準優進出戦'        1800                 北東          4        4       2       4347      15      60       6.84        2        4       34       44        2        6.24               48           5.56            33.33              44.04              34.25     
    唐津     '20190801'     4     11    '準優進出戦'        1800                 北東          4        4       5       4349      35      82       6.85        5        5       33       49        3        5.08            24.53           4.46            17.86              32.88              25.93     
    唐津     '20190801'     4     6     '一般'             1800                             3        3       4       4372      13      46       6.75        5        2       31       47        1        5.46            36.47              0                0              31.72              23.29     

 順番がバラバラになっちゃったけど Finish も入ってるしよさそう!

ここからが学習。

 MATLAB だとアプリを使えばいいよね。(便利!)
 [アプリ] → [分類学習器] っていうのを起動して T を読み込ませよう。応答は Finish を選んでセッションの開始!
TR_app1.png

 とりあえず試しに、[すべてのクイック学習] とかを選んで「学習」ボタンをクリック。
image.png

一番良くて精度 23.3%!
なんか低くない?

3日分しか取ってないからしょうがないとして、これを使って今日のレースを予想してみよう。

 とりあえずエクスポートボタンを押しておきましょう。デフォルトだと trainedModel とかいう構造体が出てきますね。

 あとは今日の出走表を取ってデータを入れればいいんだけど、出走表になくて結果にあるデータが入っちゃってるので(天気とか風とか)、適当に埋めましょう。

 今日の出走表のダウンロードから始めるとして、こんなプログラム。

boat_today.m

function T_today = boat_today
fname = ['B',datestr(now,'YYmmDD'),'.TXT'];
url = ['http://www1.mbrace.or.jp/od2/B/',datestr(now,'YYYYmm'),'/b',datestr(now,'YYmmDD'),'.lzh'];
dl = [getenv('USERPROFILE'),'\Downloads\'];
zipname = ['b',datestr(now,'YYmmDD'),'.lzh'];

if ~exist(fname)
    % 出走表を取ってこよう。
    if ~exist([dl,zipname])
        web(url,'-browser')
        pause(10)   % 早すぎるとダウンロード中とかで見つからない。
    end
    dos(['"C:\Program Files (x86)\Lhaplus\Lhaplus.exe" /o:C:\work\boat ',dl,zipname,' &']);
end

pause(5)   % 早すぎるとファイルがないとかで見つからない。

fid = fopen(fname);
S = textscan(fid,'%s','delimiter','\n');
C = S{1};
fid = fclose(fid);

%% BGN/ENDを探す
BBGN = find(contains(C,'BGN'));
BEND = find(contains(C,'END'));

%% BGN-END Loop
T_today = table;
ymd = datestr(now,'YYYYmmDD');

for BEloop = 1:length(BBGN)
    %% BGN-ENDで切り取る
    res = C(BBGN(BEloop)+1:BEND(BEloop)-1);
    res(cellfun(@isempty,res)) = [];

    % 場所
    place = res{1}(7:9);
    place(place == 12288) = [];

    % 開催日と今何日目
    idx = regexp(res{1},'第 \w日','match');

    if idx{1}(end-1) > 60000  % 全角だったら
        day = idx{1}(end-1) - 65296;
    else
        day = str2double(idx{1}(end-1));
    end

    % レース番号
    idx = ~cellfun(@isempty, regexp(res,'[1234567890]+R'));
    start_idx = find(~cellfun(@isempty, regexp(res,'1R')));

    x = find(idx);
    x_end = [x(2:end)-1 ; length(res)];
    n = length(x);

    % ここからレースのループ
    for Rloop = 1:12
        % レース番号
        race = res(x(Rloop):x_end(Rloop));
        R = Rloop;  % レース番号
        TR = textscan(race{1},'%s');
        R_NAME = TR{1}{2};   % レース名
        R_NAME(R_NAME == 12288) = [];

        LENGTH = str2double(char(TR{1}{3}(2:end-1)-65248));
        WEATHER = '晴'; 
        WINDDIR = '北西';
        WINDPOW = 0;
        WAVE = 0;

        % 舟ごと
        for iy = 1:6
            rc = race{5+iy};
            FRAME = str2double(rc(1));
            NAME = str2double(rc(3:6));
            MOTOR = str2double(rc(41:43));
            BOAT = str2double(rc(50:52));
            EXTIME = 7.00;   % 仮
            ENTRY = FRAME;   % 進入コース(本番)
            AGE = str2double(rc(11:12));
            WEIGHT = str2double(rc(15:16));
            if strcmp(rc(17:18),'A1')
                CLASS = 1;
            elseif strcmp(rc(17:18),'A2')
                CLASS = 2;
            elseif strcmp(rc(17:18),'B1')
                CLASS = 3;
            else
                CLASS = 4;
            end
            A_WINRATE = str2double(rc(19:23));
            A_DOUBLEWINRATE = str2double(rc(24:29));
            L_WINRATE = str2double(rc(30:34));
            L_DOUBLEWINRATE = str2double(rc(35:40));
            M_DOUBLEWINRATE = str2double(rc(44:49));
            B_DOUBLEWINRATE = str2double(rc(53:58));

            T_today(end+1,:) = {place,ymd,day,...
                R,R_NAME,LENGTH,WEATHER,WINDDIR,WINDPOW,...
                WAVE,FRAME,NAME,MOTOR,BOAT,EXTIME,ENTRY,...
                AGE,WEIGHT,CLASS,A_WINRATE,A_DOUBLEWINRATE,...
                L_WINRATE,L_DOUBLEWINRATE,M_DOUBLEWINRATE,...
                B_DOUBLEWINRATE};
        end
    end
end

T_today.Properties.VariableNames = {'Place','YYYYMMDD','Day','R','RaceName','Distance','Weather','WindDir','WindPow','Wave','Frame','NameNo','Motor','Boat','ExtTime','Entry',...
    'Age','Weight','Class','A_WinRate','A_DoubleWinRate','L_WinRate','L_DoubleWinRate','M_DoubleWinRate','B_DoubleWinRate'};

T_today.Place = categorical(T_today.Place);
T_today.Day = categorical(T_today.Day);
T_today.R = categorical(T_today.R);
T_today.Weather = categorical(T_today.Weather);
T_today.WindDir = categorical(T_today.WindDir);
T_today.Frame = categorical(T_today.Frame);
T_today.NameNo = categorical(T_today.NameNo);
T_today.Motor = categorical(T_today.Motor);
T_today.Boat = categorical(T_today.Boat);
T_today.Entry = categorical(T_today.Entry);

T_today.Age = categorical(T_today.Age);
T_today.Class = categorical(T_today.Class);

あとは適当なスクリプトを走らせる。

 上で作ったプログラムと構造体の predictFcn を使えばいいね。今日は 2019/8/21 なので、尼崎12RのG3優勝戦 でも予想してみよう。

T_today = boat_today;

Place = '尼崎';
R = 12;

Tx = T_today(T_today.Place==Place & T_today.R==num2str(R),:);
Result = trainedModel.predictFcn(Tx);
disp([Tx.Frame, Result])

disp コマンドの出した結果↓↓。

     1      1 
     2      4 
     3      3 
     4      1 
     5      4 
     6      3 

雑な表示だけど、

  • 1号艇1着(当たり)
  • 2号艇4着
  • 3号艇3着
  • 4号艇1着
  • 5号艇4着
  • 6号艇3着

で、実際の結果が 135426 なので、1番だけ当たり。これで 16% なので、23% って大体このくらい+うっかりもう一個当たるかな、くらいよね。

デフォルトで、何も考えずに使うとイマイチ。

 もう少し工夫しないと着順は被るし精度は低いし、いい結果は得られません、ということが分かりました!大発見ですね!(前向き)

 ただ、ドッサリ書いたら疲れたのでまた今度。。
 「いいね」が多かったら続きを書きますね!(いいねの亡者)

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3