[PYTHON] Prédiction des courses de chevaux: Si vous pensez que le taux de récupération a dépassé 100% en machine learning (LightGBM), c'est une histoire

Merci

Mise en garde!!!

** Cet article est complètement terminé **

Je suis désolé pour la personne qui l'a stocké.

Merci d'avoir remarqué le point de @ hal27.

· Ce que j'ai fait J'ai commis une erreur fatale dès la phase de grattage. Je pensais avoir acquis les données des 3 courses précédentes à partir du moment de la course, mais en fait j'obtenais les informations pour les 3 dernières courses à partir du temps d'exécution du grattage.

Cependant, lorsque j'ai prédit sans utiliser du tout les informations de l'exécution précédente, le taux de récupération était d'environ 90% en moyenne, donc Même si j'utilise les bonnes données, je pense que cela peut dépasser 100%.

** Recommencer! **

Veuillez lire cet article en pensant que cela a été fait. (Faites particulièrement attention à la partie grattage des informations sur l'exécution précédente)

introduction

Récemment, je suis accro à l'analyse de données.

Quand je lance Kaggle, un concours d'analyse de données, je pense souvent ** "Prévisions de ventes? Y a-t-il un thème plus intéressant? **".

C'est pourquoi j'ai décidé de créer un modèle de prédiction de courses de chevaux à partir de zéro pour étudier. J'espère que vous pourrez gagner de l'argent et c'est le meilleur thème d'analyse pour moi, qui aime les courses de chevaux.

Je suis presque un débutant, mais en conséquence, j'ai pu créer un modèle avec un taux de récupération stable supérieur à 100% **, donc dans cet article, je vais décrire le déroulement approximatif de la création du modèle de course de chevaux et les détails des résultats de la simulation. Je vais continuer à le faire. S'il y a quelque chose qui ne va pas dans votre façon de penser, veuillez nous donner des conseils.

Réglage de la condition

** Prédisez le temps de course du cheval courant et gagnez le cheval le plus rapide. ** **

Dans les rues, il y avait beaucoup de modèles qui augmentaient le taux de réussite du premier cheval, mais je pense qu'il y avait beaucoup de modèles où le taux de récupération n'a pas augmenté comme prévu. Si tel est le cas, il peut être intéressant de parier après avoir uniquement prédit le temps. J'ai pensé ** (voyou) ** et j'ai fait ce réglage.

Il y a en fait d'autres raisons .....

Dans les courses de chevaux, il semble que plus de gens parient sur des chevaux populaires que leur valeur de capacité. (Référence: Théorie que vous ne pouvez pas gagner si vous vous concentrez sur les chevaux populaires) En d'autres termes, plutôt que de rechercher un taux de réussite au premier rang, il peut être possible d'augmenter le taux de récupération en examinant les cotes et en faisant des prédictions. Cependant, je souhaite acheter des billets de paris avec beaucoup de temps, donc je ne veux pas incorporer les cotes fixées juste avant la course dans le montant de la fonctionnalité.

Que devrais-je faire...

Étant donné que les courses de chevaux impliquent divers facteurs, il est difficile de prévoir purement le temps de course. N'est-ce pas parce qu'il est difficile de prédire des chevaux avec des attentes élevées sur lesquelles les gens ne parieront pas? ** (Voleur) **. Très bien, allons-y dans le temps d'exécution.

** Objectif d'apprentissage et cible de simulation **

Depuis que je vis à Kyoto, j'ai ciblé ** les courses de chevaux de Kyoto uniquement **. Les données concernent presque toutes les races de 2009 à 2019 (traitées dans la section Prétraitement des données). Nous les avons divisés en données d'entraînement et données de test, et avons simulé les données de test. Il s'agit d'une simulation sur 7 ans au total.

Voici le contenu de la division des données.

Données d'entraînement Données de test
2009~2018 2019
2009~2017 2018
2009~2016 2017
2009~2015 2016
2009~2014 2015
2009~2013 2014
2009~2012 2013

En cas de fuite, les données d'entraînement sont définies sur l'année précédant les données de test.

Les détails des quantités de caractéristiques traitées sont expliqués ci-dessous.

Flux jusqu'à la création du modèle

  1. Acquisition de données (web scraping)
  2. Prétraitement des données
  3. Modélisation

1. Acquisition de données

Il semble que vous puissiez facilement obtenir les données si vous payez, mais j'ai également obtenu les données par grattage Web pour étudier.

Tout d'abord, vous pouvez facilement étudier HTML / CSS avec Progate. Je ne savais pas où trouver les données que je voulais sans au moins savoir.

Concernant le scraping WEB, je l'ai créé en référence à l'article suivant. Pour être honnête, je pense que c'était la partie la plus difficile. (Trop d'exceptions!)

