Make a submisson here: https://jovian.ai/learn/deep-learning-with-pytorch-zero-to-gans/assignment/assignment-2-train-your-first-model
In this assignment we're going to use information like a person's age, sex, BMI, no. of children and smoking habit to predict the price of yearly medical bills. This kind of model is useful for insurance companies to determine the yearly insurance premium for a person. The dataset for this problem is taken from Kaggle.
We will create a model with the following steps:
This assignment builds upon the concepts from the first 2 lessons. It will help to review these Jupyter notebooks:
As you go through this notebook, you will find a ??? in certain places. Your job is to replace the ??? with appropriate code or values, to ensure that the notebook runs properly end-to-end . In some cases, you'll be required to choose some hyperparameters (learning rate, batch size etc.). Try to experiment with the hypeparameters to get the lowest loss.
# Uncomment and run the appropriate command for your operating system, if required
# Linux / Binder
# !pip install numpy matplotlib pandas torch==1.7.0+cpu torchvision==0.8.1+cpu torchaudio==0.7.0 -f https://download.pytorch.org/whl/torch_stable.html
# Windows
# !pip install numpy matplotlib pandas torch==1.7.0+cpu torchvision==0.8.1+cpu torchaudio==0.7.0 -f https://download.pytorch.org/whl/torch_stable.html
# MacOS
# !pip install numpy matplotlib pandas torch torchvision torchaudio
import torch
import jovian
import torchvision
import torch.nn as nn
import pandas as pd
import matplotlib.pyplot as plt
import torch.nn.functional as F
from torchvision.datasets.utils import download_url
from torch.utils.data import DataLoader, TensorDataset, random_split
project_name='02-insurance-linear-regression' # will be used by jovian.commit
Let us begin by downloading the data. We'll use the download_url
function from PyTorch to get the data as a CSV (comma-separated values) file.
DATASET_URL = "https://hub.jovian.ml/wp-content/uploads/2020/05/insurance.csv"
DATA_FILENAME = "insurance.csv"
download_url(DATASET_URL, '.')
Downloading https://hub.jovian.ml/wp-content/uploads/2020/05/insurance.csv to ./insurance.csv
HBox(children=(FloatProgress(value=1.0, bar_style='info', max=1.0), HTML(value='')))
To load the dataset into memory, we'll use the read_csv
function from the pandas
library. The data will be loaded as a Pandas dataframe. See this short tutorial to learn more: https://data36.com/pandas-tutorial-1-basics-reading-data-files-dataframes-data-selection/
dataframe_raw = pd.read_csv(DATA_FILENAME)
dataframe_raw.head()
We're going to do a slight customization of the data, so that you every participant receives a slightly different version of the dataset. Fill in your name below as a string (enter at least 5 characters)
your_name = ??? # at least 5 characters
The customize_dataset
function will customize the dataset slightly using your name as a source of random numbers.
def customize_dataset(dataframe_raw, rand_str):
dataframe = dataframe_raw.copy(deep=True)
# drop some rows
dataframe = dataframe.sample(int(0.95*len(dataframe)), random_state=int(ord(rand_str[0])))
# scale input
dataframe.bmi = dataframe.bmi * ord(rand_str[1])/100.
# scale target
dataframe.charges = dataframe.charges * ord(rand_str[2])/100.
# drop column
if ord(rand_str[3]) % 2 == 1:
dataframe = dataframe.drop(['region'], axis=1)
return dataframe
dataframe = customize_dataset(dataframe_raw, your_name)
dataframe.head()
Let us answer some basic questions about the dataset.
Q: How many rows does the dataset have?
num_rows = ???
print(num_rows)
Q: How many columns doe the dataset have
num_cols = ???
print(num_cols)
Q: What are the column titles of the input variables?
input_cols = [???]
Q: Which of the input columns are non-numeric or categorial variables ?
Hint: sex
is one of them. List the columns that are not numbers.
categorical_cols = [???]
Q: What are the column titles of output/target variable(s)?
output_cols = [???]
Q: (Optional) What is the minimum, maximum and average value of the charges
column? Can you show the distribution of values in a graph?
Use this data visualization cheatsheet for referece: https://jovian.ml/aakashns/dataviz-cheatsheet
# Write your answer here
Remember to commit your notebook to Jovian after every step, so that you don't lose your work.
!pip install jovian --upgrade -q
import jovian
jovian.commit()
[jovian] Attempting to save notebook..
[jovian] Updating notebook "aakashns/02-insurance-linear-regression" on https://jovian.ai/
[jovian] Uploading notebook..
[jovian] Capturing environment..
[jovian] Committed successfully! https://jovian.ai/aakashns/02-insurance-linear-regression
We need to convert the data from the Pandas dataframe into a PyTorch tensors for training. To do this, the first step is to convert it numpy arrays. If you've filled out input_cols
, categorial_cols
and output_cols
correctly, this following function will perform the conversion to numpy arrays.
def dataframe_to_arrays(dataframe):
# Make a copy of the original dataframe
dataframe1 = dataframe.copy(deep=True)
# Convert non-numeric categorical columns to numbers
for col in categorical_cols:
dataframe1[col] = dataframe1[col].astype('category').cat.codes
# Extract input & outupts as numpy arrays
inputs_array = dataframe1[input_cols].to_numpy()
targets_array = dataframe1[output_cols].to_numpy()
return inputs_array, targets_array
Read through the Pandas documentation to understand how we're converting categorical variables into numbers.
inputs_array, targets_array = dataframe_to_arrays(dataframe)
inputs_array, targets_array
Q: Convert the numpy arrays inputs_array
and targets_array
into PyTorch tensors. Make sure that the data type is torch.float32
.
inputs = ???
targets = ???
inputs.dtype, targets.dtype
Next, we need to create PyTorch datasets & data loaders for training & validation. We'll start by creating a TensorDataset
.
dataset = TensorDataset(inputs, targets)
Q: Pick a number between 0.1
and 0.2
to determine the fraction of data that will be used for creating the validation set. Then use random_split
to create training & validation datasets.
val_percent = ??? # between 0.1 and 0.2
val_size = int(num_rows * val_percent)
train_size = num_rows - val_size
train_ds, val_ds = ??? # Use the random_split function to split dataset into 2 parts of the desired length
Finally, we can create data loaders for training & validation.
Q: Pick a batch size for the data loader.
batch_size = ???
train_loader = DataLoader(train_ds, batch_size, shuffle=True)
val_loader = DataLoader(val_ds, batch_size)
Let's look at a batch of data to verify everything is working fine so far.
for xb, yb in train_loader:
print("inputs:", xb)
print("targets:", yb)
break
Let's save our work by committing to Jovian.
jovian.commit(project=project_name, environment=None)
Our model itself is a fairly straightforward linear regression (we'll build more complex models in the next assignment).
input_size = len(input_cols)
output_size = len(output_cols)
Q: Complete the class definition below by filling out the constructor (__init__
), forward
, training_step
and validation_step
methods.
Hint: Think carefully about picking a good loss fuction (it's not cross entropy). Maybe try 2-3 of them and see which one works best. See https://pytorch.org/docs/stable/nn.functional.html#loss-functions
class InsuranceModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = ??? # fill this (hint: use input_size & output_size defined above)
def forward(self, xb):
out = ??? # fill this
return out
def training_step(self, batch):
inputs, targets = batch
# Generate predictions
out = self(inputs)
# Calcuate loss
loss = ??? # fill this
return loss
def validation_step(self, batch):
inputs, targets = batch
# Generate predictions
out = self(inputs)
# Calculate loss
loss = ??? # fill this
return {'val_loss': loss.detach()}
def validation_epoch_end(self, outputs):
batch_losses = [x['val_loss'] for x in outputs]
epoch_loss = torch.stack(batch_losses).mean() # Combine losses
return {'val_loss': epoch_loss.item()}
def epoch_end(self, epoch, result, num_epochs):
# Print result every 20th epoch
if (epoch+1) % 20 == 0 or epoch == num_epochs-1:
print("Epoch [{}], val_loss: {:.4f}".format(epoch+1, result['val_loss']))
Let us create a model using the InsuranceModel
class. You may need to come back later and re-run the next cell to reinitialize the model, in case the loss becomes nan
or infinity
.
model = InsuranceModel()
Let's check out the weights and biases of the model using model.parameters
.
list(model.parameters())
One final commit before we train the model.
jovian.commit(project=project_name, environment=None)
To train our model, we'll use the same fit
function explained in the lecture. That's the benefit of defining a generic training loop - you can use it for any problem.
def evaluate(model, val_loader):
outputs = [model.validation_step(batch) for batch in val_loader]
return model.validation_epoch_end(outputs)
def fit(epochs, lr, model, train_loader, val_loader, opt_func=torch.optim.SGD):
history = []
optimizer = opt_func(model.parameters(), lr)
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, epochs)
history.append(result)
return history
Q: Use the evaluate
function to calculate the loss on the validation set before training.
result = ??? # Use the the evaluate function
print(result)
We are now ready to train the model. You may need to run the training loop many times, for different number of epochs and with different learning rates, to get a good result. Also, if your loss becomes too large (or nan
), you may have to re-initialize the model by running the cell model = InsuranceModel()
. Experiment with this for a while, and try to get to as low a loss as possible.
Q: Train the model 4-5 times with different learning rates & for different number of epochs.
Hint: Vary learning rates by orders of 10 (e.g. 1e-2
, 1e-3
, 1e-4
, 1e-5
, 1e-6
) to figure out what works.
epochs = ???
lr = ???
history1 = fit(epochs, lr, model, train_loader, val_loader)
epochs = ???
lr = ???
history2 = fit(epochs, lr, model, train_loader, val_loader)
epochs = ???
lr = ???
history3 = fit(epochs, lr, model, train_loader, val_loader)
epochs = ???
lr = ???
history4 = fit(epochs, lr, model, train_loader, val_loader)
epochs = ???
lr = ???
history5 = fit(epochs, lr, model, train_loader, val_loader)
Q: What is the final validation loss of your model?
val_loss = ???
Let's log the final validation loss to Jovian and commit the notebook
jovian.log_metrics(val_loss=val_loss)
jovian.commit(project=project_name, environment=None)
Now scroll back up, re-initialize the model, and try different set of values for batch size, number of epochs, learning rate etc. Commit each experiment and use the "Compare" and "View Diff" options on Jovian to compare the different results.
Q: Complete the following function definition to make predictions on a single input
def predict_single(input, target, model):
inputs = input.unsqueeze(0)
predictions = ??? # fill this
prediction = predictions[0].detach()
print("Input:", input)
print("Target:", target)
print("Prediction:", prediction)
input, target = val_ds[0]
predict_single(input, target, model)
input, target = val_ds[10]
predict_single(input, target, model)
input, target = val_ds[23]
predict_single(input, target, model)
Are you happy with your model's predictions? Try to improve them further.
While this last step is optional for the submission of your assignment, we highly recommend that you do it. Try to replicate this notebook for a different linear regression or logistic regression problem. This will help solidify your understanding, and give you a chance to differentiate the generic patterns in machine learning from problem-specific details.You can use one of these starer notebooks (just change the dataset):
Here are some sources to find good datasets:
We also recommend that you write a blog about your approach to the problem. Here is a suggested structure for your post (feel free to experiment with it):
As with the previous assignment, you can embed Juptyer notebook cells & outputs from Jovian into your blog.
Don't forget to share your work on the forum: https://jovian.ai/forum/t/linear-regression-and-logistic-regression-notebooks-and-blog-posts/14039
jovian.commit(project=project_name, environment=None)
jovian.commit(project=project_name, environment=None) # try again, kaggle fails sometimes
[jovian] Attempting to save notebook..
[jovian] Updating notebook "aakashns/02-insurance-linear-regression" on https://jovian.ai/
[jovian] Uploading notebook..
[jovian] Committed successfully! https://jovian.ai/aakashns/02-insurance-linear-regression
[jovian] Attempting to save notebook..