multi-task learning parameter sharing negative transfer and representation learning

# Multi-Task Learning: Parameter Sharing, Negative Transfer, and Representation Learning

## 1. Introduction & Motivation

Multi-task learning (MTL) trains models on multiple related tasks simultaneously, leveraging shared structure to improve generalization. Rather than training separate models per task, MTL learns:

  • Shared representations: Common features useful across tasks
  • Task-specific components: Task-particular decision boundaries
  • Inductive transfer: Knowledge from one task improves others

Applications span numerous domains:
- NLP: Multiple semantic tasks (POS tagging, NER, sentiment)
- Computer vision: Multiple visual tasks (segmentation, detection, depth)
- Healthcare: Predicting multiple related patient outcomes
- Recommendation: Multiple recommendation objectives (CTR, rating, retention)

MTL provides sample efficiency and better generalization but risks negative transfer when tasks conflict. This article comprehensively covers MTL architectures, sharing strategies, transfer mechanisms, and practical deployment.

## 2. Core Concepts & Theory

### 2.1 Task Relationships

Tasks vary in relatedness:

Positive transfer: Task A helps task B (improved B performance)

$$ ext{Perf}_B^{ ext{MTL}} > ext{Perf}_B^{ ext{single}}$$

Negative transfer: Task A hurts task B (reduced B performance)

$$ ext{Perf}_B^{ ext{MTL}} < ext{Perf}_B^{ ext{single}}$$

Understanding task relationships critical for successful MTL.

### 2.2 Hard Parameter Sharing

Simplest approach: Share lower layers across all tasks

$$ ext{Shared}(x) = f_{ ext{shared}}(x)$$
$$y_i = f_i( ext{Shared}(x))$$

Architecture: Shared backbone → task-specific heads

Intuition: Lower layers learn task-agnostic features; higher layers specialize.

Compression:

$$ O(d_{ ext{shared}} + \sum_i d_i) $$

instead of

$$ \sum_i d_{ ext{full}} $$

.

### 2.3 Soft Parameter Sharing

Tasks learn separate parameters with regularization toward shared center:

$$ ext{Loss} = \sum_i L_i( heta_i) + \lambda \sum_i \| heta_i - \bar{ heta}\|^2$$

where

$$ \bar{ heta} = \frac{1}{T} \sum_i heta_i $$

is mean parameters.

More flexible than hard sharing; allows task-specific divergence while encouraging similarity.

### 2.4 Loss Function Weighting

Balance task losses:

$$ ext{Loss}_{ ext{total}} = \sum_i w_i L_i$$

Static weighting: Fixed

$$ w_i $$

per task

Dynamic weighting: Learn

$$ w_i $$

during training

Uncertainty weighting:

$$ w_i \propto 1/ ext{Var}( ext{task}_i) $$

## 3. Mathematical Formulation

### 3.1 Hard Sharing Gradient Flow

Gradient backpropagation through shared layer:

$$\frac{\partial L}{\partial ext{Shared}} = \sum_i \frac{\partial L_i}{\partial ext{Shared}}$$

Shared layer receives gradient from all tasks. Conflicting gradients can cancel (positive) or amplify (negative transfer).

### 3.2 Soft Sharing with Task Regularization

Objective:

$$\min_{ heta_1, \ldots, heta_T} \sum_{i=1}^{T} L_i( heta_i) + \frac{\lambda}{2} \sum_{i=1}^{T} \| heta_i - \bar{ heta}\|_2^2$$

Lagrangian interpretation: Soft constraint that parameters stay near average.

More aggressive regularization (

$$ \lambda $$

larger) → more sharing.

### 3.3 Uncertainty Weighting for Task Balance

Task-dependent noise level:

$$ ext{Loss} = \sum_i \frac{1}{2\sigma_i^2} L_i + \frac{1}{2} \log \sigma_i^2$$

Learns task variances

$$ \sigma_i $$

automatically. Noisy tasks downweighted.

Equivalence to Gaussian likelihood maximization.

### 3.4 Attention-Based Task Fusion

Learn task importance per feature:

$$z = \sum_i a_i(x) \cdot z_i^{(t)}$$

where

$$ a_i(x) = ext{softmax}(f_i(x)) $$