Le site à gratter est https://www.netkeiba.com/.

Voici les données obtenues en grattant réellement. Le code est dans Annexe. image.png image.png

Les données acquises sont les suivantes

feature La description feature La description
race_num Quelle race field Shiba ou saleté
dist distance l_or_r Droitier ou gaucher
sum_num Nombre de têtes weather la météo
field_cond État de Baba rank Ordre d'arrivée
horse_num Numéro de cheval horse_name Nom du cheval
gender sexe age âge
weight Poids du cheval weight_c Poids du cavalier
time Temps d'exécution sum_score Résultats totaux
odds Gagnez des chances popu Populaire

Nous avons obtenu des données pour les 3 courses précédentes avec ** +. ** **

Parmi ceux-ci, ** le temps d'exécution, l'ordre d'arrivée, les cotes, la popularité et les quantités de caractéristiques des noms de chevaux ne sont pas utilisés comme données d'entraînement. ** **

De plus, ** les informations ont été supprimées pour les chevaux n'ayant pas participé à 3 courses dans la course précédente. ** Cependant, si même un cheval a des informations dans chaque course, nous prédirons le cheval le plus rapide d'entre eux. Bien sûr, s'il y a un premier cheval dans les données effacées, l'attente sera fausse.

Les données pour environ 450 courses restent par an. (Presque toutes les races)

2. Prétraitement des données

Transformez les données résultantes pour les inclure dans un modèle d'apprentissage automatique. Cependant, je viens de changer la variable catégorielle en ** codage d'étiquette ** et ** type de caractère en type numérique **.

Puisqu'il s'agit d'une sorte d'arborescence d'algorithmes du modèle manipulé cette fois, il n'est pas standardisé.

En outre, ** je crée de nouvelles fonctionnalités en utilisant les fonctionnalités acquises. ** (distance et temps de vitesse, etc.)

3. Modélisation

Implémenté à l'aide de la bibliothèque ** LightGBM ** de ** l'algorithme d'arbre de décision d'amplification de gradient ** C'est celui qui est souvent utilisé récemment à Kaggle.

Voici le code d'implémentation.

lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_valid, y_valid, reference=lgb_train)

params = {'objective': 'regression',
          'metric': 'rmse',
          }

model = lgb.train(
    params, lgb_train,
    valid_sets=[lgb_train, lgb_eval],
    verbose_eval=10,
    num_boost_round=1000,
    early_stopping_rounds=10)

Comme vous pouvez le voir, nous n'avons rien fait (rires)

J'ai essayé d'ajuster les paramètres à l'aide d'Optuna, etc., mais comme la fonction d'évaluation et le taux de récupération sont différents, cela n'a pas conduit à une grande amélioration du taux de récupération.

simulation

Voici les résultats de la simulation sur 7 ans

** Axe horizontal **: Quelle race ** Vertical **: cotes gagnantes en cas de frappe (0 si raté) ** hit_rate **: taux de succès ** back_rate **: taux de récupération Le train et le test dans le titre représentent la période de données utilisée pour chacun. (09 est 2009)

09-10-11-12,13.jpg 09-10-11-12-13,14.jpg 09-10-11-12-13-14,15.jpg 09-10-11-12-13-14-15,16.jpg 09-10-11-12-13-14-15-16,17.jpg 09-10-11-12-13-14-15-16-17,18.jpg 09-10-11-12-13-14-15-16-17-18,19.jpg

Les résultats sont résumés ci-dessous

Année de simulation Nombre de visites Taux de succès Taux de récupération
2013 116/460 25.2% 171.9%
2014 89/489 18.5% 155.3%
2015 120/489 24.5% 154.7%
2016 101/491 20.6% 163.6%
2017 131/472 27.8% 263.5%
2018 145/451 32.2% 191.8%
2019 136/459 29.6% 161.7%
moyenne ------ 25.5% 180.4%

C'est trop beau.

Le taux de réussite est correct, mais ce qui m'a surpris, c'est que je frappais de temps en temps des chevaux avec des cotes élevées.

** En 2017, je frappe 250 fois plus de chevaux! ** **

Je suis curieux, alors jetons un œil au contenu Voici le contenu de la course du jour. (Trier par ordre de time_pred)

