рекомендации

четверг, 8 апреля 2021 г.

Введение в PyTorch - простая, но мощная библиотека Deep Learning.

Введение

Время от времени разрабатывается библиотека Python, которая может изменить ситуацию в области глубокого обучения. PyTorch - одна из таких библиотек.

В последние несколько недель я немного увлекся PyTorch. Я был потрясен тем, насколько легко ее понять. Среди различных библиотек глубокого обучения, которые я использовал до сих пор, PyTorch была самой гибкой и простой из всех.

В этой статье мы рассмотрим PyTorch с более практическим подходом, охватывая основы вместе с примерами. Мы также сравним нейронную сеть, созданную с нуля в numpy и в PyTorch, чтобы увидеть их сходство в реализации.

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

Обзор PyTorch

Создатели PyTorch говорят, что у них есть философия - они хотят быть императивными. Это означает, что мы немедленно запускаем наши вычисления. Это вписывается в методологию программирования на Python, поскольку нам не нужно ждать, пока будет написан весь код, прежде чем мы узнаем, работает он или нет. Мы можем легко запустить часть кода и проверить ее в реальном времени. Для меня, отладчика нейронной сети, это благо!

PyTorch - это библиотека Python, созданная для обеспечения гибкости в качестве платформы разработки глубокого обучения. Рабочий процесс PyTorch максимально приближен к библиотеке научных вычислений Python - numpy.

Теперь вы можете спросить, почему нужно использовать именно PyTorch для построения моделей глубокого обучения? Я могу перечислить три вещи, которые могут помочь ответить на этот вопрос:
  • Простой в использовании API - он настолько прост, насколько может быть прост Python.
  • Поддержка Python - как упоминалось выше, PyTorch плавно интегрируется со стеком науки о данных Python. Он настолько похож на numpy, что вы можете даже не заметить разницы.
  • Динамические графики вычислений - вместо предопределенных графиков с определенными функциями PyTorch предоставляет нам основу для построения графиков вычислений по ходу работы и даже изменения их во время выполнения. Это полезно в ситуациях, когда мы не знаем, сколько памяти потребуется для создания нейронной сети.
Еще несколько преимуществ использования PyTorch - это поддержка нескольких графических процессоров, пользовательские загрузчики данных и упрощенные препроцессоры.

С момента ее релиза в начале января 2016 года многие исследователи приняли ее в качестве полезной библиотеки из-за простоты построения новых и чрезвычайно сложных графиков. Тем не менее, пройдет еще некоторое время, прежде чем PyTorch будет принята большинством практиков в области науки о данных из-за того, что она новая и находится в стадии разработки.

Погружение в технические детали

Прежде чем углубляться в детали, давайте рассмотрим рабочий процесс PyTorch.

PyTorch использует парадигму императива. То есть каждая строка кода, необходимая для построения графика, определяет компонент этого графика. Мы можем независимо выполнять вычисления над этими компонентами, даже до того, как ваш график будет полностью построен. Это называется методологией «определения по запуску».


Установить PyTorch довольно просто. Вы можете выполнить шаги, указанные в официальных документах, и запустить команду в соответствии со спецификациями вашей системы. Например, команда, которую я использовал на основе выбранных мной параметров:


conda install pytorch torchvision cuda91 -c pytorch
Основные элементы, которые мы должны знать, начиная работать с PyTorch:
  • PyTorch Tensors
  • Mathematical Operations
  • Autograd module
  • Optim module и
  • nn module
Ниже мы подробно рассмотрим каждый из них.

PyTorch Tensors

Tensors - это ни что иное, как многомерные массивы. Tensors в PyTorch похожи на ndarrays numpy, с той лишь разницей, что тензоры можно использовать и на графическом процессоре. PyTorch поддерживает различные типы тензоров.
# import pytorch
import torch

# define a tensor
torch.FloatTensor([2])
 2
[torch.FloatTensor of size 1]
Mathematical Operations

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

Ниже приведен пример простой операции сложения в PyTorch:
a = torch.FloatTensor([2])
b = torch.FloatTensor([3])

a + b
 5
[torch.FloatTensor of size 1]
Разве это не похоже на типичный подход python? Мы также можем выполнять различные матричные операции с определенными тензорами PyTorch. Например, мы транспонируем двумерную матрицу:
matrix = torch.randn(3, 3)
matrix

0.7162 1.0152 1.1525
-0.3503 -0.9452 -1.0861
-0.1093 -0.0927 -0.0476
[torch.FloatTensor of size 3x3]

matrix.t()

0.7162 -0.3503 -0.1093
 1.0152 -0.9452 -0.0927
 1.1525 -1.0861 -0.0476