is learned attention weight for task i.

Dynamic weighting based on input.

## 4. Advanced Theory & Extensions

### 4.1 Cross-Stitch Units

Learn connections between task-specific layers:

$$\alpha_i = W_i \mathbf{a}_i + W_j \mathbf{a}_j$$

Learnable mixing coefficient

$$ \alpha_i \in [0, 1] $$

determines information flow between tasks.

Provides fine-grained control over sharing.

### 4.2 Modulation Networks

Modulate shared features for each task:

$$x'_i = \gamma_i(x) \odot x + \beta_i(x)$$

where

$$ \gamma_i, \beta_i $$

are task-specific functions. Feature-wise adaptive scaling.

More flexible than hard sharing, less parameters than separate models.

### 4.3 Hierarchical Multi-Task Learning

Task hierarchy: Some tasks share more with each other.

Tree structure: Related tasks grouped, share within groups, less sharing across groups.

Example: Computer vision hierarchy
- Shared bottom layer (low-level features)
- Semantic-specific layers (detection vs segmentation)
- Fine-grained task-specific heads

### 4.4 Curriculum Learning in MTL

Gradually introduce tasks:

$$ ext{Task weight}_i(t) = w_i \cdot \max(0, 1 - (t - t_i) / au)$$

Early training: Easy task(s) only
Late training: All tasks

Improves convergence and final performance.

## 5. Computational Considerations

### 5.1 Computational Cost

Shared model size:

$$ O(d_{ ext{shared}} + \sum_i d_i) $$

vs separate models:

$$ \sum_i d_{ ext{full}} = T imes d_{ ext{full}} $$

Compression ratio:

$$ \approx 1 + \frac{\sum_i d_i}{T imes d_{ ext{full}}} \approx 1 + \frac{0.1 imes d_{ ext{full}}}{d_{ ext{full}}} = 1.1 $$

For

$$ T=100 $$

tasks, ~90x compression.

### 5.2 Training Time

Single-task: Time for one task

MTL: Time for T tasks sequentially =

$$ T imes $$

single-task

But: Shared learning amortizes cost. Effective time lower.

With sufficient GPU memory, train all tasks in parallel (same time as single task).

### 5.3 Gradient Magnitude Issues

Different tasks have different loss scales:

$$\frac{\partial L_1}{\partial heta} ext{ might be } 10x ext{ larger than } \frac{\partial L_2}{\partial heta}$$

Larger gradients dominate shared layer updates, harming smaller tasks.

Solution: Gradient normalization, uncertainty weighting, or batch normalization per task.

### 5.4 Memory Optimization

Shared backbone: Single copy, all tasks access

Task-specific heads: Stored once or replicated?
- Store once: Efficient memory, must handle scheduling
- Replicate: Simpler implementation, more memory

Typical: Replicate (simplicity > minor memory overhead).

## 6. Practical Implementation Strategies

### 6.1 Architecture Design

Identify shared structure:
- Which layers useful for all tasks?
- Which task-specific?

Typical pyramid:
- Base (100% shared): Low-level features
- Middle (50% shared): Mid-level patterns
- Top (0% shared): Task-specific decisions

### 6.2 Task Selection and Ordering

Task selection:
- Complementary tasks: Reduce negative transfer
- Related domains: Maximize positive transfer
- Avoid contradictory tasks

Task ordering:
- Curriculum: Easy → hard
- Frequency: Alternate tasks
- Inverse task frequency: Hard tasks more often

### 6.3 Loss Function Design

Weighting strategy:

*Fixed weights:*

$$ w_i = 1/T $$

(equal)

*Task frequency:*

$$ w_i = n_i / \sum_j n_j $$

(by dataset size)

*Uncertainty:*

$$ w_i = 1 / ext{Var}( ext{task}_i) $$

(learned)

*Dynamic:*

$$ w_i(t) = 1 + \alpha imes ext{Grad}_{norm}( ext{task}_i) $$

(gradient-based)

Typical: Start with equal, adjust if negative transfer observed.

### 6.4 Handling Negative Transfer

Detect:
- Performance drops for some task(s)
- Large gradient magnitudes
- Conflicting gradients

Mitigate:
1. Reduce task weight: Lower

