Ubuntu commited on
Commit
aa8e01f
·
1 Parent(s): 41802f6

new optimised code

Browse files
Files changed (5) hide show
  1. README.md +23 -1
  2. configs/config.yaml +2 -1
  3. run.sh +60 -21
  4. src/training_pipeline.py +119 -95
  5. test_model.py +47 -23
README.md CHANGED
@@ -1 +1,23 @@
1
- run ./run.sh
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run ./run.sh
2
+
3
+
4
+
5
+ 1 # Full training (default from config.yaml)
6
+ 2 ./run.sh
7
+ 3
8
+ 4 # Fast test cycle (90% faster)
9
+ 5 ./run.sh --epochs 1 --batch_size 64 --num_samples 1000
10
+ 6
11
+ 7 # Custom configurations
12
+ 8 ./run.sh --epochs 3 --batch_size 32
13
+ 9 ./run.sh --num_samples 5000
14
+ 10 ./run.sh --epochs 1 # Just test with 1 epoch
15
+
16
+ Speed Comparison
17
+
18
+
19
+ ┌──────────────────────────────────────────────────────┬──────────────────┐
20
+ │ Command │ Time Estimate │
21
+ ├──────────────────────────────────────────────────────┼──────────────────┤
22
+ │ ./run.sh │ ~10 hours (full) │
23
+ │ ./run.sh --epochs 1 --batch_size 64 --num_samples 1000 │ ~5-10 minutes ⚡ │
configs/config.yaml CHANGED
@@ -22,6 +22,7 @@ dataset:
22
  # Target: popularity score (0-100)
23
  target_column: "popularity"
24
  max_length: 128
 
25
 
26
  training:
27
  epochs: 5
@@ -34,7 +35,7 @@ hardware:
34
  mixed_precision: "fp16"
35
 
36
  output:
37
- dir: "./outputs"
38
  save_strategy: "epoch"
39
  logging_steps: 10
40
 
 
22
  # Target: popularity score (0-100)
23
  target_column: "popularity"
24
  max_length: 128
25
+ # num_samples: 1000 # Uncomment to use subset (for faster testing)
26
 
27
  training:
28
  epochs: 5
 
35
  mixed_precision: "fp16"
36
 
37
  output:
38
+ dir: "./model"
39
  save_strategy: "epoch"
40
  logging_steps: 10
41
 
run.sh CHANGED
@@ -1,23 +1,51 @@
1
  #!/bin/bash
2
- # Usage: ./run.sh
 
3
 
4
  set -e
5
 
6
  GREEN='\033[0;32m'
7
- YELLOW='\033[1;33m'
8
  BLUE='\033[0;34m'
 
9
  NC='\033[0m'
10
 
11
  cd "$(dirname "${BASH_SOURCE[0]}")"
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  # Install uv if not exists
14
  if ! command -v uv &> /dev/null; then
15
- echo -e "${YELLOW}Installing uv...${NC}"
16
  curl -LsSf https://astral.sh/uv/install.sh | sh
17
  fi
18
 
19
- # Sync dependencies (creates .venv if needed)
20
- echo -e "${GREEN}✓ Syncing dependencies with uv...${NC}"
21
  uv sync
22
 
23
  # Activate venv
@@ -28,32 +56,43 @@ if [ ! -f ".env" ]; then
28
  cp .env.example .env
29
  fi
30
 
31
- # Check GPU
32
- echo ""
33
- echo -e "${YELLOW}Checking GPU availability...${NC}"
34
  python3 -c "
35
  import torch
36
  if torch.cuda.is_available():
37
- print(f'✓ CUDA available: {torch.cuda.device_count()} GPU(s)')
38
- for i in range(torch.cuda.device_count()):
39
- print(f' GPU {i}: {torch.cuda.get_device_name(i)}')
40
  else:
41
- print('⚠ No CUDA available - training will use CPU (slower)')
42
  "
43
 
44
- echo -e "${BLUE}========================================${NC}"
45
- echo -e "${BLUE} Training GPT-2 on Spotify Dataset${NC}"
46
- echo -e "${BLUE}========================================${NC}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
- python3 src/training_pipeline.py config
49
 
50
  echo ""
51
  echo -e "${GREEN}✓ Training Complete!${NC}"
52
- echo -e "Model saved to: ${YELLOW}outputs/final_model${NC}"
53
  echo ""
54
 
55
- echo -e "${BLUE}========================================${NC}"
56
- echo -e "${BLUE} Testing Model${NC}"
57
- echo -e "${BLUE}========================================${NC}"
58
 
59
- python3 test_model.py outputs/final_model
 
1
  #!/bin/bash
2
+ # Usage: ./run.sh [--epochs N] [--batch_size N] [--num_samples N]
3
+ # Example: ./run.sh --epochs 1 --batch_size 64 --num_samples 1000
4
 
5
  set -e
6
 
7
  GREEN='\033[0;32m'
 
8
  BLUE='\033[0;34m'
