利用Cifar10 数据集跑resnet网络

数据集采用的是kaagle⽐赛中的Cifar10,⽹址是https://www.kaggle.com/c/cifar-10
比赛数据集分为训练集和测试集,其中训练集包含 50000 张、测试集包含 300000 张图像。
两个数据集中的图像都是 png 格式,⾼度和宽度均为 32 像素并有三个颜⾊通道( RGB )。这些图
⽚共涵盖 10 个类别:⻜机、汽⻋、⻦类、猫、⿅、狗、⻘蛙、⻢、船和卡⻋。

一、导入所需要的包和模块

import collections
import math
import os
import shutil
import pandas as pd
import torch
import torchvision
from torch import nn

二、加载并整理数据集

def read_csv_labels(fname):
    """读取fname来给标签字典返回一个文件名"""
    with open(fname, 'r') as f:
        #只读模式,跳过文件头行(列名)
        lines = f.readlines()[1:]
    tokens = [l.rstrip().split(',') for l in lines]
    #形成二维列表,每个列表中为编号以及标签
    return dict(((name, label) for name, label in tokens))
    #形成字典key为name,value为label

data_dir='./kaggle/'
labels = read_csv_labels(os.path.join(data_dir, 'trainLabels.csv'))
print('# 训练样本 :', len(labels))
print('# 类别 :', len(set(labels.values())))

我这里将数据集存放到了一个叫kaggle的文件夹下面

# 训练样本 : 50000
# 类别 : 10

将验证集从原始的训练集就划分出来

#将验证集从原始训练集中拆分出来

def copyfile(filename, target_dir):
    """将文件复制到目标目录"""
    os.makedirs(target_dir, exist_ok=True)
    #判断并新建指定的目标目录。
    shutil.copy(filename, target_dir)
    #实现具体的文件复制功能

def reorg_train_valid(data_dir, labels, valid_ratio):
    """将验证集从原始的训练集中拆分出来"""
    # 训练数据集中样本最少的类别中的样本数
    n = collections.Counter(labels.values()).most_common()[-1][1]
    #Counter(lables.values())返回的是一个字典,其中包含了每个标签出现的次数
    # most_common()[-1][1]表示将字典从大到小排列,
    #并取[-1][1]表示取最后一个键值对中取最小的标签出现次数赋予n
    #验证集中每个类别的样本数
    
    n_valid_per_label = max(1, math.floor(n * valid_ratio))
    # math.floor具体作用是返回不大于输入参数的最大整数
    
    label_count = {}
    for train_file in os.listdir(os.path.join(data_dir, 'train')):
        #os.listdir() 函数则返回指定路径下的所有文件和子目录的名称列表。
        #循环语句的作用是遍历训练集文件夹中的所有文件名,针对每个文件名进行特定的处理。
        
        label = labels[train_file.split('.')[0]]
        #例如将train下面的图片文件1.png中转化为列表['1','png']
        #然后[0]提取出数字为索引,然后得到该索引对应的标签为cat,dog之类
        
        fname = os.path.join(data_dir, 'train', train_file)
        #每张图片的路径
        
        copyfile(fname, os.path.join(data_dir, 'train_valid_test',
                                     'train_valid', label))
        if label not in label_count or label_count[label] < n_valid_per_label:
            copyfile(fname, os.path.join(data_dir, 'train_valid_test',
                                         'valid', label))
            label_count[label] = label_count.get(label, 0) + 1
        else:
            copyfile(fname, os.path.join(data_dir, 'train_valid_test',
                                         'train', label))
    return n_valid_per_label

#将train数据集划分为train_valid文件,其中文件下有各类文件夹下的图片。
#然后有valid文件,文件下有采集一定数量的各类图片
#剩余数据图片在train里的各类图片
定义 reorg_train_valid 函数来将验证集从原始的训练集中拆分出来。此函数中的参
valid_ratio 是验证集中的样本数与原始训练集中的样本数之比。更具体地说,令 n 等于样本最少的类别 中的图像数量,⽽r 是⽐率。验证集将为每个类别拆分出 max ([nr],   1) 张图像。让我们以 valid_ratio=0.1 为 例,由于原始的训练集有50000 张图像,因此 train_valid_test/train 路径中将有 45000 张图像⽤于训练,而剩下5000 张图像将作为路径 train_valid_test/valid 中的验证集。组织数据集后,同类别的图像将放置在同⼀⽂件夹下。
 
def reorg_test(data_dir):
    """在预测期间整理测试集,以方便读取"""
    for test_file in os.listdir(os.path.join(data_dir, 'test')):
        copyfile(os.path.join(data_dir, 'test', test_file),
                 os.path.join(data_dir, 'train_valid_test', 'test',
                              'unknown'))

def reorg_cifar10_data(data_dir, valid_ratio):
    labels = read_csv_labels(os.path.join(data_dir, 'trainLabels.csv'))
    reorg_train_valid(data_dir, labels, valid_ratio)
    reorg_test(data_dir)

batch_size = 128
valid_ratio = 0.1
reorg_cifar10_data(data_dir, valid_ratio)

 

以上图片为划分好数据集后的展示,每个数据集下面都是对应标签的10个类别的图片

三、进行图像增广

