Содание модели

Последнее обновление: 03.12.2025

В прошлой теме были рассмотрены слои/модули, и как их можно объединить в единой целое. Для определения нейронной сети создается подкласс класса nn.Module - это базовый класс для всех модулей нейронной сети. В методе __init__() этого класса происходит инициализация слоев нейронной сети. А в методе forward() реализуются операции над входными данными. Например:

import torch
from torch import nn

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork()
print(model)

По сути, это классическая архитектура многослойного перцептрона (MLP), предназначенная для обработки изображений размером 28x28 пикселей. Рассмотрим подробно, что представляет этот класс:

  • class NeuralNetwork(nn.Module)

    Класс наследуется от nn.Module, который является стандартом для всех нейросетей в PyTorch.

  • self.flatten = nn.Flatten()

    self.flatten - это просто имя переменной внутри класса NeuralNetwork. Мы могли бы назвать ее self.vytyagivatel или self.reshaper, суть бы не изменилась.

    Мы присваиваем этой переменной экземпляр класса nn.Flatten(), чтобы использовать его позже в методе forward

    То есть этот слой преобразует входные данные. Если на вход подается картинка 28x28, этот слой вытягивает ее в один длинный вектор размером 784 элемента (28x28 = 784).

  • self.linear_relu_stack = nn.Sequential(...)

    Здесь мы определяем контейнер nn.Sequential, смысл которого - взять набьр модулей/слоев и выполнить их по порядку. И через параметры мы передаем в этот контейнер все необходимые слои:

    • nn.Linear(28*28, 512). nn.Linear - это слой, который применяет к входным данным линейное преобразование. В данном случае он принимает 784 входных значения и преобразует их в 512 скрытых признаков.

    • В этой модели мы используем nn.ReLU между нашими линейными слоями, но есть и другие активации для введения нелинейности в вашу модель.

    • nn.Linear(512, 512): еще один скрытый слой. Он делает нейронную сеть глубокой. Если вкратце, то он добавляет сети емкости, позволяя ей находить более сложные и хитрые закономерности в данных, чем если бы слоя было всего два (вход и выход).

      Почему именно 512 на входе и 512 на выходе? Предыдущий слой выдал нам 512 признаков. Мы не хотим пока "сжимать" информацию (уменьшать количество нейронов), потому что боимся потерять важные детали. И мы хотим просто переработать эти 512 признаков в новые, более качественные 512 признаков.

    • nn.Linear(512, 10): выходной слой. Он возвращает 10 значений. В контексте классификации цифр каждое значение соответствует одной из цифр (0–9).

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

    1. Первый слой (28*28 -> 512): Смотрит на сырые пиксели и находит простейшие палочки, черточки и границы.

    2. Скрытый слой (512 -> 512): Берет эти палочки и комбинирует их. Он понимает: "Ага, если соединить эту палочку и ту дугу, получается петля (как у цифры 6 или 8)" или "здесь есть острый угол (как у 4 или 7)".

    3. Выходной слой (512 -> 10): Смотрит на найденные петли и углы и выносит вердикт: "Это цифра 8".

    Причем без функции ReLU между слоями эти слои были бы бесполезны. Потому что в математике две линейные операции, выполненные подряд, можно заменить одной: A x B = C. В этом случае два слоя просто схлопнулись бы в один большой слой. Добавляя Linear(512, 512) вместе с ReLU, мы добавляем еще один этап нелинейной фильтрации. Это позволяет проводить границы между классами не просто прямой линией, а сложными изогнутыми кривыми. Чем больше таких слоев, тем более сложную "кривую" может построить сеть для разделения цифр.

  • forward(self, x)

    Этот метод определяет, как данные проходят через сеть:

    x = self.flatten(x)
    logits = self.linear_relu_stack(x)
    return logits
    

    сначала выравнивание (flatten), которое преобразует условно говоря изображение (двухмерный тензор размером 28*28) в линейный (плоский) вектор длиной в 784 элементов. Затем полученный вектор проходить через стек слоев (linear_relu_stack). Полученный результат в виде десяти значений и является собственно результатом нейронной сети и возвращается на выходе.

После определения классы мы можем создать объект этого класса. А с помощью стандартной функции print() мы можем вывести модель на консоль. И в итоге мы получим следующий вывод:

NeuralNetwork(
  (flatten): Flatten(start_dim=1, end_dim=-1)
  (linear_relu_stack): Sequential(
    (0): Linear(in_features=784, out_features=512, bias=True)
    (1): ReLU()
    (2): Linear(in_features=512, out_features=512, bias=True)
    (3): ReLU()
    (4): Linear(in_features=512, out_features=10, bias=True)
  )
)

ПО выводу мы видим количество входов для каждого уровня сети (in_features) и количество выходов (out_features). А bias=True означает, что в каждом линейном слое нейронной сети используется смещение (bias).

Параметры модели

Многие слои внутри нейронной сети параметризованы, то есть имеют соответствующие параметры: веса и смещения, которые оптимизируются в процессе обучения. Параметры в PyTorch представлены классом torch.nn.parameter.Parameter. По сути параметры представляют подклассы типа Tensor (тензора). PyTorch позволяет автоматически отслеживать все поля, определённые в объекте модели, которая является подклассом nn.Module. Все параметры модели доступны с помощью методов модели parameters() и named_parameters().

Метод parameters() возвращает итератор по параметрам, который мы можем использовать для перебора параметров

import torch
from torch import nn

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork()

# получаем все параметры модели
for param in model.parameters():
    print(param)

