Список работ

MNIST на PyTorch

Содержание

Введение

Описание набора данных рукописных цифр MNIST и Keras-реализации нейронных сетей (НС), классифицирующие эти цифры, можно найти, например, здесь или здесь.
Здесь же в интересах учебного процесса приводится PyTorch-реализация классификатора MNIST. За формирование модели НС отвечает класс Net:

import torch
import torch.nn as nn
import torch.nn.functional as F
#
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size = 3)
        self.conv2 = nn.Conv2d(16, 32, kernel_size = 3)
        self.max_pool2d1 = nn.MaxPool2d(2)
        self.max_pool2d2 = nn.MaxPool2d(2)
        self.conv2_drop = nn.Dropout2d(p = 0.3)
        self.dropout = nn.Dropout(p = 0.3)
        self.fc1 = nn.Linear(800, 32)
        self.fc2 = nn.Linear(32, 10)
    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.max_pool2d1(x)
        x = self.conv2_drop(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = self.max_pool2d2(x) # torch.Size([256, 32, 5, 5])
        # x = F.relu(F.max_pool2d(self.conv1(x), 2))
        # x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) # 204800
        # x = F.relu(self.conv2_drop(self.conv2(x))) # 991232
        x = x.view(-1, 800) # torch.Size([256, 800]) (32 * 5 * 5 = 800)
        x = self.dropout(x)
        x = F.relu(self.fc1(x))
        # x = F.dropout(x, training = self.training) # self.training = True при обучении
        x = self.fc2(x)
        return F.log_softmax(x, dim = -1)
model = Net() # Формируем модель НС
print(model)

НС содержит 2 сверточных слоя с активации Relu, после каждого слоя следует слой подвыборки. Второму сверточному слою предшествует слой прореживания Dropout2d. После прохождения этих слоев данные преобразуются в одномерные (x = x.view(-1, 800)) и передаются слою прореживания Dropout, а затем полносвязному слою с функцией активации Relu, далее следует полносвязный классифицирующий слой с функцией активации log_softmax. На выходе НС генерирует вероятности принадлежности образа цифры к одному из 10 классов. Класс цифры определяется по большей вероятности.
На вход НС подается пакет, содержащий пары изображение – метка.
Описание используемой модели НС (model) после применения print(model):

Net(
  (conv1): Conv2d(1, 16, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(16, 32, kernel_size=(3, 3), stride=(1, 1))
  (max_pool2d1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (max_pool2d2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (conv2_drop): Dropout2d(p=0.3, inplace=False)
  (dropout): Dropout(p=0.3, inplace=False)
  (fc1): Linear(in_features=800, out_features=32, bias=True)
  (fc2): Linear(in_features=32, out_features=10, bias=True)
)

Подготовка данных

Обучающие и проверочные данные набора MNIST можно ввести из torchvision datasets (на примере проверочных данных):

# Загрузка MNIST из torchvision datasets
import torch
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
# import numpy as np
download = True
test_data = datasets.MNIST(root = 'data', train = False,
                           download = download, transform = ToTensor())
print(type(test_data)) # <class 'torchvision.datasets.mnist.MNIST'>
fg = test_data[0] # <class 'tuple>>
ind = fg[1] # 7
img = fg[0].data # shape = (1, 28, 28) # <class 'torch.Tensor'>
# img = np.squeeze(img) # или: img = img.reshape(28, 28)
img = img.squeeze()
plt.imshow(img, cmap = plt.get_cmap('gray'))
# Если не употреблять transform = ToTensor():
# img = test_data[0][0] # shape = (28, 28) # <class 'PIL.Image.Image'>
# plt.imshow(img, cmap = plt.get_cmap('gray'))
plt.title(ind)
plt.axis('off')
plt.show()

Этот же код выводит изображение первой цифры в test_data.
Каждый элемент test_data – это пара изображение – метка. Изображение будет представлено в виде вещественного тензора, если задан параметр

transform = ToTensor()

В противном случае изображение – это PIL.Image.Image.
Пары изображение – метка несложно составить, не обращаясь к torchvision datasets. Для учебного процесса MNIST записан в двоичные файлы, загрузку которых выполняет следующий код:

import numpy as np
import matplotlib.pyplot as plt
def load_bin_data(pathToData, img_rows, img_cols,
                  num_classes, show_img, useTestData):
    print('Загрузка данных из двоичных файлов')
    with open(pathToData + 'imagesTrain.bin', 'rb') as read_binary:
        x_trn = np.fromfile(read_binary, dtype = np.uint8) # <class 'numpy.ndarray'>
    with open(pathToData + 'labelsTrain.bin', 'rb') as read_binary:
        y_trn = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'imagesTest.bin', 'rb') as read_binary:
        x_tst = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'labelsTest.bin', 'rb') as read_binary:
        y_tst = np.fromfile(read_binary, dtype = np.uint8)
    # print(x_trn.shape) # (47040000,)
    # print(y_trn.shape) # (60000,)
    x_trn_shape = int(x_trn.shape[0] / (img_rows * img_cols)) # 60000
    x_tst_shape = int(x_tst.shape[0] / (img_rows * img_cols)) # 10000
    x_trn = x_trn.reshape(x_trn_shape, 1, img_rows, img_cols)
    x_tst = x_tst.reshape(x_tst_shape, 1, img_rows, img_cols)
    # print(x_trn.shape) # (60'000, 1, 28, 28)
    # print(y_trn.shape) # (60'000,)
    # print(x_tst.shape) # (10'000, 1, 28, 28)
    # print(y_tst.shape) # (10'000,)
    if show_img:
        print('Примеры', 'тестовых' if useTestData else 'обучающих', 'данных')
        # Выводим 9 изображений обучающего или тестового набора
        for i in range(9):
            plt.subplot(3, 3, i + 1)
            ind = y_tst[i] if useTestData else y_trn[i]
            img = x_tst[i] if useTestData else x_trn[i]
            img = img.reshape(img_rows, img_cols)
            plt.imshow(img, cmap = plt.get_cmap('gray'))
            plt.title(ind)
            plt.axis('off')
        plt.subplots_adjust(hspace = 0.5) # wspace
        plt.show()
    # Преобразование целочисленных данных в float32 и приведение к диапазону [0.0, 1.0]
    x_trn = np.array(x_trn, dtype = 'float32') / 255
    x_tst = np.array(x_tst, dtype = 'float32') / 255
    # print(y_trn[0]) # (MNIST) Напечатает: 5
    return x_trn, y_trn, x_tst, y_tst

Параметры загрузки и подготовки данных:

pathToData = 'G:/AM/НС/mnist/'
img_rows = img_cols = 28
num_classes = 10
show_img = not True
useTestData = True
batch_size = 256 # Размер обучающего (проверочного) пакета

Загрузка данных:

x_trn, y_trn, x_tst, y_tst = load_bin_data(pathToData, img_rows, img_cols,
                                           num_classes, show_img, useTestData)

Подготовка данных. Формируем пары изображение – метка:

trn_data = [[x, int(y)] for x, y in zip(x_trn, y_trn)]
tst_data = [[x, int(y)] for x, y in zip(x_tst, y_tst)]

Создаем обучающие и проверочные пакеты:

from torch.utils.data import DataLoader
trn_loader = DataLoader(trn_data, batch_size = batch_size, shuffle = True)
tst_loader = DataLoader(tst_data, batch_size = batch_size, shuffle = True)

Если указать show_img = True, то увидим первое изображение первого обучающего пакета:

if show_img:
    trn_features, trn_labels = next(iter(trn_loader))
    img = trn_features[0].squeeze()
    ind = int(trn_labels[0])
    plt.imshow(img, cmap = "gray")
    plt.title(ind)
    plt.axis('off')
    plt.show()

Обучение и проверка модели НС

Эпоху обучения реализует процедура train:

def train(epoch):
    trn_loss = tst_loss = 0
    model.train() # Режим обучения
    for batch_no, (data, target) in enumerate(trn_loader):
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        trn_loss += loss.item() * data.size(0)
        # if batch_no % prn_step == 0:
        #     print('Эпоха: {} [{}/{} ({:.0f}%)]\tПотери: {:.6f}'.format(
        #          epoch, batch_no * len(data), len(trn_loader.dataset),
        #          100 * batch_no / len(trn_loader), loss.item()))
    model.eval() # Режим оценки
    for data, target in tst_loader:
        output = model(data)
        loss = criterion(output, target)
        tst_loss += loss.item() * data.size(0)
    trn_loss = trn_loss / in_trn
    tst_loss = tst_loss / in_tst
    print('Эпоха: {} Потери: обучение: {:.6f} \tпроверка: {:.6f}'.format(
        epoch + 1, trn_loss, tst_loss))

При обучении вычисляются потери на обучающем множестве как усредненные потери на каждом обучающем пакете. Функция потерь – перекрестная энтропия.
После эпохи обучения выполняется оценка модели на проверочном множестве: вычисляются потери.
Параметры обучения:

train_model = True
save_model = True
load_model = True
fn_w = 'model.wei' # Файл с весами НС
prn_step = 50
n_epochs = 15 # Число эпох обучения
criterion = nn.CrossEntropyLoss() # Функция потерь
# Число примеров в обучающем и проверочном множествах
in_trn = len(trn_loader.sampler) # или: len(trn_loader.dataset) - 60000
in_tst = len(tst_loader.sampler) # или: len(y_tst) – 10000

Перед обучением (если продолжаем процесс обучения) и/или проверкой можно загрузить ранее сохраненные параметры (веса) НС:

import torch
import torch.optim as optim
if load_model:
    print('Загрузка весов из файла', fn_w)
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model.load_state_dict(torch.load(fn_w, map_location = torch.device(device)))

Обучение (если save_model = True, то веса НС будут записаны в файл):

    # optimizer = torch.optim.SGD(model.parameters(), lr = 0.01)
    optimizer = torch.optim.Adam(model.parameters(), lr = 0.01) # betas = (0.9, 0.98), eps = 1e-9
    print('Обучение')
    for ep in range(n_epochs):
        train(ep)
    if save_model:
        print('Сохранение весов модели')
        torch.save(model.state_dict(), fn_w)

После обучения или, если train_model = False, сразу после загрузки весов НС переходим к проверке, в процессе которой вычисляются потери и точность на проверочном множестве, после чего выводятся 20 изображений первого пакет проверочного множества с указанием в заголовке предсказанных и реальных классов (в скобках) рукописных цифр.
Проверку обеспечивает процедура test:

def test():
    print('Проверка')
    tst_loss = 0 # Потери на проверочном множестве (ПМ)
    # Число верно классифицированных цифр в каждом классе (ПМ)
    cls_correct = [0]*num_classes
    # Число цифр в каждом классе (ПМ)
    cls_total = [0]*num_classes
    model.eval() #Режим оценки
    for data, target in tst_loader:
        output = model(data)
        loss = criterion(output, target)
        tst_loss += loss.item() * data.size(0)
        _, pred = torch.max(output, 1)
        # print(pred) # tensor([0, 7, 0, 0, 0, 7, 0,
        # print(target.data) # tensor([2, 7, 4, 8, 0, 7, 6,
        correct = np.squeeze(pred.eq(target.data.view_as(pred)))
        # print(correct) # tensor([False, True, False, False, True, True, False,
        for i in range(len(target)):
            label = target.data[i]
            # print(correct[i]) # tensor(False)
            # print(correct[i].item()) # False
            cls_correct[label] += correct[i].item()
            cls_total[label] += 1
    # Средние потери
    tst_loss /= len(tst_loader.sampler)
    print('Потери: {:.6f}'.format(tst_loss))
    print('Точность: {:.4f}'.format(sum(cls_correct) / sum(cls_total)))
    print('Точность по классам:')
    cls = 0
    for cc, ct in zip(cls_correct, cls_total):
        print('{} - {:.4f}'.format(cls, cc / ct))
        cls += 1

Процедура вывода начальной части первого проверочного пакета с указанием в заголовке рисунков результатов классификации:

def show_res():
    print('Смотрим начальный кусок первого пакета')
    data_iter = iter(tst_loader) # Первый пакет
    images, labels = data_iter.next()
    output = model(images)
    _, pred = torch.max(output, 1)
    images = images.numpy()
    fig = plt.figure(figsize = (12, 3))
    N = 20
    for i in range(N):
        ax = fig.add_subplot(2, int(N / 2), i + 1, xticks = [], yticks = [])
        img = images[i]
        img = np.squeeze(img) # или: img = img.reshape(img_rows, img_cols)
        ax.imshow(img, cmap = 'gray')
        ttl = str(int(pred[i].item())) + ' (' + str(labels[i].item()) + ')'
        clr = 'green' if pred[i] == labels[i] else 'red'
        ax.set_title(ttl, color = clr)
    plt.show()

Проверка и вывод рисунков:

test() # Проверка
show_res() # Смотрим начальный кусок первого пакета

Заключение

С использованием приведенных сведений студентам надлежит:

Полный код программы

from sys import exit
import matplotlib.pyplot as plt
import numpy as np
#import time
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
#
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size = 3)
        self.conv2 = nn.Conv2d(16, 32, kernel_size = 3)
        self.max_pool2d1 = nn.MaxPool2d(2)
        self.max_pool2d2 = nn.MaxPool2d(2)
        self.conv2_drop = nn.Dropout2d(p = 0.3)
        self.dropout = nn.Dropout(p = 0.3)
        self.fc1 = nn.Linear(800, 32)
        self.fc2 = nn.Linear(32, 10)
    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.max_pool2d1(x)
        x = self.conv2_drop(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = self.max_pool2d2(x) # torch.Size([256, 32, 5, 5])
        # x = F.relu(F.max_pool2d(self.conv1(x), 2))
        # x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) # 204800
        # x = F.relu(self.conv2_drop(self.conv2(x))) # 991232
        x = x.view(-1, 800) # torch.Size([256, 800]) (32 * 5 * 5 = 800)
        x = self.dropout(x)
        x = F.relu(self.fc1(x))
        # x = F.dropout(x, training = self.training) # self.training = True при обучении
        x = self.fc2(x)
        return F.log_softmax(x, dim = -1)
model = Net() # Формируем модель НС
print(model)
#
def load_bin_data(pathToData, img_rows, img_cols,
                  num_classes, show_img, useTestData):
    print('Загрузка данных из двоичных файлов')
    with open(pathToData + 'imagesTrain.bin', 'rb') as read_binary:
        x_trn = np.fromfile(read_binary, dtype = np.uint8) # <class 'numpy.ndarray'>
    with open(pathToData + 'labelsTrain.bin', 'rb') as read_binary:
        y_trn = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'imagesTest.bin', 'rb') as read_binary:
        x_tst = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'labelsTest.bin', 'rb') as read_binary:
        y_tst = np.fromfile(read_binary, dtype = np.uint8)
    # print(x_trn.shape) # (47040000,)
    # print(y_trn.shape) # (60000,)
    x_trn_shape = int(x_trn.shape[0] / (img_rows * img_cols)) # 60000
    x_tst_shape = int(x_tst.shape[0] / (img_rows * img_cols)) # 10000
    x_trn = x_trn.reshape(x_trn_shape, 1, img_rows, img_cols)
    x_tst = x_tst.reshape(x_tst_shape, 1, img_rows, img_cols)
    # print(x_trn.shape) # (60'000, 1, 28, 28)
    # print(y_trn.shape) # (60'000,)
    # print(x_tst.shape) # (10'000, 1, 28, 28)
    # print(y_tst.shape) # (10'000,)
    if show_img:
        print('Примеры', 'тестовых' if useTestData else 'обучающих', 'данных')
        # Выводим 9 изображений обучающего или тестового набора
        for i in range(9):
            plt.subplot(3, 3, i + 1)
            ind = y_tst[i] if useTestData else y_trn[i]
            img = x_tst[i] if useTestData else x_trn[i]
            img = img.reshape(img_rows, img_cols)
            plt.imshow(img, cmap = plt.get_cmap('gray'))
            plt.title(ind)
            plt.axis('off')
        plt.subplots_adjust(hspace = 0.5) # wspace
        plt.show()
    # Преобразование целочисленных данных в float32 и приведение к диапазону [0.0, 1.0]
    x_trn = np.array(x_trn, dtype = 'float32') / 255
    x_tst = np.array(x_tst, dtype = 'float32') / 255
    # print(y_trn[0]) # (MNIST) Напечатает: 5
    return x_trn, y_trn, x_tst, y_tst
