FemboyCodeDev commited on
Commit
e55f958
Β·
verified Β·
1 Parent(s): aa9c425

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +261 -0
app.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from transformers import (
4
+ GPT2LMHeadModel,
5
+ AutoTokenizer,
6
+ RobertaForSequenceClassification,
7
+ GPT2Config,
8
+ RobertaConfig,
9
+ DataCollatorForLanguageModeling
10
+ )
11
+ from datasets import load_dataset
12
+ from torch.utils.data import DataLoader
13
+ from accelerate import Accelerator
14
+ import logging
15
+
16
+ # --- Configuration ---
17
+ # Set up logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Constants
22
+ GPT2_MODEL_NAME = "gpt2"
23
+ ROBERTA_MODEL_NAME = "roberta-base"
24
+ SEQUENCE_LENGTH = 128
25
+ BATCH_SIZE = 8
26
+ NUM_EPOCHS = 3
27
+ LEARNING_RATE_G = 1e-5 # Lower LR for generation models
28
+ LEARNING_RATE_D = 5e-5 # Higher LR for classification models
29
+ D_STEPS = 1 # Number of discriminator updates per generator update
30
+ G_STEPS = 1 # Number of generator updates per batch
31
+
32
+ # --- 1. Discriminator Wrapper Class ---
33
+ # We wrap RoBERTa to make it function as a binary classifier (0: Fake, 1: Real)
34
+ class Discriminator(nn.Module):
35
+ def __init__(self, model_name):
36
+ super().__init__()
37
+ # RoBERTa is loaded for sequence classification with 2 labels (real/fake)
38
+ self.roberta = RobertaForSequenceClassification.from_pretrained(model_name, num_labels=2)
39
+
40
+ def forward(self, input_ids, attention_mask=None, labels=None):
41
+ # The RoBERTa model outputs a SequenceClassifierOutput
42
+ output = self.roberta(
43
+ input_ids=input_ids,
44
+ attention_mask=attention_mask,
45
+ labels=labels
46
+ )
47
+ # We only need the logits for the GAN loss calculation
48
+ return output.logits
49
+
50
+ # --- 2. Model and Tokenizer Initialization ---
51
+
52
+ # Initialize the tokenizers and add padding token for GPT-2
53
+ tokenizer = AutoTokenizer.from_pretrained(GPT2_MODEL_NAME)
54
+ # GPT-2 does not have a native padding token, so we set the EOS token as the pad token
55
+ # This is crucial for batching and RoBERTa's input structure
56
+ if tokenizer.pad_token is None:
57
+ tokenizer.pad_token = tokenizer.eos_token
58
+
59
+ # Initialize Models
60
+ logger.info(f"Loading Generator ({GPT2_MODEL_NAME}) and Discriminator ({ROBERTA_MODEL_NAME})...")
61
+ generator = GPT2LMHeadModel.from_pretrained(GPT2_MODEL_NAME)
62
+ # Ensure the GPT-2 model's classification head (if used) is configured for the discriminator's vocabulary.
63
+ # In a pure GAN, the generator just generates text, so we only need the LMHead.
64
+
65
+ discriminator = Discriminator(ROBERTA_MODEL_NAME)
66
+
67
+ # --- 3. Data Preprocessing ---
68
+ def preprocess_function(examples):
69
+ # Tokenize the dataset
70
+ return tokenizer(examples["text"], max_length=SEQUENCE_LENGTH, truncation=True, padding="max_length")
71
+
72
+ def load_and_prepare_data():
73
+ # Load a dataset of real text (e.g., IMDB reviews)
74
+ raw_datasets = load_dataset("imdb", split="train[:5%]")
75
+
76
+ # Select only the 'text' column for language modeling
77
+ processed_datasets = raw_datasets.map(
78
+ preprocess_function,
79
+ batched=True,
80
+ remove_columns=raw_datasets.column_names,
81
+ )
82
+
83
+ # Convert to PyTorch tensors and prepare for DataLoader
84
+ processed_datasets.set_format(type="torch", columns=["input_ids", "attention_mask"])
85
+
86
+ # Simple data collator for padding
87
+ data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
88
+
89
+ # Create DataLoader
90
+ dataloader = DataLoader(
91
+ processed_datasets,
92
+ shuffle=True,
93
+ collate_fn=data_collator, # The data collator will handle batching and masking
94
+ batch_size=BATCH_SIZE
95
+ )
96
+ return dataloader
97
+
98
+ # --- 4. Adversarial Training Function ---
99
+
100
+ def train_gan():
101
+ # Initialize Accelerator for mixed-precision and distributed training handling
102
+ accelerator = Accelerator()
103
+
104
+ dataloader = load_and_prepare_data()
105
+
106
+ # Define optimizers
107
+ optimizer_g = torch.optim.AdamW(generator.parameters(), lr=LEARNING_RATE_G)
108
+ optimizer_d = torch.optim.AdamW(discriminator.parameters(), lr=LEARNING_RATE_D)
109
+
110
+ # Move models and optimizers to the appropriate device
111
+ generator, optimizer_g, discriminator, optimizer_d, dataloader = accelerator.prepare(
112
+ generator, optimizer_g, discriminator, optimizer_d, dataloader
113
+ )
114
+
115
+ # Define Loss Function: Binary Cross-Entropy with Logits
116
+ # Since RoBERTa is outputting logits (unscaled scores), BCEWithLogitsLoss is the correct, stable choice.
117
+ loss_fn = nn.BCEWithLogitsLoss()
118
+
119
+ logger.info("Starting adversarial training loop...")
120
+
121
+ # Set models to training mode
122
+ generator.train()
123
+ discriminator.train()
124
+
125
+ for epoch in range(NUM_EPOCHS):
126
+ for step, batch in enumerate(dataloader):
127
+
128
+ # --- DISCRIMINATOR TRAINING STEP (D_STEPS times) ---
129
+ for _ in range(D_STEPS):
130
+ optimizer_d.zero_grad()
131
+
132
+ # 1. Process REAL Data
133
+ real_input_ids = batch['input_ids']
134
+ real_attention_mask = batch['attention_mask']
135
+ # Target: 1 (Real)
136
+ real_labels = torch.ones(real_input_ids.size(0), 1).to(accelerator.device)
137
+
138
+ # Get discriminator prediction for real data
139
+ # We classify the full sequence (CLS token's output is used by RoBERTa's classification head)
140
+ real_logits = discriminator(real_input_ids, attention_mask=real_attention_mask)
141
+ real_loss = loss_fn(real_logits[:, 1].unsqueeze(-1), real_labels) # Use logit for label 1 (Real)
142
+
143
+ # 2. Process FAKE (Generated) Data
144
+ # Generate text using GPT-2. We use 'no_grad' since we don't want to calculate
145
+ # gradients for the generator during the D step.
146
+ with torch.no_grad():
147
+ # Generate text. 'max_length' ensures the generated text is the same size as real data.
148
+ generated_ids = generator.generate(
149
+ real_input_ids,
150
+ max_length=SEQUENCE_LENGTH,
151
+ do_sample=True,
152
+ top_k=50,
153
+ top_p=0.95,
154
+ pad_token_id=tokenizer.eos_token_id
155
+ )
156
+
157
+ # Get generated text and attention mask
158
+ fake_input_ids = generated_ids
159
+ # RoBERTa's tokenizer automatically handles attention masking based on the pad token ID
160
+ fake_attention_mask = (fake_input_ids != tokenizer.pad_token_id).int()
161
+
162
+ # Target: 0 (Fake)
163
+ fake_labels = torch.zeros(fake_input_ids.size(0), 1).to(accelerator.device)
164
+
165
+ # Get discriminator prediction for fake data
166
+ fake_logits = discriminator(fake_input_ids, attention_mask=fake_attention_mask)
167
+ # Use logit for label 1 (Real) but target is 0 (Fake).
168
+ fake_loss = loss_fn(fake_logits[:, 1].unsqueeze(-1), fake_labels)
169
+
170
+ # 3. Total Discriminator Loss and Update
171
+ d_loss = real_loss + fake_loss
172
+
173
+ # Backpropagate and update
174
+ accelerator.backward(d_loss)
175
+ optimizer_d.step()
176
+
177
+ # --- GENERATOR TRAINING STEP (G_STEPS times) ---
178
+ # Generator aims to make D classify its output as REAL (target 1)
179
+ for _ in range(G_STEPS):
180
+ optimizer_g.zero_grad()
181
+
182
+ # Generate new fake data for the G step
183
+ # We need gradients for this step, so no 'no_grad()'
184
+ generated_ids = generator.generate(
185
+ real_input_ids,
186
+ max_length=SEQUENCE_LENGTH,
187
+ do_sample=True,
188
+ top_k=50,
189
+ top_p=0.95,
190
+ pad_token_id=tokenizer.eos_token_id
191
+ )
192
+
193
+ fake_input_ids = generated_ids
194
+ fake_attention_mask = (fake_input_ids != tokenizer.pad_token_id).int()
195
+
196
+ # Target for Generator: 1 (it wants the discriminator to think the text is Real)
197
+ generator_target_labels = torch.ones(fake_input_ids.size(0), 1).to(accelerator.device)
198
+
199
+ # Get discriminator prediction for the generated text
200
+ # We detach the discriminator's forward pass to prevent gradient updates to D during G step
201
+ discriminator_logits = discriminator(fake_input_ids.detach(), attention_mask=fake_attention_mask.detach())
202
+
203
+ # Generator Loss: BCE loss where the target is 1 (Real)
204
+ # The generator is being updated to minimize this loss, meaning its output
205
+ # should drive the discriminator's output closer to 1.
206
+ g_loss = loss_fn(discriminator_logits[:, 1].unsqueeze(-1), generator_target_labels)
207
+
208
+ # Backpropagate and update
209
+ accelerator.backward(g_loss)
210
+ optimizer_g.step()
211
+
212
+ # --- Logging and Reporting ---
213
+ if (step + 1) % 50 == 0:
214
+ # Calculate Discriminator Accuracy for monitoring
215
+ # Predictions are based on which logit is higher (0 or 1)
216
+ d_real_preds = (real_logits[:, 1] > real_logits[:, 0]).float().mean()
217
+ d_fake_preds = (fake_logits[:, 1] < fake_logits[:, 0]).float().mean()
218
+ d_accuracy = (d_real_preds + d_fake_preds) / 2
219
+
220
+ # G's success (how often D thinks the fake is real)
221
+ g_success_rate = (discriminator_logits[:, 1] > discriminator_logits[:, 0]).float().mean()
222
+
223
+ logger.info(
224
+ f"Epoch {epoch+1}/{NUM_EPOCHS}, Step {step+1}/{len(dataloader)} | "
225
+ f"D Loss: {d_loss.item():.4f}, G Loss: {g_loss.item():.4f} | "
226
+ f"D Acc: {d_accuracy.item():.2f} | G Success: {g_success_rate.item():.2f}"
227
+ )
228
+
229
+ # --- End of Epoch ---
230
+ logger.info(f"--- Epoch {epoch+1} finished. Generating sample text. ---")
231
+
232
+ # Simple evaluation by generating text
233
+ generator.eval()
234
+ prompt = "Finetuning large language models in an adversarial setting is"
235
+ input_ids = tokenizer.encode(prompt, return_tensors="pt").to(accelerator.device)
236
+
237
+ sample_output = generator.generate(
238
+ input_ids,
239
+ max_length=50,
240
+ num_return_sequences=1,
241
+ do_sample=True,
242
+ top_k=50,
243
+ top_p=0.95,
244
+ pad_token_id=tokenizer.eos_token_id
245
+ )
246
+ decoded_output = tokenizer.decode(sample_output[0], skip_special_tokens=True)
247
+ logger.info(f"Sample Output: {decoded_output}")
248
+ generator.train()
249
+
250
+ # Save the fine-tuned Generator model
251
+ accelerator.wait_for_everyone()
252
+ unwrapped_generator = accelerator.unwrap_model(generator)
253
+ unwrapped_generator.save_pretrained("./finetuned_gpt2_gan_generator")
254
+ logger.info("Fine-tuning complete. Generator saved to ./finetuned_gpt2_gan_generator")
255
+
256
+
257
+ if __name__ == "__main__":
258
+ # Note: To run this script, you typically need to use the 'accelerate launch' command:
259
+ # accelerate launch your_script_name.py
260
+ # Since this is a self-contained script in this environment, we call the function directly.
261
+ train_gan()