Machine Learning

Строим нейронную сеть с PyTorch и Google Colab

Время прочтения: 6 мин.

Предполагается, что у пользователя уже есть предварительные знания о том, как работает нейронная сеть. Эта статья основана на реализации нейронной сети (ссылка), написанной исключительно с использованием Numpy. Фактически я попыталась создать аналогию скрипта, используя вместо этого PyTorch, и добавила свои собственные интуиции и объяснения.

Поскольку я работаю в Google Colab, Вам нужно будет установить библиотеку PyTorch.

Вы можете сделать это с помощью следующей команды:

pip install torch==1.8.1+cu102 torchvision==0.9.1+cu102 torchaudio===0.8.1 -f https://download.pytorch.org/whl/torch_stable.html

Модуль torch предоставляет все необходимые Тензорные операторы, с помощью которых можно реализовать нейронную сеть в PyTorch.

import torch
import torch.nn as nn

Данные

Начнем с создания некоторых образцов данных с помощью torch.tensor команды.

В Numpy это можно сделать с помощью np.array. Обе функции служат для одной и той же цели, но в PyTorch все является тензорным, а не вектором или матрицей.

Поэтому определяем типы в PyTorch с помощью dtype=torch.xxx команды.

В данных X, представленных ниже, содержится количество изученных часов и время, которое сотрудники тратят на сон, y представляет собой оценки.

Переменная xPredicted является единственным входом, для которого мы хотим спрогнозировать оценку, используя параметры, полученные нейронной сетью.

Помните, что нейронная сеть хочет изучить сопоставление между X и y, поэтому она попытается предположить, что она узнала из обучающих данных.

X = torch.tensor(([2, 9], [1, 5], [3, 6]), dtype=torch.float) #3 на 2 тензор, количество изученных часов и время отведенное на сон
y = torch.tensor(([92], [100], [89]), dtype=torch.float) #3 на 1 тензор, оценки
xPredicted = torch.tensor(([4, 8]), dtype=torch.float) #1 на 2 тензор, вход, для которого хотим спрогнозировать оценку

Вы можете проверить размер только что созданных тензоров с помощью size команды. Это эквивалентно shape команде, используемой в таких инструментах, как Numpy и Tensorflow.

Масштабирование

Ниже представлено некоторое масштабирование выборки данных.

Обратите внимание, что функция X_max, _ возвращает и тензор, и соответствующие индексы.

Для проведения масштабирования нас интересуют только максимальные значения, поэтому индексы, которые получили с помощью функции X_max, _ использоваться не будут.

X_max, _ = torch.max(X, 0) #возвращает тензор и индексы
xPredicted_max, _ = torch.max(xPredicted, 0)
X = torch.div(X, X_max)
xPredicted = torch.div(xPredicted, xPredicted_max)
y = y / 100 #так как максимальный результат равен 100

Наши данные теперь представлены в очень хорошем формате, который наша нейронная сеть оценит позже.

Есть две функции max и div, которые делают именно то, что подразумевают: max находят максимальное значение в векторе (тензоре); div – функция для деления двух тензоров.

Модель

После обработки и представления данных в надлежащем формате остается определить свою модель.

Здесь все начинает меняться по сравнению с тем, как если бы вы строили нейронные сети, используя Keras или Tensorflow.

Однако вы быстро поймете, что PyTorch не сильно отличается от других инструментов глубокого обучения. Построим граф, на котором можно увидеть, как должны передаваться данные и какие операции выполняются с этой информацией.

В качестве иллюстрации представим нейронную сеть на рисунке ниже:

Разберем модель, которая была объявлена ​​через класс:

class Neu_Net(nn.Module):
    
def __init__(self, ):#функция инициализации

        super( Neu_Net, self).__init__()
        self.inputSize = 2
        self.outputSize = 1
        self.hiddenSize = 3
        
        self.W1 = torch.randn(self.inputSize, self.hiddenSize)
        self.W2 = torch.randn(self.hiddenSize, self.outputSize)
        
    def fw(self, X): #функция форвард
        self.z = torch.matmul(X, self.W1) 
        self.z2 = self.sg(self.z) 
        self.z3 = torch.matmul(self.z2, self.W2)
        o = self.sg(self.z3)
        return o
        
    def sg(self, s):
        return 1 / (1 + torch.exp(-s))
    
    def sgPrime(self, s):
        return s * (1 - s)
    
    def bw(self, X, y, o): #функция Backward
        self.o_error = y - o
        self.o_delta = self.o_error * self.sgPrime(o) # derivative of sig to error
        self.z2_error = torch.matmul(self.o_delta, torch.t(self.W2))
        self.z2_delta = self.z2_error * self.sgPrime(self.z2)
        self.W1 += torch.matmul(torch.t(X), self.z2_delta)
        self.W2 += torch.matmul(torch.t(self.z2), self.o_delta)
        
    def train(self, X, y):
        o = self.fw(X)
        self.bw(X, y, o)
        
    def saveWeights(self, model):
        torch.save(model, "NN")        
    def predict(self): #вывод результатов прогнозирования
        print ("Прогнозируемые данные на основе трен.весов: ")
        print ("Вход-масштабированный: \n" + str(xPredicted))
        print ("Выход: \n" + str(self.fw(xPredicted)))

