
W tym artykule opowiem, jak w ciągu 30 minut skonfigurować środowisko do uczenia maszynowego, stworzyć sieć neuronową do rozpoznawania obrazów, a następnie uruchomić tę samą sieć na procesorze graficznym (GPU).
Na początek zdefiniujmy, czym jest sieć neuronowa.
W naszym przypadku jest to matematyczny model oraz jego programowa lub sprzętowa realizacja, zbudowana na zasadzie organizacji i funkcjonowania biologicznych sieci neuronowych — sieci komórek nerwowych żywego organizmu. Pojęcie to pojawiło się podczas badania procesów zachodzących w mózgu i próby modelowania tych procesów.
Sieci neuronowe nie są programowane w tradycyjnym sensie tego słowa, lecz uczą się. Możliwość uczenia się to jedna z głównych zalet sieci neuronowych w porównaniu do tradycyjnych algorytmów. Technicznie rzecz ujmując, uczenie się polega na znalezieniu współczynników łączących neuronów. W procesie uczenia sieć neuronowa potrafi ujawniać złożone zależności między danymi wejściowymi a wyjściowymi oraz dokonywać uogólnień.
Z perspektywy uczenia maszynowego, sieć neuronowa stanowi szczególny przypadek metod rozpoznawania wzorców, analizy dyskryminacyjnej, metod klasteryzacji oraz innych metod.
Sprzęt
Najpierw zajmijmy się sprzętem. Potrzebujemy serwera z zainstalowanym systemem operacyjnym Linux. Sprzęt do pracy systemów uczenia maszynowego musi być wystarczająco mocny, a co za tym idzie, kosztowny. Tym, którzy nie mają pod ręką dobrego komputera, polecam zwrócić uwagę na ofertę dostawców chmurowych. Potrzebny serwer można szybko wynająć i płacić tylko za czas użytkowania.
W projektach, gdzie niezbędne jest tworzenie sieci neuronowych, korzystam z serwerów jednego z rosyjskich dostawców chmury. Firma oferuje w wynajem serwery chmurowe specjalnie do uczenia maszynowego z potężnymi procesorami graficznymi (GPU) Tesla V100 od firmy NVIDIA. Krótko mówiąc: użycie serwera z GPU może być nawet dziesięciokrotnie bardziej efektywne (szybsze) w porównaniu do analogicznego serwera o tej samej cenie, w którym do obliczeń używany jest CPU (wszystkim dobrze znany procesor centralny). Osiąga się to dzięki szczególnym właściwościom architektury GPU, która szybciej wykonuje obliczenia.
Aby wykonać dalsze opisywane przykłady, zakupiliśmy na kilka dni taki serwer:
- Dysk SSD 150 GB
- Pamięć RAM 32 GB
- Procesor Tesla V100 16 Gb z 4 rdzeniami
Na maszynie zainstalowano Ubuntu 18.04.
Instalujemy środowisko
Teraz zainstalujemy na serwerze wszystko, co potrzebne do pracy. Ponieważ nasz artykuł jest przede wszystkim dla początkujących, będę opowiadał o niektórych aspektach, które będą przydatne właśnie im.
Wiele pracy podczas konfiguracji środowiska wykonuje się za pomocą wiersza poleceń. Większość użytkowników korzysta z Windows jako systemu operacyjnego. Standardowa konsola w tym systemie pozostawia wiele do życzenia. Dlatego będziemy korzystać z wygodnego narzędzia . Pobieramy wersję mini i uruchamiamy Cmder.exe. Następnie należy połączyć się z serwerem za pomocą protokołu SSH:
ssh root@server-ip-or-hostnameZamiast server-ip-or-hostname wpisz adres IP lub nazwę DNS swojego serwera. Następnie wprowadź hasło, a po pomyślnym połączeniu powinniśmy otrzymać mniej więcej taką wiadomość.
Witamy w Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-74-generic x86_64)Podstawowym językiem do tworzenia modeli ML jest Python. Najpopularniejszą platformą do jego używania w systemie Linux jest .
Zainstalujemy ją na naszym serwerze.
Zaczynamy od zaktualizowania lokalnego menedżera pakietów:
sudo apt-get updateInstalujemy curl (program narzędziowy wiersza poleceń):
sudo apt-get install curlPobieramy najnowszą wersję Anaconda Distribution:
cd /tmp
curl –O https://repo.anaconda.com/archive/Anaconda3-2019.10-Linux-x86_64.shUruchamiamy instalację:
bash Anaconda3-2019.10-Linux-x86_64.shW trakcie instalacji konieczne będzie potwierdzenie umowy licencyjnej. Po pomyślnej instalacji powinieneś zobaczyć:
Dziękujemy za zainstalowanie Anaconda3!Obecnie stworzono wiele frameworków do tworzenia modeli ML, pracujemy z najbardziej popularnymi: i .
Użycie frameworka pozwala zwiększyć szybkość rozwoju i korzystać z już gotowych narzędzi do standardowych zadań.
W tym przykładzie będziemy pracować z PyTorch. Zainstalujemy go:
conda install pytorch torchvision cudatoolkit=10.1 -c pytorchTeraz musimy uruchomić Jupyter Notebook – popularne narzędzie dla specjalistów ML. Umożliwia pisanie kodu i natychmiastowe zobaczenie wyników jego wykonania. Jupyter Notebook jest częścią Anaconda i jest już zainstalowany na naszym serwerze. Należy połączyć się z nim z naszego systemu stacjonarnego.
Najpierw uruchomimy Jupyter na serwerze, wskazując port 8080:
jupyter notebook --no-browser --port=8080 --allow-rootNastępnie otworzymy w naszej konsoli Cmder nowe okno (górne menu — Nowy dialog konsoli) i połączymy się przez port 8080 z serwerem przez SSH:
ssh -L 8080:localhost:8080 root@server-ip-or-hostnamePo wprowadzeniu pierwszej komendy zostaną zaproponowane linki do otwarcia Jupyter w naszej przeglądarce:
Aby uzyskać dostęp do notebooka, otwórz ten plik w przeglądarce:
file://root/.local/share/jupyter/runtime/nbserver-18788-open.html
Lub skopiuj i wklej jeden z tych adresów URL:
http://localhost:8080/?token=cca0bd0b30857821194b9018a5394a4ed2322236f116d311
lub http://127.0.0.1:8080/?token=cca0bd0b30857821194b9018a5394a4ed2322236f116d311
Skorzystamy z linku dla localhost:8080. Skopiuj pełną ścieżkę i wklej ją w pasek adresu lokalnej przeglądarki na swoim PC. Otworzy się Jupyter Notebook.
Stwórzmy nowy notatnik: Nowy — Notatnik — Python 3.
Sprawdzimy poprawność działania wszystkich komponentów, które zainstalowaliśmy. Wprowadźmy w Jupyter przykład kodu PyTorch i uruchommy jego wykonanie (przycisk Uruchom):
from __future__ import print_function
import torch
x = torch.rand(5, 3)
print(x)
Wynik powinien wyglądać mniej więcej tak:

