Trainiere ein eigenes Modell mit PyTorch

Download this notebook

Dieser Teil läuft analog zu Trainiere ein eigenes Modell mit TensorFlow. Wenn das eigene Problem bereits zufriedenstellend mit einem PhotonAI Modell gelöst werden konnte, kann dieses Kapitel also übersprungen werden. Andererseits wird die Erstellung eines Modells in diesem Kapitel noch knapper behandelt und es kann durchaus sinnvoll sein, zunächst das vorherige Kapitel zu lesen. Abgesehen davon handelt es sich ebenfalls nicht um eine Einführung in PyTorch oder die Erstellung von Machine Learning Modellen allgemein. Vielmehr soll lediglich ein einfaches Modell erstellt werden, das in den folgenden Teilen als Beispiel im Deployment- und Veröffentlichungs-Prozess dient. Die PyTorch Tutorials helfen dabei, mehr über die Erstellung von Machine Learning Modellen mit PyTorch zu lernen.

Inhaltsverzeichnis

Was ist PyTorch?

PyTorch

PyTorch ist ein Open-Source-Deep-Learning-Framework, das besonders für seine flexible und dynamische Berechnungsgraphen-Struktur bekannt ist. Es erleichtert das Trainieren von neuronalen Netzen durch eine intuitive Schnittstelle und bietet umfangreiche Unterstützung für mehrdimensionale Datenverarbeitung. PyTorch ermöglicht es Entwicklern, Modelle auf verschiedene Plattformen zu deployen, einschließlich mobiler Geräte. Das Framework bietet eine breite Palette von Funktionen und Module, um individuelle Anforderungen zu erfüllen.

Für Python kann PyTorch im Terminal direkt über pip installiert werden:

pip install torch

import pickle
from pathlib import Path

import matplotlib.pyplot as plt
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OrdinalEncoder, StandardScaler

import torch
import torch.nn as nn
import torch.optim as optim

Vorbereitung der Daten

Zunächst definieren wir wieder unsere Daten und Projekt-Struktur. In diesem Fall befinden wir uns mit dem Notebook im Ordner /incubaitor/1_Frameworks/1_3_Torch/notebooks/. Relativ dazu haben wir nun die Daten in den Ordner /incubaitor/1_Frameworks/1_3_Torch/data/ geladen (siehe vorheriges Kapitel) und werden unsere Modelle in /incubaitor/1_Frameworks/1_3_Torch/models/ abspeichern. Somit können wir die relativen Pfade ../data/ und ../models/ nutzen. Falls es hier Probleme gibt, können auch explizite Pfade wie im PhotonAI Beispiel genutzt werden.

Nachdem wir alle Libraries und Funktionen importiert haben, bereiten wir analog zum TensorFlow-Tutorial erst unsere Daten vor. Wichtig ist hierbei, dass wir unsere Daten in die Datenstruktur torch.tensor() packen, mit der PyTorch alle Berechnungen effizient durchführen kann.

def str_to_category(string):
    """
    Converts a string to a category.
    :param string: Input string.
    :return: Category.
    """
    return string.strip(' \t\n').lower().replace(' ', '_').replace('-', '')

# Load data and split into labels and features
data = pd.read_csv('../data/vw.csv', converters={
    'model': str_to_category,
    'transmission': str_to_category,
    'fuelType': str_to_category,
})
label = data.pop('price')

# Encode categorical data
model_enc = OrdinalEncoder()
data['model'] = model_enc.fit_transform(data[['model']])
transmission_enc = OrdinalEncoder()
data['transmission'] = transmission_enc.fit_transform(data[['transmission']])
fuelType_enc = OrdinalEncoder()
data['fuelType'] = fuelType_enc.fit_transform(data[['fuelType']])

# Split data into training and test sets
train_data, test_data, train_label, test_label = train_test_split(
    data, 
    label, 
    test_size=0.2, 
    random_state=42
    )

# Normalize data
data_scaler = StandardScaler()
train_data = torch.tensor(
    data_scaler.fit_transform(train_data), 
    dtype=torch.float32
    ) 
test_data = torch.tensor(
    data_scaler.transform(test_data), 
    dtype=torch.float32
    ) 

# Normalize labels
label_scaler = StandardScaler()
train_label = torch.tensor(
    label_scaler.fit_transform(
        train_label.values.reshape(-1, 1)
        ), 
    dtype=torch.float32) 
test_label = torch.tensor(
    label_scaler.transform(
        test_label.values.reshape(-1, 1)
        ), 
        dtype=torch.float32) 

Erstellung und Training des Modells

Ähnlich zu TensorFlow benutzen wir hier den Container nn.Sequential, mit dem wir uns ein einfaches Multilayer-Perceptron (MLP) mit zwei Hidden-Layern der Größe 64 und mit ReLU-Aktivierung erstellen:

# Create the model
model = nn.Sequential(
    nn.Linear(train_data.shape[1], 64), 
    nn.ReLU(),  
    nn.Linear(64, 64),  
    nn.ReLU(),  
    nn.Linear(64, 1)  
)

Anders als TensorFlow besitzt PyTorch nicht die Funktionen compile() und fit(), um das MLP zu trainieren. Stattdessen müssen wir das Training selber mit der Funktion train_model() implementieren. Dazu geben wir auch eine Loss-Funktion und einen Optimizer an, sowie die Anzahl der Epochen und die Größe unserer Batches. Während des Trainings werden wir in der Konsole regelmäßig über den Trainingsfortschritt informiert. Dadurch können wir einschätzen wie lange das Training noch etwa braucht und kontrollieren, dass der Loss tatsächlich abnimmt und unser Modell konvergiert.