#
pathToData = 'G:/AM/НС/mnist/'
img_rows = img_cols = 28
num_classes = 10
show_img = not True
useTestData = True
batch_size = 256 # Размер обучающего (проверочного) пакета
train_model = not True
save_model = True
load_model = True
fn_w = 'model.wei' # Файл с весами НС
prn_step = 50
n_epochs = 15 # Число эпох обучения
criterion = nn.CrossEntropyLoss() # Функция потерь
#
x_trn, y_trn, x_tst, y_tst = load_bin_data(pathToData, img_rows, img_cols,
                                           num_classes, show_img, useTestData)
trn_data = [[x, int(y)] for x, y in zip(x_trn, y_trn)]
tst_data = [[x, int(y)] for x, y in zip(x_tst, y_tst)]
# Формирование обучающих и проверочных пакетов
trn_loader = DataLoader(trn_data, batch_size = batch_size, shuffle = True)
tst_loader = DataLoader(tst_data, batch_size = batch_size, shuffle = True)
# Число примеров в обучающем и проверочном множествах
in_trn = len(trn_loader.sampler) # или: len(trn_loader.dataset) - 60000
in_tst = len(tst_loader.sampler) # или: len(y_tst) - 10000
# print(len(trn_loader)) # 235 - число пакетов в обучающем множестве
if show_img:
    trn_features, trn_labels = next(iter(trn_loader))
    img = trn_features[0].squeeze()
    ind = int(trn_labels[0])
    plt.imshow(img, cmap = "gray")
    plt.title(ind)
    plt.axis('off')
    plt.show()
