Простая реализация PyTorch с нуля

искусственный интеллект Python PyTorch Нейронные сети NumPy
Простая реализация PyTorch с нуля
Выбрано с GitHub, скомпилировано сердцем машины.
В этом руководстве показано, как начать с понимания тензоров и обучить простую нейронную сеть с помощью PyTorch, и это очень простой вводный ресурс PyTorch.PyTorch построен на основе Python и библиотеки Torch и предоставляет абстракцию, подобную Numpy, для характеристики тензоров (или многомерные массивы), он также может использовать преимущества графического процессора для повышения производительности. Код для этого руководства неполный, подробности см. в оригинальной документации Jupyter Notebook.
PyTorch позволяет легко начать работу с глубоким обучением, даже если у вас мало базовых знаний в этой области. По крайней мере, полезно знать, что модель многослойной нейронной сети можно рассматривать как граф узлов, связанных весами, которые можно оценить по данным с помощью процесса оптимизации (например, вычисления градиента), основанного на прямом вычислении. и обратное распространение.
  • Предварительные требования: в этом руководстве предполагается, что читатель знаком с Python и NumPy.
  • Предварительные требования: вам необходимо установить PyTorch перед запуском оригинального Jupyter Notebook. В исходной записной книжке есть ячейки с кодом, чтобы убедиться, что вы готовы.
  • Требуемое оборудование: у вас должны быть установлены NVIDIA GPU и CUDA SDK. Сообщается, что это потенциальное ускорение 10-100. Конечно, если у вас нет этой настройки, вы все равно можете запустить PyTorch, используя только ЦП. Но помните, жизнь коротка, когда дело доходит до обучения нейросетевых моделей! Так что используйте GPU как можно больше!

1. Необходимый фон PyTorch

  • PyTorch — это пакет Python, созданный на основе библиотеки Torch и предназначенный для ускорения приложений глубокого обучения.
  • PyTorch предоставляет абстракцию, подобную NumPy, для представления тензоров (или многомерных массивов), которая может использовать преимущества графических процессоров для ускорения обучения.

1.1 Тензор PyTorch

Ключевой структурой данных в PyTorch являются тензоры, то есть многомерные массивы. Его функция аналогична объекту ndarray NumPy, поэтому мы можем использовать torch.Tensor() для создания тензоров.
#生成2-d pytorch张量(即,基质)
 pytorch_tensor = torch.Tensor(10,20)
的打印(“类型:” ,类型(pytorch_tensor), “ 和尺寸:”,pytorch_tensor.shape)

Если вам нужно представление, совместимое с NumPy, или вы хотите создать тензор PyTorch из существующего объекта NumPy, это легко сделать.
#将pytorch张量转换为numpy数组:
 numpy_tensor = pytorch_tensor.numpy()
print(“type:”,type(numpy_tensor),“and size:”,numpy_tensor.shape)

#将numpy数组转换为Pytorch Tensor:
 print(“type:”,type(torch.Tensor(numpy_tensor)),“and size:”,torch.Tensor(numpy_tensor).shape)

1.2 PyTorch и NumPy

PyTorch — это не простая замена NumPy, но он реализует множество функций NumPy. Одним из неудобств является соглашение об именовании, которое иногда сильно отличается от соглашения об именовании NumPy. Давайте возьмем несколько примеров, чтобы проиллюстрировать разницу:
1 создание тензора
T = torch.rand(2,4,3,5)
一个= np.random.rand(2,4,3,5)

2 тензорная сегментация
T = torch.rand(2,4,3,5)
一个= t.numpy()
pytorch_slice = T [ 0,1:3,:,4 ] 
numpy_slice = A [ 0,1:3,:,4 ] 
打印('张量[0,1:3,:1,4]:\ N',pytorch_slice)
打印('NdArray [0,1:3,:1,4]:\ N',numpy_slice)

------- -------------------------------------------------- ---------------- 
张量[ 0,1:3,:,4 ]:

 0.2032   0.1594   0.3114 
 0.9073   0.6497   0.2826
 [torch.FloatTensor大小的2 ×3] 

