目录
写在前面的话 CSDN真的是'sb'中的'sb'软件, 辛辛苦苦写半天 我复制个东西过来 他就把前面的刷没了 还要我重头写????????????神经并b
------------------------------------------------------------------------------------------------------------------------------
2022李宏毅作业HW3 是食物的分类 ,但是我怎么尝试 再监督学习的模式下 准确率都达不到百分之60 .。半监督也感觉效果不明显。 所以 这次就想着对比学习能不能用来解决这个问题呢 。?看了一圈,感觉simsiam是对比学习里比较简单的一种方法,好像效果也不错。 所以来看一看这个东西是怎么玩的。
simsaim 是对比学习很新的文章了。 他的训练方式简单来说就是 ,一张图片 ,用不同的方式去增广后形成图片对 。 然后用一张去预测另一张。 不懂得可以看朱老师的视频。
1 : 事先准备 。
代码地址 :
下载解压。
直接在main函数的 运行 编辑配置中输入
注意 : 第二次运行可以删掉download
2 : 代码阅读。
神经网络的一个基本的框架就是 : 数据读取 , 模型载入, 训练,测试。 我们接下来根据这四块来看。
2.1: 数据读取
运行main文件 。
进入main 函数 。
是三个数据集的读取。
train_loader ,, memory_loader 和 test_loader。 train和memory 都是训练集的数据 他们的不同之处在于, 数据增广的方式不同。 train的增广是用来训练的 memory和test的增广都是用来测试的。由于在对比学习里, 数据增广是很重要的 ,所以这里看下数据增广的方式。
**args.aug_kwargs 里规定了图片大小是32. 以及这次用的是simsaim。
这里两个train 。 只有训练集的第一个train是true。 而训练集的增广方式如下
增广方式可以参考 官网
这里依次是 : 随机resize 然后剪切为输入大小, 也就是会随机取图片里的一块。
随机水平变换
0.8的概率调节亮度对比度和饱和度。
0.2概率灰度化
对于32的照片 不做高斯模糊。
转化为张量并标准化。
然后 对于一个输入 这里会做两次transform call可以让这个类像函数那样被调用。
对于 测试用的训练集 。也就是memory 是下面的增广方式。 而test也是下面的增广方式 。
如果输入是 224 就 先放大到256,然后中心裁剪224,之后标准化。
如果输入是32 就放大到36 再中心裁剪32 .后标准化。
用的是cifar10的数据。 其实也就相当于很普通的 读图片 然后增广, 加标签。
我们只要看getitem取出来的数据是什么就好 。
总结:数据部分 我们需要做一个数据集, 然后训练集的增广要返回两个结果。 当读取数据时,返回的是图片数据和标签数据。
2.2: 模型载入
这一部分我们来看模型 ,我们可以根据下面的伪代码来看模型长什么样子。 伪代码非常容易看懂。 aug就是增广嘛。 f来提特征,然后两个预测。 算loss 回传。
这句来获得模型。
backbone就是普通的res18 这里不需要预训练的模型 只需要初始模型 。
这个就是simsam的模型了 。 projector 是个三层的普通mlp 。 encoder 就是伪代码里的f了 而predictor就是伪代码里的h了 。 我们具体来看下loss 。
传说 simsaim的精髓就在于这个loss, 在于这个z.detach 也就是传说中的stop gradiant。 有了这个梯度停止, simsaim才能够训练的起来。 这时的simsaim就和k-means算法有点类似了。 说法很多 大家可以搜搜看。
其实我们可以看出来一点东西,在算loss时, p是预测值, z是标签,如果标签也要算梯度,两边就都在变了,参考我们平时的label都是不变的,确实z也不应该算梯度。
那么什么是stop gradiant呢 就是不算梯度的意思。比如
x = 2 y = 2**2 z = y+x z.grad = 5 y.detach() z.grad = 1
本来y是x的平方 求导等于4 所以z对x求导是5 然后不算y的梯度了 那么就只剩1了 。
这就是模型的全部了 ,输入两张图片 ,然后抽特征 预测 分别算loss
3 训练过程:
训练是非常普通的训练。
从测试集中抽loader 注意抽出的是两张图片 由不同transformers形成的。 之后过模型得到loss,梯度回传。 这里日志一直报错 我直接屏蔽了。
4 测试过程:
测试过程比较的关键。
这里是用knn算法进行测试的 ,关于knn 可以看
简单的说, 就是从众。 在一个大平面上有很多的点, 然后你就看离自己最近的k个点,他们的标签是啥, 然后选最多的那个当自己的标签。
注意 这里的net 只是backbone 也就是resnet 而 memory 就是训练数据 不过增广方式不一样 。还有训练数据 和k值 取200.
一些初始化和获取类别数。
获取大平面上的点。 从训练集抽数据, 然后获取他们的特征。
最后的feature_bank大小是49664*512 也就是将近50000条数据 每个数据都有512 维的特征。 然后做了一个转置。
抽取测试集的特征。
我们来看 knn是如果计算相似度的 ,也就是距离的。torch.mm表示矩阵的乘法。 我举个例子。
下面只是例子 ,真实数据需要归一化
a = [[1,2,3], [4,5,6]] b = [[1,2,3], [2,4,6], [3,6,9], [4,8,1]]
a有2个样本, b有4个样本。 他们的特征都是3维。 现在求a[0] 和b中哪些样本最相似。
就要让a[0]和b中每一个样本点乘 得到 14, 28, 42, 23。数越大表示越相似,也就越近。 所以我们让a和b的转置相乘,得到:
tensor([[14, 28, 42, 23],
[32, 64, 96, 62]])
我们发现第一排 就是a[0]的相似度, 每一列都是与b中样本的点乘结果。
所以这里的sim_matrix 就是一个512 * 49664大小的矩阵。 512 表示有512个样本, 49664 表示每个样本和所有点的乘积。
topk 表示取最大的值,和他们下标 这里取200个 我们就得到了离每一个样本,最近的那些点,他们的下标是多少。
feature_labels.expand(feature.size(0), -1) 之前的文章说过 ,是一个复制扩充。 -1表示不改变维度。 feature是50000维 扩充后变成512 *50000 (注意label和49664不相等,是因为loader舍弃了最后的一部分,但是没关系 , 本来就取不到这部分值)。
torch.gather 是按下标取值。
我们对标签按下标取值,得到了512 *200的矩阵, 每一行都表示这个样本距离最近的200个样本的标签。
看到后面就知道这个knn_t的作用了 。 作用就是 控制相似度的权重。 比如 一个更相似的 他的标签可以一个顶好几个不相似的。 那么顶几个呢 ? 就是t控制的了 。
我们需要先搞懂scatter函数 。说实话着实有点难。因为官网的scatter都很难理解了 ,何况这个和官网不一样
我们可以看到 官网的第三个参数是src 也就是数据源,而这里是value 。。。真是奇怪。
对于tensor.scatter函数 可以看 这篇
相信大家对scatter 都有了理解。 我们回来。
这里先创建一个 长是512 *200 = 102400 宽是10的向量。
而sim_labels的大小是 (102400,1) 这个scatter做了什么呢 ? 如下
也就是把每行标签对应得数字那一列变为1 ,如果这行特征得标签是1 就把第一个数变为1,这样子。 类似的有102400行。 得到onehot后 按我得想法, 就统计200行中哪一列的1最多呗。 比如前200行里 第3列的1对多, 就说明第一个样本最近的200个里,最多的标签是2 ,。我们看看他们怎么做的。
one_hot_label.view(feature.size(0), -1, classes)
这句可以理解。 变回512 *200*10 这样就可以统计各自的两百个了。
sim_weight.unsqueeze(dim=-1)
sim_weight 虽然在上面做了一点变换,但是我们其实不用管他,因为上面只是一种归一化的方式,我们依然可以把它看作最近 当前样本特征和两百个点特征的乘积。unsqueeze 表示扩充一维 在最后, sim_weight就变成了 512 *200 *1。 我们如何理解这个pred_score呢? 我们不要看512个样本。 我们只看一个样本。 对于一个样本。他的one_label是200*10 而sim_weight就是200 *1 特征的点乘结果,也就是200个相似度分数 。 从两行 看两百行 很显然 就是让各行的标签1 乘上那个相似度分数。 之后再对200这个维度求和,就得到了各个标签相似的分数的和。 维度1*10
看到这里我们明白了 。 这里的knn并不是简单的从众,他还要看影响力。 更相似的样本,他的标签对我们的结果的影响力更大。 这里相当于对标签做了一个加权求和。
回到512维 我们得到了512*10的矩阵 表示512个样本的各个标签的相似度分数 我们只要argsort就可以得到最大值的下标啦。 np.argsort这个函数可以对向量排序 然后返回他们原来的下标 des 表示可以降序。
得到标签 , 回到原来的knn
这里是计算top1 我估计如果计算top5 估计就是 target in labels[:,4]了 得到预测标签后准确率久很好算了。
5 :线性验证
继续跟着主函数走 。 可以看到一堆保存的步骤。 然后进入linear_eval函数。 我猜测是用backbone抽特征然后直接预测结果的函数。
先读取训练集和测试集, 然后 model是resnet 一个分类器是 一个全连接。 我好奇的是为什么不直接把backbone最后一层的恒等映射改为这个分类器呢 ?
载入模型
k长这个样子 取出那些以backb开头的层 就是resnet的层。 然后去掉前面9个字母 就是resnet的名字。
定义优化器和loss 最下面这个averagemeter是啥呀
查了一下 就是一个类似于队列这种的 数据结构。 然后可以更新 关键是可以求平均。
然后定义好后 就是一个普通的训练过程了 。 值得注意的是 model是eval模型 也就是他是冻住的,参数不改变。而classfier是可以改变的, 梯度回传也只回传分类头的梯度, 这里就只训练分类器。
普通的测试。
6 : 用自己数据集进行对比学习。
路走远了,别忘了开始的方向。 我们是用对比学习解决食物分类的问题的。
我们要做的有几件事情。
第一: 改数据集 :
把它原来的三个数据集全#了。
然后 加入自己的数据集。 使用他的增广方式。 但在增广前 需要在 dataset的get里加 topil 因为他的增广里没有这个。
hw3食物分类有三个数据集:
一个有标签训练集 我用来当memory
一个无标签训练集 我用来当train
一个验证集 我用来测试。
2 改变batch_size和图片大小。
在这个文件里改batch 你会发现这个对比学习的模型 出奇的占内存,当我图片大小为224时,我的batch只能设置为32.
main函数里改imagesize 为自己的。
点运行。O了 。 然后发现效果并不是很好。。。。。