Parte 1 lectura en papel y aprendizaje en video
1 ShuffleNet V1 y V2
1.1 Estructura de la red
Destacados: Se propone la idea de channel shuffle , el cual está compuesto por GConv y DWConv, y tiene menor tiempo de cálculo en dispositivos móviles
reproducción aleatoria de canales : mejora el intercambio de información entre canales
ShuffleNet V1 :
Tabla de estructura de ShuffleNet:
Cuando solo se consideran cálculos teóricos puros, ShuffleNet requiere menos cálculo:
Sobre la base de V1, V2 señaló que la complejidad computacional no solo puede observar los FLOP y propuso cuatro pautas para diseñar redes eficientes y propuso un nuevo diseño de bloque.
Los FLOP no son un indicador directo para medir la cantidad de cálculo. El costo de tiempo causado por el acceso a la memoria, el nivel paralelo, la memoria, el tamaño y el costo también afectarán la cantidad de cálculo:
4 pautas para diseñar una red eficiente :
- Cuando los FLOP permanecen sin cambios, el MAC es el más pequeño cuando la matriz de características de entrada de la capa convolucional es igual a la matriz de características de salida (MAC es el costo de acceso a la memoria)
- Cuando los FLOP permanecen sin cambios, el MAC también aumentará cuando los grupos de GConv aumenten
- Cuanto más fragmentado es el diseño de la red, más lenta es la velocidad
- El impacto de las operaciones Element-wise es irreversible (Element-wise se refiere a las operaciones realizadas en cada elemento, incluida la función de activación ReLU, las operaciones de adición AddTensor, Addbias, etc., estas operaciones tienen FLOP pequeños pero MAC grandes)
Resumen :
- Use una convolución más balanceada (la relación de entrada a salida debe ser lo más cercana posible a 1)
- Preste atención al costo de operación de la convolución de grupo
- Reducir la fragmentación de la red
- Reducir el uso de operaciones basadas en elementos
La estructura de shuffleNet, donde los dos primeros son V1 y los dos últimos son V2:
(c) La estructura reduce la operación de fragmentación. No hay unidad en la rama izquierda, y solo se realiza ReLU en la rama derecha. Los canales de entrada y salida son consistentes, y la convolución de grupo se descarta.
(d) En la estructura, los canales de salida de las ramas izquierda y derecha son consistentes con el canal de entrada.Después de la operación de empalme, el canal de salida final es el doble del canal de entrada.
1.2 Construir ShuffleNet V2 basado en PyTorch
Enlace de código: (colab) ShuffleNetV2
# ShuffleNet V2
# 划分并组合
def channel_shuffle(x:Tensor,groups:int)->Tensor:
batch_size,num_channels,height,width = x.size()
channels_per_group = num_channels//groups
# [batch_size,num_channels,height,width]->[batch_size,groups,channels_per_group,height,width]
x = x.view(batch_size,groups,channels_per_group,height,width)
# 转换成在内存中连续的数据
x = torch.transpose(x,1,2).contiguous()
# flatten
x = x.view(batch_size,-1,height,width)
return x
class InvertedResidual(nn.Module):
def __init__(self,input_c:int,output_c:int,stride:int):
super(InvertedResidual,self).__init__()
if stride not in [1,2]:
raise ValueError("illegal stride value.")
self.stride = stride
assert output_c%2==0
branch_features = output_c//2
# 当stride为1时,input_channel应该是branch_features的两倍
# <<是位运算
assert (self.stride!=1) or (input_c==branch_features<<1)
# 右分支
if self.stride == 2:
self.branch1 = nn.Sequential(
self.depthwise_conv(input_c,input_c,kernel_s=3,stride=self.stride,padding=1),
nn.BatchNorm2d(input_c),
nn.Conv2d(input_c,branch_features,kernel_size=1,stride=1,padding=0,bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True)
)
# 左分支没有操作
else:
self.branch1 = nn.Sequential()
self.branch2 = nn.Sequential(
nn.Conv2d(input_c if self.stride>1 else branch_features,branch_features,kernel_size=1,stride=1,padding=0,bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True),
self.depthwise_conv(branch_features,branch_features,kernel_s=3,stride=self.stride,padding=1),
nn.BatchNorm2d(branch_features),
nn.Conv2d(branch_features,branch_features,kernel_size=1,stride=1,padding=0,bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True)
)
@staticmethod
def depthwise_conv(input_c:int,output_c:int,kernel_s:int,stride:int=1,padding:int=0,bias:bool=False)->nn.Conv2d:
return nn.Conv2d(in_channels=input_c,out_channels=output_c,kernel_size=kernel_s,
stride=stride,padding=padding,bias=bias,groups=input_c)
def forward(self,x:Tensor)->Tensor:
if self.stride==1:
x1,x2 = x.chunk(2,dim=1)
out = torch.cat((x1,self.branch2(x2)),dim=1)
else:
out = torch.cat((self.branch1(x),self.branch2(x)),dim=1)
out = channel_shuffle(out,2)
return out
class ShuffleNetV2(nn.Module):
def __init__(self,
stages_repeats:List[int],
stages_out_channels:List[int],
num_classes:int=1000,
inverted_residual:Callable[...,nn.Module]=InvertedResidual):
super(ShuffleNetV2,self).__init__()
if len(stages_repeats)!=3:
raise ValueError("expected stages_repeats as list of 3 positive ints")
if len(stages_out_channels)!=5:
raise ValueError("expected stages_out_channels as list of 5 positive ints")
self._stage_out_channels = stages_out_channels
# input RGB image
input_channels = 3
output_channels = self._stage_out_channels[0]
self.conv1 = nn.Sequential(
nn.Conv2d(input_channels,output_channels,kernel_size=3,stride=2,padding=1,bias=False),
nn.BatchNorm2d(output_channels),
nn.ReLU(inplace=True)
)
input_channels = output_channels
self.maxpool = nn.MaxPool2d(kernel_size=3,stride=2,padding=1)
# Static annotations for mypy
self.stage2: nn.Sequential
self.stage3: nn.Sequential
self.stage4: nn.Sequential
stage_names = ["stage{}".format(i) for i in [2,3,4]]
for name, repeats, output_channels in zip(stage_names,stages_repeats,self._stage_out_channels[1:]):
seq = [inverted_residual(input_channels,output_channels,2)]
for i in range(repeats-1):
seq.append(inverted_residual(output_channels,output_channels,1))
setattr(self,name,nn.Sequential(*seq))
input_channels = output_channels
output_channels = self._stage_out_channels[-1]
self.conv5 = nn.Sequential(
nn.Conv2d(input_channels,output_channels,kernel_size=1,stride=1,padding=0,bias=False),
nn.BatchNorm2d(output_channels),
nn.ReLU(inplace=True)
)
self.fc = nn.Linear(output_channels,num_classes)
def _forward_impl(self,x:Tensor)->Tensor:
x = self.conv1(x)
x = self.maxpool(x)
x = self.stage2(x)
x = self.stage3(x)
x = self.stage4(x)
x = self.conv5(x)
x = x.mean([2,3])
x = self.fc(x)
return x
def forward(self,x:Tensor)->Tensor:
return self._forward_impl(x)
def shufflenet_v2_x0_5(num_classes=1000):
model = ShuffleNetV2(stages_repeats=[4,8,4],stages_out_channels=[24,48,96,192,1024],num_classes=num_classes)
return model
def shufflenet_v2_x1_0(num_classes=1000):
model = ShuffleNetV2(stages_repeats=[4,8,4],stages_out_channels=[24,116,232,464,1024],num_classes=num_classes)
return model
def shufflenet_v2_x1_5(num_classes=1000):
model = ShuffleNetV2(stages_repeats=[4,8,4],stages_out_channels=[24,176,352,704,1024],num_classes=num_classes)
return model
def shufflenet_v2_x2_0(num_classes=1000):
model = ShuffleNetV2(stages_repeats=[4,8,4],stages_out_channels=[24,244,488,976,2048],=num_classes)
return model
2 EfficientNet V3
2.1 Estructura de la red
Aspectos destacados: explore simultáneamente el impacto de la resolución de entrada, la profundidad y el ancho de la red
- Aumentar la profundidad puede extraer características cada vez más complejas, pero enfrentará el problema de la desaparición del gradiente y el entrenamiento difícil
- Aumentar el ancho puede obtener características de grano fino más altas, lo que es más fácil de entrenar, pero es difícil aprender características más profundas.
- El aumento de la resolución de la entrada de la imagen a la red puede potencialmente obtener características más finas, pero la ganancia de precisión disminuirá y las imágenes de gran resolución aumentarán la cantidad de cálculo.
- Si aumenta la profundidad, el ancho y la resolución de la imagen de entrada al mismo tiempo, es posible lograr mejores resultados
La estructura de la red EfficientNet se obtiene mediante técnicas de búsqueda web
MBConv :
- Primero use la convolución 1*1 para aumentar la dimensión, y el número de núcleos de convolución es n veces mayor que el del canal de entrada
- Cuando n=1, no se usa la convolución 1*1, es decir, la estructura MBConv de la etapa 2 no tiene una convolución 1*1 para la mejora de la dimensión.
- Existe una conexión de acceso directo si y solo si la matriz de características de entrada tiene la misma forma que la matriz de características de salida
SE módulo :
El módulo SE consiste en una agrupación promedio global y dos capas completamente conectadas. El número de nodos en la primera capa completamente conectada es 1/4 de la matriz de características MBConv, y se utiliza la función de activación Swish. Los nodos de la segunda capa completamente conectada capa El número son los canales de la salida de la matriz de características por la capa convolucional DW, y se utiliza la función de activación Sigmoide.
Configuración de parámetros de red de B0 a B7:
Efecto:
Experiencia real: alta precisión, pocos parámetros, pero ocupa más memoria GPU
2.2 Cree EfficientNet V3 basado en PyTorch
Enlace de código: (colab) EfficientNet V3
# EfficientNet V3
def _make_divisible(ch,divisor=8,min_ch=None):
if min_ch is None:
min_ch = divisor
new_ch = max(min_ch,int(ch+divisor/2)//divisor*divisor)
if new_ch<0.9*ch:
new_ch += divisor
return new_ch
def drop_path(x, drop_prob: float = 0., training: bool = False):
if drop_prob==0. or not training:
return x
keep_prob = 1-drop_prob
shape = (x.shape[0],)+(1,)*(x.ndim-1) # 适用于多种维度
random_tensor = keep_prob+torch.rand(shape,dtype=x.dtype,device=x.device)
random_tensor.floor_()
output = x.div(keep_prob)*random_tensor
return output
class DropPath(nn.Module):
def __init__(self,drop_prob=None):
super(DropPath,self).__init__()
self.drop_prob = drop_prob
def forward(self,x):
return drop_path(x,self.drop_prob,self.training)
class ConvBNActivation(nn.Sequential):
def __init__(self,
in_planes:int,
out_planes:int,
kernel_size:int=3,
stride:int=1,
groups:int=1,
norm_layer:Optional[Callable[..., nn.Module]]=None,
activation_layer:Optional[Callable[...,nn.Module]]=None):
padding=(kernel_size-1)//2
if norm_layer is None:
norm_layer = nn.BatchNorm2d
if activation_layer is None:
activation_layer = nn.SiLU
super(ConvBNActivation, self).__init__(nn.Conv2d(in_channels=in_planes,
out_channels=out_planes,
kernel_size=kernel_size,
stride=stride,
padding=padding,
groups=groups,
bias=False),
norm_layer(out_planes),
activation_layer())
class SqueezeExcitation(nn.Module):
def __init__(self,
input_c:int,
expand_c:int,
squeeze_factor:int=4):
super(SqueezeExcitation,self).__init__()
squeeze_c = input_c//squeeze_factor
self.fc1 = nn.Conv2d(expand_c,squeeze_c, 1)
self.ac1 = nn.SiLU()
self.fc2 = nn.Conv2d(squeeze_c,expand_c,1)
self.ac2 = nn.Sigmoid()
def forward(self,x:Tensor)->Tensor:
scale = F.adaptive_avg_pool2d(x,output_size=(1,1))
scale = self.fc1(scale)
scale = self.ac1(scale)
scale = self.fc2(scale)
scale = self.ac2(scale)
return scale*x
class InvertedResidualConfig:
def __init__(self,
kernel:int, # 3or5
input_c:int,
out_c:int,
expanded_ratio:int, # 1or6
stride:int, # 1or2
use_se:bool, # True
drop_rate:float,
index:str, # 1a,2a,2b...
width_coefficient:float):
self.input_c = self.adjust_channels(input_c,width_coefficient)
self.kernel = kernel
self.expanded_c = self.input_c*expanded_ratio
self.out_c = self.adjust_channels(out_c,width_coefficient)
self.use_se = use_se
self.stride = stride
self.drop_rate = drop_rate
self.index = index
@staticmethod
def adjust_channels(channels:int,width_coefficient:float):
return _make_divisible(channels*width_coefficient,8)
class InvertedResidual(nn.Module):
def __init__(self,cnf:InvertedResidualConfig,norm_layer:Callable[...,nn.Module]):
super(InvertedResidual,self).__init__()
if cnf.stride not in [1,2]:
raise ValueError("illegal stride value.")
self.use_res_connect = (cnf.stride==1andcnf.input_c==cnf.out_c)
layers = OrderedDict()
activation_layer = nn.SiLU
# expand
if cnf.expanded_c!=cnf.input_c:
layers.update({"expand_conv":ConvBNActivation(cnf.input_c,
cnf.expanded_c,
kernel_size=1,
norm_layer=norm_layer,
activation_layer=activation_layer)})
# depthwise
layers.update({"dwconv":ConvBNActivation(cnf.expanded_c,
cnf.expanded_c,
kernel_size=cnf.kernel,
stride=cnf.stride,
groups=cnf.expanded_c,
norm_layer=norm_layer,
activation_layer=activation_layer)})
if cnf.use_se:
layers.update({"se":SqueezeExcitation(cnf.input_c,cnf.expanded_c)})
# project
layers.update({"project_conv":ConvBNActivation(cnf.expanded_c,
cnf.out_c,
kernel_size=1,
norm_layer=norm_layer,
activation_layer=nn.Identity)})
self.block = nn.Sequential(layers)
self.out_channels = cnf.out_c
self.is_strided = cnf.stride>1
# 只有在使用shortcut连接时才使用dropout层
if self.use_res_connect and cnf.drop_rate>0:
self.dropout = DropPath(cnf.drop_rate)
else:
self.dropout = nn.Identity()
def forward(self,x:Tensor)->Tensor:
result = self.block(x)
result = self.dropout(result)
if self.use_res_connect:
result += x
return result
class EfficientNet(nn.Module):
def __init__(self,
width_coefficient:float,
depth_coefficient:float,
num_classes:int=1000,
dropout_rate:float=0.2,
drop_connect_rate:float=0.2,
block:Optional[Callable[...,nn.Module]]=None,
norm_layer:Optional[Callable[...,nn.Module]]=None):
super(EfficientNet,self).__init__()
# kernel_size,in_channel,out_channel,exp_ratio,strides,use_SE,drop_connect_rate,repeats
default_cnf = [[3,32,16,1,1,True,drop_connect_rate,1],
[3,16,24,6,2,True,drop_connect_rate,2],
[5,24,40,6,2,True,drop_connect_rate,2],
[3,40,80,6,2,True,drop_connect_rate,3],
[5,80,112,6,1,True,drop_connect_rate,3],
[5,112,192,6,2,True,drop_connect_rate,4],
[3,192,320,6,1,True,drop_connect_rate,1]]
def round_repeats(repeats):
return int(math.ceil(depth_coefficient*repeats))
if block is None:
block = InvertedResidual
if norm_layer is None:
norm_layer = partial(nn.BatchNorm2d,eps=1e-3,momentum=0.1)
adjust_channels = partial(InvertedResidualConfig.adjust_channels,width_coefficient=width_coefficient)
# build inverted_residual_setting
bneck_conf = partial(InvertedResidualConfig,width_coefficient=width_coefficient)
b = 0
num_blocks = float(sum(round_repeats(i[-1]) for i in default_cnf))
inverted_residual_setting = []
for stage,args in enumerate(default_cnf):
cnf = copy.copy(args)
for i in range(round_repeats(cnf.pop(-1))):
if i>0:
cnf[-3] = 1
cnf[1] = cnf[2]
cnf[-1] = args[-2]*b/num_blocks #更新dropout
index = str(stage+1)+chr(i+97) #1a,2a,2b...
inverted_residual_setting.append(bneck_conf(*cnf,index))
b += 1
# create layers
layers = OrderedDict()
# first conv
layers.update({"stem_conv":ConvBNActivation(in_planes=3,
out_planes=adjust_channels(32),
kernel_size=3,
stride=2,
norm_layer=norm_layer)})
# building inverted residual blocks
for cnf in inverted_residual_setting:
layers.update({cnf.index: =block(cnf,norm_layer)})
# build top
last_conv_input_c = inverted_residual_setting[-1].out_c
last_conv_output_c = adjust_channels(1280)
layers.update({"top":ConvBNActivation(in_planes=last_conv_input_c,
out_planes=last_conv_output_c,
kernel_size=1,
norm_layer=norm_layer)})
self.features = nn.Sequential(layers)
self.avgpool = nn.AdaptiveAvgPool2d(1)
classifier = []
if dropout_rate>0:
classifier.append(nn.Dropout(p=dropout_rate,inplace=True))
classifier.append(nn.Linear(last_conv_output_c,num_classes))
self.classifier = nn.Sequential(*classifier)
# 权重初始化
for m in self.modules():
if isinstance(m,nn.Conv2d):
nn.init.kaiming_normal_(m.weight,mode="fan_out")
if m.bias is not None:
nn.init.zeros_(m.bias)
elif isinstance(m,nn.BatchNorm2d):
nn.init.ones_(m.weight)
nn.init.zeros_(m.bias)
elif isinstance(m,nn.Linear):
nn.init.normal_(m.weight,0,0.01)
nn.init.zeros_(m.bias)
def _forward_impl(self,x:Tensor)->Tensor:
x = self.features(x)
x = self.avgpool(x)
x = torch.flatten(x,1)
x = self.classifier(x)
return x
def forward(self,x:Tensor)->Tensor:
return self._forward_impl(x)
def efficientnet_b0(num_classes=1000):
# 224x224
return EfficientNet(width_coefficient=1.0,depth_coefficient=1.0,dropout_rate=0.2,num_classes=num_classes)
def efficientnet_b1(num_classes=1000):
# 240x240
return EfficientNet(width_coefficient=1.0,depth_coefficient=1.1,dropout_rate=0.2,num_classes=num_classes)
def efficientnet_b2(num_classes=1000):
# 260x260
return EfficientNet(width_coefficient=1.1,depth_coefficient=1.2,dropout_rate=0.3,num_classes=num_classes)
def efficientnet_b3(num_classes=1000):
# 300x300
return EfficientNet(width_coefficient=1.2,depth_coefficient=1.4,dropout_rate=0.3,num_classes=num_classes)
def efficientnet_b4(num_classes=1000):
# 380x380
return EfficientNet(width_coefficient=1.4,depth_coefficient=1.8,dropout_rate=0.4,num_classes=num_classes)
def efficientnet_b5(num_classes=1000):
# 456x456
return EfficientNet(width_coefficient=1.6,depth_coefficient=2.2,dropout_rate=0.4,num_classes=num_classes)
def efficientnet_b6(num_classes=1000):
# 528x528
return EfficientNet(width_coefficient=1.8,depth_coefficient=2.6,dropout_rate=0.5,num_classes=num_classes)
def efficientnet_b7(num_classes=1000):
# 600x600
return EfficientNet(width_coefficient=2.0,depth_coefficient=3.1,dropout_rate=0.5,num_classes=num_classes)
3 Transformador里的autoatención de cabezales múltiples
Transformador: Originalmente fue propuesto para el campo de la PNL, reemplazando la red secuencial anterior que no se podía paralelizar y tenía una memoria corta. En ausencia de limitaciones de hardware, Transformer puede lograr una longitud de memoria ilimitada.
El valor obtenido por la multiplicación de puntos es muy grande, y después de softmax, el gradiente se volverá muy pequeño
autoatención:
autoatención de múltiples cabezas:
Empalme de la cabeza:
Posteriormente, la matriz empalmada debe procesarse para garantizar que la longitud de los vectores de entrada y salida permanezca sin cambios. El mecanismo de atención de múltiples cabezas puede combinar la información aprendida de diferentes partes de la cabeza para generar características más significativas.La esencia de la cabeza múltiple son varios cálculos de atención independientes, que tienen la función de integración y pueden evitar el sobreajuste.
Ejercicio de código parte 2
1 Usando el modelo VGG para batallas de perros y gatos
1.1 Biblioteca de llamadas
import numpy as np
import matplotlib.pyplot as plt
import os
import torch
import torch.nn as nn
import torchvision
from torchvision import models,transforms,datasets
import time
import json
# 判断是否存在GPU设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('Using gpu: %s '%torch.cuda.is_available())
1.2 Descargar datos y descomprimirlos
# 下载数据
! wget http://fenggao-image.stor.sinaapp.com/dogscats.zip
! unzip dogscats.zip
1.3 Procesamiento de datos
# 数据处理
normalize = transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])
vgg_format = transforms.Compose([transforms.CenterCrop(224),transforms.ToTensor(),normalize])
data_dir = './dogscats'
dsets = {x:datasets.ImageFolder(os.path.join(data_dir,x),vgg_format) for x in ['train','valid']}
dset_sizes = {x:len(dsets[x]) for x in ['train','valid']}
dset_classes = dsets['train'].classes
# 查看dsets的一些属性
print(dsets['train'].classes)
print(dsets['train'].class_to_idx)
print(dsets['train'].imgs[:5])
print('dset_sizes: ', dset_sizes)
loader_train = torch.utils.data.DataLoader(dsets['train'], batch_size=64, shuffle=True, num_workers=6)
loader_valid = torch.utils.data.DataLoader(dsets['valid'], batch_size=5, shuffle=False, num_workers=6)
count = 1
for data in loader_valid:
print(count,end='\n')
if count==1:
inputs_try,labels_try = data
count+=1
print(labels_try)
print(inputs_try.shape)
# 显示图片
def imshow(inp, title=None):
inp = inp.numpy().transpose((1,2,0))
mean = np.array([0.485,0.456,0.406])
std = np.array([0.229,0.224,0.225])
inp = np.clip(std*inp+mean,0,1)
plt.imshow(inp)
if title is not None:
plt.title(title)
plt.pause(0.001)
# 显示labels_try的5张图片,即valid里第一个batch的5张图片
out = torchvision.utils.make_grid(inputs_try)
imshow(out,title=[dset_classes[x] for x in labels_try])
1.4 Crear un modelo
# 创建模型
model_vgg = models.vgg16(pretrained=True)
with open('./imagenet_class_index.json') as f:
class_dict = json.load(f)
dic_imagenet = [class_dict[str(i)][1] for i in range(len(class_dict))]
inputs_try,labels_try = inputs_try.to(device),labels_try.to(device)
model_vgg = model_vgg.to(device)
outputs_try = model_vgg(inputs_try)
print(outputs_try)
print(outputs_try.shape)
m_softm = nn.Softmax(dim=1)
probs = m_softm(outputs_try)
vals_try,pred_try = torch.max(probs,dim=1)
print('prob sum: ',torch.sum(probs,1))
print('vals_try: ',vals_try)
print('pred_try: ',pred_try)
print([dic_imagenet[i] for i in pred_try.data])
imshow(torchvision.utils.make_grid(inputs_try.data.cpu()),
title=[dset_classes[x] for x in labels_try.data.cpu()])
# 修改最后一层,冻结前面层的参数
print(model_vgg)
model_vgg_new = model_vgg;
for param in model_vgg_new.parameters():
param.requires_grad = False
model_vgg_new.classifier._modules['6'] = nn.Linear(4096,2)
model_vgg_new.classifier._modules['7'] = torch.nn.LogSoftmax(dim=1)
model_vgg_new = model_vgg_new.to(device)
print(model_vgg_new.classifier)
Mostrar modelo:
VGG(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU(inplace=True)
(2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU(inplace=True)
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(6): ReLU(inplace=True)
(7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(8): ReLU(inplace=True)
(9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU(inplace=True)
(12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(13): ReLU(inplace=True)
(14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(15): ReLU(inplace=True)
(16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(18): ReLU(inplace=True)
(19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(20): ReLU(inplace=True)
(21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(22): ReLU(inplace=True)
(23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(25): ReLU(inplace=True)
(26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(27): ReLU(inplace=True)
(28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(29): ReLU(inplace=True)
(30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
(classifier): Sequential(
(0): Linear(in_features=25088, out_features=4096, bias=True)
(1): ReLU(inplace=True)
(2): Dropout(p=0.5, inplace=False)
(3): Linear(in_features=4096, out_features=4096, bias=True)
(4): ReLU(inplace=True)
(5): Dropout(p=0.5, inplace=False)
(6): Linear(in_features=4096, out_features=1000, bias=True)
)
)
Sequential(
(0): Linear(in_features=25088, out_features=4096, bias=True)
(1): ReLU(inplace=True)
(2): Dropout(p=0.5, inplace=False)
(3): Linear(in_features=4096, out_features=4096, bias=True)
(4): ReLU(inplace=True)
(5): Dropout(p=0.5, inplace=False)
(6): Linear(in_features=4096, out_features=2, bias=True)
(7): LogSoftmax(dim=1)
)
1.5 Entrenamiento y Pruebas
# 训练并测试全连接层
criterion = nn.NLLLoss()
# 学习率
lr = 0.001
# 随机梯度下降
optimizer_vgg = torch.optim.SGD(model_vgg_new.classifier[6].parameters(),lr=lr)
def train_model(model,dataloader,size,epochs=1,optimizer=None):
model.train()
for epoch in range(epochs):
running_loss = 0.0
running_corrects = 0
count = 0
for inputs,classes in dataloader:
inputs = inputs.to(device)
classes = classes.to(device)
outputs = model(inputs)
loss = criterion(outputs,classes)
optimizer = optimizer
optimizer.zero_grad()
loss.backward()
optimizer.step()
_,preds = torch.max(outputs.data,1)
# statistics
running_loss += loss.data.item()
running_corrects += torch.sum(preds==classes.data)
count += len(inputs)
print('Training: No. ',count,' process ... total: ',size)
epoch_loss = running_loss/size
epoch_acc = running_corrects.data.item()/size
print('Loss: {:.4f} Acc: {:.4f}'.format(epoch_loss,epoch_acc))
# 模型训练
train_model(model_vgg_new,loader_train,size=dset_sizes['train'],epochs=1,optimizer=optimizer_vgg)
# 测试
def test_model(model,dataloader,size):
model.eval()
predictions = np.zeros(size)
all_classes = np.zeros(size)
all_proba = np.zeros((size,2))
i = 0
running_loss = 0.0
running_corrects = 0
for inputs,classes in dataloader:
inputs = inputs.to(device)
classes = classes.to(device)
outputs = model(inputs)
loss = criterion(outputs,classes)
_,preds = torch.max(outputs.data,1)
# statistics
running_loss += loss.data.item()
running_corrects += torch.sum(preds==classes.data)
predictions[i:i+len(classes)] = preds.to('cpu').numpy()
all_classes[i:i+len(classes)] = classes.to('cpu').numpy()
all_proba[i:i+len(classes),:] = outputs.data.to('cpu').numpy()
i += len(classes)
print('Testing: No. ',i,' process ... total: ',size)
epoch_loss = running_loss/size
epoch_acc = running_corrects.data.item()/size
print('Loss: {:.4f} Acc: {:.4f}'.format(epoch_loss,epoch_acc))
return predictions,all_proba,all_classes
predictions,all_proba,all_classes = test_model(model_vgg_new,loader_valid,size=dset_sizes['valid'])
1.6 Visualización
2 preguntas del desafío de apreciación del arte con IA
Enlace de código:
Intente usar resnet aquí, el código principal es el siguiente:
num_workers = 2
batch_size = 32
net = models.resnet50(pretrained=True)
net.fc = nn.Linear(net.fc.in_features,49,bias=True)
net = net.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(),lr=0.001)
train_data = PDataset(train_dir,transform=transform)
train_dataloader = DataLoader(dataset=train_data,shuffle=True,num_workers=num_workers,batch_size=batch_size,pin_memory=True)
for epoch in range(30): # 重复多轮训练
for i,(img,label) in enumerate(train_dataloader):
img = img.to(device)
labels = label.to(device)
# 优化器梯度归零
optimizer.zero_grad()
# 正向传播+反向传播+优化
outputs = net(img)
loss = criterion(outputs,labels)
loss.backward()
optimizer.step()
# 输出统计信息
print('Epoch: %d loss: %.3f' %(epoch+1,loss.item()))
print('Finished Training')
import pandas as pd
test_imgs = os.listdir(test_dir)
id_list = []
label_list = []
for img in test_imgs:
id = img.split('.')[0]
img = Image.open(test_dir+img).convert('RGB')
img = transform(img).to(device)
img = img.unsqueeze(0)
label = net(img)
i = 0
# print(label[0])
max = label[0][0]
max_id = 0
for value in label[0]:
if value > max:
max = value
max_id = i
i+=1
print(max_id)
id_list.append(id)
label_list.append(max_id)
dataframe = pd.DataFrame(
{'id': id_list, 'nameid': label_list})
dataframe.to_csv(r"results.csv", sep=',')
Resultados de entrenamiento de resnet50:
Resultado del juicio después de generar csv y enviar:
Entre ellos, results1 es el resultado de 15 rondas de entrenamiento resnet50 y result2 es el resultado de 50 rondas de entrenamiento resnet50, y los resultados son particularmente malos. . . . Esto puede estar relacionado con muy pocas rondas de entrenamiento, porque hay 49 categorías en esta competencia, lo cual es un problema de múltiples categorías y, a menudo, requiere una mejor estructura de red y una pérdida menor para lograr el efecto deseado. Además, de acuerdo con los materiales proporcionados, tanto el conjunto de entrenamiento como el de prueba tienden a ser de cola larga, lo que puede llevar fácilmente al modelo de entrenamiento a clasificar todas las imágenes en la misma categoría y al observar el archivo csv encontró que la mayoría de las imágenes están todos clasificados como No. 3 o No. 16, y No. 3 y No. 16 contienen el tamaño de muestra más grande, lo que indica que el fenómeno del oportunismo modelo realmente sucedió.
A continuación, estudié el esquema de código proporcionado por el ganador del primer premio. La red troncal utilizada aquí es EfficientNet B3, y registré 5 conjuntos de resultados de prueba con diferentes pérdidas, y utilicé la votación para determinar a qué imagen pertenece. Clase, creo que esto El mecanismo de votación es lo más destacado de este esquema de código, puede combinar las ventajas de 5 resultados de entrenamiento, aprender unos de otros, es muy inteligente y este tipo de parámetros de red troncal es pequeño, la eficiencia de cálculo principal no se juzgará 5 veces Como resultado, lleva mucho tiempo.
Observando los esquemas de código del segundo y tercer lugar, encontramos que también utilizaron este mecanismo de votación, el segundo lugar utilizó resnet200 como red troncal, y el tercer lugar utilizó eficientenet b3 y resnext50 respectivamente. Personalmente, creo que cuando la estructura de la red troncal es simple y la eficiencia de cálculo es alta, este mecanismo de votación se puede utilizar para mejorar la precisión del reconocimiento.