За последние пару лет PyTorch стал одной из ведущих сред машинного обучения, и различные компании используют ее простую в использовании структуру для своих исследований. Согласно The Gradient, PyTorch становится серьезным конкурентом давнему фавориту, TensorFlow, демонстрируя значительный рост упоминаний о нем на исследовательских конференциях.
Основанная в 2016 году Адамом Пашке, Сэмом Гроссом, Сумит Чинтала и Грегори Ченаном, ее использование идиоматического Python делает ее очень простой в реализации.
В приведенном ниже коде показана умная интеграция PyTorch с идиоматическим Python.
В этом посте мы продемонстрируем логистическую регрессию в PyTorch, используя некоторые данные Car Crash (нажмите здесь, чтобы загрузить).
Наша цель — предсказать уровень ПОВРЕЖДЕНИЙ автомобиля. Это разделено на три категории (что делает это проблемой классификации):
- $500 или менее в ущерб
- $501 — $1,500
- Более 1500 долларов США
1. Подготовка данных
Во-первых, давайте подготовим наши данные для моделирования.
Мы собираемся загрузить данные в виде Pandas DataFrame, выполнить предварительную обработку, а затем преобразовать в тензорный формат, который будет использоваться PyTorch.
import pandas as pd pd.set_option('display.max_columns', None) data = pd.read_csv('traffic_crashes_chicago.csv')
Давайте проверим наши значения NaN:
data.isna().sum() CRASH_RECORD_ID 0 RD_NO 3917 CRASH_DATE_EST_I 446607 CRASH_DATE 0 POSTED_SPEED_LIMIT 0 TRAFFIC_CONTROL_DEVICE 0 DEVICE_CONDITION 0 WEATHER_CONDITION 0 LIGHTING_CONDITION 0 FIRST_CRASH_TYPE 0 TRAFFICWAY_TYPE 0 LANE_CNT 283902 ALIGNMENT 0 ROADWAY_SURFACE_COND 0 ROAD_DEFECT 0 REPORT_TYPE 11746 CRASH_TYPE 0 INTERSECTION_RELATED_I 373918 NOT_RIGHT_OF_WAY_I 460154 HIT_AND_RUN_I 340916 DAMAGE 0 DATE_POLICE_NOTIFIED 0 PRIM_CONTRIBUTORY_CAUSE 0 SEC_CONTRIBUTORY_CAUSE 0 STREET_NO 0 STREET_DIRECTION 3 STREET_NAME 1 BEAT_OF_OCCURRENCE 5 PHOTOS_TAKEN_I 476801 STATEMENTS_TAKEN_I 473134 DOORING_I 481317 WORK_ZONE_I 479748 WORK_ZONE_TYPE 480406 WORKERS_PRESENT_I 482118 NUM_UNITS 0 MOST_SEVERE_INJURY 978 INJURIES_TOTAL 967 INJURIES_FATAL 967 INJURIES_INCAPACITATING 967 INJURIES_NON_INCAPACITATING 967 INJURIES_REPORTED_NOT_EVIDENT 967 INJURIES_NO_INDICATION 967 INJURIES_UNKNOWN 967 CRASH_HOUR 0 CRASH_DAY_OF_WEEK 0 CRASH_MONTH 0 LATITUDE 2670 LONGITUDE 2670 LOCATION 2670 dtype: int64
Теперь мы отбросим все тяжелые столбцы NaN и столбцы, которые не имеют никакой прогностической ценности:
data.drop(labels=['CRASH_RECORD_ID', 'RD_NO', 'CRASH_DATE_EST_I', 'LANE_CNT', 'NOT_RIGHT_OF_WAY_I', 'HIT_AND_RUN_I', 'DATE_POLICE_NOTIFIED', 'STREET_NO', 'STREET_DIRECTION', 'STREET_NAME', 'BEAT_OF_OCCURRENCE', 'PHOTOS_TAKEN_I', 'STATEMENTS_TAKEN_I', 'DOORING_I', 'WORK_ZONE_I', 'CRASH_DATE', 'REPORT_TYPE', 'INTERSECTION_RELATED_I', 'WORK_ZONE_TYPE', 'LOCATION', 'WORKERS_PRESENT_I'], axis=1, inplace=True)
Поскольку оставшиеся значения NaN занимают лишь небольшую часть оставшегося набора данных, мы удалим все строки, содержащие значения NaN:
data.dropna(inplace=True)
Теперь давайте разобьем данные на наборы training и test, используя sklearn
. В PyTorch также есть вариант:
from torch.utils.data import random_split
Это может выполнить эту операцию, но для этого примера мы будем использовать sklearn
:
X = data.drop(labels='DAMAGE', axis=1) y = data['DAMAGE'] from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y)
Давайте масштабируем наши числовые переменные и возьмем манекены для наших категориальных переменных:
from sklearn.preprocessing import StandardScaler ss = StandardScaler() numerical_train = X_train[['LATITUDE', 'LONGITUDE']] num_X_train_scaled = pd.DataFrame(ss.fit_transform(numerical_train), columns =numerical_train.columns) categorical_train = X_train.drop(labels=['LATITUDE', 'LONGITUDE'], axis=1) # Using OneHotEncoder to get converted categorical variables from sklearn.preprocessing import OneHotEncoder ohe = OneHotEncoder(sparse=False) X_tr_dummies = pd.DataFrame(ohe.fit_transform(categorical_train), columns = ohe.get_feature_names()) X_tr_transformed = pd.concat([X_tr_dummies, num_X_train_scaled], axis=1)
А для наших тестовых данных…
numerical_test = X_test[['LATITUDE', 'LONGITUDE']] num_X_test_scaled = pd.DataFrame(ss.transform(numerical_test), columns=numerical_test.columns) categorical_test = X_test.drop(labels=['LATITUDE', 'LONGITUDE'], axis=1) X_te_dummies = pd.DataFrame(ohe.transform(categorical_test), columns = ohe.get_feature_names()) X_te_transformed = pd.concat([X_te_dummies, numerical_test], axis=1)
И, наконец, что не менее важно, мы должны преобразовать наш Pandas Dataframe в тензор PyTorch:
# X_train values X_tr_tensor = torch.tensor(X_tr_transformed.values) # X_test values X_te_tensor = torch.tensor(X_te_transformed.values)
2. Разделите наши данные на «пакеты»
Вкратце это процесс логистической регрессии PyTorch:
- Получите случайную выборку из данных («партия»).
- Вычислите потери (в этом примере мы будем использовать функцию потерь перекрестной энтропии).
- Вычислите градиент, выполнив «обратный проход» (loss.backward())
- Используйте функцию оптимизатора, чтобы предпринять шаг для минимизации потерь (в этом примере мы будем использовать SGD, стохастический градиентный спуск).
- Промойте и повторите для любого заданного количества эпох.
Давайте начнем!
import torch X_tr_tensor = torch.tensor(x_tr_transformed.values) X_tr_tensor.shape >>> torch.Size([359417, 387]) X_te_tensor = torch.tensor(x_te_transformed.values) X_te_tensor.shape >>> torch.Size([210273, 387])
Давайте разделим наши данные на 100 пакетов одинакового размера:
from torch.utils.data import DataLoader batch_size = 100 train_loader = DataLoader(X_tr_tensor, batch_size, shuffle=True) test_loader = DataLoader(X_te_tensor, batch_size)
3. Создайте экземпляр нашей модели!
import torch.nn as nn input_size = X_tr_tensor.shape[1] num_classes = 3 # Logistic regression model model = nn.Linear(input_size, num_classes)
Давайте посмотрим на наши начальные веса и смещения:
model.weight.shape >>> torch.Size([3, 387]) model.weight >>> Parameter containing: tensor([[ 0.0189, 0.0020, -0.0448, ..., 0.0175, 0.0464, 0.0140], [-0.0335, 0.0505, -0.0031, ..., -0.0387, -0.0364, -0.0502], [ 0.0013, 0.0317, 0.0103, ..., -0.0373, 0.0377, 0.0122]], requires_grad=True) model.bias.shape >>> torch.Size([3]) # 3 biases for 3 classes, a ternary classifier model.bias >>> Parameter containing: tensor([-0.0396, -0.0158, -0.0029], requires_grad=True) # Alternative: list(model.parameters()) >>> [Parameter containing: tensor([[ 0.0189, 0.0020, -0.0448, ..., 0.0175, 0.0464, 0.0140], [-0.0335, 0.0505, -0.0031, ..., -0.0387, -0.0364, -0.0502], [ 0.0013, 0.0317, 0.0103, ..., -0.0373, 0.0377, 0.0122]], requires_grad=True), Parameter containing: tensor([-0.0396, -0.0158, -0.0029], requires_grad=True)]
4. Создавайте прогнозы:
outputs = model(X_tr_tensor.float())
В sklearn
мы можем генерировать вероятности, используя метод predict_proba()
. В PyTorch мы будем использовать следующее:
import torch.nn.functional as F probs = F.softmax(outputs, dim=1) print("Here are some of our newly converted probabilities:\n", probs[:2].data) >>> Here are our sample probabilities: tensor([[0.2804, 0.3906, 0.3291], [0.3087, 0.3869, 0.3043]]) print(torch.sum(probs[0]).item()) >>> Sum: 1.0
Этот метод softmax()
преобразует значения логита в положительные вероятности:
Прежде чем мы сможем рассчитать, насколько хороша наша модель, мы должны преобразовать наши данные y_train
и y_test
в тензоры:
# Converting y values into their respective classes (0,1,2) conditions_train = [(y_train == 'OVER $1,500'), (y_train == '$501 - $1,500'), (y_train == '$500 OR LESS')] conditions_test = [(y_test == 'OVER $1,500'), (y_test == '$501 - $1,500'), (y_test == '$500 OR LESS')] choices = [0,1,2] y_train = np.select(conditions_train, choices) y_test = np.select(conditions_test, choices) #Typecasting into tensors: y_tr_tensor = torch.tensor(y_train) y_te_tensor = torch.tensor(y_test)
5. Рассчитайте убытки
loss_fn = F.cross_entropy loss = loss_fn(outputs, y_tr_tensor) print(loss) >>> tensor(1.1407, grad_fn=<NllLossBackward>)
Поскольку кросс-энтропия представляет собой отрицательный логарифм прогнозируемой вероятности правильной метки, усредненной по всем обучающим выборкам, мы можем получить представление о нашей средней «точности», используя следующее :
import math math.exp(-loss) >>> 0.3196105865388012
6. Создайте/внедрите функцию или подкласс для повторения процесса для всех пакетов и эпох:
Чтобы упростить этот процесс для каждого пакета и каждой эпохи, мы можем использовать функцию, подобную следующей (источник: FreeCodeCamp):
def fit(epochs, lr, model, train_loader, val_loader, opt_func=torch.optim.SGD ): optimizer = opt_func(model.parameters(), lr) history = [] # for recording epoch-wise results for epoch in range(epochs): # Training Phase for batch in train_loader: loss = model.training_step(batch) loss.backward() optimizer.step() optimizer.zero_grad() # Validation phase result = evaluate(model, val_loader) model.epoch_end(epoch, result) history.append(result) return history
Этот учебный модуль можно анализировать с помощью других функций PyTorch, таких как TensorBoard. Вы можете получить доступ к официальному руководству здесь.
Если вы хотите поиграть с некоторыми другими наборами данных с нейронной сетью, вы можете загрузить наборы данных с помощью следующего кода:
# MNIST (Modified National Institute of Standards and Technology)
# handwritten digits 0-9 .
from torchvision.datasets import MNIST
dataset = MNIST(root='data/', download=True)
Чтобы просмотреть другие встроенные наборы данных, нажмите здесь. Другие наборы данных включают CelebA (крупномасштабный набор данных атрибутов лиц с более чем 200 тыс. изображений знаменитостей), CIFAR (распознавание объектов) и Cityscapes (изображения городских уличных сцен из 50 разных городов).