Single FC-NN on Numeric Data (PyTorch)


In [1]:
# restart your notebook if prompted on Colab
try:
    import verta
except ImportError:
    !pip install verta

This example features:

  • PyTorch fully-connected neural network
  • PyTorch's DataSet utility for batching training data
  • verta's Python client logging training results

In [2]:
HOST = "app.verta.ai"

PROJECT_NAME = "MNIST Multiclassification"
EXPERIMENT_NAME = "FC-NN"

In [3]:
# import os
# os.environ['VERTA_EMAIL'] = 
# os.environ['VERTA_DEV_KEY'] =

Imports


In [4]:
from __future__ import print_function

import warnings
warnings.filterwarnings("ignore", category=FutureWarning)

import itertools
import time

import six

import numpy as np
import pandas as pd

from sklearn import datasets

import torch
import torch.nn as nn
import torch.nn.functional as func
import torch.optim as optim
import torch.utils.data as data_utils

Log Workflow

Prepare Data


In [5]:
data = datasets.load_digits()

X = data['data']
y = data['target']

In [6]:
df = pd.DataFrame(np.hstack((X, y.reshape(-1, 1))),
                  columns=["pixel_{}".format(i) for i in range(X.shape[-1])] + ['digit'])

df.head()

In [7]:
# gather indices to split training data into training and validation sets
shuffled_idxs = np.random.permutation(len(y))
idxs_train = shuffled_idxs[int(len(shuffled_idxs)/10):]  # last 90%
idxs_val = shuffled_idxs[:int(len(shuffled_idxs)/10)]  # first 10%

X_train, y_train = (torch.tensor(X[idxs_train], dtype=torch.float),
                    torch.tensor(y[idxs_train], dtype=torch.long))
X_val, y_val = (torch.tensor(X[idxs_val], dtype=torch.float),
                torch.tensor(y[idxs_val], dtype=torch.long))

In [8]:
# create Dataset object to support batch training
class TrainingDataset(data_utils.Dataset):
    def __init__(self, features, labels):
        self.features = features
        self.labels = labels
        
    def __len__(self):
        return len(self.labels)
    
    def __getitem__(self, idx):
        return (self.features[idx], self.labels[idx])

Instantiate Client


In [9]:
from verta import Client
from verta.utils import ModelAPI

client = Client(HOST)
proj = client.set_project(PROJECT_NAME)
expt = client.set_experiment(EXPERIMENT_NAME)
run = client.set_experiment_run()

Define Model


In [10]:
hidden_size = 512
run.log_hyperparameter("hidden_size", hidden_size)
dropout = 0.2
run.log_hyperparameter("dropout", dropout)

class Net(nn.Module):
    def __init__(self, num_features=X.shape[1],
                 hidden_size=hidden_size):
        super(Net, self).__init__()
        self.fc      = nn.Linear(num_features, hidden_size)
        self.dropout = nn.Dropout(dropout)
        self.output  = nn.Linear(hidden_size, 10)
        
    def forward(self, x):
        x = x.view(x.shape[0], -1)  # flatten non-batch dimensions
        x = func.relu(self.fc(x))
        x = self.dropout(x)
        x = func.softmax(self.output(x), dim=-1)
        return x

Prepare Hyperparameters


In [11]:
# specify training procedure
model = Net()

criterion = torch.nn.CrossEntropyLoss()
run.log_hyperparameter("loss_fn", "cross entropy")
optimizer = torch.optim.Adam(model.parameters())
run.log_hyperparameter("optimizer", "adam")

num_epochs = 5
run.log_hyperparameter("num_epochs", num_epochs)
batch_size = 32
run.log_hyperparameter("batch_size", batch_size)

Run and Log Training


In [12]:
# enable batching of training data
dataset = TrainingDataset(X_train, y_train)
dataloader = data_utils.DataLoader(dataset,
                                   batch_size=batch_size,
                                   shuffle=True)

In [13]:
for i_epoch in range(num_epochs):
    for i_batch, (X_batch, y_batch) in enumerate(dataloader):
        model.zero_grad()  # reset model gradients

        output = model(X_batch)  # conduct forward pass

        loss = criterion(output, y_batch)  # compare model output w/ ground truth
        
        print("\repoch {}/{} | ".format(i_epoch+1, num_epochs), end='')
        print("iteration {}/{} | ".format(i_batch+1, len(dataloader)), end='')
        print("epoch loss avg: {}".format(loss.item()), end='')

        loss.backward()  # backpropogate loss to calculate gradients
        optimizer.step()  # update model weights
    with torch.no_grad():  # no need to calculate gradients when assessing accuracy
        print()
        
        pred_train = model(X_train).numpy().argmax(axis=1)
        train_acc = (pred_train == y_train.numpy()).mean()
        print("Training accuracy: {}".format(train_acc))
        run.log_observation("train_acc", train_acc)
        
        pred_val = model(X_val).numpy().argmax(axis=1)
        val_acc = (pred_val == y_val.numpy()).mean()
        print("Validation accuracy: {}".format(val_acc))
        run.log_observation("val_acc", val_acc)

Log Notebook Contents


In [14]:
run.log_code()

Calculate and Log Accuracy on Full Training Set


In [15]:
with torch.no_grad():  # no need to calculate gradients when assessing accuracy
    pred_train = model(X_train).numpy().argmax(axis=1)
    train_acc = (pred_train == y_train.numpy()).mean()
    print("Training accuracy: {}".format(train_acc))
    run.log_metric("train_acc", train_acc)

Log Model for Deployment


In [16]:
# create deployment artifacts
model_api = ModelAPI(X_train.tolist(), model(X_train).tolist())
requirements = ["torch"]

# save and log model
run.log_model(model, model_api=model_api)
run.log_requirements(requirements)

Make Live Predictions

Deploy Model Through Web App


In [17]:
run

Load Deployed Model


In [18]:
from verta._demo_utils import DeployedModel

deployed_model = DeployedModel(HOST, run.id)

Query Deployed Model


In [19]:
for x in itertools.cycle(X.tolist()):
    print(deployed_model.predict([x]))
    time.sleep(.5)