# Define the loss function as the Mean Squared Error
criterion = nn.MSELoss()

# Define the optimizer (Adam)
optimizer = optim.Adam(model.parameters())

# Define function for training the model
def train_model(model, 
                criterion, 
                optimizer, 
                train_data, 
                train_label, 
                epochs, 
                batch_size
                ):
    
    model.train()
    for epoch in range(epochs):
        for i in range(0, len(train_data), batch_size):
            batch_data = train_data[i:i+batch_size]
            batch_label = train_label[i:i+batch_size]

            optimizer.zero_grad()
            output = model(batch_data)
            loss = criterion(output, batch_label)
            loss.backward()
            optimizer.step()
        print(f'Epoch: {epoch+1}/{epochs}, loss: {loss.item()}')

# Train the model
train_model(model, 
            criterion, 
            optimizer, 
            train_data, 
            train_label, 
            epochs=50, 
            batch_size=64
            )

Evaluation und Speicherung

Um das Modell zu evaluieren, schreiben wir uns wiederum eine Funktion evaluate_model(), die uns den Test-Loss in zwei verschiedenen Metriken ausgibt. Außerdem wollen wir wieder unsere Ergebnisse visualisieren und machen dies analog zum TensorFlow Tutorial.

# Define function for evaluating the model
def evaluate_model(model, criterion, test_data, test_label):
    model.eval()
    with torch.no_grad():
        output = model(test_data)
        test_loss = criterion(output, test_label)
        mae = torch.mean(torch.abs(output - test_label))
    return test_loss, mae

# Evaluate the model
test_loss, mae = evaluate_model(model, criterion, test_data, test_label)
print(f'Test Loss: {test_loss}, Mean Absolute Error: {mae}')

# Plot some predictions
predictions = label_scaler.inverse_transform(
    model(test_data).detach().numpy()
    ) 
test_label = label_scaler.inverse_transform(test_label.numpy()) 
plt.scatter(test_label, predictions, s=0.1)
plt.plot([0, test_label.max()], [0, test_label.max()], '--', color='red')
plt.xlabel('True Values')
plt.ylabel('Predictions')
plt.show()

Um das Modell zu speichern, können wir ganz einfach torch.save() benutzen, der das Modell am gewünschten Pfad speichert. Außerdem speichern wir wieder alle unsere Encoder und Scaler, um unsere Eingabedaten korrekt vorbereiten zu können.

# Save the model, encoder and scaler
Path("../models/").mkdir(parents=True, exist_ok=True)
torch.save(model, '../models/model.pt') 
pickle.dump(model_enc, open('../models/model_enc.pkl', 'wb'))
pickle.dump(transmission_enc, open('../models/transmission_enc.pkl', 'wb'))
pickle.dump(fuelType_enc, open('../models/fuelType_enc.pkl', 'wb'))
pickle.dump(data_scaler, open('../models/data_scaler.pkl', 'wb'))
pickle.dump(label_scaler, open('../models/label_scaler.pkl', 'wb'))

Verwendung des Modells

Um das Modell zur Vorhersage zu benutzen, laden wir es mit torch.load() und erstellen uns einen DataFrame, in den wir Beispiel-Daten speichern. Hierbei ist wiederum darauf zu achten, dass die Daten in torch.tensor() gewrappt werden.

# Load model, encoder, and scaler
model = torch.load("../models/model.pt") 
model_enc = pickle.load(open('../models/model_enc.pkl', 'rb'))
transmission_enc = pickle.load(open('../models/transmission_enc.pkl', 'rb'))
fuelType_enc = pickle.load(open('../models/fuelType_enc.pkl', 'rb'))
data_scaler = pickle.load(open('../models/data_scaler.pkl', 'rb'))
label_scaler = pickle.load(open('../models/label_scaler.pkl', 'rb'))


# Load and prepare test data
dummy_data = pd.DataFrame({
        "model": [str_to_category("T-Roc")],
        "year": [2019],
        "transmission": [str_to_category("Manual")],
        "mileage": [12132],
        "fuelType": [str_to_category("Petrol")],
        "tax": [145],
        "mpg": [42.7],
        "engineSize": [2.0],
    })
dummy_data.loc[:, "model"] = model_enc.transform(
    dummy_data.loc[:, ["model"]]
    )
dummy_data.loc[:, "transmission"] = transmission_enc.transform(
    dummy_data.loc[:, ["transmission"]]
    )
dummy_data.loc[:, "fuelType"] = fuelType_enc.transform(
    dummy_data.loc[:, ["fuelType"]]
    )
dummy_data = data_scaler.transform(dummy_data)

# Convert the dummy_data to PyTorch Tensor
dummy_data = torch.tensor(dummy_data, dtype=torch.float32)

Schließlich können wir den Preis des Beispiel-Autos vorhersagen:

# Predict
with torch.no_grad():
    result = model(dummy_data)
    result = label_scaler.inverse_transform(result.numpy())[0, 0]
    print(result)

Als Ergebnis wird etwa 26788.51 ausgegeben, was nah an dem Ergebnis des PhotonAI-Modells und des TensorFlow-Modells liegt und ebenfalls einem realistischen Preis entspricht.

Wir konnten mit PyTorch also ein eigenes Modell entwerfen, trainieren und nun sogar verwenden. Dafür waren zwar deutlich mehr manuelle Schritte notwendig, die uns zuvor von der PhotonAI Hyperpipe abgenommen wurden, im Ausgleich konnten wir jedoch auch kategorische Features für unsere Vorhersage nutzen.