Как повысить эффективность бега с помощью данных и машинного обучения.

Мне всегда очень нравился мир бега, и с раннего возраста я участвовал в соревнованиях, стремясь увеличить свой темп и дистанцию, но на бег влияет несколько факторов, от психологических до внешних. Однако с использованием гаджетов, носимых устройств и приложений, таких как Strava, стало возможным собирать показатели и выполнять анализ, способствующий повышению производительности.

Краткое содержание

  • Доступ к данным;
  • очистка данных;
  • идеи;
  • выбор функций;
  • Машинное обучение;
  • Заключение;
  • Использованная литература.

Доступ к данным

В качестве источника данных мы будем использовать Strava, приложение Fremium с API, позволяющим собирать данные без особых усилий.

Упомянутые здесь шаги для вставки в API также можно найти в официальной документации, но если вы хотите быть объективным, то основные шаги будут описаны в этой статье.



Первым шагом для доступа к данным является создание учетной записи Strava, вход в систему, а затем создание приложения, что можно сделать по этой ссылке.

После того, как приложение будет создано, мы в основном будем использовать Идентификатор клиента (выделено зеленым цветом) и Секрет клиента (выделено синим цветом). Который будет помещен в файл .env репозитория или папки, где будет выполняться проект.

После этого вам нужно будет получить доступ к следующей ссылке, изменив поле [CLIENT_ID] на значение, доступное в вашем приложении, которое показано зеленым цветом на изображении выше.

http://www.strava.com/oauth/authorize?client_id=[CLIENT_ID]&response_type=code&redirect_uri=http://localhost/exchange_token&approval_prompt=force&scope=profile:read_all,activity:read_all

При доступе к этой ссылке выберите данные, к которым вы хотите, чтобы приложение имело доступ (как на изображении ниже), и нажмите авторизовать.

Это действие создаст другой URI, необходимый для копирования кода из параметра code и вставки его в вышеупомянутый .env. Ниже приведен URI после авторизации, а также как должен выглядеть файл .env.

http://localhost/exchange_token?state=&code=2e9fuhef9f2jd293jqd0g8erqt84r802jqd0137q&scope=read,activity:read_all,profile:read_all

client_id=36431
client_secret=fasfjsfajfjie8293u2jf2j92jf232ije0jje92j
strava_code=2e9fuhef9f2jd293jqd0g8erqt84r802jqd0137q

После выполнения этих шагов необходимо будет запустить следующий скрипт Python в той же среде, что и файл .env. Этот сценарий обновит токены, необходимые для сбора данных, и сгенерирует файл JSON с данными, возвращенными из запроса. Я назвал этот скрипт create_token.py .

import os
import json
import requests
from dotenv import load_dotenv

load_dotenv()

response = requests.post(
    url='https://www.strava.com/oauth/token',
    data={
        'client_id': int(os.environ.get('client_id')),
        'client_secret': os.environ.get('client_secret'),
        'code': os.environ.get('strava_code'),
        'grant_type': 'authorization_code'
    }
)
strava_tokens = response.json()
with open('strava_tokens.json', 'w', encoding='utf-8') as outfile:
    json.dump(strava_tokens, outfile)
print(strava_tokens)

После обновления токенов, а также переменных среды просто запустите код для получения данных из API, который приведен ниже. Здесь можно заметить, что захват происходит по страницам, поэтому выполнялся цикл до тех пор, пока ответ не окажется пустым или не возникнет ошибка. Стоит отметить, что перед запуском скрипта я создал две папки с именами data и result в одной рабочей области для хранения захваченных CSV-файлов, а приведенный ниже скрипт был назван get_activities.py.

import os
import json
import glob
import time
import requests
import pandas as pd
from dotenv import load_dotenv

load_dotenv()

def main():
    url = "https://www.strava.com/api/v3/activities"
    access_token = get_credentials()
    page = 1
    print('Getting data from Strava')
    while True:
        response = get_data(url, access_token, 200, page)
        if 'message' in response.columns:
            raise Exception('Authorization Error')
        if response.empty:
            break
        save_csv(response, f'data/strava_activities_page_{page}.csv')
        page += 1
    merge_files('data/', 'result/strava_all_activities.csv')
    print('Done Successfully')
