wikillm

Paper · 2022-05-30 · cs.CV

Prompt-aligned Gradient for Prompt Tuning

Learns CLIP prompt vectors as CoOp does, but removes from each cross-entropy gradient the part that conflicts with the gradient of a KL term toward zero-shot CLIP predictions. Author-reported, ResNet-50, 11-dataset average: 62.61% vs CoOp 59.46% at 1 shot, 74.28% vs 73.49% at 16 shots.

Soft prompt tuningVision-language pre-trainingFew-shot learningGradient projectionprompt-tuningvision-language-modelclipfew-shotgradient-projectioncatastrophic-forgetting

arXivPDFCodeMarkdown11 min read

AI-compiled by claude-haiku-5-5 on 2026-10-19, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.

In plain words

Large image-text models such as CLIP can label a photo by comparing it with a sentence like "a photo of a dog". Learning a few extra prompt vectors from labelled examples can make this more accurate. With only a handful of examples, though, it often goes wrong: the prompt fits those few photos so closely that the model ends up worse than with the untouched sentence.

ProGrad keeps the model's own opinion in the loop. At each training step it compares the update suggested by the new examples with the update that would keep the predictions close to the original model's. Where the two point against each other, the conflicting part is removed. The prompt then learns only what does not contradict what the model already knew. The change is small: switch it off and you get ordinary prompt tuning back.

The point is that when labelled data is scarce, protecting the pretrained knowledge can hold up better than trusting the few examples fully. The authors report this on image classification benchmarks.

TL;DR

ProGrad is CoOp-style prompt tuning for CLIP with one change: the cross-entropy gradient is projected so that it never works against the gradient of a KL term that keeps predictions near zero-shot CLIP §3.2. The author-reported ResNet-50 few-shot average beats CoOp at every shot count, by the most at 1 shot (62.61% vs 59.46%) tab. 1. Official PyTorch code is released under MIT, but its base-to-new context length, its λ and its zero-shot prompts differ from the paper in places (see Paper vs code). Relevant when few-shot prompt tuning falls below zero-shot.

Problem

CLIP's zero-shot classifier uses a hand-written prompt such as "a photo of a [CLASS]" §1. Prompt tuning replaces that text with learned context vectors, as in CoOp §1 §3.1. The authors find that such tuning can lose accuracy as training continues, and with one or two shots can fall below the zero-shot baseline §1 fig. 1. Grad-CAM maps show CoOp's attention drifting onto the background, while zero-shot CLIP stays on the foreground object §1 fig. 2. Existing remedies, early stopping and data augmentation, are not specific to prompts, which the authors see as the gap §1.

The method assumes that the zero-shot prediction is a useful teacher of general knowledge, and that disagreement between the labels and that teacher is what should be suppressed §3.2.

Method

  1. Zero-shot teacher. Class probabilities come from the cosine similarity between the image feature and each class's text feature from a fixed template, scaled by CLIP's temperature §3.1 eq. 1. The teacher is computed without gradients prograd.py.
  2. Student prompt. M learnable context vectors v are placed before each class-name embedding. Only v is trained; the image and text encoders are frozen §3.1 prograd.py. The cross-entropy against the labels is L_ce eq. 2.
  3. Domain-specific direction. g_ce is the gradient of L_ce with respect to v §3.2.
  4. General-knowledge direction. L_kl is a KL-type loss between the student and the teacher probabilities, weighted by the teacher. g_kl is its gradient with respect to v eq. 3 §3.2.
  5. Projection. If g_ce and g_kl have a non-negative dot product, use g_ce unchanged. Otherwise subtract λ times the component of g_ce that lies along g_kl eq. 4 §3.2. λ = 1 removes that component fully; λ = 0 gives CoOp back §3.2.
  6. Update. Take the optimizer step with the projected gradient. The paper does not name the optimizer in §3; the training recipe uses SGD app. A2.
  7. Theory (sketch). The generalization bound combines the empirical risk, a domain-gap term, and Rademacher complexity on both domains eq. 6 §3.2. Simplifying assumptions are a large pretraining sample count and a small domain gap. Under them the paper argues that the estimation-error part of the CoOp bound is at least twice that of ProGrad eq. 8 §3.2. The paper does not measure the gap term.
Pseudocode
# ProGrad update for the context vectors v  [§3.2] [eq. 2-4]
# Input:  images (B, 3, H, W); labels (B,); context vectors v
# Output: updated v

