- BrainTools - https://www.braintools.ru -
В данной публикации попробуем сформировать простейшую нейросеть. Будем использовать Colab. Данный выбор также хорош тем, что то, что позволено Юpyтеру не позволено быку. Иметь локальные вычислительные мощности. В принципе довольно неплохая инфраструктура для проверки базовых алгоритмов налету. Если есть что то подобное на других платформах или можно сделать с использованием иных агентов, пожалуйста, прокомментируйте.
Целью является демонстрация сохранения информации об обучении [1] в спектре весов, при его фильтрации и постеризации происходит не полное стирание этих данных, что можно использовать для дообучения в качестве начальных условий. При этом, после постеризации, коэффициенты весов выраженные в спектральных составляющих занимают существенно меньшее место. Также этот эффект интересен с точки зрения [2] проектирования ИНС.
Вместо кода будут md-саммари по разделам, их можно использовать для генерации в качестве промптов для ИИ-агента.
Эта ячейка определяет функцию generate_random_rectangle. Она использует библиотеку random для генерации следующих параметров:
Соотношение сторон (ширина/высота): от 0.3 до 3.0.
Ширина и высота: регулируются, чтобы находиться в диапазоне от 1 до 10.
Координаты центра (cx, cy): от -5 до 5 для обеих осей.
Прямоугольник считается квадратом (is_square=True), если его соотношение сторон находится в диапазоне от 0.8 до 1.2.
Эта ячейка демонстрирует работу функции generate_random_rectangle, вызывая ее 3 раза в цикле for i in range(3). Результаты каждого вызова функции (rect_data) выводятся на консоль с использованием форматированных строк (f-string) и ограничением до двух знаков после запятой (:.2f) для числовых значений, таких как координаты центра, ширина, высота и соотношение сторон.
Эта ячейка использует библиотеки matplotlib.pyplot (как plt) и matplotlib.patches для визуализации 15 случайных прямоугольников, сгенерированных функцией generate_random_rectangle.
Фигуры добавляются на оси (ax.add_patch(patches.Rectangle(...))) с прозрачностью alpha_value = 0.3.
Квадраты (is_square равно True) заливаются красным ('red'), а остальные прямоугольники — зеленым ('green').
Пределы осей установлены от -10 до 10 по x и y (ax.set_xlim, ax.set_ylim).
Для отображения графика используется plt.show [3]().
Далее преобразуем прямоугольники в датасет. Используем координаты вершин а также классы – квадрат или прямоугольник
Эта ячейка определяет функцию generate_pytorch_dataset, которая генерирует набор данных для PyTorch. Для каждого из num_samples образцов она вызывает generate_random_rectangle и вычисляет 8 координат вершин (x, y для нижнего левого, нижнего правого, верхнего правого и верхнего левого углов).
Метка (label) присваивается как 'square' или 'rectangle' в зависимости от поля is_square.
Эта ячейка демонстрирует использование функции generate_pytorch_dataset, создавая небольшой набор данных из 5 образцов (num_samples=5). Она выводит метку (label) и 8 координат (coordinates) для каждого сгенерированного образца, чтобы показать формат данных.
Эта ячейка определяет класс RectangleDataset, наследующийся от torch.utils.data [4].Dataset, для подготовки данных к обучению PyTorch.
Он преобразует 8 координат в тензор torch.float32.
Метки (‘rectangle’: 0, ‘square’: 1) преобразуются в тензор torch.long.
Для демонстрации создается набор данных из 20000 образцов (pytorch_dataset_tensors).
Пример образца прямоугольника в виде координат вершин, обратить внимание [5] что образован вектор как сигнал, порядок определяется порядком следования в этом векторе (нижний левый… верхний правый…)
Образец 1: Метка: rectangle Координаты (НЛ, НП, ВП, ВЛ): [-6.5377609951629, -5.900628595832362, 3.2412660734930583, -5.900628595832362, 3.2412660734930583, -1.991917979473778, -6.5377609951629, -1.991917979473778]
По образцам создаются тензоры с входным сигналом, который является координатами
Создаем PyTorch RectangleDataset с 20000 образцами для демонстрации…
Всего образцов в наборе данных PyTorch: 20000
Количество прямоугольников: 17083
Количество квадратов: 2917
Образец 1 (из набора данных PyTorch): Тензор координат: tensor([-5.9753, -2.5356, -2.9142, -2.5356, -2.9142, -1.1667, -5.9753, -1.1667]) Тип данных координат: torch.float32 Тензор метки: 0 Тип данных метки: torch.int64
Определение параметров обучения. Задаётся конфигурация и тип функции активации ReLU
Эта ячейка определяет архитектуру нейронной сети Classifier с использованием torch.nn. Модель состоит из двух полносвязных слоев (nn.Linear) и функции активации nn.ReLU():
Первый слой (fc1) имеет 8 входных нейронов (координаты) и 32 выходных.
Второй слой (fc2) имеет 32 входных нейрона [6] и 2 выходных (для классификации ‘rectangle’ или ‘square’).
Функция потерь nn.CrossEntropyLoss() и оптимизатор optim.Adam с шагом обучения lr=0.001 инициализируются. DataLoader с batch_size = 64 используется для формирования батчей данных.
Архитектура модели: Classifier(
(fc1): Linear(in_features=8, out_features=32, bias=True) (relu): ReLU()
(fc2): Linear(in_features=32, out_features=2, bias=True) )
После чего можно произвести обучение нейросети.
Эта ячейка выполняет обучение модели Classifier в течение 10 эпох (num_epochs = 10).
В каждой эпохе:
Модель переводится в режим обучения (model.train()).
Градиенты обнуляются (optimizer.zero [7]_grad()).
Выполняется прямой проход (model(inputs)).
Вычисляются потери (criterion(outputs, labels)).
Обратный проход (loss.backward()) и обновление весов (optimizer.step()).
Затем модель переводится в режим оценки (model.eval()) для расчета точности на обучающем наборе, отключая вычисление градиентов (torch.no [8]_grad()). Веса обученной модели сохраняются в глобальную переменную original_trained_model_state.
Следует вывод, по мере обучения потери, вернее, значение loss-функции уменьшаются, точность возрастает. Также, веса сохраняются в переменную для дальнейшего использования в качестве основы для фильтрации и постеризации.
Начинаем обучение на 10 эпох… Эпоха 1, Потери: 0.3943, Точность: 85.61% Эпоха 2, Потери: 0.2185, Точность: 93.08% Эпоха 3, Потери: 0.1358, Точность: 97.33%
Необходимо создать модель для валидации параметров, иными словами, для проверки работоспособности модели. Дополнительно с использованием pip устанавливаются библиотеки для дальнейшей визуализации структуры модели.
Эта ячейка создает отдельный набор данных для валидации, используя RectangleDataset с num_samples=2000. Для удобства батчирования данных создается validation_dataloader с batch_size=64.
Эта ячейка оценивает производительность обученной модели на validation_dataset. Модель переводится в режим оценки (model.eval()) и вычисление градиентов отключается (torch.no [8]_grad()).
Модель предсказывает метки для входных данных.
Вычисляются потери (loss.item()) и общая точность.
Эта ячейка устанавливает необходимые библиотеки torchviz и graphviz с помощью команды !pip install. Эти библиотеки используются для визуализации графа вычислений нейронной сети.
Вывод:
Всего образцов для валидации: 2000 Количество прямоугольников в наборе валидации: 1706 Количество квадратов в наборе валидации: 294
Потери на валидации: 0.0441 Точность на валидации: 99.10%
Далее строится график, так как это первая итерация получаются первые 10 эпох
Эта ячейка использует библиотеку matplotlib.pyplot для построения графика истории потерь обучения.
Используется история обучения
График отображает изменение loss (Потери) в зависимости от epoch (Эпоха) с маркерами o и синей линией.
На оси X показаны номера эпох от 1 до 10, что позволяет визуально отслеживать процесс сходимости модели.
С использованием встроенных средств можно построить структуру модели. Для обхода применяется затравочный тензор. Используется утилита dot
Эта ячейка использует библиотеку torchviz для визуализации архитектуры модели Classifier. Для этого:
Создается фиктивный входной тензор dummy_input размером (1, 8) с случайными значениями с помощью torch.randn().
torchviz.make_dot() используется для генерации графа вычислений на основе прямого прохода модели с этим входным тензором.
Граф отображается непосредственно в окне или png файл

