Open29

読者コミュニティ2

ピン留めされたアイテム

この度、より議論に適した場として、GitHubに書籍購入者限定コミュニティを用意しましたので、今後の投稿はそちらにお願い致します!

何ができるか

  • Issuesを作成してトピックごとに議論
    この「読者コミュニティ」と同じように議論ができる場です。トピックごとにタイトルをつけて「Issues」という単位で管理できるので、この「読者コミュニティ」より使いやすいと思います。

    画像のように、現在コミュニティに投稿されている議論は既にまとめておきました。

  • Pull requestsを使ったソースコードの改善(予定)
    「ここのソースコードを変えて欲しい」という要望を送信することができる機能です。これによってエラーが起こってしまうコードの修正や、コミュニティに寄せられた新しいソースコードのアイデアなどをすぐに反映させることができます。

参加対象者

以下のいずれかを満たす人を対象者とします

参加方法

githubのアカウントを作成し、dijzpeb2@gmail.comまで、以下の2点をお送りください

  • 書籍の購入証明(購入した時のメールや、支払い完了画面のスクリーンショットなど)
  • githubアカウント名

確認が出来次第、招待のリンクを送信します。

本の感想や質問をお気軽にコメントしてください。ソースコードを挿入したい場合は

```python
挿入したいコード
```

でできます。

YouTubeのメンバーシップに登録していて、Zennのアカウント名と異なる場合には、メンバー登録していることを知らせていただけると助かります!

約1カ月、中央競馬で運用してみました。結果と感想です。
使ってみて、「ランク学習が必要なのかな?」と感じました。

使ってみた結果

賭けたレース・賭けていないレースはありますが、本番の実行コードで1頭目に出力された馬の結果です。(7/31-9/4 中央競馬の全レース(348R))
※スコアに関係なく、出力された1頭目(スコアが最も高い馬番に全て賭けた場合)です。

R 単勝回収値 複勝回収値 勝率 複勝率
1R 166.6% 100.0% 34.5% 62.1%
2R 71.0% 61.0% 24.1% 48.3%
3R 76.6% 65.2% 17.2% 41.4%
4R 71.7% 96.2% 24.1% 48.3%
5R 157.2% 114.1% 6.9% 17.2%
6R 186.9% 189.0% 24.1% 48.3%
7R 83.1% 79.3% 31.0% 55.2%
8R 53.4% 61.0% 20.7% 34.5%
9R 42.1% 65.2% 17.2% 65.5%
10R 54.1% 96.2% 17.2% 48.3%
11R 72.1% 114.1% 17.2% 37.9%
12R 93.4% 189.0% 31.0% 55.2%
平均 94.0% 91.6% 22.1% 46.8%

流し馬券の場合、回収率が100%を超えるために、score=3.0以上必要なことが多いです。
score=3.0以上の結果は、348R中12R(全体の3%程度)でした。

現状でこのAIは新馬戦や未勝利戦・1勝クラス・2勝クラスあたりで使える印象です。
ざっくりと「午前のレースが得意・午後は苦手」という結果です。

感想

「戦績が少ない馬のレースは予測できる」状態のため、過去のレース結果の比較が難しいように感じました。
今後の開発ロードマップに記載されているように、「lambdarank」で学習することで的中率の精度が上がるのでは?と感じているところです。

ランク学習について調べたところ、Queryデータを作るのが大変そうな印象で挫折しました。。。
素人には難しいです。。

希望

・精度が上がると思うので、是非「ランク学習」を取り入れて頂きたいです。
使えそうなサイト:[競馬予想AI] ランク学習で着順予想するとなかなか強力だったお話

・賭け方について、「フォーメーション」にすることでBOXよりも回収率が上がるのではないでしょうか。
フォーメーションの場合、「回収率に期待できる賭け方を探す」「レースごとに良い賭け方を出力する」というものでしょうか。。

train_query = train.groupby(level=0)['馬番'].count()
valid_query = valid.groupby(level=0)['馬番'].count()

Queryデータはこれで一発です!