$$ w_i $$

for conflicting task
2. Reduce sharing: Use soft instead of hard sharing
3. Task adaptation: Allow more task-specific parameters
4. Separate models: Resort to task-specific models if necessary

## 7. Benchmark Datasets & Evaluation

### 7.1 Multi-Task Benchmarks

NYU v2 Dataset (Vision):
- 4 tasks: Segmentation, Depth, Surface Normals, Boundaries
- 1449 RGB-D images from indoor scenes
- Shared encoder, task-specific decoders

Results:
- Single-task (avg): 39.4% mIoU (segmentation)
- MTL hard-sharing: 41.2% (+1.8%)
- MTL soft-sharing: 42.1% (+2.7%)
- Best ensemble: 43.1%

CelebA Dataset (Vision):
- 40 tasks: Binary attribute prediction
- 202K images
- Shared CNN backbone

Results:
- Single-task (avg): 88.5% accuracy
- MTL: 89.2% accuracy (+0.7%)
- Multi-headed: Best for per-attribute metrics

SQuAD + SuperGLUE (NLP):
- 9 language understanding tasks
- Shared BERT backbone, task-specific heads

Results:
- Single-task BERT: Avg 78.5%
- MTL BERT: Avg 79.8% (+1.3%)

### 7.2 Negative Transfer Analysis

Cross-Domain MTL (ImageNet classification):
- Task 1: CIFAR-10
- Task 2: STL-10
- Different scales, domains

Results:
- CIFAR alone: 95.2%
- CIFAR + STL (MTL): 94.1% (-1.1% negative transfer)
- With reduced sharing: 94.8% (mitigated)

### 7.3 Evaluation Metrics

Per-task performance: Metric for each task
- Allows identifying negative transfer
- Task-specific analysis

Average performance: Mean across tasks
-

$$ ext{AvgPerf} = \frac{1}{T} \sum_i ext{Perf}_i $$

Relative improvement: Compared to single-task
-

$$ ext{Improvement} = ( ext{MTL} - ext{Single}) / ext{Single} $$

## 8. Key Challenges & Limitations

### 8.1 Negative Transfer

MTL doesn't always help. Causes:

  • Task conflict: Contradictory objectives
  • Capacity constraints: Model can't learn all tasks well
  • Imbalanced tasks: Large task dominates gradient

Mitigation: Task weighting, separate heads, curriculum learning.

### 8.2 Task Relationships Unknown

Prior: Don't know which tasks help which. Requires:

  • Experimentation
  • Domain expertise
  • Automated task relation discovery

Current research: Learning task relationships from data.

### 8.3 Scalability to Many Tasks

Hard sharing problematic with T >> 1:

  • Shared layer receives T different gradient signals
  • Gradient conflicts increase
  • Compromise solution hurts all tasks

Solution: Soft sharing, task clustering, hierarchical MTL.

### 8.4 Hyperparameter Complexity

More hyperparameters than single-task:

  • Task weights

$$ w_i $$

  • Sharing architecture
  • Regularization strength
  • Loss function design

Requires tuning; no universal configuration.

## 9. Hyperparameter Tuning & Optimization

### 9.1 Task Weighting

Fixed schemes:
- Equal:

$$ w_i = 1 $$

  • Uniform loss:

$$ w_i = 1/T $$

  • Inverse frequency:

$$ w_i = n_{ ext{total}}/n_i $$

(by dataset size)

Learned schemes:
- Uncertainty:

$$ w_i = 1/\sigma_i^2 $$

(learned variances)
- Gradient norm:

$$ w_i = G_i / \sum_j G_j $$

(normalized gradient norms)

Typical: Start equal, adjust if negative transfer detected.

### 9.2 Sharing Architecture

Shared layer size: Typically 60-80% of single-task

  • Larger shared: More compression, risk of under-specification
  • Smaller shared: Redundant parameters, less efficiency gain

Depth of sharing: How many layers shared?

  • 100% sharing: All layers except final (high compression, risky)
  • 50% sharing: First half shared (balance)
  • 0% sharing (besides embedding): Task-specific except input

### 9.3 Soft Sharing Regularization

**Regularization strength

$$ \lambda $$

