Mất không giảm trong Pytorch CNN


8

Tôi đang làm CNN với Pytorch cho một nhiệm vụ, nhưng nó sẽ không học và cải thiện độ chính xác. Tôi đã tạo một phiên bản làm việc với bộ dữ liệu MNIST để tôi có thể đăng nó ở đây. Tôi chỉ đang tìm kiếm một câu trả lời là tại sao nó không hoạt động. Kiến trúc rất ổn, tôi đã triển khai nó trong Keras và tôi đã có độ chính xác hơn 92% sau 3 kỷ nguyên. Lưu ý: Tôi đã định hình lại MNIST thành các hình ảnh 60x60 vì đó là cách các hình ảnh trong vấn đề "thực" của tôi.

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)))

1
Các comp.ai.neural-netsCâu Hỏi Thường Gặp có một số gợi ý tuyệt vời về nơi để xem xét nếu ròng thần kinh của bạn không được học tập; Tôi khuyên bạn nên bắt đầu từ đó.
Ari Cooper-Davis

Hàm mất, hình dạng đầu ra của mạng và nhãn đích không có ý nghĩa ở đây (ít nhất sự kết hợp này là sai). MNIST có 10 lớp và các nhãn là một số nguyên từ 0 đến 9. BCELoss mong đợi một giá trị duy nhất trong khoảng từ 0 đến 1 cho mỗi mục tiêu. Thay vào đó, bạn nên xuất ra 10 bản ghi (không nhất thiết phải sigmoided) và sau đó sử dụng nn.CrossEntropyLosscho bài toán phân loại lớp đơn. nn.CrossEntropyLossáp dụng cả softmax và NLLLoss dưới dạng một opperation duy nhất, vì vậy trước tiên đừng dùng softmax.
jodag

Ngoài ra, nếu bạn muốn thực hiện một vấn đề hồi quy, tức là ước tính một số thực tế là đầu ra (không được khuyến nghị cho các vấn đề loại phân loại) thì bạn có thể thử nn.MSELossnhưng sau đó bạn cần điều chỉnh các mục tiêu để chúng nằm trong phạm vi đầu ra sigmoid hoặc don ' t áp dụng sigmoid sau lớp cuối cùng.
jodag

Bạn đã có nó để làm việc theo cách này? Bây giờ nó nói với tôi điều đó RuntimeError: multi-target not supported.
Nicolas Gervais

bạn cần phải bóp một kích thước của nhãn (nó phải là một thang đo 1D của số nguyên kích thước của kích thước lô)
jodag

Câu trả lời:


7

Đầu tiên là những vấn đề chính ...

1. Vấn đề chính với mã này là bạn đang sử dụng hình dạng đầu ra sai và chức năng mất sai để phân loại.

nn.BCELosstính toán tổn thất entropy chéo nhị phân . Điều này có thể áp dụng khi bạn có một hoặc nhiều mục tiêu là 0 hoặc 1 (do đó là nhị phân). Trong trường hợp của bạn, mục tiêu là một số nguyên duy nhất trong khoảng từ 0 đến 9. Vì chỉ có một số lượng nhỏ các giá trị mục tiêu tiềm năng, nên cách tiếp cận phổ biến nhất là sử dụng mất entropy chéo phân loại ( nn.CrossEntropyLoss). Định nghĩa "lý thuyết" về mất entropy chéo dự kiến ​​các đầu ra của mạng và các mục tiêu đều là các vectơ 10 chiều trong đó mục tiêu là tất cả các số 0 trừ một vị trí (được mã hóa một lần nóng). Tuy nhiên, vì sự ổn định tính toán và lý do hiệu quả không gian, pytorch nn.CrossEntropyLosstrực tiếp lấy số nguyên làm mục tiêu . Tuy nhiên, bạn vẫn cần cung cấp cho nó một vectơ đầu ra 10 chiều từ mạng của mình.

# pseudo code (ignoring batch dimension)
loss = nn.functional.cross_entropy_loss(<output 10d vector>, <integer target>)

Để khắc phục vấn đề này trong mã của bạn, chúng tôi cần có fc3tính năng 10 chiều và chúng tôi cần các nhãn là số nguyên (không phải số float). Ngoài ra, không cần sử dụng .sigmoidtrên fc3 vì hàm mất entropy chéo của pytorch áp dụng nội bộ log-softmax trước khi tính giá trị tổn thất cuối cùng.