9
+ YELLOW='\033[1;33m'
10
  NC='\033[0m'
11
 
12
  cd "$(dirname "${BASH_SOURCE[0]}")"
13
 
14
+ # Default values
15
+ EPOCHS=""
16
+ BATCH_SIZE=""
17
+ NUM_SAMPLES=""
18
+
19
+ # Parse arguments
20
+ while [[ $# -gt 0 ]]; do
21
+ case $1 in
22
+ --epochs)
23
+ EPOCHS="--epochs $2"
24
+ shift 2
25
+ ;;
26
+ --batch_size)
27
+ BATCH_SIZE="--batch_size $2"
28
+ shift 2
29
+ ;;
30
+ --num_samples)
31
+ NUM_SAMPLES="--num_samples $2"
32
+ shift 2
33
+ ;;
34
+ *)
35
+ echo -e "${YELLOW}Unknown option: $1${NC}"
36
+ exit 1
37
+ ;;
38
+ esac
39
+ done
40
+
41
  # Install uv if not exists
42
  if ! command -v uv &> /dev/null; then
43
+ echo -e "${GREEN}Installing uv...${NC}"
44
  curl -LsSf https://astral.sh/uv/install.sh | sh
45
  fi
46
 
47
+ # Sync dependencies
48
+ echo -e "${GREEN}✓ Syncing dependencies...${NC}"
49
  uv sync
50
 
51
  # Activate venv
 
56
  cp .env.example .env
57
  fi
58
 
59
+ # Quick GPU check
 
 
60
  python3 -c "
61
  import torch
62
  if torch.cuda.is_available():
63
+ print(f'✓ GPU: {torch.cuda.get_device_name(0)}')
 
 
64
  else:
65
+ print('⚠ CPU mode (slower)')
66
  "
67
 
68
+ # Build training command
69
+ TRAIN_CMD="python3 src/training_pipeline.py config"
70
+ if [ -n "$EPOCHS" ]; then
71
+ TRAIN_CMD="$TRAIN_CMD $EPOCHS"
72
+ fi
73
+ if [ -n "$BATCH_SIZE" ]; then
74
+ TRAIN_CMD="$TRAIN_CMD $BATCH_SIZE"
75
+ fi
76
+ if [ -n "$NUM_SAMPLES" ]; then
77
+ TRAIN_CMD="$TRAIN_CMD $NUM_SAMPLES"
78
+ fi
79
+
80
+ echo -e "${BLUE}╔════════════════════════════════════════╗${NC}"
81
+ echo -e "${BLUE}║ Training GPT-2 on Spotify Dataset ║${NC}"
82
+ echo -e "${BLUE}╚════════════════════════════════════════╝${NC}"
83
+ echo ""
84
+ echo -e "${YELLOW}Command: $TRAIN_CMD${NC}"
85
+ echo ""
86
 
87
+ eval $TRAIN_CMD
88
 
89
  echo ""
90
  echo -e "${GREEN}✓ Training Complete!${NC}"
91
+ echo -e " Model: ${BLUE}./model${NC}"
92
  echo ""
93
 
94
+ echo -e "${BLUE}╔════════════════════════════════════════╗${NC}"
95
+ echo -e "${BLUE} Testing Model${NC}"
96
+ echo -e "${BLUE}╚════════════════════════════════════════╝${NC}"
97
 
98
+ python3 test_model.py ./model
src/training_pipeline.py CHANGED
@@ -25,28 +25,43 @@ import evaluate
25
  import numpy as np
26
  from tqdm import tqdm
27
 
 
 
 
 
 
 
28
  load_dotenv()
29
 
30
- # Setup logging
31
  logging.basicConfig(
32
- level=logging.INFO,
33
- format='%(asctime)s - %(levelname)s - %(message)s'
 
34
  )
35
  logger = logging.getLogger(__name__)
36
 
37
 
38
  class PerformanceCallback(TrainerCallback):
39
- """Track metrics per epoch"""
40
  def __init__(self):
41
  self.epoch_metrics = []
42
 
43
  def on_epoch_end(self, args, state, control, metrics=None, **kwargs):
44
  if metrics:
45
  self.epoch_metrics.append({'epoch': state.epoch, 'metrics': metrics.copy()})
46
- logger.info(f"\n=== Epoch {state.epoch:.2f} Complete ===")
47
- for k, v in metrics.items():
48
- if isinstance(v, (int, float)):
49
- logger.info(f" {k}: {v:.4f}")
 
 
 
 
 
 
 
 
50
  return control
51
 
52
 
@@ -57,41 +72,29 @@ def load_config(config_name: str = 'config'):
57
 
58
 
59
  def print_model_load_report(model, pretrained_name):
