Первая нейронная сеть PyTorch

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

В следующем примере показано, как использовать PyTorch для реализации простой нейронной сети для обучения бинарной классификации.

Структура сети включает входной слой, скрытый слой и выходной слой и использует функцию активации ReLU и функцию активации Sigmoid.

Используется функция потерь среднеквадратичной ошибки и оптимизатор стохастического градиентного спуска.

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

Пример

# Импорт библиотеки PyTorch
import torch
import torch.nn as nn

# Определение размера входного слоя, размера скрытого слоя, размера выходного слоя и размера пакета
n_in, n_h, n_out, batch_size = 10, 5, 1, 10

# Создать фиктивные входные данные и целевые данные
x = torch.randn(batch_size, n_in)  # Сгенерировать входные данные случайным образом
y = torch.tensor([[1.0], [0.0], [0.0],
                 [1.0], [1.0], [1.0], [0.0], [0.0], [1.0], [1.0]])  # Целевые выходные данные

# Создать последовательную модель с линейными слоями, функцией активации ReLU и функцией активации Sigmoid
model = nn.Sequential(
   nn.Linear(n_in, n_h),  # Линейное преобразование из входного слоя в скрытый слой
   nn.ReLU(),            # Функция активации ReLU для скрытого слоя
   nn.Linear(n_h, n_out),  # Линейное преобразование из скрытого слоя в выходной слой
   nn.Sigmoid()           # Функция активации Sigmoid для выходного слоя
)

# Определить функцию потерь среднеквадратичной ошибки и оптимизатор стохастического градиентного спуска
criterion = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)  # Скорость обучения равна 0.01

# Выполнить алгоритм градиентного спуска для обучения модели
for epoch in range(50):  # Повторить 50 раз
   y_pred = model(x)  # Прямое распространение, вычислить предсказанные значения
   loss = criterion(y_pred, y)  # Вычислить потерю
   print('эпоха: ', epoch, 'потеря: ', loss.item())  # Вывести значение потерь

   optimizer.zero_grad()  # Очистить градиенты
   loss.backward()  # Обратное распространение, вычислить градиенты
   optimizer.step()  # Обновить параметры модели

Результат вывода аналогичен следующему:

epoch:  0 loss:  0.2591968774795532
epoch:  1 loss:  0.25902628898620605
epoch:  2 loss:  0.25885599851608276
epoch:  3 loss:  0.25868603587150574
epoch:  4 loss:  0.25851646065711975
...

Определите параметры сети:

n_in, n_h, n_out, batch_size = 10, 5, 1, 10
  • n_in: Размер входного слоя равен 10, то есть каждая точка данных имеет 10 признаков.
  • n_h: Размер скрытого слоя равен 5, то есть скрытый слой содержит 5 нейронов.
  • n_out: Размер выходного слоя равен 1, то есть он выводит скаляр, представляющий результат бинарной классификации (0 или 1).
  • batch_size: Каждый батч содержит 10 образцов.

Сгенерировать входные данные и целевые данные:

x = torch.randn(batch_size, n_in)  # 随机生成输入数据
y = torch.tensor([[1.0], [0.0], [0.0], 
                 [1.0], [1.0], [1.0], [0.0], [0.0], [1.0], [1.0]])  # 目标输出数据
  • x: Случайным образом сгенерировать форму(10, 10)матрицы входных данных, представляющей 10 образцов, каждый из которых имеет 10 признаков.
  • y: Целевые выходные данные (метки), представляющие метку класса (0 или 1) для каждого входного образца, — это тензор 10×1.

Определить модель нейронной сети:

model = nn.Sequential(
   nn.Linear(n_in, n_h),  # 输入层到隐藏层的线性变换
   nn.ReLU(),            # 隐藏层的ReLU激活函数
   nn.Linear(n_h, n_out),  # 隐藏层到输出层的线性变换
   nn.Sigmoid()           # 输出层的Sigmoid激活函数
)

nn.SequentialИспользуется для последовательного определения слоёв сети.

  • nn.Linear(n_in, n_h): Определяет линейное преобразование из входного слоя в скрытый слой, с 10 входными признаками и 5 нейронами в скрытом слое.
  • nn.ReLU(): Добавляет функцию активации ReLU после скрытого слоя для увеличения нелинейности.
  • nn.Linear(n_h, n_out): Определяет линейное преобразование из скрытого слоя в выходной слой, с 1 выходным нейроном.
  • nn.Sigmoid(): Выходной слой использует функцию активации Sigmoid для отображения результата в диапазон от 0 до 1 для бинарной классификации.

Определить функцию потерь и оптимизатор:

criterion = torch.nn.MSELoss()  # 使用均方误差损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)  # 使用随机梯度下降优化器,学习率为 0.01

Цикл обучения:

for epoch in range(50):  # 训练50轮
   y_pred = model(x)  # 前向传播,计算预测值
   loss = criterion(y_pred, y)  # 计算损失
   print('epoch: ', epoch, 'loss: ', loss.item())  # 打印损失值

   optimizer.zero_grad()  # 清零梯度
   loss.backward()  # 反向传播,计算梯度
   optimizer.step()  # 更新模型参数
  • for epoch in range(50): Выполните 50 итераций обучения.
  • y_pred = model(x): Выполните прямое распространение, используя текущие параметры модели для вычисления входных данныхx.
  • loss = criterion(y_pred, y): Вычислите предсказанные значения и целевые значенияyпотерю между ними.
  • optimizer.zero_grad(): Очистите значения градиента от предыдущего раунда обучения.
  • loss.backward(): Обратное распространение, вычислите градиент функции потерь по параметрам модели.
  • optimizer.step(): Обновите параметры модели на основе вычисленных градиентов.

Код визуализации:

Пример

import torch
import torch.nn as nn
import matplotlib.pyplot as plt

# Определение размера входного слоя, размера скрытого слоя, размера выходного слоя и размера пакета
n_in, n_h, n_out, batch_size = 10, 5, 1, 10

# Создать фиктивные входные данные и целевые данные
x = torch.randn(batch_size, n_in)  # Сгенерировать входные данные случайным образом
y = torch.tensor([[1.0], [0.0], [0.0],
                  [1.0], [1.0], [1.0], [0.0], [0.0], [1.0], [1.0]])  # Целевые выходные данные

# Создать последовательную модель с линейными слоями, функцией активации ReLU и функцией активации Sigmoid
model = nn.Sequential(
    nn.Linear(n_in, n_h),  # Линейное преобразование из входного слоя в скрытый слой
    nn.ReLU(),            # Функция активации ReLU для скрытого слоя
    nn.Linear(n_h, n_out),  # Линейное преобразование из скрытого слоя в выходной слой
    nn.Sigmoid()           # Функция активации Sigmoid для выходного слоя
)

# Определить функцию потерь среднеквадратичной ошибки и оптимизатор стохастического градиентного спуска
criterion = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)  # Скорость обучения равна 0.01

# Используется для хранения значения потерь для каждой эпохи
losses = []

# Выполнить алгоритм градиентного спуска для обучения модели
for epoch in range(50):  # Повторить 50 раз
    y_pred = model(x)  # Прямое распространение, вычислить предсказанные значения
    loss = criterion(y_pred, y)  # Вычислить потерю
    losses.append(loss.item())  # Записать значение потерь
    print(f'Эпоха [{epoch+1}/50], Потеря: {loss.item():.4f}')  # Вывести значение потери

    optimizer.zero_grad()  # Очистить градиенты
    loss.backward()  # Обратное распространение, вычислить градиенты
    optimizer.step()  # Обновить параметры модели

# Визуализировать кривую потерь
plt.figure(figsize=(8, 5))
plt.plot(range(1, 51), losses, label='Потеря')
plt.xlabel('Эпоха')
plt.ylabel('Потери')
plt.title('Потери при обучении по эпохам')
plt.legend()
plt.grid()
plt.show()

# Визуализировать сравнение предсказанных результатов с фактическими целевыми значениями
y_pred_final = model(x).detach().numpy()  # Итоговые предсказанные значения
y_actual = y.numpy()  # Фактические значения

plt.figure(figsize=(8, 5))
plt.plot(range(1, batch_size + 1), y_actual, 'o-', label='Фактическое', color='blue')
plt.plot(range(1, batch_size + 1), y_pred_final, 'x--', label='Предсказано', color='red')
plt.xlabel('Индекс образца')
plt.ylabel('Значение')
plt.title('Фактические и предсказанные значения')
plt.legend()
plt.grid()
plt.show()

Отображается следующим образом:

Другой пример

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

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

1. Подготовка данных

Сначала сгенерируем некоторые простые двумерные данные:

Пример

import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt

# Сгенерировать случайные данные
n_samples = 100
data = torch.randn(n_samples, 2)  # Сгенерировать 100 двумерных точек данных
labels = (data[:, 0]**2 + data[:, 1]**2 < 1).float().unsqueeze(1)  # Точка внутри круга равна 1, вне круга — 0

# Визуализировать данные
plt.scatter(data[:, 0], data[:, 1], c=labels.squeeze(), cmap='coolwarm')
plt.title("Сгенерированные данные")
plt.xlabel("Признак 1")
plt.ylabel("Признак 2")
plt.show()

Описание данных:

  • dataВходные данные — двумерная точка, каждая точка имеет две характеристики.
  • labelsЦелевая классификация: 1, если точка находится внутри круговой области, иначе 0.

Отображается следующим образом:

2. Определение нейронной сети

Используйте PyTorch, чтобы создать простую нейронную сеть прямого распространения.

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

Пример

class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        # Определение слоёв нейронной сети
        self.fc1 = nn.Linear(2, 4)  # Входной слой имеет 2 признака, скрытый слой имеет 4 нейрона
        self.fc2 = nn.Linear(4, 1)  # Скрытый слой выводит на 1 нейрон (для бинарной классификации)
        self.sigmoid = nn.Sigmoid()  # Функция активации для бинарной классификации

    def forward(self, x):
        x = torch.relu(self.fc1(x))  # Используйте функцию активации ReLU
        x = self.sigmoid(self.fc2(x))  # Выходной слой использует функцию активации Sigmoid
        return x