После чего с использованием модели фильтруем сгенерированные прямоугольники на прямоугольники и квадраты
Эта ячейка генерирует 50 новых тестовых образцов (num_test_samples = 50) с использованием generate_pytorch_dataset. Затем обученная модель (model.eval() и torch.no [8]_grad()) предсказывает метки для этих образцов. matplotlib.pyplot и matplotlib.patches используются для построения двух графиков:
Левый график: показывает фактические фигуры (квадраты красные, прямоугольники зеленые).
Правый график: отображает только те фигуры, которые модель предсказала как квадраты.
Вывод отображает все фигуры и выделенные нейросетью
Количество фактических меток: Прямоугольники=43, Квадраты=7
Количество предсказанных меток: Прямоугольники=43, Квадраты=7
Точность на 50 тестовых образцах: 100.00%
Переходим к непосредственно исследованию весов нейросети. Построим “тепловую” карту для каждого слоя, отражающую значения весов нейронов.
Эта ячейка визуализирует веса (weights) каждого линейного слоя (fc1, fc2) нейронной сети с помощью matplotlib.pyplot. Перед этим загружаются сохраненные исходные веса (original_trained_model_state) в модель.
Веса преобразуются в массив numpy (layer_module.weight.cpu().detach().numpy()).
Отображаются в виде тепловых карт (ax.imshow(weights.T, cmap='viridis', aspect='auto')).
Цикл по всем весам сохраняя их на графике
Для первого слоя :
Ось Y подписана входные признаки
Ось X — Нейроны скрытого слоя
Для последнего слоя:
Ось Y подписана Нейроны скрытого слоя
Ось X — Выходные классы
Основные веса представлены на рис. 1.
После чего осуществляем прямое дискретное преобразование Фурье. Переводим двумерные графики весов слоёв в комплексно-частотную область, для мнимой и действительных составляющих, не АЧХ/ФЧХ.
Эта ячейка применяет Дискретное Преобразование Фурье (np.fft.fft) к весам (weights) каждого линейного слоя модели вдоль оси входных признаков (axis=1).
Затем визуализируются действительная (np.real(dft_result)) и мнимая (np.imag(dft_result)) части полученных Фурье-коэффициентов.
Отображение происходит в виде тепловых карт (ax.imshow(..., cmap='coolwarm')).
Для двух слоев и более создаются отдельные подграфики для действительной и мнимой частей.
Двумерная, комплексно-частотная характеристика исходных весов представлена на рис. 2.
Для реализации идеи используем сглаживание с применением усредняющего фильтра с параметром 2 точки.
Эта ячейка продолжает анализ весов в частотной области, применяя сглаживание к действительной и мнимой частям результатов ДПФ.
Для этого используется scipy.ndimage.uniform_filter() с filter_size пользователем.
Сглаженные действительные (smoothed_real_part) и мнимые (smoothed_imag_part) части весов каждого линейного слоя визуализируются в виде тепловых карт (ax.imshow(..., cmap='coolwarm')).
Это помогает выявить более крупные структурные паттерны в Фурье-представлении весов.
Результирующий спектр показан на рис. 3, 4, и 5 для параметров сглаживания 2,3 и 4 соответственно.
Далее производится постеризация с использованием заданного количества уровней. Для упрощения приведём пример для 2 и 3 уровней для графика на рис. 4 с параметром n = 3.
Эта ячейка вводит функцию posterize_array для квантования значений массива до n_levels.
Эта функция применяется к действительной и мнимой частям результатов ДПФ весов каждого линейного слоя.
Полученные постеризованные значения (posterized_real_part, posterized_imag_part) визуализируются в виде тепловых карт (ax.imshow(..., cmap='coolwarm')).
Данные сохраняются в глобальную переменную saved_posterized_dft_data.
На рис. 6 представлен результат постеризации для 3 уровней и количества точек усреднения 3, на рис. 7 – результат постеризации для 2 уровня и такого же количества точек усреднения 3. Постеризация означает “забыть” некоторые спектральные составляющие распределения весов.
Следует отметить что на рис. 7 фактически имеем двоичную карту спектра весов, состоящую только из -k и k.
Далее над этим штрих-кодом, фактически представляющий собой гармоники, образующие карту весов, осуществляем обратное двумерное дискретное преобразование Фурье.
Эта ячейка выполняет обратное Дискретное Преобразование Фурье (np.fft.ifft) над постеризованными действительной и мнимой частями ДПФ, которые были сохранены в saved_posterized_dft_data.
Восстановленные веса (reconstructed_weights) для каждого слоя затем визуализируются в виде тепловых карт (ax.imshow(reconstructed_weights.T, cmap='viridis')).
Это позволяет увидеть, как выглядят веса после этапов ДПФ, постеризации и ОДПФ.
Восстановленные веса для слоев сохранены в reconstructed_weights_per_layer.
После чего восстанавливаются веса, содержащие усреднённые спектральные составляющие, представленные на рис. 8. Веса распределяются согласно восстановленному спектру, включая эффекты Гиббса (для сигнала) и прочие характерные детали.
Далее проведём эксперимент с первой итерацией. Предположим количество точек фильтрации 2 и уровней 3.
Загружаем эти веса в модель:
Эта ячейка загружает восстановленные веса, полученные после обратного ДПФ постеризованных значений (reconstructed_weights_per_layer), обратно в линейные слои (fc1, fc2) модели Classifier.
Для каждого слоя проверяется совпадение форм весов.
Затем новые веса присваиваются атрибуту .data (module.weight.data [9] = new_weights) соответствующего слоя torch.nn.Linear.
Это позволяет модифицировать модель для дальнейшего тестирования или дообучения с измененными весами.
Вывод, проверяем что веса успешно загружены из постеризованной модели
Загружаем восстановленные веса в модель… Веса для слоя ‘fc1’ успешно загружены. Веса для слоя ‘fc2’ успешно загружены. Восстановленные веса успешно загружены в модель.
Дообучаем модель
Эта ячейка продолжает обучение модели с загруженными ‘постеризованными’ весами в течение еще 10 эпох (num_epochs_for_retraining = 10). Процесс обучения аналогичен первоначальному, с вычислением потерь (epoch_loss) и точности (epoch_accuracy) на обучающем наборе данных.
Результаты этого дополнительного этапа обучения добавляются в общую training_history.
И производим объединение результата.
Эта ячейка визуализирует объединенную историю обучения модели, используя matplotlib.pyplot. Она строит два графика — один для потерь (losses) и один для точности (accuracies), разделяя их по экспериментам (начальное обучение, первое дообучение).
Каждый эксперимент представлен отдельной линией с различными colors и linestyles.
Оси X показывают Относительную эпоху (начиная с 1) для каждого этапа обучения, что позволяет сравнивать динамику производительности модели до и после модификации весов.
Начиная с ячейки загрузки постеризованных весов варьируем условия. Пока что вручную но можно автоматизировать. Задаём последовательно уровней фильтрации 2,3,4 для каждого уровня количество уровней постеризации 3 затем 2. Получается 6 значений экспериментов. Выведем общий график
Пробуем самый плавный вариант – 6 точек и 2 уровня дискретизации и 8 уровней и 2 уровня дискретизации.