def get_credentials():
    with open('strava_tokens.json', encoding='utf-8') as json_file:
        strava_tokens = json.load(json_file)
    if 'expires_at' not in strava_tokens.keys() or strava_tokens['expires_at'] < time.time():
        strava_tokens = refresh_credentials(strava_tokens)
    return strava_tokens['access_token']
def refresh_credentials(strava_tokens):
    response = requests.post(
        url='https://www.strava.com/oauth/token',
        data={
            'client_id': int(os.environ.get('client_id')),
            'client_secret': os.environ.get('client_secret'),
            'grant_type': 'refresh_token',
            'refresh_token': strava_tokens['refresh_token']
        }
    )
    strava_tokens = response.json()
    with open('strava_tokens.json', 'w', encoding='utf-8') as outfile:
        json.dump(strava_tokens, outfile)
    with open('strava_tokens.json', encoding='utf-8') as check:
        data = json.load(check)
    return data
def get_data(url, access_token, numb_item_page, page):
    print(f'Getting data from page {page}')
    response = requests.get(
        f'{url}?access_token={access_token}&per_page={numb_item_page}&page={page}'
    )
    response = response.json()
    dataframe = pd.json_normalize(response)
    return dataframe
def save_csv(dataframe, filename):
    print(f'Saving {filename}')
    dataframe.to_csv(filename)
def merge_files(path, filename):
    print('Merging files')
    csv_files = [pd.read_csv(_file)
                 for _file in glob.glob(os.path.join(path, "*.csv"))]
    final_df = csv_files.pop(len(csv_files)-1)
    final_df = final_df.append(csv_files)
    save_csv(final_df, filename)
if __name__ == '__main__':
    main()

Имея данные на руках, ДАВАЙТЕ НАЧНЕМ АНАЛИЗ, и для выполнения этого подвига мы будем использовать блокнот Jupyter.

Очистка данных

Первым шагом является импорт библиотек, которые будут использоваться для проведения анализа данных и могут быть отнесены к общим библиотекам, которые будут использоваться для поддержки скрипта, библиотекам для анализа и построения фреймов данных и, наконец, библиотекам для выполнения машинных вычислений. техники обучения.

# general
import subprocess
import calendar
from geopy.geocoders import Nominatim

# df and plotting
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

# machine learning
from sklearn import preprocessing
from sklearn import metrics
from sklearn.cluster import KMeans
from sklearn.feature_selection import chi2
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import RFE
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import SGDClassifier
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler


df = pd.read_csv('result/strava_all_activities.csv')
print('Dataframe Shape:', df.shape)
df.head()

Первый контакт после прочтения полученного CSV показывает, что наш фрейм данных имеет около 58 столбцов и количество строк, которые могут варьироваться в зависимости от результатов каждого спортсмена. Но когда мы смотрим на качество данных, мы замечаем, что многие из них нулевые или не вносят вклад в анализ, поэтому мы выберем только те столбцы, которые интересны для исследования.

null_df = [[col, df[col].isnull().sum()] for col in df.columns]
print('Null Data:', df.isnull().sum().sum())
list(filter(lambda x: x[1]>0, null_df))

selected_columns = ['distance', 'moving_time', 'elapsed_time',
                    'total_elevation_gain', 'type','sport_type', 'id', 'start_date',
                    'start_date_local','location_country', 'achievement_count', 'kudos_count',
                    'comment_count','athlete_count', 'start_latlng',
                    'end_latlng', 'average_speed', 'max_speed', 'average_cadence',
                    'average_heartrate', 'max_heartrate', 'elev_high','elev_low',
                    'upload_id', 'external_id', 'pr_count', 'map.summary_polyline']
df = df[selected_columns]

Сначала я решил извлечь максимум из того, что предлагает набор данных, и попытался удалить как можно меньше данных, поэтому я сгенерировал некоторые данные из даты, заполнил некоторые нулевые поля нулевыми значениями, пометил неизвестно или со средним значением и, наконец, Я сгенерировал некоторые данные, стандартизировав расстояние в километрах и время в минутах.

df['start_date_local'] = pd.to_datetime(df['start_date_local'], errors='coerce')
df = df.sort_values(by='start_date_local')

df['weekday'] = df['start_date_local'].map(lambda x: x.weekday)
df['start_time'] = df['start_date_local'].dt.time
df['start_time'] = df['start_time'].astype(str)
df['start_date'] = df['start_date_local'].dt.date

