A pesquisa intitulada "VanillaNet: The Power of Minimalism in Deep Learning", desenvolvida por Hanting Chen, Yunhe Wang, Jianyuan Guo e Dacheng Tao, com contribuições da Huawei Noah's Ark Lab e da University of Sydney, propõe uma arquitetura de rede neural inovadora com foco no design minimalista. Esta rede, denominada VanillaNet, foi concebida para evitar as complexidades comuns em modelos de deep learning modernos, como alta profundidade, conexões residuais (shortcuts) e mecanismos avançados de atenção.
A essência da VanillaNet reside na sua estrutura direta e compacta, otimizada para ambientes com recursos computacionais limitados. Cada camada é cuidadosamente projetada para ser eficiente, e uma estratégia de treinamento exclusiva permite a fusão de funções de ativação não lineares pós-treinamento, restaurando a simplicidade da arquitetura original para inferência. Demonstrou-se, através de extensos testes, que a VanillaNet pode atingir desempenho comparável ao de redes neurais profundas e transformadores visuais amplamente reconhecidos, validando o potencial do minimalismo.
As últimas décadas testemunharam um avanço notável das redes neurais, muitas vezes impulsionado pelo aumento da complexidade e profundidade para melhorar a performance. Modelos como AlexNet e ResNet estabeleceram novos padrões em reconhecimento de imagem. No entanto, essa complexidade crescente introduz desafios significativos na implementação e consumo de recursos. Por exemplo, operações de atalho em ResNet exigem grande largura de banda de memória, e mecanismos de atenção em Swin Transformers demandam engenharia sofisticada.
A arquitetura da VanillaNet adere aos princípios fundamentais do design de redes neurais, incorporando um módulo inicial (stem), um corpo principal composto por estágios sequenciais e uma camada densa para classificação. Distintamente, a VanillaNet emprega apenas uma camada convolucional por estágio, o que resulta em uma rede notavelmente simplificada. A ausência de conexões de atalho foi uma escolha deliberada, pois os autores constataram que tais adições ofereciam um ganho de desempenho marginal.
Para otimizar o desempenho da VanillaNet, foi introduzida uma técnica de "treinamento profundo". Inicialmente, durante a fase de treinamento, um par de camadas convolucionais com funções de ativação é utilizado no lugar de uma única camada. À medida que o treinamento avança, o número dessas camadas não lineares é gradualmente reduzido, culminando na fusão das duas camadas convolucionais em uma única para a fase de inferência, minimizando assim o tempo de processamento.
A validação da VanillaNet foi realizada utilizando o conjunto de dados ImageNet. Estudos de ablação confirmaram a eficácia dos módulos propostos, incluindo as funções de ativação em série e a estratégia de treinamento profundo. Adicionalmente, visualizações dos mapas de características da VanillaNet foram empregadas para analisar como a rede assimila informações visuais.
Esta pesquisa explora a viabilidade de construir redes neurais de alto desempenho sem recorrer a arquiteturas complexas, como atalhos, alta profundidade ou camadas de atenção, promovendo uma mudança de paradigma em direção à simplicidade. Os resultados dos experimentos confirmam que a VanillaNet oferece performance competitiva em tarefas de classificação de imagens em larga escala, comparável a redes profundas e transformadores visuais, destacando a promessa do minimalismo no aprendizado profundo.
A integração da VanillaNet no backbone do YOLO11 demonstra resultados promissores. Embora a rede seja intensiva em termos de uso de memória, os ganhos de precisão em comparação com modelos como o FastNet são notáveis.
import torch
import torch.nn as nn
from timm.layers import weight_init # Utilizado para inicialização de pesos
__all__ = [
'vanillanet_v5', 'vanillanet_v6', 'vanillanet_v7', 'vanillanet_v8',
'vanillanet_v9', 'vanillanet_v10', 'vanillanet_v11', 'vanillanet_v12',
'vanillanet_v13', 'vanillanet_v13_large', 'vanillanet_v13_adaptive_pool'
]
class UnidadeAtivacaoDinamica(nn.ReLU):
"""
Unidade de ativação personalizada que pode ser fundida para inferência.
Incorpora uma convolução depthwise e Batch Normalization, com opção de fusão.
"""
def __init__(self, num_canais, kernel_tamanho_ativacao=3, modo_inferencia=False):
super(UnidadeAtivacaoDinamica, self).__init__()
self.modo_inferencia = modo_inferencia
# Pesos para a convolução depthwise
self.kernel_conv = torch.nn.Parameter(
torch.randn(num_canais, 1, 2 * kernel_tamanho_ativacao + 1, 2 * kernel_tamanho_ativacao + 1)
)
self.bias_conv = None # Bias será adicionado após fusão com BN
self.normalizacao_batch = nn.BatchNorm2d(num_canais, eps=1e-6)
self.num_canais = num_canais
self.kernel_tamanho_ativacao = kernel_tamanho_ativacao
weight_init.trunc_normal_(self.kernel_conv, std=.02)
def forward(self, x_input):
activated_x = super(UnidadeAtivacaoDinamica, self).forward(x_input)
if self.modo_inferencia:
# Em modo de inferência, BN é fundido, usa o bias_conv
return torch.nn.functional.conv2d(
activated_x,
self.kernel_conv,
self.bias_conv,
padding=(2 * self.kernel_tamanho_ativacao + 1) // 2,
groups=self.num_canais
)
else:
# Em modo de treinamento, BN é separado
return self.normalizacao_batch(
torch.nn.functional.conv2d(
activated_x,
self.kernel_conv,
padding=self.kernel_tamanho_ativacao,
groups=self.num_canais
)
)
def _fundir_bn_com_kernel(self, conv_kernel, bn_module):
"""Funde os parâmetros do BatchNorm nos pesos da convolução."""
mean = bn_module.running_mean
variance = bn_module.running_var
gamma = bn_module.weight # Scale
beta = bn_module.bias # Shift
epsilon = bn_module.eps
std_dev = (variance + epsilon).sqrt()
scale_factor = (gamma / std_dev).reshape(-1, 1, 1, 1)
# Ajusta o kernel
novo_kernel = conv_kernel * scale_factor
# Ajusta o bias
novo_bias = beta + (0 - mean) * gamma / std_dev
return novo_kernel, novo_bias
def para_modo_inferencia(self):
"""Converte o módulo para o modo de inferência, fundindo BN."""
if not self.modo_inferencia:
fused_kernel, fused_bias = self._fundir_bn_com_kernel(self.kernel_conv, self.normalizacao_batch)
self.kernel_conv.data = fused_kernel
self.bias_conv = torch.nn.Parameter(torch.zeros(self.num_canais))
self.bias_conv.data = fused_bias
self.__delattr__('normalizacao_batch') # Remove BN após fusão
self.modo_inferencia = True
class BlocoMinimalista(nn.Module):
"""
Bloco fundamental da VanillaNet, com caminho de treinamento e inferência.
Durante o treinamento, usa duas convoluções 1x1 com Leaky ReLU intermediária,
que se fundem em uma única convolução 1x1 para inferência.
"""
def __init__(self, canais_entrada, canais_saida, kernel_tamanho_ativacao=3, passo_pooling=2,
modo_inferencia=False, pooling_adaptativo=None):
super().__init__()
self.fator_leaky_relu = 1 # Coeficiente para Leaky ReLU
self.modo_inferencia = modo_inferencia
if self.modo_inferencia:
self.camada_conv_final = nn.Conv2d(canais_entrada, canais_saida, kernel_size=1)
else:
self.seq_treino_1 = nn.Sequential(
nn.Conv2d(canais_entrada, canais_entrada, kernel_size=1),
nn.BatchNorm2d(canais_entrada, eps=1e-6),
)
self.seq_treino_2 = nn.Sequential(
nn.Conv2d(canais_entrada, canais_saida, kernel_size=1),
nn.BatchNorm2d(canais_saida, eps=1e-6)
)
if not pooling_adaptativo:
self.camada_pooling = nn.Identity() if passo_pooling == 1 else nn.MaxPool2d(passo_pooling)
else:
self.camada_pooling = nn.Identity() if passo_pooling == 1 else nn.AdaptiveMaxPool2d((pooling_adaptativo, pooling_adaptativo))
self.ativacao_personalizada = UnidadeAtivacaoDinamica(canais_saida, kernel_tamanho_ativacao)
def forward(self, x_input):
if self.modo_inferencia:
x = self.camada_conv_final(x_input)
else:
x = self.seq_treino_1(x_input)
x = torch.nn.functional.leaky_relu(x, self.fator_leaky_relu)
x = self.seq_treino_2(x)
x = self.camada_pooling(x)
x = self.ativacao_personalizada(x)
return x
def _fundir_bn_conv_weights(self, conv_layer, bn_layer, conv_bias=True):
"""Funde BatchNorm nos pesos e bias de uma camada convolucional."""
conv_kernel = conv_layer.weight
conv_bias_val = conv_layer.bias if conv_bias and conv_layer.bias is not None else 0
running_mean = bn_layer.running_mean
running_var = bn_layer.running_var
gamma = bn_layer.weight
beta = bn_layer.bias
eps = bn_layer.eps
std_dev = (running_var + eps).sqrt()
scale_factor = (gamma / std_dev).reshape(-1, 1, 1, 1)
fused_kernel = conv_kernel * scale_factor
fused_bias = beta + (conv_bias_val - running_mean) * gamma / std_dev
return fused_kernel, fused_bias
def preparar_para_inferencia(self):
"""Converte o bloco para o modo de inferência, fundindo convoluções e BN."""
if not self.modo_inferencia:
# Funde BN da primeira convolução
k1, b1 = self._fundir_bn_conv_weights(self.seq_treino_1[0], self.seq_treino_1[1], conv_bias=False)
self.seq_treino_1[0].weight.data = k1
self.seq_treino_1[0].bias = nn.Parameter(b1) # Adiciona bias explicitamente
# Funde BN da segunda convolução
k2, b2 = self._fundir_bn_conv_weights(self.seq_treino_2[0], self.seq_treino_2[1])
# Agora funde as duas convoluções 1x1 e seus biases
self.camada_conv_final = self.seq_treino_2[0] # Reusa a segunda conv para armazenar o resultado fundido
# Matrizes de convolução 1x1 são basicamente matrizes densas em cada posição espacial
# A fusão de duas convs 1x1 é uma multiplicação de matrizes.
# k2 tem forma (out_channels, in_channels, 1, 1)
# k1 tem forma (in_channels, in_channels, 1, 1) - canais intermediários
# Para multiplicação de matrizes: transponha (1,3) para obter (out_channels, 1, 1, in_channels),
# em seguida transponha (1,3) novamente para o formato final
fused_kernel_data = torch.matmul(k2.squeeze(3).squeeze(2).transpose(0, 1), # (C_in_2, C_out_1)
k1.squeeze(3).squeeze(2)).transpose(0, 1).unsqueeze(2).unsqueeze(3) # (C_out_2, C_in_1)
self.camada_conv_final.weight.data = fused_kernel_data
# Fusão dos biases: b2 + (b1 * k2_sum_across_spatial_dims)
fused_bias_data = b2 + (b1.view(1, -1, 1, 1) * k2).sum(3).sum(2).sum(1)
self.camada_conv_final.bias.data = fused_bias_data
self.__delattr__('seq_treino_1')
self.__delattr__('seq_treino_2')
self.ativacao_personalizada.para_modo_inferencia()
self.modo_inferencia = True
class RedeNeuralVanilla(nn.Module):
"""
Implementação da arquitetura VanillaNet, uma rede neural minimalista.
"""
def __init__(self, canais_entrada_img=3, num_classes_saida=1000, dimensoes_estagios=[96, 192, 384, 768],
taxa_dropout=0, kernel_ativacao=3, passos_estagios=[2, 2, 2, 1],
modo_inferencia=False, pooling_adaptativo=None, **kwargs):
super().__init__()
self.modo_inferencia = modo_inferencia
self.fator_leaky_relu = 1 # Coeficiente para Leaky ReLU global
if self.modo_inferencia:
self.bloco_raiz_prod = nn.Sequential(
nn.Conv2d(canais_entrada_img, dimensoes_estagios[0], kernel_size=4, stride=4),
UnidadeAtivacaoDinamica(dimensoes_estagios[0], kernel_ativacao)
)
else:
self.bloco_raiz_treino_1 = nn.Sequential(
nn.Conv2d(canais_entrada_img, dimensoes_estagios[0], kernel_size=4, stride=4),
nn.BatchNorm2d(dimensoes_estagios[0], eps=1e-6),
)
self.bloco_raiz_treino_2 = nn.Sequential(
nn.Conv2d(dimensoes_estagios[0], dimensoes_estagios[0], kernel_size=1, stride=1),
nn.BatchNorm2d(dimensoes_estagios[0], eps=1e-6),
UnidadeAtivacaoDinamica(dimensoes_estagios[0], kernel_ativacao)
)
self.lista_estagios = nn.ModuleList()
for i in range(len(passos_estagios)):
current_adaptive_pool = pooling_adaptativo[i] if pooling_adaptativo else None
estagio = BlocoMinimalista(
canais_entrada=dimensoes_estagios[i],
canais_saida=dimensoes_estagios[i + 1],
kernel_tamanho_ativacao=kernel_ativacao,
passo_pooling=passos_estagios[i],
modo_inferencia=modo_inferencia,
pooling_adaptativo=current_adaptive_pool
)
self.lista_estagios.append(estagio)
self.num_estagios = len(passos_estagios)
self._inicializar_pesos_rede(self) # Inicializa pesos
# Calcula as larguras de saída dos feature maps para verificar a arquitetura
with torch.no_grad():
dummy_input = torch.randn(1, 3, 640, 640)
dummy_output = self.forward(dummy_input)
self.larguras_saida = [fmap.size(1) for fmap in dummy_output]
def _inicializar_pesos_rede(self, modulo):
"""Inicializa pesos para convoluções e camadas lineares."""
if isinstance(modulo, (nn.Conv2d, nn.Linear)):
weight_init.trunc_normal_(modulo.weight, std=.02)
if modulo.bias is not None:
nn.init.constant_(modulo.bias, 0)
def ajustar_fator_leaky_relu(self, fator):
"""Ajusta o fator de Leaky ReLU em todos os blocos e no stem."""
for estagio in self.lista_estagios:
estagio.fator_leaky_relu = fator
self.fator_leaky_relu = fator
def forward(self, x_input):
saidas_rede = []
if self.modo_inferencia:
x = self.bloco_raiz_prod(x_input)
else:
x = self.bloco_raiz_treino_1(x_input)
x = torch.nn.functional.leaky_relu(x, self.fator_leaky_relu)
x = self.bloco_raiz_treino_2(x)
saidas_rede.append(x)
for estagio_idx in range(self.num_estagios):
x = self.lista_estagios[estagio_idx](x)
saidas_rede.append(x)
return saidas_rede
def _fundir_seq_conv_bn(self, conv_layer, bn_layer):
"""Função auxiliar para fundir um Conv2d com um BatchNorm2d."""
conv_kernel = conv_layer.weight
conv_bias = conv_layer.bias if conv_layer.bias is not None else 0
running_mean = bn_layer.running_mean
running_var = bn_layer.running_var
gamma = bn_layer.weight
beta = bn_layer.bias
eps = bn_layer.eps
std = (running_var + eps).sqrt()
scale_factor = (gamma / std).reshape(-1, 1, 1, 1)
fused_kernel = conv_kernel * scale_factor
fused_bias = beta + (conv_bias - running_mean) * gamma / std
return fused_kernel, fused_bias
def converter_para_producao(self):
"""
Converte toda a rede para o modo de inferência, fundindo BatchNorms
e convoluções em camadas únicas.
"""
if not self.modo_inferencia:
# Converte e funde o bloco raiz
self.bloco_raiz_treino_2[2].para_modo_inferencia() # Ativação do stem
k_s1, b_s1 = self._fundir_seq_conv_bn(self.bloco_raiz_treino_1[0], self.bloco_raiz_treino_1[1])
self.bloco_raiz_treino_1[0].weight.data = k_s1
self.bloco_raiz_treino_1[0].bias = nn.Parameter(b_s1)
k_s2, b_s2 = self._fundir_seq_conv_bn(self.bloco_raiz_treino_2[0], self.bloco_raiz_treino_2[1])
# Funde as duas convoluções do stem
# k_s2 tem forma (C_out_2, C_in_2, 1, 1)
# k_s1 tem forma (C_out_1, C_in_1, K, K)
fused_stem_kernel = torch.einsum('oi,icjk->ocjk', k_s2.squeeze(3).squeeze(2),
self.bloco_raiz_treino_1[0].weight.data)
self.bloco_raiz_treino_1[0].weight.data = fused_stem_kernel
fused_stem_bias = b_s2 + (self.bloco_raiz_treino_1[0].bias.data.view(1, -1, 1, 1) * k_s2).sum(3).sum(2).sum(1)
self.bloco_raiz_treino_1[0].bias.data = fused_stem_bias
self.bloco_raiz_prod = nn.Sequential(*[self.bloco_raiz_treino_1[0], self.bloco_raiz_treino_2[2]])
self.__delattr__('bloco_raiz_treino_1')
self.__delattr__('bloco_raiz_treino_2')
# Converte cada estágio individualmente
for i in range(self.num_estagios):
self.lista_estagios[i].preparar_para_inferencia()
self.modo_inferencia = True
def obter_modelo_vanillanet_v5(**kwargs):
"""Retorna uma VanillaNet de 5 estágios."""
return RedeNeuralVanilla(dims=[128 * 4, 256 * 4, 512 * 4, 1024 * 4], passos_estagios=[2, 2, 2], **kwargs)
def obter_modelo_vanillanet_v6(**kwargs):
"""Retorna uma VanillaNet de 6 estágios."""
return RedeNeuralVanilla(dims=[128 * 4, 256 * 4, 512 * 4, 1024 * 4, 1024 * 4], passos_estagios=[2, 2, 2, 1], **kwargs)
def obter_modelo_vanillanet_v7(**kwargs):
"""Retorna uma VanillaNet de 7 estágios."""
return RedeNeuralVanilla(dims=[128 * 4, 128 * 4, 256 * 4, 512 * 4, 1024 * 4, 1024 * 4], passos_estagios=[1, 2, 2, 2, 1], **kwargs)
def obter_modelo_vanillanet_v8(**kwargs):
"""Retorna uma VanillaNet de 8 estágios."""
return RedeNeuralVanilla(dims=[128 * 4, 128 * 4, 256 * 4, 512 * 4, 512 * 4, 1024 * 4, 1024 * 4],
passos_estagios=[1, 2, 2, 1, 2, 1], **kwargs)
def obter_modelo_vanillanet_v9(**kwargs):
"""Retorna uma VanillaNet de 9 estágios."""
return RedeNeuralVanilla(dims=[128 * 4, 128 * 4, 256 * 4, 512 * 4, 512 * 4, 512 * 4, 1024 * 4, 1024 * 4],
passos_estagios=[1, 2, 2, 1, 1, 2, 1], **kwargs)
def obter_modelo_vanillanet_v10(**kwargs):
"""Retorna uma VanillaNet de 10 estágios."""
return RedeNeuralVanilla(
dims=[128 * 4, 128 * 4, 256 * 4, 512 * 4, 512 * 4, 512 * 4, 512 * 4, 1024 * 4, 1024 * 4],
passos_estagios=[1, 2, 2, 1, 1, 1, 2, 1],
**kwargs)
def obter_modelo_vanillanet_v11(**kwargs):
"""Retorna uma VanillaNet de 11 estágios."""
model = RedeNeuralVanilla(
dimensoes_estagios=[128 * 4, 128 * 4, 256 * 4, 512 * 4, 512 * 4, 512 * 4, 512 * 4, 512 * 4, 1024 * 4, 1024 * 4],
passos_estagios=[1, 2, 2, 1, 1, 1, 1, 2, 1],
**kwargs)
return model
def obter_modelo_vanillanet_v12(**kwargs):
"""Retorna uma VanillaNet de 12 estágios."""
return RedeNeuralVanilla(
dims=[128 * 4, 128 * 4, 256 * 4, 512 * 4, 512 * 4, 512 * 4, 512 * 4, 512 * 4, 512 * 4, 1024 * 4, 1024 * 4],
passos_estagios=[1, 2, 2, 1, 1, 1, 1, 1, 2, 1],
**kwargs)
def obter_modelo_vanillanet_v13(**kwargs):
"""Retorna uma VanillaNet de 13 estágios."""
return RedeNeuralVanilla(
dims=[128 * 4, 128 * 4, 256 * 4, 512 * 4, 512 * 4, 512 * 4, 512 * 4, 512 * 4, 512 * 4, 512 * 4, 1024 * 4,
1024 * 4],
passos_estagios=[1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 1],
**kwargs)
def obter_modelo_vanillanet_v13_large(**kwargs):
"""Retorna uma VanillaNet de 13 estágios com largura de canal aumentada (x1.5)."""
return RedeNeuralVanilla(
dims=[128 * 6, 128 * 6, 256 * 6, 512 * 6, 512 * 6, 512 * 6, 512 * 6, 512 * 6, 512 * 6, 512 * 6, 1024 * 6,
1024 * 6],
passos_estagios=[1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 1],
**kwargs)
def obter_modelo_vanillanet_v13_adaptive_pool(**kwargs):
"""Retorna uma VanillaNet de 13 estágios com largura de canal aumentada (x1.5) e pooling adaptativo."""
return RedeNeuralVanilla(
dims=[128 * 6, 128 * 6, 256 * 6, 512 * 6, 512 * 6, 512 * 6, 512 * 6, 512 * 6, 512 * 6, 512 * 6, 1024 * 6,
1024 * 6],
passos_estagios=[1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 1],
pooling_adaptativo=[0, 38, 19, 0, 0, 0, 0, 0, 0, 10, 0], # Dimensões adaptativas para pooling
**kwargs)
Após a integração do backbone VanillaNet e a execução dos testes com o YOLO11, os seguintes resultados foram observados:
Ultralytics 8.3.0 🚀 Python-3.9.20 torch-2.0.1 CUDA:0 (NVIDIA GeForce RTX 3060 Laptop GPU, 12288MiB)
YOLO11-VanillaNet summary: 205 layers, 29,772,192 parameters, 0 gradients, 150.5 GFLOPs
Class Images Instances Box(P R mAP50 mAP50-95): 100%|██████████| 8/8 [00:04<00:00, 1.75it/s]
all 128 929 0.797 0.614 0.695 0.565
person 61 254 0.96 0.568 0.788 0.603
bicycle 3 6 0.893 0.167 0.201 0.179
car 12 46 0.763 0.21 0.256 0.151
motorcycle 4 5 0.73 1 0.995 0.902
airplane 5 6 0.824 0.788 0.913 0.692
bus 5 7 0.821 0.714 0.718 0.647
train 3 3 0.445 1 0.863 0.642
truck 5 12 0.864 0.5 0.522 0.425
boat 2 6 0.586 0.476 0.554 0.379
traffic light 4 14 0.725 0.143 0.267 0.188
stop sign 2 2 1 0 0.595 0.526
bench 5 9 0.409 0.556 0.513 0.448
bird 2 16 0.957 0.938 0.991 0.69
cat 4 4 0.844 0.75 0.774 0.709
dog 9 9 0.987 0.778 0.899 0.718
horse 1 2 0.918 1 0.995 0.921
elephant 4 17 0.827 0.941 0.902 0.757
bear 1 1 0.802 1 0.995 0.995
zebra 2 4 0.685 1 0.995 0.945
giraffe 4 9 0.62 1 0.956 0.801
backpack 4 6 1 0.533 0.669 0.619
umbrella 4 18 0.711 0.944 0.921 0.716
handbag 9 19 1 0.218 0.365 0.295
tie 6 7 0.715 0.571 0.688 0.492
suitcase 2 4 0.861 0.75 0.776 0.68
frisbee 5 5 0.668 0.4 0.631 0.463
skis 1 1 0 0 0 0
snowboard 2 7 0.81 0.857 0.944 0.684
sports ball 6 6 0.943 0.167 0.171 0.137
kite 2 10 1 0.128 0.315 0.083
baseball bat 4 4 1 0 0.264 0.234
baseball glove 4 7 1 0.191 0.289 0.261
skateboard 3 5 0.979 0.4 0.648 0.43
tennis racket 5 7 1 0.365 0.464 0.381
bottle 6 18 0.897 0.222 0.24 0.212
wine glass 5 16 0.668 0.755 0.733 0.423
cup 10 36 0.838 0.431 0.631 0.379
fork 6 6 0.606 0.273 0.471 0.367
knife 7 16 1 0.366 0.605 0.323
spoon 5 22 0.888 0.36 0.542 0.333
bowl 9 28 0.951 0.69 0.743 0.621
banana 1 1 0.839 1 0.995 0.995
sandwich 2 2 0.761 1 0.995 0.92
orange 1 4 0.45 1 0.995 0.805
broccoli 4 11 0.727 0.455 0.514 0.418
carrot 3 24 0.442 0.75 0.703 0.529
hot dog 1 2 0.825 1 0.995 0.921
pizza 5 5 0.89 0.8 0.862 0.783
donut 2 14 1 0.974 0.995 0.811
cake 4 4 1 0.479 0.794 0.721
chair 9 35 0.86 0.703 0.889 0.646
couch 5 6 0.563 0.438 0.788 0.634
potted plant 9 14 0.844 0.929 0.945 0.744
bed 3 3 0.931 1 0.995 0.895
dining table 10 13 0.857 0.615 0.756 0.608
toilet 2 2 0.878 1 0.995 0.895
tv 2 2 0.909 1 0.995 0.895
laptop 2 3 0.525 0.333 0.525 0.451
mouse 2 2 0 0 0 0
remote 5 8 1 0.236 0.407 0.343
cell phone 5 8 0.735 0.125 0.154 0.122
microwave 3 3 0.929 1 0.995 0.764
oven 5 5 0.917 0.8 0.797 0.657
sink 4 6 0.751 0.505 0.65 0.555
refrigerator 5 5 0.607 0.8 0.906 0.631
book 6 29 0.754 0.422 0.576 0.278
clock 8 9 1 0.737 0.874 0.762
vase 2 2 0.762 0.5 0.521 0.414
scissors 1 1 0.86 1 0.995 0.796
teddy bear 6 21 0.948 0.878 0.971 0.809
toothbrush 2 5 0.853 1 0.995 0.849
Speed: 0.8ms preprocess, 22.1ms inference, 0.0ms loss, 1.7ms postprocess per image
Results saved to runs\train\exp_yolo11-VanillaNet_datasets_coco1283