Инициализация

def __init__(self, ):
    super( Neu_Net, self).__init__()
    self.inputSize = 2
    self.outputSize = 1
    self.hiddenSize = 3 
    self.W1 = torch.randn(self.inputSize, self.hiddenSize) 
    self.W2 = torch.randn(self.hiddenSize, self.outputSize)

Следующим шагом является инициализация через def __init__(self,), которая будет выполняться при создании экземпляра настроенной нейронной сети.

Вы также можете объявить параметры для своей модели, а именно размер скрытых слоев, входных данных. Так как нейронную сеть представляем для начального знакомства, то явно заявили размер матриц весов: одна, в которой хранятся параметры от входа до скрытого слоя, и параметр – от скрытого до входного слоя. Обе весовые матрицы инициализируются значениями, случайно выбранными из нормального распределения с помощью torch.randn(…).

def fw(self, X):
self.z = torch.matmul(X, self.W1) 
self.z2 = self.sg(self.z)
self.z3 = torch.matmul(self.z2, self.W2)
o = self.sg(self.z3)
return o

В функции fw происходит вся магия. Здесь данные поступают и вводятся в граф – в структуру нейронной сети, которую мы построили.

Так как нейронная сеть имеет только один скрытый слой, то функция fw выглядит очень простой.

Функция принимает входные данные X, затем выполняет матричное умножение (torch.matmul(…)) с первой весовой матрицей self.W1. Затем применяется функция активации sg. Полученная матрица активации затем умножается на вторую матрицу весов self.W2. Затем выполняется еще одна активация, которая отображает вывод нейронной сети или графа вычислений.

Обратная функция

Функция bw содержит алгоритм обратного распространения, цель которого – свести к минимуму потери в отношении наших весов.

Другими словами, веса необходимо обновлять таким образом, чтобы потери уменьшались во время обучения нейронной сети. Вся магия возможна с алгоритмом градиентного спуска, который объявлен в bw функции.

def bw(self, X, y, o):
self.o_error = y - o
self.o_delta = self.o_error * self.sgPrime(o) 
self.z2_error = torch.matmul(self.o_delta, torch.t(self.W2))
self.z2_delta = self.z2_error * self.sgPrime(self.z2)
self.W1 += torch.matmul(torch.t(X), self.z2_delta)
self.W2 += torch.matmul(torch.t(self.z2), self.o_delta) 

Подготовка

Теперь осталось только обучить нейронную сеть. Сначала мы создаем экземпляр только что построенного графа вычислений:

NN =  Neu_Net() 

Затем тренируем модель для 1000 обходов.

Обратите внимание, что PyTorch NN(X) автоматически вызывает fw функцию, поэтому нет необходимости явно вызывать ее как NN.fw(X).

После того как мы получили прогнозируемые выходные данные для каждого цикла обучения, мы вычисляем потери с помощью следующего кода:

torch.mean((y - NN(X))**2).detach().item()

Затем обучаем через NN.train(X, y).

После того как мы обучили нейронную сеть, мы можем сохранить модель и вывести предсказанное значение одного экземпляра которое мы объявили в начале xPredicted.

NN = Neu_Net()
	for i in range(1000):
	    print ("#" + str(i) + " Loss: " + str(torch.mean((y - NN(X))**2).detach().item()))
	    NN.train(X, y)
	NN.saveWeights(NN)
	NN.predict()

Ниже приведен фрагмент проигрыша в финальных раундах тренировок:

Потери продолжают уменьшаться, а это значит, что нейронная сеть учится.

Вы только что научились создавать и обучать нейронную сеть с нуля с помощью PyTorch. Теперь Вы можете добавить больше скрытых слоев или попытаться применить условия смещения.

Советуем почитать