NdArray [ 0,1:3,:,4 ]:
[[ 0.20322084   0.15935552   0.31143939 ] 
[ 0.90726137   0.64966112   0.28259504 ]]

3 тензорная маскировка
t = t  - 0.5
 a = t.numpy()
pytorch_masked = t [t> 0 ] 
numpy_masked = a [a> 0 ]

4 Изменение формы тензора
pytorch_reshape = t.view([ 6,5,4 ])
numpy_reshape = a.reshape([ 6,5,4 ])

1.3 Переменные PyTorch

  • Простая оболочка для тензоров PyTorch
  • Помогите построить вычислительные графики
  • Необходимая часть Автограда (библиотека автоматического дифференцирования)
  • сохранить градиенты об этих переменных в .grad
Структурная схема:
Вычислительные графики и переменные: в PyTorch нейронные сети представлены с использованием взаимосвязанных переменных в виде вычислительных графиков. PyTorch позволяет строить вычислительные графы с помощью кода для построения сетевых моделей; Затем PyTorch упрощает процесс оценки весов моделей, например, путем автоматического расчета градиентов.
Например, предположим, что мы хотим построить двухслойную модель, затем сначала создадим тензорные переменные для ввода и вывода. Мы можем обернуть тензор PyTorch в переменный объект:
从 torch.autograd 进口可变
进口 torch.nn.functional 作为 ˚F 

X =变数(torch.randn(4,1),requires_grad = 假)
Y =变量(torch.randn(3,1),requires_grad = 假)

Мы устанавливаем для requirements_grad значение True, указывая на то, что мы хотим автоматически вычислять градиенты, которые будут использоваться при обратном распространении для оптимизации весов.
Теперь давайте определим веса:
W1 =变数(torch.randn(5,4),requires_grad = 真)
W2 =变量(torch.randn(3,5),requires_grad = 真)

Обучите модель:
def  model_forward (x):
    return F.sigmoid(w2 @ F.sigmoid(w1 @ x))

 print(w1)
 print(w1.data.shape)
 print(w1.grad)#最初,不存在

 ---- -------------------------------------------------- -------------------
可变含:
  1.6068  -1.3304  -0.6717  -0.6097 
-0.3414  -0.5062  -0.2533   1.0260 
-0.0341  -1.2144  -1.5983  -0.1392 
-0.5473   0.0084   0.4054   0.0970 
 0.3596   0.5987  -0.0324   0.6116
 [torch.Float传感器的大小5 X4的] 

torch.Size([ 5,4 ])
无

1.4 Обратное распространение PyTorch

Итак, у нас есть входные данные и цель, веса модели, а затем пришло время обучить модель Нам нужны три компонента:
функция потерь: опишите, насколько далеки прогнозы нашей модели от цели;
导入 torch.nn 为 nn 
条件= nn.MSELoss()

Алгоритм оптимизации: используется для обновления весов;
导入 torch.optim 作为 optim 
optimizer = optim.SGD([w1,w2],lr = 0.001)

Этапы обратного распространения:
对于历元在范围(10):
    损耗=标准(model_forward(x)中,y)的
    optimizer.zero_grad() #零出以前梯度
     loss.backward()#计算新梯度
     optimizer.step() #应用这些梯度


打印( w1)

------------------------------------------------ ------------------------- 
变量包含:
 1.6067  -1.3303  -0.6717  -0.6095 
-0.3414  -0.5062  -0.2533   1.0259 
-0.0340  -1.2145  -1.5983  -0.1396 
-0.5476   0.0085   0.4055   0.0976
 0.3597   0.5986  -0.0324   0.6113
 [火炬。尺寸为5 x4的飞溅传感器 ]

1.5 Интерфейс PyTorch CUDA

Одной из сильных сторон PyTorch является интерфейс CUDA для тензоров и библиотека autograd. Используя графические процессоры CUDA, вы можете ускорить не только обучение и вывод нейронной сети, но и любую рабочую нагрузку, которая сопоставляется с тензорами PyTorch.
Вы можете вызвать функцию torch.cuda.is_available(), чтобы проверить, доступна ли CUDA в PyTorch.
cuda_gpu = torch.cuda.is_available()
if(cuda_gpu):
    print(“Great,you have a GPU!”)
