[LAB 08 · 图像重建:遮挡损失与表示评价](/course/notebooks/b5ba27f2c5ba4ec0922208a7d5dc80a4.html) # PCA、均值基线与冻结评价 · 实现代码 [独立阅读](/course/notebook-blocks/41ffe8f7d5284c27aec5c2474b234a1f.html) 按问题继续 索引恢复、去噪/遮挡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 ```