С помощью свойств параметра мы можем вывести его конкретные атрибуты:

import torch
from torch import nn

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork()

for param in model.parameters():
     print(f"Layer: {param.name} | Size: {param.size()} | Values : {param[:2]} \n")

В этом примере мы перебираем каждый параметр и выводим его имя, размер и его значения:

Layer: None | Size: torch.Size([512, 784]) | Values : tensor([[ 0.0328,  0.0123, -0.0047,  ...,  0.0354, -0.0008,  0.0075],
        [ 0.0297, -0.0023, -0.0237,  ...,  0.0199,  0.0167,  0.0047]],
       grad_fn=<SliceBackward0>) 

Layer: None | Size: torch.Size([512]) | Values : tensor([-0.0143,  0.0043], grad_fn=<SliceBackward0>) 

Layer: None | Size: torch.Size([512, 512]) | Values : tensor([[-0.0119,  0.0180, -0.0380,  ..., -0.0039,  0.0009, -0.0283],
        [-0.0191,  0.0013, -0.0424,  ...,  0.0412, -0.0094,  0.0376]],
       grad_fn=<SliceBackward0>) 

Layer: None | Size: torch.Size([512]) | Values : tensor([ 0.0274, -0.0006], grad_fn=<SliceBackward0>) 

Layer: None | Size: torch.Size([10, 512]) | Values : tensor([[ 0.0301, -0.0303,  0.0163,  ..., -0.0123,  0.0206, -0.0227],
        [-0.0069,  0.0160, -0.0440,  ..., -0.0231,  0.0300,  0.0033]],
       grad_fn=<SliceBackward0>) 

Layer: None | Size: torch.Size([10]) | Values : tensor([ 0.0021, -0.0270], grad_fn=<SliceBackward0>) 

Метод named_parameters() работает аналогично, только возвращает кортеж с двумя элементами - именем параметра и самим параметром:

import torch
from torch import nn

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork()

# получаем все параметры модели
for name, param in model.named_parameters():
    print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")

Состояние модели

Специальная функция state_dict() возвращает состояние модуля (модели), а именно веса (weight) и смещения (bias) модели. Например:

import torch
from torch import nn

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(in_features=4, out_features=2),
            nn.ReLU(),
            nn.Linear(2, 1)
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork()

print(model.state_dict())

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

OrderedDict({
    'linear_relu_stack.0.weight': tensor([[ 0.2111,  0.3965, -0.4370, -0.4225], [-0.4033, -0.3406, -0.2488, -0.2482]]), 
    'linear_relu_stack.0.bias': tensor([-0.0102, -0.0873]), 
    'linear_relu_stack.2.weight': tensor([[0.0295, 0.3597]]), 
    'linear_relu_stack.2.bias': tensor([-0.0763])})

Здесь мы можем увидеть, какие веса устанавливаются автоматически для каждого слоя (linear_relu_stack.N.weight), а также их смещения по умолчанию (linear_relu_stack.N.bias). В дальнейшем при обучении параметры модели изменяются. И значение этого метода состоит в том, что мы можем получить эти параметры (веса и смещения) и сохранить во внешний файл. А затем в произвольный момент времени загрузить эти параметры обратно без необходимости заново обучать модуль.

Определение устройства для тренировки

Для обучения модели можно использовать аппаратный ускоритель, например, CUDA, MPS, MTIA, or XPU, если он доступен. В противном случае можно использовать центральный процессор. Обычно аппаратный ускоритель предпочтителен в силу большей производительности. Для выбора ускорителя применяется функция torch.accelerator.current_accelerator(), которая возвращает объект класса torch.device(). Так, если текущий ускоритель доступен, мы используем его, иначе используем центральный процессор:

import torch

device = torch.accelerator.current_accelerator().type if torch.accelerator.is_available() else "cpu"
print(f"Using {device}")

Здесь мы получаем в переменную device тип ускорителя с помощью функции type(), но только если он доступен. Если недоступен, то передаем в переменную строку "cpu".

Также PyTorch предоставляет ряд пакетов, в частности, torch.cuda и torch.backends.mps (Metal Performance Shaders или MPS) для обращения к конкретным ускорителям/устройствам. Например, проверим, доступны ли torch.cuda или torch.backends.mps, в противном случае используем CPU:

import torch

device = (
    "cuda" if torch.cuda.is_available()
    else "mps" if torch.backends.mps.is_available()
    else "cpu"
)
print(f"Using {device}")

В данном случае если CUDA доступна, устройство использует CUDA. Если CUDA недоступна, устройство проверяет наличие MPS. Metal Performance Shaders (MPS) — фреймворк Apple для использования ускорения графического процессора на устройствах Mac. Если MPS доступна, устройство использует mps. Если же ни CUDA, ни MPS недоступны, устройство по умолчанию использует CPU для вычислений, выбрав для устройства значение cpu.

Рассмотрим, как мы можем перестить нашу нейронную сеть на устройство (при его доступности):

import torch
from torch import nn

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

# получаем устройство
device = torch.accelerator.current_accelerator().type if torch.accelerator.is_available() else "cpu"
# перемещаем на устройство
model = NeuralNetwork().to(device)
# выводим модель на консоль
print(model)

Здесь после определения класса нейронной сети сначала проверяем, доступен ли аппаратный ускоритель (например, GPU от NVIDIA, Apple Silicon MPS или другие NPU). Если да - используется он, если нет - вычисления будут проводиться на центральном процессоре (CPU).

Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850