else:
    print(“Life is short  -  consider a GPU!”)

Отлично, теперь у вас есть GPU.
.толстый()
После этого использовать cuda для ускорения кода так же просто, как позвонить. Если вы вызовете .cuda() для тензора, он выполнит миграцию данных с CPU на GPU CUDA. Если вы вызываете .cuda() для модели, она не только перемещает всю внутреннюю память в GPU, но и сопоставляет весь вычислительный граф с GPU.
Чтобы скопировать тензор или модель обратно в ЦП, например, для взаимодействия с NumPy, вы можете вызвать .cpu().
如果 cuda_gpu:
    x = x.cuda()
    print(type(x.data))

x = x.cpu()
print(type(x.data))

--------------- -------------------------------------------------- -------- 
< class ' 火炬。cuda。FloatTensor '> 
< class ' 火炬。FloatTensor '>

Давайте определим две функции (функция обучения и функция тестирования) для выполнения задач обучения и вывода с нашей моделью. Код также взят из официального руководства по PyTorch, и мы выбрали все необходимые шаги для обучения/вывода.
Для обучения и тестирования сети нам необходимо выполнить ряд действий, которые напрямую сопоставляются с кодом PyTorch:
1. Переключаем модель в режим обучения/вывода;
2. Мы итеративно обучаем модель, собирая изображения в наборе данных партиями;
3. Для каждой партии изображений мы загружаем данные и аннотации и запускаем прямой шаг сети, чтобы получить выходные данные модели;
4. Мы определяем функцию потерь для расчета потерь между выходом модели и целью для каждой партии;
5. Во время обучения мы инициализируем градиент до нуля и используем оптимизатор и обратное распространение, определенные на предыдущем шаге, для вычисления всех иерархических градиентов, связанных с потерями;
6. Во время тренировки мы выполняем шаг обновления веса.
def  train (model,epoch,criterion,optimizer,data_loader):
     model.train()
     for batch_idx,(data,target) in enumerate(data_loader):
         if cuda_gpu:
            data,target = data.cuda(),target.cuda( )
            model.cuda()
        数据,目标=变量(数据),变量(目标)
        输出=模型(数据)

        optimizer.zero_grad()
        损失=标准(输出,目标)
        loss.backward()
        optimizer.step()
         if( batch_idx + 1)% 400 == 0:
            print('Train Epoch:{} [{} / {}({:.0f}%)] \ tLoss:{:.6f}'. format(
                epoch,(batch_idx + 1)* len(data),len(data_loader.dataset ),
                100 *(batch_idx + 1)/ LEN(data_loader),loss.data [ 0 ]))


DEF  试验(模型,历元,标准,data_loader) :
     model.eval()
    test_loss = 0
     正确= 0 
    为数据,目标在 data_loader中:
        if cuda_gpu:
            data,target = data.cuda(),target.cuda()
            model.cuda()
        数据,target =变量(数据),变量(目标)
        output = model(data)
        test_loss + = criterion(output,target).data [ 0 ] 
        pred = output.data.max(1)[ 1 ] #获取最大对数概率索引
         + = pred.eq(目标数据).cpu()。sum()

    test_loss / = len(data_loader)#失败函数已经在批量大小上取平均值
     acc = correct / len(data_loader.dataset)
    print('\ nTest set:Average loss:{:。 4f},准确度:{} / {}({:.0f }%)\ n'.format(
        test_loss,correct,len(data_loader.dataset),100. * acc))
    return(acc,test_loss)

Теперь, когда введение сделано, давайте начнем это путешествие по науке о данных!

2. Используйте PyTorch для анализа данных

  • Сборка моделей с использованием библиотеки torch.nn
  • Обучите модель с помощью библиотеки torch.autograd
  • Инкапсулировать данные в библиотеку torch.utils.data.Dataset
  • Используйте интерфейс NumPy для подключения ваших моделей, данных и ваших любимых инструментов
