利用pytorch搭建ResNet18网络训练CIFAR100数据集

注意事项

1.当抽取特定类(classes)进行训练时,可以使用以下方法:

class_indices = list(range(10, 20)) + list(range(50, 60))+list(range(80,90)) #定义要抽取的类别序号
#根据类别序号抽取训练和测试样本
#CIFAR-100数据集的标签是从0开始的,所以类别序号[10, 19]对应于标签索引[10, 20),
#类别序号[50, 59]对应于标签索引[50, 60)。确保你在定义类别序号时与数据集的类别对应。
#train_sampler和test_sampler,它们是自定义的采样器。采样器用于确定从数据集中选择哪些样本用于训练和测试。
#在这里,train_sampler和test_sampler分别使用train_indices和test_indices来选择相应的样本。
train_indices = [i for i in range(len(train_data)) if train_data.targets[i] in class_indices]
train_sampler = torch.utils.data.sampler.SubsetRandomSampler(train_indices) #创建自定义的采样器,仅选择包含所选类别的样本

test_indices = [i for i in range(len(test_data)) if test_data.targets[i] in class_indices]
test_sampler = torch.utils.data.sampler.SubsetRandomSampler(test_indices)

print(len(train_sampler))
print(len(test_sampler))

#利用dataloader来加载数据集
train= DataLoader(train_data, batch_size=64, sampler=train_sampler) #在下面的源码解释中可以看出,如果sampler不为默认的None的时候,不用设置shuffle属性了
test = DataLoader(test_data, batch_size=64, sampler=test_sampler)

2.原始数据集每个数据由data(图片)和targets(标签)两部分组成,通过train_data.datatrain_data.targets调用,图片的维度(shape)为(32, 32, 3),测试代码如下:

# 试验输出
print(train_data.targets[0]) #输出第一个标签值,为19,对应牛的标签
print(type(train_data.targets)) # <class 'list'>,数据集标签类型是列表
print(train_data.data[0].shape) #(32, 32, 3) 原始数据集图像的维度
plt.imshow(train_data.data[0]) #输出了牛的图片
plt.show()

3.在使用DataLoader处理完数据集后,图片的维度(shape)变为torch.Size([3, 32, 32]),这是神经网络能够接受的维度和类型,因此如果需要输出DataLoader处理过后的数据的图片,需要进行维度转换,具体操作见下:

examples = enumerate(test) #将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
batch_idx, (example_imgs, example_labels) = next(examples) #next的作用是返回迭代器的下一个项目,这里相当于取出来了test_data的第一个batch
print(batch_idx) #0
print(example_imgs[0].shape) #torch.Size([3, 32, 32]),经过DataLoader后,数据的维度会从32,32,3变为3,32,32,即神经网络能够接受的维度
print(example_labels[0].shape) #torch.Size([])
fig = plt.figure()
for i in range(64):
    img=example_imgs[i] #3,32,32是原本图片数据的维度
    img=np.transpose(img,(1,2,0)) #32,32,3需要转换成该维度才能输出3通道图片,即彩色图片
    plt.subplot(8, 8, i + 1)
    plt.imshow(img)
# plt.savefig('CIFAR100')
plt.show()

4.传统的ResNet18网络训练CIFAR100正确度不高,因此本文做了优化,主要是改变了第一层卷积层的卷积核以及最大池化层的参数;优化器的选择;学习率的设置;dropout的设置。

网络结构图如下(图上的参数没有修改,具体参数设置请参考代码):

具体代码

具体代码如下:

import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
import torch.nn.functional as F
import matplotlib.pyplot as plt
import numpy as np
import pickle
import time

if torch.cuda.is_available():
    device=torch.device("cuda")
else:
    device=torch.device("cpu")

mean = [0.5070751592371323, 0.48654887331495095, 0.4409178433670343]
std = [0.2673342858792401, 0.2564384629170883, 0.27615047132568404]

transforms_fn=torchvision.transforms.Compose([
    torchvision.transforms.ToTensor(),
    torchvision.transforms.Normalize(mean, std)
])

#100 classes containing 600 images each. There are 500 training images and 100 testing images per class.
#训练集
train_data=torchvision.datasets.CIFAR100('./cifar100_data',train=True,transform=transforms_fn,download=True)
#测试集
test_data=torchvision.datasets.CIFAR100('./cifar100_data',train=False,transform=transforms_fn,download=True)

train_data_size=len(train_data)
test_data_size=len(test_data)
print("训练数据集的长度为{}".format(train_data_size))
print("测试数据集的长度为{}".format(test_data_size))

# 试验输出
print(train_data.targets[0]) #输出第一个标签值,为19,对应牛的标签
print(type(train_data.targets)) # <class 'list'>,数据集标签类型是列表
print(train_data.data[0].shape) #(32, 32, 3) 原始数据集图像的维度
plt.imshow(train_data.data[0]) #输出了牛的图片
plt.show()

