Lecture 03PCA、均值基线与冻结评价 · 实现代码
PCA、均值基线与冻结评价 · 实现代码
按问题继续
索引恢复、去噪/遮挡ConvAE训练、PCA基线、独立冻结评价
展开完整实现代码
def run_reconstruction(out_dir='lab08-output'):
started=time.time();out=Path(out_dir);out.mkdir(parents=True,exist_ok=True)
x,y,train,test=digit_data()
np.savez_compressed(out/'inputs.npz',images=x.numpy(),labels=y,train_ids=train,test_ids=test)
denoise,random_d,hist_d,delta_d=train_ae(x,train,'denoise',40)
masked,random_m,hist_m,delta_m=train_ae(x,train,'masked',50)
torch.save(denoise.state_dict(),out/'denoise.pt');torch.save(masked.state_dict(),out/'masked-conv-ae.pt')
torch.save({'denoise':random_d.state_dict(),'masked':random_m.state_dict()},out/'random-init.pt')
denoise.eval();masked.eval();torch.manual_seed(SEED+19)
clean=x[test];corrupted=noise_corrupt(clean)
with torch.no_grad():recon=denoise(corrupted)[0]
mean_image=x[train].mean(0,keepdim=True)
# PCA fits clean training images only. It is a rank-16 linear projection baseline.
a=x[train].flatten(1).numpy();mu=a.mean(0);_,_,vt=np.linalg.svd(a-mu,full_matrices=False)
v=vt[:16];noisy=corrupted.flatten(1).numpy();pca=np.clip((noisy-mu)@v.T@v+mu,0,1)
metrics={'denoise':{'corrupted_input_mse':float(F.mse_loss(corrupted,clean)),
'train_mean_mse':float(((clean-mean_image)**2).mean()),
'rank16_pca_noisy_mse':float(np.mean((pca-clean.flatten(1).numpy())**2)),
'learned_conv_ae_mse':float(F.mse_loss(recon,clean))}}
masked_outputs={}
for ratio in [.25,.5,.75]:
torch.manual_seed(SEED+25) # Same permutation at all ratios: nested hidden sets.
mask=patch_mask(len(clean),ratio)
inp=torch.cat([clean*(1-mask),mask],1)
with torch.no_grad():pred=masked(inp)[0]
loss=(((pred-clean)**2)*mask).sum()/mask.sum()
mean_loss=(((mean_image-clean)**2)*mask).sum()/mask.sum()
zero_loss=((clean**2)*mask).sum()/mask.sum()
metrics[f'mask_{ratio}']={'hidden_pixels_mse':float(loss),'train_mean_hidden_mse':float(mean_loss),
'zero_fill_hidden_mse':float(zero_loss),'visible_patches':int(16*(1-ratio)),
'conv_encoder_grid_pixels':64}
masked_outputs[ratio]=(mask,pred)
probes={}
for name,model,is_masked in [('random-denoise',random_d,False),('trained-denoise',denoise,False),('random-masked',random_m,True),('trained-masked',masked,True)]:
probes[name]=frozen_probe(model,x,y,train,test,is_masked)
# Save complete inference evidence, including corruption and masks.
output_arrays={'clean':clean.numpy(),'corrupted':corrupted.numpy(),'denoised':recon.numpy(),'pca':pca.reshape(-1,1,8,8)}
for ratio,(mask,pred) in masked_outputs.items():output_arrays[f'mask_{ratio}']=mask.numpy();output_arrays[f'pred_{ratio}']=pred.numpy()
np.savez_compressed(out/'predictions.npz',**output_arrays)
# Examples selected BEFORE seeing predictions: first eight test IDs, never cherry-picked.
mask,pred=masked_outputs[.5]
fig,axes=plt.subplots(8,6,figsize=(9,12))
titles=['clean','corrupted','denoised','50% masked','predicted hidden','absolute error']
for i in range(8):
completed=clean[i]*(1-mask[i])+pred[i]*mask[i]
ims=[clean[i],corrupted[i],recon[i],clean[i]*(1-mask[i]),completed,abs(completed-clean[i])]
for j,im in enumerate(ims):
axes[i,j].imshow(im[0],cmap='magma' if j==5 else 'gray',vmin=0,vmax=1,interpolation='nearest')
axes[i,j].set_xticks([]);axes[i,j].set_yticks([])
if i==0:axes[i,j].set_title(titles[j],fontsize=9)
axes[i,0].set_ylabel(f'id {test[i]}\ny={y[test[i]]}',fontsize=8)
fig.tight_layout();fig.savefig(out/'reconstruction-grid.png',dpi=150);plt.close(fig)
fig,axes=plt.subplots(1,3,figsize=(14,4))
axes[0].plot(hist_d,label='denoise: all pixels');axes[0].plot(hist_m,label='masked: hidden only');axes[0].legend(fontsize=8)
axes[0].set(xlabel='epoch (0-based)',ylabel='train MSE',title='Objectives have different pixel sets')
ratios=[.25,.5,.75]
axes[1].plot(ratios,[metrics[f'mask_{r}']['hidden_pixels_mse'] for r in ratios],'o-',label='trained at 50%')
axes[1].plot(ratios,[metrics[f'mask_{r}']['train_mean_hidden_mse'] for r in ratios],'o--',label='train mean')
axes[1].set(xlabel='evaluation mask ratio',ylabel='held-out hidden MSE');axes[1].legend()
axes[2].bar(list(probes),list(probes.values()));axes[2].tick_params(axis='x',rotation=25)
axes[2].set(ylim=(0,1),ylabel='held-out accuracy',title='Frozen clean h: cosine 5-NN')
fig.tight_layout();fig.savefig(out/'loss-mask-probe.png',dpi=140);plt.close(fig)
config={'seed':SEED,'dataset':'sklearn load_digits, 1797 grayscale 8x8, pixels divided by16','train_n':len(train),'test_n':len(test),'split':'stratified random source-image split before corruption','denoise_epochs':40,'masked_epochs':50,'batch_size':64,'adam_lr':.003,'latent_dim':16,'denoise_corruption':{'pixel_drop':.25,'gaussian_sigma':.15,'clip':[0,1]},'mask_training_ratio':.5,'mask_patch_pixels':[2,2],'labels_used':'stratify/probe only; never AE loss','mask_encoder':'dense two-channel convolutional autoencoder; NOT ViT MAE','test_tuning':'none; predetermined epochs/mask ratios'}
(out/'config.json').write_text(json.dumps(config,indent=2))
payload={'code_sha256':globals().get('SOURCE_SHA256') or hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),'kind':'local CPU full training and held-out evaluation','utc':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),'seconds':time.time()-started,'torch':torch.__version__,'numpy':np.__version__,'python':platform.python_version(),'metrics':metrics,'frozen_clean_5nn':probes,'loss_history':{'denoise':hist_d,'masked':hist_m},'encoder_weight_l2_change':{'denoise':delta_d,'masked':delta_m},'index_warmup':mask_index_warmup(),'inputs_sha256':hashlib.sha256((out/'inputs.npz').read_bytes()).hexdigest()}
(out/'results.json').write_text(json.dumps(payload,indent=2))
print(json.dumps({'metrics':metrics,'frozen_clean_5nn':probes,'seconds':payload['seconds']},indent=2))
return payload