参考与前言
- 课程链接:跟李沐学AI的个人空间_哔哩哔哩_bilibili
- 课程主页:https://zh-v2.d2l.ai/chapter_introduction/index.html
- 相关代码参考:https://github.com/SHENZHENYI/Classify-Leaves-Kaggle-MuLi-d2l-course
- pytorch官方相关教程参考:https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html
本部分主要用于记录自己做第二次作业的一些感受,主要是也第一次Dataloader 也算是一次学习了
首先遇到的问题:
- Dataloader没有注意到一个是Dataset,然后还要建一个batch_size的Loader
- 输出的output无法和label进行比较,最后直接算max来的
- 现在算是知道了沐神讲的,数据预处理的重要性,特别是关于图片的分类中的处理看起来更为重要 -> 看原始数据不同 -> 水平垂直25%随机翻转
- loss抖动过大,原因loss的计算结果不应该作为看图的东西,而应该是准确率 -> label smoothing
- optimization选取 从SGD -> Adam-> AdamW
1. 模型
Resnet18
其实看kaggle讨论和代码区 挺多人上了resnet50及更为复杂的模型,基本没有自己手写的cnn,所以我就用了小巧点的 毕竟想快点看到结果(基本就训练个半小时),也不冲前五了
import torch
import pandas as pd
from tqdm.notebook import tqdm
import numpy as np
import os
import torchvision
from torchvision import datasets, models, transforms
import torch.nn as nn
from torch.nn import functional as F
from torch.utils import data
import torch.optim as optim
from torchvision.io import read_image
from PIL import Image
from torch.utils.data import Dataset,DataLoader
from torchvision.transforms import ToTensor, Lambda
import albumentations
from albumentations import pytorch as AT
import cv2
import matplotlib.pyplot as plt
import wandb
num_epochs, lr, bs, weight_decay = 50, 0.001, 64, 2e-4
NUM_SAVE = num_epochs//5
class MODEL(nn.Module):
def __init__(self, out_label):
super().__init__()
self.resnet = models.resnet18(pretrained=True)
self.resnet.fc = nn.Linear(self.resnet.fc.in_features,out_label)
def forward(self, X):
return self.resnet(X)
Loss选取
没啥讲究,就选了CrossEntropyLoss 但是看到讨论区有人对这个做了顺滑功能,不然loss看着抖动特别大 emmm
criterion = nn.CrossEntropyLoss()
但其实在后面计算精度的时候,是直接取最大的那个然后对比label是否一致,类似于这样:
acc = (outputs.argmax(dim=-1) == labels).float().mean()
优化器
倒是这个优化器,一开始选了SGD,Adam 都不太熟内在 然后weight decay还是临时补了看了一下 然后一开始这个weight decay直接拉到了0.95 没有意识到事情的严重性(就是之前从上一个那里拉的;相关阅读:L2正则=Weight Decay?并不是这样 - 知乎 (zhihu.com)
后面看搜了搜就发现有AdamW,然后看其他人做的时候(参考链接处的那位同学) 还有对不同的layer有不同的学习率
net = MODEL(out_label = len(classes)).to(device)
params_1x = [param for name, param in net.resnet.named_parameters()
if name not in ["fc.weight", "fc.bias"]]
optimizer = optim.AdamW([{'params': params_1x},
{'params': net.resnet.fc.parameters(),
'lr': lr * 10}],
lr=lr, weight_decay=weight_decay)
2. 数据处理
这一块其实看训练和测试数据集 应该是要把光亮也多少加进去,就是随机加,但是我看了一下ColorJitter 并没有random的概率 emmm 所以就懒了,其实随便写一个random应该也可以
导入数据
和前一个其实差不多,主要是对着pytorch的教程写了一个dataloader然后看其他人 哦吼 原来图片操作这么多
首先是直接read_csv,然后大概按0.9和0.1随机分割训练和验证集,其实也可以用K-fold 我第一次没想好怎么个分割法,直接从train_dataloader那里不太好,从这里吧 每次又需要建新的loader也不太好,干脆 就不弄了,直接这样吧,这次有val主要是 意识到了 还是要看看的严重性 emmm
test_data = pd.read_csv('test.csv')
all_data = pd.read_csv('train.csv')
train_data = all_data.sample(n=int(len(all_data)*0.9),ignore_index=True)
val_data = all_data.sample(n=int(len(all_data)*0.1),ignore_index=True)
classes = all_data['label'].unique().tolist()
print("train_data:",all_data.shape,"test_data shape:",test_data.shape,"\nlabel size:", len(classes))
注意这里的sample函数是来源于pandas的,需要pandas New in version 1.3.0. 不然会报错没有ignore_index,如果不ignore index的话 emmm loader会报错,因为索引有问题,光这个bug 找了大概半小时,后知后觉 算是学习了
预处理
主要写在了dataset这一环,果然和杰哥说的一样 炼丹就是写dataloader hhhhh
class MyDataset(Dataset):
def __init__(self, annotations, img_dir, mode=None, target_transform=None):
super().__init__()
self.img_labels = annotations
self.img_dir = img_dir
if mode=='train':
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.RandomHorizontalFlip(p=.25),
transforms.RandomVerticalFlip(p=.5),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),])
elif mode=='test':
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),])
elif mode=='val':
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),])
self.transform = preprocess
self.target_transform = target_transform
def __len__(self):
return len(self.img_labels)
def __getitem__(self, idx):
img_path = os.path.join(self.img_dir[idx])
with Image.open(img_path) as im:
image = im
label = self.img_labels.iloc[idx]
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
可以看到对于test和val都不做图像增广的操作,train也是按0.25 0.25的比例来做,其实加一个亮光应该更好一点,另外我发现这个增广是增广并没有增多数据,其实增多应该也没啥问题?可能担心过拟合?
target_transform = Lambda(lambda y: torch.tensor(classes.index(y)))
training_data = MyDataset(train_data['label'], train_data['image'], 'train', target_transform)
train_dataloader = DataLoader(training_data, batch_size=bs, shuffle=True)
val_data = MyDataset(val_data['label'], val_data['image'], 'val', target_transform)
val_dataloader = DataLoader(val_data, batch_size=bs, shuffle=False)
testing_data = MyDataset(test_data['image'], test_data['image'], 'test', None)
test_dataloader = DataLoader(testing_data, batch_size=bs, shuffle=False)
print("train_data length:",len(training_data),"test_data length:",len(test_data))
3. 训练步骤
这一次没啥了 而且function都懒了 直接写在主体里了,其中有wandb的都是我… 习惯看数据的平台 对比与tensorboard emmm 可以让我多设备 在线看效果 hhh 想起来一开始,吃饭路上看了一眼自己的精度曲线95%好耶,然后一提交88% emmm
wandb.watch(net)
step = 0
for epoch in tqdm(range(num_epochs)):
train_accs = []
for i, data in enumerate(train_dataloader,0):
inputs, labels = data
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
step+=1
acc = (outputs.argmax(dim=-1) == labels).float().mean()
train_accs.append(acc)
wandb.log({'loss': loss,'step':step})
del inputs, labels
train_accuracy = sum(train_accs) / len(train_accs)
wandb.log({'train accuracy': train_accuracy,'epoch': epoch})
val_accs = []
for i, data in enumerate(val_dataloader,0):
inputs, labels = data
inputs, labels = inputs.to(device), labels.to(device)
outputs = net(inputs)
acc = (outputs.argmax(dim=-1) == labels).float().mean()
val_accs.append(acc)
del inputs, labels
val_accuracy = sum(val_accs) / len(val_accs)
wandb.log({'accuracy': val_accuracy,'epoch': epoch})
print("No. epoch:",epoch, "accuracy:"+"{:.2f}%".format(train_accuracy.item()*100))
if epoch%NUM_SAVE==0 and epoch!=0:
torch.save(net.state_dict(),'checkpoint_'+str(epoch))
print("Model Saved")
wandb.finish()
print('Finished Training, the last loss is:', loss.item())
唯一需要注意的是关于精度最好还是打一下 不然看着loss 抖动总是心有余悸,比如这样 是不是右边的顺眼多了
4. 结果
看kaggle上 有人0.99真的是 太太太太 吓人了,一开始光溜溜的写完loader,用的SGD基本默认参数,就大概直接train 上传后有92%结果,然后后面开始骚操作了一个比一个低,主要是我都不知道我改了些啥,emmm 做好记录的重要原因体现了,最新的结果emm 还是91%,所以又回到了最初的起点 hhhh 就是这篇博文上的代码提交的,给训练了50 epoch,大概贴一下val train loss的图和大致模型梯度(但是有太多个layer梯度了 所以 只截取了一点点)
|