私は先行して試していますが、目的変数の調整が難しいですね…。着順を何着まで評価するかや、オッズの重み付けの度合いなど、色々試行錯誤してます。

初めまして。
新しいコミュニティが出来ていたので、こちらに投稿させて頂いています。

1つ前のコミュニティで、Hamaさんという方が回収率表示する際、gain処理での時間がかかりすぎるという投稿をしていらしたのですが、私も2017年~2021年8月8日までのデータを使用して、馬連BOXで約8時間かかる問題が発生しています。(単勝でも15分程)

プログラミング初心者のため原因を探る方法も掴み切れていない状況です。

どなたか同じような現象が起きた方、改善策がお分かりになる方がいればご教授頂けると幸いです。

だいたい同じくらいのデータ量で試してみましたが、こちらでは1/100完了時点での推定完了時間は40分弱でした。
Hamaさんが何度か試してみたら解決したとあるので、メモリ不足とかじゃないでしょうか。もしくは単純なスペック不足か。
一度jupyter labをシャットダウンしてもう一度試してみてください。スペックの可能性がある場合はgoogle colabを試してみるなどしてみてください。

Keshikiさん
その後別PCで試す機会があり、Keshikiさんと同様に40分弱の推定完了時刻となりました。
仰られる通り、メモリ不足・スペック不足の可能性が高いように思われます。
アドバイス頂きありがとうございました。

はじめまして
merge_horse_results()について質問があります。
この関数は、r_data_peの各レースの馬データに対して、「そのレースより過去の成績を載せた列を付け足す」ものか、「全期間の馬の過去成績を載せた列を付け足す」どちらなのでしょうか?

仮に後者とした場合、04章 【機械学習モデル作成&学習】にてr.data_cを分割してtest,trainデータとしていますが、r.data_cの分割のされ方によってはtrainデータに未来の馬の成績が入った状態になり、testデータによる検証に影響を与えるのではないでしょうか?
(素人考えですが、的外れでしたら申し訳ありません)

前者です。
HorseResultsクラスのaverage()でレースの日付より過去のデータに絞っています。

ありがとうございます。よかったです

いつもお世話になっております。

HorseResultクラスにある上り3Fのデータを使用してみようと思い、preprocessing()にて、開催、距離に応じた基準値からの差を出してみようと思いました。

そこで開催地が01:札幌だったらという感じで条件を追加していこうとしたのですが、うまく条件を記述できません。。初歩的な質問で申し訳ないですが、どのように記載すればよいでしょうか・・?

  if df['開催'] == '01':

という感じでやってみたのですが、以下のエラーが出てしまいます。

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

イメージ的には

 if df['開催'] == '01':
  if df['馬場'] == '良':
    if df['course_len'] == 12:
      →札幌で馬場が良で距離1200mのときの3F基準値と、取得した3Fの差を出す

という感じで考えていました。

dfに入るのが1レース分ならif df['開催'].iloc[0] == '01':でできます。開催場所は同じなので一番上のレコードだけ見るという感じです。

1レース分じゃないなら、専用の関数を作っておいて、groupbyしてapply lambdaでその関数を回し、その結果をマージするのがいいと思います。

Keshiki様> いつもありがとうございます。

HorseResultsクラスのpreprocessing()なので、1レースずつの処理になると思います。(他の処理も同様に処理をしているため。)

試しに教わった条件を入れてみたところ、エラーは出なかったのですが、どうも条件に入らないようです。

class HorseResults:
    def __init__(self, horse_results):
        self.horse_results = horse_results[['日付', '着順', '賞金', '着差', '通過', '開催', '距離','上り','馬場']]
        self.preprocessing()