[torch.FloatTensor of size 3x3]
Autograd module

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


from torch.autograd import Variable

x = Variable(train_x)
y = Variable(train_y, requires_grad=False)
Optim module

torch.optim - модуль, реализующий различные алгоритмы оптимизации, используемые для построения нейронных сетей. Большинство часто используемых методов уже поддерживаются, поэтому нам не нужно создавать их с нуля (если вы этого не хотите!).

Ниже приведен код для использования оптимизатора Adam:
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
nn module

PyTorch autograd позволяет легко определять вычислительные графы и принимать градиенты, но исходный autograd может быть слишком низкоуровневым для определения сложных нейронных сетей. Здесь может помочь модуль nn.

Пакет nn определяет набор модулей, которые мы можем рассматривать как слой нейронной сети, который производит вывод из ввода и может иметь некоторые обучаемые веса.

Вы можете рассматривать модуль nn как основу PyTorch!
import torch

# define model
model = torch.nn.Sequential(
 torch.nn.Linear(input_num_units, hidden_num_units),
 torch.nn.ReLU(),
 torch.nn.Linear(hidden_num_units, output_num_units),
)
loss_fn = torch.nn.CrossEntropyLoss()
Теперь, когда вы знаете основные компоненты PyTorch, вы можете легко создать свою собственную нейронную сеть с нуля. Следуйте за ним, если хотите знать, как это сделать!

Создание нейронной сети в Numpy vs. PyTorch

Я уже упоминал ранее, что PyTorch и Numpy очень похожи. Посмотрим, почему. В этом разделе мы увидим реализацию простой нейронной сети для решения задачи двоичной классификации (вы можете просмотреть эту статью, чтобы получить подробное объяснение).
## Neural network in numpy

import numpy as np

#Input array
X=np.array([[1,0,1,0],[1,0,1,1],[0,1,0,1]])

#Output
y=np.array([[1],[1],[0]])

#Sigmoid Function
def sigmoid (x):
 return 1/(1 + np.exp(-x))

#Derivative of Sigmoid Function
def derivatives_sigmoid(x):
 return x * (1 - x)

#Variable initialization
epoch=5000 #Setting training iterations
lr=0.1 #Setting learning rate
inputlayer_neurons = X.shape[1] #number of features in data set
hiddenlayer_neurons = 3 #number of hidden layers neurons
output_neurons = 1 #number of neurons at output layer

#weight and bias initialization
wh=np.random.uniform(size=(inputlayer_neurons,hiddenlayer_neurons))
bh=np.random.uniform(size=(1,hiddenlayer_neurons))
wout=np.random.uniform(size=(hiddenlayer_neurons,output_neurons))
bout=np.random.uniform(size=(1,output_neurons))

for i in range(epoch):
  #Forward Propogation
  hidden_layer_input1=np.dot(X,wh)
  hidden_layer_input=hidden_layer_input1 + bh
  hiddenlayer_activations = sigmoid(hidden_layer_input)
  output_layer_input1=np.dot(hiddenlayer_activations,wout)
  output_layer_input= output_layer_input1+ bout
  output = sigmoid(output_layer_input)

  #Backpropagation
  E = y-output
  slope_output_layer = derivatives_sigmoid(output)
  slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)
  d_output = E * slope_output_layer
  Error_at_hidden_layer = d_output.dot(wout.T)
  d_hiddenlayer = Error_at_hidden_layer * slope_hidden_layer
  wout += hiddenlayer_activations.T.dot(d_output) *lr
  bout += np.sum(d_output, axis=0,keepdims=True) *lr
  wh += X.T.dot(d_hiddenlayer) *lr
  bh += np.sum(d_hiddenlayer, axis=0,keepdims=True) *lr

print('actual :\n', y, '\n')
print('predicted :\n', output)
Теперь попытайтесь найти разницу в супер простой реализации того же самого в PyTorch (различия выделены жирным шрифтом в приведенном ниже коде).
## neural network in pytorch
import torch

#Input array
X = torch.Tensor([[1,0,1,0],[1,0,1,1],[0,1,0,1]])

#Output
y = torch.Tensor([[1],[1],[0]])

#Sigmoid Function
def sigmoid (x):
  return 1/(1 + torch.exp(-x))

#Derivative of Sigmoid Function
def derivatives_sigmoid(x):
  return x * (1 - x)

#Variable initialization
epoch=5000 #Setting training iterations
lr=0.1 #Setting learning rate
inputlayer_neurons = X.shape[1] #number of features in data set
hiddenlayer_neurons = 3 #number of hidden layers neurons
output_neurons = 1 #number of neurons at output layer

