** 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)
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.
** 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.
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.
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.
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)
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.)
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.
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)
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)
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 **
C'est une bonne idée, alors je vais essayer différentes choses.
Je l'ai frappé seulement 6 fois.
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.
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) **
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
Le taux de récupération peut dépasser 100% sans presque aucune ingéniosité lightGBM génial
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
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 **
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
.
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