def train(epoch):
    trn_loss = tst_loss = 0
    model.train() # Режим обучения
    for batch_no, (data, target) in enumerate(trn_loader):
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        trn_loss += loss.item() * data.size(0)
        # if batch_no % prn_step == 0:
        #     print('Эпоха: {} [{}/{} ({:.0f}%)]\tПотери: {:.6f}'.format(
        #          epoch, batch_no * len(data), len(trn_loader.dataset),
        #          100 * batch_no / len(trn_loader), loss.item()))
    model.eval() # Режим оценки
    for data, target in tst_loader:
        output = model(data)
        loss = criterion(output, target)
        tst_loss += loss.item() * data.size(0)
    trn_loss = trn_loss / in_trn
    tst_loss = tst_loss / in_tst
    print('Эпоха: {} Потери: обучение: {:.6f} \tпроверка: {:.6f}'.format(
        epoch + 1, trn_loss, tst_loss))
if load_model:
    print('Загрузка весов из файла', fn_w)
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model.load_state_dict(torch.load(fn_w, map_location = torch.device(device)))
if train_model:
    # optimizer = torch.optim.SGD(model.parameters(), lr = 0.01)
    optimizer = torch.optim.Adam(model.parameters(), lr = 0.01) # betas = (0.9, 0.98), eps = 1e-9
    print('Обучение')
    for ep in range(n_epochs):
        train(ep)
    if save_model:
        print('Сохранение весов модели')
        torch.save(model.state_dict(), fn_w)
