Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Transfer Learning with ResNet50 on Dog and Wolf Classification

Tech Jul 14 1

Today’s focus is on implementing transfer learning using ResNet50 to classify images of dogs and wolves. We’ll walk through data preparation, dataset loading with augmentation, model fine-tuning, training, and prediction visualization—all within MindSpore.

Data Preparation

We begin by downloading a curated dataset of dog and wolf images sourced from ImageNet. Each class contains approximately 120 training and 30 validation images. The dataset is downloaded and automatically extracted using the following code: ``` from download import download

dataset_url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/intermediate/Canidae_data.zip" download(dataset_url, "./datasets-Canidae", kind="zip", replace=True)


The extracted structure contains two subdirectories: `train/` and `val/`, each organized by class label. ### Loading and Augmenting the Dataset

We use `mindspore.dataset.ImageFolderDataset` to load the images and apply transformations based on training or validation mode. Key hyperparameters are defined as follows: ```
batch_size = 18
image_size = 224
num_epochs = 5
lr = 0.001
momentum = 0.9
workers = 4

The data loader function applies different augmentations for training and validation: - Training: Random crop, horizontal flip, normalization, and channel rearrangement.

  • Validation: Resize, center crop, normalization, and channel rearrangement (no randomness).
import mindspore as ms
import mindspore.dataset as ds
import mindspore.dataset.vision as vision

data_path_train = "./datasets-Canidae/data/Canidae/train/"
data_path_val = "./datasets-Canidae/data/Canidae/val/"

mean = [0.485 * 255, 0.456 * 255, 0.406 * 255]
std = [0.229 * 255, 0.224 * 255, 0.225 * 255]
scale = 32

def create_dataset_canidae(dataset_path, usage):
    data_set = ds.ImageFolderDataset(dataset_path, num_parallel_workers=workers, shuffle=True)

    if usage == "train":
        trans = [
            vision.RandomCropDecodeResize(size=image_size, scale=(0.08, 1.0), ratio=(0.75, 1.333)),
            vision.RandomHorizontalFlip(prob=0.5),
            vision.Normalize(mean=mean, std=std),
            vision.HWC2CHW()
        ]
    else:
        trans = [
            vision.Decode(),
            vision.Resize(image_size + scale),
            vision.CenterCrop(image_size),
            vision.Normalize(mean=mean, std=std),
            vision.HWC2CHW()
        ]

    data_set = data_set.map(operations=trans, input_columns='image', num_parallel_workers=workers)
    data_set = data_set.batch(batch_size)
    return data_set

dataset_train = create_dataset_canidae(data_path_train, "train")
dataset_val = create_dataset_canidae(data_path_val, "val")

We visualize a batch of training samples to inspect the augmented images and their labels: ``` import matplotlib.pyplot as plt import numpy as np

class_name = {0: "dogs", 1: "wolves"}

data = next(dataset_train.create_dict_iterator()) images = data["image"].asnumpy() labels = data["label"].asnumpy()

plt.figure(figsize=(5, 5)) for i in range(4): img = images[i] img = np.transpose(img, (1, 2, 0)) img = img * std[:3] + mean[:3] # Denormalize img = np.clip(img, 0, 1)

plt.subplot(2, 2, i+1)
plt.imshow(img)
plt.title(class_name[int(labels[i])], fontsize=10)
plt.axis("off")

plt.show()


### Building and Loading ResNet50 with Pretrained Weights

We implement a modular ResNet50 architecture with residual blocks. The model consists of convolutional layers, batch normalization, ReLU activations, and skip connections. The final fully connected layer is replaced to match our two-class classification task. ```
from mindspore import nn, load_checkpoint, load_param_into_net
from typing import Type, List, Optional, Union

weight_init = nn.Normal(sigma=0.02)
gamma_init = nn.Normal(sigma=0.02)

class ResidualBlock(nn.Cell):
    expansion = 4

    def __init__(self, in_channel: int, out_channel: int, stride: int = 1, down_sample: Optional[nn.Cell] = None):
        super(ResidualBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_channel, out_channel, kernel_size=1, weight_init=weight_init)
        self.bn1 = nn.BatchNorm2d(out_channel, gamma_init=gamma_init)
        self.conv2 = nn.Conv2d(out_channel, out_channel, kernel_size=3, stride=stride, weight_init=weight_init)
        self.bn2 = nn.BatchNorm2d(out_channel, gamma_init=gamma_init)
        self.conv3 = nn.Conv2d(out_channel, out_channel * self.expansion, kernel_size=1, weight_init=weight_init)
        self.bn3 = nn.BatchNorm2d(out_channel * self.expansion, gamma_init=gamma_init)
        self.relu = nn.ReLU()
        self.down_sample = down_sample

    def construct(self, x):
        identity = x
        out = self.relu(self.bn1(self.conv1(x)))
        out = self.relu(self.bn2(self.conv2(out)))
        out = self.bn3(self.conv3(out))

        if self.down_sample is not None:
            identity = self.down_sample(x)
        out += identity
        return self.relu(out)

def make_layer(in_channels, block: Type[ResidualBlock], planes: int, blocks: int, stride: int = 1):
    down_sample = None
    if stride != 1 or in_channels != planes * block.expansion:
        down_sample = nn.SequentialCell([
            nn.Conv2d(in_channels, planes * block.expansion, kernel_size=1, stride=stride, weight_init=weight_init),
            nn.BatchNorm2d(planes * block.expansion, gamma_init=gamma_init)
        ])

    layers = [block(in_channels, planes, stride, down_sample)]
    for _ in range(1, blocks):
        layers.append(block(planes * block.expansion, planes))

    return nn.SequentialCell(layers)