2. Như Serget Dymchenko đã chỉ ra, bạn cần chuyển mạng sang evalchế độ trong khi suy luận và trainchế độ trong khi đi tàu. Điều này chủ yếu ảnh hưởng đến các lớp bỏ học và batch_norm vì chúng hoạt động khác nhau trong quá trình đào tạo và suy luận.

3. Tỷ lệ học tập 0,03 có lẽ hơi quá cao. Nó hoạt động tốt với tỷ lệ học tập là 0,001 và trong một vài thí nghiệm tôi đã thấy sự phân kỳ đào tạo ở mức 0,03.


Để phù hợp với các bản sửa lỗi này, một số thay đổi cần phải được thực hiện. Các chỉnh sửa tối thiểu cho mã được hiển thị dưới đây. Tôi đã nhận xét bất kỳ dòng nào đã được thay đổi kèm ####theo một mô tả ngắn về sự thay đổi.

import numpy as np
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
#### float32 -> int64
y_train, y_test = y_train.astype('int64'), y_test.astype('int64')

#### no reason to test for cuda before converting to numpy

#### I assume you were taking a subset for debugging? No reason to not use all the data
x_train = torch.from_numpy(x_train)
x_test = torch.from_numpy(x_test)
y_train = torch.from_numpy(y_train)
y_test = torch.from_numpy(y_test)


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)
        #### 1 -> 10
        self.fc3 = nn.Linear(2048, 10)

    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)
        #### removed sigmoid
        x = self.fc3(x)
        return x


net = ConvNet()

#### 0.03 -> 1e-3
optimizer = optim.Adam(net.parameters(), lr=1e-3)

#### BCELoss -> CrossEntropyLoss
loss_function = nn.CrossEntropyLoss()


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):
        #### .unsqueeze(0) removed
        return x_train[index], y_train[index]

    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):
        #### .unsqueeze(0) removed
        return x_test[index], y_test[index]

    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
    #### put net in train mode
    net.train()
    for idx, (images, labels) in enumerate(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

        #### put net in eval mode
        net.eval()
        with torch.no_grad():
            for images, labels in test_loader:
                log_ps = net(images)
                test_loss += loss_function(log_ps, labels)
                #### removed torch.exp() since exponential is monotone, taking it doesn't change the order of outputs. Similarly with torch.softmax()
                top_p, top_class = log_ps.topk(1, dim=1)
                #### convert to float/long using proper methods. what you have won't work for cuda tensors.
                equals = top_class.long() == labels.long().view(*top_class.shape)
                accuracy += torch.mean(equals.float())
        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)))

Kết quả đào tạo hiện đang ...

[Epoch: 1/10]  [Training Loss: 0.139]  [Test Loss: 0.046]  [Test Accuracy: 0.986]
[Epoch: 2/10]  [Training Loss: 0.046]  [Test Loss: 0.042]  [Test Accuracy: 0.987]
[Epoch: 3/10]  [Training Loss: 0.031]  [Test Loss: 0.040]  [Test Accuracy: 0.988]
[Epoch: 4/10]  [Training Loss: 0.022]  [Test Loss: 0.029]  [Test Accuracy: 0.990]
[Epoch: 5/10]  [Training Loss: 0.017]  [Test Loss: 0.066]  [Test Accuracy: 0.987]
[Epoch: 6/10]  [Training Loss: 0.015]  [Test Loss: 0.056]  [Test Accuracy: 0.985]
[Epoch: 7/10]  [Training Loss: 0.018]  [Test Loss: 0.039]  [Test Accuracy: 0.991]
[Epoch: 8/10]  [Training Loss: 0.012]  [Test Loss: 0.057]  [Test Accuracy: 0.988]
[Epoch: 9/10]  [Training Loss: 0.012]  [Test Loss: 0.041]  [Test Accuracy: 0.991]
[Epoch: 10/10]  [Training Loss: 0.007]  [Test Loss: 0.048]  [Test Accuracy: 0.992]

Một số vấn đề khác sẽ cải thiện hiệu suất và mã của bạn.

4. Bạn sẽ không bao giờ di chuyển mô hình sang GPU. Điều này có nghĩa là bạn sẽ không được tăng tốc GPU.

5. torchvision được thiết kế với tất cả các biến đổi và bộ dữ liệu tiêu chuẩn và được xây dựng để sử dụng với PyTorch. Tôi khuyên bạn nên sử dụng nó. Điều này cũng loại bỏ sự phụ thuộc vào máy ảnh trong mã của bạn.

