PRUTHVIn commited on
Commit
18ffedc
·
verified ·
1 Parent(s): 8472d3f
Files changed (1) hide show
  1. main.py +29 -88
main.py CHANGED
@@ -1,31 +1,27 @@
1
  import re
2
  from collections import Counter
 
3
 
4
  import torch
5
  import torch.nn as nn
6
- from torch.utils.data import Dataset, DataLoader, random_split
7
  from PIL import Image
8
  import torchvision.transforms as transforms
9
  import torchvision.models as models
10
- from datasets import load_dataset
11
- import pandas as pd
12
  from langdetect import detect
13
- import pickle
14
 
15
- # Global config
16
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
17
  MAX_LEN = 20
18
 
19
- # Image transform used for both training and inference
20
  transform = transforms.Compose([
21
  transforms.Resize((224, 224)),
22
  transforms.ToTensor()
23
  ])
24
 
25
 
26
- # =========================
27
- # Dataset & preprocessing
28
- # =========================
29
  class VQADataset(Dataset):
30
  def __init__(self, df, transform):
31
  self.df = df.reset_index(drop=True)
@@ -44,10 +40,12 @@ class VQADataset(Dataset):
44
 
45
 
46
  def prepare_data(max_answers=50, min_word_count=3, max_len=MAX_LEN):
47
- """Load VQA-RAD, clean, build vocab, return dataset + mappings."""
 
 
 
48
  dataset = load_dataset("flaviagiammarino/vqa-rad")
49
- df = pd.DataFrame(dataset["train"])
50
- df = df[["image", "question", "answer"]]
51
 
52
  def clean_text(text):
53
  text = str(text).lower()
@@ -95,17 +93,17 @@ def prepare_data(max_answers=50, min_word_count=3, max_len=MAX_LEN):
95
  }
96
 
97
 
98
- # =========================
99
- # Model
100
- # =========================
101
  class VQAModel(nn.Module):
102
  def __init__(self, vocab_size, embed_dim, hidden_dim, num_answers):
103
  super().__init__()
104
- # Use pretrained ResNet18
105
  self.cnn = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)
106
  self.cnn.fc = nn.Identity()
 
107
  self.embedding = nn.Embedding(vocab_size, embed_dim)
108
  self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)
 
109
  self.fc1 = nn.Linear(512 + hidden_dim, 256)
110
  self.relu = nn.ReLU()
111
  self.fc2 = nn.Linear(256, num_answers)
@@ -115,18 +113,15 @@ class VQAModel(nn.Module):
115
  q_embed = self.embedding(question)
116
  _, (h, _) = self.lstm(q_embed)
117
  q_feat = h.squeeze(0)
118
- combined = torch.cat((img_feat, q_feat), dim=1)
119
- x = self.relu(self.fc1(combined))
120
- out = self.fc2(x)
121
- return out
122
 
123
 
124
- # =========================
125
- # Training (local, not used on Space)
126
- # =========================
127
  def train_model(train_dataset, vocab, idx_to_answer,
128
  epochs=20, batch_size=32, lr=1e-3, save_prefix="vqa_custom"):
129
- """Train model locally and save artifacts."""
130
  vocab_size = len(vocab)
131
  num_answers = len(idx_to_answer)
132
 
