重新学习了一下 ResNet。。这作业平均一跑就是3、4个小时
题目大意是让你做异常检测(anomaly detection),即给你一些正常的图片,再让你测试图片是正常的还是异常的(可以理解为 2 分类问题,只不过其中一个类别是无限大的)
代码:https://www.kaggle.com/code/skyrainwind/hw8-anomaly-detection
题目分析
调一下 fcn 的结构(比如加一层),调一下训练次数就能过 medium
再把 fcn 换成 cnn,发现 performance 并没有明显提高,考虑再换成 resnet,就达到 strong baseline 了,其中因为 decoder 没调好还多 train 了一次
代码分析
anomaly detection 的基本思想就是训练一个 encoder,一个 decoder,使得图片等输入到 encoder(encoder 的输出一般是一个比较小的向量,直观来说就是降维、选出了关键影响因素) 和 decoder 之后,与原输入的差距越小越好。
在过 medium 的 fcn 中,我加了一层 fcn,并调整 encoder 的输出为一个 10 维向量(而非 3 维):
class fcn_autoencoder(nn.Module):
def __init__(self):
super(fcn_autoencoder, self).__init__()
self.encoder = nn.Sequential(
nn.Linear(64 * 64 * 3, 1024),
nn.ReLU(),
nn.Linear(1024, 512),
nn.ReLU(),
nn.Linear(512, 256),
nn.ReLU(),
nn.Linear(256, 64),
nn.ReLU(),
nn.Linear(64, 10)
)
self.decoder = nn.Sequential(
nn.Linear(10, 64),
nn.ReLU(),
nn.Linear(64, 256),
nn.ReLU(),
nn.Linear(256, 512),
nn.ReLU(),
nn.Linear(512, 1024),
nn.ReLU(),
nn.Linear(1024, 64 * 64 * 3),
nn.Tanh()
)
resnet(深度残差网络)在图像处理和 transformer 等领域都有着广泛应用,其核心是每一个 Residual_block 中的最后输出都加上输入,这个过程也叫 shortcut。而每个 Residual_block 都由好几个卷积层构成,这样可以有效避免产生网络退化的问题。在 transformer 中,encoder 和 decoder 都有在 attention 层之后的 add&norm 过程,这个 add 就是 shortcut 的过程。
在代码上的实现,就可以先实现一个 residual_block 包含几个 cnn 层,并加上 shortcut(值得注意的是,输出和对应的输入可能大小不同,需要额外 downsample 一下),在大的 resnet 中包含若干个小的 residual_block 作为 encoder 层,类似于一个“压缩”的过程,decoder 就是直接卷积层实现,注意一下最后直接连激活函数 tanh 即可,不用再 batch normalization 了!
卷积层的参数也要调一调,调的结果就是卷积层让图片长宽缩小一半,反卷积层扩大一倍。
class Residual_block(nn.Module):
def __init__(self, ic, oc, stride=1): # 当传入 stride = 2 时,会把图片长宽缩小一倍
super().__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(ic, oc, stride=stride, padding=1, kernel_size=3), # stride=2: (H,W) -> (H/2, W/2)
nn.BatchNorm2d(oc),
nn.ReLU(inplace=True)
)
self.conv2 = nn.Sequential(
nn.Conv2d(oc, oc, stride=1, padding=1, kernel_size=3), # (H,W) -> (H,W)
nn.BatchNorm2d(oc)
)
self.downsample = None # 让原来的 x 变成能和 forward(x) 相加的形状,包括 channel 和 (H,W) 都应相同
if((stride != 1) or (ic != oc)): # stride != 1 -> (H,W) 变小 ic != oc -> channel 不同
self.downsample = nn.Sequential(
nn.Conv2d(ic, oc, stride=stride, kernel_size=1),
nn.BatchNorm2d(oc)
)
def forward(self, x):
residual = x
x = self.conv1(x)
x = self.conv2(x)
if(self.downsample != None):
residual = self.downsample(residual)
x = x + residual
x = nn.ReLU(inplace = True)(x)
return x
class ResNet(nn.Module):
def __init__(self, block=Residual_block, num_layers = [2, 1, 1, 1]):
super().__init__()
self.preconv = nn.Sequential( # 3*64*64 --> 32*64*64
nn.Conv2d(3, 32, kernel_size=3, padding=1, stride=1, bias=False),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True)
)
def make_residual(block, ic, oc, num_layer, stride=1):
layers = []
layers.append(block(ic, oc, stride))
for i in range(num_layer-1):
layers.append(block(oc, oc))
return nn.Sequential(*layers)
self.layer0 = make_residual(block, ic=32, oc=64, num_layer=num_layers[0], stride=2)
self.layer1 = make_residual(block, ic=64, oc=128, num_layer=num_layers[1], stride=2)
self.layer2 = make_residual(block, ic=128, oc=128, num_layer=num_layers[2], stride=2)
self.layer3 = make_residual(block, ic=128, oc=64, num_layer=num_layers[3], stride=2)
self.fc = nn.Sequential(
nn.Flatten(), # 也可以用 .view(shape[0], -1)
nn.Dropout(0.2),
nn.Linear(64*4*4, 64),
nn.BatchNorm1d(64),
nn.ReLU(inplace = True)
)
# 关于 ConvTranspose2d 的介绍:https://blog.csdn.net/qq_36201400/article/details/112604740
# 大概就是在原图相邻的两个格子之间插 stride-1 个 0(这样原图就会变大了),padding <- kernel-padding-1
self.decoder = nn.Sequential(
nn.Linear(64, 64*4*4), # (64) -> (64*4*4)
nn.BatchNorm1d(64*4*4),
nn.ReLU(),
nn.Unflatten(1, (64, 4, 4)), # (64*4*4) -> (64, 4, 4)
nn.ConvTranspose2d(64,128,kernel_size=4,stride=2,padding=1), # (64,4,4) -> (128,8,8)
nn.BatchNorm2d(128),
nn.ReLU(),
nn.ConvTranspose2d(128,128,kernel_size=4,stride=2,padding=1), # (128,8,8) -> (128,16,16)
nn.BatchNorm2d(128),
nn.ReLU(),
nn.ConvTranspose2d(128,128,kernel_size=4,stride=2,padding=1), # (128,16,16) -> (128,32,32)
nn.BatchNorm2d(128),
nn.ReLU(),
nn.ConvTranspose2d(128,3,kernel_size=4,stride=2,padding=1), # (128,32,32) -> (3,64,64)
nn.Tanh()
)
def encoder(self, x):
x = self.preconv(x) # (3,64,64) -> (32,64,64)
x = self.layer0(x) # (32,64,64) -> (64,32,32) 且通过 resnet(shortcut) 实现,下同
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x) # (128,8,8) -> (64,4,4)
x = self.fc(x) # (64,4,4) -> (64*4*4) -> (64)
return x
def forward(self, x): # x : (3, 64, 64)
x = self.encoder(x)
x = self.decoder(x)
return x
标签:nn,李宏毅,self,oc,Detection,stride,64,2022,128
From: https://www.cnblogs.com/SkyRainWind/p/18023310