| from tqdm.auto import tqdm |
| import torch |
| import os |
| from warnings import deprecated |
| from src.models.muti_model import Multimodal |
|
|
| import pandas as pd |
|
|
| class MyModel: |
| def __init__(self, config): |
| self.config = config |
| self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
|
|
| self.model = Multimodal(config=config).to(self.device) |
| self.optimizer = torch.optim.Adam(params=self.model.parameters(), lr=self.config.learning_rate) |
|
|
| pos_weight_tensor = None |
| if config.train_file_path and os.path.exists(config.train_file_path): |
| train_df = pd.read_csv(config.train_file_path) |
| pos_weight_val = train_df[train_df['label'] == 0].shape[0] / (train_df[train_df['label'] == 1].shape[0] + 1e-5) |
| pos_weight_tensor = torch.tensor([pos_weight_val], dtype=torch.float32).to(self.device) |
| self.loss_fn = torch.nn.BCEWithLogitsLoss(pos_weight=pos_weight_tensor) |
|
|
| self.train_loss = [] |
| self.val_loss = [] |
| self.best_val_loss = float('inf') |
| self.patience_counter = 0 |
|
|
| def train(self, train_data_loader, val_data_loader): |
| self.load_model() |
| for epoch in range(self.config.epochs): |
| self.model.train() |
| train_loss = 0.0 |
|
|
| train_pbar = tqdm(train_data_loader, desc=f"Epoch {epoch+1}/{self.config.epochs} [Train]", leave=False) |
| for img_feats, text_feats, labels in train_pbar: |
| img_feats = img_feats.to(self.device) |
| text_feats = text_feats.to(self.device) |
| labels = labels.to(self.device).unsqueeze(1) |
|
|
| self.optimizer.zero_grad() |
| outputs = self.model(img_feats, text_feats) |
| loss = self.loss_fn(outputs, labels) |
|
|
| loss.backward() |
| self.optimizer.step() |
|
|
| train_loss += loss.item() |
| train_pbar.set_postfix({"batch_loss": f"{loss.item():.4f}"}) |
|
|
| avg_train_loss = train_loss / len(train_data_loader) |
| avg_val_loss = self.validate(val_data_loader) |
|
|
| self.train_loss.append(avg_train_loss) |
| self.val_loss.append(avg_val_loss) |
|
|
| print(f"Epoch [{epoch+1}/{self.config.epochs}] -> Train Loss: {avg_train_loss:.4f} | Val Loss: {avg_val_loss:.4f}") |
|
|
| if avg_val_loss < self.best_val_loss: |
| self.best_val_loss = avg_val_loss |
| self.patience_counter = 0 |
| self.save_model() |
| else: |
| self.patience_counter += 1 |
| if self.patience_counter >= self.config.patience: |
| print(f"Early stopping triggered at epoch {epoch+1}") |
| break |
|
|
| def validate(self, val_data_loader): |
| self.model.eval() |
| val_loss = 0.0 |
|
|
| val_pbar = tqdm(val_data_loader, desc="Validating", leave=False) |
| with torch.no_grad(): |
| for img_feats, text_feats, labels in val_pbar: |
| img_feats = img_feats.to(self.device) |
| text_feats = text_feats.to(self.device) |
| labels = labels.to(self.device).unsqueeze(1) |
|
|
| outputs = self.model(img_feats, text_feats) |
| loss = self.loss_fn(outputs, labels) |
|
|
| val_loss += loss.item() |
| val_pbar.set_postfix({"val_batch_loss": f"{loss.item():.4f}"}) |
|
|
| return val_loss / len(val_data_loader) |
|
|
| def predict(self, img_feats, text_feats): |
| self.model.eval() |
| with torch.no_grad(): |
| img_feats = img_feats.to(self.device) |
| text_feats = text_feats.to(self.device) |
|
|
| logits = self.model(img_feats, text_feats) |
| probs = torch.sigmoid(logits) |
| return probs |
|
|
| |
| @deprecated( |
| "predict_emb is deprecated. The new dual-index pipeline stores ImageEncoder " |
| "and TextEncoder embeddings separately in Pinecone; use those encoders directly " |
| "instead of calling predict_emb." |
| ) |
| def predict_emb(self, img_feats, text_feats): |
| self.model.eval() |
|
|
| with torch.no_grad(): |
| img_feats = img_feats.to(self.device) |
| text_feats = text_feats.to(self.device) |
|
|
| embedding = self.model( |
| img_feats, |
| text_feats, |
| return_embedding=True |
| ) |
|
|
| return embedding |
|
|
| def save_model(self): |
| os.makedirs(self.config.model_dir, exist_ok=True) |
| checkpoint_path = os.path.join(self.config.model_dir, self.config.model_name) |
| torch.save({ |
| 'model_state_dict': self.model.state_dict(), |
| 'optimizer_state_dict': self.optimizer.state_dict(), |
| 'best_val_loss': self.best_val_loss, |
| 'train_loss': self.train_loss, |
| 'val_loss': self.val_loss |
| }, checkpoint_path) |
|
|
| def load_model(self): |
| checkpoint_path = os.path.join(self.config.model_dir, self.config.model_name) |
| if os.path.exists(checkpoint_path): |
| checkpoint = torch.load(checkpoint_path, map_location=self.device) |
| self.model.load_state_dict(checkpoint['model_state_dict']) |
| self.optimizer.load_state_dict(checkpoint['optimizer_state_dict']) |
| self.best_val_loss = checkpoint.get('best_val_loss', float('inf')) |
| self.train_loss = checkpoint.get('train_loss', []) |
| self.val_loss = checkpoint.get('val_loss', []) |