2nd MAI (Medical AI) Competition: Improving Variant Sensitivity in Genomic Language Models
Competition Period: November 18 - December 8, 2024
This project addresses the challenge of detecting genomic variants using deep learning embeddings. The competition required generating fixed-dimensional vector representations of DNA sequences that can sensitively distinguish between reference and variant sequences, particularly for medically significant mutations.
Quantitatively evaluate and improve variant sensitivity in genomic language models (gLMs) for precision medicine applications. The goal was to create embeddings that maximize:
| Metric | Value | 비고 |
|---|---|---|
| 최종 순위 (Rank) | 91st / 815 팀 | 상위 11.1% |
| 최종 점수 (Score) | 0.54554 | (1위: 0.74616) |
| Best Method | Contrastive Learning with ClinVar Data | |
| Baseline Score | 0.5432 (ESM-2) | |
| Improvement | +0.90% |
Final Score = (Normalize(CD) + Normalize(CDD) + Normalize(PCC)) / 3
where:
The winning approach leveraged task-specific contrastive learning with domain knowledge:
# Margin-based contrastive loss with pathogenic weighting
loss = weight * max(0, margin - cosine_distance(ref, variant))
weight = 2.0 for pathogenic, 1.0 for benign
Result: 0.54805 score (+0.90% over baseline)
| Rank | Date | Method | Model | Dimensions | Score | Δ Score |
|---|---|---|---|---|---|---|
| 1 | 12-07 | Contrastive Learning | NT 500m | PCA 768D | 0.54805 | Baseline |
| 2 | 11-30 | Standard Extraction | NT 500m | PCA 256D | 0.5445 | -0.0036 |
| 2 | 11-28 | Standard Extraction | NT 500m | PCA 768D | 0.5445 | -0.0036 |
| 4 | 11-20 | Baseline | ESM-2 L33 | PCA 768D | 0.5432 | -0.0049 |
| 5 | 12-01 | Layer Concat (4 layers) | NT 500m | PCA 768D | 0.5432 | -0.0049 |
| 6 | 11-29 | 1000g Genome | NT 1000g | PCA 768D | 0.5432 | -0.0049 |
| 7 | 11-27 | Layer Selection | ESM-2 L30 | PCA 768D | 0.5429 | -0.0052 |
| 8 | 11-30 | Feature Combination | NT Mean+Center | PCA 768D | 0.5428 | -0.0053 |
| 9 | 11-28 | Layer Selection | ESM-2 L20 | PCA 768D | 0.5425 | -0.0056 |
| 10 | 11-30 | Center Pooling | NT Center | PCA 768D | 0.5421 | -0.0060 |
| 11 | 11-29 | Large Model | NT 2.5b | PCA 768D | 0.5420 | -0.0061 |
| 12 | 12-01 | Character-level | HyenaDNA | 256D | 0.4977 | -0.0504 |
| 13 | 12-01 | Multi-species | NT-v2 50m | 512D | 0.4925 | -0.0556 |
Contrastive Learning with Domain Data (0.5481)
PCA Dimensionality Reduction (+0.049)
Optimal PCA Dimensions
Human-specific Models
Architectural Changes (-0.0049)
Alternative Pooling Strategies (-0.0024)
Model Scale (-0.0025)
Character-level Models (-0.0468)
Classification Fine-tuning (Failed)
class ContrastiveModel(nn.Module):
"""Task-specific contrastive learning for variant detection"""
def __init__(self, backbone_model):
super().__init__()
self.backbone = backbone_model # Frozen NT-500m
self.projection = nn.Sequential(
nn.Linear(1280, 512),
nn.ReLU(),
nn.Linear(512, 768)
)
def forward(self, ref_seq, var_seq):
ref_emb = self.backbone(ref_seq).mean(dim=1)
var_emb = self.backbone(var_seq).mean(dim=1)
ref_proj = self.projection(ref_emb)
var_proj = self.projection(var_emb)
return ref_proj, var_proj
class ContrastiveLoss(nn.Module):
"""Margin-based contrastive loss with pathogenic weighting"""
def forward(self, ref_emb, var_emb, weight, margin=0.5):
# Cosine distance
distance = 1 - F.cosine_similarity(ref_emb, var_emb)
# Margin loss: push variants apart
loss = weight * torch.clamp(margin - distance, min=0)
return loss.mean()
# Download and process ClinVar variant data
def prepare_clinvar_data():
"""Extract pathogenic/benign variant pairs from ClinVar"""
# Filter human variants
variants = load_clinvar_vcf()
variants = variants[variants.chromosome.isin([f'chr{i}' for i in range(1,23)])]
# Label mapping
pathogenic = variants[variants.CLNSIG.str.contains('Pathogenic')]
benign = variants[variants.CLNSIG.str.contains('Benign')]
# Extract reference and variant sequences (1024bp windows)
pairs = []
for idx, row in variants.iterrows():
ref_seq = get_sequence(row.chromosome, row.position, window=1024)
var_seq = introduce_variant(ref_seq, row.alt_allele, row.position)
pairs.append({
'ref_seq': ref_seq,
'var_seq': var_seq,
'pathogenicity': row.CLNSIG,
'weight': 2.0 if 'Pathogenic' in row.CLNSIG else 1.0
})
return pd.DataFrame(pairs)
# Hyperparameters
BATCH_SIZE = 2
GRADIENT_ACCUMULATION = 8 # Effective batch = 16
LEARNING_RATE = 1e-5
NUM_EPOCHS = 3
MARGIN = 0.5
PATHOGENIC_WEIGHT = 2.0
# GPU memory optimization (RTX 4060 8GB)
- Mixed precision training (FP16)
- Gradient accumulation
- Batch size 2
def extract_embeddings(model, sequences, batch_size=8):
"""Extract embeddings for test sequences"""
embeddings = []
model.eval()
with torch.no_grad():
for batch in batches(sequences, batch_size):
# Tokenize
inputs = tokenizer(batch, return_tensors='pt',
padding=True, truncation=True)
# Forward pass
outputs = model(**inputs)
hidden_states = outputs.hidden_states[-1]
# Mean pooling (exclude padding)
mask = inputs['attention_mask'].unsqueeze(-1)
emb = (hidden_states * mask).sum(1) / mask.sum(1)
embeddings.append(emb.cpu().numpy())
return np.vstack(embeddings)
from sklearn.decomposition import PCA
# Fit PCA on training embeddings
pca = PCA(n_components=768, random_state=42)
pca.fit(train_embeddings)
# Transform test embeddings
test_embeddings_pca = pca.transform(test_embeddings)
# Variance explained: 99%
print(f"Variance explained: {pca.explained_variance_ratio_.sum():.2%}")
Goal: Establish baseline with protein language models
Goal: Test specialized genomic language models
Key Discovery: PCA noise reduction is essential (+0.049 improvement)
Goal: Test architectural modifications
Key Discovery: Pre-trained representations already optimal, architectural changes ineffective
Goal: Task-specific fine-tuning with genomic tasks
Key Discovery: Classification fine-tuning ≠ Similarity-based variant detection (task mismatch)
Goal: Task-specific learning with domain knowledge
Key Discovery: Domain-specific contrastive learning with appropriate loss function achieves best performance
mai/
├── README.md # This file
├── CLAUDE.md # Detailed experiment log
├── data/
│ ├── raw/
│ │ ├── test.csv # 13,711 DNA sequences (1024bp)
│ │ └── sample_submission.csv
│ ├── processed/
│ │ ├── clinvar_variants.csv # ClinVar training data (100K pairs)
│ │ ├── embeddings_nt_1280d.npy # Raw NT embeddings
│ │ ├── embeddings_nt_768d.npy # PCA 768D
│ │ ├── embeddings_nt_256d.npy # PCA 256D
│ │ └── embeddings_contrastive_*.npy # Contrastive embeddings
│ └── clinvar/
│ └── clinvar_20241110.vcf.gz # ClinVar variant database
├── src/
│ ├── models/
│ │ ├── contrastive_learning.py # 🌟 Contrastive training
│ │ ├── extract_contrastive.py # Contrastive embedding extraction
│ │ ├── prepare_clinvar_data.py # ClinVar data preparation
│ │ ├── extract_nt_embeddings.py # Standard NT extraction
│ │ ├── extract_layer_concat.py # Layer concatenation
│ │ ├── extract_hyenadna.py # HyenaDNA extraction
│ │ └── apply_pca.py # PCA dimensionality reduction
│ ├── submission/
│ │ └── create_submission.py # Generate submission CSV
│ └── utils/
│ └── metrics.py # Evaluation metrics
├── models/
│ └── contrastive_nt_epoch3.pt # Best checkpoint
├── submissions/
│ ├── submission_contrastive_pca768d_*.csv # 🏆 Best (0.5481)
│ ├── submission_nt_pca_fixed_*.csv # Baseline (0.5445)
│ └── [18 experimental submissions]
├── logs/
│ ├── contrastive_learning.log # Training logs
│ └── experiments/ # Detailed experiment logs
└── research/
├── layer_analysis.md # Layer selection analysis
└── pooling_comparison.md # Pooling strategy comparison
Finding: Task-specific contrastive learning with domain data (ClinVar) outperformed all architectural modifications.
Insight: Understanding the biological problem (pathogenic vs benign variants) and incorporating domain knowledge through appropriate loss functions is more effective than blindly modifying architectures.
Finding: Layer concatenation, weighted layers, and other architectural changes showed no improvement (0.5432 = baseline).
Insight: Large-scale pre-training (NT 500m on 1000+ genomes) has already learned optimal representations. Fine-tuning the embedding space (not the encoder) is the right approach.
Finding: PCA 768D (0.5445) >> Raw 1280D (0.4955), improvement of +0.049 (+9.9%).
Insight: PCA removes noise dimensions that interfere with variant detection. This is critical for similarity-based tasks where small distances matter.
Finding: PCA 256D (95% variance) = PCA 768D (99% variance) = 0.5445 for standard extraction.
Insight: The extra 4% variance doesn't contribute to variant sensitivity. Task-relevant information saturates at lower dimensions.
Finding: Splice/promoter classification fine-tuning completely failed (loss stuck at random baseline).
Insight: Training objective must match evaluation objective. Classification accuracy doesn't transfer to embedding similarity tasks.
Finding: Margin-based contrastive loss with pathogenic weighting achieved best performance (0.5481).
Insight: Standard cross-entropy or triplet losses weren't optimal. Designing a loss function that directly optimizes for ref-variant distance discrimination was key.
Finding: NT 500m-human-ref (0.5445) > NT 2.5b-multi-species (0.5420)
Insight: A smaller model trained on the right data (human-specific) outperforms larger models trained on broader data. Domain alignment trumps model capacity.
Finding: 18 submissions with systematic testing of hypotheses (pooling, layers, models, fine-tuning).
Insight: Quickly testing and rejecting unsuccessful approaches (e.g., UMAP, character-level models) allowed focus on promising directions (contrastive learning).
# System
- Ubuntu 20.04+ (WSL supported)
- NVIDIA GPU with 8GB+ VRAM
- CUDA 11.8+
# Python
Python 3.12.0
# 1. Clone repository
git clone <repository-url>
cd mai
# 2. Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/Mac
# or
venv\Scripts\activate # Windows
# 3. Install dependencies
pip install torch==2.7.1+cu118 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
pip install transformers==4.57.1
pip install pandas numpy scikit-learn tqdm
pip install datasets # For ClinVar data
# Download ClinVar variants
python src/models/prepare_clinvar_data.py
# This will:
# - Download ClinVar VCF (latest version)
# - Filter human variants (chr1-22)
# - Extract pathogenic/benign pairs
# - Generate reference-variant sequence pairs (1024bp windows)
# Output: data/processed/clinvar_variants.csv (100K pairs)
# Train contrastive model
python src/models/contrastive_learning.py
# Configuration (in script):
# - MODEL: InstaDeepAI/nucleotide-transformer-500m-human-ref
# - BATCH_SIZE: 2 (gradient accumulation = 8)
# - LEARNING_RATE: 1e-5
# - EPOCHS: 3
# - MARGIN: 0.5
# - PATHOGENIC_WEIGHT: 2.0
# Training takes ~6 hours on RTX 4060
# Checkpoint saved to: models/contrastive_nt_epoch3.pt
# Extract embeddings for test data
python src/models/extract_contrastive.py
# This will:
# - Load trained model from checkpoint
# - Process test.csv sequences
# - Generate 1280D embeddings
# - Apply PCA reduction to 768D
# - Save to: data/processed/embeddings_contrastive_768d.npy
# Generate submission file
python src/submission/create_submission.py \
--embedding data/processed/embeddings_contrastive_768d.npy \
--output submissions/submission_contrastive.csv
# Output format:
# ID,emb_0000,emb_0001,...,emb_0767
# 0,0.123,-0.456,...,0.789
# ...
# Reproduce baseline (NT standard extraction)
python src/models/extract_nt_embeddings.py
python src/models/apply_pca.py --dims 768
python src/submission/create_submission.py
# Test alternative models
python src/models/extract_hyenadna.py # HyenaDNA
python src/models/extract_layer_concat.py # Layer concatenation
| Category | Technology |
|---|---|
| Language | Python 3.12 |
| Deep Learning | PyTorch 2.7.1 |
| Transformers | Hugging Face Transformers 4.57.1 |
| Models | Nucleotide Transformer, ESM-2, HyenaDNA |
| Dimensionality Reduction | scikit-learn PCA |
| Data Processing | pandas, NumPy |
| GPU | NVIDIA RTX 4060 (8GB) |
| Platform | WSL Ubuntu 20.04 |
| Model | Type | Parameters | Dimensions | Best Score | Status |
|---|---|---|---|---|---|
| NT 500m-human-ref | DNA | 485M | 1280 | 0.5481 | ✅ Winner (contrastive) |
| NT 500m-human-ref | DNA | 485M | 1280 | 0.5445 | ✅ Baseline (standard) |
| NT 500m-1000g | DNA | 485M | 1280 | 0.5432 | ✅ Tested |
| NT 2.5b-multi | DNA | 2.5B | 2560 | 0.5420 | ✅ Tested |
| ESM-2 650M | Protein | 650M | 1280 | 0.5432 | ✅ Initial baseline |
| HyenaDNA-small | DNA | 1.6M | 256 | 0.4977 | ❌ Character-level |
| NT-v2-50m | DNA | 50M | 512 | 0.4925 | ❌ Multi-species |
| DNABERT-2 | DNA | 117M | 768 | - | ❌ Triton compatibility |
| Caduceus | DNA | 12M | 256 | - | ❌ mamba_ssm dependency |
This project is for academic and portfolio purposes. Competition data and models are subject to their respective licenses:
Team: softkleenex GitHub: https://github.com/softkleenex Competition: 2nd MAI Genomic Variant Detection Date: December 2024
Last Updated: December 8, 2024
2nd MAI (Medical AI) Competition: Improving Variant Sensitivity in Genomic Language Models
Competition Period: November 18 - December 8, 2024
This project addresses the challenge of detecting genomic variants using deep learning embeddings. The competition required generating fixed-dimensional vector representations of DNA sequences that can sensitively distinguish between reference and variant sequences, particularly for medically significant mutations.
Quantitatively evaluate and improve variant sensitivity in genomic language models (gLMs) for precision medicine applications. The goal was to create embeddings that maximize:
| Metric | Value | 비고 |
|---|---|---|
| 최종 순위 (Rank) | 91st / 815 팀 | 상위 11.1% |
| 최종 점수 (Score) | 0.54554 | (1위: 0.74616) |
| Best Method | Contrastive Learning with ClinVar Data | |
| Baseline Score | 0.5432 (ESM-2) | |
| Improvement | +0.90% |
Final Score = (Normalize(CD) + Normalize(CDD) + Normalize(PCC)) / 3
where:
The winning approach leveraged task-specific contrastive learning with domain knowledge:
# Margin-based contrastive loss with pathogenic weighting
loss = weight * max(0, margin - cosine_distance(ref, variant))
weight = 2.0 for pathogenic, 1.0 for benign
Result: 0.54805 score (+0.90% over baseline)
| Rank | Date | Method | Model | Dimensions | Score | Δ Score |
|---|---|---|---|---|---|---|
| 1 | 12-07 | Contrastive Learning | NT 500m | PCA 768D | 0.54805 | Baseline |
| 2 | 11-30 | Standard Extraction | NT 500m | PCA 256D | 0.5445 | -0.0036 |
| 2 | 11-28 | Standard Extraction | NT 500m | PCA 768D | 0.5445 | -0.0036 |
| 4 | 11-20 | Baseline | ESM-2 L33 | PCA 768D | 0.5432 | -0.0049 |
| 5 | 12-01 | Layer Concat (4 layers) | NT 500m | PCA 768D | 0.5432 | -0.0049 |
| 6 | 11-29 | 1000g Genome | NT 1000g | PCA 768D | 0.5432 | -0.0049 |
| 7 | 11-27 | Layer Selection | ESM-2 L30 | PCA 768D | 0.5429 | -0.0052 |
| 8 | 11-30 | Feature Combination | NT Mean+Center | PCA 768D | 0.5428 | -0.0053 |
| 9 | 11-28 | Layer Selection | ESM-2 L20 | PCA 768D | 0.5425 | -0.0056 |
| 10 | 11-30 | Center Pooling | NT Center | PCA 768D | 0.5421 | -0.0060 |
| 11 | 11-29 | Large Model | NT 2.5b | PCA 768D | 0.5420 | -0.0061 |
| 12 | 12-01 | Character-level | HyenaDNA | 256D | 0.4977 | -0.0504 |
| 13 | 12-01 | Multi-species | NT-v2 50m | 512D | 0.4925 | -0.0556 |
Contrastive Learning with Domain Data (0.5481)
PCA Dimensionality Reduction (+0.049)
Optimal PCA Dimensions
Human-specific Models
Architectural Changes (-0.0049)
Alternative Pooling Strategies (-0.0024)
Model Scale (-0.0025)
Character-level Models (-0.0468)
Classification Fine-tuning (Failed)
class ContrastiveModel(nn.Module):
"""Task-specific contrastive learning for variant detection"""
def __init__(self, backbone_model):
super().__init__()
self.backbone = backbone_model # Frozen NT-500m
self.projection = nn.Sequential(
nn.Linear(1280, 512),
nn.ReLU(),
nn.Linear(512, 768)
)
def forward(self, ref_seq, var_seq):
ref_emb = self.backbone(ref_seq).mean(dim=1)
var_emb = self.backbone(var_seq).mean(dim=1)
ref_proj = self.projection(ref_emb)
var_proj = self.projection(var_emb)
return ref_proj, var_proj
class ContrastiveLoss(nn.Module):
"""Margin-based contrastive loss with pathogenic weighting"""
def forward(self, ref_emb, var_emb, weight, margin=0.5):
# Cosine distance
distance = 1 - F.cosine_similarity(ref_emb, var_emb)
# Margin loss: push variants apart
loss = weight * torch.clamp(margin - distance, min=0)
return loss.mean()
# Download and process ClinVar variant data
def prepare_clinvar_data():
"""Extract pathogenic/benign variant pairs from ClinVar"""
# Filter human variants
variants = load_clinvar_vcf()
variants = variants[variants.chromosome.isin([f'chr{i}' for i in range(1,23)])]
# Label mapping
pathogenic = variants[variants.CLNSIG.str.contains('Pathogenic')]
benign = variants[variants.CLNSIG.str.contains('Benign')]
# Extract reference and variant sequences (1024bp windows)
pairs = []
for idx, row in variants.iterrows():
ref_seq = get_sequence(row.chromosome, row.position, window=1024)
var_seq = introduce_variant(ref_seq, row.alt_allele, row.position)
pairs.append({
'ref_seq': ref_seq,
'var_seq': var_seq,
'pathogenicity': row.CLNSIG,
'weight': 2.0 if 'Pathogenic' in row.CLNSIG else 1.0
})
return pd.DataFrame(pairs)
# Hyperparameters
BATCH_SIZE = 2
GRADIENT_ACCUMULATION = 8 # Effective batch = 16
LEARNING_RATE = 1e-5
NUM_EPOCHS = 3
MARGIN = 0.5
PATHOGENIC_WEIGHT = 2.0
# GPU memory optimization (RTX 4060 8GB)
- Mixed precision training (FP16)
- Gradient accumulation
- Batch size 2
def extract_embeddings(model, sequences, batch_size=8):
"""Extract embeddings for test sequences"""
embeddings = []
model.eval()
with torch.no_grad():
for batch in batches(sequences, batch_size):
# Tokenize
inputs = tokenizer(batch, return_tensors='pt',
padding=True, truncation=True)
# Forward pass
outputs = model(**inputs)
hidden_states = outputs.hidden_states[-1]
# Mean pooling (exclude padding)
mask = inputs['attention_mask'].unsqueeze(-1)
emb = (hidden_states * mask).sum(1) / mask.sum(1)
embeddings.append(emb.cpu().numpy())
return np.vstack(embeddings)
from sklearn.decomposition import PCA
# Fit PCA on training embeddings
pca = PCA(n_components=768, random_state=42)
pca.fit(train_embeddings)
# Transform test embeddings
test_embeddings_pca = pca.transform(test_embeddings)
# Variance explained: 99%
print(f"Variance explained: {pca.explained_variance_ratio_.sum():.2%}")
Goal: Establish baseline with protein language models
Goal: Test specialized genomic language models
Key Discovery: PCA noise reduction is essential (+0.049 improvement)
Goal: Test architectural modifications
Key Discovery: Pre-trained representations already optimal, architectural changes ineffective
Goal: Task-specific fine-tuning with genomic tasks
Key Discovery: Classification fine-tuning ≠ Similarity-based variant detection (task mismatch)
Goal: Task-specific learning with domain knowledge
Key Discovery: Domain-specific contrastive learning with appropriate loss function achieves best performance
mai/
├── README.md # This file
├── CLAUDE.md # Detailed experiment log
├── data/
│ ├── raw/
│ │ ├── test.csv # 13,711 DNA sequences (1024bp)
│ │ └── sample_submission.csv
│ ├── processed/
│ │ ├── clinvar_variants.csv # ClinVar training data (100K pairs)
│ │ ├── embeddings_nt_1280d.npy # Raw NT embeddings
│ │ ├── embeddings_nt_768d.npy # PCA 768D
│ │ ├── embeddings_nt_256d.npy # PCA 256D
│ │ └── embeddings_contrastive_*.npy # Contrastive embeddings
│ └── clinvar/
│ └── clinvar_20241110.vcf.gz # ClinVar variant database
├── src/
│ ├── models/
│ │ ├── contrastive_learning.py # 🌟 Contrastive training
│ │ ├── extract_contrastive.py # Contrastive embedding extraction
│ │ ├── prepare_clinvar_data.py # ClinVar data preparation
│ │ ├── extract_nt_embeddings.py # Standard NT extraction
│ │ ├── extract_layer_concat.py # Layer concatenation
│ │ ├── extract_hyenadna.py # HyenaDNA extraction
│ │ └── apply_pca.py # PCA dimensionality reduction
│ ├── submission/
│ │ └── create_submission.py # Generate submission CSV
│ └── utils/
│ └── metrics.py # Evaluation metrics
├── models/
│ └── contrastive_nt_epoch3.pt # Best checkpoint
├── submissions/
│ ├── submission_contrastive_pca768d_*.csv # 🏆 Best (0.5481)
│ ├── submission_nt_pca_fixed_*.csv # Baseline (0.5445)
│ └── [18 experimental submissions]
├── logs/
│ ├── contrastive_learning.log # Training logs
│ └── experiments/ # Detailed experiment logs
└── research/
├── layer_analysis.md # Layer selection analysis
└── pooling_comparison.md # Pooling strategy comparison
Finding: Task-specific contrastive learning with domain data (ClinVar) outperformed all architectural modifications.
Insight: Understanding the biological problem (pathogenic vs benign variants) and incorporating domain knowledge through appropriate loss functions is more effective than blindly modifying architectures.
Finding: Layer concatenation, weighted layers, and other architectural changes showed no improvement (0.5432 = baseline).
Insight: Large-scale pre-training (NT 500m on 1000+ genomes) has already learned optimal representations. Fine-tuning the embedding space (not the encoder) is the right approach.
Finding: PCA 768D (0.5445) >> Raw 1280D (0.4955), improvement of +0.049 (+9.9%).
Insight: PCA removes noise dimensions that interfere with variant detection. This is critical for similarity-based tasks where small distances matter.
Finding: PCA 256D (95% variance) = PCA 768D (99% variance) = 0.5445 for standard extraction.
Insight: The extra 4% variance doesn't contribute to variant sensitivity. Task-relevant information saturates at lower dimensions.
Finding: Splice/promoter classification fine-tuning completely failed (loss stuck at random baseline).
Insight: Training objective must match evaluation objective. Classification accuracy doesn't transfer to embedding similarity tasks.
Finding: Margin-based contrastive loss with pathogenic weighting achieved best performance (0.5481).
Insight: Standard cross-entropy or triplet losses weren't optimal. Designing a loss function that directly optimizes for ref-variant distance discrimination was key.
Finding: NT 500m-human-ref (0.5445) > NT 2.5b-multi-species (0.5420)
Insight: A smaller model trained on the right data (human-specific) outperforms larger models trained on broader data. Domain alignment trumps model capacity.
Finding: 18 submissions with systematic testing of hypotheses (pooling, layers, models, fine-tuning).
Insight: Quickly testing and rejecting unsuccessful approaches (e.g., UMAP, character-level models) allowed focus on promising directions (contrastive learning).
# System
- Ubuntu 20.04+ (WSL supported)
- NVIDIA GPU with 8GB+ VRAM
- CUDA 11.8+
# Python
Python 3.12.0
# 1. Clone repository
git clone <repository-url>
cd mai
# 2. Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/Mac
# or
venv\Scripts\activate # Windows
# 3. Install dependencies
pip install torch==2.7.1+cu118 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
pip install transformers==4.57.1
pip install pandas numpy scikit-learn tqdm
pip install datasets # For ClinVar data
# Download ClinVar variants
python src/models/prepare_clinvar_data.py
# This will:
# - Download ClinVar VCF (latest version)
# - Filter human variants (chr1-22)
# - Extract pathogenic/benign pairs
# - Generate reference-variant sequence pairs (1024bp windows)
# Output: data/processed/clinvar_variants.csv (100K pairs)
# Train contrastive model
python src/models/contrastive_learning.py
# Configuration (in script):
# - MODEL: InstaDeepAI/nucleotide-transformer-500m-human-ref
# - BATCH_SIZE: 2 (gradient accumulation = 8)
# - LEARNING_RATE: 1e-5
# - EPOCHS: 3
# - MARGIN: 0.5
# - PATHOGENIC_WEIGHT: 2.0
# Training takes ~6 hours on RTX 4060
# Checkpoint saved to: models/contrastive_nt_epoch3.pt
# Extract embeddings for test data
python src/models/extract_contrastive.py
# This will:
# - Load trained model from checkpoint
# - Process test.csv sequences
# - Generate 1280D embeddings
# - Apply PCA reduction to 768D
# - Save to: data/processed/embeddings_contrastive_768d.npy
# Generate submission file
python src/submission/create_submission.py \
--embedding data/processed/embeddings_contrastive_768d.npy \
--output submissions/submission_contrastive.csv
# Output format:
# ID,emb_0000,emb_0001,...,emb_0767
# 0,0.123,-0.456,...,0.789
# ...
# Reproduce baseline (NT standard extraction)
python src/models/extract_nt_embeddings.py
python src/models/apply_pca.py --dims 768
python src/submission/create_submission.py
# Test alternative models
python src/models/extract_hyenadna.py # HyenaDNA
python src/models/extract_layer_concat.py # Layer concatenation
| Category | Technology |
|---|---|
| Language | Python 3.12 |
| Deep Learning | PyTorch 2.7.1 |
| Transformers | Hugging Face Transformers 4.57.1 |
| Models | Nucleotide Transformer, ESM-2, HyenaDNA |
| Dimensionality Reduction | scikit-learn PCA |
| Data Processing | pandas, NumPy |
| GPU | NVIDIA RTX 4060 (8GB) |
| Platform | WSL Ubuntu 20.04 |
| Model | Type | Parameters | Dimensions | Best Score | Status |
|---|---|---|---|---|---|
| NT 500m-human-ref | DNA | 485M | 1280 | 0.5481 | ✅ Winner (contrastive) |
| NT 500m-human-ref | DNA | 485M | 1280 | 0.5445 | ✅ Baseline (standard) |
| NT 500m-1000g | DNA | 485M | 1280 | 0.5432 | ✅ Tested |
| NT 2.5b-multi | DNA | 2.5B | 2560 | 0.5420 | ✅ Tested |
| ESM-2 650M | Protein | 650M | 1280 | 0.5432 | ✅ Initial baseline |
| HyenaDNA-small | DNA | 1.6M | 256 | 0.4977 | ❌ Character-level |
| NT-v2-50m | DNA | 50M | 512 | 0.4925 | ❌ Multi-species |
| DNABERT-2 | DNA | 117M | 768 | - | ❌ Triton compatibility |
| Caduceus | DNA | 12M | 256 | - | ❌ mamba_ssm dependency |
This project is for academic and portfolio purposes. Competition data and models are subject to their respective licenses:
Team: softkleenex GitHub: https://github.com/softkleenex Competition: 2nd MAI Genomic Variant Detection Date: December 2024
Last Updated: December 8, 2024