Jeśli uzyskasz podobny rezultat, oznacza to, że wszystko zostało poprawnie skonfigurowane i możemy przejść do opracowywania sieci neuronowej!
Tworzymy sieć neuronową
Stworzymy sieć neuronową do rozpoznawania obrazów. Na podstawie tego .
Do trenowania sieci wykorzystamy publicznie dostępny zbiór danych CIFAR10. Zawiera on klasy: „samolot”, „samochód”, „ptak”, „kot”, „jeleń”, „pies”, „żaba”, „koń”, „statek”, „ciężarówka”. Obrazy w CIFAR10 mają rozmiar 3x32x32, czyli są 3-kanałowymi kolorowymi obrazami o wymiarach 32×32 piksele.

Do pracy będziemy korzystać z przygotowanego pakietu PyTorch do pracy z obrazami — torchvision.
Wykonamy następujące kroki w kolejności:
- Ładowanie i normalizacja zbiorów danych treningowych i testowych
- Definicja sieci neuronowej
- Trening sieci na danych treningowych
- Testowanie sieci na danych testowych
- Powtórzymy trening i testowanie z wykorzystaniem GPU
Cały poniższy kod będziemy wykonywać w Jupyter Notebook.
Ładowanie i normalizacja CIFAR10
Skończ kopiując i wykonując w Jupyter następujący kod:
import torch
import torchvision
import torchvision.transforms as transforms
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
shuffle=False, num_workers=2)
classes = ('samolot', 'samochód', 'ptak', 'kot',
'jeleń', 'pies', 'żaba', 'koń', 'statek', 'ciężarówka')Odpowiedź powinna być taka:
Pobieranie https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz do ./data/cifar-10-python.tar.gz
Rozpakowywanie ./data/cifar-10-python.tar.gz do ./data
Pliki już pobrane i zweryfikowaneWyświetlimy kilka obrazów treningowych do weryfikacji:
import matplotlib.pyplot as plt
import numpy as np
# funkcje do wyświetlania obrazu
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
# pobierz kilka losowych obrazów treningowych
dataiter = iter(trainloader)
images, labels = dataiter.next()
# pokaż obrazy
imshow(torchvision.utils.make_grid(images))
# wyświetl etykiety
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