def test():
    print('Проверка')
    tst_loss = 0 # Потери на проверочном множестве (ПМ)
    # Число верно классифицированных цифр в каждом классе (ПМ)
    cls_correct = [0]*num_classes
    # Число цифр в каждом классе (ПМ)
    cls_total = [0]*num_classes
    model.eval() #Режим оценки
    for data, target in tst_loader:
        output = model(data)
        loss = criterion(output, target)
        tst_loss += loss.item() * data.size(0)
        _, pred = torch.max(output, 1)
        # print(pred) # tensor([0, 7, 0, 0, 0, 7, 0,
        # print(target.data) # tensor([2, 7, 4, 8, 0, 7, 6,
        correct = np.squeeze(pred.eq(target.data.view_as(pred)))
        # print(correct) # tensor([False, True, False, False, True, True, False,
        for i in range(len(target)):
            label = target.data[i]
            # print(correct[i]) # tensor(False)
            # print(correct[i].item()) # False
            cls_correct[label] += correct[i].item()
            cls_total[label] += 1
    # Средние потери
    tst_loss /= len(tst_loader.sampler)
    print('Потери: {:.6f}'.format(tst_loss))
    print('Точность: {:.4f}'.format(sum(cls_correct) / sum(cls_total)))
    print('Точность по классам:')
    cls = 0
    for cc, ct in zip(cls_correct, cls_total):
        print('{} - {:.4f}'.format(cls, cc / ct))
        cls += 1
def show_res():
    print('Смотрим начальный кусок первого пакета')
    data_iter = iter(tst_loader) # Первый пакет
    images, labels = data_iter.next()
    output = model(images)
    _, pred = torch.max(output, 1)
    images = images.numpy()
    fig = plt.figure(figsize = (12, 3))
    N = 20
    for i in range(N):
        ax = fig.add_subplot(2, int(N / 2), i + 1, xticks = [], yticks = [])
        img = images[i]
        img = np.squeeze(img) # или: img = img.reshape(img_rows, img_cols)
        ax.imshow(img, cmap = 'gray')
        ttl = str(int(pred[i].item())) + ' (' + str(labels[i].item()) + ')'
        clr = 'green' if pred[i] == labels[i] else 'red'
        ax.set_title(ttl, color = clr)
    plt.show()
test() # Проверка
show_res() # Смотрим начальный кусок первого пакета

Список работ

Рейтинг@Mail.ru