6. Bình thường hóa dữ liệu của bạn bằng cách trừ giá trị trung bình và chia cho độ lệch chuẩn để cải thiện hiệu suất của mạng. Với đèn pin bạn có thể sử dụng transforms.Normalize. Điều này sẽ không tạo ra sự khác biệt lớn trong MNIST vì nó đã quá dễ dàng. Nhưng trong những vấn đề khó khăn hơn, nó trở nên quan trọng.


Mã cải tiến hơn nữa được hiển thị bên dưới (nhanh hơn nhiều trên GPU).

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 torchvision.datasets import MNIST
from torchvision import transforms

dim = 60

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, 10)

    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 = self.fc3(x)
        return x


net = ConvNet()
if torch.cuda.is_available():
    net.cuda()

optimizer = optim.Adam(net.parameters(), lr=1e-3)

loss_function = nn.CrossEntropyLoss()

train_dataset = MNIST('./data', train=True, download=True,
                      transform=transforms.Compose([
                          transforms.Resize((dim, dim)),
                          transforms.ToTensor(),
                          transforms.Normalize((0.1307,), (0.3081,))
                      ]))
test_dataset = MNIST('./data', train=False, download=True,
                     transform=transforms.Compose([
                         transforms.Resize((dim, dim)),
                         transforms.ToTensor(),
                         transforms.Normalize((0.1307,), (0.3081,))
                     ]))

train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True, num_workers=8)
test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False, num_workers=8)

epochs = 10
steps = 0
train_losses, test_losses = [], []
for e in range(epochs):
    running_loss = 0
    net.train()
    for images, labels in train_loader:
        if torch.cuda.is_available():
            images, labels = images.cuda(), labels.cuda()
        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

        net.eval()
        with torch.no_grad():
            for images, labels in test_loader:
                if torch.cuda.is_available():
                    images, labels = images.cuda(), labels.cuda()
                log_ps = net(images)
                test_loss += loss_function(log_ps, labels)
                top_p, top_class = log_ps.topk(1, dim=1)
                equals = top_class.flatten().long() == labels
                accuracy += torch.mean(equals.float()).item()
        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)))

Cập nhật kết quả đào tạo ...

[Epoch: 1/10]  [Training Loss: 0.125]  [Test Loss: 0.045]  [Test Accuracy: 0.987]
[Epoch: 2/10]  [Training Loss: 0.043]  [Test Loss: 0.031]  [Test Accuracy: 0.991]
[Epoch: 3/10]  [Training Loss: 0.030]  [Test Loss: 0.030]  [Test Accuracy: 0.991]
[Epoch: 4/10]  [Training Loss: 0.024]  [Test Loss: 0.046]  [Test Accuracy: 0.990]
[Epoch: 5/10]  [Training Loss: 0.020]  [Test Loss: 0.032]  [Test Accuracy: 0.992]
[Epoch: 6/10]  [Training Loss: 0.017]  [Test Loss: 0.046]  [Test Accuracy: 0.991]
[Epoch: 7/10]  [Training Loss: 0.015]  [Test Loss: 0.034]  [Test Accuracy: 0.992]
[Epoch: 8/10]  [Training Loss: 0.011]  [Test Loss: 0.048]  [Test Accuracy: 0.992]
[Epoch: 9/10]  [Training Loss: 0.012]  [Test Loss: 0.037]  [Test Accuracy: 0.991]
[Epoch: 10/10]  [Training Loss: 0.013]  [Test Loss: 0.038]  [Test Accuracy: 0.992]

1
Nó đã làm việc! Tôi thực sự đã phạm một sai lầm lớn, vấn đề đơn giản hóa MNIST này có 10 lớp và vấn đề của tôi chỉ có hai. Vì vậy, tôi không thể sử dụng mọi thứ bạn đã làm. Nhưng chơi xung quanh với các khuyến nghị của bạn, tôi đã có thể làm cho nó hoạt động, vì vậy cảm ơn bạn!
Nicolas Gervais

4

Một điều tôi nhận thấy rằng bạn kiểm tra mô hình trong chế độ tàu. Bạn cần gọi net.eval()để vô hiệu hóa bỏ học (và sau đó net.train()một lần nữa để đưa nó trở lại chế độ tàu).

Có thể có những vấn đề khác. Là mất đào tạo đi xuống? Bạn đã cố gắng để phù hợp với một ví dụ duy nhất?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.