60
- """
61
- Print a report showing model loading status
62
- Similar to Hugging Face's loading report
63
- """
64
- logger.info(f"\n{model.__class__.__name__} LOAD REPORT from: {pretrained_name}")
65
- logger.info("Key | Status | Details")
66
- logger.info("------------------------+------------+--------")
67
- logger.info("classifier.bias | INITIALIZED| Regression head (new)")
68
- logger.info("classifier.weight | INITIALIZED| Regression head (new)")
69
- logger.info("pre_classifier.bias | INITIALIZED| Classification head (new)")
70
- logger.info("pre_classifier.weight | INITIALIZED| Classification head (new)")
71
- logger.info("\nNotes:")
72
- logger.info("- INITIALIZED: New layers for regression task (trained on downstream task)")
73
- logger.info("- Base DistilBERT weights loaded successfully ✓")
74
 
75
 
76
  def compute_metrics(eval_pred, metric_names=['mse', 'mae', 'r2']):
77
  """Compute regression metrics"""
78
  predictions, labels = eval_pred
79
-
80
- # Handle tuple output from model
81
  if isinstance(predictions, tuple):
82
  predictions = predictions[0]
83
-
84
  predictions = predictions.squeeze(-1)
85
  labels = labels.squeeze(-1)
86
-
87
  results = {}
88
  for name in metric_names:
89
- try:
90
- metric = evaluate.load(name)
91
- results[name] = metric.compute(predictions=predictions, references=labels)
92
- except Exception as e:
93
- logger.warning(f"Could not load metric {name}: {e}")
94
-
95
  return results
96
 
97
 
@@ -179,24 +182,32 @@ def generate_recommendations(prediction: float, features: Dict[str, float]) -> L
179
  return recommendations
180
 
181
 
182
- def train(config_name: str = 'config'):
183
  """Main training function for regression"""
184
- logger.info("=" * 60)
185
- logger.info("🎵 ViralTrack Predictor - Popularity Prediction")
186
- logger.info("=" * 60)
187
 
188
  # Load config
189
  cfg = load_config(config_name)
190
- logger.info(f"\nUsing config: {config_name}")
 
 
 
 
 
 
 
 
191
 
192
  # Setup HF auth
193
  hf_token = os.getenv("HF_TOKEN")
194
  if hf_token:
195
- logger.info("✓ Hugging Face token loaded")
196
 
197
  # Load dataset
198
  ds_cfg = cfg['dataset']
199
- logger.info(f"\n📊 Loading dataset: {ds_cfg['name']}")
200
 
201
  load_kwargs = {'path': ds_cfg['name']}
202
  if ds_cfg.get('config'):
@@ -213,23 +224,23 @@ def train(config_name: str = 'config'):
213
  'test': dataset['test']
214
  })
215
 
216
- logger.info(f" Train: {len(dataset['train'])} samples")
 
 
 
 
 
 
 
 
 
 
 
217
  if 'validation' in dataset:
218
- logger.info(f" Validation: {len(dataset['validation'])} samples")
219
  if 'test' in dataset:
220
- logger.info(f" Test: {len(dataset['test'])} samples")
221
-
222
- # Log first 20 rows of training data
223
- logger.info("\n📋 First 20 rows of training data:")
224
- logger.info("=" * 80)
225
- train_sample = dataset['train'].select(range(min(20, len(dataset['train']))))
226
- for i in range(len(train_sample)):
227
- row = train_sample[i]
228
- logger.info(f"\n[Row {i}]")
229
- for key, value in row.items():
230
- val_str = str(value)[:200] + "..." if len(str(value)) > 200 else str(value)
231
- logger.info(f" {key}: {val_str}")
232
- logger.info("=" * 80)
233
 
234
  # Load tokenizer and model
235
  model_cfg = cfg['model']
@@ -237,38 +248,33 @@ def train(config_name: str = 'config'):
237
  target_col = ds_cfg.get('target_column', 'label')
238
  max_length = ds_cfg.get('max_length', 512)
239
 
240
- logger.info(f"\n🤖 Loading model: {model_cfg['name']}")
241
- logger.info(f" Target: {target_col} (regression)")
242
- logger.info(f" Features: {feature_columns}")
243
-
 
244
  tokenizer = AutoTokenizer.from_pretrained(model_cfg['name'])
245
 
246
- # Fix: Set pad_token for models without one (like GPT-2)
247
  if tokenizer.pad_token is None:
248
  tokenizer.pad_token = tokenizer.eos_token
249
 
250
- # Regression: num_labels=1
251
- logger.info(f"\nLoading weights...")
252
-
253
- # Show progress bar for model loading
254
  with tqdm(total=100, desc="Loading weights", bar_format='{desc}: |{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]') as pbar:
255
  model = AutoModelForSequenceClassification.from_pretrained(
256
  model_cfg['name'],
257
- num_labels=1, # Regression output
258
  problem_type="regression",
259
  trust_remote_code=model_cfg.get('trust_remote_code', False),
 
260
  )
261
  pbar.update(100)
262
 
263
- # Print model loading report
264
  print_model_load_report(model, model_cfg['name'])
265
 
266
- logger.info(f" Parameters: {sum(p.numel() for p in model.parameters()):,}")
267
-
268
  # Tokenize - combine text features and normalize audio features