・
・
・
    def preprocessing(self):
        df = self.horse_results.copy()

        # 着順に数字以外の文字列が含まれているものを取り除く
        df['着順'] = pd.to_numeric(df['着順'], errors='coerce')
        df.dropna(subset=['着順'], inplace=True)
        df['着順'] = df['着順'].astype(int)

        df["date"] = pd.to_datetime(df["日付"])
        df.drop(['日付'], axis=1, inplace=True)
        
        #賞金のNaNを0で埋める
        df['賞金'].fillna(0, inplace=True)
        
        #1着の着差を0にする
        df['着差'] = df['着差'].map(lambda x: 0 if x<0 else x)
        
        #レース展開データ
        #n=1: 最初のコーナー位置, n=4: 最終コーナー位置
        def corner(x, n):
            if type(x) != str:
                return x
            elif n==4:
                return int(re.findall(r'\d+', x)[-1])
            elif n==1:
                return int(re.findall(r'\d+', x)[0])
        df['first_corner'] = df['通過'].map(lambda x: corner(x, 1))
        df['final_corner'] = df['通過'].map(lambda x: corner(x, 4))
        
        df['final_to_rank'] = df['final_corner'] - df['着順']
        df['first_to_rank'] = df['first_corner'] - df['着順']
        df['first_to_final'] = df['first_corner'] - df['final_corner']
        
        #開催場所
        df['開催'] = df['開催'].str.extract(r'(\D+)')[0].map(place_dict).fillna('11')
        #race_type
        df['race_type'] = df['距離'].str.extract(r'(\D+)')[0].map(race_type_dict)
        #距離は10の位を切り捨てる
        df['course_len'] = df['距離'].str.extract(r'(\d+)').astype(int) // 100
        df.drop(['距離'], axis=1, inplace=True)
        #インデックス名を与える
        df.index.name = 'horse_id'
        
        #データがないものはNaにする
        df['上り'].fillna(0, inplace=True)
        
        #    '札幌':'01',  '函館':'02',  '福島':'03',  '新潟':'04',  '東京':'05', 
        #    '中山':'06',  '中京':'07',  '京都':'08',  '阪神':'09',  '小倉':'10'

        #独自追加 基準値から3Fの差を出す
        up_3_halong = 0.0
        if df['開催'].iloc[0] == '01':
            if df['馬場'].iloc[0] == '良':
                if df['course_len'].iloc[0] == 12:
                    up_3_halong = 10
                else:
                    up_3_halong = 100
            else:
                 up_3_halong = 200
        else:
             up_3_halong = 300

        df['up_3halong'] = up_3_halong
        
        self.horse_results = df
        self.target_list = ['着順', '賞金', '着差', 'first_corner', 'final_corner',
                            'first_to_rank', 'first_to_final','final_to_rank','up_3halong']

↑は条件に適した値が入るか試しにup_3_halong変数に値を入れたものです。

しかし結果を見ると全部300が入ってました。

 	course_len 	開催 	up_3halong_3R
202101010101 	12.0 	01 	300.0
202101010101 	12.0 	01 	300.0
202101010101 	12.0 	01 	300.0
202101010101 	12.0 	01 	300.0
202101010101 	12.0 	01 	300.0
... 	... 	... 	...
202109020412 	14.0 	09 	300.0
202109020412 	14.0 	09 	300.0
202109020412 	14.0 	09 	300.0
202109020412 	14.0 	09 	300.0
202109020412 	14.0 	09 	300.0

※本当はr.data_hは開催を削除しますが、確認のために残しときました。
if df['開催'].iloc[0] == '01':で札幌のレースを判定できると思ったのですが、何が悪いか分かりますでしょうか・・・??

hrクラスのdfに入るのはすべてのレースですね。
この形だとgroupbyすると少し難しくなるので、ちょっと遅くなってもいいならgroupbyせずに

up_3_halong = df[['開催', '馬場', 'course_len', '上り']].apply(lambda x: self.専用の関数(x), axis=1)
df['up_3halong'] = up_3_halong

とするのが簡単かと思います。
xにはdf[['開催', '馬場', 'course_len', '上り']]の各レコード(Series型)が入りますので、専用の関数でif x['開催'] == '01':という感じでやります。returnは「基準値と3Fの差」の値にします。
前の投稿では「結果をマージ」と書きましたが、up_3_halongはSeries型になるのでmergeやconcatではなく代入でOKです。

Keshiki様>
 何度もありがとうございました。おかげさまでやりたいことがようやくでき、開催場所、馬場、コース種類、コース長さ のそれぞれにあった基準タイムを分けることが出来ました!

