GitHub Actions commited on
Commit
3c10869
·
1 Parent(s): d4c6bba

Sync from GitHub Actions

Browse files
config/model.yaml CHANGED
@@ -1,10 +1,8 @@
1
  model:
2
  name: "openai/clip-vit-large-patch14"
3
- learning_rate: 1e-5
4
- batch_size: 128
5
- epochs: 5
6
- grad_accum_steps: 1
7
- num_warmup_steps : 100
8
  new_model: "mohsin416/clip-vit-large-patch14-fashion-retrieval-lora"
9
 
10
  index:
 
1
  model:
2
  name: "openai/clip-vit-large-patch14"
3
+ learning_rate: 1e-4
4
+ batch_size: 32
5
+ epochs: 10
 
 
6
  new_model: "mohsin416/clip-vit-large-patch14-fashion-retrieval-lora"
7
 
8
  index:
config/schema.yaml CHANGED
@@ -3,8 +3,6 @@ model:
3
  learning_rate : float
4
  batch_size : int
5
  epochs : int
6
- num_warmup_steps : int
7
- grad_accum_steps : int
8
  lora_model: str
9
 
10
 
 
3
  learning_rate : float
4
  batch_size : int
5
  epochs : int
 
 
6
  lora_model: str
7
 
8
 
visual_product_search/data/dataset.py CHANGED
@@ -1,23 +1,41 @@
 
 
1
  import torch
2
- from torch.utils.data import Dataset, DataLoader
3
- from torch.nn.functional import pad
4
- from transformers import CLIPProcessor
5
  from PIL import Image
6
- from visual_product_search.logger import logging
7
  from visual_product_search.exception import ExceptionHandle
8
- import pandas as pd
9
- import sys, os
10
 
11
  class ProductDataset(Dataset):
12
- def __init__(self, df: pd.DataFrame, processor: CLIPProcessor, img_dir: str, preprocessed_dir: str = None):
13
- self.df = df
14
- self.processor = processor
15
- self.img_dir = img_dir
16
- self.preprocessed_dir = preprocessed_dir
 
17
 