df = df.drop('start_date_local', 1)
df.head()

df = df.drop(df[(df.distance < 1) & (df.type == 'Run')].index)
df = df.drop(df[(df.distance < 1) & (df.type == 'Ride')].index)
df = df.drop(df[df.average_speed > 30].index)
df = df.reset_index(drop=True)

df['elev_high'] = df['elev_high'].fillna(value=0)
df['elev_low'] = df['elev_low'].fillna(value=0)
df['upload_id'] = df['upload_id'].fillna(value='unknown')
df['external_id'] = df['external_id'].fillna(value='unknown')
df['map.summary_polyline'] = df['map.summary_polyline'].fillna(value='unknown')
df['average_cadence'] = df['average_cadence'].fillna(value=df['average_cadence'].mean())
df['average_heartrate'] = df['average_heartrate'].fillna(value=df['average_heartrate'].mean())
df['max_heartrate'] = df['max_heartrate'].fillna(value=df['max_heartrate'].mean())

df['moving_time_minutes'] = round(df['moving_time']/60, 2)
df['distance_km'] = round(df['distance'] / 1000, 2)
df['pace'] = df['moving_time_minutes'] / df['distance_km']
df['avg_speed_kmh'] = round(60/df['pace'], 2)
df['max_speed_kmh'] = round(df['max_speed']*3.6, 2)

df['elev'] = df['elev_high'] - df['elev_low']
df['year']= df['start_date'].map(lambda x: x.year)

Следует выделить один случай: я хотел заполнить поля города и штата, поскольку среди столбцов также были поля Широта и Долгота из начального обучения. Так что в данном случае использовалась библиотека geopy.

def get_city_state_from_value(value):
    value = value.replace('[','').replace(']','').split(',')
    if value != ['']:
        location = geolocator.reverse(', '.join(value))
        result = f'{location[0].split(",")[1]}, {location[0].split(",")[4]}'
    else:
        result = 'unknown'
    return result
geolocator = Nominatim(user_agent="strava_exploration_data")
df['location'] = df['start_latlng'].map(get_city_state_from_value)

Наконец, был создан фильтр на основе темпа ниже 5 мин/км, то есть был создан столбец, показывающий все забеги со средней скоростью 12 км/ч, которые можно было использовать в качестве переменной отклика в будущих анализах.

df['pace_sub_5'] = np.where(df['pace']<=5, True, False)
df.head()

А при выполнении df.info() и df.describe().transpose() удалось получить следующие данные:

Инсайты

Теперь, имея точные и адекватные данные, мы можем провести серию анализов, пытаясь ответить на такие вопросы, как: Каково количество регистраций, сделанных каждый год? И по следующему блоку можно ответить на этот вопрос.

fig = sns.catplot(x='year', hue='type', data=df, kind='count')
fig.fig.suptitle('Exercices by Years')
fig.set_xlabels('Year')
fig.set_ylabels('Effortments')
fig

Помня, что Strava регистрирует разные типы активности, но в данном случае мы будем подчеркивать только бег, поэтому среди переменных, влияющих на эффективность бега (внутренне), я выделяю высоту, частоту сердечных сокращений и факторы, связанные со скоростью, поэтому я хотел связать их с другими областями.

Был проведен первый анализ, связывающий время активности с высотой пути, из которого можно сделать вывод, что высота имеет тенденцию увеличиваться в зависимости от времени активности или даже в зависимости от расстояния.

runs = df.loc[df['type'] == 'Run']
sns.regplot(x='moving_time_minutes', y = 'elev', data=runs).set_title("Exercice Time vs Elevation")

sns.regplot(x='distance', y = 'elev', data=runs).set_title("Distance vs Elevation")

Но это не следует принимать за правило, так как в некоторых случаях низкие высоты присутствуют независимо от времени или расстояния активности, что очень часто встречается при тренировках на кольцевых беговых дорожках.

Еще одно наблюдение, касающееся времени движения и расстояния, стоит отметить, что самый длинный и трудоемкий тренировочный день обычно приходится на воскресенье, так как доступность для тренировок больше.

runs.groupby('weekday').mean()['moving_time_minutes'].plot.bar()

runs.groupby('weekday').mean()['distance'].plot.bar()

