"""Contrastive Divergence: train an energy net so data sits in low-energy regions.CD pushes E down on real data and up on short-run MCMC "negatives" from the model."""importosimporttorchfromtorchimportnnfromtorchebm.coreimportBaseModelfromtorchebm.datasetsimportTwoMoonsDatasetfromtorchebm.lossesimportContrastiveDivergencefromtorchebm.samplersimportLangevinDynamicsSMOKE=os.getenv("TORCHEBM_SMOKE")=="1"N_STEPS=20ifSMOKEelse1000classMLPEnergy(BaseModel):# E(x): R^2 -> R, one scalar energy per pointdef__init__(self):super().__init__()self.net=nn.Sequential(nn.Linear(2,128),nn.SiLU(),nn.Linear(128,128),nn.SiLU(),nn.Linear(128,1),)defforward(self,x):returnself.net(x).squeeze(-1)data=TwoMoonsDataset(n_samples=3000,noise=0.05,seed=0).get_data()energy=MLPEnergy()sampler=LangevinDynamics(model=energy,step_size=0.1,noise_scale=1.0)# draws negativescd=ContrastiveDivergence(model=energy,sampler=sampler,k_steps=10)# CD-10opt=torch.optim.Adam(energy.parameters(),lr=1e-3)forstepinrange(N_STEPS):batch=data[torch.randint(len(data),(256,))]loss,_negatives=cd(batch)# gradient of loss shapes the energy landscapeopt.zero_grad()loss.backward()opt.step()ifstep%200==0:print(f"step {step:4d} loss {loss.item():+.3f}")