【ちょっとしたバグ報告】
間違いがありましたらご了承ください。ご指摘いただけると幸いです。


ResultsとShutubaTableのスクレイピングについて、一部のレースでcourse_lenが2となってしまっています。例えば内2周3600mの2が引っかかってしまっているようです。以下のように変更することで修正できます。

#変更前
df['course_len'] = [int(re.findall(r'\d+', text)[0])] * len(df)
#変更後
df['course_len'] = [int(re.findall(r'\d+', text)[-1])] * len(df)


pedsのデータですが、スクレイピングの時点で重複を削除しているので、血統表の1~4代前までの同じ世代に同じ馬名があるときデータがずれてしまっています。
例) オールドフレイム(2014104728): Hail to Reasonはpeds_14に入るはずですが、peds_13に入ってしまっています。母母からのデータが存在しないため、母母父と母母母が同じNaNとなり重複扱いになってデータがずれていきます。
また、そもそも同名の別馬というのも結構いるようですので、馬名でカテゴリ変数化してしまうと別の馬であるにもかかわらず同じ値のカテゴリ変数を持つものが出てきてしまいます。
したがって、pedsのデータはhorse_idを取得するのがいいと思いますがいかがでしょうか。


ModelEvaluatorクラスのpredict_proba()内でprobaをレースごとに標準化する際のx.std()についてですが、pandasではデフォルトで不偏標準偏差の値が求まるようになっているようなので、x.std(ddof=0)として標準偏差の値にするのがいいのではないかと思いますがいかがでしょうか。

①についてですが、私も同じバグを発見しました。
私は、以下のように修正しております。
やりたいことは、0番目の配列を取ることではなく、m(メートル)よりも前の数字を取り出すことなので、こっちの方がしっくりきました。
#変更前
df['course_len'] = [int(re.findall(r'\d+', text)[0])] * len(df)
#変更後
#正規表現によりmより前の数字だけを取り出す
df['course_len'] = [int(re.findall(r"(\d+)(?=m)", text)[0])] * len(df)

y_test = test['rank']について

この式は、要するに、LGBで、的中率を最適化(=MAX)するように加重を求め、その的中率最適化モデルで、回収率を見るということであっていますか?

馬王という競馬ソフトでは、回収率を出し、特徴量を決め、手作業で、以下のような調整をして回収率が適当にちょうどいい感じにイジるという方式をやっていたのでそれから卒業したいのです。

①一番人気をカット
②馬連配当20倍以上
③一番人気の得点(ここでいう、threshold)が高い場合は、見送り

