Estou fazendo uma CNN com Pytorch para uma tarefa, mas ela não aprende e melhora a precisão. Eu fiz uma versão trabalhando com o conjunto de dados MNIST para que eu pudesse publicá-la aqui. Eu só estou procurando uma resposta sobre por que não está funcionando. A arquitetura é ótima, eu a implementei no Keras e tive mais de 92% de precisão após três épocas. Nota: reformulei o MNIST em imagens de 60x60 porque é assim que as imagens estão no meu problema "real".
import numpy as np
from PIL import Image
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
from torch.autograd import Variable
from keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
def resize(pics):
pictures = []
for image in pics:
image = Image.fromarray(image).resize((dim, dim))
image = np.array(image)
pictures.append(image)
return np.array(pictures)
dim = 60
x_train, x_test = resize(x_train), resize(x_test) # because my real problem is in 60x60
x_train = x_train.reshape(-1, 1, dim, dim).astype('float32') / 255
x_test = x_test.reshape(-1, 1, dim, dim).astype('float32') / 255
y_train, y_test = y_train.astype('float32'), y_test.astype('float32')
if torch.cuda.is_available():
x_train = torch.from_numpy(x_train)[:10_000]
x_test = torch.from_numpy(x_test)[:4_000]
y_train = torch.from_numpy(y_train)[:10_000]
y_test = torch.from_numpy(y_test)[:4_000]
class ConvNet(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1, 32, 3)
self.conv2 = nn.Conv2d(32, 64, 3)
self.conv3 = nn.Conv2d(64, 128, 3)
self.fc1 = nn.Linear(5*5*128, 1024)
self.fc2 = nn.Linear(1024, 2048)
self.fc3 = nn.Linear(2048, 1)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
x = F.max_pool2d(F.relu(self.conv2(x)), (2, 2))
x = F.max_pool2d(F.relu(self.conv3(x)), (2, 2))
x = x.view(x.size(0), -1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = F.dropout(x, 0.5)
x = torch.sigmoid(self.fc3(x))
return x
net = ConvNet()
optimizer = optim.Adam(net.parameters(), lr=0.03)
loss_function = nn.BCELoss()
class FaceTrain:
def __init__(self):
self.len = x_train.shape[0]
self.x_train = x_train
self.y_train = y_train
def __getitem__(self, index):
return x_train[index], y_train[index].unsqueeze(0)
def __len__(self):
return self.len
class FaceTest:
def __init__(self):
self.len = x_test.shape[0]
self.x_test = x_test
self.y_test = y_test
def __getitem__(self, index):
return x_test[index], y_test[index].unsqueeze(0)
def __len__(self):
return self.len
train = FaceTrain()
test = FaceTest()
train_loader = DataLoader(dataset=train, batch_size=64, shuffle=True)
test_loader = DataLoader(dataset=test, batch_size=64, shuffle=True)
epochs = 10
steps = 0
train_losses, test_losses = [], []
for e in range(epochs):
running_loss = 0
for images, labels in train_loader:
optimizer.zero_grad()
log_ps = net(images)
loss = loss_function(log_ps, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
else:
test_loss = 0
accuracy = 0
with torch.no_grad():
for images, labels in test_loader:
log_ps = net(images)
test_loss += loss_function(log_ps, labels)
ps = torch.exp(log_ps)
top_p, top_class = ps.topk(1, dim=1)
equals = top_class.type('torch.LongTensor') == labels.type(torch.LongTensor).view(*top_class.shape)
accuracy += torch.mean(equals.type('torch.FloatTensor'))
train_losses.append(running_loss/len(train_loader))
test_losses.append(test_loss/len(test_loader))
print("[Epoch: {}/{}] ".format(e+1, epochs),
"[Training Loss: {:.3f}] ".format(running_loss/len(train_loader)),
"[Test Loss: {:.3f}] ".format(test_loss/len(test_loader)),
"[Test Accuracy: {:.3f}]".format(accuracy/len(test_loader)))
python
conv-neural-network
pytorch
Nicolas Gervais
fonte
fonte
comp.ai.neural-nets
perguntas frequentes têm ótimas sugestões sobre onde procurar se sua rede neural não está aprendendo; Eu recomendo começar por aí.nn.CrossEntropyLoss
para problemas de classificação de classe única.nn.CrossEntropyLoss
aplica o softmax e o NLLLoss como uma única operação, portanto, não o softmax primeiro.nn.MSELoss
mas precisará ajustar os alvos para que caiam no intervalo da saída sigmóide ou não Não aplique sigmóide após a camada final.RuntimeError: multi-target not supported
.Respostas:
Primeiro as principais questões ...
1. O principal problema com esse código é que você está usando o formato de saída errado e a função de perda incorreta para classificação.
nn.BCELoss
calcula a perda de entropia cruzada binária . Isso é aplicável quando você tem um ou mais destinos que são 0 ou 1 (daí o binário). No seu caso, o destino é um número inteiro único entre 0 e 9. Como há apenas um pequeno número de valores-alvo em potencial, a abordagem mais comum é usar a perda categórica de entropia cruzada (nn.CrossEntropyLoss
). A definição "teórica" de perda de entropia cruzada espera que as saídas da rede e os alvos sejam 10 vetores dimensionais em que o alvo seja todos os zeros, exceto em um local (codificado um a quente). No entanto, por razões de estabilidade computacional e eficiência de espaço, o pytorchnn.CrossEntropyLoss
leva diretamente o número inteiro como alvo . Contudo, você ainda precisará fornecer um vetor de saída 10 dimensional da sua rede.Para corrigir esse problema no seu código, precisamos ter
fc3
um recurso de 10 dimensões de saída e precisamos que os rótulos sejam inteiros (não flutuantes). Além disso, não há necessidade de usar.sigmoid
no fc3, pois a função de perda de entropia cruzada do pytorch aplica internamente o log-softmax antes de calcular o valor da perda final.2. Conforme apontado por Serget Dymchenko, você precisa mudar a rede para o
eval
modo durante a inferência e otrain
modo durante o trem. Isso afeta principalmente as camadas de desistência e batch_norm, pois se comportam de maneira diferente durante o treinamento e a inferência.3. Uma taxa de aprendizado de 0,03 é provavelmente um pouco alta demais. Funciona muito bem com uma taxa de aprendizado de 0,001 e, em algumas experiências, vi o treinamento divergir em 0,03.
Para acomodar essas correções, é necessário fazer várias alterações. As correções mínimas para o código são mostradas abaixo. Comentei todas as linhas que foram alteradas,
####
seguidas por uma breve descrição da mudança.Os resultados do treinamento são agora ...
Alguns outros problemas que melhorarão seu desempenho e código.
4. Você nunca está movendo o modelo para a GPU. Isso significa que você não estará recebendo aceleração da GPU.
5.
torchvision
foi desenvolvido com todas as transformações e conjuntos de dados padrão e foi desenvolvido para ser usado com o PyTorch. Eu recomendo usá-lo. Isso também remove a dependência de keras no seu código.6. Normalize seus dados subtraindo a média e dividindo pelo desvio padrão para melhorar o desempenho da sua rede. Com a visão da tocha, você pode usar
transforms.Normalize
. Isso não fará muita diferença no MNIST, porque já é fácil demais. Mas em problemas mais difíceis, acaba sendo importante.Código aprimorado adicional é mostrado abaixo (muito mais rápido na GPU).
Resultados atualizados do treinamento ...
fonte
Uma coisa que notei é que você testa o modelo no modo de trem. Você precisa ligar
net.eval()
para desativar as desistências (enet.train()
novamente para colocá-lo novamente no modo de trem).Talvez haja outras questões. A perda de treinamento está diminuindo? Você já tentou se ajustar demais em um único exemplo?
fonte