На главном графике представлены следующие эксперименты. Начальное обучение – без фильтра, базовые веса. Первое дообучение – сглаживание 2 уровней 3, второе – сглаживание 2 уровней 2, эксп 4 – сглаживание 3 уровней 3, экс 5 – сглаживание 3 уровней 2, экс 6 – сглаживание 4 уровней 3, эк 7 – сглаживание 4 уровней 2, эк 8 – сглаживание 6 уровней 2, эк 9 – сглаживание 8 уровней 2, эк 10 – сглаживание 8 уровней 3.
Как видно, ключевое различие в основном в уровнях постеризации. И, действительно, идеальный фильтр уменьшает амплитуду “пульсаций” весов, но не стирает информацию (!), просто её амплитуда становится меньше, если фильтр имеет ограниченную разрядную сетку – тогда происходят потери. Поэтому, для лучшей имитации даже с фильтром с плавающей запятой, необходимо применять не нормализованную постеризацию, то есть в абсолютном значении. Тем не менее, результат следующий – использование весов, восстановленных из доведённой до двоичного значения структуры спектра, позволяет дообучить модель начиная со 2 эпохи уже до релизного варианта полного обучения. Самое интересное что точность при “стирании” части спектра остаётся относительно высокой. В процессе обучения модели с начальными условиями с тремя уровнями дают лучший результат, с двумя – сходятся практически к модели обучаемой с нуля, однако, дают большую точность.
Количество уровней постеризации: 3
Теоретически необходимо 2 бит на каждое значение для 3 уровней.
Слой: fc1
Размерность данных (каждая часть): (32, 8)
Объем действительной части (float32): 1024 байт
Объем мнимой части (float32): 1024 байт
Объем действительной части (uint8): 256 байт
Объем мнимой части (uint8): 256 байт
Теоретический мин. объем (биты): 1024 бит
Слой: fc2
Размерность данных (каждая часть): (2, 32)
Объем действительной части (float32): 256 байт
Объем мнимой части (float32): 256 байт
Объем действительной части (uint8): 64 байт
Объем мнимой части (uint8): 64 байт
Теоретический мин. объем (биты): 256 бит
--- Сводка ---
Общее количество элементов для постеризованных весов: 640
Общий объем данных (float32): 2560 байт
Общий объем данных (uint8): 640 байт
Общий теоретический мин. объем данных (биты): 1280 бит
Общий теоретический мин. объем данных (байты): 160 байт
Относительное сокращение объема (float32 -> uint8): 75.00%
Относительное сокращение объема (float32 -> теоретический мин.): 93.75%
Таким образом, не смотря на то, что стираем “память” в виде квантования спектра по уровню, оставляя определённые ноты, характерные для шаблона распределения весов нейросети, можно её довольно быстро восстановить дообучением. Данное явление по всей видимости связано также с особенностями самих методов, которые обеспечивают реализацию алгоритмов первого и второго порядка, практически точно предсказывая градиент от заданных начальных условий, содержащей крупицы информации от предыдущего обучения, плюс ещё функция активации ReLU имеющая линейный участок. Таким образом, сеть может “забыть” до 75%-90% спектрального распределения весов, затем, в процессе переобучения быстро восстановиться от этих начальных условий.
Автор: TimurZhoraev
Источник [10]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/29704
URLs in this post:
[1] обучении: http://www.braintools.ru/article/5125
[2] зрения: http://www.braintools.ru/article/6238
[3] plt.show: http://plt.show
[4] torch.utils.data: http://torch.utils.data
[5] внимание: http://www.braintools.ru/article/7595
[6] нейрона: http://www.braintools.ru/article/6020
[7] optimizer.zero: http://optimizer.zero
[8] torch.no: http://torch.no
[9] module.weight.data: http://module.weight.data
[10] Источник: https://habr.com/ru/articles/1030520/?utm_campaign=1030520&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.