За последние пару лет 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 разных городов).