#weight and bias initialization
wh=torch.randn(inputlayer_neurons, hiddenlayer_neurons).type(torch.FloatTensor)
bh=torch.randn(1, hiddenlayer_neurons).type(torch.FloatTensor)
wout=torch.randn(hiddenlayer_neurons, output_neurons)
bout=torch.randn(1, output_neurons)

for i in range(epoch):

  #Forward Propogation
  hidden_layer_input1 = torch.mm(X, wh)
  hidden_layer_input = hidden_layer_input1 + bh
  hidden_layer_activations = sigmoid(hidden_layer_input)
 
  output_layer_input1 = torch.mm(hidden_layer_activations, wout)
  output_layer_input = output_layer_input1 + bout
  output = sigmoid(output_layer_input1)

  #Backpropagation
  E = y-output
  slope_output_layer = derivatives_sigmoid(output)
  slope_hidden_layer = derivatives_sigmoid(hidden_layer_activations)
  d_output = E * slope_output_layer
  Error_at_hidden_layer = torch.mm(d_output, wout.t())
  d_hiddenlayer = Error_at_hidden_layer * slope_hidden_layer
  wout += torch.mm(hidden_layer_activations.t(), d_output) *lr
  bout += d_output.sum() *lr
  wh += torch.mm(X.t(), d_hiddenlayer) *lr
  bh += d_output.sum() *lr
 
print('actual :\n', y, '\n')
print('predicted :\n', output)
Сравнение с другими библиотеками глубокого обучения

В одном сценарии тестирования успешно показано, что PyTorch превосходит все другие основные библиотеки глубокого обучения в обучении сети с долгой краткосрочной памятью (LSTM), имея самое низкое среднее время на эпоху.

API-интерфейсы для загрузки данных в PyTorch хорошо разработаны. Интерфейсы указываются в наборе данных, сэмплере и загрузчике данных.

Сравнивая инструменты для загрузки данных в TensorFlow (считыватели, очереди и т. д.), я обнаружил, что модули загрузки данных PyTorch довольно просты в использовании. Кроме того, PyTorch работает без проблем, когда мы пытаемся построить нейронную сеть, поэтому нам не нужно полагаться на сторонние высокоуровневые библиотеки, такие как keras.

С другой стороны, я бы пока не рекомендовал использовать PyTorch для развертывания. PyTorch еще предстоит развиваться. Как сказали разработчики PyTorch: «Мы видим, что пользователи сначала создают модель PyTorch. Когда они будут готовы запустить свою модель в производство, они просто преобразуют ее в модель Caffe 2, а затем отправляют либо на мобильную, либо на другую платформу».

Пример - решение проблемы распознавания изображений в PyTorch

Чтобы познакомиться с PyTorch, мы решим практическую задачу глубокого обучения в Analytics Vidhya - определение цифр. Давайте посмотрим на нашу постановку задачи:

Наша проблема - проблема распознавания изображений, чтобы идентифицировать цифры из изображений 28 x 28. У нас есть подмножество изображений для обучения, а остальные - для тестирования нашей модели.

Итак, сначала скачайте обучающий и тестовые файлы. Набор данных содержит заархивированный файл со всеми изображениями, и файлы train.csv и test.csv содержат имена соответствующих обучающих и тестовых изображений. Никаких дополнительных функций в наборах данных не предусмотрено, только необработанные изображения в формате «.png».

Давайте начнем:

ШАГ 0: Готовимся

а) Импортируйте все необходимые библиотеки
# import modules
%pylab inline
import os
import numpy as np
import pandas as pd
from scipy.misc import imread
from sklearn.metrics import accuracy_score
б) Давайте зададим начальное значение, чтобы мы могли контролировать случайность наших моделей.
# To stop potential randomness
seed = 128
rng = np.random.RandomState(seed)
c) Первый шаг - задать пути к каталогам для сохранности!
root_dir = os.path.abspath('.')
data_dir = os.path.join(root_dir, 'data')

# check for existence
os.path.exists(root_dir), os.path.exists(data_dir)
ШАГ 1: Загрузка и предварительная обработка данных

а) Теперь давайте прочитаем наши наборы данных. Они представлены в формате .csv и имеют имя файла с соответствующими метками.
# load dataset
train = pd.read_csv(os.path.join(data_dir, 'Train', 'train.csv'))
test = pd.read_csv(os.path.join(data_dir, 'Test.csv'))

sample_submission = pd.read_csv(os.path.join(data_dir, 'Sample_Submission.csv'))

train.head()

