Ebben az alfejezetben az autoenkódolót (autoencoder) mint adatvezérelt rekonstrukciós modellt vezetjük be. A korábbi 25.1. Teljes képosztályozó adatfolyam osztályozási adatfolyam a képet címkévé alakította; itt a cél maga a bemenet visszaállítása egy szűk reprezentáción keresztül. A 28.1. Embedding, távolság és normalizálás embedding-tanulásából tudjuk, hogy a reprezentáció hasznossága a downstream céltól függ; ez az autoencoder esetében is fontos korlát lesz. Előismeretként a 7.4. Numerikus stabilitás és jelentésmegőrző implementáció stabil veszteségszámítást, a 15.4. Független gradiensellenőrzési protokoll gradiensellenőrzést, a 23.4. Hibaszeletek és alkalmazási határ hibaszeleteket és a 27.4. Instance mask és összetett feladat encoder–decoder szerkezetű látásmódját idézzük fel. Induló kérdés: ha a GlyphLab egy képét dimenziós vektorként fogjuk fel, és dimenziós kódba tömörítjük lineárisan, mekkora a minimális átlagos négyzetes rekonstrukciós hiba? Sejtésünk: az elhagyott variancia; pontos képletét azonnal levezetjük.
Legyen a bemeneti tér , a latens (rejtett) kód tere . Egy batch , egy megfigyelés oszlopvektor. Az encoder (kódoló) egy paraméteres függvény, amely a
latens reprezentációt állítja elő. A decoder (dekódoló) a rekonstrukciót adja:
A szűk keresztmetszet (bottleneck) feltétele ; enélkül elegendő kapacitású modell triviálisan megtanulhatná az identitást. A tanítási cél az átlagos négyzetes rekonstrukciós hiba:
A könyvtári MSELoss reduction='mean' esetén a
pixeldimenzión is átlagol, ezért a laborban a
skálát használjuk.
Lineáris eset. Ha és affin leképezés bias nélkül, akkor a 3.2. Mátrixszorzás és affín réteg rétegkonvenciója szerint , mellett
A centrálást külön lépésként kezeljük, mert a PCA-kapcsolathoz nulla átlagú adatra van szükség.
Legyen centrált, azaz . Legyen a tapasztalati kovarianciamátrix , sajátértékei , a hozzájuk tartozó ortonormált sajátvektorok . Rögzítsünk értéket.
Tétel (lineáris autoencoder globális optimuma). Legyen centrált, , és tegyük fel . A
feladat optimuma . Ha és , akkor az optimális szorzat egyértelmű, mégpedig , ahol .
Bizonyításvázlat. A rekonstrukció , ahol és , ezért legfeljebb rangú mátrix. A klasszikus Eckart–Young-tétel szerint minden legfeljebb rangú mátrixra , ahol az szinguláris értékei; egyenlőség a csonkolt SVD-vel, mellett teljesül. Mivel , az alsó korlát az állított sajátértékösszeg. A választással , tehát a korlát elérhető. A sajátérték-rés miatt az altér egyértelmű; teljes rangú esetén a mátrix, így is egyértelmű. ∎
Az Eckart–Young-tételt itt idézett eredményként használjuk; bizonyítását nem ismételjük, mert az SVD-tulajdonságokra épül. A 3.4. Sajátérték, SVD és spektrális határ egységben felépített SVD eszköztár alkalmas a nyomon követésére. A tétel tartalmát a megadott lineáris algebrai feltételek szerint használjuk, az itt adott levezetés önálló.
Az optimális páros nem egyértelmű. Ha optimum, akkor tetszőleges invertálható mellett , is optimum, mert . A latens térbeli koordináták tetszőleges invertálható lineáris keveredéséig határozottak meg; a rekonstrukciós altér azonban egyértelmű.
Tekintsük a centrált adatot
Itt . A kovariancia:
tehát . A esetben az első sajátvektor , a második elhagyott sajátérték . Válasszuk az optimális paramétereket és alakban. Ekkor
A hibák az utolsó két sorban és , ezért a batch-átlag , pontosan a tétel állítása szerint.
A nemlineáris esetben és általában ReLU- és konvolúciós rétegekből áll; a cél nem konvex, és nincs PCA-hoz hasonló zárt megoldás. A rekonstrukciós hiba csökkentése itt sem garantál hasznos downstream reprezentációt. A laborban egy kis CNN-autoencodert vetünk össze a lineáris, PCA-alapú rekonstrukciós baseline-nal a GlyphLab adaton.
A CNN encoder kimenete a dimenziós latens; ez a szűk keresztmetszet. A decoder transzponált konvolúciókkal állítja vissza a méretet. A transzponált konvolúció itt a megfelelő lineáris operátor adjungáltja, nem általános inverz; a rétegek a 15.3. Transposed convolution mint adjungált alfejezetben bevezetett értelemben nem az encoder inverzét számolják.
A labor a lab_autoencoder munkakönyvtárban fut. A
GlyphLab splitje a metadata.csv-ből jön; a tanítás a train,
az epoch-választás a val, a végső összehasonlítás a test halmazon
történik. A lineáris baseline a train adatból számított SVD-vel zárt
alakú; a CNN-autoencodert Adam optimalizálóval tanítjuk. A kézirat
írásakor a szkriptet nem futtattuk, ezért a kimeneti MSE-számok jelzett,
nem mért eredmények.
# lab_autoencoder/autoencoder_lab.py
import argparse, csv, json
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
def split_indices(meta_path):
result = {"train": [], "val": [], "test": []}
with open(meta_path, newline="") as f:
for row in csv.DictReader(f):
result[row["split"]].append(int(row["index"]))
return result
class CNNAutoencoder(nn.Module):
def __init__(self):
super().__init__()
self.encoder = nn.Sequential(
nn.Conv2d(1, 16, 3, stride=2, padding=1), nn.ReLU(),
nn.Conv2d(16, 32, 3, stride=2, padding=1), nn.ReLU(),
nn.Conv2d(32, 8, 3, stride=2, padding=1), nn.ReLU(),
)
self.decoder = nn.Sequential(
nn.ConvTranspose2d(8, 32, 4, stride=2, padding=1), nn.ReLU(),
nn.ConvTranspose2d(32, 16, 4, stride=2, padding=1), nn.ReLU(),
nn.ConvTranspose2d(16, 1, 4, stride=2, padding=1), nn.Sigmoid(),
)
def forward(self, x):
return self.decoder(self.encoder(x))
def linear_pca(images, idx_train, idx_test, k):
Xtr = images[idx_train].reshape(len(idx_train), -1).astype(np.float32)
Xte = images[idx_test].reshape(len(idx_test), -1).astype(np.float32)
mu = Xtr.mean(axis=0, keepdims=True)
U, S, Vt = np.linalg.svd(Xtr - mu, full_matrices=False)
Vk = Vt[:k].T
Xtr_hat = (Xtr - mu) @ Vk @ Vk.T + mu
Xte_hat = (Xte - mu) @ Vk @ Vk.T + mu
return float(np.mean((Xtr - Xtr_hat) ** 2)), float(np.mean((Xte - Xte_hat) ** 2))
def main():
p = argparse.ArgumentParser()
p.add_argument("--data", required=True)
p.add_argument("--meta", required=True)
p.add_argument("--latent", type=int, default=72)
p.add_argument("--epochs", type=int, default=20)
p.add_argument("--batch", type=int, default=32)
p.add_argument("--lr", type=float, default=1e-3)
p.add_argument("--seed", type=int, default=123)
args = p.parse_args()
torch.manual_seed(args.seed)
np.random.seed(args.seed)
device = torch.device("cpu")
images = np.load(args.data).astype(np.float32) # (360,1,24,24)
idx = split_indices(args.meta)
lin_train, lin_test = linear_pca(images, idx["train"], idx["test"], args.latent)
model = CNNAutoencoder().to(device)
opt = torch.optim.Adam(model.parameters(), lr=args.lr)
lossfn = nn.MSELoss()
tr = torch.from_numpy(images[idx["train"]])
va = torch.from_numpy(images[idx["val"]])
te = torch.from_numpy(images[idx["test"]])
tr_loader = DataLoader(TensorDataset(tr, tr), batch_size=args.batch, shuffle=True)
best_val, best_state = float("inf"), None
for _ in range(args.epochs):
model.train()
for xb, _ in tr_loader:
opt.zero_grad()
loss = lossfn(model(xb.to(device)), xb.to(device))
loss.backward()
opt.step()
model.eval()
with torch.no_grad():
vloss = lossfn(model(va.to(device)), va.to(device)).item()
if vloss < best_val:
best_val, best_state = vloss, {k: v.detach().clone() for k, v in model.state_dict().items()}
model.load_state_dict(best_state)
model.eval()
with torch.no_grad():
cnn_test = lossfn(model(te.to(device)), te.to(device)).item()
print(json.dumps({"linear_train_mse": lin_train, "linear_test_mse": lin_test,
"cnn_val_mse": best_val, "cnn_test_mse": cnn_test}))
if __name__ == "__main__":
main()Futtatási parancs:
python autoencoder_lab.py --data ../lab/data/images.npy --meta ../lab/data/metadata.csv --latent 72 --epochs 20 --batch 32 --lr 0.001 --seed 123Az ellenőrzés a shape-ekre: a lineáris PCA bemenete
,
a teszt
;
a CNN bemenete
,
az encoder kimenete
,
a dekódolóé
.
A linear_test_mse a PCA-altérre vetítés hibája a testen; a
cnn_test_mse csak a fenti parancs tényleges futtatása után
állítható. A várható összehasonlítás: a CNN több nemlineáris
kapacitással alacsonyabb train hibát érhet el, de a
test-összehasonlításból a val-epocha-választás és a véges testméret
miatt óvatosan szabad következtetni.
A leggyakoribb tévedés, hogy „minden autoencoder PCA-t számol, és a rekonstrukció garantálja a hasznos downstream reprezentációt”. Ez két ponton is hamis. Egyrészt lineáris esetben is csak a rekonstrukciós altér egyértelmű; a latens koordináták az invertálható keveredés miatt nem feltétlenül a főkomponens-koordináták. Másrészt a varianciaközpontú PCA elhagyhat éppen olyan kis szórású jellemzőt, amely a downstream döntéshez elengedhetetlen. Példa: legyen , ahol nagy szórású háttérváltozó, pedig a címkét meghatározó jellemző. A lineáris autoencoder a főkomponens irányát, -t tartja meg; a rekonstrukciós hiba kicsi, de a latensből a címke nem állítható helyre. A rekonstrukciós cél tehát adattömörítő, nem automatikusan szemantikai jellemzőtanuló.
Ellenőrizzük magunkat: (a) mi változik a lineáris optimumban, ha a bias nem nulla? (b) miért nem egyértelműek a lineáris súlyok? (c) mikor nulla a minimális hiba? A válasz: (a) a centrálást a train átlaggal kell kezelni, mert a bias nélküli levezetés nulla átlagot feltételez; (b) a latens invertálható lineáris keverése az altérre vetítést változatlanul hagyja; (c) ha az adat már eleve egy -dimenziós altérben van. Visszahívás: a 17.1. LeNet és a lokális megosztott reprezentáció alfejezetben látott konvolúciós építőelemek itt az encoder–decoderben térnek vissza; a 28.1. Embedding, távolság és normalizálás alfejezetben tanultakat pedig úgy alkalmazzuk, hogy a reprezentáció minősége csak a megcélzott feladaton mérhető.
F1 (fogalmi, nehézség 1). Fogalmazza meg, miért nem állítható, hogy minden autoencoder PCA-t számol. Adjon két különböző okot, egy lineáris és egy nemlineáris példával. Átadandó: legfeljebb fél oldalas indoklás az altér és a koordináta megkülönböztetésével.
F2 (levezetési, nehézség 2). A fenti kézi példa mátrixára SVD-könyvtár nélkül, a kovarianciamátrixból vezesse le a lineáris autoencoder optimális irányát és minimális átlagos hibáját. Átadandó: a kovarianciamátrix, a sajátértékek, az optimális irány és a hiba.
F3 (implementációs, nehézség 1). Írjon
NumPy-függvényt pca_reconstruct(X_train, X_test, k)
aláírással, amely a train átlaggal centrál, SVD-vel kiszámítja a top
altérre vetítést, és visszaadja a test rekonstrukciót. Ellenőrizze a
kézi példán: a
hiba
legyen
tűrésen belül. Átadandó: futtatható kód és a hiba.
F4 (hibakeresési, nehézség 2). Egy hallgató a
CNN-autoencoder lossát
F.mse_loss(pred, target, reduction='sum') módon számolja,
majd összehasonlítja a lineáris baseline reduction='mean'
értékével. Mi a hiba, mi a következménye, és hogyan javítja?
Átadandó: javított kódrészlet és a skálakapcsolat.
F5 (kísérleti tervezés, nehézség 3). Tervezzen kísérletet annak eldöntésére, hogy a CNN-autoencoder latens reprezentációja hasznosabb-e a GlyphLab orientációs osztályozásához, mint az azonos dimenziójú PCA-latens. Átadandó: split, baseline, befagyasztási protokoll, metrika és érvényességi korlát.
F1. Az állítás azért hamis, mert (a) még a lineáris esetben sem maguk a főkomponens-koordináták egyértelműek: tetszőleges invertálható keverés optimális marad, miközben a latens koordináták megváltoznak; (b) a nemlineáris autoencoder nemlineáris sokaságra vetít, így az optimum általában nem írható le lineáris altérrel. A pontos állítás: lineáris, centrált, négyzetes hibájú, esetben az optimális rekonstrukciós altér a top főkomponens altere.
F2. Az mátrixot már kiszámoltuk, így . A sajátértékek ; az első sajátvektor . A optimum , . A rekonstrukciós hibák az utolsó két sorban és ; az átlag , ahogy a tétel állítja. Szélsőeset: ha , a hiba , mert az adat két dimenziós altérben van.
F3. A függvény:
def pca_reconstruct(X_train, X_test, k):
mu = X_train.mean(axis=0, keepdims=True)
U, S, Vt = np.linalg.svd(X_train - mu, full_matrices=False)
Vk = Vt[:k].T
X_hat = (X_test - mu) @ Vk @ Vk.T + mu
return X_hat
X = np.array([[2.,0.],[-2.,0.],[0.,1.],[0.,-1.]], dtype=np.float64)
Xh = pca_reconstruct(X, X, 1)
print(np.mean((X - Xh) ** 2)) # 0.5Az ellenőrző érték , a abszolút tolerancia teljesül. A kimeneti shape megegyezik a bemeneti shape-szel.
F4. A hiba a reduction meg nem egyezése: a
sum-hányados
-szerese
a mean-nek. Így a CNN hiba számszerűen nagyobbnak látszik, és a két
modell nem összehasonlítható. Javítás: mindkét ágon azonos
reduction='mean'-t használunk; például a CNN-hez is
F.mse_loss(pred, target, reduction='mean'), és a lineáris
baseline-ban np.mean((X-Xh)**2). Visszaellenőrzés: egy
32×24×24 batchre a sum/mean hányados
legyen.
F5. Rögzített train/val/test GlyphLab split. Baseline: PCA-latens (72 dimenzió) fölött logisztikus regresszió. Vizsgált modell: a labor CNN-autoencoder befagyasztott encoderének kimenete fölött ugyanolyan lineáris fej. A fejet csak a train latenseken tanítjuk, a val a korai leállításra szolgál; az autoencoder súlyait sem a val, sem a test nem finomhangolja. Metrika: osztályonkénti accuracy, a bizonytalanságot scene_id szerint rétegzett bootstrap adja. Korlát: a rekonstrukciós előtanítás a címkét nem látta, ezért a kísérlet azt ellenőrzi, hogy a rekonstrukció megőrizte-e az orientációhoz szükséges információt; az eredmény nem általánosítható természetes képekre.
Az autoencoder egy encodert, egy latens kódot és egy decodert kapcsol össze; a cél a bemenet rekonstrukciója. A szűk keresztmetszet tömörítésre kényszerít, de önmagában nem garantál hasznos reprezentációt. Centrált adatra, lineáris leképezésekkel és átlagos négyzetes hibával a globális optimum rekonstrukciós altere a top főkomponens altere; a hibát az elhagyott sajátértékek összege adja. A latens koordináták invertálható lineáris keveredésig egyértelműek. A nemlineáris CNN-autoencodernek nincs ilyen zárt megoldása. A labor a PCA-alapú lineáris baseline-t és egy kis CNN-autoencodert veti össze a GlyphLab rekonstrukciós hibáján.
A 29.2. Denoising és korrupciós modell egységben a denoising autoencodert vizsgáljuk: ott a bemenet zajos, a cél a zajmentes kép, így a rekonstrukciós cél kifejezetten a zajmodellre irányul.