image.png

image.png

Je devine sérieusement.

J'ai peur que quelque chose ne va pas. Est-il acceptable de dépasser 100% si facilement en premier lieu?

Que dois-je rechercher dans un tel cas? .....

** Vous devez l'essayer dans la vraie vie! ** **

Voici ce que j'ai recherché. ・ ** Utilisez-vous uniquement les fonctionnalités qui peuvent être utilisées le jour **? ・ ** La formule de calcul du taux de récupération est-elle correcte? ** ・ ** Y a-t-il des divergences avec les informations sur Internet? ** ** ・ ** Pouvez-vous sélectionner la personne avec l'heure prévue le plus tôt **? ・ ** Jouez le modèle créé à partir de différentes directions **

J'ai joué avec le modèle

C'est une bonne idée, alors je vais essayer différentes choses.

1 Essayez de monter le cheval que vous prédisiez être le plus lent

aaa.png

Je l'ai frappé seulement 6 fois.

2 Importance de la quantité de caractéristiques

Vous trouverez ci-dessous les anannces d'importation de fonctionnalités lightGBM (2019). «A», «b», «c» sont les vitesses (dist / temps) des chevaux dans la course précédente, la course précédente et la course précédente, respectivement.

aaa.png

Je sais que «dist (distance de course)» est important parce que je prédis le temps, mais quelle est l'importance de «race_id (quelle course de l'année)»?

La saison est-elle importante pour prédire le temps?

De plus, l'environnement est d'une grande importance, comme race_cond (baba state), race_num (quelle course du jour).

** * J'ai écrit Addition. (28/05/2020) **

3 Que se passe-t-il si vous continuez à être le nième plus populaire?

Cela n'a rien à voir avec le modèle que j'ai créé (rires)

Voici les résultats de ** 2019 **

nième plus populaire Taux de succès Taux de récupération
Le plus populaire 30.9% 71.1%
2e plus populaire 17.3% 77.2%
3e plus populaire 15.3% 90.3%
4e plus populaire 10.1% 81.3%
5ème plus populaire 8.4% 100.5%
6ème plus populaire 6.2% 92.4%
7e plus populaire 3.2% 64.2%
8e plus populaire 2.4% 52.1%
9e plus populaire 1.5% 48.2%
10e plus populaire 1.3% 59.1%
11e plus populaire 1.5% 127.6%
12e plus populaire 1.3% 113.9%
13e plus populaire 1.5% 138.6%
14e plus populaire 0.4% 77.8%

C'est un résultat très intéressant. Si vous voulez récupérer, vous pouvez continuer à acheter des chevaux impopulaires. Cela ne semble pas amusant à regarder parce que ça frappe à peine (rires)

C'est intéressant, j'ai donc jeté un coup d'œil à la ** moyenne 2013-2019 **.

nième plus populaire Taux de succès Taux de récupération
Le plus populaire 31.7% 73.4%
2e plus populaire 20.0% 83.7%
3e plus populaire 13.2% 80.2%
4e plus populaire 9.9% 81.9%
5ème plus populaire 7.8% 89.1%
6ème plus populaire 5.5% 89.8%
7e plus populaire 4.2% 86.0%
8e plus populaire 2.4% 64.8%
9e plus populaire 2.1% 64.8%
10e plus populaire 1.7% 80.9%
11e plus populaire 1.1% 98.2%
12e plus populaire 1.0% 69.4%
13e plus populaire 1.1% 113.2%
14e plus populaire 0.2% 35.4%

intéressant

Résumé

Le taux de récupération peut dépasser 100% sans presque aucune ingéniosité lightGBM génial

appendice

Veuillez noter qu'il s'agit d'un code sale.

・ ** Grattage (acquisition des informations de course et URL de chaque cheval) **


def url_to_soup(url):
    
    time.sleep(1)
        
    html = requests.get(url)
    html.encoding = 'EUC-JP'
    return BeautifulSoup(html.text, 'html.parser')