269
- logger.info(f"\n🔧 Preprocessing...")
270
-
271
- # Normalize numerical features for model input
272
  def normalize_features(ex):
273
  # Combine text features
274
  text_parts = []
@@ -302,7 +308,7 @@ def train(config_name: str = 'config'):
302
  )
303
 
304
  dataset = DatasetDict(tokenized)
305
- logger.info("✓ Preprocessing complete")
306
 
307
  # Training args
308
  train_cfg = cfg['training']
@@ -312,15 +318,19 @@ def train(config_name: str = 'config'):
312
  output_dir = Path(out_cfg.get('dir', './outputs'))
313
  output_dir.mkdir(parents=True, exist_ok=True)
314
 
315
- logger.info(f"\n🚀 Training...")
316
- logger.info(f" Epochs: {train_cfg['epochs']}")
317
- logger.info(f" Batch size: {train_cfg['batch_size']}")
318
- logger.info(f" Learning rate: {train_cfg['learning_rate']}")
 
 
 
 
319
 
320
  # Split data for validation
321
  has_validation = 'validation' in dataset
322
  if not has_validation:
323
- logger.info(" Creating validation split...")
324
  train_val = dataset['train'].train_test_split(test_size=0.1)
325
  dataset = DatasetDict({
326
  'train': train_val['train'],
@@ -328,8 +338,8 @@ def train(config_name: str = 'config'):
328
  })
329
  has_validation = True
330
 
331
- logger.info(f" Train: {len(dataset['train'])} samples")
332
- logger.info(f" Validation: {len(dataset['validation'])} samples")
333
 
334
  training_args = TrainingArguments(
335
  output_dir=str(output_dir),
@@ -347,9 +357,11 @@ def train(config_name: str = 'config'):
347
  metric_for_best_model='loss',
348
  greater_is_better=False,
349
  report_to='none',
 
350
  )
351
 
352
  # Train
 
353
  trainer = Trainer(
354
  model=model,
355
  args=training_args,
@@ -364,45 +376,57 @@ def train(config_name: str = 'config'):
364
  trainer.train()
365
 
366
  # Evaluate
367
- logger.info(f"\n📈 Evaluating...")
 
 
368
  if 'test' in dataset:
369
  eval_dataset = dataset['test']
370
  else:
371
  eval_dataset = dataset['validation']
372
  metrics = trainer.evaluate(eval_dataset)
373
 
374
- logger.info(f"\n=== Final Metrics ===")
375
  for k, v in metrics.items():
376
  if isinstance(v, (int, float)):
377
- # Scale MSE/MAE back to 0-100 range
378
  if k in ['eval_mse', 'eval_mae']:
379
- logger.info(f" {k}: {v * 100:.4f} (on 0-100 scale)")
380
  elif k == 'eval_r2':
381
- logger.info(f" {k}: {v:.4f}")
382
  else:
383
- logger.info(f" {k}: {v:.4f}")
 
384
 
385
  # Save
386
- model_path = output_dir / 'final_model'
387
  model.save_pretrained(str(model_path))
388
  tokenizer.save_pretrained(str(model_path))
389
- logger.info(f"\n💾 Model saved to: {model_path}")
390
 
391
  # Feature importance analysis
392
  feature_importance = get_feature_importance(model, tokenizer, feature_columns)
393
- logger.info("\n=== Feature Analysis ===")
 
 
394
  for feat, info in feature_importance.items():
395
- logger.info(f" {feat}: {info['description']}")
 
396
 
397
- logger.info("\n" + "=" * 60)
398
- logger.info("🎵 Training Complete!")
399
- logger.info(" Model can predict track popularity and provide recommendations")
400
- logger.info("=" * 60)
401
 
402
  return {'metrics': metrics, 'model_path': str(model_path)}
403
 
404
 
405
  if __name__ == '__main__':
406
- import sys
407
- config = sys.argv[1] if len(sys.argv) > 1 else 'config'
408
- train(config)
 
 
 
 
 
 
 
 
25
  import numpy as np
26
  from tqdm import tqdm
27
 
28
+ # Suppress HTTP logs from transformers/datasets
29
+ logging.getLogger("filelock").setLevel(logging.ERROR)
30
+ logging.getLogger("urllib3").setLevel(logging.ERROR)
31
+ logging.getLogger("huggingface_hub").setLevel(logging.ERROR)
32
+ logging.getLogger("datasets").setLevel(logging.ERROR)
33
+
34
  load_dotenv()
35
 
36
+ # Setup logging - cleaner format
37
  logging.basicConfig(
38
+ level=logging.ERROR,
39
+ format='%(message)s',
40
+ handlers=[logging.StreamHandler()]
41
  )
42
  logger = logging.getLogger(__name__)
43
 
44
 
45
  class PerformanceCallback(TrainerCallback):
46
+ """Track metrics per epoch with clean output"""
47
  def __init__(self):
48
  self.epoch_metrics = []
49
 
50
  def on_epoch_end(self, args, state, control, metrics=None, **kwargs):
51
  if metrics:
52
  self.epoch_metrics.append({'epoch': state.epoch, 'metrics': metrics.copy()})
53
+ # Clean epoch summary
54
+ print(f"\n{'='*50}")
55
+ print(f"✅ Epoch {state.epoch:.0f}/{args.num_train_epochs:.0f} Complete")
56
+ print(f"{'='*50}")
57
+ key_metrics = ['loss', 'mae', 'r2']
58
+ for k in key_metrics:
59
+ full_key = f'eval_{k}' if k != 'loss' else k
60
+ if full_key in metrics:
61
+ val = metrics[full_key]
62
+ if isinstance(val, (int, float)):
63
+ print(f" {k.upper():<15} {val:.4f}")
64
+ print(f"{'='*50}\n")
65
  return control
66
 
67
 
 
72
 
73
 
74
  def print_model_load_report(model, pretrained_name):
75
+ """Clean model loading status"""
76
+ print(f"\n📦 Model: {model.__class__.__name__}")
77
+ print(f" Source: {pretrained_name}")
78
+ print(f" Params: {sum(p.numel() for p in model.parameters()):,}")
79
+ print(f" Base weights loaded")
80
+ print(f" New regression head added\n")
 
 
 
 
 
 
 
 
81
 
82
 
83
  def compute_metrics(eval_pred, metric_names=['mse', 'mae', 'r2']):
84
  """Compute regression metrics"""
85
  predictions, labels = eval_pred
86
+
 
87
  if isinstance(predictions, tuple):
88
  predictions = predictions[0]
89
+
90
  predictions = predictions.squeeze(-1)
91
  labels = labels.squeeze(-1)
92
+
93
  results = {}
94
  for name in metric_names:
95
+ metric = evaluate.load(name)
96
+ results[name] = metric.compute(predictions=predictions, references=labels)
97
+
 
 
 
98
  return results
99
 
100
 
 
182
  return recommendations
183
 
184
 
185
+ def train(config_name: str = 'config', epochs: int = None, batch_size: int = None, num_samples: int = None):
186
  """Main training function for regression"""
187
+ print(f"\n{'🎵'*30}")
188
+ print(" VIRALTRACK PREDICTOR - Spotify Popularity Prediction")
189
+ print(f"{'🎵'*30}\n")
190
 
191
  # Load config
192
  cfg = load_config(config_name)
193
+ print(f"📋 Config: {config_name}\n")
194
+
195
+ # Override config with CLI args if provided
196
+ if epochs is not None:
197
+ cfg['training']['epochs'] = epochs
198
+ if batch_size is not None:
199
+ cfg['training']['batch_size'] = batch_size
200
+ if num_samples is not None:
201
+ cfg['dataset']['num_samples'] = num_samples
202
 
203
  # Setup HF auth
204
  hf_token = os.getenv("HF_TOKEN")
205
  if hf_token:
206
+ print("✓ Hugging Face token loaded\n")
207
 
208
  # Load dataset
209
  ds_cfg = cfg['dataset']
210
+ print(f"📊 Dataset: {ds_cfg['name']}")
211
 
212
  load_kwargs = {'path': ds_cfg['name']}
213
  if ds_cfg.get('config'):
 
224
  'test': dataset['test']
225
  })
226
 
227
+ # Subsample if requested
228
+ num_samples = ds_cfg.get('num_samples')
229
+ if num_samples is not None:
230
+ print(f"⚡ Using subset: {num_samples} samples (for faster testing)")
231
+ if len(dataset['train']) > num_samples:
232
+ dataset['train'] = dataset['train'].select(range(num_samples))
233
+ if 'validation' in dataset and len(dataset['validation']) > num_samples // 10:
234
+ dataset['validation'] = dataset['validation'].select(range(min(num_samples // 10, len(dataset['validation']))))
235
+ if 'test' in dataset and len(dataset['test']) > num_samples // 10:
236
+ dataset['test'] = dataset['test'].select(range(min(num_samples // 10, len(dataset['test']))))
237
+
238
+ print(f" ├─ Train: {len(dataset['train']):,} samples")
239
  if 'validation' in dataset:
240
+ print(f" ├─ Validation: {len(dataset['validation']):,} samples")
241
  if 'test' in dataset:
242
+ print(f" └─ Test: {len(dataset['test']):,} samples")
243
+ print()
 
 
 
 
 
 
 
 
 
 
 
244
 
245
  # Load tokenizer and model
246
  model_cfg = cfg['model']
 
248
  target_col = ds_cfg.get('target_column', 'label')
249
  max_length = ds_cfg.get('max_length', 512)
250
 
251
+ print(f"🤖 Model: {model_cfg['name']}")
252
+ print(f" Target: {target_col} (regression)")
253
+ print(f" Features: {len(feature_columns)} columns\n")
254
+
255
+ print("⏳ Loading tokenizer...")
256
  tokenizer = AutoTokenizer.from_pretrained(model_cfg['name'])
257
 
 
258
  if tokenizer.pad_token is None:
259
  tokenizer.pad_token = tokenizer.eos_token
260
 
261
+ print("⏳ Loading model weights...\n")
262
+
 
 
263
  with tqdm(total=100, desc="Loading weights", bar_format='{desc}: |{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]') as pbar:
264
  model = AutoModelForSequenceClassification.from_pretrained(
265
  model_cfg['name'],
266
+ num_labels=1,
267
  problem_type="regression",
268
  trust_remote_code=model_cfg.get('trust_remote_code', False),
269
+ ignore_mismatched_sizes=True,
270
  )
271
  pbar.update(100)
272
 
 
273
  print_model_load_report(model, model_cfg['name'])
274
 
 
 
275
  # Tokenize - combine text features and normalize audio features
276
+ print("🔧 Preprocessing data...")
277
+
 
278
  def normalize_features(ex):
279
  # Combine text features
280
  text_parts = []
 
308
  )
309
 
310
  dataset = DatasetDict(tokenized)
311
+ print("✓ Preprocessing complete\n")
312
 
313
  # Training args
314
  train_cfg = cfg['training']
 
318
  output_dir = Path(out_cfg.get('dir', './outputs'))
319
  output_dir.mkdir(parents=True, exist_ok=True)
320
 
321
+ print(f"{'='*50}")
322
+ print("🚀 TRAINING CONFIGURATION")
323
+ print(f"{'='*50}")
324
+ print(f" Epochs: {train_cfg['epochs']}")
325
+ print(f" Batch size: {train_cfg['batch_size']}")
326
+ print(f" Learning rate: {train_cfg['learning_rate']}")
327
+ print(f" Output dir: {output_dir}")
328
+ print(f"{'='*50}\n")
329
 
330
  # Split data for validation
331
  has_validation = 'validation' in dataset
332
  if not has_validation:
333
+ print(" Creating validation split...")
334
  train_val = dataset['train'].train_test_split(test_size=0.1)
335
  dataset = DatasetDict({
336
  'train': train_val['train'],
 
338
  })
339
  has_validation = True
340
 
341
+ print(f"📈 Training: {len(dataset['train']):,} samples")
342
+ print(f" Validating: {len(dataset['validation']):,} samples\n")
343
 
344
  training_args = TrainingArguments(
345
  output_dir=str(output_dir),
 
357
  metric_for_best_model='loss',
358
  greater_is_better=False,
359
  report_to='none',
360
+ disable_tqdm=False,
361
  )
362
 
363
  # Train
364
+ print("⏳ Starting training...\n")
365
  trainer = Trainer(
366
  model=model,
367
  args=training_args,
 
376
  trainer.train()
377
 
378
  # Evaluate
379
+ print(f"\n{'='*50}")
380
+ print("📈 EVALUATION")
381
+ print(f"{'='*50}")
382
  if 'test' in dataset:
383
  eval_dataset = dataset['test']
384
  else:
385
  eval_dataset = dataset['validation']
386
  metrics = trainer.evaluate(eval_dataset)
387
 
388
+ print(f"\n=== Final Metrics ===")
389
  for k, v in metrics.items():
390
  if isinstance(v, (int, float)):
 
391
  if k in ['eval_mse', 'eval_mae']:
392
+ print(f" {k:<15} {v * 100:.4f} (on 0-100 scale)")
393
  elif k == 'eval_r2':
394
+ print(f" {k:<15} {v:.4f}")
395
  else:
396
+ print(f" {k:<15} {v:.4f}")
397
+ print(f"{'='*50}\n")
398
 
399
  # Save
400
+ model_path = output_dir # Save directly to output_dir (e.g., ./model)
401
  model.save_pretrained(str(model_path))
402
  tokenizer.save_pretrained(str(model_path))
403
+ print(f"💾 Model saved to: {model_path}\n")
404
 
405
  # Feature importance analysis
406
  feature_importance = get_feature_importance(model, tokenizer, feature_columns)
407
+ print(f"{'='*50}")
408
+ print("📊 FEATURE ANALYSIS")
409
+ print(f"{'='*50}")
410
  for feat, info in feature_importance.items():
411
+ print(f" {feat}: {info['description']}")
412
+ print(f"{'='*50}\n")
413
 
414
+ print(f"{'🎵'*30}")
415
+ print(" TRAINING COMPLETE!")
416
+ print(f"{'🎵'*30}")
417
+ print(" Model can predict track popularity and provide recommendations\n")
418
 
419
  return {'metrics': metrics, 'model_path': str(model_path)}
420
 
421
 
422
  if __name__ == '__main__':
423
+ import argparse
424
+
425
+ parser = argparse.ArgumentParser(description='Train ViralTrack Predictor')
426
+ parser.add_argument('config', nargs='?', default='config', help='Config file name (default: config)')
427
+ parser.add_argument('--epochs', type=int, default=None, help='Number of training epochs')
428
+ parser.add_argument('--batch_size', type=int, default=None, help='Training batch size')
429
+ parser.add_argument('--num_samples', type=int, default=None, help='Number of samples to use (for faster testing)')
430
+
431
+ args = parser.parse_args()
432
+ train(args.config, epochs=args.epochs, batch_size=args.batch_size, num_samples=args.num_samples)
test_model.py CHANGED
@@ -18,38 +18,54 @@ def load_model(model_path):
18
  return model, tokenizer
19
 
20
 
21
- def predict_popularity(model, tokenizer, track_name, audio_features=None):
22
  """
23
  Predict popularity for a track
24
-
25
  Args:
26
  model: Trained model
27
  tokenizer: Tokenizer
28
  track_name: Song title
 
29
  audio_features: Dict of audio features (danceability, energy, etc.)
30
-
31
  Returns:
32
- popularity_score (0-100), confidence, recommendations
33
  """
34
- # Build input text with audio features if provided
 
 
 
 
 
 
 
35
  if audio_features:
36
- feature_str = ' | '.join([f"{k}:{v:.3f}" for k, v in audio_features.items()])
37
- input_text = f"{track_name} | {feature_str}"
 
 
 
 
 
38
  else:
39
- input_text = track_name
40
-
41
- # Tokenize
42
  inputs = tokenizer(input_text, return_tensors='pt', padding=True, truncation=True, max_length=128)
43
 
44
  # Predict
45
  with torch.no_grad():
46
  outputs = model(**inputs)
47
- # Regression output - sigmoid to get 0-1 range, then scale to 0-100
48
- raw_score = torch.sigmoid(outputs.logits).item() * 100
49
-
 
 
 
50
  # Generate recommendations
51
  recommendations = generate_recommendations(raw_score, audio_features or {})
52
-
53
  return raw_score, recommendations
54
 
55
 
@@ -103,6 +119,7 @@ def test_model(model_path, test_tracks=None):
103
  test_tracks = [
104
  {
105
  'track_name': "Bohemian Rhapsody",
 
106
  'audio_features': {
107
  'danceability': 0.416,
108
  'energy': 0.489,
@@ -117,6 +134,7 @@ def test_model(model_path, test_tracks=None):
117
  },
118
  {
119
  'track_name': "Shape of You",
 
120
  'audio_features': {
121
  'danceability': 0.825,
122
  'energy': 0.652,
@@ -131,6 +149,7 @@ def test_model(model_path, test_tracks=None):
131
  },
132
  {
133
  'track_name': "Blinding Lights",
 
134
  'audio_features': {
135
  'danceability': 0.514,
136
  'energy': 0.730,
@@ -145,6 +164,7 @@ def test_model(model_path, test_tracks=None):
145
  },
146
  {
147
  'track_name': "Bad Guy",
 
148
  'audio_features': {
149
  'danceability': 0.703,
150
  'energy': 0.432,
@@ -159,6 +179,7 @@ def test_model(model_path, test_tracks=None):
159
  },
160
  {
161
  'track_name': "Old Town Road",
 
162
  'audio_features': {
163
  'danceability': 0.547,
164
  'energy': 0.621,
@@ -180,9 +201,10 @@ def test_model(model_path, test_tracks=None):
180
  results = []
181
  for track in test_tracks:
182
  track_name = track['track_name']
 
183
  audio_features = track.get('audio_features', {})
184
-
185
- popularity, recommendations = predict_popularity(model, tokenizer, track_name, audio_features)
186
 
187
  results.append({
188
  'track_name': track_name,
@@ -217,19 +239,21 @@ def interactive_mode(model, tokenizer):
217
  print("\n" + "=" * 70)
218
  print("🎤 Interactive Mode - Enter track details (or 'quit' to exit)")
219
  print("=" * 70)
220
-
221
  while True:
222
  track_name = input("\n🎵 Track name: ").strip()
223
  if track_name.lower() in ['quit', 'exit', 'q']:
224
  break
225
-
 
 
226
  # Optional: enter audio features
227
  use_features = input(" Add audio features? (y/n): ").strip().lower()
228
  audio_features = {}
229
-
230
  if use_features == 'y':
231
  print(" Enter features (or press Enter to skip):")
232
- for feat in ['danceability', 'energy', 'valence', 'tempo', 'duration_ms',
233
  'acousticness', 'instrumentalness', 'liveness', 'speechiness']:
234
  val = input(f" {feat}: ").strip()
235
  if val:
@@ -237,8 +261,8 @@ def interactive_mode(model, tokenizer):
237
  audio_features[feat] = float(val)
238
  except ValueError:
239
  pass
240
-
241
- popularity, recommendations = predict_popularity(model, tokenizer, track_name, audio_features)
242
 
243
  print(f"\n 📊 Predicted Popularity: {popularity:.1f}/100")
244
  print(f"\n 💡 Recommendations:")
@@ -248,7 +272,7 @@ def interactive_mode(model, tokenizer):
248
 
249
  if __name__ == '__main__':
250
  if len(sys.argv) < 2:
251
- model_path = 'outputs/final_model'
252
  else:
253
  model_path = sys.argv[1]
254
 
 
18
  return model, tokenizer
19
 
20
 
21
+ def predict_popularity(model, tokenizer, track_name, artists="", audio_features=None):
22
  """
23
  Predict popularity for a track
24
+
25
  Args:
26
  model: Trained model
27
  tokenizer: Tokenizer
28
  track_name: Song title
29
+ artists: Artist name(s)
30
  audio_features: Dict of audio features (danceability, energy, etc.)
31
+
32
  Returns:
33
+ popularity_score (0-100), recommendations
34
  """
35
+ # Build input text - SAME FORMAT AS TRAINING
36
+ text_parts = [track_name]
37
+ if artists:
38
+ text_parts.append(artists)
39
+ combined_text = ' '.join(text_parts)
40
+
41
+ # Add audio features in same format as training
42
+ numerical = []
43
  if audio_features:
44
+ for col, val in audio_features.items():
45
+ if col not in ['track_name', 'artists']:
46
+ numerical.append(f"{col}:{float(val):.3f}")
47
+
48
+ # Combine all into text for the model (matches training preprocessing)
49
+ if numerical:
50
+ input_text = f"{combined_text} | {' '.join(numerical)}"
51
  else:
52
+ input_text = combined_text
53
+
54
+ # Tokenize - SAME PARAMETERS AS TRAINING (max_length=128)
55
  inputs = tokenizer(input_text, return_tensors='pt', padding=True, truncation=True, max_length=128)
56
 
57
  # Predict
58
  with torch.no_grad():
59
  outputs = model(**inputs)
60
+ # Regression output - already scaled to 0-1 during training, scale back to 0-100
61
+ raw_score = outputs.logits.item() * 100
62
+
63
+ # Clamp to valid range
64
+ raw_score = max(0, min(100, raw_score))
65
+
66
  # Generate recommendations
67
  recommendations = generate_recommendations(raw_score, audio_features or {})
68
+
69
  return raw_score, recommendations
70
 
71
 
 
119
  test_tracks = [
120
  {
121
  'track_name': "Bohemian Rhapsody",
122
+ 'artists': "Queen",
123
  'audio_features': {
124
  'danceability': 0.416,
125
  'energy': 0.489,
 
134
  },
135
  {
136
  'track_name': "Shape of You",
137
+ 'artists': "Ed Sheeran",
138
  'audio_features': {
139
  'danceability': 0.825,
140
  'energy': 0.652,
 
149
  },
150
  {
151
  'track_name': "Blinding Lights",
152
+ 'artists': "The Weeknd",
153
  'audio_features': {
154
  'danceability': 0.514,
155
  'energy': 0.730,
 
164
  },
165
  {
166
  'track_name': "Bad Guy",
167
+ 'artists': "Billie Eilish",
168
  'audio_features': {
169
  'danceability': 0.703,
170
  'energy': 0.432,
 
179
  },
180
  {
181
  'track_name': "Old Town Road",
182
+ 'artists': "Lil Nas X",
183
  'audio_features': {
184
  'danceability': 0.547,
185
  'energy': 0.621,
 
201
  results = []
202
  for track in test_tracks:
203
  track_name = track['track_name']
204
+ artists = track.get('artists', '')
205
  audio_features = track.get('audio_features', {})
206
+
207
+ popularity, recommendations = predict_popularity(model, tokenizer, track_name, artists, audio_features)
208
 
209
  results.append({
210
  'track_name': track_name,
 
239
  print("\n" + "=" * 70)
240
  print("🎤 Interactive Mode - Enter track details (or 'quit' to exit)")
241
  print("=" * 70)
242
+
243
  while True:
244
  track_name = input("\n🎵 Track name: ").strip()
245
  if track_name.lower() in ['quit', 'exit', 'q']:
246
  break
247
+
248
+ artists = input(" Artists: ").strip()
249
+
250
  # Optional: enter audio features
251
  use_features = input(" Add audio features? (y/n): ").strip().lower()
252
  audio_features = {}
253
+
254
  if use_features == 'y':
255
  print(" Enter features (or press Enter to skip):")
256
+ for feat in ['danceability', 'energy', 'valence', 'tempo', 'duration_ms',
257
  'acousticness', 'instrumentalness', 'liveness', 'speechiness']:
258
  val = input(f" {feat}: ").strip()
259
  if val:
 
261
  audio_features[feat] = float(val)
262
  except ValueError:
263
  pass
264
+
265
+ popularity, recommendations = predict_popularity(model, tokenizer, track_name, artists, audio_features)
266
 
267
  print(f"\n 📊 Predicted Popularity: {popularity:.1f}/100")
268
  print(f"\n 💡 Recommendations:")
 
272
 
273
  if __name__ == '__main__':
274
  if len(sys.argv) < 2:
275
+ model_path = 'model' # Default: ./model (current directory)
276
  else:
277
  model_path = sys.argv[1]
278