:** 0.001-0.1 typical

  • Higher

$$ \lambda $$

: More sharing, less task specialization
- Lower

$$ \lambda $$

: Task independence, less transfer benefit

Tune via validation performance per task.

### 9.4 Learning Rate and Optimization

Single learning rate: Works well if tasks balanced

Per-task learning rates: More complex, sometimes better for imbalanced tasks

Gradient normalization: Clip or normalize gradients
- Prevents large-task domination
- Typical: Norm clipping to 1.0

## 10. Real-World Applications & Case Studies

### 10.1 NLP Multi-Task Fine-tuning

Problem: Fine-tune BERT on multiple text tasks

Setup:
- Tasks: Sentiment (SST), Similarity (STS), NLI (MNLI)
- Shared BERT encoder
- Task-specific classification heads
- 5K-10K training samples per task

Architecture:
- BERT backbone (frozen or fine-tuned)
- Task-specific linear layers
- Joint training with uncertainty weighting

Results:
- Single-task BERT: SST 92.2%, STS 88.5%, MNLI 82.1%
- MTL BERT: SST 93.1%, STS 89.2%, MNLI 83.4%
- Improvements: +0.9%, +0.7%, +1.3%

Deployment:
- Single model replaces 3 separate models
- 3x computational savings
- Shared knowledge improves all tasks

### 10.2 Computer Vision: Object Detection + Segmentation

Problem: Simultaneously detect objects and segment instances

Setup:
- Tasks: Object detection (classification + localization), Instance segmentation
- Shared ResNet backbone
- Task-specific heads (Faster RCNN for detection, Mask RCNN for segmentation)

Architecture:
- Shared CNN: ResNet-50
- Task 1 (Detection): RPN + classifier + bbox regressor
- Task 2 (Segmentation): Mask head
- Joint training

Results:
- Detection mAP: 40.2 (single) → 40.8 (MTL)
- Segmentation mAP: 35.1 (single) → 35.9 (MTL)
- Overall: 1-2% improvement, single model

### 10.3 Recommendation Systems: Multi-Objective Learning

Problem: Predict CTR, rating, and dwell time simultaneously

Setup:
- Tasks: Click-through rate (CTR), Rating (1-5), Dwell time
- Different objectives but related (all indicate engagement)
- Shared embedding layer
- Task-specific output layers

Architecture:
- Shared embeddings + dense layers
- Task-specific output layers
- Weighted loss:

$$ 0.5 L_{ ext{CTR}} + 0.3 L_{ ext{Rating}} + 0.2 L_{ ext{DwellTime}} $$

Results:
- CTR AUC: 0.80 (single) → 0.815 (MTL)
- Rating RMSE: 1.15 (single) → 1.10 (MTL)
- Dwell RMSE: 25.3 (single) → 24.1 (MTL)
- Business impact: +2% engagement

Key insight:
- CTR and engagement highly correlated
- Shared learning benefits both
- Better recommendation quality

### 10.4 Healthcare: Predicting Multiple Patient Outcomes

Problem: Predict mortality, readmission, LOS for ICU patients

Setup:
- Tasks: Mortality (binary), Readmission (binary), Length of Stay (regression)
- Patient data: Vitals, lab values, medications (~50 features)
- 10K+ patient trajectories

Architecture:
- Shared LSTM encoder (temporal)
- Task-specific heads
- Soft sharing + uncertainty weighting

Results:
- Mortality AUC: 0.81 (single) → 0.83 (MTL)
- Readmission AUC: 0.72 (single) → 0.75 (MTL)
- LOS RMSE: 3.2 (single) → 3.0 (MTL)

Clinical impact:
- Better predictions for clinicians
- Single model easier to deploy
- Shared knowledge improves all predictions

## 11. Integration with Other Methods

### 11.1 Meta-Learning for Task Adaptation

Learn initial weights that adapt quickly to new tasks:

$$ heta_i^* = heta - \alpha abla L_i( heta)$$

Combine with MTL: Pre-train on multiple source tasks, adapt to target task.

### 11.2 Multi-Task with Transfer Learning

Fine-tune pre-trained models on multiple tasks:
- BERT → Multiple NLP tasks
- ResNet → Multiple vision tasks

