RGarrido03 commited on
Commit
e7ce0fc
·
verified ·
1 Parent(s): 012ff7f

Upload cgan_architecture.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. cgan_architecture.py +186 -0
cgan_architecture.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Conditional GAN for MNIST digit generation.
2
+
3
+ Architecture:
4
+ Generator: Embedding(y) → concat with z → ConvTranspose2d upsampling
5
+ Discriminator: Conv2d downsampling + Projection Discriminator (Miyato & Koyama, ICLR 2018)
6
+
7
+ The Projection Discriminator incorporates the class label through an inner
8
+ product between the discriminator's penultimate features and a class
9
+ embedding, rather than via naive concatenation. This is theoretically
10
+ well-grounded and empirically superior.
11
+
12
+ D(x, y) = fc(h(x)) + ⟨embed(y), h(x)⟩
13
+
14
+ where h(x) is the feature extractor output and fc gives the unconditional score.
15
+
16
+ Reference: "cGANs with Projection Discriminator" (Miyato & Koyama, 2018)
17
+ https://arxiv.org/abs/1802.05637
18
+ """
19
+
20
+ import torch
21
+ import torch.nn as nn
22
+ from torch.nn.utils import spectral_norm
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Generator
26
+ # ---------------------------------------------------------------------------
27
+
28
+
29
+ class Generator(nn.Module):
30
+ """Conditional generator: (z, y) → grayscale MNIST image.
31
+
32
+ Label embedding is concatenated to the latent vector before the first
33
+ linear projection, following the cGAN conditioning approach.
34
+
35
+ Args:
36
+ latent_dim: Dimensionality of the noise vector z.
37
+ embed_dim: Dimensionality of the class embedding.
38
+ base_channels: Channel width of the shallowest decoder stage.
39
+ num_classes: Number of digit classes (10 for MNIST).
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ latent_dim: int = 100,
45
+ embed_dim: int = 100,
46
+ base_channels: int = 256,
47
+ num_classes: int = 10,
48
+ ) -> None:
49
+ super().__init__()
50
+ self.latent_dim = latent_dim
51
+ self.embed_dim = embed_dim
52
+ self.num_classes = num_classes
53
+
54
+ self.embed = nn.Embedding(num_classes, embed_dim)
55
+
56
+ in_dim = latent_dim + embed_dim
57
+ # Project to 7×7 feature map
58
+ self.project = nn.Sequential(
59
+ nn.Linear(in_dim, base_channels * 7 * 7, bias=False),
60
+ nn.BatchNorm1d(base_channels * 7 * 7),
61
+ nn.ReLU(inplace=True),
62
+ )
63
+ self.base_channels = base_channels
64
+
65
+ # 7×7 → 14×14 → 28×28
66
+ self.main = nn.Sequential(
67
+ nn.ConvTranspose2d(
68
+ base_channels, base_channels // 2, 4, stride=2, padding=1, bias=False
69
+ ),
70
+ nn.BatchNorm2d(base_channels // 2),
71
+ nn.ReLU(inplace=True),
72
+ nn.ConvTranspose2d(
73
+ base_channels // 2, base_channels // 4, 4, stride=2, padding=1, bias=False
74
+ ),
75
+ nn.BatchNorm2d(base_channels // 4),
76
+ nn.ReLU(inplace=True),
77
+ nn.Conv2d(base_channels // 4, 1, 3, stride=1, padding=1),
78
+ nn.Tanh(),
79
+ )
80
+
81
+ self._init_weights()
82
+
83
+ def _init_weights(self) -> None:
84
+ for m in self.modules():
85
+ if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
86
+ nn.init.normal_(m.weight, 0.0, 0.02)
87
+ elif isinstance(m, nn.BatchNorm2d):
88
+ nn.init.normal_(m.weight, 1.0, 0.02)
89
+ nn.init.zeros_(m.bias)
90
+ elif isinstance(m, nn.Embedding):
91
+ nn.init.normal_(m.weight, 0.0, 0.02)
92
+
93
+ def forward(self, z: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
94
+ """
95
+ Args:
96
+ z: Noise tensor, shape (B, latent_dim).
97
+ y: Class labels, shape (B,), values in [0, num_classes).
98
+
99
+ Returns:
100
+ Generated images, shape (B, 1, 28, 28), range [-1, 1].
101
+ """
102
+ e = self.embed(y) # B × embed_dim
103
+ inp = torch.cat([z, e], dim=1) # B × (latent_dim + embed_dim)
104
+ h = self.project(inp).view(-1, self.base_channels, 7, 7)
105
+ return self.main(h)
106
+
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # Discriminator (Projection)
110
+ # ---------------------------------------------------------------------------
111
+
112
+
113
+ class Discriminator(nn.Module):
114
+ """Projection discriminator for conditional image scoring.
115
+
116
+ D(x, y) = fc(h(x)) + ⟨embed(y), h(x)⟩
117
+
118
+ Spectral Normalization is applied to all linear and conv layers for
119
+ Lipschitz regularisation. No BatchNorm (incompatible with SN).
120
+
121
+ Args:
122
+ base_channels: Channel width of the first downsampling stage.
123
+ num_classes: Number of digit classes (10 for MNIST).
124
+ feature_dim: Dimensionality of the penultimate feature vector.
125
+ """
126
+
127
+ def __init__(
128
+ self,
129
+ base_channels: int = 64,
130
+ num_classes: int = 10,
131
+ feature_dim: int = 512,
132
+ ) -> None:
133
+ super().__init__()
134
+ self.num_classes = num_classes
135
+
136
+ # 1×28×28 → 14×14 → 7×7 → 4×4 (after final conv)
137
+ self.features = nn.Sequential(
138
+ spectral_norm(nn.Conv2d(1, base_channels, 4, stride=2, padding=1, bias=False)),
139
+ nn.LeakyReLU(0.2, inplace=True),
140
+ spectral_norm(
141
+ nn.Conv2d(base_channels, base_channels * 2, 4, stride=2, padding=1, bias=False)
142
+ ),
143
+ nn.LeakyReLU(0.2, inplace=True),
144
+ spectral_norm(
145
+ nn.Conv2d(base_channels * 2, base_channels * 4, 3, stride=1, padding=1, bias=False)
146
+ ),
147
+ nn.LeakyReLU(0.2, inplace=True),
148
+ nn.AdaptiveAvgPool2d(1),
149
+ nn.Flatten(),
150
+ )
151
+ feat_ch = base_channels * 4
152
+
153
+ self.fc_feat = spectral_norm(nn.Linear(feat_ch, feature_dim, bias=False))
154
+
155
+ # Unconditional score
156
+ self.fc_out = spectral_norm(nn.Linear(feature_dim, 1, bias=True))
157
+
158
+ # Class embedding for projection
159
+ self.embed = spectral_norm(nn.Embedding(num_classes, feature_dim))
160
+
161
+ self._init_weights()
162
+
163
+ def _init_weights(self) -> None:
164
+ for m in self.modules():
165
+ if isinstance(m, nn.Conv2d):
166
+ nn.init.normal_(m.weight, 0.0, 0.02)
167
+ elif isinstance(m, nn.Linear):
168
+ nn.init.normal_(m.weight, 0.0, 0.02)
169
+ elif isinstance(m, nn.Embedding):
170
+ nn.init.normal_(m.weight, 0.0, 0.02)
171
+
172
+ def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
173
+ """
174
+ Args:
175
+ x: Image tensor, shape (B, 1, 28, 28), range [-1, 1].
176
+ y: Class labels, shape (B,), values in [0, num_classes).
177
+
178
+ Returns:
179
+ Score tensor, shape (B, 1).
180
+ """
181
+ h = self.fc_feat(self.features(x)) # B × feature_dim
182
+
183
+ unconditional = self.fc_out(h) # B × 1
184
+ projection = (h * self.embed(y)).sum(dim=1, keepdim=True) # B × 1
185
+
186
+ return unconditional + projection