@@ -161,53 +156,7 @@ def train_model(train_dataset, vocab, idx_to_answer,
161
  return model
162
 
163
 
164
- # =========================
165
- # BLIP + translator (optional, heavy)
166
- # =========================
167
- def load_blip_and_translator():
168
- from transformers import Blip2Processor, Blip2ForConditionalGeneration, AutoTokenizer, AutoModelForSeq2SeqLM
169
-
170
- processor = Blip2Processor.from_pretrained("Salesforce/blip2-flan-t5-xl")
171
- blip_model = Blip2ForConditionalGeneration.from_pretrained(
172
- "Salesforce/blip2-flan-t5-xl",
173
- device_map="auto",
174
- torch_dtype=torch.float16,
175
- )
176
-
177
- translator_tokenizer = AutoTokenizer.from_pretrained("facebook/nllb-200-distilled-600M")
178
- translator_model = AutoModelForSeq2SeqLM.from_pretrained("facebook/nllb-200-distilled-600M")
179
-
180
- lang_code_map = {
181
- "en": "eng_Latn",
182
- "hi": "hin_Deva",
183
- "te": "tel_Telu",
184
- "ta": "tam_Taml",
185
- "kn": "kan_Knda",
186
- "ml": "mal_Mlym",
187
- }
188
-
189
- def open_vqa(image_path, question):
190
- image = Image.open(image_path).convert("RGB")
191
- inputs = processor(image, question, return_tensors="pt").to(blip_model.device)
192
- out = blip_model.generate(**inputs, max_new_tokens=50)
193
- return processor.decode(out[0], skip_special_tokens=True)
194
-
195
- def translate(text, src, tgt):
196
- translator_tokenizer.src_lang = lang_code_map[src]
197
- inputs = translator_tokenizer(text, return_tensors="pt")
198
- tokens = translator_model.generate(
199
- **inputs,
200
- forced_bos_token_id=translator_tokenizer.convert_tokens_to_ids(lang_code_map[tgt]),
201
- max_length=50,
202
- )
203
- return translator_tokenizer.decode(tokens[0], skip_special_tokens=True)
204
-
205
- return processor, blip_model, translator_tokenizer, translator_model, open_vqa, translate
206
-
207
-
208
- # =========================
209
- # Load artifacts + inference
210
- # =========================
211
  def load_artifacts(prefix="vqa_custom", map_location=None):
212
  with open(f"{prefix}_vocab.pkl", "rb") as f:
213
  vocab = pickle.load(f)
@@ -220,8 +169,7 @@ def load_artifacts(prefix="vqa_custom", map_location=None):
220
  model.eval()
221
 
222
  def encode_question_infer(q, max_len=MAX_LEN):
223
- q = str(q).lower()
224
- tokens = q.split()
225
  enc = [vocab.get(w, vocab["<UNK>"]) for w in tokens]
226
  enc = enc[:max_len] + [vocab["<PAD>"]] * (max_len - len(enc))
227
  return torch.tensor(enc).unsqueeze(0)
@@ -236,12 +184,9 @@ def load_artifacts(prefix="vqa_custom", map_location=None):
236
  return idx_to_answer[pred.item()]
237
 
238
  def final_pipeline(image_path, question, open_vqa_fn=None, translate_fn=None):
 
239
  lang = detect(question)
240
-
241
- if lang != "en" and translate_fn is not None:
242
- q_en = translate_fn(question, lang, "en")
243
- else:
244
- q_en = question
245
 
246
  if open_vqa_fn is not None and ("what is" in q_en.lower() or "this place" in q_en.lower()):
247
  answer_en = open_vqa_fn(image_path, q_en)
@@ -250,8 +195,7 @@ def load_artifacts(prefix="vqa_custom", map_location=None):
250
 
251
  if lang != "en" and translate_fn is not None:
252
  return translate_fn(answer_en, "en", lang)
253
- else:
254
- return answer_en
255
 
256
  return final_pipeline, predict_custom_vqa, vocab, idx_to_answer, model, encode_question_infer
257
 
@@ -260,24 +204,21 @@ def load_artifacts_and_helpers(prefix="vqa_custom", map_location=None):
260
  return load_artifacts(prefix=prefix, map_location=map_location)
261
 
262
 
263
- # =========================
264
- # Optional CLI (local only)
265
- # =========================
266
  if __name__ == "__main__":
 
267
  import argparse
268
-
269
- parser = argparse.ArgumentParser(description="VQA pipeline (prepare/train/infer)")
270
  parser.add_argument("--prepare", action="store_true")
271
  parser.add_argument("--train", action="store_true")
272
  parser.add_argument("--epochs", type=int, default=20)
273
  parser.add_argument("--prefix", default="vqa_custom")
274
- parser.add_argument("--image", help="Path to image for inference")
275
  parser.add_argument("--question", default="What is in the image?")
276
  args = parser.parse_args()
277
 
278
  if args.prepare or args.train:
279
  artifacts = prepare_data()
280
- print("Prepared dataset with", len(artifacts["answer_to_idx"]), "answer classes.")
281
 
282
  if args.train:
283
  train_model(
@@ -289,5 +230,5 @@ if __name__ == "__main__":
289
  )
290
 
291
  if args.image:
292
- final_pipeline, _, _, _, _, _ = load_artifacts(prefix=args.prefix)
293
  print(final_pipeline(args.image, args.question, open_vqa_fn=None, translate_fn=None))
 
1
  import re
2
  from collections import Counter
3
+ import pickle
4
 
5
  import torch
6
  import torch.nn as nn
7
+ from torch.utils.data import Dataset, DataLoader
8
  from PIL import Image
9
  import torchvision.transforms as transforms
10
  import torchvision.models as models
 
 
11
  from langdetect import detect
 
12
 
13
+ # ========== Global config ==========
14
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
15
  MAX_LEN = 20
16
 
17
+ # Fixed image transform
18
  transform = transforms.Compose([
19
  transforms.Resize((224, 224)),
20
  transforms.ToTensor()
21
  ])
22
 
23
 
24
+ # ========== Dataset & preprocessing (for local training only) ==========
 
 
25
  class VQADataset(Dataset):
26
  def __init__(self, df, transform):
27
  self.df = df.reset_index(drop=True)
 
40
 
41
 
42
  def prepare_data(max_answers=50, min_word_count=3, max_len=MAX_LEN):
43
+ """For local training only – not used in Space."""
44
+ from datasets import load_dataset
45
+ import pandas as pd
46
+
47
  dataset = load_dataset("flaviagiammarino/vqa-rad")
48
+ df = pd.DataFrame(dataset["train"])[["image", "question", "answer"]]
 
49
 
50
  def clean_text(text):
51
  text = str(text).lower()
 
93
  }
