import os
import time
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import datasets, transforms, models
from collections import OrderedDict
import PIL
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
dataset_location = '/home/marcin/Datasets/udacity-challange-flower-data/flower_data/'
This section does following:
You need to re-run this section only if:
imgnet_mean, imgnet_std = np.array([0.485, 0.456, 0.406]), np.array([0.229, 0.224, 0.225])
# Option 'v2'
transforms_train = transforms.Compose([
transforms.Resize(256),
transforms.Pad(100, padding_mode='reflect'),
transforms.RandomAffine(degrees=90, translate=(.2, .2), shear=30, resample=PIL.Image.BILINEAR),
transforms.CenterCrop(256),
transforms.RandomResizedCrop(224, scale=(0.1 , 2.)),
transforms.RandomHorizontalFlip(),
transforms.RandomVerticalFlip(),
transforms.ColorJitter(brightness=.2, contrast=.2, saturation=.2, hue=.1),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])
])
# # Option 'v1'
# transforms_train = transforms.Compose([
# transforms.Resize(256),
# transforms.Pad(100, padding_mode='reflect'),
# transforms.RandomRotation(45),
# transforms.CenterCrop(256),
# transforms.RandomResizedCrop(224, scale=(0.8 , 1.0)),
# transforms.RandomHorizontalFlip(),
# transforms.ToTensor(),
# transforms.Normalize([0.485, 0.456, 0.406],
# [0.229, 0.224, 0.225])
# ])
# Option 'Tomasz'
# transforms_train = transforms.Compose([
# transforms.RandomRotation(30),
# transforms.RandomResizedCrop(224, scale=(0.08 , 1.0)),
# transforms.RandomHorizontalFlip(),
# transforms.ToTensor(),
# transforms.Normalize([0.485, 0.456, 0.406],
# [0.229, 0.224, 0.225])
# ])
transforms_valid = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])
])
Test transforms
def tensor_img_2_numpy(tensor_img):
ttt = transforms.functional.normalize(tensor_img, -imgnet_mean/imgnet_std, 1/imgnet_std)
return transforms.functional.to_pil_image(ttt)
img = PIL.Image.open(os.path.join(dataset_location, 'train/1/image_06734.jpg'))
fig, axes = plt.subplots(ncols=6, figsize=[16,4])
axes[0].set_title('Original')
axes[0].imshow(img)
axes[0].axis('off')
axes[1].set_title('Valid/Test')
tensor_img = transforms_valid(img)
axes[1].imshow(tensor_img_2_numpy(tensor_img))
axes[1].axis('off')
for i in range(2, len(axes)):
axes[i].set_title(f'Train #{i-2}')
tensor_img = transforms_train(img)
axes[i].imshow(tensor_img_2_numpy(tensor_img))
axes[i].axis('off')
Create Dataloaders
dataset_train = datasets.ImageFolder(os.path.join(dataset_location, 'train'), transforms_train)
dataset_valid = datasets.ImageFolder(os.path.join(dataset_location, 'valid'), transforms_valid)
dataset_test = datasets.ImageFolder(os.path.join(dataset_location, 'test'), transforms_valid)
print('Number of train images:', len(dataset_train))
print('Number of valid images:', len(dataset_valid))
print('Number of test images:', len(dataset_test))
dataloader_train = torch.utils.data.DataLoader(dataset_train, batch_size=16, shuffle=True,
num_workers=6, pin_memory=True)
dataloader_valid = torch.utils.data.DataLoader(dataset_valid, batch_size=16, shuffle=True,
num_workers=6, pin_memory=True)
dataloader_test = torch.utils.data.DataLoader(dataset_test, batch_size=16, shuffle=True,
num_workers=6, pin_memory=True)
Sanity check
imgs, lbls = iter(dataloader_train).next()
print(imgs.shape)
print(lbls.shape)
fig, axes = plt.subplots(ncols=8, figsize=[16,4])
for i in range(len(axes)):
tensor_img = imgs[i]
axes[i].imshow(tensor_img_2_numpy(tensor_img))
axes[i].axis('off')
class Passthrough(torch.nn.Module):
def forward(self, input):
return input
#model_cnn = models.densenet121(pretrained=True)
#model_cnn = models.resnet50(pretrained=True)
#model_cnn = models.resnet152(pretrained=True)
#model_cnn = models.inception_v3(pretrained=True)
model_cnn = models.densenet201(pretrained=True)
# disable all gradients
for param in model_cnn.parameters():
param.requires_grad = False
model_cnn.classifier = Passthrough()
model_cnn.to(device);
def extract_features(model_cnn, dataloader, num_epochs=1):
"""
Params:
model_cnn - big convnet with final dense layers removed
dataloader - we get data from here
"""
features_list = []
labels_list = []
model_cnn.eval()
for epoch in range(num_epochs):
print(f'Processing epoch: {epoch:3d}', end='')
time_start = time.time()
for images, labels in dataloader:
images = images.to(device)
outputs = model_cnn(images)
features_list.append(outputs.cpu().numpy())
labels_list.append(labels.numpy())
time_interval = time.time() - time_start
print(f' fime: {time_interval:5.2f}')
features_arr = np.concatenate(features_list)
labels_arr = np.concatenate(labels_list)
return features_arr, labels_arr
train_features, train_labels = extract_features(model_cnn, dataloader_train, num_epochs=30)
print('Shape of train features (inputs):', train_features.shape)
print('Shape of train labels (targets): ', train_labels.shape)
print('Type of train feat. and labels: ', train_features.dtype, train_labels.dtype)
print('Sample of labels:', train_labels[:20])
valid_features, valid_labels = extract_features(model_cnn, dataloader_valid)
print('Shape of validation features (inputs):', valid_features.shape)
print('Shape of validation labels (targets): ', valid_labels.shape)
print('Type of valid. features and labels: ', valid_features.dtype, valid_labels.dtype)
print('Sample of labels:', valid_labels[:20])
test_features, test_labels = extract_features(model_cnn, dataloader_test)
print('Shape of test features (inputs):', test_features.shape)
print('Shape of test labels (targets): ', test_labels.shape)
print('Type of test. features and labels: ', test_features.dtype, test_labels.dtype)
print('Sample of labels:', test_labels[:20])
Save Checkpoint
dataset_npz = os.path.join(dataset_location, 'dataset_densenet201_aug_v2_30x.npz')
dataset_npz
#uncomment if you realy want to save, will override existing
# np.savez(dataset_npz,
# train_features=train_features,
# train_labels=train_labels,
# valid_features=valid_features,
# valid_labels=valid_labels,
# test_features=test_features,
# test_labels=test_labels)
Load Dataset
dataset_npz = os.path.join(dataset_location, 'dataset_densenet201_augtom.npz')
dataset_npz
npzfile = np.load(dataset_npz)
train_features = npzfile['train_features']
train_labels = npzfile['train_labels']
valid_features = npzfile['valid_features']
valid_labels = npzfile['valid_labels']
test_features = npzfile['test_features']
test_labels = npzfile['test_labels']
print('Shape of train features (inputs):', train_features.shape)
print('Shape of train labels (targets): ', train_labels.shape)
print('Type of train feat. and labels: ', train_features.dtype, train_labels.dtype)
print('Sample of labels:', train_labels[:20])
print('Shape of validation features (inputs):', valid_features.shape)
print('Shape of validation labels (targets): ', valid_labels.shape)
print('Type of valid. features and labels: ', valid_features.dtype, valid_labels.dtype)
print('Sample of labels:', valid_labels[:20])
print('Shape of test features (inputs):', test_features.shape)
print('Shape of test labels (targets): ', test_labels.shape)
print('Type of test. features and labels: ', test_features.dtype, test_labels.dtype)
print('Sample of labels:', test_labels[:20])
Load dataset to GPU
x_train = torch.tensor(train_features).to(device)
y_train = torch.tensor(train_labels).to(device)
x_valid = torch.tensor(valid_features).to(device)
y_valid = torch.tensor(valid_labels).to(device)
x_test = torch.tensor(test_features).to(device)
y_test = torch.tensor(test_labels).to(device)
Define Model
model = nn.Sequential(OrderedDict([
('bn1', nn.BatchNorm1d(1920)),
('fc1', nn.Linear(1920, 512)),
('elu1', nn.ELU()),
('drp1', nn.Dropout(0.75)),
# ('fc2', nn.Linear(512, 512)),
# ('elu2', nn.ELU()),
# ('drp2', nn.Dropout(0.75)),
('fcf', nn.Linear(512, 102)),
]))
model.to(device)
criterion = nn.CrossEntropyLoss() # nn.NLLLoss()
#optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)
optimizer = torch.optim.RMSprop(model.parameters(), lr=0.00003)
#scheduler = torch.optim.lr_scheduler()
hist = { 'tloss':[], 'tacc':[], # mini-batch loss/acc every iteration
'train_loss':[], 'train_acc':[], # train set loss/acc every epoch
'valid_loss':[], 'valid_acc':[] } # valid set loss/acc every epoch
Helper
def accuracy(logits, labels):
predictions = torch.argmax(logits, dim=1)
return (predictions == labels).float().mean() # tensor!!
Train model
num_epochs = 100
batch_size = 250
train_size = len(x_train)
train_start_time = time.time()
for epoch in range(num_epochs):
epoch_time_start = time.time()
### Train ###
model.train()
indices = torch.randperm(len(x_train), device=device) # indices = [2423, 1563, 4854, ...]
for i in range(0, len(x_train), batch_size): # i = 0, batch_size, 2*batch_size, ...
# Pick mini-batch
x = x_train[indices[i:i+batch_size]]
y = y_train[indices[i:i+batch_size]]
# Optimize
optimizer.zero_grad()
outputs = model(x) # logits
loss = criterion(outputs, y)
loss.backward()
optimizer.step()
# Record per-iteration stats
with torch.no_grad():
acc = accuracy(outputs, y)
hist['tacc'].append( acc.item() )
hist['tloss'].append( loss.item() )
### Evaluate ###
model.eval()
with torch.no_grad():
# Eval on train set
outputs = model(x_train) # pass-in whole train dataset at once
loss = criterion(outputs, y_train)
acc = accuracy(outputs, y_train)
hist['train_acc'].append( acc.item() )
hist['train_loss'].append( loss.item() )
# Eval on valid set
outputs = model(x_valid)
loss = criterion(outputs, y_valid)
acc = accuracy(outputs, y_valid)
hist['valid_acc'].append( acc.item() )
hist['valid_loss'].append( loss.item() )
epoch_time_interval = time.time() - epoch_time_start
### Print Summary ###
if epoch == 0:
print(' (time ) ep loss / acc loss / acc')
print(f'Epoch ({epoch_time_interval:4.2}s): {epoch:3}'
f' Train: {hist["train_loss"][-1]:6.4f} / {hist["train_acc"][-1]:6.4f}'
f' Valid: {hist["valid_loss"][-1]:6.4f} / {hist["valid_acc"][-1]:6.4f}')
print()
total_train_time = time.time() - train_start_time
print(f'Total train time {total_train_time:6.2f}s')
Evaluate on all datasets (sic)
for g in optimizer.param_groups:
print(g['lr'])
for g in optimizer.param_groups:
g['lr'] = 0.00001
0.00003
### Evaluate ###
model.eval()
with torch.no_grad():
# Eval on train set
outputs = model(x_train) # pass-in whole train dataset at once
train_loss = criterion(outputs, y_train).item()
train_acc = accuracy(outputs, y_train).item()
# Eval on valid set
outputs = model(x_valid)
valid_loss = criterion(outputs, y_valid).item()
valid_acc = accuracy(outputs, y_valid).item()
# Eval on valid set
outputs = model(x_test)
test_loss = criterion(outputs, y_test).item()
test_acc = accuracy(outputs, y_test).item()
all_loss = train_loss*len(x_train) + valid_loss*len(x_valid) + test_loss*len(x_test)
all_loss /= len(x_train)+len(x_valid)+len(x_test)
all_acc = train_acc*len(x_train) + valid_acc*len(x_valid) + test_acc*len(x_test)
all_acc /= len(x_train)+len(x_valid)+len(x_test)
print( ' loss / acc')
print(f'Train: {train_loss:6.4f} / {train_acc:4.4f}')
print(f'Valid: {valid_loss:6.4f} / {valid_acc:4.4f}')
print(f'Test: {test_loss:6.4f} / {test_acc:4.4f}')
print(f'All: {all_loss:6.4f} / {all_acc:4.4f}')
def pretty_plot(ax, data, label, color, alpha):
def smooth(y, n):
return np.convolve(y, v=np.ones(n)/n, mode='same')
#ax.scatter(range(len(data)), data, marker='.', s=1, color=color, alpha=alpha/5)
ax.plot(smooth(data, 55), label=label, color=color, alpha=alpha)
def plot_hist(hist, title):
fig, (ax, ax2) = plt.subplots(nrows=1, ncols=2, figsize=[16,3])
fig.suptitle(title, fontsize=16)
#ax.plot(hist['train_loss'], label='train_loss', color='blue')
pretty_plot(ax.twiny(), hist['tloss'], 'tloss', color='blue', alpha=.5)
ax.plot(hist['valid_loss'], label='valid_loss', color='orange')
ax.set_title('Loss'); ax.legend(); ax.grid(); ax.set_ylim([0, 1]);
#fig, ax = plt.subplots(nrows=1, ncols=1, figsize=[16,3])
ax2.plot(hist['train_acc'], label='train_acc', color='blue')
#pretty_plot(ax2.twiny(), hist['tacc'], 'tacc', color='blue', alpha=1)
ax2.plot(hist['valid_acc'], label='valid_acc', color='orange')
ax2.set_title('Accuracy'); ax2.legend(); ax2.grid(); ax2.set_ylim([.8, 1]);
plt.tight_layout()
plot_hist(hist, title='Test')
plot_hist(hist, title='Test')
plot_hist(hist, title='Test')
plot_hist(hist, title='Test')
model_pth = os.path.join(dataset_location, 'model.pth')
model_pth
model_cnn.classifier = model
torch.save({'arch': 'densenet201',
'state_dict': model_cnn.state_dict(),
'class_to_idx': dataset_train.class_to_idx},
model_pth)
model_cnn.classifier = Passthrough()
Param in_features was set to match CNN output size.
print(model)
plot_hist(hist, title='Inception-v3 no-aug')
plot_hist(hist, title='ResNet151 no-aug')
plot_hist(hist, title='ResNet50 no-aug')
plot_hist(hist, title='DenseNet121 no-aug')
plot_hist(hist, title='DenseNet-202 no-aug')