Что касается средней скорости (км/ч), можно было заметить, что чем дольше время активности, тем ниже средняя скорость, поскольку такие факторы, как усталость, начинают мешать производительности, но когда мы наблюдаем среднюю скорость в по отношению к расстоянию можно сказать, что оно имеет тенденцию к росту, хотя и в небольшом масштабе.

sns.regplot(x='moving_time_minutes', y = 'avg_speed_kmh', data=runs).set_title("Average Speed vs Moving Time")

sns.regplot(x='distance', y = 'avg_speed_kmh', data=runs).set_title("Average Speed vs Distance")

Наконец, в моем случае стоит упомянуть, что средняя скорость со временем увеличилась, что является результатом тренировок и всех усилий, направленных на спорт, как показано на изображении ниже.

fig = plt.figure()
ax1 = fig.add_subplot(111)

x = np.asarray(runs.start_date)
y = np.asarray(runs.average_speed)

ax1.plot_date(x, y)
ax1.set_title('Average Speed over Time')

x2 = mdates.date2num(x)
z=np.polyfit(x2,y,1)
p=np.poly1d(z)
plt.plot(x,p(x2),'r--')
fig.autofmt_xdate(rotation=45)
fig.tight_layout()
fig.show()

Выбор функции

Что касается выбора признаков, стоит сказать, что это важный шаг для последующего применения моделей машинного обучения. Для выбора можно использовать статистические методы, которые выявят, какие функции (или характеристики) являются наиболее важными для хорошей работы модели. Таким образом, мы также сможем подтвердить, действительно ли эмпирически приведенные характеристики являются хорошими.

Первый способ узнать больше об функциях и о том, как они взаимодействуют с переменной ответа, — это использовать матрицу корреляции, к которой можно легко получить доступ в pandas с помощью следующей команды.

corr = runs.corr()
plt.figure(figsize = (12,8))
sns.heatmap(corr, fmt=".2f");
plt.title('Correlation between dataset variables')
plt.show()

Из этой матрицы можно определить, что значения, ближайшие к +1, имеют положительную корреляцию, а значения, ближайшие к -1, имеют отрицательную корреляцию.

Чтобы продолжить анализ, текущий фрейм данных будет зашифрован, чтобы предотвратить проблемы, связанные со смещением и изучением последовательности фреймов данных. Кроме того, из анализа будут удалены категориальные характеристики, а также id прогонов, поскольку эти поля недействительны для обучения модели и поля, которые не будут способствовать предсказанию, так как они формируются после завершения прогона.

runs = runs.sample(frac=1).reset_index(drop=True)

categorical_cols = [col for col in runs.columns if runs[col].dtypes == 'O']

useless_vars = ['id', 'achievement_count', 'kudos_count', 'comment_count', 'pr_count']
tweak_runs = runs.drop(categorical_cols+useless_vars, axis=1)
tweak_runs

В этот момент переменные ответа и те, которые будут использоваться для обучения, будут разделены, чтобы после этого действия можно было применить метод SelectKBest с использованием chi2 (или других одномерных статистических тестов) для выбора наиболее важных K признаков. для хорошей работы модели.

y = tweak_runs['pace']
X = tweak_runs.drop('pace',1)

best_features = SelectKBest(chi2, k=7).fit_transform(X, y.astype(int))
best_features

Другой альтернативой выбору признаков является применение рекурсивного исключения признаков (RFE), которое обучает выбранную модель и рекурсивно исключает признаки в соответствии с их неважностью. Но, как описано, на этом этапе необходимо, чтобы модели уже были выбраны для этого запуска, поэтому мы запустим одну для выполнения линейной регрессии, а другую для выполнения стохастического градиентного спуска (SGD), поэтому переменная отклика будет отличаться для этих двух. модели .

Стоит отметить, что RFE — тяжелый метод для многомерных наборов данных, поэтому альтернативой является использование SelectFromModel. Ниже следует определение функции для выполнения RFE, а также ее использование для случая линейной регрессии и SGD соответственно.

def get_best_rfe_features(X,y, model):
    rfe = RFE(model, step=0.05).fit(X, y)
    selected_features = [i for i, j in zip(X.columns, rfe.support_) if j]
    return selected_features
y = tweak_runs['pace']
X = tweak_runs.drop('pace',1)