y_test=test[払い戻し累計」、として
y_testを、MAXにするように、パラメターを調整するのは難しいのでしょうか?

いつもお世話になっております。
馬番順に並んでいる調教データを着順で並んでいるrace_resultsに自分なりにあるゆる方法でmergeしようとしてもどうしても上手くいきませんでした。そこでrace_resultsをrace_idごとに馬番順に並び替えて、concatすれば簡単かなと思ったのですが、race_resultsを馬番順に並び替えることが出来ませんでした(笑)groupbyだのsortだの色々したのですが… 
どなた様かご教授頂けますでしょうか?

私は下のような感じでやってます。

df = df.groupby(level=0).apply(lambda x: x.sort_values('馬番'))
df.reset_index(level=0, drop=True, inplace=True)

LightGBMでは着順順のまま学習させるとリークになるという話を聞いたので並べ替えています。

ありがとうございます!
相当似た感じでやったのですがカスってました(笑)
リークの問題も確かに思いました。一番目に来るのが熱いみたいに学習するのかなとか。頭数がバラバラなので微妙ですが。そもそも馬ごとに横に学習しているイメージはありますが、縦の関係で学習しているのかどうかもよく分かってません…

お世話になっております。

  1. メモリ使用量について
    10 年分のデータを利用し、独自特徴量を追加したところ、メモリ不足になってしまいました。
import gc

をしておき、例えばですが下記のように要所要所でメモリ解放するのがいいかと思いました。

      self.data_p = df
      del df
      gc.collect()

ただ、これをしていいのかがわからずでアドバイスをお願いできればと存じます。
また、return で返している df については、return 後に同様の処理をするとメモリ解放がされるかご教示いただければと存じます。
例えば、以下のような処理イメージです。

    return merged_df
    del merged_df
    gc.collect()

※ 自分で試したいところなのですが、リソースのスペックが低く検証に時間がかかるため、ご存知であれば、、return してるので、意味はなさそうな気はしております、、

  1. 型変換につきまして
    Kaggle で活用されている reduce_mem_usage 関数があり、利用してみたところメモリの利用減少が認められました。
    しかし型を変換しているため、これが学習結果に関連するかを懸念に思っております。アドバイスいただければと存じます。

  2. Note Book の分割について
    上記のようにメモリ不足になってしまったため、Note Book を分割しようと思っております。

    1. スクレイピング専用
    1. 学習専用
    1. 予想専用

この際ですが、「2. 学習専用」と「3. 予想専用」が違う Note Book の場合でも、利用するデータが同じ、且つ利用するハイパーパラメータが同じであれば(学習する Note Book と 予想する Note Book) を分割しても問題なく同じ結果になるのでしょうか?

第30回のコードを実装し運用していたところ以下のエラーが出てくるようになりました。
最近変更を加えたこととしては直近3週間分のレース結果をスクレイピングし追加しました。
その後このようなエラーが発生し学習が進まない状態となっています。
2021年のresults, horse_results, peds, return_resultsをスクレイピングしupdate_data関数で更新しましたが、改善されませんでした。
エラー解決の糸口かエラー解決の方法がわかる方いらっしゃったら教えていただけないでしょうか?
results.index.unique()とreturn_tables.unique()の長さは一致していました。

<ipython-input-39-7e41a6998438>:11: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray.
states = np.array([self.memory[index]['state'] for index in batch_indexes])

TypeError Traceback (most recent call last)
TypeError: only size-1 arrays can be converted to Python scalars

The above exception was the direct cause of the following exception:

ValueError Traceback (most recent call last)
<ipython-input-41-495cb750bdbb> in <module>
38 }
39 agent.replay_buffer.append(transition)
---> 40 agent.update_q()
41 episode_rewards.append(episode_reward)
42 if episode % 10 == 0:

<ipython-input-40-b29b4c614620> in update_q(self)
12 def update_q(self):
13 batch = self.replay_buffer.sample(self.batch_size)
---> 14 q = self.qnet(torch.tensor(np.float32(batch['states']), dtype=torch.float))
15 targetq = copy.deepcopy(q.data.numpy())
16

ValueError: setting an array element with a sequence.

python初心者です。

Pedsクラスでスクレイピングを行いたいのですが、
peds_results = Peds.scrape(horse_id_list)でスクレイピングを行おうとすると
NameError: name 'horse_id_list' is not defined
となってしまいます。
Results.scrapeとHorseResults.scrapeのスクレイピングはうまくいっています。
気になるのはpickleファイルを読み込んだ時、results.pickleはLastmodifiedが更新されたのに対して、horse_results.pickleは更新されませんでした。

対処法をご教示いただけますと幸いです。

horse_id_listが作成されていないようですので前回スクレイピングしたresultsからhorse_id_listを作成してみてください。

horse_id_list = results['horse_id'].unique()



初めまして!Python初心者です。
Chapter03のデータ加工・前処理の部分で
r.merge_horse_results(hr, n_samples_list=[5, 9, 'all'])
r.data_h.head() #jupyterで出力
の入力でKeyError:'開催'と出てきてしまいます。
dfの列にもきちんと入れてあるのでなぜこのようなエラーが出てしまっているかわかりません。
どなたかご教授いただけないでしょうか??

プログレスバーを見ると処理される前にエラーが出ているようですので「開催」列がないことが原因のようですが…
この処理の前に

r.preprocessing()

を実行したと思いますが、r.data_pの中に「開催」列はありますか?

Nobuさん
返信ありがとうございます!r_data_pの中身を確認したところ、抜けていたので追加したら無事に解決しました。ありがとうございました。

ログインするとコメントできます