minhas informações de contato
Correspondência[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Aqui implementamos uma rede VGG-16 para classificar o conjunto de dados CIFAR
Prefácio
《Redes convolucionais muito profundas para reconhecimento de imagens em larga escala》
ICLR 2015
VGGÉ de OxfordVisualGeometriaG Proposto pelo grupo do grupo (você deverá conseguir ver a origem do nome VGG). Esta rede é um trabalho relacionado no ILSVRC 2014. O trabalho principal é provar que aumentar a profundidade da rede pode afetar até certo ponto o desempenho final da rede. O VGG possui duas estruturas, nomeadamente VGG16 e VGG19. Não há diferença essencial entre os dois, mas a profundidade da rede é diferente.
Princípio VGG
Uma melhoria do VGG16 em comparação com AlexNet é queUse vários kernels de convolução 3x3 consecutivos para substituir os kernels de convolução maiores no AlexNet (11x11, 7x7, 5x5) . Para um determinado campo receptivo (o tamanho local da imagem de entrada em relação à saída), o uso de pequenos núcleos de convolução empilhados é melhor do que o uso de grandes núcleos de convolução, porque múltiplas camadas não lineares podem aumentar a profundidade da rede para garantir um modo de aprendizado mais complexo e. o custo é relativamente pequeno (menos parâmetros).
Simplificando, no VGG, três kernels de convolução 3x3 são usados para substituir o kernel de convolução 7x7, e dois kernels de convolução 3x3 são usados para substituir o kernel de convolução 5*5. Do campo receptivo, a profundidade da rede é melhorada e o efeito da rede neural é melhorado até certo ponto.
Por exemplo, a superposição camada por camada de três núcleos de convolução 3x3 com um passo de 1 pode ser considerada como um campo receptivo de tamanho 7 (na verdade, isso significa que três convoluções contínuas 3x3 são equivalentes a uma convolução 7x7), e seu os parâmetros totais são A quantidade é 3x (9xC ^ 2). Se o kernel de convolução 7x7 for usado diretamente, o número total de parâmetros é 49xC ^ 2, onde C se refere ao número de canais de entrada e saída.Obviamente, 27xC2 menos de 49xC2, ou seja, os parâmetros são reduzidos e o kernel de convolução 3x3 contribui para melhor manter as propriedades da imagem;
Aqui está uma explicação de por que dois kernels de convolução 3x3 podem ser usados em vez de kernels de convolução 5*5:
A convolução 5x5 é considerada uma pequena rede totalmente conectada que desliza na área 5x5. Podemos primeiro convolucionar com um filtro de convolução 3x3 e, em seguida, usar uma camada totalmente conectada para conectar a saída de convolução 3x3. ser visto como uma camada convolucional 3x3. Desta forma, podemos colocar em cascata (sobrepor) duas convoluções 3x3 em vez de uma convolução 5x5.
Os detalhes são mostrados na figura abaixo:
Estrutura da rede VGG
A seguir está a estrutura da rede VGG (VGG16 e VGG19 estão presentes):
Estrutura da rede GG
VGG16 contém 16 camadas ocultas (13 camadas convolucionais e 3 camadas totalmente conectadas), conforme mostrado na coluna D da figura acima
VGG19 contém 19 camadas ocultas (16 camadas convolucionais e 3 camadas totalmente conectadas), conforme mostrado na coluna E na figura acima
A estrutura da rede VGG é muito consistente, usando convolução 3x3 e pooling máximo de 2x2 do início ao fim.
Vantagens do VGG
A estrutura do VGGNet é muito simples. Toda a rede usa o mesmo tamanho de kernel de convolução (3x3) e tamanho máximo de pooling (2x2).
A combinação de várias camadas convolucionais de filtro pequeno (3x3) é melhor do que uma camada convolucional de filtro grande (5x5 ou 7x7):
Verifica-se que o desempenho pode ser melhorado aprofundando continuamente a estrutura da rede.
Desvantagens do VGG
O VGG consome mais recursos de computação e usa mais parâmetros (este não é o pote da convolução 3x3), resultando em mais uso de memória (140M).
O conjunto de dados CIFAR (Instituto Canadense de Pesquisa Avançada) é um pequeno conjunto de dados de imagens amplamente utilizado no campo de visão computacional. É usado principalmente para treinar algoritmos de aprendizado de máquina e visão computacional, especialmente em tarefas como reconhecimento e classificação de imagens. O conjunto de dados CIFAR consiste em duas partes principais: CIFAR-10 e CIFAR-100.
CIFAR-10 é um conjunto de dados contendo 60.000 imagens coloridas 32x32, que são divididas em 10 categorias, cada categoria contendo 6.000 imagens. As 10 categorias são: aviões, carros, pássaros, gatos, veados, cachorros, sapos, cavalos, barcos e caminhões. No conjunto de dados, 50.000 imagens são usadas para treinamento e 10.000 imagens são usadas para teste. O conjunto de dados CIFAR-10 tornou-se um dos conjuntos de dados muito populares em pesquisa e ensino na área de visão computacional devido ao seu tamanho moderado e rica informação de classe.
O conjunto de dados CIFAR é comumente usado para tarefas como classificação de imagens, reconhecimento de objetos e treinamento e teste de redes neurais convolucionais (CNN). Devido ao seu tamanho moderado e rica informação de categoria, é ideal para iniciantes e pesquisadores que exploram algoritmos de reconhecimento de imagem. Além disso, muitas competições de visão computacional e aprendizado de máquina também usam o conjunto de dados CIFAR como referência para avaliar o desempenho dos algoritmos dos concorrentes.
Para preparar o conjunto de dados, já baixei. Se não funcionar, basta baixá-lo do site oficial ou entregarei diretamente para você.
Caso necessite do conjunto de dados, entre em contato com o email: [email protected]
Meu conjunto de dados foi originalmente gerado por meio dos dados baixados no torchvision. Eu realmente não quero fazer isso agora, quero implementar a definição do conjunto de dados de dados e o carregamento do DataLoader passo a passo, entender esse processo e entender. o processo de processamento do conjunto de dados pode torná-lo mais profundo sobre o aprendizado profundo.
O estilo do conjunto de dados é o seguinte:
A categoria de rótulo do conjunto de dados usa um.meta
O arquivo está armazenado, então precisamos analisar .meta
arquivo para ler todos os dados da tag. O código de análise é o seguinte:
# 首先了解所有的标签,TODO 可以详细了解一下这个解包的过程
import pickle
def unpickle(file):
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
return dict
meta_data = unpickle('./dataset_method_1/cifar-10-batches-py/batches.meta')
label_names = meta_data[b'label_names']
# 将字节标签转换为字符串
label_names = [label.decode('utf-8') for label in label_names]
print(label_names)
Os resultados da análise são os seguintes:
['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
Nosso conjunto de dados foi baixado, então precisamos ler o conteúdo do arquivo. Como o arquivo é um arquivo binário, precisamos usar o modo de leitura binária para lê-lo.
O código de leitura é o seguinte:
# 载入单个批次的数据
import numpy as np
def load_data_batch(file):
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
X = dict[b'data']
Y = dict[b'labels']
X = X.reshape(10000, 3, 32, 32).transpose(0, 2, 3, 1) # reshape and transpose to (10000, 32, 32, 3)
Y = np.array(Y)
return X, Y
# 加载第一个数据批次
data_batch_1 = './dataset_method_1/cifar-10-batches-py/data_batch_1'
X1, Y1 = load_data_batch(data_batch_1)
print(f'数据形状: {X1.shape}, 标签形状: {Y1.shape}')
resultado:
数据形状: (10000, 32, 32, 3), 标签形状: (10000,)
Após o teste acima, sabemos como carregar os dados. Agora vamos carregar todos os dados.
Carregando o conjunto de treinamento:
# 整合所有批次的数据
def load_all_data_batches(batch_files):
X_list, Y_list = [], []
for file in batch_files:
X, Y = load_data_batch(file)
X_list.append(X)
Y_list.append(Y)
X_all = np.concatenate(X_list)
Y_all = np.concatenate(Y_list)
return X_all, Y_all
batch_files = [
'./dataset_method_1/cifar-10-batches-py/data_batch_1',
'./dataset_method_1/cifar-10-batches-py/data_batch_2',
'./dataset_method_1/cifar-10-batches-py/data_batch_3',
'./dataset_method_1/cifar-10-batches-py/data_batch_4',
'./dataset_method_1/cifar-10-batches-py/data_batch_5'
]
X_train, Y_train = load_all_data_batches(batch_files)
print(f'训练数据形状: {X_train.shape}, 训练标签形状: {Y_train.shape}')
Y_train = Y_train.astype(np.int64)
Saída:
训练数据形状: (50000, 32, 32, 3), 训练标签形状: (50000,)
Carregando o conjunto de testes:
test_batch = './dataset_method_1/cifar-10-batches-py/test_batch'
X_test, Y_test = load_data_batch(test_batch)
Y_test = Y_test.astype(np.int64)
print(f'测试数据形状: {X_test.shape}, 测试标签形状: {Y_test.shape}')
Saída:
测试数据形状: (10000, 32, 32, 3), 测试标签形状: (10000,)
Definir uma subclasse da classe Dataset facilita o carregamento subsequente do Dataloader para treinamento em lote.
Existem três métodos que as subclasses do Dataset devem implementar.
__init__()
construtor de classe__len__()
Retorna o comprimento do conjunto de dados__getitem__()
Obtenha um dado do conjunto de dadosAqui minha implementação é a seguinte:
from torch.utils.data import DataLoader, Dataset
# 定义 Pytorch 的数据集
class CIFARDataset(Dataset):
def __init__(self, images, labels, transform=None):
self.images = images
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.images)
def __getitem__(self, idx):
image = self.images[idx]
label = self.labels[idx]
if self.transform:
image = self.transform(image)
return image, label
transform_train = transforms.Compose(
[transforms.Pad(4),
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
transforms.RandomHorizontalFlip(),
transforms.RandomGrayscale(),
transforms.RandomCrop(32, padding=4),
])
# 把数据集变成 Image 的数组,不然好像不能进行数据的增强
# 改变训练数据
from PIL import Image
def get_PIL_Images(origin_data):
datas = []
for i in range(len(origin_data)):
data = Image.fromarray(origin_data[i])
datas.append(data)
return datas
train_data = get_PIL_Images(X_train)
train_loader = DataLoader(CIFARDataset(train_data, Y_train, transform_train), batch_size=24, shuffle=True)
# 测试集的预处理
transform_test = transforms.Compose(
[
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))]
)
test_loader = DataLoader(CIFARDataset(X_test, Y_test, transform_test), batch_size=24, shuffle=False)
Implementamos a estrutura Pytorch baseada na rede VGG16 mencionada acima.
dividido principalmente:
A implementação é a seguinte:
class VGG16(nn.Module):
def __init__(self):
super(VGG16, self).__init__()
# 卷积层,这里进行卷积
self.convolusion = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=3, padding=1), # 设置为padding=1 卷积完后,数据大小不会变
nn.BatchNorm2d(96),
nn.ReLU(inplace=True),
nn.Conv2d(96, 96, kernel_size=3, padding=1),
nn.BatchNorm2d(96),
nn.ReLU(inplace=True),
nn.Conv2d(96, 96, kernel_size=3, padding=1),
nn.BatchNorm2d(96),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(96, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(256, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.BatchNorm2d(512),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.AvgPool2d(kernel_size=1, stride=1)
)
# 全连接层
self.dense = nn.Sequential(
nn.Linear(512, 4096), # 32*32 的图像大小经过 5 次最大化池化后就只有 1*1 了,所以就是 512 个通道的数据输入全连接层
nn.ReLU(inplace=True),
nn.Dropout(0.4),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(0.4),
)
# 输出层
self.classifier = nn.Linear(4096, 10)
def forward(self, x):
out = self.convolusion(x)
out = out.view(out.size(0), -1)
out = self.dense(out)
out = self.classifier(out)
return out
Para treinamento e teste, você só precisa instanciar o modelo, definir a função de otimização, a função de perda e a taxa de perda e, em seguida, realizar o treinamento e o teste.
código mostrado abaixo:
Definição de hiperparâmetro:
# 定义模型进行训练
model = VGG16()
# model.load_state_dict(torch.load('./my-VGG16.pth'))
optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=5e-3)
loss_func = nn.CrossEntropyLoss()
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.4, last_epoch=-1)
Função de teste:
def test():
model.eval()
correct = 0 # 预测正确的图片数
total = 0 # 总共的图片数
with torch.no_grad():
for data in test_loader:
images, labels = data
images = images.to(device)
outputs = model(images).to(device)
outputs = outputs.cpu()
outputarr = outputs.numpy()
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
accuracy = 100 * correct / total
accuracy_rate.append(accuracy)
print(f'准确率为:{accuracy}%'.format(accuracy))
Épocas de treinamento:
# 定义训练步骤
total_times = 40
total = 0
accuracy_rate = []
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
for epoch in range(total_times):
model.train()
model.to(device)
running_loss = 0.0
total_correct = 0
total_trainset = 0
print("epoch: ",epoch)
for i, (data,labels) in enumerate(train_loader):
data = data.to(device)
outputs = model(data).to(device)
labels = labels.to(device)
loss = loss_func(outputs,labels).to(device)
optimizer.zero_grad()
loss.backward()
optimizer.step()
running_loss += loss.item()
_,pred = outputs.max(1)
correct = (pred == labels).sum().item()
total_correct += correct
total_trainset += data.shape[0]
if i % 100 == 0 and i > 0:
print(f"正在进行第{i}次训练, running_loss={running_loss}".format(i, running_loss))
running_loss = 0.0
test()
scheduler.step()
Salve o modelo treinado:
torch.save(model.state_dict(), './my-VGG16.pth')
accuracy_rate = np.array(accuracy_rate)
times = np.linspace(1, total_times, total_times)
plt.xlabel('times')
plt.ylabel('accuracy rate')
plt.plot(times, accuracy_rate)
plt.show()
print(accuracy_rate)
model_my_vgg = VGG16()
model_my_vgg.load_state_dict(torch.load('./my-VGG16-best.pth',map_location='cpu'))
from torchvision import transforms
from PIL import Image
# 定义图像预处理步骤
preprocess = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(),
# transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225]),
])
def load_image(image_path):
image = Image.open(image_path)
image = preprocess(image)
image = image.unsqueeze(0) # 添加批次维度
return image
image_data = load_image('./plane2.jpg')
print(image_data.shape)
output = model_my_vgg(image_data)
verify_data = X1[9]
verify_label = Y1[9]
output_verify = model_my_vgg(transform_test(verify_data).unsqueeze(0))
print(output)
print(output_verify)
Saída:
torch.Size([1, 3, 32, 32])
tensor([[ 1.5990, -0.5269, 0.7254, 0.3432, -0.5036, -0.3267, -0.5302, -0.9417,
0.4186, -0.1213]], grad_fn=<AddmmBackward0>)
tensor([[-0.6541, -2.0759, 0.6308, 1.9791, 0.8525, 1.2313, 0.1856, 0.3243,
-1.3374, -1.0211]], grad_fn=<AddmmBackward0>)
print(label_names[torch.argmax(output,dim=1,keepdim=False)])
print(label_names[verify_label])
print("pred:",label_names[torch.argmax(output_verify,dim=1,keepdim=False)])
airplane
cat
pred: cat
Verifique o cavalo
cão de verificação