Предыдущая статья — Глубокое обучение и нейронные сети с Python и Pytorch. Часть VI: модель сверточной нейронной сети.
Данная статья предполагает, что у вас есть доступ к GPU либо локально, либо через облако.
Если вы используете сервер, вам нужно будет загрузить данные, извлечь, а также установить jupyter notebook
:
wget https://download.microsoft.com/download/3/E/1/3E1C3F21-ECDB-4869-8368-6DEBA77B919F/kagglecatsanddogs_3367a.zip sudo apt-get install unzip unzip kagglecatsanddogs_3367a.zip pip3 install jupyterlab
Далее вы можете запустить его следующим образом:
jupyter lab --allow-root --ip=0.0.0.0
После этого вы увидите что-то вроде этого:
To access the notebook, open this file in a browser: file:///root/.local/share/jupyter/runtime/nbserver-1470-open.html Or copy and paste one of these URLs: http://localhost:8888/?token=f407ba1f9a362822f2a294277b2be3e9 or http://127.0.0.1:8888/?token=f407ba1f9a362822f2a294277b2be3e9
Затем вам нужно будет перейти по указанному выше URL-адресу, заменив 127.0.0.1 на IP-адрес вашего сервера.
Наш код на настоящий момент имеет следующий вид:
import os import cv2 import numpy as np from tqdm import tqdm import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim REBUILD_DATA = False # set to true to one once, then back to false unless you want to change something in your training data. class DogsVSCats(): IMG_SIZE = 50 CATS = "PetImages/Cat" DOGS = "PetImages/Dog" TESTING = "PetImages/Testing" LABELS = {CATS: 0, DOGS: 1} training_data = [] catcount = 0 dogcount = 0 def make_training_data(self): for label in self.LABELS: print(label) for f in tqdm(os.listdir(label)): if "jpg" in f: try: path = os.path.join(label, f) img = cv2.imread(path, cv2.IMREAD_GRAYSCALE) img = cv2.resize(img, (self.IMG_SIZE, self.IMG_SIZE)) self.training_data.append([np.array(img), np.eye(2)[self.LABELS[label]]]) # do something like print(np.eye(2)[1]), just makes one_hot #print(np.eye(2)[self.LABELS[label]]) if label == self.CATS: self.catcount += 1 elif label == self.DOGS: self.dogcount += 1 except Exception as e: pass #print(label, f, str(e)) np.random.shuffle(self.training_data) np.save("training_data.npy", self.training_data) print('Cats:',dogsvcats.catcount) print('Dogs:',dogsvcats.dogcount) class Net(nn.Module): def __init__(self): super().__init__() # just run the init of parent class (nn.Module) self.conv1 = nn.Conv2d(1, 32, 5) # input is 1 image, 32 output channels, 5x5 kernel / window self.conv2 = nn.Conv2d(32, 64, 5) # input is 32, bc the first layer output 32. Then we say the output will be 64 channels, 5x5 kernel / window self.conv3 = nn.Conv2d(64, 128, 5) x = torch.randn(50,50).view(-1,1,50,50) self._to_linear = None self.convs(x) self.fc1 = nn.Linear(self._to_linear, 512) #flattening. self.fc2 = nn.Linear(512, 2) # 512 in, 2 out bc we're doing 2 classes (dog vs cat). def convs(self, x): # max pooling over 2x2 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)) if self._to_linear is None: self._to_linear = x[0].shape[0]*x[0].shape[1]*x[0].shape[2] return x def forward(self, x): x = self.convs(x) x = x.view(-1, self._to_linear) # .view is reshape ... this flattens X before x = F.relu(self.fc1(x)) x = self.fc2(x) # bc this is our output layer. No activation here. return F.softmax(x, dim=1) net = Net() print(net) if REBUILD_DATA: dogsvcats = DogsVSCats() dogsvcats.make_training_data() training_data = np.load("training_data.npy", allow_pickle=True) print(len(training_data)) optimizer = optim.Adam(net.parameters(), lr=0.001) loss_function = nn.MSELoss() X = torch.Tensor([i[0] for i in training_data]).view(-1,50,50) X = X/255.0 y = torch.Tensor([i[1] for i in training_data]) VAL_PCT = 0.1 # lets reserve 10% of our data for validation val_size = int(len(X)*VAL_PCT) train_X = X[:-val_size] train_y = y[:-val_size] test_X = X[-val_size:] test_y = y[-val_size:] BATCH_SIZE = 100 EPOCHS = 1 def train(net): for epoch in range(EPOCHS): for i in tqdm(range(0, len(train_X), BATCH_SIZE)): # from 0, to the len of x, stepping BATCH_SIZE at a time. [:50] ..for now just to dev #print(f"{i}:{i+BATCH_SIZE}") batch_X = train_X[i:i+BATCH_SIZE].view(-1, 1, 50, 50) batch_y = train_y[i:i+BATCH_SIZE] net.zero_grad() outputs = net(batch_X) loss = loss_function(outputs, batch_y) loss.backward() optimizer.step() # Does the update print(f"Epoch: {epoch}. Loss: {loss}") def test(net): correct = 0 total = 0 with torch.no_grad(): for i in tqdm(range(len(test_X))): real_class = torch.argmax(test_y[i]) net_out = net(test_X[i].view(-1, 1, 50, 50))[0] # returns a list, predicted_class = torch.argmax(net_out) if predicted_class == real_class: correct += 1 total += 1 print("Accuracy: ", round(correct/total, 3))
Результат:
Net( (conv1): Conv2d(1, 32, kernel_size=(5, 5), stride=(1, 1)) (conv2): Conv2d(32, 64, kernel_size=(5, 5), stride=(1, 1)) (conv3): Conv2d(64, 128, kernel_size=(5, 5), stride=(1, 1)) (fc1): Linear(in_features=512, out_features=512, bias=True) (fc2): Linear(in_features=512, out_features=2, bias=True) ) 24946
Мы пошли несколько дальше и сделали быструю функцию для обработки процесса обучения, в основном для того, чтобы не запускать заново тренировочную часть нашего кода. Вместо этого мы хотим поговорить про работу на GPU.
Для начала вам понадобится версия Pytorch для GPU. А чтобы использовать Pytorch на графическом процессоре, вам понадобится графический процессор NVIDIA с поддержкой технологии CUDA.
Если его нет, то потребуются облачные провайдеры, например Linode. Прим. переводчика — отлично подойдут Google Colaboratory или Kaggle. Они абсолютно бесплатны.
Для работы на локальном компьютере вам нужно загрузить CUDA toolkit.
После этого необходимо загрузить и распаковать CuDNN, переместив содержимое CuDNN в каталог Cuda Toolkit. Когда вы распакуете архив CuDNN, у вас будет 3 каталога внутри каталога cuda. Вам просто нужно переместить содержимое этих каталогов (bin, include и lib) в одноименные директории, находящиеся в каталоге Cuda Toolkit.
Сделав это, вам надо будет убедиться что у вас установлена версия Pytorch для GPU. Ее можно найти на стартовой странице фреймворка Pytorch. Также надо сказать, что если вы используете пакет Anaconda, то в нем уже предустановлены все возможные версии Pytorch.
Убедиться, что CUDA установлена, можно следующим образом:
torch.cuda.is_available()
Результат:
True
Теперь мы можем подумать о том, что именно хотим делать с графическим процессором. Как минимум нам нужно, чтобы на нем выполнялось обучение нашей модели.
Это означает, что все наши данные также должны обрабатываться на графическом процессоре.
Для начала давайте разместим на GPU нашу нейронную сеть. Для этого мы можем просто установить следующий флаг:
device = torch.device("cuda:0") device
Результат:
device(type='cuda', index=0)
Однако зачастую мы хотим написать код, которым будут пользоваться разные люди, в том числе и те, у кого может отсутствовать возможность использовать графический процессор. Решить эту проблему можно следующим образом:
if torch.cuda.is_available(): device = torch.device("cuda:0") # здесь вы можете продолжить, например,cuda:1 cuda:2... и т. д. print("Running on the GPU") else: device = torch.device("cpu") print("Running on the CPU")
Результат:
Running on the GPU
Большинство базовых нейронных сетей не особо выигрывают от использования нескольких графических процессоров, но в дальнейшем вы можете все же захотеть использовать несколько GPU для своей задачи. Узнать, сколько вам доступно процессоров, можно следующим образом:
torch.cuda.device_count()
Результат:
1
Таким образом мы можем экстраполировать номера индексов и назначить конкретным графическим процессорам определенные слои нашей сети.
На данный момент мы пишем код, которому в действительности нужен только один графический (или обычный) процессор, поэтому мы будем использовать только одно устройство. И теперь, выяснив, какое устройство лучше всего использовать, мы можем приступить к его настройке. Сделать это очень просто, достаточно лишь выполнить следующий код:
net.to(device)
Результат:
Net( (conv1): Conv2d(1, 32, kernel_size=(5, 5), stride=(1, 1)) (conv2): Conv2d(32, 64, kernel_size=(5, 5), stride=(1, 1)) (conv3): Conv2d(64, 128, kernel_size=(5, 5), stride=(1, 1)) (fc1): Linear(in_features=512, out_features=512, bias=True) (fc2): Linear(in_features=512, out_features=2, bias=True) )
Выше мы уже определили нашу сеть, но обычно вы просто сразу ее определяете и отправляете на устройство, например следующим образом:
net = Net().to(device)
Теперь мы можем перейти к обучению, но на этот раз мы поместим наши батчи в GPU. В этом примере мы реально можем за один раз поместить все наши данные в GPU, так как у нас не очень большой объем данных. И это, конечно, сэкономит нам некоторое время, затрачиваемое на передачу данных из VRAM в GPU и обратно. Но обычно подобное невозможно, поэтому мы покажем вам, как это происходит в нормальных условиях.
Мы скопируем нашу функцию train
, а затем произведем в ней одну небольшую модификацию: после задания наших батчей мы сразу будем перемещать их в GPU, выполнив batch_X, batch_y = batch_X.to(device), batch_y.to(device)
. Итак, теперь наша функция train
имеет следующий вид:
EPOCHS = 3 def train(net): optimizer = optim.Adam(net.parameters(), lr=0.001) BATCH_SIZE = 100 EPOCHS = 3 for epoch in range(EPOCHS): for i in range(0, len(train_X), BATCH_SIZE): # from 0, to the len of x, stepping BATCH_SIZE at a time. [:50] ..for now just to dev #print(f"{i}:{i+BATCH_SIZE}") batch_X = train_X[i:i+BATCH_SIZE].view(-1, 1, 50, 50) batch_y = train_y[i:i+BATCH_SIZE] batch_X, batch_y = batch_X.to(device), batch_y.to(device) net.zero_grad() optimizer.zero_grad() # zero the gradient buffers outputs = net(batch_X) loss = loss_function(outputs, batch_y) loss.backward() optimizer.step() # Does the update print(f"Epoch: {epoch}. Loss: {loss}") train(net)
Результат:
Epoch: 0. Loss: 0.23834262788295746 Epoch: 1. Loss: 0.20373524725437164 Epoch: 2. Loss: 0.1704103648662567
Как можно заметить, сейчас это работает намного быстрее. Теперь мы можем проверить нашу модель на тестовой выборке (это можно сделать как на CPU, так и на GPU). Поскольку эта выборка совсем небольшая, сделаем это тоже на GPU:
test_X.to(device) test_y.to(device) def test(net): correct = 0 total = 0 with torch.no_grad(): for i in tqdm(range(len(test_X))): real_class = torch.argmax(test_y[i]).to(device) net_out = net(test_X[i].view(-1, 1, 50, 50).to(device))[0] # returns a list, predicted_class = torch.argmax(net_out) if predicted_class == real_class: correct += 1 total += 1 print("Accuracy: ", round(correct/total, 3)) test(net)
Результат:
100%|a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^| 2494/2494 [00:08<00:00, 299.45it/s]
Accuracy: 0.706
А вот тоже самое с использованием батчей работает быстрее:
correct = 0 total = 0 for i in tqdm(range(0, len(test_X), BATCH_SIZE)): batch_X = test_X[i:i+BATCH_SIZE].view(-1, 1, 50, 50).to(device) batch_y = test_y[i:i+BATCH_SIZE].to(device) batch_out = net(batch_X) out_maxes = [torch.argmax(i) for i in batch_out] target_maxes = [torch.argmax(i) for i in batch_y] for i,j in zip(out_maxes, target_maxes): if i == j: correct += 1 total += 1 print("Accuracy: ", round(correct/total, 3))
Результат:
100%|a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^a-^| 25/25 [00:01<00:00, 19.49it/s]
Accuracy: 0.706
Итак, по сравнению с тем, что было, мы добились значительного прогресса.
Мы научились создавать и обучать нейронные сети и уже получили приличный результат.
Но можем ли мы добиться большего, чем точность в 70%? Должны ли мы увеличивать количество эпох? И если да, то когда нам все же остановиться?
Что, если у нас есть несколько моделей, и чтобы их сравнить, нужно довести их до совершенства? Мы же не знаем, когда это произойдет!
В следующей статье мы рассмотрим некоторые базовые методы анализа и визуализации результатов. А также разберем несколько концепций, которые необходимо учитывать при анализе эффективности работы модели.
Следующая статья — Глубокое обучение и нейронные сети с Python и Pytorch. Часть VIII: основы анализа нейронных сетей.