** Dieser Artikel ist komplett fertig **
Es tut mir leid für die Person, die es auf Lager hat.
Vielen Dank, dass Sie den Punkt von @ hal27 bemerkt haben.
· Was ich getan habe Ich habe beim Schaben einen fatalen Fehler gemacht. Ich dachte, ich hätte die Daten für die letzten drei Rennen ab dem Zeitpunkt des Rennens erfasst, aber tatsächlich habe ich die Informationen für die letzten drei Rennen ab dem Zeitpunkt der Ausführung des Scrapings erfasst.
Als ich jedoch voraussagte, ohne die Informationen des vorherigen Laufs zu verwenden, lag die Wiederherstellungsrate im Durchschnitt bei etwa 90% Selbst wenn ich die richtigen Daten verwende, denke ich, dass sie 100% überschreiten können.
** Von vorn anfangen! **
Bitte lesen Sie diesen Artikel, während Sie denken, dass dies geschehen ist. (Achten Sie besonders auf den Scraping-Teil der vorherigen Laufinformationen.)
Vor kurzem bin ich süchtig nach Datenanalyse.
Wenn ich Kaggle, einen Datenanalyse-Wettbewerb, durchführe, denke ich oft ** "Umsatzprognose? Gibt es ein interessanteres Thema? **".
Aus diesem Grund habe ich beschlossen, ein Vorhersagemodell für Pferderennen von Grund auf neu zu erstellen, um es zu studieren. Hoffentlich können Sie Geld verdienen und es ist das beste Analysethema für mich, der Pferderennen liebt.
Ich bin fast ein Anfänger, aber als Ergebnis konnte ich ein Modell mit einer stabilen Wiederherstellungsrate von über 100% ** erstellen. In diesem Artikel werde ich den groben Ablauf bis zur Erstellung des Pferderennmodells und die Details der Simulationsergebnisse beschreiben. Ich werde es weiterhin tun. Wenn etwas mit Ihrer Denkweise nicht stimmt, geben Sie uns bitte eine Anleitung.
** Vorhersage der Laufzeit des Laufpferdes und Gewinn des schnellsten Pferdes. ** **.
Auf den Straßen gab es viele Modelle, die die Trefferquote des ersten Pferdes erhöhten, aber ich denke, dass es viele Modelle gab, bei denen die Wiederherstellungsrate nicht wie erwartet anstieg. Wenn ja, könnte es schön sein, nach reiner Vorhersage der Zeit zu wetten. Ich dachte ** (Schurke) ** und nahm diese Einstellung vor.
Es gibt tatsächlich andere Gründe .....
Im Pferderennen scheinen mehr Menschen auf beliebte Pferde zu setzen als auf ihren Fähigkeitswert. (Referenz: Theorie, dass Sie nicht gewinnen können, wenn Sie sich auf beliebte Pferde konzentrieren) Mit anderen Worten, anstatt eine Trefferquote auf dem ersten Platz zu verfolgen, kann es möglich sein, die Wiederherstellungsrate zu erhöhen, indem man die Gewinnchancen betrachtet und Vorhersagen trifft. Ich möchte jedoch Wettkarten mit viel Zeit kaufen, daher möchte ich die Quoten, die kurz vor dem Rennen festgelegt wurden, nicht in den Funktionsbetrag einbeziehen.
Was soll ich machen...
Da Pferderennen verschiedene Faktoren mit sich bringt, ist es schwierig, die Laufzeit rein vorherzusagen. Ist es nicht so, dass es schwierig ist, Pferde mit hohen Erwartungen vorherzusagen, auf die die Leute nicht wetten werden? ** (Schurke) **. Okay, lass uns in die Laufzeit gehen.
Da ich in Kyoto lebe, habe ich mich nur auf Pferderennen in Kyoto konzentriert. Die Daten beziehen sich auf fast alle Rennen von 2009 bis 2019 (siehe Abschnitt Datenvorverarbeitung). Wir haben sie in Trainingsdaten und Testdaten unterteilt und die Testdaten simuliert. Es ist eine Simulation für insgesamt 7 Jahre.
Das Folgende ist der Inhalt der Datenaufteilung.
Trainingsdaten | Testdaten |
---|---|
2009~2018 | 2019 |
2009~2017 | 2018 |
2009~2016 | 2017 |
2009~2015 | 2016 |
2009~2014 | 2015 |
2009~2013 | 2014 |
2009~2012 | 2013 |
Im Falle eines Lecks werden die Trainingsdaten auf das Jahr vor den Testdaten eingestellt.
Die Details der behandelten Merkmalsmengen werden unten erläutert.
Es scheint, dass Sie die Daten leicht erhalten können, wenn Sie bezahlen, aber ich habe die Daten auch durch Web-Scraping für das Studium erhalten.
Zunächst können Sie HTML / CSS mit Progate problemlos studieren. Ich wusste nicht, wo ich die gewünschten Daten finden konnte, ohne es zumindest zu wissen.
In Bezug auf das WEB-Scraping habe ich es unter Bezugnahme auf den folgenden Artikel erstellt. Um ehrlich zu sein, denke ich, dass dies der schwierigste Teil war. (Zu viele Ausnahmen!)
Die zu kratzende Site ist https://www.netkeiba.com/.
Das Folgende sind die Daten, die durch tatsächliches Schaben erhalten werden. Der Code befindet sich in Anhang.
Die erfassten Daten sind wie folgt
feature | Erläuterung | feature | Erläuterung |
---|---|---|---|
race_num | Welche Rasse | field | Shiba oder Dreck |
dist | Entfernung | l_or_r | Rechtshänder oder Linkshänder |
sum_num | Anzahl der Köpfe | weather | das Wetter |
field_cond | Baba Staat | rank | Reihenfolge der Ankunft |
horse_num | Pferdenummer | horse_name | Pferdename |
gender | Sex | age | Alter |
weight | Pferdegewicht | weight_c | Reitergewicht |
time | Laufzeit | sum_score | Gesamtergebnisse |
odds | Gewinnchancen | popu | Beliebt |
Wir haben Daten für die letzten 3 Rennen mit ** + erhalten. ** **.
Von diesen werden ** Laufzeit, Reihenfolge der Ankunft, Gewinnchancen, Beliebtheit und Eigenschaften von Pferdenamen nicht als Trainingsdaten verwendet. ** **.
Außerdem wurden ** Informationen für Pferde gelöscht, die im vorherigen Rennen keine 3 Rennen hatten. ** Wenn jedoch nur ein Pferd in jedem Rennen Informationen hat, werden wir das schnellste Pferd unter ihnen vorhersagen. Wenn die gelöschten Daten ein erstes Pferd enthalten, ist die Erwartung natürlich falsch.
Daten für etwa 450 Rennen bleiben pro Jahr. (Fast alle Rennen)
Transformieren Sie die resultierenden Daten für die Aufnahme in ein maschinelles Lernmodell. Ich habe jedoch gerade die kategoriale Variable in ** Beschriftungscodierung ** und ** Zeichentyp in numerischen Typ ** geändert.
Da es sich um eine Art Algorithmusbaummodell des diesmal behandelten Modells handelt, ist es nicht standardisiert.
Außerdem ** mache ich einige neue Funktionen mit den erworbenen Funktionen. ** (Entfernung und Zeit bis zur Geschwindigkeit usw.)
Implementiert mit der ** LightGBM ** -Bibliothek des ** Gradientenverstärkungs-Entscheidungsbaum-Algorithmus **. Das ist das, was in letzter Zeit in Kaggle oft verwendet wird.
Unten finden Sie den Implementierungscode.
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)
Wie Sie sehen, haben wir nichts getan (lacht)
Ich habe versucht, die Parameter mit Optuna usw. abzustimmen, aber da die Bewertungsfunktion und die Wiederherstellungsrate unterschiedlich sind, führte dies nicht zu einer wesentlichen Verbesserung der Wiederherstellungsrate.
Nachfolgend sind die Simulationsergebnisse für 7 Jahre aufgeführt
** Horizontale Achse **: Welches Rennen ** Vertikal **: Gewinnchancen bei Treffer (0 bei Fehlschlag) ** Trefferquote **: Trefferquote ** back_rate **: Wiederherstellungsrate Der Titelzug und der Test repräsentieren den Zeitraum der jeweils verwendeten Daten. (09 ist 2009)
Die Ergebnisse sind unten zusammengefasst
Simulationsjahr | Anzahl der Treffer | Trefferquote | Erholungsrate |
---|---|---|---|
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% |
durchschnittlich | ------ | 25.5% | 180.4% |
Es ist zu gut.
Die Trefferquote ist in Ordnung, aber was mich überraschte, war, dass ich von Zeit zu Zeit Pferde mit hohen Chancen schlug.
** 2017 schlage ich 250 Mal mehr Pferde! ** **.
Ich bin neugierig, also schauen wir uns den Inhalt an Nachfolgend finden Sie den Inhalt des Rennens des Tages. (Sortieren in der Reihenfolge von time_pred)
Ich rate ernsthaft.
Ich habe Angst, dass etwas nicht stimmt. Ist es in Ordnung, 100% so leicht zu überschreiten?
Worauf sollte ich in einem solchen Fall achten? .....
** Du musst es im wirklichen Leben versuchen! ** **.
Folgendes habe ich gesucht. ・ ** Verwenden Sie nur die Funktionen, die am Tag verwendet werden können **? ・ ** Ist die Formel zur Berechnung der Wiederherstellungsrate korrekt? ** ・ ** Gibt es Unstimmigkeiten mit den Informationen im Internet? ** **. ・ ** Können Sie die Person mit der frühesten erwarteten Zeit auswählen **? ・ ** Spielen Sie das erstellte Modell aus verschiedenen Richtungen ab **
Es ist eine gute Idee, also werde ich verschiedene Dinge ausprobieren.
Ich habe es nur 6 Mal getroffen.
Unten finden Sie die LightGBM-Funktionen zum Importieren von Funktionen (2019). "a", "b", "c" sind die Geschwindigkeiten (dist / time) der Pferde im vorherigen Lauf, im vorherigen Lauf bzw. im vorherigen Lauf.
Ich verstehe, dass "dist (Race Distance)" wichtig ist, weil ich die Zeit vorhersage, aber welche Bedeutung hat "race_id (welches Rennen des Jahres)"?
Ist die Jahreszeit wichtig für die Vorhersage der Zeit?
Darüber hinaus ist die Umgebung von großer Bedeutung, z. B. "Race_Cond (Baba State)", "Race_Num (welches Rennen des Tages)".
** * Ich habe Addition geschrieben. (2020/05/28) **
Es hat nichts mit dem Modell zu tun, das ich erstellt habe (lacht)
Nachfolgend sind die Ergebnisse von ** 2019 ** aufgeführt
n-ten beliebtesten | Trefferquote | Erholungsrate |
---|---|---|
Am beliebtesten | 30.9% | 71.1% |
2. beliebteste | 17.3% | 77.2% |
3. am beliebtesten | 15.3% | 90.3% |
4. am beliebtesten | 10.1% | 81.3% |
5. am beliebtesten | 8.4% | 100.5% |
6. am beliebtesten | 6.2% | 92.4% |
7. am beliebtesten | 3.2% | 64.2% |
8. am beliebtesten | 2.4% | 52.1% |
9. am beliebtesten | 1.5% | 48.2% |
10. am beliebtesten | 1.3% | 59.1% |
11. am beliebtesten | 1.5% | 127.6% |
12. am beliebtesten | 1.3% | 113.9% |
13. am beliebtesten | 1.5% | 138.6% |
14. am beliebtesten | 0.4% | 77.8% |
Es ist ein sehr interessantes Ergebnis. Wenn Sie sich erholen möchten, können Sie weiterhin unbeliebte Pferde kaufen. Es scheint keinen Spaß zu machen, es zu sehen, weil es kaum trifft (lacht)
Es ist interessant, also habe ich mir den ** Durchschnitt 2013-2019 ** angesehen.
n-ten beliebtesten | Trefferquote | Erholungsrate |
---|---|---|
Am beliebtesten | 31.7% | 73.4% |
2. beliebteste | 20.0% | 83.7% |
3. am beliebtesten | 13.2% | 80.2% |
4. am beliebtesten | 9.9% | 81.9% |
5. am beliebtesten | 7.8% | 89.1% |
6. am beliebtesten | 5.5% | 89.8% |
7. am beliebtesten | 4.2% | 86.0% |
8. am beliebtesten | 2.4% | 64.8% |
9. am beliebtesten | 2.1% | 64.8% |
10. am beliebtesten | 1.7% | 80.9% |
11. am beliebtesten | 1.1% | 98.2% |
12. am beliebtesten | 1.0% | 69.4% |
13. am beliebtesten | 1.1% | 113.2% |
14. am beliebtesten | 0.2% | 35.4% |
interessant
Die Wiederherstellungsrate kann fast ohne Einfallsreichtum 100% überschreiten lightGBM super
Bitte beachten Sie, dass dies ein schmutziger Code ist.
・ ** Scraping (Erfassung der Renninformationen und der URL jedes Pferdes) **
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']
#Gemeinsame Gegenstände#
# 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
#Nicht üblich#
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]))]
#Holen Sie sich die Informationen, die Sie von hier erhalten können
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
・ ** Scraping (bisherige Renninformationen für jedes Pferd) **
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
Betrachten wir die Bedeutung von race_id
für Feature-Wichtigkeiten.
Erstens können Sie sehen, dass die Wichtigkeit von race_id
aus den folgenden zwei Schritten hoch geschätzt wird.
** 1 Vergleiche mit und ohne race_id **
Wenn die Merkmalsmenge von'race_id 'gelöscht wurde, betrug die Wiederfindungsrate ungefähr 10% und die Testrate war in allen 7 Jahren ungefähr 0,1 schlechter.
Daraus können wir ersehen, dass die Feature-Menge von race_id
erforderlich ist.
** 2 Machen Sie dasselbe wie 1 mit dem zweitwichtigsten dist **
Sie können sehen, dass sich die Vorhersagegenauigkeit erheblich verschlechtert hat.
Ab ** 1.2 können Sie sehen, dass die Bedeutung von race_id
überschätzt wird. ** **.
Dieses Mal werden "gültig" und "Zug" wie folgt zufällig aufgeteilt.
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.3,\
random_state=0)
Dies gibt Ihnen eine ungefähre Zeit, wenn Sie die race_id
kennen.
Das Folgende ist das Ergebnis des Versuchs, shuffle = False
zu setzen.
Auf diese Weise wird race_id
weniger wichtig. Es ist jedoch eine andere Dimension als die Verbesserung der Wiederherstellungsrate und der RMSE.
Es wird tatsächlich schlimmer.
Recommended Posts