Definicja sieci neuronowej
Rozważmy najpierw jak działa sieć neuronowa do rozpoznawania obrazów. To prosta sieć o pełnym połączeniu. Przyjmuje dane wejściowe, przepuszcza je przez kilka warstw jeden po drugim, a następnie w końcu zwraca dane wyjściowe.

Stwórzmy podobną sieć w naszym środowisku:
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(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
Zdefiniujmy także funkcję straty i optymalizator
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)Trening sieci na danych treningowych
Rozpoczynamy trening naszej sieci neuronowej. Zwracam uwagę, że po uruchomieniu tego kodu, trzeba poczekać trochę czasu na zakończenie pracy. Zajęło mi to 5 minut. Proces uczenia sieci wymaga czasu.
dla epoch w zakresie(2): # powtarzanie zbioru danych wielokrotnie
bieżąca_strata = 0.0
dla i, dane w enumerate(trainloader, 0):
# pobierz dane wejściowe; dane to lista [wejścia, etykiety]
wejścia, etykiety = dane
# wyzeruj gradienty parametrów
optimizer.zero_grad()
# przód + tył + optymalizacja
wyniki = net(wejścia)
strata = criterion(wyniki, etykiety)
strata.backward()
optimizer.step()
# wyświetl statystyki
bieżąca_strata += strata.item()
jeśli i % 2000 == 1999: # drukuj co 2000 mini-partii
print('[%d, ] strata: %.3f' %
(epoch + 1, i + 1, bieżąca_strata / 2000))
bieżąca_strata = 0.0
print('Zakończono szkolenie')
Otrzymamy taki wynik:

Zapisujemy nasz wytrenowany model:
ŚCIEŻKA = './cifar_net.pth'
torch.save(net.state_dict(), ŚCIEŻKA)Testowanie sieci na danych testowych
Wytrenowaliśmy sieć przy użyciu zbioru danych treningowych. Ale musimy sprawdzić, czy sieć w ogóle się czegoś nauczyła.
Sprawdzimy to, przewidując etykietę klasy, którą przewiduje sieć neuronowa, i weryfikując ją pod kątem prawdziwości. Jeśli prognoza jest poprawna, dodajemy próbkę do listy poprawnych prognoz.
Pokażmy obrazek z zestawu testowego:
dataiter = iter(testloader)
obrazy, etykiety = dataiter.next()
# drukuj obrazy
imshow(torchvision.utils.make_grid(obrazy))
print('Prawda: ', ' '.join('%5s' % classes[etykiety[j]] for j in range(4))) 
Teraz poprośmy sieć neuronową, aby powiedziała nam, co znajduje się na tych obrazkach:
net = Net()
net.load_state_dict(torch.load(ŚCIEŻKA))
wyjścia = net(obrazy)
_, przewidziane = torch.max(wyjścia, 1)
print('Przewidziane: ', ' '.join('%5s' % classes[przewidziane[j]]
for j in range(4)))