Прежде чем рассматривать сложные модели, давайте рассмотрим простую: линейную регрессию на простом синтетическом наборе данных, который мы можем сгенерировать с помощью инструментов sklearn.
从 sklearn.datasets 导入 make_regression 
进口 seaborn 作为 SNS 
进口熊猫作为 PD 
进口 matplotlib.pyplot 作为 PLT 

sns.set()

x_train,y_train,W_target = make_regression(N_SAMPLES次= 100,n_features = 1,噪声= 10,COEF = 真)

DF = pd.DataFrame(data = { 'X':x_train.ravel(),'Y':y_train.ravel()})

sns.lmplot(x = 'X',y = 'Y',data = df,fit_reg = True)
plt.show()

1)x_torch = torch.FloatTensor(x_train)
y_torch = torch.FloatTensor(y_train)
y_torch = y_torch.view(y_torch.size()[ 0 ],1)

В библиотеке PyTorch nn есть ряд полезных модулей, одним из которых является linear. Как следует из названия, он выполняет линейное преобразование входных данных, известное как линейная регрессия.
class  LinearRegression (torch.nn.Module):
    def  __init__ (self,input_size,output_size):
         super(LinearRegression,self).__ init __()
        self.linear = torch.nn.Linear(input_size,output_size)  

     def  forward (self,x ):
        返回 self.linear(x)的

模型=线性回归( 1, 1)

Для обучения линейной регрессии нам нужно добавить подходящую функцию потерь из библиотеки nn. Для линейной регрессии мы будем использовать MSELoss () — функцию потери среднеквадратичной ошибки.

Нам также нужно использовать функцию оптимизации (SGD) и запустить обратное распространение аналогично предыдущему примеру. По сути, мы повторяем шаги в функции train(), определенные выше. Причина, по которой эту функцию нельзя использовать напрямую, заключается в том, что мы реализуем ее для классификации, а не для регрессии, и используем кросс-энтропийную потерю и индекс наибольшего элемента в качестве предсказания модели. В то время как для линейной регрессии мы используем выходные данные линейного слоя в качестве прогноза.

准则= torch.nn.MSELoss()
优化= torch.optim.SGD(model.parameters(),LR = 0.1)   


对于历元在范围(50):
    数据,目标=变量(x_torch),变量(y_torch)
    输出= model(data)

    optimizer.zero_grad()
    loss = criterion(output,target)
    loss.backward()
    optimizer.step()

predict = model(Variable(x_torch))。data.numpy()

Теперь мы можем распечатать необработанные данные и линейную регрессию в PyTorch.
plt.plot(x_train,y_train,'o',label = '原始数据')
plt.plot(x_train,predicted,label = 'Fitted line')
plt.legend()
plt.show()

Чтобы перейти к более сложным моделям, мы загрузили набор данных MNIST в папку «Наборы данных» и протестировали некоторые из начальной предварительной обработки, доступной в PyTorch.PyTorch имеет загрузчики и процессоры данных, которые можно использовать для разных наборов данных. После загрузки вы можете использовать его в любое время. Вы также можете обернуть данные в тензоры PyTorch, чтобы создать свой собственный класс загрузчика данных.
Размер партии — это термин в машинном обучении, который относится к количеству обучающих выборок, используемых в одной итерации. Размер партии может быть одним из трех:
  • пакетный режим: размер пакета равен всему набору данных, поэтому значения итерации и эпохи согласованы;
  • мини-пакетный режим: размер пакета больше 1, но меньше размера всего набора данных. Как правило, количество может быть значением, которое делится на весь набор данных.
  • Случайный режим: размер партии равен 1. Таким образом, градиенты и параметры нейронной сети обновляются после каждой выборки.
从 torchvision 导入数据集,变换

batch_num_size = 64

 train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('data',train = True,download = True,transform = transforms.Compose([ 
        transforms.ToTensor()),
        转换。 Normalize((0.1307,),(0.3081,))
    ])),
    batch_size = batch_num_size,shuffle = True)

