2021李宏毅作业hw3 --食物分类。对比出来的80准确率。

系列文章:

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后  这个模型几乎完全训练不动 

2021李宏毅作业hw3 --食物分类。对比出来的80准确率。_第1张图片

 这个原因我不知道是为什么 , 后面再解决吧。 我感觉很奇怪 反正。

而当你去掉这一项 就可以大概的训练起来了。  至少准确率能到30左右 

如果你选取一个合适的学习率和模型,比如我选的是res18 那么你大概能达到百分之五十的准确率。 

如果你加上半监督, 那么准确率也是一直在浮动, 最高到百分之60左右。 我也不知道怎么样才能做到更高了。 

转换思路,对比学习。 

        正值此时,李沐频道的朱老师发了一个对比学习的综述视频。 看了之后发现, 咦,对比学习不是正好可以用来解决这个问题吗? 拥有大量的无标签数据。 

        精调细选之后,挑取了simsaim作为我们使用的模型, 第一是因为他够新, 第二是因为他非常的简单,听说。 关于simsaim 来学习食物分类看下面这篇。

对比学习 ——simsiam 代码解析。:_亮子李的博客-CSDN博客

在git里 我也上传了自己用simsaim训练的代码。 用的时候需要改下数据的地址。 

经过simsaim之后我们能得到两个模型。 一个是提取特征的res18 模型, 一个是用来分类的classfier模型。 回来后我们可以丢弃classfier,只用那个res18的backbone。 其实就是进行一个抽特征之后线性验证的过程。  现在我们有了抽特征的模型 ,就相当于一个预训练的模型,然后要对分类头进行训练 。 

直接看过程吧。  注意复制simsaim的models和optimizer 两个模块 到这个项目里来。 

2021李宏毅作业hw3 --食物分类。对比出来的80准确率。_第2张图片

1 数据 

        

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的 ,没有变化。 

2 训练 

    训练方式和以前也有所不同。 主要的不同部分在于 提取特征和分类头分开了 。我们来看。

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了 。如果你直接一上来分类头是随机的情况下乱训练, 很容易让对比学习学到的模型崩溃。 所以在一定的准确率之前, 还不能让特征提取器乱动。 

        这样训练出来的准确率能达到百分之八十多一点点。  但是训练的非常慢

3半监督。 

   好吧 我承认, 我训练了半天 ,半监督根本没用上,当我尝试加入半监督后,准确率根本就上不去。 但还是自己辛辛苦苦写的。 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分钟,令人绝望。 

你可能感兴趣的:(李宏毅2022作业专题,深度学习,神经网络,python)