#图像增广
transform_train = torchvision.transforms.Compose([
    # 在高度和宽度上将图像放大到40像素的正方形
    torchvision.transforms.Resize(40),
    # 随机裁剪出一个高度和宽度均为40像素的正方形图像,
    # 生成一个面积为原始图像面积0.64~1倍的小正方形,
    # 然后将其缩放为高度和宽度均为32像素的正方形
    torchvision.transforms.RandomResizedCrop(32, scale=(0.64, 1.0),
                                                   ratio=(1.0, 1.0)),
    torchvision.transforms.RandomHorizontalFlip(),
    torchvision.transforms.ToTensor(),
    # 标准化图像的每个通道
    torchvision.transforms.Normalize([0.4914, 0.4822, 0.4465],
                                     [0.2023, 0.1994, 0.2010])])

#测试期间,我们只对图像执⾏标准化,以消除评估结果中的随机性
transform_test = torchvision.transforms.Compose([
    torchvision.transforms.ToTensor(),
    torchvision.transforms.Normalize([0.4914, 0.4822, 0.4465],
                                     [0.2023, 0.1994, 0.2010])])

四、读取数据集

#读取数据集
train_ds, train_valid_ds = [torchvision.datasets.ImageFolder(
    os.path.join(data_dir, 'train_valid_test', folder),
    transform=transform_train) for folder in ['train', 'train_valid']]

valid_ds, test_ds = [torchvision.datasets.ImageFolder(
    os.path.join(data_dir, 'train_valid_test', folder),
    transform=transform_test) for folder in ['valid', 'test']]


train_iter, train_valid_iter = [torch.utils.data.DataLoader(
    dataset, batch_size, shuffle=True, drop_last=True)
    for dataset in (train_ds, train_valid_ds)]

valid_iter = torch.utils.data.DataLoader(valid_ds, batch_size, shuffle=False,
                                         drop_last=True)

test_iter = torch.utils.data.DataLoader(test_ds, batch_size, shuffle=False,
                                        drop_last=False)

五、模型

def resnet18(num_classes, in_channels=1):
    """稍加修改的ResNet-18模型"""
    def resnet_block(in_channels, out_channels, num_residuals,
                     first_block=False):
        blk = []
        for i in range(num_residuals):
            if i == 0 and not first_block:
                blk.append(d2l.Residual(in_channels, out_channels,
                                        use_1x1conv=True, strides=2))
            else:
                blk.append(d2l.Residual(out_channels, out_channels))
        return nn.Sequential(*blk)

    # 该模型使用了更小的卷积核、步长和填充,而且删除了最大汇聚层
    net = nn.Sequential(
        nn.Conv2d(in_channels, 64, kernel_size=3, stride=1, padding=1),
        nn.BatchNorm2d(64),
        nn.ReLU())
    net.add_module("resnet_block1", resnet_block(
        64, 64, 2, first_block=True))
    net.add_module("resnet_block2", resnet_block(64, 128, 2))
    net.add_module("resnet_block3", resnet_block(128, 256, 2))
    net.add_module("resnet_block4", resnet_block(256, 512, 2))
    net.add_module("global_avg_pool", nn.AdaptiveAvgPool2d((1,1)))
    net.add_module("fc", nn.Sequential(nn.Flatten(),
                                       nn.Linear(512, num_classes)))
    return net

这里使用的是修改过的resnet18模型,将第一层的7*7的卷积层换为了3*3的卷积层,stride为1,且填充为1,最后删去了最大汇聚层。

def get_net():
    num_classes = 10
    net=resnet18(num_classes, 3)
    return net

def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            nn.init.xavier_uniform_(m.weight)
            net.apply(init_weights)

六、训练

from torch.utils.tensorboard import SummaryWriter

train_data_size = len(train_ds)
test_data_size = len(valid_ds)


model = get_net()
init_weights(model)
device = torch.device("cuda")

#损失函数
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.to(device)


# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 250



# 优化器
# learning_rate = 0.01
# 1e-2=1 x (10)^(-2) = 1 /100 = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)




# 添加tensorboard
writer = SummaryWriter("../logs_train")

for i in range(epoch):
    print("-------第 {} 轮训练开始-------".format(i+1))

    # 训练步骤开始
    model.train()
    for data in train_iter:
        imgs, targets = data
        imgs = imgs.to(device)
        targets = targets.to(device)
        model=model.to(device)
        outputs = model(imgs)
        loss = loss_fn(outputs, targets)

        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0:
            print("训练次数:{}, Loss: {}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)

    # 测试步骤开始
    model.eval()
    total_test_loss = 0
    total_accuracy = 0
    with torch.no_grad():
        for data in valid_iter:
            imgs, targets = data
            imgs = imgs.to(device)
            targets = targets.to(device)
            outputs = model(imgs)
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("整体测试集上的Loss: {}".format(total_test_loss))
    print("整体测试集上的正确率: {}".format(total_accuracy/test_data_size))
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy/test_data_size, total_test_step)
    total_test_step = total_test_step + 1

    torch.save(model, "resnet_{}.pth".format(i))
    print("模型已保存")

writer.close()


训练次数设置为250,学习率为0.01,优化器为SGD,用tensorboard可视化训练过程,并保存训练参数。最后训练发现验证集的准确率为0.875左右。

 本文的部分代码参考自《动手学深度学习》以及训练部分参考tudui

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值