MNIST — классический игрушечный набор данных для распознавания изображений. Набор данных состоит из рукописных изображений, которые послужили основой для сравнительного анализа алгоритмов классификации.
Вы можете запустить этот блокнот на Kaggle.
Содержание:
- Импорт
- Подготовка данных
- Исследуйте данные
- Модель
- Simple Feed Forward NN (точность теста 96%)
- CNN на основе LeNet5 (точность теста 98%)
- Подготовка тестовых данных
- Глядя на прогноз
- Экспорт данных
- Ансамбли (точность теста 98%)
- История коммитов
Импорт
# Basic Torch import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader import torchvision.transforms as transforms import torchvision from torch.utils.data import TensorDataset from torch.optim import Adam, SGD # Basic Numeric Computation import numpy as np import pandas as pd # Look at data from matplotlib import pyplot # Easy way to split train data from sklearn.model_selection import train_test_split # Looking at directory import os base_dir = "../input" print(os.listdir(base_dir)) device = torch.device("cpu")# if torch.cuda.is_available() else torch.device("cpu") device epochs=10
Вывод:
['train.csv', 'sample_submission.csv', 'test.csv']
Подготовка данных
Извлечение нагрузки преобразования (ETL)
1. Извлечение
train = pd.read_csv(base_dir + '/train.csv') test = pd.read_csv(base_dir + '/test.csv') train.head()
2. Преобразование
# Convert Dataframe into format ready for training def createImageData(raw: pd.DataFrame): y = raw['label'].values y.resize(y.shape[0],1) x = raw[[i for i in raw.columns if i != 'label']].values x = x.reshape([-1,1, 28, 28]) y = y.astype(int).reshape(-1) x = x.astype(float) return x, y ## Convert to One Hot Encoding def one_hot_embedding(labels, num_classes=10): y = torch.eye(num_classes) return y[labels] x_train, y_train = createImageData(train) #x_train, x_val, y_train, y_val = train_test_split(x,y, test_size=0.02) #x_train.shape, y_train.shape, x_val.shape, y_val.shape x_train.shape, y_train.shape
Вне:
((42000, 1, 28, 28), (42000,)) # Normalization mean = x_train.mean() std = x_train.std() x_train = (x_train-mean)/std #x_val = (x_val-mean)/std # Numpy to Torch Tensor x_train = torch.from_numpy(np.float32(x_train)).to(device) y_train = torch.from_numpy(y_train.astype(np.long)).to(device) y_train = one_hot_embedding(y_train) #x_val = torch.from_numpy(np.float32(x_val)) #y_val = torch.from_numpy(y_val.astype(np.long))
3. Загрузка
# Convert into Torch Dataset train_ds = TensorDataset(x_train, y_train) #val_ds = TensorDataset(x_val,y_val)
Вывод:
# Make Data Loader train_dl = DataLoader(train_ds, batch_size=64)
Исследуйте данные
Всегда полезно просмотреть свои данные перед построением модели. Это помогает понять, с чем будет иметь дело модель, и устраняет предположения, которые вы могли неосознанно ввести в свою модель.
index = 1 pyplot.imshow(x_train.cpu()[index].reshape((28, 28)), cmap="gray") print(y_train[index])
Вывод:
tensor([1., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
Модель
Ниже приведены вспомогательные функции. Первоначально они были написаны как обычные функции Pytorch, но позже были абстрагированы, чтобы сделать код чистым и легко использовать повторно позже.
# Helper Functions ## Initialize weight with xavier_uniform def init_weights(m): if type(m) == nn.Linear: torch.nn.init.xavier_uniform(m.weight) m.bias.data.fill_(0.01) ## Flatten Later class Flatten(nn.Module): def forward(self, input): return input.view(input.size(0), -1) # Train the network and print accuracy and loss overtime def fit(train_dl, model, loss, optim, epochs=10): model = model.to(device) print('Epoch\tAccuracy\tLoss') accuracy_overtime = [] loss_overtime = [] for epoch in range(epochs): avg_loss = 0 correct = 0 total=0 for x, y in train_dl: # Iterate over Data Loder # Forward pass yhat = model(x) l = loss(y, yhat) #Metrics avg_loss+=l.item() # Backward pass optim.zero_grad() l.backward() optim.step() # Metrics _, original = torch.max(y, 1) _, predicted = torch.max(yhat.data, 1) total += y.size(0) correct = correct + (original == predicted).sum().item() accuracy_overtime.append(correct/total) loss_overtime.append(avg_loss/len(train_dl)) print(epoch,accuracy_overtime[-1], loss_overtime[-1], sep='\t') return accuracy_overtime, loss_overtime # Plot Accuracy and Loss of Model def plot_accuracy_loss(accuracy, loss): f = pyplot.figure(figsize=(15,5)) ax1 = f.add_subplot(121) ax2 = f.add_subplot(122) ax1.title.set_text("Accuracy over epochs") ax2.title.set_text("Loss over epochs") ax1.plot(accuracy) ax2.plot(loss, 'r:') # Take an array and show what model predicts def predict_for_index(array, model, index): testing = array[index].view(1,28,28) pyplot.imshow(x_train[index].reshape((28, 28)), cmap="gray") print(x_train[index].shape) a = model(testing.float()) print('Prediction',torch.argmax(a,1))
Нейронная сеть прямого распространения с двумя скрытыми слоями
Нейронная сеть с прямой связью не идеальна для классификации изображений. Причина: параметры не используются совместно.
Кроме того, если вы увеличите эпоху за пределы 10, вы обнаружите, что модель быстро переобучается.
Сначала я сделал тупую модель и сосредоточился на том, чтобы пройти весь поток. Это связано с тем, что глубокое обучение — это итеративный процесс, и слишком много времени, потраченное на создание идеальной модели, может привести к потере большого количества времени без результата.
Я буду делать Convolutional NN позже, что потребует лучшей настройки гиперпараметров.
Вход в эту нейронную сеть с прямой связью составляет 784 функции. Целевые метки для прогнозирования — 10. Я интуитивно выбираю 100 как количество скрытых единиц в слое, потому что это золотая середина между 784 и 10.
(m, 784) -> (m, 100) -> (m,10)
m: количество изделий в мини-партии
# Define the model ff_model = nn.Sequential( Flatten(), nn.Linear(28*28, 100), nn.ReLU(), nn.Linear(100, 10), nn.Softmax(1), ).to(device) # Initialize model with xavier initialization which is recommended for ReLu ff_model.apply(init_weights)
Выход[12]:
Sequential( (0): Flatten() (1): Linear(in_features=784, out_features=100, bias=True) (2): ReLU() (3): Linear(in_features=100, out_features=10, bias=True) (4): Softmax() )
Определить гиперпараметры и обучить
optim = Adam(ff_model.parameters()) loss = nn.MSELoss() output = fit(train_dl, ff_model, loss, optim, epochs) plot_accuracy_loss(*output)
Вывод:
Epoch Accuracy Loss 0 0.9061666666666667 0.013730582292927031 1 0.9598333333333333 0.006338598204933814 2 0.9694047619047619 0.004766992041984232 3 0.9772857142857143 0.003731012495671476 4 0.9806666666666667 0.003179915646412032 5 0.9833571428571428 0.002766264982197474 6 0.9863571428571428 0.002329402380182783 7 0.9866428571428572 0.002256228744125365 8 0.9878333333333333 0.0020849015744137766 9 0.9884285714285714 0.0019292909375259308
Глядя на прогноз, который делает модель
index = 4 predict_for_index(x_train, ff_model, index)
Вывод:
torch.Size([1, 28, 28]) Prediction tensor([0])
Сверточный NN
CNN идеально подходит для изображений, потому что параметры, используемые для обнаружения чего-либо в одной части изображения, такие же, как когда-то используемые в других частях изображения.
Первоначально я начал с простой CNN, которая не была точной и часто давала переобучение. Мне пришлось пересмотреть видео из курса сверточных нейронных сетей Эндрю Нг, и я снова узнал, что всегда лучше черпать вдохновение из других успешных моделей.
Эта модель имеет точность 0,98942.
Исходники для создания модели:
- LeNet-5 — классическая архитектура CNN (ссылка на изображение)
- DeepLearning.ai-Summary/Сверточные нейронные сети
# A too simple NN taken from pytorch.org/tutorials class Mnist_CNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1) self.conv2 = nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1) self.conv3 = nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1) def forward(self, xb): xb = xb.view(-1, 1, 28, 28) xb = F.relu(self.conv1(xb)) xb = F.relu(self.conv2(xb)) xb = F.relu(self.conv3(xb)) xb = F.avg_pool2d(xb, 4) return xb.view(-1, xb.size(1)) class LeNet5(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(1, 6, kernel_size=5, stride=1) self.average1 = nn.AvgPool2d(2, stride=2) self.conv2 = nn.Conv2d(6, 16, kernel_size=5, stride=1) self.average2 = nn.AvgPool2d(2, stride=2) self.conv3 = nn.Conv2d(16, 120, kernel_size=4, stride=1) self.flatten = Flatten() self.fc1 = nn.Linear(120, 82) self.fc2 = nn.Linear(82,10) def forward(self, xb): xb = xb.view(-1, 1, 28, 28) xb = F.tanh(self.conv1(xb)) xb = self.average1(xb) xb = F.tanh(self.conv2(xb)) xb = self.average2(xb) xb = F.tanh(self.conv3(xb)) xb = xb.view(-1, xb.shape[1]) xb = F.relu(self.fc1(xb)) xb = F.relu(self.fc2(xb)) return xb
In [17]:
conv_model = LeNet5() conv_model.apply(init_weights) loss = nn.MSELoss() optim = SGD(conv_model.parameters(), lr=0.1, momentum=0.9) plot_accuracy_loss(*fit(train_dl, conv_model,loss,optim,epochs)
Вывод:
Epoch Accuracy Loss 0 0.8568095238095238 0.022169289579246437 1 0.964 0.00809634799903891 2 0.9754285714285714 0.006067726472456468 3 0.980452380952381 0.00498412443012002 4 0.9839761904761904 0.004297092746283372 5 0.9863809523809524 0.0038011955834260347 6 0.9877619047619047 0.003426484330387182 7 0.9887142857142858 0.0031120370952284907 8 0.9897857142857143 0.0028682300103583785 9 0.9905952380952381 0.0026617846957140688
Подготовка тестовых данных
Нормализация: нам нужно нормализовать те же значения, которые использовались для нормализации данных поезда, иначе это приведет к неравномерному распределению между поездом и тестовым набором.
In [18]:
x_test = test.values x_test = x_test.reshape([-1, 28, 28]).astype(float) x_test = (x_test-mean)/std x_test = torch.from_numpy(np.float32(x_test)) x_test.shape
Вышли[18]:
torch.Size([28000, 28, 28])
Глядя на прогноз
In [19]:
index = 7 predict_for_index(x_test, ff_model, index) predict_for_index(x_test, conv_model, index)
Вне:
torch.Size([1, 28, 28]) Prediction tensor([3]) torch.Size([1, 28, 28]) Prediction tensor([3])
Экспорт данных для отправки
In [20]:
# Export data to CSV in format of submission def export_csv(model_name, predictions, commit_no): df = pd.DataFrame(prediction.tolist(), columns=['Label']) df['ImageId'] = df.index + 1 file_name = f'submission_{model_name}_v{commit_no}.csv' print('Saving ',file_name) df[['ImageId','Label']].to_csv(file_name, index = False)
In [21]:
test.head()
In [22]:
# just to make output easier to read commit_no = 7
In [23]:
ff_test_yhat = ff_model(x_test.float()) prediction = torch.argmax(ff_test_yhat,1) print('Prediction',prediction) export_csv('ff_model',prediction, commit_no=commit_no)
Вне:
Prediction tensor([2, 0, 9, ..., 3, 9, 2]) Saving submission_ff_model_v7.csv
In [24]:
cn_train_yhat = conv_model(x_test) prediction = torch.argmax(cn_train_yhat,1) yo = torch.argmax(y_train,1) export_csv('lenet_model',prediction, commit_no=commit_no)
Вне:
Saving submission_lenet_model_v7.csv
Ансамбль
Ансамбль — популярный метод, обычно используемый на соревнованиях, который позволяет комбинировать результаты разных моделей для получения более высокой точности.
В этом конкретном случае кажется, что LeNet был намного лучше, чем сеть с прямой связью, поэтому точность ансамбля на тестовых данных такая же, как у LeNet5–0,98942
In [25]:
ensemble = ff_test_yhat + cn_train_yhat # Add probabilities of individual predictions ensemble_one_hot = torch.argmax(y_train,1) # Find argmax export_csv('ensemble',ensemble_one_hot, commit_no=commit_no)
Вне:
Saving submission_ensemble_v7.csv
Вы можете запустить этот блокнот на Kaggle.
Если у вас есть какие-либо советы, предложения или какой-либо интересный набор данных, вы можете связаться с разделом комментариев ниже.