class_indices = list(range(10, 20)) + list(range(50, 60))+list(range(80,90)) #定义要抽取的类别序号
#根据类别序号抽取训练和测试样本
#CIFAR-100数据集的标签是从0开始的,所以类别序号[10, 19]对应于标签索引[10, 20),
#类别序号[50, 59]对应于标签索引[50, 60)。确保你在定义类别序号时与数据集的类别对应。
#train_sampler和test_sampler,它们是自定义的采样器。采样器用于确定从数据集中选择哪些样本用于训练和测试。
#在这里,train_sampler和test_sampler分别使用train_indices和test_indices来选择相应的样本。
train_indices = [i for i in range(len(train_data)) if train_data.targets[i] in class_indices]
train_sampler = torch.utils.data.sampler.SubsetRandomSampler(train_indices) #创建自定义的采样器,仅选择包含所选类别的样本

test_indices = [i for i in range(len(test_data)) if test_data.targets[i] in class_indices]
test_sampler = torch.utils.data.sampler.SubsetRandomSampler(test_indices)

print(len(train_sampler))
print(len(test_sampler))

#利用dataloader来加载数据集
train= DataLoader(train_data, batch_size=64, sampler=train_sampler) #在下面的源码解释中可以看出,如果sampler不为默认的None的时候,不用设置shuffle属性了
test = DataLoader(test_data, batch_size=64, sampler=test_sampler)



examples = enumerate(test) #将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
batch_idx, (example_imgs, example_labels) = next(examples) #next的作用是返回迭代器的下一个项目,这里相当于取出来了test_data的第一个batch
print(batch_idx) #0
print(example_imgs[0].shape) #torch.Size([3, 32, 32]),经过DataLoader后,数据的维度会从32,32,3变为3,32,32,即神经网络能够接受的维度
print(example_labels[0].shape) #torch.Size([])
fig = plt.figure()
for i in range(64):
    img=example_imgs[i] #3,32,32是原本图片数据的维度
    img=np.transpose(img,(1,2,0)) #32,32,3需要转换成该维度才能输出3通道图片,即彩色图片
    plt.subplot(8, 8, i + 1)
    plt.imshow(img)
# plt.savefig('CIFAR100')
plt.show()

# examples = enumerate(test_data) #将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
# batch_idx, (example_data, example_targets) = next(examples)
# fig = plt.figure()
# for i in range(100):
#   plt.subplot(4,25,i+1)
#   plt.tight_layout()
#   plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
#   plt.title("Ground Truth: {}".format(example_targets[i]))
#   plt.xticks([])
#   plt.yticks([])
# plt.show()

#定义残差块ResBlock
class ResBlock(nn.Module):
    def __init__(self,inchannel,outchannel,stride=1):
        super(ResBlock, self).__init__()
        #定义残差块里连续的2个卷积层
        self.block_conv=nn.Sequential(
            nn.Conv2d(inchannel,outchannel,kernel_size=3,stride=stride,padding=1),
            nn.BatchNorm2d(outchannel),
            nn.ReLU(),
            # nn.MaxPool2d(2),
            nn.Conv2d(outchannel,outchannel,kernel_size=3,stride=1,padding=1),
            nn.BatchNorm2d(outchannel)
        )

        # shortcut 部分
        # 由于存在维度不一致的情况 所以分情况
        self.shortcut = nn.Sequential()
        if stride != 1 or inchannel != outchannel:
            self.shortcut = nn.Sequential(
                # 卷积核为1 进行升降维
                # 注意跳变时 都是stride!=1的时候 也就是每次输出信道升维的时候
                nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(outchannel)
            )

    def forward(self,x):
        out1=self.block_conv(x)
        out2=self.shortcut(x)+out1
        out2=F.relu(out2) #F.relu()是函数调用,一般使用在foreward函数里。而nn.ReLU()是模块调用,一般在定义网络层的时候使用
        return out2