def prograd_step(images, labels, context, lr, lam):
    # Frozen zero-shot CLIP probabilities, no gradient  [§3.1] [eq. 1]
    teacher_probs = zero_shot_probs(images)            # (B, K)
    # Prompted CLIP probabilities for the current v  [§3.1] [eq. 2]
    student_probs = prompted_probs(images, context)    # (B, K)
    # Cross-entropy gradient: the domain-specific direction  [eq. 2]
    grad_ce = gradient(cross_entropy(student_probs, labels), context)
    # KL gradient: the general-knowledge direction  [eq. 3]
    grad_kl = gradient(kl(teacher_probs, student_probs), context)
    # Keep grad_ce unless it points against grad_kl  [eq. 4]
    if dot(grad_ce, grad_kl) < 0:
        # Remove the component of grad_ce that opposes grad_kl  [eq. 4]
        scale = dot(grad_ce, grad_kl) / norm_sq(grad_kl)
        grad_ce = grad_ce - lam * scale * grad_kl
    # SGD step on the adjusted gradient  [app. A2]
    context = context - lr * grad_ce
    return context

Key equations, as written in the paper. Here g_ce = ∇_v L_ce and g_kl = ∇_v L_kl §3.2.

p_\text{zs}(\bm{w}_i \mid \bm{x}) = \frac{\exp(\langle \bm{w}_i, \bm{f}\rangle/\tau)}{\sum_{j=1}^{K}\exp(\langle \bm{w}_j, \bm{f}\rangle/\tau)} eq. 1

\mathcal{L}_\text{ce}(\bm{v}) = -\sum_i \bm{y}_i \log p(\bm{t}_i \mid \bm{x}), \quad p(\bm{t}_i \mid \bm{x}) = \frac{\exp(\langle g(\bm{t}_i), \bm{f}\rangle/\tau)}{\sum_{j=1}^{K}\exp(\langle g(\bm{t}_j), \bm{f}\rangle/\tau)} eq. 2

\mathcal{L}_\text{kl}(\bm{v}) = -\sum_i p_\text{zs}(\bm{w}_i \mid \bm{x}) \log \frac{p(\bm{t}_i \mid \bm{x})}{p_\text{zs}(\bm{w}_i \mid \bm{x})} eq. 3

\tilde{g} = \begin{cases} g_\text{ce}, & \text{if } g_\text{ce}\cdot g_\text{kl} \ge 0 \\ g_\text{ce} - \lambda\,\dfrac{g_\text{ce}\cdot g_\text{kl}}{\lVert g_\text{kl}\rVert^2}\, g_\text{kl}, & \text{otherwise} \end{cases} eq. 4

Hyperparameters

