李哥深度学习代码复盘--分类实战(上)

分类实战:对图片进行分类

其中带标签的训练数据共有11类,每类280个,不带标签的训练数据共6786个,不带标签的数据需要用到半监督学习的方法。

上半是对带标签的数据进行训练验证,下半则主要介绍半监督学习及代码复盘

1.数据处理

(1)数据增广,通过对现有的数据样本进行变换,生成更多数据样本,以提高模型泛化能力,减少过拟合风险。

#训练集数据变换
train_transform = transforms.Compose(
    [
        transforms.ToPILImage(),             #将数据转换为PIL Image对象,以便对其进行操作
        transforms.RandomResizedCrop(224),   #随机放大裁剪,并调整到224*224
        transforms.RandomRotation(50),       #50度以内随机旋转
        transforms.ToTensor()                #将PIL Image对象转换为tensor,即模型接受的输入
    ]
)
#验证集数据变换   (验证和测试需要用原图,故不做变换)
val_transform = transforms.Compose(
    [
        transforms.ToTensor()
    ]
)

(2)数据集类,类中初始化、获取项目和长度三个函数与回归实战大体相似,但因为分类图片存储在文件夹中,不能使用csv模块读写,故另外定义一个读文件函数。

HW = 224
class food_Dataset(Dataset):
    def __init__(self, path, mode="train"):
        self.mode = mode
        self.X, self.Y = self.read_file(path)   #调用读文件函数
        self.Y = torch.LongTensor(self.Y)       #标签转为长整型
        if mode == "train":
            self.transform = train_transform    #数据增广,允许图片进行变换
        else:
            self.transform = val_transform

    def read_file(self, path):
        for i in tqdm(range(11)):             # 标签数据被分成11个文件夹,tqdm用于显示进度
            file_dir = path + "/%02d" % i     # %02d,保留两位整数
            file_list = os.listdir(file_dir)  # 列出文件夹下所有的文件名字

            xi = np.zeros((len(file_list), HW, HW, 3), dtype=np.uint8)  #某类的所有图片
            yi = np.zeros(len(file_list), dtype=np.uint8)    #某类图片对应的标签

            for j, img_name in enumerate(file_list):
                img_path = os.path.join(file_dir, img_name)  
                #合并目录路径和图片名字得到图片路径
                img = Image.open(img_path)  # 读取图片
                img = img.resize((HW, HW))  # 调整图片大小  512*512 --> 224*224
                xi[j, ...] = img    #存入图像
                yi[j] = i    #存入标签

            if i == 0:         #如果是第一次则直接赋值,否则拼接以合并所有图像与标签
                X = xi
                Y = yi
            else:
                X = np.concatenate((X, xi), axis=0)   #axis=0代表新内容放到下一行
                Y = np.concatenate((Y, yi), axis=0)
        print("读到了%d个数据" % len(Y))    #显示一共读到了多少数据
        return X, Y
    #读文件函数接收文件路径参数,返回所有读取并调整大小后的图像数据及其对应的标签

    def __getitem__(self, item):
        return self.transform(self.X[item]), self.Y[item]
        
    def __len__(self):
        return len(self.X)
#实例化
train_set = food_Dataset(train_path, "train")    
val_set = food_Dataset(val_path, "val")

2.定义模型(三种方法)

(1)自定义。

class myModel(nn.Module):
    def __init__(self, num_class):       #num_class为图片类别数
        super(myModel, self).__init__()
        #3*224*224 ->512*7*7 
        #layer0到layer1为四个卷积层序列
        self.layer0 = nn.Sequential(
            nn.Conv2d(3, 64, 3, 1, 1),     #卷积层  3*224*224->64*224*224
        #(输入为3,输出为64,卷积核为3*3,步长1,填充1(保证输出与输入尺寸一致,根据卷积核大小定)
            nn.BatchNorm2d(64),            #批量归一化层(标准化处理,加速训练过程)
            nn.ReLU(),                     #激活函数
            nn.MaxPool2d(2),               #最大池化层,池化窗口2*2  64*224*224->64*112*112
        )
        self.layer1 = nn.Sequential(
            nn.Conv2d(64, 128, 3, 1, 1),   # 64*112*112->128*112*112
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.MaxPool2d(2),               # 128*112*112->128*56*56
        )
        self.layer2 = nn.Sequential(
            nn.Conv2d(128, 256, 3, 1, 1),  # 128*56*56->256*56*56
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(2),               # 256*56*56->256*28*28
        )
        self.layer3 = nn.Sequential(
            nn.Conv2d(256, 512, 3, 1, 1),  # 256*28*28->512*28*28
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.MaxPool2d(2),               # 512*28*28->512*14*14
        )

        self.pool = nn.MaxPool2d(2)        #额外池化层  512*14*14->512*7*7(参数量25088)
        self.fc1 = nn.Linear(25088, 1000)  #第一个全连接层   25088-->1000
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(1000, num_class)   #第二个全连接层   1000-->num_class

    def forward(self, x):
        x = self.layer0(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.pool(x)
        x = x.view(x.size()[0], -1)    #拉直,将多维张量转化为一维向量以便进入全连接层
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        return x
#实例化
model = myModel(11) 

其中每个卷积层也可如下,不过前向传播会稍繁复。

        self.conv1 = nn.Conv2d(3, 64, 3, 1, 1)   
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU()
        self.pool1 = nn.MaxPool2d(2)

(2)从其他文件中调用,比如之前写过的可以直接拿来用。

from model_utils.model import initialize_model
#引用model_utils文件夹下的model.py文件中定义的initialize_model模型模块
model, _ = initialize_model("resnet18", 11, use_pretrained=True)
#调用并传入相关参数,具体看函数定义

(3)从模块里调用。

from torchvision.models import resnet18    #引入ResNet-18卷积神经网络模型
model = resnet18(pretrained=True)     #实例化,True用架构和参数,false只用架构
in_fetures = model.fc.in_features     #获取原输入特征
model.fc = nn.Linear(in_fetures, 11)  #建立新的全连接层,接受原来的输入,但调整输出为所需

3.训练和验证

与回归实战类似,区别在于用最大准确率代替了最小损失,初始化为0,若验证准确率大于最大准确率则更新模型。同时记录了每轮模型在训练集和验证集上的准确率,进行可视化。

在训练集上的准确次数计算如下:

train_acc += np.sum(np.argmax(pred.detach().cpu().numpy(), axis=1) == target.cpu().numpy())
#argmax指出最大值下标, 最大值下标与标签比较得到预测正确数量

4.配置及调用

本次实战使用了新的优化器AdamW,它与SGD有两个区别,一是SGD主要计算某点的梯度,而AdamW需要综合该点与该点以前的梯度;二是,可以自动调整学习率,更不容易出现梯度爆炸。

总体而言AdamW能够提供比SGD更稳定的训练过程。

另外也使用了新的损失计算函数--交叉熵,用于处理本次实战面对的分类问题。

所得结果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值