94
 
95
 
96
+ # ========== Model ==========
 
 
97
  class VQAModel(nn.Module):
98
  def __init__(self, vocab_size, embed_dim, hidden_dim, num_answers):
99
  super().__init__()
100
+ # Use lightweight ResNet18 backbone
101
  self.cnn = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)
102
  self.cnn.fc = nn.Identity()
103
+
104
  self.embedding = nn.Embedding(vocab_size, embed_dim)
105
  self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)
106
+
107
  self.fc1 = nn.Linear(512 + hidden_dim, 256)
108
  self.relu = nn.ReLU()
109
  self.fc2 = nn.Linear(256, num_answers)
 
113
  q_embed = self.embedding(question)
114
  _, (h, _) = self.lstm(q_embed)
115
  q_feat = h.squeeze(0)
116
+ x = torch.cat((img_feat, q_feat), dim=1)
117
+ x = self.relu(self.fc1(x))
118
+ return self.fc2(x)
 
119
 
120
 
121
+ # ========== Training (local only) ==========
 
 
122
  def train_model(train_dataset, vocab, idx_to_answer,
123
  epochs=20, batch_size=32, lr=1e-3, save_prefix="vqa_custom"):
124
+ """Run this only on Colab / local, not in Space."""
125
  vocab_size = len(vocab)
126
  num_answers = len(idx_to_answer)
127
 
 
156
  return model
157
 
158
 
159
+ # ========== Load artifacts + inference ==========
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  def load_artifacts(prefix="vqa_custom", map_location=None):
161
  with open(f"{prefix}_vocab.pkl", "rb") as f:
162
  vocab = pickle.load(f)
 
169
  model.eval()
170
 
171
  def encode_question_infer(q, max_len=MAX_LEN):
172
+ tokens = str(q).lower().split()
 
173
  enc = [vocab.get(w, vocab["<UNK>"]) for w in tokens]
174
  enc = enc[:max_len] + [vocab["<PAD>"]] * (max_len - len(enc))
175
  return torch.tensor(enc).unsqueeze(0)
 
184
  return idx_to_answer[pred.item()]
185
 
186
  def final_pipeline(image_path, question, open_vqa_fn=None, translate_fn=None):
187
+ # Keep it simple in Space: English only, no BLIP / translator unless passed in
188
  lang = detect(question)
189
+ q_en = question if lang == "en" or translate_fn is None else translate_fn(question, lang, "en")
 
 
 
 
190
 
191
  if open_vqa_fn is not None and ("what is" in q_en.lower() or "this place" in q_en.lower()):
192
  answer_en = open_vqa_fn(image_path, q_en)
 
195
 
196
  if lang != "en" and translate_fn is not None:
197
  return translate_fn(answer_en, "en", lang)
198
+ return answer_en
 
199
 
200
  return final_pipeline, predict_custom_vqa, vocab, idx_to_answer, model, encode_question_infer
201
 
 
204
  return load_artifacts(prefix=prefix, map_location=map_location)
205
 
206
 
 
 
 
207
  if __name__ == "__main__":
208
+ # Local CLI only; never runs in Space
209
  import argparse
210
+ parser = argparse.ArgumentParser()
 
211
  parser.add_argument("--prepare", action="store_true")
212
  parser.add_argument("--train", action="store_true")
213
  parser.add_argument("--epochs", type=int, default=20)
214
  parser.add_argument("--prefix", default="vqa_custom")
215
+ parser.add_argument("--image")
216
  parser.add_argument("--question", default="What is in the image?")
217
  args = parser.parse_args()
218
 
219
  if args.prepare or args.train:
220
  artifacts = prepare_data()
221
+ print("Prepared dataset with", len(artifacts["answer_to_idx"]), "answers")
222
 
223
  if args.train:
224
  train_model(
 
230
  )
231
 
232
  if args.image:
233
+ final_pipeline, *_ = load_artifacts(prefix=args.prefix)
234
  print(final_pipeline(args.image, args.question, open_vqa_fn=None, translate_fn=None))