def race_info_df(url):
    
    df1 = pd.DataFrame()
    HorseLink = []
    
    try:
        # year = '2018'
        # url = 'https://race.sp.netkeiba.com/?pid=race_result&race_id=201108030409&rf=rs'
        soup = url_to_soup(url)
        
        if soup.find_all('li',class_='NoData') != []:
            return df1,HorseLink
            
        else:
            
            race_cols = ['year', 'date', 'place', 'race_num' ,'race_class', 'field', 'dist', 'l_or_r',\
                        'sum_num','weather', 'field_cond', 'rank', 'horse_num', 'horse_name', 'gender', 'age',\
                        'weight', 'weight_c', 'time', 'jackie', 'j_weght', 'odds', 'popu']
            
            #Articles communs#
            # Year = year
            Date = soup.find_all('div', class_='Change_Btn Day')[0].text.split()[0]
            Place = soup.find_all('div', class_="Change_Btn Course")[0].text.split()[0]

            RaceClass = soup.find_all('div', class_="RaceDetail fc")[0].text.split()[0][-6:].replace('、','')

            RaceNum = soup.find('span', id= re.compile("kaisaiDate")).text
            RaceData = soup.find_all('dd', class_="Race_Data")[0].contents
            Field  = RaceData[2].text[0]
            Dist = RaceData[2].text[1:5]

            l_index = RaceData[3].find('(')
            r_index = RaceData[3].find(')')
            LOrR = RaceData[3][l_index+1:r_index]

            RD = RaceData[3][r_index+1:]
            SumNum = RD.split()[0]
            Weather = RD.split()[1]
            FieldCond =  soup.find_all('span',class_= re.compile("Item"))[0].text

            #Pas commun#
            HorseLink = []
            for m in range(int(SumNum[:-1])):
                HN = soup.find_all('dt',class_='Horse_Name')[m].contents[1].text
                HL = soup.find_all('dt',class_='Horse_Name')[m].contents[1].get('href')
                HorseLink.append(HL if HN!='' else soup.find_all('dt',class_='Horse_Name')[m].contents[3].get('href'))

            HorseName = []
            for m in range(int(SumNum[:-1])):
                HN = soup.find_all('dt',class_='Horse_Name')[m].contents[1].text
                HorseName.append(HN if HN!='' else soup.find_all('dt',class_='Horse_Name')[m].contents[3].text)
            #     print(soup.find_all('dt',class_='Horse_Name')[m].contents[3])

            Rank = [soup.find_all('div',class_='Rank')[m].text for m in range(int(SumNum[:-1]))]

            #Obtenez les informations que vous pouvez obtenir d'ici
            HorseNum = [soup.find_all('td', class_ = re.compile('Num Waku'))[m].text.strip() for m in range(1,int(SumNum[:-1])*2+1,2)]

            Detail_Left = soup.find_all('span',class_='Detail_Left')
            Gender = [Detail_Left[m].text.split()[0][0] for m in range(int(SumNum[:-1]))]
            Age =  [Detail_Left[m].text.split()[0][1] for m in range(int(SumNum[:-1]))]
            Weight = [Detail_Left[m].text.split()[1][0:3] for m in range(int(SumNum[:-1]))]
            WeightC = [Detail_Left[m].text.split()[1][3:].replace('(','').replace(')','') for m in range(int(SumNum[:-1]))]

            Time = [soup.find_all('td', class_="Time")[m].contents[1].text.split('\n')[1] for m in range(int(SumNum[:-1]))]

            Detail_Right = soup.find_all('span',class_='Detail_Right')
            Jackie = [Detail_Right[m].text.split()[0] for m in range(int(SumNum[:-1]))]
            JWeight = [Detail_Right[m].text.split()[1].replace('(','').replace(')','')for m in range(int(SumNum[:-1]))]
            Odds = [soup.find_all('td', class_="Odds")[m].contents[1].text.split('\n')[1][:-1] for m in range(int(SumNum[:-1]))]
            Popu = [soup.find_all('td', class_="Odds")[m].contents[1].text.split('\n')[2][:-2] for m in range(int(SumNum[:-1]))]

            Year = [year for a in range(int(SumNum[:-1]))]
            RaceCols =  [Year, Date, Place, RaceNum ,RaceClass, Field, Dist, LOrR,\
                      SumNum,Weather, FieldCond, Rank, HorseNum, HorseName, Gender, Age,\
                      Weight, WeightC, Time, Jackie, JWeight, Odds, Popu]
            for race_col,RaceCol in zip(race_cols,RaceCols):
                df1[race_col] = RaceCol

            return df1,HorseLink
        
    except:
        return df1,HorseLink