18
- if preprocessed_dir:
19
- os.makedirs(preprocessed_dir, exist_ok=True)
20
- logging.info(f"Preprocessed images will be cached in {preprocessed_dir}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  def __len__(self):
23
  return len(self.df)
@@ -25,44 +43,50 @@ class ProductDataset(Dataset):
25
  def __getitem__(self, idx):
26
  try:
27
  row = self.df.iloc[idx]
28
- img_name = row['filename']
29
- img_path = os.path.join(self.img_dir, img_name)
 
 
 
 
 
30
 
31
- tensor_path = os.path.join(self.preprocessed_dir, img_name + ".pt") if self.preprocessed_dir else None
32
-
33
  if tensor_path and os.path.exists(tensor_path):
34
  pixel_values = torch.load(tensor_path)
35
-
36
  else:
37
  try:
38
  image = Image.open(img_path).resize((224, 224))
39
- pixel_values = self.processor(images=image, return_tensors="pt")['pixel_values'].squeeze(0)
 
 
40
  if tensor_path:
41
  torch.save(pixel_values, tensor_path)
42
-
43
  except FileNotFoundError:
44
  logging.warning(f"Image not found: {img_path}, using dummy tensor.")
45
  pixel_values = torch.zeros(3, 224, 224)
46
 
47
- filtered_row = row.drop(labels=["filename", "link"])
48
- text = " ".join(str(v) for v in filtered_row.values)
 
49
  text_inputs = self.processor.tokenizer(
50
- text,
51
  padding="max_length",
52
  truncation=True,
53
  max_length=77,
54
- return_tensors="pt"
55
  )
56
-
57
  input_ids = text_inputs["input_ids"].squeeze(0)
58
  attention_mask = text_inputs["attention_mask"].squeeze(0)
59
 
60
  return {
61
  "input_ids": input_ids,
62
  "attention_mask": attention_mask,
63
- "pixel_values": pixel_values
 
 
64
  }
65
 
66
  except Exception as e:
67
- logging.error(f"Error processing item {idx}")
68
  raise ExceptionHandle(e, sys)
 
1
+ import os
2
+ import sys
3
  import torch
4
+ import pandas as pd
5
+ from torch.utils.data import Dataset
 
6
  from PIL import Image
7
+ from transformers import CLIPProcessor
8
  from visual_product_search.exception import ExceptionHandle
9
+ from visual_product_search.logger import logging
10
+
11
 
12
  class ProductDataset(Dataset):
13
+ def __init__(self, df: pd.DataFrame, image_folder: str, processor: CLIPProcessor, preprocessed_dir: str = None):
14
+ try:
15
+ df["image_path"] = df["filename"].astype(str).apply(
16
+ lambda x: os.path.join(image_folder, x)
17
+ )
18
+ df = df[df["image_path"].apply(os.path.exists)].reset_index(drop=True)
19
 
20
+ df["caption"] = (
21
+ df["gender"].fillna("") + " "
22
+ + df["masterCategory"].fillna("") + " "
23
+ + df["subCategory"].fillna("") + " "
24
+ + df["baseColour"].fillna("") + " "
25
+ + df["articleType"].fillna("") + " "
26
+ + df["productDisplayName"].fillna("")
27
+ ).str.strip()
28
+
29
+ self.df = df.reset_index(drop=True)
30
+ self.processor = processor
31
+ self.preprocessed_dir = preprocessed_dir
32
+
33
+ if preprocessed_dir:
34
+ os.makedirs(preprocessed_dir, exist_ok=True)
35
+ logging.info(f"Preprocessed images will be cached in {preprocessed_dir}")
36
+
37
+ except Exception as e:
38
+ raise ExceptionHandle(e, sys)
39
 
40
  def __len__(self):
41
  return len(self.df)
 
43
  def __getitem__(self, idx):
44
  try:
45
  row = self.df.iloc[idx]
46
+ img_path = row["image_path"]
47
+
48
+ tensor_path = (
49
+ os.path.join(self.preprocessed_dir, row["filename"] + ".pt")
50
+ if self.preprocessed_dir
51
+ else None
52
+ )
53
 
 
 
54
  if tensor_path and os.path.exists(tensor_path):
55
  pixel_values = torch.load(tensor_path)
 
56
  else:
57
  try:
58
  image = Image.open(img_path).resize((224, 224))
59
+ pixel_values = self.processor(images=image, return_tensors="pt")[
60
+ "pixel_values"
61
+ ].squeeze(0)
62
  if tensor_path:
63
  torch.save(pixel_values, tensor_path)
 
64
  except FileNotFoundError:
65
  logging.warning(f"Image not found: {img_path}, using dummy tensor.")
66
  pixel_values = torch.zeros(3, 224, 224)
67
 
68
+ caption = row["caption"]
69
+ img_link = row.get("link", None)
70
+
71
  text_inputs = self.processor.tokenizer(
72
+ caption,
73
  padding="max_length",
74
  truncation=True,
75
  max_length=77,
76
+ return_tensors="pt",
77
  )
78
+
79
  input_ids = text_inputs["input_ids"].squeeze(0)
80
  attention_mask = text_inputs["attention_mask"].squeeze(0)
81
 
82
  return {
83
  "input_ids": input_ids,
84
  "attention_mask": attention_mask,
85
+ "pixel_values": pixel_values,
86
+ "caption": caption,
87
+ "img_link": img_link,
88
  }
89
 
90
  except Exception as e:
91
+ logging.error(f"Error processing dataset item {idx}")
92
  raise ExceptionHandle(e, sys)
visual_product_search/embeddings/train.py CHANGED
@@ -1,126 +1,80 @@
1
  import torch
2
- from torch.nn import CrossEntropyLoss
3
  from torch.amp import GradScaler, autocast
4
  from torch.optim import AdamW
5
- from transformers import get_cosine_schedule_with_warmup
6
  from visual_product_search.logger import logging
 
7
  from visual_product_search.exception import ExceptionHandle
8
  import sys
9
 
10
- def train(model, dataloader, device, epochs=5, lr=1e-5, grad_accum_steps=1, num_warmup_step=50):
11
- optimizer = AdamW(model.parameters(), lr=lr)
12
- scaler = GradScaler(device="cuda")
13
- total_steps = epochs * len(dataloader)
14
-
15
- scheduler = get_cosine_schedule_with_warmup(
16
- optimizer,
17
- num_warmup_steps=num_warmup_step,
18
- num_training_steps=total_steps
19
- )
20
-
21
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  for epoch in range(epochs):
23
  model.train()
24
- total_loss = 0
25
-
26
- for step, batch in enumerate(dataloader):
27
- try:
28
- input_ids = batch['input_ids'].to(device)
29
- attention_mask = batch['attention_mask'].to(device)
30
- pixel_values = batch['pixel_values'].to(device)
31
-
32
- optimizer.zero_grad()
33
-
34
- with autocast(device_type="cuda"):
35
- outputs = model(input_ids=input_ids,
36
- attention_mask=attention_mask,
37
- pixel_values=pixel_values)
38
-
39
- img_embd = outputs.image_embeds
40
- text_embd = outputs.text_embeds
41
-
42
- img_embd = img_embd / img_embd.norm(p=2, dim=-1, keepdim=True)
43
- text_embd = text_embd / text_embd.norm(p=2, dim=-1, keepdim=True)
44
-
45
- logits_per_image = img_embd @ text_embd.T
46
- labels = torch.arange(len(img_embd)).to(device)
47
 
48
- loss_i = CrossEntropyLoss()(logits_per_image, labels)
49
- loss_t = CrossEntropyLoss()(logits_per_image.T, labels)
50
-
51
- loss = (loss_i + loss_t) / 2
52
-
53
- loss = loss / grad_accum_steps
54
- scaler.scale(loss).backward()
55
-
56
- if (step + 1) % grad_accum_steps == 0:
57
- scaler.step(optimizer)
58
- scaler.update()
59
- optimizer.zero_grad()
60
- scheduler.step()
61
-
62
- total_loss += loss.item() * grad_accum_steps
63
-
64
- if step % 10 == 0:
65
- logging.info(f"Epoch {epoch+1}/{epochs}, Step {step}, Loss: {loss.item():.4f}")
66
-
67
- except Exception as e:
68
- logging.error(f"Failure at step {step} in epoch {epoch+1}")
69
- raise ExceptionHandle(e, sys)
70
-
71
-
72
- avg_loss = total_loss / len(dataloader)
73
- logging.info(f" Epoch {epoch + 1} / {epochs} finished | Avg Loss : {avg_loss:.4f}")
74
-
75
- model.eval()
76
- return model
77
-
78
- except Exception as e:
79
- logging.critical("Training loop crashed")
80
- raise ExceptionHandle(e, sys)
81
-
82
-
83
-
84
-
85
- # for epoch in range(EPOCHS):
86
- # model.train()
87
- # total_loss = 0.0
88
- # start_epoch = time.time()
89
- # print(f"\n======== Epoch {epoch+1}/{EPOCHS} ========")
90
-
91
- # for batch_idx, (imgs, captions) in enumerate(dataloader, start=1):
92
- # batch_start = time.time()
93
- # imgs = imgs.to(DEVICE)
94
- # inputs_txt = processor(text=list(captions), return_tensors="pt", padding=True, truncation=True).to(DEVICE)
95
 
96
- # optimizer.zero_grad()
97
- # with autocast(device_type="cuda", dtype=torch.float16):
98
- # inputs_img = processor(images=imgs, return_tensors="pt").to(DEVICE)
99
- # img_embeds = model.get_image_features(**inputs_img)
100
- # txt_embeds = model.get_text_features(**inputs_txt)
 
 
101
 
102
- # img_embeds = nn.functional.normalize(img_embeds, dim=-1)
103
- # txt_embeds = nn.functional.normalize(txt_embeds, dim=-1)
104
 
105
- # logits = img_embeds @ txt_embeds.T * 100
106
- # labels = torch.arange(len(logits), device=DEVICE)
107
- # loss_i2t = loss_fn(logits, labels)
108
- # loss_t2i = loss_fn(logits.T, labels)
109
- # loss = (loss_i2t + loss_t2i) / 2
110
 
111
- # scaler.scale(loss).backward()
112
- # scaler.step(optimizer)
113
- # scaler.update()
114
 
115
- # total_loss += loss.item()
116
 
117
- # if batch_idx % 50 == 0 or batch_idx == len(dataloader):
118
- # elapsed = time.time() - batch_start
119
- # print(f"[Epoch {epoch+1} Batch {batch_idx}/{len(dataloader)}] "
120
- # f"Batch Loss: {loss.item():.4f}, "
121
- # f"Elapsed: {elapsed:.2f}s, "
122
- # f"GPU Memory Used: {torch.cuda.memory_allocated()/1024**3:.2f} GB")
 
 
 
 
 
 
 
 
123
 
124
- # avg_loss = total_loss / len(dataloader)
125
- # epoch_time = time.time() - start_epoch
126
- # print(f"Epoch {epoch+1} Completed | Avg Loss: {avg_loss:.4f} | Time: {epoch_time/60:.2f} min")
 
1
  import torch
2
+ from torch import nn
3
  from torch.amp import GradScaler, autocast
4
  from torch.optim import AdamW
5
+ from peft import LoraConfig, get_peft_model
6
  from visual_product_search.logger import logging
7
+ import time
8
  from visual_product_search.exception import ExceptionHandle
9
  import sys
10
 
11
+
12
+ def train(model, dataloader, device, epochs=10, lr=1e-4):
 
 
 
 
 
 
 
 
 
13
  try:
14
+ lora_config = LoraConfig(
15
+ r=16,
16
+ lora_alpha=16,
17
+ target_modules=["q_proj", "v_proj"],
18
+ lora_dropout=0.05,
19
+ bias="none",
20
+ task_type="FEATURE_EXTRACTION"
21
+ )
22
+ model = get_peft_model(model, lora_config)
23
+ model.to(device)
24
+
25
+ optimizer = AdamW(model.parameters(), lr=lr)
26
+ loss_fn = nn.CrossEntropyLoss()
27
+ scaler = GradScaler()
28
+
29
  for epoch in range(epochs):
30
  model.train()
31
+ total_loss = 0.0
32
+ start_epoch = time.time()
33
+ logging.info(f"--------- Epoch {epoch+1}/{epochs} ---------")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ for batch_idx, batch in enumerate(dataloader, start=1):
36
+ imgs = batch["pixel_values"].to(device)
37
+ input_ids = batch["input_ids"].to(device)
38
+ attention_mask = batch["attention_mask"].to(device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
+ optimizer.zero_grad()
41
+ with autocast(device_type="cuda", dtype=torch.float16):
42
+ img_embeds = model.get_image_features(pixel_values=imgs)
43
+ txt_embeds = model.get_text_features(
44
+ input_ids=input_ids,
45
+ attention_mask=attention_mask
46
+ )
47
 
48
+ img_embeds = nn.functional.normalize(img_embeds, dim=-1)
49
+ txt_embeds = nn.functional.normalize(txt_embeds, dim=-1)
50
 
51
+ logits = img_embeds @ txt_embeds.T * 100
52
+ labels = torch.arange(len(logits), device=device)
53
+ loss_i2t = loss_fn(logits, labels)
54
+ loss_t2i = loss_fn(logits.T, labels)
55
+ loss = (loss_i2t + loss_t2i) / 2
56
 
57
+ scaler.scale(loss).backward()
58
+ scaler.step(optimizer)
59
+ scaler.update()
60
 
61
+ total_loss += loss.item()
62
 
63
+ if batch_idx % 50 == 0 or batch_idx == len(dataloader):
64
+ logging.info(
65
+ f"[Epoch {epoch+1} Batch {batch_idx}/{len(dataloader)}] "
66
+ f"Batch Loss: {loss.item():.4f}"
67
+ )
68
+
69
+ avg_loss = total_loss / len(dataloader)
70
+ epoch_time = time.time() - start_epoch
71
+ logging.info(
72
+ f"Epoch {epoch+1} Completed | Avg Loss: {avg_loss:.4f} | "
73
+ f"Time: {epoch_time/60:.2f} min"
74
+ )
75
+
76
+ return model
77
 
78
+ except Exception as e:
79
+ logging.critical("Training loop crashed")
80
+ raise ExceptionHandle(e, sys)
visual_product_search/indexing/indexer.py CHANGED
@@ -47,7 +47,7 @@ class DatabaseIndexer:
47
  index_params = {
48
  "index_type" : "HNSW",
49
  "metric_type" : "COSINE",
50
- "params" : {"M" : 8, "efConstruction" : 64}
51
  }
52
  self.collection.create_index(field_name="embedding", index_params=index_params)
53
  logging.info("Index Created successfully")
 
47
  index_params = {
48
  "index_type" : "HNSW",
49
  "metric_type" : "COSINE",
50
+ "params" : {"M" : 48, "efConstruction" : 200}
51
  }
52
  self.collection.create_index(field_name="embedding", index_params=index_params)
53
  logging.info("Index Created successfully")
visual_product_search/pipeline/training_pipeline.py CHANGED
@@ -1,6 +1,6 @@
1
  import torch, gc
2
  from torch.utils.data import DataLoader
3
- import numpy as np
4
  import os
5
  from pathlib import Path
6
  import sys
@@ -58,8 +58,6 @@ class VisualProductPipeline:
58
  device,
59
  epochs=self.config["model"]["epochs"],
60
  lr=self.config["model"]["learning_rate"],
61
- grad_accum_steps=self.config["model"]["grad_accum_steps"],
62
- num_warmup_step=self.config["model"]["num_warmup_steps"]
63
  )
64
  logging.info("Model training completed")
65
  return trained_model
@@ -67,26 +65,43 @@ class VisualProductPipeline:
67
  except Exception as e:
68
  raise ExceptionHandle(e, sys)
69
 
70
- def create_embeddings(self, df, img_dir, model, processor, device):
71
  try:
72
  logging.info("Creating embeddings")
73
  embeddings = []
74
- metadata = []
75
  img_link = []
 
76
 
77
- for row in df.itertuples(index=False):
78
- img_path = f"{img_dir}/{row.filename}"
79
- embd = get_image_embedding(img_path, model, processor, device)
80
- embeddings.append(embd)
81
- img_link.append(row.link)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
- filtered_row = row._asdict()
84
- filtered_row.pop("filename", None)
85
- filtered_row.pop("link", None)
86
- metadata.append(" ".join(str(v) for v in filtered_row.values()))
87
-
88
- embeddings = np.vstack(embeddings)
89
- logging.info(f"create embeddings for {len(df)} samples")
90
  return embeddings, metadata, img_link
91
 
92
  except Exception as e:
@@ -133,7 +148,7 @@ class VisualProductPipeline:
133
  df, img_dir = self.data_ingestion()
134
  model, processor, device = self.model_loading()
135
 
136
- dataset = ProductDataset(df, processor, img_dir, str(self.cache_dir))
137
 
138
  dataloader = DataLoader(
139
  dataset,
@@ -148,7 +163,7 @@ class VisualProductPipeline:
148
  trained_model = self.start_training(model, dataloader, device)
149
  self.push_hub(trained_model, processor)
150
 
151
- embeddings, metadata, img_link = self.create_embeddings(df, img_dir, trained_model, processor, device)
152
  self.start_indexing(embeddings, metadata, img_link)
153
 
154
  del model, processor, trained_model
 
1
  import torch, gc
2
  from torch.utils.data import DataLoader
3
+ import torch.nn.functional.normalize as F
4
  import os
5
  from pathlib import Path
6
  import sys
 
58
  device,
59
  epochs=self.config["model"]["epochs"],
60
  lr=self.config["model"]["learning_rate"],
 
 
61
  )
62
  logging.info("Model training completed")
63
  return trained_model
 
65
  except Exception as e:
66
  raise ExceptionHandle(e, sys)
67
 
68
+ def create_embeddings(self, dataloader, model, device):
69
  try:
70
  logging.info("Creating embeddings")
71
  embeddings = []
 
72
  img_link = []
73
+ metadata = []
74
 
75
+ with torch.no_grad():
76
+ for batch in dataloader:
77
+ try:
78
+ imgs = batch["pixel_values"].to(device)
79
+ caps = batch["caption"]
80
+ links = batch["img_link"]
81
+
82
+ if imgs is None or len(imgs) == 0:
83
+ print("Image not found, skipping")
84
+ continue
85
+
86
+ img_embeds = model.get_image_features(pixel_values=imgs)
87
+ img_embeds = F(img_embeds, dim=-1)
88
+
89
+ embeddings.append(img_embeds.cpu())
90
+ metadata.extend(caps)
91
+ img_link.extend(links)
92
+
93
+ except Exception as e:
94
+ print(f"Skipping batch due to error: {e}")
95
+ continue
96
+
97
+ if embeddings:
98
+ embeddings = torch.cat(embeddings, dim=0)
99
+ else:
100
+ embeddings = torch.empty(0)
101
 
102
+ logging.info(f"Embeddings shape: {embeddings.shape}")
103
+ logging.info(f"Metadata length: {len(metadata)}")
104
+ logging.info(f"Image links length: {len(img_link)}")
 
 
 
 
105
  return embeddings, metadata, img_link
106
 
107
  except Exception as e:
 
148
  df, img_dir = self.data_ingestion()
149
  model, processor, device = self.model_loading()
150
 
151
+ dataset = ProductDataset(df, img_dir, processor, str(self.cache_dir))
152
 
153
  dataloader = DataLoader(
154
  dataset,
 
163
  trained_model = self.start_training(model, dataloader, device)
164
  self.push_hub(trained_model, processor)
165
 
166
+ embeddings, metadata, img_link = self.create_embeddings(dataloader, trained_model, device)
167
  self.start_indexing(embeddings, metadata, img_link)
168
 
169
  del model, processor, trained_model