6.6 Failų įvestis ir išvestis
Iki šiol aptarėme, kaip apdoroti duomenis ir kaip kurti, apmokyti ir testuoti giliojo mokymosi modelius. Tačiau tam tikru momentu, tikėkimės, būsime pakankamai patenkinti išmoktais modeliais, kad norėsime išsaugoti rezultatus vėlesniam naudojimui įvairiuose kontekstuose (galbūt net prognozėms diegimo metu daryti). Be to, vykdant ilgą apmokymo procesą, geriausia praktika yra periodiškai išsaugoti tarpinius rezultatus (kontrolinius taškus), kad nepamestume kelių dienų skaičiavimų, jei užkliūtume už savo serverio maitinimo laido. Taigi atėjo laikas išmokti įkelti ir saugoti ir atskirus svorių vektorius, ir visus modelius. Šiame skyrelyje nagrinėjami abu klausimai.
from mxnet import np, npx
from mxnet.gluon import nn
npx.set_np()
import torch
from torch import nn
from torch.nn import functional as F
import tensorflow as tf
import numpy as np
from d2l import jax as d2l
import flax
from flax import linen as nn
from flax.training import checkpoints
import jax
from jax import numpy as jnp
6.6.1 (Tenzorių įkėlimas ir išsaugojimas)
Atskiriems tenzoriams galime tiesiogiai
kviesti funkcijas load ir save
juos atitinkamai nuskaityti ir įrašyti.
Abi funkcijos reikalauja, kad nurodytume pavadinimą,
o save kaip įvesties reikalauja kintamojo, kurį reikia išsaugoti.
x = np.arange(4)
npx.save('x-file', x)
x = torch.arange(4)
torch.save(x, 'x-file')
x = tf.range(4)
np.save('x-file.npy', x)
x = jnp.arange(4)
jnp.save('x-file.npy', x)
Dabar galime nuskaityti duomenis iš išsaugoto failo atgal į atmintį.
x2 = npx.load('x-file')
x2
x2 = torch.load('x-file')
x2
x2 = np.load('x-file.npy', allow_pickle=True)
x2
x2 = jnp.load('x-file.npy', allow_pickle=True)
x2
Galime išsaugoti tenzorių sąrašą ir nuskaityti juos atgal į atmintį.
y = np.zeros(4)
npx.save('x-files', [x, y])
x2, y2 = npx.load('x-files')
(x2, y2)
y = torch.zeros(4)
torch.save([x, y],'x-files')
x2, y2 = torch.load('x-files')
(x2, y2)
y = tf.zeros(4)
np.save('xy-files.npy', [x, y])
x2, y2 = np.load('xy-files.npy', allow_pickle=True)
(x2, y2)
y = jnp.zeros(4)
jnp.save('xy-files.npy', [x, y])
x2, y2 = jnp.load('xy-files.npy', allow_pickle=True)
(x2, y2)
Galime net įrašyti ir nuskaityti žodyną, atvaizduojantį eilutes į tenzorius. Tai patogu, kai norime nuskaityti ar įrašyti visus modelio svorius.
mydict = {'x': x, 'y': y}
npx.save('mydict', mydict)
mydict2 = npx.load('mydict')
mydict2
mydict = {'x': x, 'y': y}
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')
mydict2
mydict = {'x': x, 'y': y}
np.save('mydict.npy', mydict)
mydict2 = np.load('mydict.npy', allow_pickle=True)
mydict2
mydict = {'x': x, 'y': y}
jnp.save('mydict.npy', mydict)
mydict2 = jnp.load('mydict.npy', allow_pickle=True)
mydict2
6.6.2 Modelio parametrų įkėlimas ir išsaugojimas
Atskirų svorių vektorių (ar kitų tenzorių) išsaugojimas yra naudingas, bet tampa labai varginantis, jei norime išsaugoti (o vėliau įkelti) visą modelį. Juk galime turėti šimtus parametrų grupių, pasklidusių po visą modelį. Dėl šios priežasties giliojo mokymosi karkasas suteikia įtaisytųjų funkcijų visiems tinklams įkelti ir išsaugoti. Svarbi detalė, į kurią reikia atkreipti dėmesį, yra ta, kad tai išsaugo modelio parametrus, o ne visą modelį. Pavyzdžiui, jei turime 3 sluoksnių MLP, architektūrą turime nurodyti atskirai. Priežastis ta, kad patys modeliai gali turėti savavališko kodo, todėl jų negalima taip natūraliai serializuoti. Taigi, kad atkurtume modelį, turime sugeneruoti architektūrą kode ir tada įkelti parametrus iš disko. (Pradėkime nuo mūsų pažįstamo MLP.)
class MLP(nn.Block):
def __init__(self, **kwargs):
super(MLP, self).__init__(**kwargs)
self.hidden = nn.Dense(256, activation='relu')
self.output = nn.Dense(10)
def forward(self, x):
return self.output(self.hidden(x))
net = MLP()
net.initialize()
X = np.random.uniform(size=(2, 20))
Y = net(X)
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.hidden = nn.LazyLinear(256)
self.output = nn.LazyLinear(10)
def forward(self, x):
return self.output(F.relu(self.hidden(x)))
net = MLP()
X = torch.randn(size=(2, 20))
Y = net(X)
class MLP(tf.keras.Model):
def __init__(self):
super().__init__()
self.flatten = tf.keras.layers.Flatten()
self.hidden = tf.keras.layers.Dense(units=256, activation=tf.nn.relu)
self.out = tf.keras.layers.Dense(units=10)
def call(self, inputs):
x = self.flatten(inputs)
x = self.hidden(x)
return self.out(x)
net = MLP()
X = tf.random.uniform((2, 20))
Y = net(X)
class MLP(nn.Module):
def setup(self):
self.hidden = nn.Dense(256)
self.output = nn.Dense(10)
def __call__(self, x):
return self.output(nn.relu(self.hidden(x)))
net = MLP()
X = jax.random.normal(jax.random.PRNGKey(d2l.get_seed()), (2, 20))
Y, params = net.init_with_output(jax.random.PRNGKey(d2l.get_seed()), X)
Toliau išsaugome modelio parametrus faile pavadinimu „mlp.params“.
net.save_parameters('mlp.params')
torch.save(net.state_dict(), 'mlp.params')
net.save_weights('mlp.params')
checkpoints.save_checkpoint('ckpt_dir', params, step=1, overwrite=True)
Kad atkurtume modelį, sukuriame originalaus MLP modelio kloną. Užuot atsitiktinai inicializavę modelio parametrus, tiesiogiai nuskaitome faile išsaugotus parametrus.
clone = MLP()
clone.load_parameters('mlp.params')
clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))
clone.eval()
clone = MLP()
clone.load_weights('mlp.params')
clone = MLP()
cloned_params = flax.core.freeze(checkpoints.restore_checkpoint('ckpt_dir',
target=None))
Kadangi abu egzemplioriai turi tuos pačius modelio parametrus,
tos pačios įvesties X skaičiavimo rezultatas turėtų būti toks pat.
Patikrinkime tai.
Y_clone = clone(X)
Y_clone == Y
Y_clone = clone.apply(cloned_params, X)
Y_clone == Y
6.6.3 Santrauka
Funkcijas save ir load galima naudoti tenzorių objektų failų įvesčiai ir išvesčiai atlikti.
Visas tinklo parametrų aibes galime išsaugoti ir įkelti per parametrų žodyną.
Architektūros išsaugojimas turi būti atliekamas kode, o ne parametruose.
6.6.4 Pratimai
- Net jei nereikia diegti apmokytų modelių kitame įrenginyje, kokia praktinė nauda saugoti modelio parametrus?
- Tarkime, kad norime pakartotinai panaudoti tik tinklo dalis, kad įtrauktume jas į tinklą su kitokia architektūra. Kaip naudotumėte, tarkime, pirmuosius du ankstesnio tinklo sluoksnius naujame tinkle?
- Kaip išsaugotumėte tinklo architektūrą ir parametrus? Kokius apribojimus architektūrai nustatytumėte?