test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('data',train = False,transform = transforms。撰写([
        transforms.ToTensor(),
        transforms.Normalize((0.1307,),(0.3081,))
    ])),
    batch_size = batch_num_size,shuffle = True)

3. Сверточная нейронная сеть LeNet (CNN) в PyTorch

Теперь давайте создадим нашу первую простую нейронную сеть с нуля. Сеть должна выполнять классификацию изображений, распознавая рукописные цифры в наборе данных MNIST. Это четырехслойная сверточная нейронная сеть (CNN), обычная архитектура для анализа набора данных MNIST. Код взят из официального руководства по PyTorch, дополнительные примеры можно найти здесь (http://pytorch.org/tutorials/).
Мы будем использовать несколько модулей из библиотеки torch.nn:
1. Линейный слой: выполняет линейное преобразование входного тензора с использованием весов слоя;
2. Conv1 и Conv2: сверточные слои, каждый слой выводит скалярное произведение между ядром свертки (весовой тензор небольшого размера) и входной областью того же размера;
3. Relu: Модифицированная линейная единичная функция с использованием поэлементной функции активации max(0, x);
Слой пула: выполняет субдискретизацию для конкретного региона (обычно 2x2 пикселя) с использованием операции max;
5. Dropout2D: случайным образом установите все каналы входного тензора в ноль. Когда карты объектов имеют сильную корреляцию, dropout2D улучшает независимость между картами объектов;
6. Softmax: применяет функцию Log(Softmax(x)) к n-мерному входному тензору таким образом, чтобы выходные данные находились в диапазоне от 0 до 1.
类 LeNet (nn.Module) :
    DEF  __init__ (个体):
         超级(LeNet,自我).__ INIT __()
        self.conv1 = nn.Conv2d( 1, 10,kernel_size = 5)
        self.conv2 = nn.Conv2d( 10, 20,kernel_size = 5)
        self.conv2_drop = nn.Dropout2d()
        self.fc1 = nn.Linear( 320, 50)
        self.fc2 = nn.Linear( 50, 10)

     DEF  向前(个体,X) :
         X = F .relu(F.max_pool2d(self.conv1(x)的2))
        X = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(X)),2))
        X = x.view(-1,320)
        X = F.relu(self.fc1(X ))
        x = F.dropout(x,training = self.training)
        x = self.fc2(x)
        return F.log_softmax(x,dim = 1)

После создания класса LeNet создайте объект и перейдите к графическому процессору:
model = LeNet()
if cuda_gpu:
    model.cuda()

print('MNIST_net model:\ n')
print(model)

----------------------- -------------------------------------------------- 
MNIST_net模型:

LeNet(
  (CONV1):Conv2d(1,10,kernel_size =(5,5),跨度=(1,1))
  (CONV2):Conv2d(10,20,kernel_size =(5,5),步幅=(1,1))
  (conv2_drop):Dropout2d(p值=0.5)
  (fc1):线性(in_features = 320,out_features = 50,bias = 真)
  (fc2):线性(in_features = 50,out_features = 10,bias = 真)
)

Чтобы обучить эту модель, нам нужно использовать SGD с импульсом, скоростью обучения 0,01 и импульсом 0,5.
criteria = nn.CrossEntropyLoss()   
优化器= optim.SGD(model.parameters(),lr = 0.005,动量= 0.9)

Всего за 5 эпох (одна эпоха означает, что вы используете весь обучающий набор данных для обновления весов обученной модели), мы можем обучить достаточно точную модель LeNet. Этот код проверяет, есть ли предварительная подготовка в файле. Если модель загружена, то она загружается, если нет — обучается и сохраняется на диск.
import os epochs

 = 5 
if(os.path.isfile('pretrained / MNIST_net.t7')):
    print('Loading model')
    model.load_state_dict(torch.load('pretrained / MNIST_net.t7',map_location = lambda storage ,LOC:存储))
    ACC,损耗=试验(模型,1,标准,test_loader)
否则:
    打印('训练模式')
    用于历元在范围(1,历元+ 1:)
        系(模型,历元,标准,优化,train_loader)
        acc,loss = test(model,1,criterion,test_loader)
    torch.save(model.state_dict(),'pretrained / MNIST_net.t7')

