Остановка и продолжение обучения сети¶
Введение¶
В этом туториале мы рассмотрим такую возможность библиотеки PuzzleLib
, как продолжение обучения с некоторого чекпойнта.
Возможность останавливать обучение модели по необходимости, чтобы потом продолжить - важная, но не сложная техническая задача. Она может помочь организовывать длинные процессы обучения, сохранять чекпойнты, загружаться и продолжать обучение от любого из них и, как следствие, безопасно экспериментировать.
Перед продолжением чтения рекомендуем пройти, если вы ещё этого не сделали, туториал Обучение MNIST классификатора, так как данный материал базируется на нём и не затрагивает некоторые моменты, которые были освещены в MNIST-туториале.
Обучающая выборка¶
Зайдите на сайт Яна ЛеКуна (создателя датасета MNIST) и скачайте следующие файлы:
- t10k-images.idx3-ubyte.gz
- t10k-labels.idx1-ubyte.gz
- train-images.idx3-ubyte.gz
- train-labels.idx1-ubyte.gz
Поместите скачанные файлы в выбранный вами каталог, а затем распакуйте. Получившиеся файлы содержат 70000 чёрно-белых изображений размера 28 на 28 пикселей с рукописными цифрами от 0 до 9.
Реализация инструментами библиотеки¶
Давайте взглянем на структуру скрипта, т.к. хоть он и базируется на туториале Обучение MNIST классификатора, здесь всё же присутствуют некоторые отличия.
Импорты аналогичные:
import numpy as np
import os
from PuzzleLib.Datasets import MnistLoader
from PuzzleLib.Models.Nets.LeNet import loadLeNet
from PuzzleLib.Containers import Sequential
from PuzzleLib.Modules import Conv2D, MaxPool2D, Activation, Flatten, Linear
from PuzzleLib.Modules.Activation import relu
from PuzzleLib.Handlers import Trainer, Validator
from PuzzleLib.Optimizers import MomentumSGD
from PuzzleLib.Cost import CrossEntropy
#net = loadLeNet(None, initscheme=None)
Но для лучшего понимания реализуем все слои самостоятельно. Для удобства вынесем построение архитекутры сети в отдельную функцию buildNet
:
def buildNet():
net = Sequential()
net.append(Conv2D(1, 16, 3))
net.append(MaxPool2D())
net.append(Activation(relu))
net.append(Conv2D(16, 32, 4))
net.append(MaxPool2D())
net.append(Activation(relu))
net.append(Flatten())
net.append(Linear(32 * 5 * 5, 1024))
net.append(Activation(relu))
net.append(Linear(1024, 10))
return net
Также вынесен в отдельную функцию сам процесс обучения - train
:
def train(net, optimizer, data, labels, epochs):
cost = CrossEntropy(maxlabels=10)
trainer = Trainer(net, cost, optimizer)
validator = Validator(net, cost)
for i in range(epochs):
trainer.trainFromHost(data[:60000], labels[:60000], macroBatchSize=60000,
onMacroBatchFinish=lambda tr: print("Train error: %s" % tr.cost.getMeanError()))
print("Accuracy: %s" % (1.0 - validator.validateFromHost(data[60000:], labels[60000:], macroBatchSize=10000)))
optimizer.learnRate *= 0.9
print("Reduced optimizer learn rate to %s" % optimizer.learnRate)
Аргументы функции:
net
- объект, представляющий сеть в библиотеке (в нашем случаеSequential
);optimizer
- оптимизатор сети, являющийся объектом классаOptimizer
из семейства Optimizers;data
- тензор данных форматаnp.ndarray
; в нашем случае это будут тензора размерности (N, C, H, W), где
N - общее количество картинок,
C - канальность картинок (для MNIST картинки ч/б, т.е. одноканальные),
H - высота и W - ширина картинок (28 и 28 соответственно);
- labels
- вектор ярлыков (лейблов) формата np.ndarray
и длины N для соответствующих картинок;
- epochs
- количество эпох обучения.
Загрузим данные:
path="./"
mnist = MnistLoader()
data, labels = mnist.load(path)
data, labels = data[:], labels[:]
print("Loaded mnist")
np.random.seed(1234)
Important
Не забудьте поменять значение переменной path
на тот путь, по которому вы распаковали архивы с датасетом.
Следующим шагом создаём сеть и устанавливаем на неё оптимизатор:
net = buildNet()
optimizer = MomentumSGD()
optimizer.setupOn(net, useGlobalState=True)
optimizer.learnRate = 0.1
optimizer.momRate = 0.9
Теперь нам нужно провести несколько эпох обучения - мы выбрали 10:
epochs = 10
print("Training for %s epochs ..." % epochs)
train(net, optimizer, data, labels, epochs)
По истечении 10 эпох объекты net
и optimizer
стали обладать некоторым специфическим внутренним состоянием (распределением значений параметров), которое мы хотим зафиксировать, чтобы использовать в дальнейшем для продолжения обучения:
print("Saving net and optimizer ...")
net.save(os.path.join(path, "net.hdf"))
optimizer.save(os.path.join(path, "optimizer.hdf"))
И наконец, в тот момент, когда мы захотим продолжить обучение, мы просто загружаем параметры из файлов (в предварительно созданные объекты соответствующих классов):
print("Reloading net and optimizer ...")
net.load(os.path.join(path, "net.hdf"))
optimizer.load(os.path.join(path, "optimizer.hdf"))
print("Continuing training for %s epochs ..." % epochs)
train(net, optimizer, data, labels, epochs)
В конце скрипта выполняется чистка от файлов, созданных за время его выполнения:
os.remove(os.path.join(path, "net.hdf"))
os.remove(os.path.join(path, "optimizer.hdf"))