#构建RESNET18
class ResNet_18(nn.Module):
    def __init__(self,ResBlock,num_classes):
        super(ResNet_18, self).__init__()

        self.in_channels = 64 #输入layer1时的channel
        #第一层单独卷积层
        self.conv1=nn.Sequential(
            # (n-f+2*p)/s+1,n=28,n=32
            # nn.Conv2d(in_channels=3,out_channels=64,kernel_size=7,stride=2,padding=3),
            nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1), #64
            nn.BatchNorm2d(64),
            nn.ReLU(),
            # nn.MaxPool2d(kernel_size=3,stride=2,padding=1)
            nn.MaxPool2d(kernel_size=1, stride=1, padding=0) #64
            # nn.Dropout(0.25)
        )

        self.layer1=self.make_layer(ResBlock,64,2,stride=1) #64
        self.layer2 = self.make_layer(ResBlock, 128, 2, stride=2) #32
        self.layer3 = self.make_layer(ResBlock, 256, 2, stride=2) #16
        self.layer4 = self.make_layer(ResBlock, 512, 2, stride=2) #8


        self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) #torch.nn.AdaptiveAvgPool2d()接受两个参数,分别为输出特征图的长和宽,其通道数前后不发生变化。
                                                    #即这里将输入图片像素强制转换为1*1
        # self.linear=nn.Linear(2*2*512,512)
        # self.linear2=nn.Linear(512,100)

        self.linear=nn.Linear(512*1*1,num_classes)

        self.dropout = nn.Dropout(0.3)

    # 这个函数主要是用来,重复同一个残差块
    def make_layer(self, block, out_channels, num_blocks, stride):
        strides = [stride] + [1] * (num_blocks - 1)
        layers = []
        for stride in strides:
            layers.append(block(self.in_channels, out_channels, stride))
            self.in_channels = out_channels
        return nn.Sequential(*layers)

    def forward(self, x):
        x=self.conv1(x)
        # x=self.dropout(x)
        x=self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x=self.avgpool(x)
        x = x.view(x.size(0), -1)
        x=self.linear(x)
        x=self.dropout(x)

        return x

#网络模型
model=ResNet_18(ResBlock,num_classes=100)
model.to(device)
print(model)
#损失函数
loss_fn=nn.CrossEntropyLoss() #对于cross_entropy来说,他首先会对input进行log_softmax操作,然后再将log_softmax(input)的结果送入nll_loss;而nll_loss的input就是input。
#在多分类问题中,如果使用nn.CrossEntropyLoss(),则预测模型的输出层无需添加softmax层!!!
#如果是F.nll_loss,则需要添加softmax层!!!
loss_fn.to(device)

learning_rate=0.01

optimizer=torch.optim.SGD(params=model.parameters(),lr=learning_rate, momentum=0.9,weight_decay=0.0001)


train_acc_list = []
train_loss_list = []
test_acc_list = []
test_loss_list=[]
epochs=50

for epoch in range(epochs):
    print("-----第{}轮训练开始------".format(epoch + 1))
    train_loss=0.0
    test_loss=0.0
    train_sum,train_cor,test_sum,test_cor=0,0,0,0

    #训练步骤开始
    model.train()
    for batch_idx,(data,target) in enumerate(train):
        data,target=data.to(device),target.to(device)

        optimizer.zero_grad()  # 要将梯度清零,因为如果梯度不清零,pytorch中会将上次计算的梯度和本次计算的梯度累加
        # output = model(data)
        output = model(data)
        # loss = loss_fn(output, target)
        loss = loss_fn(output, target)
        loss.backward()
        optimizer.step()  # 更新所有的参数

        # 计算每轮训练集的Loss
        train_loss += loss.item()

        _, predicted = torch.max(output.data, 1)  # 选择最大的(概率)值所在的列数就是他所对应的类别数,
        # train_cor += (predicted == target).sum().item()  # 正确分类个数
        train_cor += (predicted == target).sum().item()  # 正确分类个数
        train_sum += target.size(0)  # train_sum+=predicted.shape[0]

    #测试步骤开始
    model.eval()
    # with torch.no_grad():
    for batch_idx1,(data,target) in enumerate(test):
        data, target = data.to(device), target.to(device)

        output = model(data)
        loss = loss_fn(output, target)
        test_loss+=loss.item()
        _, predicted = torch.max(output.data, 1)
        test_cor += (predicted == target).sum().item()
        test_sum += target.size(0)

    print("Train loss:{}   Train accuracy:{}%   Test loss:{}   Test accuracy:{}%".format(train_loss/batch_idx,100*train_cor/train_sum,
                                                                                       test_loss/batch_idx1,100*test_cor/test_sum))
    train_loss_list.append(train_loss / batch_idx)
    train_acc_list.append(100 * train_cor / train_sum)
    test_acc_list.append(100 * test_cor/ test_sum)
    test_loss_list.append(test_loss / batch_idx1)

#保存网络
torch.save(model,"CIFAR100_epoch{}.pth".format(epochs))



plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig=plt.figure()
plt.plot(range(len(train_loss_list)),train_loss_list,'blue')
plt.plot(range(len(test_loss_list)),test_loss_list,'red')
plt.legend(['训练损失','测试损失'],fontsize=14,loc='best')
plt.xlabel('训练轮数',fontsize=14)
plt.ylabel('损失值',fontsize=14)
plt.grid()
# plt.savefig('CIFAR100_figLOSS_6')
plt.show()

fig=plt.figure()
plt.plot(range(len(train_acc_list)),train_acc_list,'blue')
plt.plot(range(len(test_acc_list)),test_acc_list,'red')
plt.legend(['训练准确率','测试准确率'],fontsize=14,loc='best')
plt.xlabel('训练轮数',fontsize=14)
plt.ylabel('准确率(%)',fontsize=14)
plt.grid()
# plt.savefig('CIFAR100_figAccuracy_6')
plt.show()

训练结果

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值