encoded_y = preprocessing.LabelEncoder().fit_transform(y)
model = LinearRegression()
linear_feats = get_best_rfe_features(X, encoded_y, model)

y = tweak_runs['pace_sub_5']
X = tweak_runs.drop('pace_sub_5',1)

model = SGDClassifier(loss="hinge", penalty="l2", max_iter=5)
class_feats = get_best_rfe_features(X, y, model)

Другие методы и комбинации, которые могут быть применены, например, изменение loss модели SGD или даже количества steps в RFE, но поскольку цель этой статьи не в том, чтобы углубляться в этот путь, изложение методов достаточно для нас, чтобы принести хорошие результаты

Машинное обучение

Среди методов машинного обучения в этом проекте мы будем использовать три: для кластеризации, регрессии и классификации.

Кластеризация

Среди методов машинного обучения, которые могут быть реализованы, мы упомянем одну из областей обучения без учителя, которая используется для выполнения кластеризации, которая называется K-средних. Поэтому на практике этот метод будет группировать записи похожих гонок. Стоит отметить, что этот метод является стохастическим, поэтому каждое выполнение может давать разные результаты.

Чтобы запустить K-means, просто отделите переменную ответа от других, в которой мы будем использовать функцию pandas get_dummies, чтобы также использовать категориальные переменные, которые будут преобразованы в другие фиктивные переменные. После разделения переменных просто выберите количество кластеров и перейдите к классу K-средних вместе с соответствием набору данных характеристик. Чтобы узнать, в каком кластере находится каждая выборка, мы сделаем копию исходного набора данных прогона и добавим в записи идентификатор кластера.

X = runs.drop('pace',1)
X = pd.get_dummies(X)

model = KMeans(n_clusters=4).fit(X)
clusterin_runs = runs.copy()
clusterin_runs['Cluster'] = model.labels_

Как только это будет сделано, мы сможем просмотреть такие показатели, как количество записей в каждом из кластеров, среднее значение, стандартное отклонение, записи и другие, как показано в примерах ниже.

clustering_runs['Cluster'].value_counts()

clustering_runs.groupby('Cluster').mean()

clustering_runs.groupby('Cluster').std()

clustering_runs[clustering_runs['Cluster'] == 2]

Регрессия

Когда дело доходит до регрессий, модели вынуждены предсказывать значение y с учетом набора функций x, то есть модель узнает, какие значения составляют уравнение линии, корректируя несколько строк на карте функций и возвращая линия с наименьшей ошибкой между точками.

Для применения линейной регрессии мы будем использовать только те признаки, которые были получены при предыдущей выборке и сохранены в переменной linear_feats. Кроме того, мы разделим набор данных на 80% для этапа обучения и 20% для этапа тестирования, чтобы мы могли измерить производительность алгоритма после этапа обучения.

y = runs['pace']
X = runs[linear_feats]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

После завершения деления просто отправьте набор обучающих данных функции подбора линейной регрессии и выполните прогноз относительно тестовой базы.

model = LinearRegression()
model.fit(X_train,y_train)
y_pred = model.predict(X_test)

Имея прогнозируемые значения и фактические значения, можно рассчитать среднеквадратичную ошибку (MSE или среднеквадратичную ошибку), чтобы продемонстрировать, насколько сильно модель допускает ошибки, а также можно графически увидеть прогнозируемое значение в отношении к фактическому значению с помощью приведенного ниже кода. Среднеквадратическая ошибка составила 0,271.

print('MSE:', metrics.mean_squared_error(y_test, y_pred))

plt.figure(figsize=(10,10))
plt.scatter(y_test, y_pred, c='crimson')
plt.yscale('log')
plt.xscale('log')

p1 = max(max(y_pred), max(y_test))
p2 = min(min(y_pred), min(y_test))
plt.plot([p1, p2], [p1, p2], 'b-')
plt.xlabel('True Values', fontsize=15)
plt.ylabel('Predictions', fontsize=15)
plt.axis('equal')
plt.show()

Для примера предположим, что я пробежал 5 км за 1488 секунд (24,8 минуты) со средней скоростью 4 м/с (14,4 км/ч) и с максимальной скоростью 5,6 м/с (20,16 км/ч) с шагом из 84 в 2022 году, выполняя темп ниже 5, прогнозируется, что мой темп будет равен 4,1752 мин/км.