Benefits: Pre-training + multi-task transfer.

### 11.3 Domain Adaptation with MTL

Combine MTL with domain adaptation:
- Domain-invariant shared representations
- Task-specific heads
- Domain classifier (adversarial) for alignment

Improves both domain transfer and task learning.

### 11.4 Hierarchical MTL with Task Relationships

Learn task relationships:
- Task embedding space
- Soft parameter sharing based on task similarity
- Automatic discovery of task structure

## 12. Future Research Directions

### 12.1 Automatic Task Relation Discovery

Learning which tasks help which, without manual input:
- Task similarity from data
- Gradient alignment analysis
- Meta-learning approach

### 12.2 Handling Negative Transfer

Detecting and mitigating:
- Online detection of negative transfer
- Automatic weight adjustment
- Task decomposition for conflict resolution

### 12.3 Scaling to Hundreds of Tasks

Practical systems with >100 tasks:
- Efficient sharing mechanisms
- Task clustering
- Computation scheduling

### 12.4 Continual Multi-Task Learning

Adding new tasks without forgetting:
- Continual learning + MTL
- Task-incremental learning with knowledge consolidation

## 13. Summary & Key Takeaways

MTL Benefits:
- Sample efficiency: 10-30% improvement with limited data
- Model compression: 50-90% parameter reduction vs separate models
- Generalization: Shared representations often generalize better

Sharing Strategies:
- Hard sharing: Simple, 90% compression, risk of negative transfer
- Soft sharing: Flexible, 50% compression, better handles conflicts
- Attention-based: Dynamic weighting per input

Task Weighting:
- Equal weights:

$$ w_i = 1 $$

(baseline)
- Inverse frequency: By dataset size
- Uncertainty: Learns task variance automatically
- Gradient norm: Balances gradient magnitudes

Performance:
- Well-related tasks: 2-5% improvement typical
- Moderately related: 1-2% improvement
- Conflicting tasks: Negative transfer, avoid or separate

When to Use MTL:
- Multiple related tasks available
- Limited training data per task
- Seek model efficiency
- Tasks share low-level structure

When to Avoid:
- Tasks highly conflicting
- Single task most important
- No shared structure
- Separate models more efficient

Hyperparameters:
- Sharing depth: 50-100% (depend on task similarity)
- Regularization: λ = 0.01-0.1 for soft sharing
- Task weights: Start equal, adjust if negative transfer
- Architecture: Shared backbone + task-specific heads

Current Limitations:
- Negative transfer common, not well understood
- Task relationship discovery manual
- Scalability to 100+ tasks limited
- Hyperparameter tuning complex

Multi-task learning increasingly popular for practical systems. Provides meaningful benefits for related tasks; effectiveness depends critically on task selection and loss weighting. Recent work on automated task weighting and relation discovery showing promise.

---

## Appendix: Practical Implementation Labs

### Lab 1: Hard Parameter Sharing

import torch
import torch.nn as nn

class HardSharingMTL(nn.Module):
    def __init__(self, input_dim, shared_dim, num_tasks, task_dims):
        super().__init__()
        # Shared layers
        self.shared = nn.Sequential(
            nn.Linear(input_dim, shared_dim),
            nn.ReLU(),
            nn.Linear(shared_dim, shared_dim),
            nn.ReLU()
        )
        
        # Task-specific heads
        self.task_heads = nn.ModuleList([
            nn.Linear(shared_dim, task_dims[i]) for i in range(num_tasks)
        ])
    
    def forward(self, x):
        shared_rep = self.shared(x)
        task_outputs = [head(shared_rep) for head in self.task_heads]
        return task_outputs

# Training
model = HardSharingMTL(input_dim=50, shared_dim=100, num_tasks=3, 
                      task_dims=[10, 5, 1])
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

for epoch in range(100):
    outputs = model(X_batch)
    
    # Compute loss for each task
    loss_1 = criterion_1(outputs[0], y1_batch)
    loss_2 = criterion_2(outputs[1], y2_batch)
    loss_3 = criterion_3(outputs[2], y3_batch)
    
    total_loss = w1 * loss_1 + w2 * loss_2 + w3 * loss_3
    
    optimizer.zero_grad()
    total_loss.backward()
    optimizer.step()