NameValueWhereNotes
BackboneResNet-50§4ResNet-101 and ViT-B/32, ViT-B/16 appear only in the robustness table tab. 2. The released ProGrad configs are RN50 only rn50.yaml
Context tokens M, few-shot16§4Same size as CoOp; ProGrad++ also trains one prompt of 16 tokens §4
Context tokens M, DG / base-to-new / cross-dataset4§4Released base-to-new scripts use 16; see Paper vs code
Trainable parameterscontext vectors v only§3.1 §3.2Other weights frozen prograd.py
Initialisationzero vectors for the leading M − k tokens; word embeddings of the hand-crafted prompt for the last kapp. A2Example given: "a photo of a" for k = 4. Released code uses the per-dataset template from app. A3 prograd.py
λ1 by default; 0.8 for 16 shots§4λ = 0 is CoOp §3.2. Released scripts use 1.0 everywhere; see Paper vs code
OptimizerSGDapp. A2Momentum and weight decay are not stated in the paper; the code uses Dassl defaults of 0.9 and 5e-4 defaults.py
Initial learning rate0.002app. A2rn50.yaml
LR schedulecosine annealing; first epoch at constant 1e-5app. A2rn50.yaml
Epochs, 10 non-ImageNet datasets50 (1 shot), 100 (2 and 4 shots), 200 (8 and 16 shots)app. A2rn50_ep50.yaml, [rn50_ep100.yaml#L18], [rn50.yaml#L18]; the README pairs them with shots README.md
Epochs, ImageNet50 at every shot countapp. A2The ProGrad folder has no ImageNet-specific config; the same three files apply
Batch size32 (CoCoOp: 1)app. A2rn50.yaml
Input pipeline224 × 224; random resized crop, random flip, normalisation with CLIP mean and std; bicubic§4 app. A2Paper defers to CoOp. Code spells it out rn50.yaml
Shots1, 2, 4, 8, 16 (few-shot); 4 (DG, base-to-new)§4
Seeds3 random seeds, averaged§4prograd.sh
KL temperaturenot stated—Code uses T = 1.0 rn50.yaml
Precisionnot stated—Code defaults to fp16 train.py
Evaluationfull test split§4
Hardwareone 2080 Ti GPU; ImageNet on one A100app. A2

Results

Author-reported. Setup: ResNet-50 unless stated; 11 datasets (ImageNet, Caltech101, OxfordPets, StanfordCars, Flowers102, Food101, FGVCAircraft, EuroSAT, UCF101, DTD, SUN397); 3 seeds averaged; full test split §4. Baselines: zero-shot CLIP with one prompt (CLIP) and with prompt ensembling (CLIP++), linear probe (LP), CoOp, CoCoOp §4. ProGrad++ uses prompt ensembling to build the teacher §4. The per-dataset and standard-deviation tables are in the appendix and are not transcribed here.

Few-shot classification, average over 11 datasets, M = 16 tab. 1:

Method0124816
CLIP58.77-----
CLIP++59.38-----
LP-37.3248.0257.2764.8870.57
CoOp-59.4662.3766.7970.0473.49
ProGrad-62.6164.9068.4571.4174.28
ProGrad++-63.0665.2868.7171.8075.03

Robustness to distribution shift, trained on ImageNet, evaluated on ImageNet-V2, -Sketch, -A and -R tab. 2:

BackboneMethodImageNet-V2-Sketch-A-R
ResNet-50CLIP58.1851.3433.3221.6556.00
ResNet-50LP41.2933.6513.0911.1826.82
ResNet-50CoOp61.3453.8132.8322.0854.62
ResNet-50CoCoOp61.0453.7132.3022.0753.60
ResNet-50ProGrad62.1754.7034.4023.0556.77
ResNet-101CLIP61.2454.8238.6628.0364.34
ResNet-101LP47.0138.4619.0916.3339.43
ResNet-101CoOp63.9956.9939.4029.5064.04
ResNet-101CoCoOp63.5956.9839.1629.0964.14
ResNet-101ProGrad64.9857.8640.5330.1365.61
ViT-B/32CLIP62.0054.7540.8229.5966.01
ViT-B/32LP46.7739.1220.3216.3239.48
ViT-B/32CoOp64.7456.5940.0331.1064.54
ViT-B/32CoCoOp64.6356.5940.7430.2764.12
ViT-B/32ProGrad65.3657.4241.7331.8966.53
ViT-B/16CLIP66.7360.8446.1347.8074.01
ViT-B/16LP54.7045.5728.2022.4744.12
ViT-B/16CoOp69.8662.8346.9048.9874.55
ViT-B/16CoCoOp70.1363.0546.4849.3673.80
ViT-B/16ProGrad70.4563.3548.1749.4575.21

Base-to-new generalization, 4 shots, averaged over 11 datasets. H is the harmonic mean of base and new accuracy tab. 3 §4:

MethodBaseNewH
CLIP61.7265.9163.64
CoOp71.9661.2665.58
CoCoOp72.2360.7765.35
ProGrad73.2965.9669.06

Comparison with knowledge distillation (LwF) and a cosine classifier, average over 11 datasets tab. 5 tab. 6:

Method124816
CoOp59.4662.3766.7970.0473.49
LwF (KD), α = 0.2561.0963.0167.7470.9073.39
LwF (KD), α = 0.561.1363.3667.1470.3472.68
LwF (KD), α = 161.5264.0766.5270.0172.01
LwF (KD), α = 260.9862.6664.9267.7868.98
LwF (KD), α = 459.5861.7662.9265.0165.42
ProGrad62.6164.9068.4571.4174.28
Cosine classifier30.5043.7453.3361.2665.00
Cosine classifier + ProGrad32.2946.1455.1862.0566.47

λ ablation on DTD and OxfordPets, accuracy by shots (1, 2, 4, 8, 16) §4, λ table:

λOxfordPets 124816DTD 124816
086.0182.2186.6385.1587.0644.3346.2254.0859.9962.89
0.287.1283.1684.8784.0086.1543.8045.2154.0260.1463.61
0.488.0983.5685.5084.0486.6744.1747.4454.3259.6563.16
0.787.7484.7086.9386.3087.9044.9347.7754.9259.2863.10
0.988.2686.4787.5287.3888.5245.7848.4655.4460.4664.28
1.088.3686.8988.0487.9189.0046.1449.7854.4357.9861.15

The paper reports a 3.2-point average gain over CoOp at 1 shot and gains of 5.1 to 9.5 points on FGVCAircraft, EuroSAT and Flowers102 at 1 shot; the unit of those per-dataset gains is not stated §4.

Paper vs code

  • Context length for base-to-new. The paper uses M = 4 for base-to-new §4. The released base-to-new scripts set NCTX=16 base2new_train_prograd.sh base2new_test_prograd.sh. The release has no DG or cross-dataset script, so those settings cannot be rerun from it.
  • λ at 16 shots. The paper sets λ = 0.8 at 16 shots §4. Every released script sets LAMBDA=1.0 prograd.sh, and the default is 1.0 train.py. No 16-shot override exists. The λ table has no 0.8 row §4, λ table.
  • ProGrad++. The paper builds ProGrad++'s general direction from prompt ensembling §4. trainers/prograd.py builds its teacher from a single template prograd.py. Ensembling appears only in the zero-shot CLIP++ baseline zsclip.py. ProGrad++ cannot be rerun from this release as written.
  • Zero-shot templates differ between trainers. The paper's prompt table lists "a type of aircraft, a photo of a {}" for FGVCAircraft and "a texture of {}" for DTD app. A3. trainers/prograd.py matches this prograd.py. trainers/zsclip.py uses "a photo of a {}, a type of aircraft." and "{} texture." zsclip.py. Which file produced the "CLIP" row is not stated, so the baseline depends on the trainer used.
  • KL form. Eq. 3 is a KL-type expression eq. 3. The code computes the cross-entropy between teacher and student probabilities, multiplied by T² prograd.py. The two differ only by the teacher's entropy, which does not depend on v, so the gradients with respect to v are identical. The logged kl_loss is not a KL value.
  • Projection granularity. Eq. 4 projects one vector eq. 4. The code runs the sign test and projection per parameter tensor trainer.py. Only one tensor is trained (the context vectors), so the two are the same here.
  • Backbones. The robustness table has ResNet-101 and ViT rows tab. 2. The release has only RN50 configs .
  • Consistent with the paper. Optimizer, initial learning rate, cosine schedule, warm-up at 1e-5, epochs by shot count, batch size 32, and initialisation from zeros plus the template embedding app. A2 match rn50.yaml and prograd.py. Also consistent: the projection formula eq. 4 and its λ = 0 limit.

Pitfalls

  • The teacher sets the ceiling. The projection can only protect what the zero-shot prompt already gets right. The paper's failure analysis finds that many ProGrad failures are also misclassified by zero-shot CLIP [§4, failure cases] fig. 5. An oracle general direction gives a stronger model §4 fig. 7. A poor hand-written prompt therefore caps the method.
  • Batch reduction is not stated. Eqs. 2 and 3 are per-sample sums; the code averages both losses over the batch prograd.py prograd.py. The projection is unchanged by scaling g_kl, so only the cross-entropy scale changes the step size. Changing the reduction changes the effective learning rate of the CE term.
  • Cost. Each step runs a no-grad zero-shot forward pass and two backward passes, one for the KL term and one for the CE term prograd.py. The Dassl routine keeps the KL graph (retain_graph=True) and clones the KL gradients trainer.py. The paper calls the method efficient but gives no timing or memory figures §3.2.
  • The temperature is CLIP's. The paper's τ is CLIP's learned temperature §3.1. The code uses CLIP's logit scale exponentiated prograd.py. It is not a separate hyperparameter.
  • Initialisation template. The initialisation example in app. A2 is "a photo of a". The released code and app. A3 use a per-dataset template prograd.py. Use the per-dataset template, as the code does.
  • Unstated optimizer details. The paper gives no momentum or weight decay. The code's Dassl defaults are listed under Hyperparameters.
  • Precision. The code defaults to fp16 CLIP weights train.py. The paper does not state precision.
  • Theory is a sketch. The bound eq. 6 and its approximation eq. 7 drop the pretraining estimation term and assume a small domain gap. The gap term is an integral probability metric that the paper does not estimate §3.2.
  • The λ evidence is thin. The λ ablation covers only DTD and OxfordPets, and its effects are mixed: DTD at 16 shots is best at λ = 0.9, while OxfordPets at 16 shots is best at λ = 1.0 §4, λ table. The 16-shot λ = 0.8 setting in the main results is not covered by it.

Lineage

  • Builds on: CoOp, the learned context-vector prompt method whose training recipe ProGrad reuses §3.1 app. A2. The code is built on CoOp and Dassl readme.md.
  • Compared against: zero-shot CLIP and CLIP++, linear probe, CoOp, CoCoOp, learning without forgetting (LwF, knowledge distillation), and a cosine classifier §4. CLIP-Adapter results are in the appendix §4.
  • Superseded by: not stated in the paper or the release.

Source and license

Adapted from “Prompt-aligned Gradient for Prompt Tuning” by Beier Zhu, Yulei Niu, Yucheng Han, Yue Wu, Hanwang Zhang (arXiv:2205.14865), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/BeierZhu/Prompt-align at commit 1b762036cbe9 (MIT). Any quoted code is shown under that license.