б) Давайте посмотрим, как выглядят наши данные! Читаем наше изображение и показываем его.
# print an image
img_name = rng.choice(train.filename)
filepath = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)

img = imread(filepath, flatten=True)

pylab.imshow(img, cmap='gray')
pylab.axis('off')
pylab.show()

г) Для упрощения работы с данными давайте сохраним все изображения в виде массивов numpy.
# load images to create train and test set
temp = []
for img_name in train.filename:
  image_path = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)
  img = imread(image_path, flatten=True)
  img = img.astype('float32')
  temp.append(img)
 
train_x = np.stack(temp)

train_x /= 255.0
train_x = train_x.reshape(-1, 784).astype('float32')

temp = []
for img_name in test.filename:
  image_path = os.path.join(data_dir, 'Train', 'Images', 'test', img_name)
  img = imread(image_path, flatten=True)
  img = img.astype('float32')
  temp.append(img)
 
test_x = np.stack(temp)

test_x /= 255.0
test_x = test_x.reshape(-1, 784).astype('float32')

train_y = train.label.values
д) Поскольку это типичная проблема машинного обучения, для проверки правильности работы нашей модели мы создаем набор для проверки. Давайте возьмем размер разделения 70:30 для набора обучающих и тестовых данных.
# create validation set
split_size = int(train_x.shape[0]*0.7)

train_x, val_x = train_x[:split_size], train_x[split_size:]
train_y, val_y = train_y[:split_size], train_y[split_size:]
ШАГ 2: Построение модели

а) Теперь самое главное! Давайте определим архитектуру нашей нейронной сети. Мы определяем нейронную сеть с 3 уровнями: входной, скрытый и выходной. Количество нейронов на входе и выходе фиксировано, так как на входе используется изображение 28 x 28, а на выходе - вектор 10 x 1, представляющий класс. Задаем 50 нейронов в скрытом слое. Здесь мы используем в качестве алгоритма оптимизации Adam, который является эффективным вариантом алгоритма градиентного спуска.
import torch
from torch.autograd import Variable
# number of neurons in each layer
input_num_units = 28*28
hidden_num_units = 500
output_num_units = 10

# set remaining variables
epochs = 5
batch_size = 128
learning_rate = 0.00
б) Время обучить нашу модель
# define model
model = torch.nn.Sequential(
  torch.nn.Linear(input_num_units, hidden_num_units),
  torch.nn.ReLU(),
  torch.nn.Linear(hidden_num_units, output_num_units),
)
loss_fn = torch.nn.CrossEntropyLoss()

# define optimization algorithm
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
## helper functions
# preprocess a batch of dataset
def preproc(unclean_batch_x):
  """Convert values to range 0-1"""
  temp_batch = unclean_batch_x / unclean_batch_x.max()
 
  return temp_batch

# create a batch
def batch_creator(batch_size):
  dataset_name = 'train'
  dataset_length = train_x.shape[0]
  
  batch_mask = rng.choice(dataset_length, batch_size)
  
  batch_x = eval(dataset_name + '_x')[batch_mask]
  batch_x = preproc(batch_x)
  
  if dataset_name == 'train':
    batch_y = eval(dataset_name).ix[batch_mask, 'label'].values
  
  return batch_x, batch_y
# train network
total_batch = int(train.shape[0]/batch_size)

for epoch in range(epochs):
  avg_cost = 0
  for i in range(total_batch):
    # create batch
    batch_x, batch_y = batch_creator(batch_size)

    # pass that batch for training
    x, y = Variable(torch.from_numpy(batch_x)), Variable(torch.from_numpy(batch_y), requires_grad=False)
    pred = model(x)

    # get loss
    loss = loss_fn(pred, y)

    # perform backpropagation
    loss.backward()
    optimizer.step()
    avg_cost += loss.data[0]/total_batch

  print(epoch, avg_cost)
# get training accuracy
x, y = Variable(torch.from_numpy(preproc(train_x))), Variable(torch.from_numpy(train_y), requires_grad=False)
pred = model(x)

final_pred = np.argmax(pred.data.numpy(), axis=1)

accuracy_score(train_y, final_pred)
# get validation accuracy
x, y = Variable(torch.from_numpy(preproc(val_x))), Variable(torch.from_numpy(val_y), requires_grad=False)
pred = model(x)
final_pred = np.argmax(pred.data.numpy(), axis=1)

accuracy_score(val_y, final_pred)
Результат обучения:
0.8779008746355685
тогда как оценка валидации:
0.867482993197279
Это довольно впечатляющий результат, особенно если учесть, что мы обучили очень простую нейронную сеть всего за пять эпох!

Заключение

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

Комментариев нет:

Отправить комментарий