2022李宏毅作业hw1—新冠阳性人员数量预测。_亮子李的博客-CSDN博客
hw-2 李宏毅2022年作业2 phoneme识别 单strong-hmm详细解释。_亮子李的博客-CSDN博客
git地址:
lihongyi2022homework/hw3_food at main · xiaolilaoli/lihongyi2022homework · GitHub
注意我做的是2021的 ,因为2022的和2020的是一样的 所以之前做过了 ,就选了不一样的2021的。
作业三我准备,做着写着 因为,有点小困难。第三个作业,是需要极强的半监督和数据增广能力。这些我都是现学的,所以怕最后忘掉,所以我走一步记一步,正好用博文来做笔记。
注意在作业3和后面的作业里, 我可能就不再会详细的写怎么代码的每一部分了,因为和hw1和hw2都是大同小异的。 可以参考之前的形成自己的模块。 除非那个作业有很不一样的地方。
我最后的准确率是百分之80左右。 有时候甚至能达到百分之82。 可惜的是目前这个作业关闭了提交入口。 如果说kaggle上面的分数就是准确率的话,那我的准确率就在strongline边上。 可是我实在无法想象90多的准确率是怎么达到的。 注意这个作业是不可以用预训练模型的,如果你用预训练的resnet 那么准确率很容易就达到九十多。 但是就违规了。
可以看到我训练集都没有办法达到95以上的准确率,可能是模型太小了。 我很想换成res50试一试, 可是显卡真的不允许。 我不理解对比学习占空间怎么这么大 。。。。。。。
kaggle数据地址: ml2021spring-hw3 | Kaggle
数据 是这个任务的重头戏。其实我第一次学李老师的课时做过食物分类的作业,不过当时完全是小白。 徒增笑耳。 这次的作业是把上次的有监督任务改成了半监督任务。
可以看到 有标签的数据只有280 *11, 比测试集还要少,这种一般都没有办法做很高的准确率这样子。所以我们必须用上没有标签的训练数据,足足有6786张。
一般来说 步骤就是 用有标签的数据训练一个模型 , 在模型的准确率达到一定门槛的时候, 用这个模型去预测无标签的数据 如果置信度大于一个阈值 比如 我们认为置信度大于0.85的图片 可以作为训练的图片。 就把这张作为新的训练集。 迭代几次模型后,再次对无标签数据进行预测 再次挑取那些置信度高的数据。
然后我去学习了数据增广的一些知识。我发现数据增广的影响实在是太大了。
train_transform = transforms.Compose([
# transforms.Resize((224, 224)),
# transforms.ToTensor(),
transforms.ToPILImage(),
# transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
autoaugment.AutoAugment(),
transforms.ToTensor(),
# transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
])
这里是一些可选的数据增广方式 我发现 如果你 加入了最后一项NORMlize后 这个模型几乎完全训练不动
这个原因我不知道是为什么 , 后面再解决吧。 我感觉很奇怪 反正。
而当你去掉这一项 就可以大概的训练起来了。 至少准确率能到30左右
如果你选取一个合适的学习率和模型,比如我选的是res18 那么你大概能达到百分之五十的准确率。
如果你加上半监督, 那么准确率也是一直在浮动, 最高到百分之60左右。 我也不知道怎么样才能做到更高了。
正值此时,李沐频道的朱老师发了一个对比学习的综述视频。 看了之后发现, 咦,对比学习不是正好可以用来解决这个问题吗? 拥有大量的无标签数据。
精调细选之后,挑取了simsaim作为我们使用的模型, 第一是因为他够新, 第二是因为他非常的简单,听说。 关于simsaim 来学习食物分类看下面这篇。
对比学习 ——simsiam 代码解析。:_亮子李的博客-CSDN博客
在git里 我也上传了自己用simsaim训练的代码。 用的时候需要改下数据的地址。
经过simsaim之后我们能得到两个模型。 一个是提取特征的res18 模型, 一个是用来分类的classfier模型。 回来后我们可以丢弃classfier,只用那个res18的backbone。 其实就是进行一个抽特征之后线性验证的过程。 现在我们有了抽特征的模型 ,就相当于一个预训练的模型,然后要对分类头进行训练 。
直接看过程吧。 注意复制simsaim的models和optimizer 两个模块 到这个项目里来。
train_loader = getDataLoader(filepath, 'train', batchSize)
val_loader = getDataLoader(filepath, 'val', batchSize)
no_label_Loader = getDataLoader(filepath,'train_unl', batchSize)
看看dataset 怎么写的可以。
sim_train_trans = transforms.Compose([
transforms.ToPILImage(),
transforms.RandomResizedCrop(HW, scale=(0.08, 1.0), ratio=(3.0/4.0,4.0/3.0), interpolation=Image.BICUBIC),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(*imagenet_norm)
])
sim_test_trans = transforms.Compose([
transforms.ToPILImage(),
transforms.Resize(int(HW*(8/7)), interpolation=Image.BICUBIC), # 224 -> 256
transforms.CenterCrop(HW),
transforms.ToTensor(),
transforms.Normalize(*imagenet_norm)
])
class foodDataset(Dataset):
def __init__(self, path, mode):
y = None
self.transform = None
self.mode = mode
pathDict = {'train':'training/labeled','train_unl':'training/unlabeled', 'val':'validation', 'test':'testing'}
imgPaths = path +'/'+ pathDict[mode]
# train_transform = transforms.Compose([
# transforms.RandomResizedCrop(224),
# transforms.RandomHorizontalFlip(),
# # autoaugment.AutoAugment(),
# transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
# ])
if mode == 'test':
x = self._readfile(imgPaths,False)
self.transform = sim_test_trans
elif mode == 'train':
x, y =self._readfile(imgPaths,True)
self.transform = sim_train_trans
elif mode == 'val':
x, y =self._readfile(imgPaths,True)
# self.transform = test_transform
self.transform = sim_test_trans
elif mode == 'train_unl':
x = self._readfile(imgPaths,False)
self.transform = sim_test_trans
if y is not None:
y = torch.LongTensor(y)
self.x, self.y = x, y
def __getitem__(self, index):
orix = self.x[index]
if self.transform == None:
xT = torch.tensor(orix).float()
else:
xT = self.transform(orix)
if self.y is not None:
y = self.y[index]
return xT, y, orix
else:
return xT, orix
def _readfile(self,path, label=True):
if label:
x, y = [], []
for i in tqdm(range(11)):
label = '/%02d/'%i
imgDirpath = path+label
imglist = os.listdir(imgDirpath)
xi = np.zeros((len(imglist), HW, HW ,3),dtype=np.uint8)
yi = np.zeros((len(imglist)),dtype=np.uint8)
for j, each in enumerate(imglist):
imgpath = imgDirpath + each
img = Image.open(imgpath)
img = img.resize((HW, HW))
xi[j,...] = img
yi[j] = i
if i == 0:
x = xi
y = yi
else:
x = np.concatenate((x, xi), axis=0)
y = np.concatenate((y, yi), axis=0)
print('读入有标签数据%d个 '%len(x))
return x, y
else:
imgDirpath = path + '/00/'
imgList = os.listdir(imgDirpath)
x = np.zeros((len(imgList), HW, HW ,3),dtype=np.uint8)
for i, each in enumerate(imgList):
imgpath = imgDirpath + each
img = Image.open(imgpath)
img = img.resize((HW, HW))
x[i,...] = img
return x
def __len__(self):
return len(self.x)
写了一个读文件的函数 。 然后值得注意的是 数据增广和 对比学习时的增广方式一定要保持一致。我这里采取的方式 都是原来simsaim的 ,没有变化。
训练方式和以前也有所不同。 主要的不同部分在于 提取特征和分类头分开了 。我们来看。
def load_backBone(model, prepath, is_dict=False):
save_dict = torch.load(prepath, map_location='cpu')
if is_dict:
new_state_dict = save_dict
else:
new_state_dict = {'state_dict':save_dict.module.state_dict()}
msg = model.load_state_dict({k[9:]:v for k, v in new_state_dict['state_dict'].items()
if k.startswith('backbone.')}, strict=True)
return model
backbone = get_backbone('resnet18_cifar_variant1')
backbone = load_backBone(backbone,SimPrePath,False)
classfier = nn.Linear(in_features=512, out_features=11, bias=True)
上面是获取模型。 下面是超参数,我直接搬simsaim的过来 。 学习率也可以自己调调。 我们可以看到原来的学习率是1点多 。有点可怕。 但是还挺好用的。 不可思议。
##lr = 30*batchSize/256
optimizer = get_optimizer(
'sgd', classfier,
lr=0.001,
momentum=0.9,
weight_decay=0)
# define lr scheduler
scheduler = LR_Scheduler(
optimizer,
0, 0*batchSize/256,
epoch, 0.001, 0*batchSize/256,
len(train_loader),
)
下面是训练的代码 。
classifier.train()
for data in tqdm(train_loader):
if flag ==0:
backBone.eval()
if max_acc > 0.74:
flag = 1
classifier.zero_grad()
x , target = data[0].to(device), data[1].to(device)
with torch.no_grad():
feature = backBone(x)
num = random.randint(0,10000)
if num == 99:
samplePlot(train_loader,True,isbat=False,ori =True)
pred = classifier(feature)
bat_loss = loss(pred, target)
bat_loss.backward()
scheduler.step()
optimizer.step()
else:
backBone.train()
classifier.zero_grad()
backBone.zero_grad()
x , target = data[0].to(device), data[1].to(device)
feature = backBone(x)
num = random.randint(0,10000)
if num == 99:
samplePlot(train_loader,True,isbat=False,ori =True)
pred = classifier(feature)
bat_loss = loss(pred, target)
bat_loss.backward()
scheduler.step()
optimizer.step()
train_loss += bat_loss.item() #.detach 表示去掉梯度
train_acc += np.sum(np.argmax(pred.cpu().data.numpy(),axis=1)== data[1].numpy())
plt_train_loss . append(train_loss/train_loader.dataset.__len__())
plt_train_acc.append(train_acc/train_loader.dataset.__len__())
可以看到我是怎么写的。 如果在准确率小于一定阈值的情况下, 这里是0.74 .我选择只训练分类头。(通过.train .eval控制。) 如果大于0.74 我会让分类头和特征提取器一起训练。 我想的是 之前对比学习准确率已经到0.72了 。如果你直接一上来分类头是随机的情况下乱训练, 很容易让对比学习学到的模型崩溃。 所以在一定的准确率之前, 还不能让特征提取器乱动。
这样训练出来的准确率能达到百分之八十多一点点。 但是训练的非常慢
好吧 我承认, 我训练了半天 ,半监督根本没用上,当我尝试加入半监督后,准确率根本就上不去。 但还是自己辛辛苦苦写的。 po上来给大家看看吧。也许大家能看出我哪里有问题才导致上不去。 事实上,我在对比学习前就试过半监督了,准确率根本没有上去过。
半监督的关键在于新数据集的制作。 我们知道 半监督, 就是达到一定准确率的模型 来预测没有标签的图片, 将这些图片达到的定置信度的打上标签作为训练数据。
所以最关键的部分就是打标签的部分啦。 我是这样做的。 上面读取了无标签数据 。 当准确率到80 就启用半监督。
if do_semi and plt_val_acc[-1] > acc_thres and i % semi_epoch==0:
semi_Loader = get_semi_loader(train_loader,no_label_Loader, backBone,classifier, device, conf_thres)
def get_semi_loader(train_loader, dataloader, backBone,classifier, device, thres):
semi_set = noLabDataset(train_loader.dataset, dataloader, backBone, classifier, device, thres)
dataloader = DataLoader(semi_set, batch_size=dataloader.batch_size,shuffle=True)
return dataloader
我们来看这个dataset怎么写的
class noLabDataset(Dataset):
def __init__(self,train_dataset, dataloader, backBone,classifier, device, thres=0.85):
super(noLabDataset, self).__init__()
self.transformers = sim_train_trans #增广 这个训练时用的
self.backBone = backBone
self.classifier = classifier #模型也要传入进来
self.device = device
self.thres = thres #这里置信度阈值 我设置的 0.99
x, y = self._model_pred(dataloader) #核心
self.x = np.concatenate((np.array(x),train_dataset.x),axis=0)
self.y = torch.cat(((torch.LongTensor(y),train_dataset.y)),dim=0)
def _model_pred(self, dataloader):
backBone = self.backBone
classifier = self.classifier
device = self.device
thres = self.thres
pred_probs = []
labels = []
x = []
y = []
with torch.no_grad():
for data in dataloader:
imgs = data[0].to(device)
feature = backBone(imgs)
pred = classifier(feature)
soft = torch.nn.Softmax(dim=1)
pred_p = soft(pred)
pred_max, preds = pred_p.max(1) #得到最大值 ,和最大值的位置 。 就是置信度和标签。
pred_probs.extend(pred_max.cpu().numpy().tolist())
labels.extend(preds.cpu().numpy().tolist())
for index, prob in enumerate(pred_probs):
if prob > thres:
x.append(dataloader.dataset[index][1])
y.append(labels[index])
return x, y
def __getitem__(self, index):
x = self.x[index]
x= self.transformers(x)
y = self.y[index]
return x, y
def __len__(self):
return len(self.x)
模型 传进来 ,用模型预测图片 如果置信度超过阈值 就记下来。 最后与训练集的数据cat起来 。在制作loader时进行打乱。 这样就得到了带有无标签数据的训练数据集。
然后照常训练即可。 每10个epoch更新一次这个数据集。
这个作业没有办法提交, 所以测试写了也没啥意义。 我的感觉是好难。 真的 ,准确率好难上去,我有时候很想知道我和那些大佬们的差距在哪里,为啥他们的准确率能高这么多,他们是怎么做到的。???而我根本没有办法让模型收敛。唉 ,难过。
想做的尝试是用res50进行训练 ,但是我没有卡呀没有卡。res50的bat只能设为8。训练一个epo要15分钟,令人绝望。