------------------ -------------------------------------------------- ----- 
加载模型
试验组:平均损耗:0.0471,准确度:9859号文件 / 10000(99%)

Теперь давайте посмотрим на модель. Сначала распечатайте информацию о модели. Функция печати показывает все слои (поскольку Dropout реализован как отдельный слой) с их именами и параметрами. Существует также итератор, который проходит через все именованные блоки в модели. Это помогает, когда у вас есть комплекс с несколькими «внутренними» моделями. Итерация между всеми именованными модулями позволяет нам создавать анализаторы моделей, которые могут считывать параметры модели и создавать модули, подобные этой сети.
print('Internal models:')
for idx,m in enumerate(model.named_modules()):
    print(idx,' - >',m)
    print('-------------- -------------------------------------------------- ---------”)

#输出:
内部模型:
0 - >('',LeNet(
  (CONV1):Conv2d(1,10,kernel_size =(5,5),跨度=(1,1))
  (CONV2):Conv2d(10,20,kernel_size =(5,5),跨度=(1,1))
  (conv2_drop):Dropout2d(p值= 0.5)
  (FC1):线性(in_features = 320,out_features = 50,偏压= 真)
  (FC2):线性(in_features = 50,out_features = 10,bias = True)
))
----------------------------------------- -------------------------------- 
1 - >('CONV1',Conv2d(1,10,kernel_size =(5,5),跨度=(1,1)))
-------------------------------------------------- ----------------------- 
2 - >('CONV2',Conv2d(10,20,kernel_size =(5,5),跨度=(1,1)))
---------------------------------------------- --------------------------- 
3 - >('conv2_drop',Dropout2d(p = 0.5))
-------- -------------------------------------------------- --------------- 
4 - >('fc1',Linear(in_features = 320,out_features = 50,bias = True))
-------------------------------------------------- ----------------------- 
5 - >('fc2',Linear(in_features = 50,out_features = 10,bias = True))
---- -------------------------------------------------- -------------------

Вы можете использовать метод .cpu() для перемещения тензора на ЦП (или убедиться, что он там). В качестве альтернативы используйте метод .cuda() для перемещения тензоров на графический процессор, когда графический процессор доступен (доступно torch.cuda). Вы можете видеть, если тензор находится на GPU, он имеет тип torch.cuda.FloatTensor. Если тензор находится на процессоре, его тип — torch.FloatTensor.
print(type(t.cpu()。data))
if torch.cuda.is_available():
    print(“Cuda is available”)
    print(type(t.cuda()。data))
else:
    print(“Cuda is不可用“)

---------------------------------------------- --------------------------- 
< 类 ' 炬。FloatTensor '> 
Cuda的 是 可用的
 < 类 ' 炬。cuda。FloatTensor '>

Если тензор находится на ЦП, мы можем преобразовать его в массив NumPy, который использует то же место в памяти, и изменение одного изменяет другое.
如果 torch.cuda.is_available():
    尝试:
        打印(t.data.numpy()),
    除了 RuntimeError 为 e:
        “你不能将GPU张量转换为numpy nd数组,你必须将你的weight tendor复制到cpu然后获取numpy数组“

 print(type(t.cpu()。data.numpy()))
print(t.cpu()。data.numpy()。shape)
print(t.cpu()。data)。 numpy的())

Теперь, когда мы понимаем, как преобразовывать тензоры в массивы NumPy, мы можем использовать эти знания для визуализации с помощью matplotlib! Распечатаем сверточные фильтры для первого сверточного слоя.
data = model.conv1.weight.cpu()。data.numpy()
print(data.shape)
print(data [:, 0 ] .shape)

kernel_num = data.shape [ 0 ] 

fig,axes = plt.subplots( NCOLS = kernel_num,figsize =(2 * kernel_num,2))

为山口在范围(kernel_num):
    轴[COL] .imshow(数据[COL,0,:,:],CMAP = plt.cm.gray)
PLT。显示()

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