第一次写博客,才刚开始学习深度学习,轻喷(有问题我会及时更正!!!)
个人理解:
在一些类内差异小、但影响因素较大的图像分类任务中,使用全局的CNN特征可能会缺少一些不变性,为了消除这些干扰因素,作者提出了Bilinear CNN Model,比如说:
如果提取的特征和空间位置关联过强,可能也会导致分类错误,例如:
可能我学习到一个船的特征:由船身和船帆组成(学习到的是船帆在船身上方),而当我给一张由一个完整船和另一个只有船帆的图片,那它可能就不会认为它是船(此时一个船的船帆在空间位置上在另一个船的船身的下方),因为它学习到的是船帆在船身的上方。
1、其他的一些方法:
基于“局部模型”的方法:
(1)手工特征
我们可以使用手工的方法来设计局部的特征(避免使用全局的特征),然后再拿去做“模式匹配”,例如:
缺点:显而易见的是,由于是手工设计出来的特征,它很可能对于最后的分类任务而言并不是最优的结果,最好的特征。
(2)“局部的CNN”模型
对于鸟的这种细分类任务,我们除了可以提取框住整个鸟的的特征外,还可以提取其它候选框的特征,比如说增加鸟的头部和身子的候选框的特征,加上全部鸟的候选框特征一起来(整个鸟、鸟头部、鸟身子)作为该图像的特征。
缺点:这种局部位置肯定也需要标注信息,对所有的数据集进行标注任务无疑是非常耗时耗力的工作。
使用本身具有一定不变性的特征:
(3)例如使用sift特征(如果需要讲解sift特征可以留言)
sift:
论文中给出的结论是这种方法相比基于“局部模型”的方法会略差一点(我没有去查阅其它相关资料)。
2、Bilinear CNN Model
Bilinear model它由两个基于cnn的特征提取器组成,在图像的每个位置使用外积将其输出相乘,并跨位置合并得到一个图像描述符。 外积捕捉特征通道之间的成对相关性。
论文中说是由双通道理论得来的灵感(形式上好像差不多,但是我真不知道有什么一样,强行往上靠吗?),人类视觉有一个“where”和“what”通道,简单理解就是一个处理物体在哪,一个处理物体的形状等一些信息:
其它的就不多说,直接上这个公式理解:
对于一张图片,有两个CNN特征提取器为和
,去除最后的池化层和全连接层,假设分别可以得到
的特征,那每个特征有
=144个位置,一个位置外积可以得到
的特征,144个就有144个
特征,对144个进行求和池化就只有
维的特征,再经过拉直、sqrt等等送往全连接层进行分类。(有错误欢迎指正)
双线性模型:,其它公式就不敲了,直接放图片了:
它的端到端的训练:
3、代码实现篇
以resnet50为特征提取器为例,在CUB-200数据集上实验:
CUB200:
对于数据集下载划分等等网上有很多资源,如果需要我可以后期再续写。
数据预处理和加载:
def load_path(datatype):
if datatype == 'CUB':
# 换成自己的数据集的位置
path = 'E:/pycharm/zDatafile/CUB-data/CUB_200_2011/'
ROOT_TRAIN = path + 'dataset/train/'
ROOT_TEST = path + 'dataset/test/'
return ROOT_TRAIN, ROOT_TEST
def train_data_load(datatype):
data_transform = transforms.Compose([
transforms.Resize((448,448)),
# transforms.RandomSizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(degrees=(-10, +10)),
#transforms.RandomCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.4856, 0.4994, 0.4324], std=[0.1817, 0.1811, 0.1927])
])
root_train,_ = load_path(datatype)
train_dataset = torchvision.datasets.ImageFolder(root_train,
transform=data_transform)
CLASS = train_dataset.class_to_idx
train_loader = torch.utils.data.DataLoader(train_dataset,
batch_size=opt.BATCH_SIZE,
shuffle=True)
return train_loader
def test_data_load(datatype):
data_transform = transforms.Compose([
transforms.Resize((448, 448)),
transforms.CenterCrop(448),
transforms.ToTensor(),
transforms.Normalize(mean=[0.4856, 0.4994, 0.4324], std=[0.1817, 0.1811, 0.1927])
])
_,root_test = load_path(datatype)
test_dataset = torchvision.datasets.ImageFolder(root_test,
transform=data_transform)
CLASS = test_dataset.class_to_idx
test_loader = torch.utils.data.DataLoader(test_dataset,
batch_size=opt.BATCH_SIZE,
shuffle=False)
return test_loader
模型定义:
class BCNNresnet(nn.Module):
def __init__(self):
super(BCNNresnet, self).__init__()
# 2个卷积层和1个最大池化层
self.features = torchvision.models.resnet50(pretrained=True)
self.features = nn.Sequential(*list(self.features.children())[:-2]) # 去除最后的pool层
# 冻结以前的所有层
for param in self.features.parameters():
param.requres_grad = False
self.fc = nn.Linear(2048*2048, 200)
def forward(self, x):
x = self.features(x)
#print("x:",x.shape)
x = torch.einsum('imjk,injk->imn',x,x)/196
x = x.view(-1, 2048*2048)
#print("shape of x before pooling:", x.shape)
x = torch.mul(torch.sign(x),torch.sqrt(torch.abs(x)+1e-12))
x = F.normalize(x, p=2, dim=1)
x = self.fc(x)
return x
训练:
整个代码结构如图,BiP存放模型,config是存放超参数设置的,datasets为数据加载和预处理,logger为保存日志,mian为训练,训练代码和保存模型基本都大同小异,想想这里就不放了,我之前还跑过一个小点的模型,使用的是更小一点的vgg(small vgg),如下:
4、缺点(不足)
下面简单谈谈bilinear model的缺点:
(1)论文中的大致说法:使用不同的特征提取器比使用相同的两个特征提取器结果上要更好一点,但是没有办法搜索出最适合的两个特征提取器,那现在所谓的最佳的组合可能只是一个次优的结果。
(2)一个很显然的问题是,参数量巨大,如果最后提取的特征是维的,拉直后也就是262144,如果送往1024维的全连接层,就会有2亿多的参数,计算量和存储要求都有巨大的挑战,这也是后面会有很多在此基础上进行改进的论文,有空我们之后会慢慢说一些改进后的论文和实现,比如说compact bilinear pooling,FBC等等。
----------------------------------------------------------------分割线------------------------------------------------------- 第一次写,没有经验,可能没有太多的专业描述,下次也会多备注一些相关理论和论文的出处,然后这也只是我个人的一些理解,也欢迎各位大佬指正。说明:部分图片来源于网络,如有侵权请告知,会立即删除。转载请注明,谢谢。