### Lab 2: Soft Parameter Sharing

class SoftSharingMTL(nn.Module):
    def __init__(self, input_dim, hidden_dim, num_tasks, lambda_reg=0.01):
        super().__init__()
        self.num_tasks = num_tasks
        self.lambda_reg = lambda_reg
        
        # Task-specific parameters
        self.layers = nn.ModuleList([
            nn.Linear(input_dim, hidden_dim) for _ in range(num_tasks)
        ])
        self.output_layers = nn.ModuleList([
            nn.Linear(hidden_dim, 1) for _ in range(num_tasks)
        ])
    
    def forward(self, x):
        outputs = []
        for i in range(self.num_tasks):
            h = torch.relu(self.layers[i](x))
            y = self.output_layers[i](h)
            outputs.append(y)
        return outputs
    
    def regularization_loss(self):
        """Compute parameter similarity regularization"""
        # Compute mean parameters
        mean_weights = sum([self.layers[i].weight for i in range(self.num_tasks)]) / self.num_tasks
        
        reg_loss = 0
        for i in range(self.num_tasks):
            reg_loss += torch.norm(self.layers[i].weight - mean_weights) ** 2
        
        return self.lambda_reg * reg_loss

# Training
model = SoftSharingMTL(input_dim=50, hidden_dim=100, num_tasks=3, lambda_reg=0.1)

for epoch in range(100):
    outputs = model(X_batch)
    
    # Task losses
    task_losses = [criterion(outputs[i], y_batch[i]) for i in range(3)]
    total_loss = sum(task_losses) + model.regularization_loss()
    
    optimizer.zero_grad()
    total_loss.backward()
    optimizer.step()

### Lab 3: Uncertainty Weighting

class UncertaintyWeightedMTL(nn.Module):
    def __init__(self, num_tasks):
        super().__init__()
        self.log_vars = nn.Parameter(torch.zeros(num_tasks))
    
    def forward(self, task_losses):
        """Compute weighted loss with learned task variances"""
        loss = 0
        for i, task_loss in enumerate(task_losses):
            precision = torch.exp(-self.log_vars[i])
            loss += precision * task_loss + 0.5 * self.log_vars[i]
        return loss

# Training
model_mtl = HardSharingMTL(...)
uncertainty_model = UncertaintyWeightedMTL(num_tasks=3)
optimizer = torch.optim.Adam(list(model_mtl.parameters()) + 
                            list(uncertainty_model.parameters()), lr=0.001)

for epoch in range(100):
    outputs = model_mtl(X_batch)
    
    # Compute task losses
    loss_1 = criterion_1(outputs[0], y1_batch)
    loss_2 = criterion_2(outputs[1], y2_batch)
    loss_3 = criterion_3(outputs[2], y3_batch)
    
    total_loss = uncertainty_model([loss_1, loss_2, loss_3])
    
    optimizer.zero_grad()
    total_loss.backward()
    optimizer.step()

### Lab 4: Task-Weighted Ensemble

def evaluate_per_task_performance(model, test_loaders, metrics):
    """Evaluate MTL model performance per task"""
    results = {}
    
    for task_id, (loader, metric) in enumerate(zip(test_loaders, metrics)):
        task_perf = 0
        count = 0
        
        for x_batch, y_batch in loader:
            outputs = model(x_batch)
            task_output = outputs[task_id]
            task_perf += metric(task_output, y_batch).item()
            count += 1
        
        results[f'task_{task_id}'] = task_perf / count
    
    return results

def adjust_task_weights(results, current_weights):
    """Adjust weights based on performance"""
    # Lower weight if task performing well, increase if struggling
    new_weights = {}
    
    for task, perf in results.items():
        if perf > 0.9:  # Good performance
            new_weights[task] = current_weights[task] * 0.9
        elif perf < 0.7:  # Poor performance
            new_weights[task] = current_weights[task] * 1.1
        else:
            new_weights[task] = current_weights[task]
    
    # Normalize
    total = sum(new_weights.values())
    return {k: v/total for k, v in new_weights.items()}

Go deeper with CFSGPT

Get AI-powered deep-dives, save terms, and run advanced simulations — free account.

Create Free Account