・ ** Grattage (informations de course de chaque cheval jusqu'à présent) **


def horse_info_df(HorseLink, df1):
    
    df2 = pd.DataFrame()
    # print(HorseLink)

    for n,url2 in enumerate(HorseLink):

        try: 
            soup2 = url_to_soup(url2)

            horse_cols = ['sum_score',\
                          'popu_1','rank_1','odds_1','sum_num_1','field_1','dist_1','time_1',\
                          'popu_2','rank_2','2','sum_num_2','field_2','dist2','time_2',\
                          'popu_3','rank_3','odds_3','sum_num_3','field_3','dist_3','time_3']

            sec = 1
            ya = soup2.find_all('section',class_="RaceResults Sire")
            #ya = soup.find_all('div',class_="Title_Sec")
            if ya !=[]:
                sec = 2
            tbody1 = soup2.find_all('tbody')[sec] 
            SomeScore = tbody1.find_all('td')[0].text
            # print(SomeScore)

            tbody3 = soup2.find_all('tbody')[2+sec]

            HorseCols = [SomeScore]

            for late in range(1,4):
                HorseCols.append(tbody3.contents[late].find_all('td')[2].text)  # Popu
                HorseCols.append(tbody3.contents[late].find_all('td')[3].text)  # Rank
                HorseCols.append(tbody3.contents[late].find_all('td')[6].text)  # Odds
                HorseCols.append(tbody3.contents[late].find_all('td')[7].text)  # SumNum
                HorseCols.append(tbody3.contents[late].find_all('td')[10].text[0]) # Field
                HorseCols.append(tbody3.contents[late].find_all('td')[10].text[1:5]) # Dist
                HorseCols.append(tbody3.contents[late].find_all('td')[14].text) # Time


            dfplus = pd.DataFrame([HorseCols], columns=horse_cols)
            dfplus['horse_name'] = df1['horse_name'][n]

            df2 = pd.concat([df2,dfplus])
        except:
            pass

    return df2

Postscript

Examen de l'importance des quantités caractéristiques (2020/05/28)

Considérons l'importance de race_id dans les fonctionnalités importantes. Tout d'abord, vous pouvez voir que l'importance de race_id est fortement estimée à partir des deux étapes suivantes.

** 1 Comparer avec et sans race_id **

Lorsque la quantité de caractéristique de «race_id» a été effacée, le taux de récupération était d'environ 10% et le rmse pour le test était d'environ 0,1 pire au cours des 7 années. À partir de là, nous pouvons voir que la quantité de fonctionnalités de race_id est requise.

** 2 Faites la même chose que 1 avec le deuxième dist le plus important ** aaa.png

Vous pouvez voir que la précision de la prédiction s'est considérablement détériorée.

Depuis ** 1.2, vous pouvez voir que l'importance de race_id est surestimée. ** **

Cette fois, «valide» et «train» sont répartis aléatoirement comme suit.

X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.3,\
                                                      random_state=0)

Cela vous donnera un temps approximatif si vous connaissez le race_id.

Ce qui suit est le résultat d'essayer de mettre shuffle = False. aaa.png

De cette façon, race_id devient moins important. Cependant, il s'agit d'une dimension différente de l'amélioration du taux de récupération et des RMSE. C'est en fait de pire en pire.

Recommended Posts

Prédiction des courses de chevaux: Si vous pensez que le taux de récupération a dépassé 100% en machine learning (LightGBM), c'est une histoire
J'ai écrit un code qui dépasse le taux de récupération de 100% dans la prédiction des courses de chevaux en utilisant LightGBM (partie 2)
Une histoire sur l'obtention d'un taux de récupération des courses de chevaux de plus de 100% grâce à l'apprentissage automatique
Une méthode concrète pour prédire les courses de chevaux et simuler le taux de récupération par apprentissage automatique
Avec l'apprentissage en profondeur, vous pouvez dépasser le taux de récupération de 100% dans les courses de chevaux
[Vérification] Ce n'est pas parce qu'il existe un apprentissage en profondeur que le taux de récupération peut facilement dépasser 100% dans les courses de chevaux.
Un débutant en apprentissage automatique a essayé de créer un modèle de prédiction de courses de chevaux avec python
Mise en place d'un modèle de prédiction des taux de change (taux dollar-yen) par machine learning
Présentation du livre "Créer une IA rentable avec Python" qui vous permet d'apprendre l'apprentissage automatique dans le cours le plus court
MALSS (introduction), un outil qui prend en charge l'apprentissage automatique en Python
Un exemple de mécanisme qui renvoie une prédiction par HTTP à partir du résultat de l'apprentissage automatique
Si vous pensez que la personne que vous mettez avec pip ne fonctionne pas → Utilisez-vous python3 par hasard?
Si vous êtes novice en programmation, pourquoi ne pas créer un "jeu" pour le moment? L'histoire