model.predict(
    pd.DataFrame(data={
        'moving_time': 1488,
        'average_speed': 4.0,
        'max_speed': 5.6,
        'average_cadence': 84.0,
        'moving_time_minutes': 24.8,
        'distance_km': 5.0,
        'avg_speed_kmh': 14.4,
        'max_speed_kmh': 20.16 ,
        'year': 2022,
        'pace_sub_5': True},
    index=[0]
    )
)

Классификация

Классификации, в свою очередь, используются для дифференциации классов, то есть переменная отклика имеет характеристику, которая маркирует выборки, и, таким образом, мы можем попытаться предсказать, какую метку получит данная выборка, основываясь на ее характеристиках.

Для выполнения этого действия проделаем нечто подобное тому, что было сделано на этапе регрессии, разделив базы на 80% для обучения и 20% для тестирования, а также разделение выборок с рекомендуемыми характеристиками и переменной отклика, которые в этом случае будет бинарным, показывающим, был ли темп гонки ниже 5 или нет.

y = runs['pace_sub_5']
X = runs[class_feats]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

Проведя разделение баз, подгоним классификатор SGDClassifier из библиотеки SKLearn, реализующий регуляризацию линейных моделей с помощью стохастического градиентного спуска для оптимизации параметров итеративным способом с целью минимизации интересующей функции. В приведенном ниже фрагменте кода прогнозирование также было выполнено по отношению к тестовому набору данных.

model = SGDClassifier()
model.fit(X_train,y_train)
y_pred = model.predict(X_test)

В отличие от регрессий, для систем классификации мы можем использовать другие показатели, такие как точность, которая представляет собой отношение между правильными прецизионностями к общей выполненной точности, которая в нашем случае составила 95,12%. Чтобы получить представление об истинно положительном (фактическое значение положительное, а предсказанное положительное), истинно отрицательном (фактическое значение отрицательное, а предсказанное отрицательное), ложноположительном (фактическое значение отрицательное, а предсказанное положительное) и ложноотрицательном (фактическое значение положительный, а прогноз отрицательный), мы можем разработать матрицу путаницы, как показано в приведенном ниже коде.

print('Accuracy:', accuracy_score(y_test, y_pred))

cm = confusion_matrix(y_test, y_pred, labels=model.classes_)
disp = ConfusionMatrixDisplay(confusion_matrix=cm,display_labels=model.classes_)

disp.plot()
plt.show()

Поэтому, чтобы проиллюстрировать реальную ситуацию, предположим, что я бегу 5000 м с общим временем активности, равным 1488 секундам (24,8 мин), и временем движения 1440 секунд, с набором высоты 25 м и перепадом высот 10 м. (547 м максимальной высоты и 237 м минимальной высоты), достигнув 25 км/ч максимальной скорости и 210 уд/мин, я смог пройти гонку со средним темпом ниже 5 мин/км.

model.predict(
    pd.DataFrame(data={
        'distance': 5000 ,
        'moving_time': 1440,
        'elapsed_time': 1488,
        'total_elevation_gain': 25,
        'max_heartrate': 210,
        'elev_high': 547,
        'elev_low': 537,
        'moving_time_minutes': 24.8,
        'max_speed_kmh': 25,
        'elev': 10
    },
        index=[0]
    )
)

Заключение

Возможности для изучения данных и применения различных типов моделей бесчисленны, когда данные присутствуют. В этой статье удалось протестировать некоторые из них и получить отличные результаты в отношении понимания и прогнозов на гонку, и следует учитывать, что данные основаны на одном спортсмене, то есть, чтобы выводы были более в целом, потребуется большее разнообразие в отношении спортсменов, составляющих набор данных, однако, если следовать начальным шагам, возможно, что каждый спортсмен сможет иметь предикторы и идеи, связанные с их собственными данными.

Если вы хотите проверить тесты и используемый код, не стесняйтесь обращаться к следующему репозиторию:



Рекомендации

[1] Ламартин, С. Линейная регрессия со Склеарном: Conceito e Aplicação (2020). Середина.

[2] Стоилькович, М. Алгоритм стохастического градиентного спуска с Python и NumPy (2020), Real Python.

[3] Васконселлос, П. Выбранные в качестве мелорес особенности для модели машинного обучения (2019), Пауло Васконселлос — Cientista de Dados Brasileiro.

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу