softkleenex/dacon-mai-glm-2025

제2회 MAI 경진대회 - 유전체 언어모델 변이 민감도 평가 (Dacon)

Python

0

15 commits

updated Jun 26, 2026

See the code

README

Genomic Variant Detection using Deep Learning

2nd MAI (Medical AI) Competition: Improving Variant Sensitivity in Genomic Language Models

Competition Score Rank Python PyTorch

Competition Period: November 18 - December 8, 2024


Table of Contents


Overview

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.

Competition Objective

Quantitatively evaluate and improve variant sensitivity in genomic language models (gLMs) for precision medicine applications. The goal was to create embeddings that maximize:

  • CD (Cosine Distance): Average distance between reference-variant pairs
  • CDD (Cosine Distance Difference): Discrimination between pathogenic vs benign variants
  • PCC (Pearson Correlation): Correlation with variant count

Final Achievement (Private Leaderboard)

MetricValue비고
최종 순위 (Rank)91st / 815 팀상위 11.1%
최종 점수 (Score)0.54554(1위: 0.74616)
Best MethodContrastive Learning with ClinVar Data
Baseline Score0.5432 (ESM-2)
Improvement+0.90%

Problem Statement

Input

  • 13,711 DNA sequences (1,024 bp each)
  • Sequences contain single nucleotide variants (SNVs)
  • No information about which sequences are reference or variant

Output

  • Fixed-dimensional embeddings (≤768 dimensions)
  • Embeddings must capture variant sensitivity
  • Maximum file size: 512MB

Evaluation Metrics

Final Score = (Normalize(CD) + Normalize(CDD) + Normalize(PCC)) / 3

where:

  • CD: Average cosine distance between ref-variant pairs
  • CDD: Distance difference between pathogenic vs benign variants
  • PCC: Correlation between variant count and distance

Constraints

  • Pre-trained models only (published before Nov 10, 2025)
  • No post-processing of embeddings (ensemble, noise injection)
  • No API-based models (OpenAI, Gemini)
  • Open-source licenses only

Solution Approach

Final Solution: Contrastive Learning ⭐

The winning approach leveraged task-specific contrastive learning with domain knowledge:

1. Domain Data Collection

  • Downloaded 100K sequences from ClinVar database
  • Extracted Pathogenic and Benign variant pairs
  • Prepared structured reference-variant pairs with labels

2. Custom Contrastive Loss

# 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

3. Fine-tuning Strategy

  • Base model: Nucleotide Transformer 500m-human-ref
  • Frozen backbone (preserve pre-trained knowledge)
  • Projection head only (trainable)
  • Focus: Maximize inter-pair distance for pathogenic variants

4. Dimensionality Reduction

  • PCA with 768 dimensions (99% variance retained)
  • Noise reduction while preserving variant signals

Result: 0.54805 score (+0.90% over baseline)


Key Results

Performance Progression

RankDateMethodModelDimensionsScoreΔ Score
112-07Contrastive LearningNT 500mPCA 768D0.54805Baseline
211-30Standard ExtractionNT 500mPCA 256D0.5445-0.0036
211-28Standard ExtractionNT 500mPCA 768D0.5445-0.0036
411-20BaselineESM-2 L33PCA 768D0.5432-0.0049
512-01Layer Concat (4 layers)NT 500mPCA 768D0.5432-0.0049
611-291000g GenomeNT 1000gPCA 768D0.5432-0.0049
711-27Layer SelectionESM-2 L30PCA 768D0.5429-0.0052
811-30Feature CombinationNT Mean+CenterPCA 768D0.5428-0.0053
911-28Layer SelectionESM-2 L20PCA 768D0.5425-0.0056
1011-30Center PoolingNT CenterPCA 768D0.5421-0.0060
1111-29Large ModelNT 2.5bPCA 768D0.5420-0.0061
1212-01Character-levelHyenaDNA256D0.4977-0.0504
1312-01Multi-speciesNT-v2 50m512D0.4925-0.0556

Key Insights from Experiments

✅ What Worked

  1. Contrastive Learning with Domain Data (0.5481)

    • Task-specific training with ClinVar pathogenic/benign pairs
    • Margin-based loss function
    • Pathogenic weighting (2x)
    • +0.90% improvement over baseline
  2. PCA Dimensionality Reduction (+0.049)

    • Raw 1280D: 0.4955
    • PCA 768D: 0.5445
    • Noise reduction is critical
  3. Optimal PCA Dimensions

    • 256D (95% variance) = 768D (99% variance) = 0.5445
    • Diminishing returns after 256D
  4. Human-specific Models

    • human-ref (0.5445) > 1000g (0.5432) > multi-species (0.5420)
    • Domain alignment matters

❌ What Didn't Work

  1. Architectural Changes (-0.0049)

    • Layer concatenation (4/2/6 layers): No improvement
    • Multi-layer information didn't help
    • Pre-trained representations already optimal
  2. Alternative Pooling Strategies (-0.0024)

    • Center pooling: 0.5421
    • Mean + Center: 0.5428
    • Simple mean pooling is best
  3. Model Scale (-0.0025)

    • NT 2.5b: 0.5420 vs NT 500m: 0.5445
    • Bigger ≠ Better for this task
  4. Character-level Models (-0.0468)

    • HyenaDNA: 0.4977
    • Token-based > Character-based for variants
  5. Classification Fine-tuning (Failed)

    • Splice sites: Loss stuck at random (1.099)
    • Promoter prediction: Loss stuck at random (0.693)
    • Task mismatch: Classification ≠ Similarity

Technical Implementation

1. Contrastive Learning Pipeline

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()

2. Data Preparation (ClinVar)

# 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)

3. Training Configuration

# 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

4. Embedding Extraction

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)

5. PCA Dimensionality Reduction

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%}")

Experimental Journey

Phase 1: Baseline Exploration (Nov 18-20)

Goal: Establish baseline with protein language models

  • ✅ ESM-2 650M (L33): 0.5432
  • Initial dimensionality: 1280D → PCA 768D
  • Observation: DNA-specific models needed

Phase 2: DNA Model Testing (Nov 21-29)

Goal: Test specialized genomic language models

  • ✅ Nucleotide Transformer 500m: 0.5445 (+0.0013)
  • ✅ PCA optimization: 256D = 768D = 0.5445
  • ❌ UMAP: 0.4949 (-0.0483) - nonlinear projection harmful
  • ❌ Model scale: NT 2.5b (0.5420) < NT 500m (0.5445)
  • ❌ Raw dimensions: 1280D (0.4955) << PCA 768D (0.5445)

Key Discovery: PCA noise reduction is essential (+0.049 improvement)

Phase 3: Architecture Experiments (Nov 30 - Dec 1)

Goal: Test architectural modifications

  • ❌ Layer concatenation (4 layers): 0.5432 (no improvement)
  • ❌ Center pooling: 0.5421 (-0.0024)
  • ❌ Feature combinations: 0.5428 (-0.0017)
  • ❌ Weighted layers: Similar to baseline
  • ❌ HyenaDNA character-level: 0.4977 (-0.0468)
  • ❌ NT-v2 multi-species: 0.4925 (-0.0520)

Key Discovery: Pre-trained representations already optimal, architectural changes ineffective

Phase 4: Fine-tuning Attempts (Nov 30)

Goal: Task-specific fine-tuning with genomic tasks

  • ❌ Splice site classification: Loss stuck at 1.099 (random)
  • ❌ Promoter classification: Loss stuck at 0.693 (random)

Key Discovery: Classification fine-tuning ≠ Similarity-based variant detection (task mismatch)

Phase 5: Contrastive Learning (Dec 2-7)

Goal: Task-specific learning with domain knowledge

  • 📚 Collected 100K ClinVar variant pairs
  • 🔧 Designed margin-based contrastive loss
  • 🎯 Implemented pathogenic weighting (2x)
  • ✅ Final score: 0.54805 (+0.90% over baseline)

Key Discovery: Domain-specific contrastive learning with appropriate loss function achieves best performance


Project Structure

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

Key Learnings

1. Domain Knowledge > Architectural Complexity

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.

2. Pre-trained Representations Are Already Optimal

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.

3. PCA for Noise Reduction, Not Just Compression

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.

4. Optimal Dimensionality Is Task-Dependent

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.

5. Classification Fine-tuning ≠ Similarity Tasks

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.

6. Task-Specific Loss Functions Matter

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.

7. Model Selection: Alignment > Scale

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.

8. Experiment Efficiency Through Fast Failures

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).


Installation & Usage

Prerequisites

# System
- Ubuntu 20.04+ (WSL supported)
- NVIDIA GPU with 8GB+ VRAM
- CUDA 11.8+

# Python
Python 3.12.0

Setup

# 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

Data Preparation

# 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)

Training

# 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

Inference

# 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
# ...

Experiment Reproduction

# 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

Technical Stack

CategoryTechnology
LanguagePython 3.12
Deep LearningPyTorch 2.7.1
TransformersHugging Face Transformers 4.57.1
ModelsNucleotide Transformer, ESM-2, HyenaDNA
Dimensionality Reductionscikit-learn PCA
Data Processingpandas, NumPy
GPUNVIDIA RTX 4060 (8GB)
PlatformWSL Ubuntu 20.04

Models Evaluated

ModelTypeParametersDimensionsBest ScoreStatus
NT 500m-human-refDNA485M12800.5481✅ Winner (contrastive)
NT 500m-human-refDNA485M12800.5445✅ Baseline (standard)
NT 500m-1000gDNA485M12800.5432✅ Tested
NT 2.5b-multiDNA2.5B25600.5420✅ Tested
ESM-2 650MProtein650M12800.5432✅ Initial baseline
HyenaDNA-smallDNA1.6M2560.4977❌ Character-level
NT-v2-50mDNA50M5120.4925❌ Multi-species
DNABERT-2DNA117M768-❌ Triton compatibility
CaduceusDNA12M256-❌ mamba_ssm dependency

References

Competition

Models & Papers

Domain Knowledge


License

This project is for academic and portfolio purposes. Competition data and models are subject to their respective licenses:

  • Nucleotide Transformer: Apache 2.0
  • ESM-2: MIT License
  • Competition data: For competition use only

Author

Team: softkleenex GitHub: https://github.com/softkleenex Competition: 2nd MAI Genomic Variant Detection Date: December 2024


Acknowledgments

  • Competition Organizers: Korea University College of Medicine, BK21, SNA
  • Model Developers: InstaDeepAI (NT), Meta AI (ESM-2), HazyResearch (HyenaDNA)
  • Data Sources: NCBI ClinVar, UCSC Genome Browser
  • Compute: Personal workstation (RTX 4060)

Last Updated: December 8, 2024

Blog

softkleenex/dacon-mai-glm-2025

제2회 MAI 경진대회 - 유전체 언어모델 변이 민감도 평가 (Dacon)

Python

0

15 commits

updated Jun 26, 2026

See the code

README

Genomic Variant Detection using Deep Learning

2nd MAI (Medical AI) Competition: Improving Variant Sensitivity in Genomic Language Models

Competition Score Rank Python PyTorch

Competition Period: November 18 - December 8, 2024


Table of Contents


Overview

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.

Competition Objective

Quantitatively evaluate and improve variant sensitivity in genomic language models (gLMs) for precision medicine applications. The goal was to create embeddings that maximize:

  • CD (Cosine Distance): Average distance between reference-variant pairs
  • CDD (Cosine Distance Difference): Discrimination between pathogenic vs benign variants
  • PCC (Pearson Correlation): Correlation with variant count

Final Achievement (Private Leaderboard)

MetricValue비고
최종 순위 (Rank)91st / 815 팀상위 11.1%
최종 점수 (Score)0.54554(1위: 0.74616)
Best MethodContrastive Learning with ClinVar Data
Baseline Score0.5432 (ESM-2)
Improvement+0.90%

Problem Statement

Input

  • 13,711 DNA sequences (1,024 bp each)
  • Sequences contain single nucleotide variants (SNVs)
  • No information about which sequences are reference or variant

Output

  • Fixed-dimensional embeddings (≤768 dimensions)
  • Embeddings must capture variant sensitivity
  • Maximum file size: 512MB

Evaluation Metrics

Final Score = (Normalize(CD) + Normalize(CDD) + Normalize(PCC)) / 3

where:

  • CD: Average cosine distance between ref-variant pairs
  • CDD: Distance difference between pathogenic vs benign variants
  • PCC: Correlation between variant count and distance

Constraints

  • Pre-trained models only (published before Nov 10, 2025)
  • No post-processing of embeddings (ensemble, noise injection)
  • No API-based models (OpenAI, Gemini)
  • Open-source licenses only

Solution Approach

Final Solution: Contrastive Learning ⭐

The winning approach leveraged task-specific contrastive learning with domain knowledge:

1. Domain Data Collection

  • Downloaded 100K sequences from ClinVar database
  • Extracted Pathogenic and Benign variant pairs
  • Prepared structured reference-variant pairs with labels

2. Custom Contrastive Loss

# 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

3. Fine-tuning Strategy

  • Base model: Nucleotide Transformer 500m-human-ref
  • Frozen backbone (preserve pre-trained knowledge)
  • Projection head only (trainable)
  • Focus: Maximize inter-pair distance for pathogenic variants

4. Dimensionality Reduction

  • PCA with 768 dimensions (99% variance retained)
  • Noise reduction while preserving variant signals

Result: 0.54805 score (+0.90% over baseline)


Key Results

Performance Progression

RankDateMethodModelDimensionsScoreΔ Score
112-07Contrastive LearningNT 500mPCA 768D0.54805Baseline
211-30Standard ExtractionNT 500mPCA 256D0.5445-0.0036
211-28Standard ExtractionNT 500mPCA 768D0.5445-0.0036
411-20BaselineESM-2 L33PCA 768D0.5432-0.0049
512-01Layer Concat (4 layers)NT 500mPCA 768D0.5432-0.0049
611-291000g GenomeNT 1000gPCA 768D0.5432-0.0049
711-27Layer SelectionESM-2 L30PCA 768D0.5429-0.0052
811-30Feature CombinationNT Mean+CenterPCA 768D0.5428-0.0053
911-28Layer SelectionESM-2 L20PCA 768D0.5425-0.0056
1011-30Center PoolingNT CenterPCA 768D0.5421-0.0060
1111-29Large ModelNT 2.5bPCA 768D0.5420-0.0061
1212-01Character-levelHyenaDNA256D0.4977-0.0504
1312-01Multi-speciesNT-v2 50m512D0.4925-0.0556

Key Insights from Experiments

✅ What Worked

  1. Contrastive Learning with Domain Data (0.5481)

    • Task-specific training with ClinVar pathogenic/benign pairs
    • Margin-based loss function
    • Pathogenic weighting (2x)
    • +0.90% improvement over baseline
  2. PCA Dimensionality Reduction (+0.049)

    • Raw 1280D: 0.4955
    • PCA 768D: 0.5445
    • Noise reduction is critical
  3. Optimal PCA Dimensions

    • 256D (95% variance) = 768D (99% variance) = 0.5445
    • Diminishing returns after 256D
  4. Human-specific Models

    • human-ref (0.5445) > 1000g (0.5432) > multi-species (0.5420)
    • Domain alignment matters

❌ What Didn't Work

  1. Architectural Changes (-0.0049)

    • Layer concatenation (4/2/6 layers): No improvement
    • Multi-layer information didn't help
    • Pre-trained representations already optimal
  2. Alternative Pooling Strategies (-0.0024)

    • Center pooling: 0.5421
    • Mean + Center: 0.5428
    • Simple mean pooling is best
  3. Model Scale (-0.0025)

    • NT 2.5b: 0.5420 vs NT 500m: 0.5445
    • Bigger ≠ Better for this task
  4. Character-level Models (-0.0468)

    • HyenaDNA: 0.4977
    • Token-based > Character-based for variants
  5. Classification Fine-tuning (Failed)

    • Splice sites: Loss stuck at random (1.099)
    • Promoter prediction: Loss stuck at random (0.693)
    • Task mismatch: Classification ≠ Similarity

Technical Implementation

1. Contrastive Learning Pipeline

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()

2. Data Preparation (ClinVar)

# 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)

3. Training Configuration

# 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

4. Embedding Extraction

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)

5. PCA Dimensionality Reduction

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%}")

Experimental Journey

Phase 1: Baseline Exploration (Nov 18-20)

Goal: Establish baseline with protein language models

  • ✅ ESM-2 650M (L33): 0.5432
  • Initial dimensionality: 1280D → PCA 768D
  • Observation: DNA-specific models needed

Phase 2: DNA Model Testing (Nov 21-29)

Goal: Test specialized genomic language models

  • ✅ Nucleotide Transformer 500m: 0.5445 (+0.0013)
  • ✅ PCA optimization: 256D = 768D = 0.5445
  • ❌ UMAP: 0.4949 (-0.0483) - nonlinear projection harmful
  • ❌ Model scale: NT 2.5b (0.5420) < NT 500m (0.5445)
  • ❌ Raw dimensions: 1280D (0.4955) << PCA 768D (0.5445)

Key Discovery: PCA noise reduction is essential (+0.049 improvement)

Phase 3: Architecture Experiments (Nov 30 - Dec 1)

Goal: Test architectural modifications

  • ❌ Layer concatenation (4 layers): 0.5432 (no improvement)
  • ❌ Center pooling: 0.5421 (-0.0024)
  • ❌ Feature combinations: 0.5428 (-0.0017)
  • ❌ Weighted layers: Similar to baseline
  • ❌ HyenaDNA character-level: 0.4977 (-0.0468)
  • ❌ NT-v2 multi-species: 0.4925 (-0.0520)

Key Discovery: Pre-trained representations already optimal, architectural changes ineffective

Phase 4: Fine-tuning Attempts (Nov 30)

Goal: Task-specific fine-tuning with genomic tasks

  • ❌ Splice site classification: Loss stuck at 1.099 (random)
  • ❌ Promoter classification: Loss stuck at 0.693 (random)

Key Discovery: Classification fine-tuning ≠ Similarity-based variant detection (task mismatch)

Phase 5: Contrastive Learning (Dec 2-7)

Goal: Task-specific learning with domain knowledge

  • 📚 Collected 100K ClinVar variant pairs
  • 🔧 Designed margin-based contrastive loss
  • 🎯 Implemented pathogenic weighting (2x)
  • ✅ Final score: 0.54805 (+0.90% over baseline)

Key Discovery: Domain-specific contrastive learning with appropriate loss function achieves best performance


Project Structure

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

Key Learnings

1. Domain Knowledge > Architectural Complexity

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.

2. Pre-trained Representations Are Already Optimal

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.

3. PCA for Noise Reduction, Not Just Compression

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.

4. Optimal Dimensionality Is Task-Dependent

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.

5. Classification Fine-tuning ≠ Similarity Tasks

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.

6. Task-Specific Loss Functions Matter

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.

7. Model Selection: Alignment > Scale

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.

8. Experiment Efficiency Through Fast Failures

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).


Installation & Usage

Prerequisites

# System
- Ubuntu 20.04+ (WSL supported)
- NVIDIA GPU with 8GB+ VRAM
- CUDA 11.8+

# Python
Python 3.12.0

Setup

# 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

Data Preparation

# 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)

Training

# 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

Inference

# 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
# ...

Experiment Reproduction

# 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

Technical Stack

CategoryTechnology
LanguagePython 3.12
Deep LearningPyTorch 2.7.1
TransformersHugging Face Transformers 4.57.1
ModelsNucleotide Transformer, ESM-2, HyenaDNA
Dimensionality Reductionscikit-learn PCA
Data Processingpandas, NumPy
GPUNVIDIA RTX 4060 (8GB)
PlatformWSL Ubuntu 20.04

Models Evaluated

ModelTypeParametersDimensionsBest ScoreStatus
NT 500m-human-refDNA485M12800.5481✅ Winner (contrastive)
NT 500m-human-refDNA485M12800.5445✅ Baseline (standard)
NT 500m-1000gDNA485M12800.5432✅ Tested
NT 2.5b-multiDNA2.5B25600.5420✅ Tested
ESM-2 650MProtein650M12800.5432✅ Initial baseline
HyenaDNA-smallDNA1.6M2560.4977❌ Character-level
NT-v2-50mDNA50M5120.4925❌ Multi-species
DNABERT-2DNA117M768-❌ Triton compatibility
CaduceusDNA12M256-❌ mamba_ssm dependency

References

Competition

Models & Papers

Domain Knowledge


License

This project is for academic and portfolio purposes. Competition data and models are subject to their respective licenses:

  • Nucleotide Transformer: Apache 2.0
  • ESM-2: MIT License
  • Competition data: For competition use only

Author

Team: softkleenex GitHub: https://github.com/softkleenex Competition: 2nd MAI Genomic Variant Detection Date: December 2024


Acknowledgments

  • Competition Organizers: Korea University College of Medicine, BK21, SNA
  • Model Developers: InstaDeepAI (NT), Meta AI (ESM-2), HazyResearch (HyenaDNA)
  • Data Sources: NCBI ClinVar, UCSC Genome Browser
  • Compute: Personal workstation (RTX 4060)

Last Updated: December 8, 2024

Blog