class ResNet(nn.Cell):
    def __init__(self, block: Type[ResidualBlock], layers: List[int], num_classes: int, input_channel: int):
        super(ResNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, weight_init=weight_init)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU()
        self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode="same")

        self.layer1 = make_layer(64, block, 64, layers[0])
        self.layer2 = make_layer(64 * block.expansion, block, 128, layers[1], stride=2)
        self.layer3 = make_layer(128 * block.expansion, block, 256, layers[2], stride=2)
        self.layer4 = make_layer(256 * block.expansion, block, 512, layers[3], stride=2)

        self.avg_pool = nn.AvgPool2d()
        self.flatten = nn.Flatten()
        self.fc = nn.Dense(input_channel, num_classes)

    def construct(self, x):
        x = self.relu(self.bn1(self.conv1(x)))
        x = self.max_pool(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x = self.avg_pool(x)
        x = self.flatten(x)
        x = self.fc(x)
        return x

def resnet50(num_classes: int = 2, pretrained: bool = False):
    url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt"
    ckpt_path = "./LoadPretrainedModel/resnet50_224_new.ckpt"

    if pretrained:
        download(url=url, path=ckpt_path, replace=True)
        model = ResNet(ResidualBlock, [3, 4, 6, 3], num_classes, 2048)
        param_dict = load_checkpoint(ckpt_path)
        load_param_into_net(model, param_dict)
        return model
    else:
        return ResNet(ResidualBlock, [3, 4, 6, 3], num_classes, 2048)

Freezing Features and Fine-Tuning

To leverage transfer learning, we freeze all layers except the final fully connected layer. This prevents gradient updates in the pretrained feature extractor and focuses training on adapting the classifier head: ``` import mindspore as ms from mindspore import train

net = resnet50(pretrained=True)

Replace the final layer for 2-class classification

in_features = net.fc.in_channels net.fc = nn.Dense(in_features, 2) net.avg_pool = nn.AvgPool2d(kernel_size=7) # Adjust for 224x224 input

Freeze all parameters except the final layer

for param in net.get_parameters(): if param.name not in ["fc.weight", "fc.bias"]: param.requires_grad = False

Define optimizer and loss

optimizer = nn.Momentum(params=net.trainable_params(), learning_rate=lr, momentum=momentum) loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')

def forward_fn(inputs, labels): logits = net(inputs) loss = loss_fn(logits, labels) return loss

grad_fn = ms.value_and_grad(forward_fn, None, optimizer.parameters)

def train_step(inputs, labels): loss, grads = grad_fn(inputs, labels) optimizer(grads) return loss

model = train.Model(net, loss_fn, optimizer, metrics={"Accuracy": train.Accuracy()})


### Training and Validation Loop

We train for 5 epochs, evaluating accuracy on the validation set after each epoch. The best-performing checkpoint is saved: ```
from mindspore import save_checkpoint
import os
import time

data_loader_train = dataset_train.create_tuple_iterator(num_epochs=num_epochs)
data_loader_val = dataset_val.create_tuple_iterator(num_epochs=num_epochs)

best_ckpt_dir = "./BestCheckpoint"
best_ckpt_path = os.path.join(best_ckpt_dir, "resnet50-best-freezing-param.ckpt")
best_acc = 0.0

if not os.path.exists(best_ckpt_dir):
    os.makedirs(best_ckpt_dir)

print("Starting training...")

for epoch in range(num_epochs):
    net.set_train()
    epoch_loss = []

    start_time = time.time()
    for images, labels in data_loader_train:
        labels = labels.astype(ms.int32)
        loss = train_step(images, labels)
        epoch_loss.append(loss.asnumpy())

    # Validate
    val_acc = model.eval(dataset_val)["Accuracy"]
    end_time = time.time()

    avg_loss = sum(epoch_loss) / len(epoch_loss)
    epoch_duration = (end_time - start_time) * 1000
    step_duration = epoch_duration / dataset_train.get_dataset_size()

    print(f"Epoch [{epoch+1}/{num_epochs}] "
          f"Loss: {avg_loss:.3f} | "
          f"Val Acc: {val_acc:.3f} | "
          f"Time: {epoch_duration:.1f}ms/epoch ({step_duration:.1f}ms/step)")

    if val_acc > best_acc:
        best_acc = val_acc
        save_checkpoint(net, best_ckpt_path)

print(f"\nTraining complete. Best validation accuracy: {best_acc:.3f}")

Visualizing Predictions on Validation Set

After training, we load the best checkpoint and visualize predictions on unseen validation images. Correct prediction are shown in blue; incorrect ones in red: ``` import numpy as np

def visualize_predictions(ckpt_path, val_dataset): net = resnet50(num_classes=2, pretrained=False) net.fc = nn.Dense(net.fc.in_channels, 2) net.avg_pool = nn.AvgPool2d(kernel_size=7)

param_dict = load_checkpoint(ckpt_path)
load_param_into_net(net, param_dict)
model = train.Model(net)

data = next(val_dataset.create_dict_iterator())
images = data["image"].asnumpy()
labels = data["label"].asnumpy()

outputs = model.predict(ms.Tensor(data["image"]))
preds = np.argmax(outputs.asnumpy(), axis=1)

plt.figure(figsize=(5, 5))
for i in range(4):
    img = images[i]
    img = np.transpose(img, (1, 2, 0))
    img = img * [0.229, 0.224, 0.225] + [0.485, 0.456, 0.406]
    img = np.clip(img, 0, 1)

    color = 'blue' if preds[i] == labels[i] else 'red'
    plt.subplot(2, 2, i+1)
    plt.imshow(img)
    plt.title(f"Pred: {class_name[preds[i]]}", color=color)
    plt.axis('off')

plt.tight_layout()
plt.show()

visualize_predictions(best_ckpt_path, dataset_val)

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.