# Создать экземпляр модели
model = SimpleNN()

3. Определите функцию потерь и оптимизатор

Пример

# Определите функцию потерь и оптимизатор для бинарной классификации
criterion = nn.BCELoss()  # Потеря бинарной кросс-энтропии
optimizer = optim.SGD(model.parameters(), lr=0.1)  # Используйте оптимизатор стохастического градиентного спуска

4. Обучите модель

Обучите модель на данных, чтобы она научилась классифицировать.

Пример

# Обучить
epochs = 100
for epoch in range(epochs):
    # Прямое распространение
    outputs = model(data)
    loss = criterion(outputs, labels)

    # Обратное распространение
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    # Выводить потери каждые 10 эпох
    if (epoch + 1) % 10 == 0:
        print(f'Эпоха [{epoch + 1}/{epochs}], Потери: {loss.item():.4f}')

5. Протестируйте модель и визуализируйте результаты

Мы тестируем модель и строим границу решения на изображении.

Пример

# Визуализация границы решения
def plot_decision_boundary(model, data):
    x_min, x_max = data[:, 0].min() - 1, data[:, 0].max() + 1
    y_min, y_max = data[:, 1].min() - 1, data[:, 1].max() + 1
    xx, yy = torch.meshgrid(torch.arange(x_min, x_max, 0.1), torch.arange(y_min, y_max, 0.1), indexing='ij')
    grid = torch.cat([xx.reshape(-1, 1), yy.reshape(-1, 1)], dim=1)
    predictions = model(grid).detach().numpy().reshape(xx.shape)
    plt.contourf(xx, yy, predictions, levels=[0, 0.5, 1], cmap='coolwarm', alpha=0.7)
    plt.scatter(data[:, 0], data[:, 1], c=labels.squeeze(), cmap='coolwarm', edgecolors='k')
    plt.title("Граница решения")
    plt.show()

plot_decision_boundary(model, data)

6. Полный код

Полный код приведён ниже:

Пример

import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt

# Сгенерировать случайные данные
n_samples = 100
data = torch.randn(n_samples, 2)  # Сгенерировать 100 двумерных точек данных
labels = (data[:, 0]**2 + data[:, 1]**2 < 1).float().unsqueeze(1)  # Точка внутри круга равна 1, вне круга — 0

# Визуализировать данные
plt.scatter(data[:, 0], data[:, 1], c=labels.squeeze(), cmap='coolwarm')
plt.title("Сгенерированные данные")
plt.xlabel("Признак 1")
plt.ylabel("Признак 2")
plt.show()

# Определяем нейронную сеть прямого распространения
class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        # Определение слоёв нейронной сети
        self.fc1 = nn.Linear(2, 4)  # Входной слой имеет 2 признака, скрытый слой имеет 4 нейрона
        self.fc2 = nn.Linear(4, 1)  # Скрытый слой выводит на 1 нейрон (для бинарной классификации)
        self.sigmoid = nn.Sigmoid()  # Функция активации для бинарной классификации

    def forward(self, x):
        x = torch.relu(self.fc1(x))  # Используйте функцию активации ReLU
        x = self.sigmoid(self.fc2(x))  # Выходной слой использует функцию активации Sigmoid
        return x

# Создать экземпляр модели
model = SimpleNN()

# Определяем функцию потерь и оптимизатор
criterion = nn.BCELoss()  # Потеря бинарной кросс-энтропии
optimizer = optim.SGD(model.parameters(), lr=0.1)  # Используйте оптимизатор стохастического градиентного спуска

# Обучить
epochs = 100
for epoch in range(epochs):
    # Прямое распространение
    outputs = model(data)
    loss = criterion(outputs, labels)

    # Обратное распространение
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    # Выводить потери каждые 10 эпох
    if (epoch + 1) % 10 == 0:
        print(f'Эпоха [{epoch + 1}/{epochs}], Потери: {loss.item():.4f}')

# Визуализация границы решения
def plot_decision_boundary(model, data):
    x_min, x_max = data[:, 0].min() - 1, data[:, 0].max() + 1
    y_min, y_max = data[:, 1].min() - 1, data[:, 1].max() + 1
    xx, yy = torch.meshgrid(torch.arange(x_min, x_max, 0.1), torch.arange(y_min, y_max, 0.1), indexing='ij')
    grid = torch.cat([xx.reshape(-1, 1), yy.reshape(-1, 1)], dim=1)
    predictions = model(grid).detach().numpy().reshape(xx.shape)
    plt.contourf(xx, yy, predictions, levels=[0, 0.5, 1], cmap='coolwarm', alpha=0.7)
    plt.scatter(data[:, 0], data[:, 1], c=labels.squeeze(), cmap='coolwarm', edgecolors='k')
    plt.title("Граница решения")
    plt.show()

plot_decision_boundary(model, data)

Вывод функции потерь во время обучения:

Epoch [10/100], Loss: 0.5247
Epoch [20/100], Loss: 0.3142
...
Epoch [100/100], Loss: 0.0957

На рисунке показаны исходные точки данных (красные и синие), а также граница классификации, изученная моделью.

Другие расширения