# Data Scientist Day 5 github: https://github.com/Study-boy-dot/Image-Processing-3/tree/main kaggle: https://www.kaggle.com/dataminingee/cifar10-with-mycnn ## Environment |Environment|Version| |-----------|----------| |python |3.8| |jupyter notebook|6.5.2| |cuda|12.0| ## Packages |Package|Version| |-----------|----------| |torch | 2.1.0+cu118| |torchaudio | 2.1.0+cu118| |torchsummary | 1.5.1| |torchvision | 0.16.0+cu118| |numpy|1.23.4| |matplotlib|3.3.1| ## Code Review ### Load Cifar10 Dataset Using `torchvision.dataset.CIFAR10` to load cifar10 dataset * specify loaded dataset is for training or testing * specify images transform method(will explain it later) ``` traindata = torchvision.datasets.CIFAR10(root='./data', train=True, transform=transform) trainloader = torch.utils.data.DataLoader(traindata, batch_size, shuffle=True) ``` ### Images Transform Doing iamges augmentation, apply it to load dataset process ``` transform = transforms.Compose([transforms.Resize(32, 32), transforms.ToTensor(), ...]) ``` ### Model Design self.feature1: CONV => RELU => CONV => RELU => MAXPOOLING => DROPOUT self.feature2: CONV => RELU => CONV => RELU => MAXPOOLING => DROPOUT self.classifier: FLATTEN => LINEAR => RELU => DROPOUT => LINEAR(num classes) **Do not add softmax layer to classifier because loss function has internally apply softmax activation to model's output and use it to calculate cross-entropy loss** ``` self.feature1 = nn.Sequential(nn.Conv2D(input_channel, output_channel, kernel_size, padding), nn.ReLU(), ... ) self.feature2 = nn.Sequential(nn.Conv2D(input_channel, output_channel, kernel_size, padding), ... ) self.classifier = nn.Sequential(nn.Linear(input_channel * width * height, output_size), ... ) ``` **In the class forward(self, x) function will specify how each block transfer feature** ``` def forward(self, x): x = self.feature1(x) x = self.feature2(x) x = self.classifier(x) return x ``` <font size=20 color=#c4706a weight=bold>Complete Code:</font> ``` import torch import torch.nn as nn class ConvNet(nn.Module): def __init__(self, num_classes): super(ConvNet, self).__init__() # Feature extraction layers self.features1 = nn.Sequential( nn.Conv2d(3, 32, kernel_size=3, padding=1), # 1st Convolutional layer nn.ReLU(), nn.Conv2d(32, 32, kernel_size=3, padding=1), # 2nd Convolutional layer nn.ReLU(), nn.MaxPool2d(2), # Max Pooling nn.Dropout(0.25) ) self.features2 = nn.Sequential( nn.Conv2d(32, 64, kernel_size=3, padding=1), # 3rd Convolutional layer nn.ReLU(), nn.Conv2d(64, 64, kernel_size=3, padding=1), # 4th Convolutional layer nn.ReLU(), nn.MaxPool2d(2), # Max Pooling nn.Dropout(0.25) ) # Classification layers self.classifier = nn.Sequential( nn.Flatten(), # Flatten the tensor nn.Linear(64 * 8 * 8, 512), # Fully connected layer nn.ReLU(), nn.Dropout(0.5), nn.Linear(512, num_classes) # Output layer ) def forward(self, x): x = self.features1(x) x = self.features2(x) x = self.classifier(x) return x ``` ### Model Training Using Pytorch training model, training process need to specify by user * Create loss function * Create optimizer ``` criterion = nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr, momentum) ``` Start training model, example has not show moving dataset and model to GPU `model = model.to(device)` ``` for e in range(epochs): model.train() # turn model to train mode for inputs, labels in trainloader: optimizer.zero_grad() outputs = model(inputs) # Calculate loss loss = criterion(outputs, labels) # Backpropagation & Optimizer step loss.backward() optimizer.step() ``` <font size=20 color=#c4706a weight=bold>Complete Code:</font> ``` import copy def train(model, criterion, optimizer, num_epochs, dataloader_train, dataloader_val=None): history = { 'loss':[], 'acc': [], 'val_loss' : [], 'val_acc': [], 'best_train_acc' : (0, 0) } best_acc = 0.0 best_model_weight = copy.deepcopy(model.state_dict()) train_dataset_size = len(dataloader_train.dataset) test_dataset_size = len(dataloader_val.dataset) for e in range(num_epochs): print(f'\033[1;35mEpoch:{e}\033[0m') model.train() # change model to training mode each_epoch_loss = 0.0 each_epoch_acc = 0.0 each_epoch_val_loss = 0.0 each_epoch_val_acc = 0.0 for inputs, labels in dataloader_train: inputs = inputs.to(device) labels = labels.to(device) outputs = model(inputs) # Calculate loss loss = criterion(outputs, labels) # Backpropagation and Optimization optimizer.zero_grad() loss.backward() optimizer.step() #values, indexes _, pred = torch.max(outputs, 1) # choose the first biggest value # Store result each_epoch_loss += loss.item() each_epoch_acc += (pred == labels).sum().item() # Update history result each_epoch_loss /= train_dataset_size each_epoch_acc /= train_dataset_size each_epoch_acc *= 100 history['loss'].append(each_epoch_loss) history['acc'].append(each_epoch_acc) print(f'\033[1;34mTrainning: Epoch:{e} Loss:{each_epoch_loss:.2f} Accuracy:{each_epoch_acc:.2f}\033[0m') # Store best result if each_epoch_acc > best_acc: best_acc = each_epoch_acc best_model_weight = copy.deepcopy(model.state_dict()) history['best_train_acc'] = (e, best_acc) if dataloader_val: model.eval() # change model to evaluate mode with torch.no_grad(): for inputs, labels in dataloader_val: inputs = inputs.to(device) labels = labels.to(device) # Evaluate outputs = model(inputs) val_loss = criterion(outputs, labels) # values, indexes _, pred = torch.max(outputs, 1) # Store result each_epoch_val_loss += val_loss.item() each_epoch_val_acc += (pred == labels).sum().item() each_epoch_val_loss /= test_dataset_size each_epoch_val_acc /= test_dataset_size each_epoch_val_acc *= 100 history['val_loss'].append(each_epoch_val_loss) history['val_acc'].append(each_epoch_val_acc) print(f'\033[1;34mTesting: Epoch:{e} Loss:{each_epoch_val_loss:.2f} Accuracy:{each_epoch_val_acc:.2f}\033[0m') print(f"Best Result in \033[1;35mEpoch:{history['best_train_acc'][0]}\033[0m \033[1;33mAccuracy:{history['best_train_acc'][1]:.2f}\033[0m") model.load_state_dict(best_model_weight) return model, history ``` ### Save Model ``` model_path='model.pth' torch.save(model, model_path) ``` ### Append Layer to model ``` model.classfier.append(nn.Linear(4096, 512)) ... ``` ### Freeze Layer In transfer learning process, model is pretrained, and only need to train the classfier layer to match with dataset provided. Freeze the features' layer can lower down training model time, increase effieciency ``` for param in model.parameters(): param.requires_grad = False ``` ### Early Stop in Training Process Specify patience level, when there is continuous validation loss not fewer than the best validation loss over patience level times. Stop the training process ``` def train(model, criterion, optimzer, num_epochs, trainloader, testloader=None): # Early Stop best_val_loss = float('inf') patience = 3 no_improvement_count = 0 for e in range(num_epochs): '''Training Process code here''' # Evaluate model if testloader: '''Evaluate Model code here''' if val_loss < best_val_loss: best_val_loss = val_loss no_improvement_count = 0 else: no_improvement_count += 1 # Early Stop Condition if no_improvement_count >= patience: print(f"Early stopping after {e} epochs with no improvement.") break return model, history ```