Wyniki wydają się całkiem dobre: sieć poprawnie zidentyfikowała trzy obrazki z czterech.
Zobaczmy, jak sieć działa na całym zbiorze danych.
poprawne = 0
total = 0
z torch.no_grad():
dla danych w testloader:
obrazy, etykiety = dane
wyjścia = net(obrazy)
_, przewidziane = torch.max(wyjścia.data, 1)
total += etykiety.size(0)
poprawne += (przewidziane == etykiety).sum().item()
print('Dokładność sieci na 10000 obrazkach testowych: %d %%' % (
100 * poprawne / total)) 
Wygląda na to, że sieć coś wie i działa. Gdyby klasyfikowała na oślep, dokładność wynosiłaby 10%.
Teraz zobaczmy, które klasy sieć identyfikuje najlepiej:
klasa_poprawna = lista(0. dla i w zakresie(10))
klasa_razem = lista(0. dla i w zakresie(10))
z torch.no_grad():
dla danych w testloader:
obrazy, etykiety = dane
wyniki = net(obrazy)
_, przewidywane = torch.max(wyniki, 1)
c = (przewidywane == etykiety).squeeze()
dla i w zakresie(4):
etykieta = etykiety[i]
klasa_poprawna[etykieta] += c[i].item()
klasa_razem[etykieta] += 1
dla i w zakresie(10):
print('Dokładność %5s : %%' % (
klasy[i], 100 * klasa_poprawna[i] / klasa_razem[i])) 
Wygląda na to, że sieć najlepiej identyfikuje samochody i statki: 71% dokładności.
Tak więc, sieć działa. Teraz spróbujmy przenieść ją na procesor graficzny (GPU) i zobaczymy, co się zmieni.
Trening sieci neuronowej na GPU
Najpierw wyjaśnię krótko, czym jest CUDA. CUDA (Compute Unified Device Architecture) to platforma obliczeń równoległych stworzona przez firmę NVIDIA, przeznaczona do ogólnych obliczeń na procesorach graficznych (GPU). Dzięki CUDA programiści mogą znacznie przyspieszyć aplikacje obliczeniowe, wykorzystując możliwości procesorów graficznych. Na naszym serwerze, który nabyliśmy, ta platforma już jest zainstalowana.
Najpierw zdefiniujmy nasze GPU jako pierwsze widoczne urządzenie cuda.
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# Zakładając, że jesteśmy na maszynie CUDA, powinno to wyświetlić urządzenie CUDA:
print(device) 
Wysyłamy sieć na GPU:
net.to(device)Musimy również przesyłać dane wejściowe i cele na każdym kroku do GPU:
inputs, labels = data[0].to(device), data[1].to(device)Uruchomimy ponowne uczenie sieci na GPU:
import torch.optim jako optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
for epoch w zakresie(2): # powtarzanie zbioru danych wielokrotnie
bieżąca_strata = 0.0
dla i, dane w enumerate(trainloader, 0):
# pobierz dane wejściowe; dane to lista [wejścia, etykiety]
wejścia, etykiety = dane[0].to(device), dane[1].to(device)
# wyzeruj gradienty parametrów
optimizer.zero_grad()
# przód + tył + optymalizacja
wyniki = net(wejścia)
strata = criterion(wyniki, etykiety)
strata.backward()
optimizer.step()
# wyświetl statystyki
bieżąca_strata += strata.item()
jeśli i % 2000 == 1999: # drukuj co 2000 mini-partii
print('[%d, ] strata: %.3f' %
(epoch + 1, i + 1, bieżąca_strata / 2000))
bieżąca_strata = 0.0
print('Zakończono szkolenie')Tym razem trening sieci trwał około 3 minut. Przypomnijmy, że ten sam etap na zwykłym procesorze trwał 5 minut. Różnica nie jest znacząca, ponieważ nasza sieć nie jest tak duża. Przy użyciu większych zbiorów danych do treningu różnica pomiędzy szybkością działania GPU a tradycyjnego procesora będzie rosła.
Na tym wydaje się wszystko. Co udało nam się zrobić:
- Zbadaliśmy, czym jest GPU i wybraliśmy serwer, na którym jest zainstalowany;
- Skonfigurowaliśmy środowisko programistyczne do tworzenia sieci neuronowej;
- Stworzyliśmy sieć neuronową do rozpoznawania obrazów i ją wytrenowaliśmy;
- Powtórzyliśmy trening sieci przy użyciu GPU i uzyskaliśmy przyspieszenie.
Chętnie odpowiem na pytania w komentarzach.
Źródło: habr.com
