Модуль: PyTorch. Теория: как обучается нейросеть


PyTorch и тензоры


Войдите на сайт, чтобы ответы сохранялись и баллы за тетрадь засчитывались.

Как изображение становится понятным нейросети

Представьте, что на экране появилась рукописная семёрка. Для человека это сразу цифра: мы узнаём наклонную черту и короткую перекладину, даже если почерк непривычный. Компьютер начинает с другого. Ему доступна таблица чисел — яркостей отдельных точек изображения, которые называют пикселями. Чтобы получить из этой таблицы ответ «семь», нужно выполнить последовательность вычислений. Нейросеть как раз задаёт такую последовательность, а во время обучения подбираются числа, от которых зависит её результат.

Эти подбираемые числа называют параметрами модели. К ним относятся веса и смещения, уже встречавшиеся нам в выражении X @ W + b. Веса определяют, как входные значения участвуют в вычислении, а смещения позволяют сдвигать результат. При работе с NumPy мы сами записывали вычисления и производные. По мере роста сети такой код становится труднее проверять: ошибка в одной производной может испортить всё обучение. PyTorch позволяет поручить библиотеке вычисление производных и обновление параметров, сохранив за нами выбор данных, устройства сети и способа оценки её ответа.

Начнём с того, в каком виде PyTorch хранит числа. Его основной объект — тензор. В этой главе можно понимать тензор как числовой массив: одно число, строку чисел, таблицу или массив с большим количеством осей. Само слово не означает, что перед нами что-то обязательно сложное. Таблица из двух строк и трёх столбцов — тоже тензор. Важно уметь читать её форму и понимать, что означает каждая ось.

Будем придерживаться удобного соглашения: в таблице данных одна строка соответствует одному объекту, а столбцы содержат его признаки. Признак — числовая характеристика объекта; для маленького изображения такими характеристиками могут быть яркости пикселей. В следующем примере у нас два объекта и по три признака у каждого. Посмотрим на форму таблицы, тип хранящихся чисел и один выбранный элемент.



Первая строка вывода, torch.Size([2, 3]), сообщает форму тензора: две строки, три столбца. Форма отвечает на вопрос «как организованы значения», а не «какие значения записаны внутри». Поэтому замена всех чисел нулями не изменила бы X.shape. Выражение X[1, 0] выбирает первую величину во второй строке: индексы отсчитываются от нуля. Результат всё ещё является тензором, а метод .item() извлекает из тензора с одним элементом обычное число Python — здесь это 4.0.

Рядом с формой мы увидели torch.float32. Это тип данных, то есть способ представления каждого числа в памяти. Вещественные числа позволяют хранить дробные значения: вес нейрона вполне может равняться 0.2, а после обучения стать 0.3. Целочисленный тип нужен в других ситуациях, например для номера правильного класса. Метка «7» означает выбор одной из десяти цифр, а не измерение величины, лежащей между шестью и восемью.



В этом примере PyTorch создаёт первый тензор с типом torch.int64, поскольку ему передали целые числа. Во втором случае мы явно попросили torch.float32. Когда будем обучать классификатор, входные признаки и параметры представим вещественными числами, а номера классов для выбранной функции ошибки — целыми. Одинаковые на вид значения ещё не означают, что тензоры можно бездумно подставлять друг вместо друга: значение, форма и тип выполняют разные роли.

Почему форма изображения меняется, а изображение остаётся тем же

Теперь вернёмся к цифре. Изображение размером 8 × 8 содержит 64 пикселя. Если таких изображений двадцать, удобно хранить их в тензоре формы (20, 8, 8): первая ось перебирает изображения, две остальные — строки и столбцы внутри изображения. Линейный слой, с которым мы начнём работу, будет получать каждую картинку как строку из 64 признаков. Поэтому изменим форму данных, сохранив порядок значений.



Получившаяся форма (20, 64) означает, что объекты по-прежнему разделены: каждой картинке принадлежит своя строка. Команда reshape не распознала изображение и не добавила информации. Она только иначе организовала те же 1280 значений. Число элементов при таком преобразовании должно сохраняться. В примере все пиксели равны нулю, чтобы мы могли сосредоточиться на форме, но с настоящими изображениями выполнялось бы то же преобразование.

Ту же задачу решает images.flatten(start_dim=1): он объединяет оси начиная с оси 1 и сохраняет ось объектов. Если вызвать flatten() без аргументов, в одну длинную строку объединятся также разные изображения. Тогда мы потеряем удобное разделение на объекты. Прежде чем менять форму, полезно проговорить, что должно означать каждое измерение результата.

Пока у нас есть только данные, а не обучающаяся система. Следующий шаг — разобраться, как вычисления превращают эти данные в новые значения. Именно здесь знакомые сложение и умножение получают важное уточнение: для тензоров имеет значение не только действие, но и то, какие оси оно соединяет.

Проверим понимание

Перед ответом на вопросы представьте таблицу целиком: сколько в ней объектов и сколько значений относится к каждому? Это помогает выбрать форму по смыслу, а не угадать её по записи.


У тензора 7 объектов, по 4 признака у каждого. Какая форма сохраняет объекты по строкам?


Вход — 6 изображений формы (6, 8, 8). Как получить по одной строке на изображение?