Paper · 2022-07-16 · cs.CV
TS2-Net: Token Shift and Selection Transformer for Text-Video Retrieval
TS2-Net adapts a CLIP ViT-B/32 video encoder. In its last two blocks, whole tokens are shifted between adjacent frames, and a learned perturbed top-k keeps a few patch tokens per frame. Frame similarities are softmax-weighted and trained with a symmetric contrastive loss. Author-reported R@1 47.0 on MSR-VTT 1k-A with ViT-B/32, 49.4 with ViT-B/16.
Cross-modal retrievalVision Transformer (ViT) backbonesContrastive learningToken pruningvideotext-video-retrievaltoken-selectionvision-transformercontrastive-learning
arXivPDFCodeMarkdown17 min read
AI-compiled by claude-haiku-5-5 on 2026-11-09, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
Text-video search means finding the videos that match a sentence. A video model has to notice both small objects, such as a hat, and small movements, such as someone talking. Many current models keep the image backbone's fixed structure and only combine per-frame summaries, so these details get lost.
TS2-Net changes how the image transformer handles frames. In its last two layers, each frame borrows whole patch tokens from its neighbouring frames, so the model can see how things move over time without losing the content of any patch. Each frame also keeps only a few of its most informative patches, which cuts the repeated background that would otherwise dominate the result. The sentence is compared with each frame, and the frame scores are combined into one match score. The model is trained so that matching pairs score higher than mismatched ones, and the authors report new best results on five common benchmarks.
TL;DR
- Video encoder: CLIP ViT-B/32. Token shift in blocks 11 and 12 moves whole tokens between adjacent frames; a learned top-k keeps the [CLS] token plus K patch tokens per frame. §3.1 §3.2
- Matching: per-frame cosine similarities, combined with softmax weights (λ = 4). Trained with a symmetric contrastive loss. §3.3
- Author-reported: R@1 47.0 on MSR-VTT 1k-A (ViT-B/32), 49.4 with ViT-B/16. Five benchmarks, with claimed records on MSR-VTT, VATEX, LSMDC, ActivityNet and DiDeMo. tab. 4 tab. 5
- The released code differs from the paper in several places (selection score, K on LSMDC, shift ratio, frame normalisation, frame aggregator, and the initialisation of that aggregator). See Paper vs code.
Problem
Text-video retrieval ranks candidate videos for a text query. The paper argues that video encoders built on pre-trained vision backbones keep a fixed structure, so they cannot be adapted to produce finer spatial-temporal features §1. Encoders that use only the [CLS] token miss small objects, and encoders that use every token of every frame suffer from redundancy, where background tokens dominate the video representation §3.2. The paper targets subtle movements and small objects, which the examples in fig. 1 illustrate §1.
Method
The model has three parts: a text encoder, a video encoder and a matching step §3. The text encoder is a 12-layer transformer that the paper calls GPT and that is initialised from CLIP §3 §4.1. The query vector q is the output at an [EOS] token appended to the query §3.
Video encoder, token shift. The backbone is a 12-layer ViT-B/32 §4.1. The shift is applied in layers 11 and 12 §4.2 tab. 1. It is parameter-free: in the chosen layers, some tokens of each frame are replaced by the same-position tokens of the previous frame, and others by those of the next frame, so the shift runs in both directions §3.1. Shifting whole tokens rather than channel slices is the paper's main departure from earlier shift variants §3.1 fig. 3 tab. 2. The paper places a residual connection between the original and the shifted features §3.1. The shift ratio that works best is 25% of tokens tab. 1.
Video encoder, token selection. Per frame, an MLP reduces the channels from C to C/2. The reduced [CLS] feature is concatenated to every token, and a second MLP followed by softmax gives an importance score to each of the N+1 tokens (eq. 1) §3.2. The K most informative tokens are taken with an indicator matrix M, so the selected tokens are M transposed times the token matrix (eq. 2) §3.2. Top-k is not differentiable, so the paper uses the perturbed maximum with Gaussian noise: ε = 0.05 and m = 500 samples. Eq. 3 is the optimisation form of the top-k. The forward and backward estimators are not in the compiled paper: they sit in a commented-out block §3.2. The selected tokens of all frames go to a joint spatial-temporal transformer. The paper says each frame is represented by its most informative token §3.2.
Matching. Each frame gets a cosine similarity s_i to the query (eq. 4) §3.3. The score of a video is the sum of the frame similarities, weighted by softmax(λ s_i) with λ = 4 (eq. 5) §3.3.
Training. For a batch of B pairs, the loss is the average of a text-to-video and a video-to-text cross-entropy over the score matrix, scaled by a trainable temperature τ (eq. 6 to eq. 8) §3.3. At test time, videos are ranked by the score §3.3.
Pseudocode for the three components. It follows the paper's text; where the released code differs, the line is marked and listed under Paper vs code.
# Token shift on the attention input of ViT blocks 11 and 12 [§3.1] [tab. 1]
# tokens: (T, N+1, C) for one video; index 0 is the [CLS] token
# The shifted tensor feeds the attention call only; the block adds back its
# unshifted input [code: modules/module_clip.py#L251-L253]
def token_shift(tokens):
# Stride between shifted positions, derived from the token count
# (no ratio parameter) [code: modules/until_module.py#L360]
stride = (N + 1) // 7
shifted = copy(tokens)
# Each frame takes whole tokens from the previous frame; [CLS] (index
# 0) is never chosen [code: modules/until_module.py#L382-L383]
left = range(1, N + 1, stride)
shifted[1:, left] = tokens[:-1, left]
# Each frame takes whole tokens from the next frame, starting at
# position 4 [code: modules/until_module.py#L384-L385]
right = range(4, N + 1, stride)
shifted[:-1, right] = tokens[1:, right]
# Edge frames keep their own tokens; nothing is zero-padded
# [code: modules/until_module.py#L362-L363]
return shifted# Per-frame token selection with [CLS] always kept [§3.2] [eq. 1] [eq. 2]
# tokens: (T, N+1, C); index 0 is [CLS]; k = 3 patches in the released code
def select_tokens(tokens, k):
# Channel reduction C -> C/2 (the code adds a LayerNorm first)
# [code: modules/differential_topk.py#L66-L70]
reduced = reduce_channels(tokens)
# Append the reduced [CLS] feature to every token [eq. 1]
# [code: modules/differential_topk.py#L93]
joined = concat(reduced, reduced[:, :1])
# One score per token; the paper uses softmax, the code uses tanh
# [code: modules/differential_topk.py#L78]
scores = tanh(score_mlp(joined))
# Soft selection of k patch tokens; [CLS] gets no score
# [code: modules/differential_topk.py#L116-L117]
indicator = perturbed_top_k(scores[:, 1:], k) # (T, k, N)
# Output [CLS] followed by the selected patch tokens [eq. 2]
# [code: modules/differential_topk.py#L120-L127]
picked = indicator @ tokens[:, 1:]
return concat(tokens[:, :1], picked) # (T, k+1, C)# Perturbed top-k: soft one-hot selection under Gaussian noise [§3.2]
# Backward (Jacobian estimate): mean over draws of (one-hot times noise),
# divided by sigma [§3.2] [code: modules/differential_topk.py#L44-L58]
# scores: (T, N) patch scores; defaults are the paper's values
def perturbed_top_k(scores, k, draws=500, sigma=0.05):
# m = 500 noise vectors per frame [code: modules/differential_topk.py#L24]
noise = normal(0, 1, shape=(T, draws, N))
# Perturb the scores and keep the k largest in each draw
# [code: modules/differential_topk.py#L25-L28]
noisy = scores[:, None, :] + sigma * noise
indices = sort(topk(noisy, k))
# Average the one-hot picks over the draws
# [code: modules/differential_topk.py#L30-L31]
return mean(one_hot(indices, N), over_draws) # (T, k, N)# Frame similarities and the text-video score [§3.3] [eq. 4] [eq. 5]
# query: (C,) text vector; frames: (T, C), T <= 12 on the MSR-VTT setting
def video_score(query, frames, lam=4.0):
# Cosine similarity of the query with every frame [eq. 4]
sims = cosine(frames, query) # (T,)
# Released code divides by the L2 norm over frames before the softmax
# [code: modules/modeling.py#L561] (the paper's weights use sims directly)
sims_scaled = sims / norm(sims)
# Padded frames get weight zero [code: modules/modeling.py#L562]
# Frame weights: softmax of lam times the scaled similarities
# [code: modules/modeling.py#L563]
weights = softmax(lam * sims_scaled)
# Score: weighted sum of frame similarities [eq. 5]
return sum(weights * sims)# Symmetric contrastive loss over a batch of B pairs [§3.3] [eq. 6-8]
# sims: (B, B) video scores; diagonal entries are the matching pairs
def retrieval_loss(sims, scale):
# scale = exp(learned logit_scale), the paper's trainable tau
# [code: modules/modeling.py#L569]
logits = scale * sims
# Text-to-video: row-wise softmax, cross-entropy on the diagonal [eq. 6]
# [code: modules/until_module.py#L187-L191]
loss_t2v = mean(-log_softmax(logits, dim=1).diagonal())
# Video-to-text: the same on the transposed matrix [eq. 7]
loss_v2t = mean(-log_softmax(logits.T, dim=1).diagonal())
# Average of the two directions [eq. 8]
return (loss_t2v + loss_v2t) / 2Hyperparameters
| Name | Value | Where | Notes |
|---|---|---|---|
| Text encoder layers | 12 | §4.1 | Paper calls it GPT; the code uses the CLIP text transformer module_clip.py |
| Video backbone | CLIP ViT-B/32, 12 layers | §4.1 | ViT-B/16 variant only in tab. 4 and tab. 6 |
| Embedding dimension | 512 | §4.1 | Text and frame embeddings |
| Token shift blocks | 11 and 12 (1-indexed) | §4.2 tab. 1 | Code: 0-indexed blocks 10 and 11 until_module.py |
| Token shift ratio | 25% (best of 12.5, 25, 50) | tab. 1 | Not a parameter in the code; see Paper vs code |
| Token selection layers | 4 | §4.1 | Matches the scripts' cross_num_hidden_layers 4 (see Paper vs code) |
| Selected tokens K | 4 for MSR-VTT, VATEX, ActivityNet, DiDeMo; 1 for LSMDC | §4.1 tab. 3 | Released code: fixed top-3 patches plus [CLS] on all datasets modeling.py |
| Perturbation ε (σ) | 0.05 | §3.2 | differential_topk.py |
| Noise samples m | 500 | §3.2 | differential_topk.py |
| Frame softmax λ | 4 | §3.3 | Code applies 4 after dividing by the L2 norm modeling.py |
| Temperature τ | trainable | §3.3 | Code: exp of CLIP's logit_scale modeling.py |
| Optimizer | Adam | §4.1 | Code: BertAdam, betas (0.9, 0.98), eps 1e-6 main_task_retrieval.py |
| Learning rate, CLIP parameters (text encoder, token shift) | 1e-7 | §4.1 | Code: base lr 1e-4 times coef_lr 1e-3 main_task_retrieval.py [scripts/run_msrvtt.sh] |
| Learning rate, token selection and other modules | 1e-4 | §4.1 | Code: base lr main_task_retrieval.py |
| Warmup | proportion not stated (paper cites Goyal et al.) | §4.1 | Code default 10% main_task_retrieval.py |
| LR schedule after warmup | not stated | — | Code: warmup then cosine optimization.py |
| Weight decay | not stated | — | Code: 0.2 on non-bias parameters main_task_retrieval.py |
| Gradient clipping | not stated | — | Code: max norm 1.0 main_task_retrieval.py |
| Batch size | 128 | §4.1 | Scripts: 128 [scripts/run_msrvtt.sh] [scripts/run_vatex.sh] |
| Epochs | not stated | — | Scripts: 5 for MSR-VTT and VATEX [scripts/run_msrvtt.sh] |
| Max query words | 32 (MSR-VTT, VATEX, LSMDC); 64 (ActivityNet, DiDeMo) | §4.1 | Scripts: 32 |
| Max frames | 12 (MSR-VTT, VATEX, LSMDC); 64 (ActivityNet, DiDeMo) | §4.1 | Scripts: 12. No ActivityNet or DiDeMo script in the repository |
| Frame sampling | not stated | — | Scripts: 1 fps, then uniform sampling to the frame limit [scripts/run_msrvtt.sh] dataloader_msrvtt_retrieval.py |
| Random seed | not stated | — | Code default 42 main_task_retrieval.py |
| Data, MSR-VTT | 1k-A: 9,000 training videos, 1,000 test pairs | §4.1 | Training uses all captions per video (--expand_msrvtt_sentences) |
| Data, VATEX | HGR split: 25,991 train, 1,500 val, 1,500 test | §4.1 | |
| Data, LSMDC | about 100k train, 7,408 val, 1,000 test; test movies disjoint | §4.1 | |
| Data, ActivityNet | train split for training, val1 for testing; paragraph queries | §4.1 | |
| Data, DiDeMo | 8,395 train, 1,065 val, 1,004 test | §4.1 | Paragraph queries |
Results
Author-reported. Metrics: R@1, R@5, R@10, median rank (MdR) and mean rank (MnR); rsum sums the six R@K values. Unless noted, the paper's own model uses CLIP ViT-B/32 and no inverted softmax tab. 4 tab. 5.
MSR-VTT 1k-A test set, tab. 4. Columns: text-to-video R@1, R@5, R@10, MdR, MnR; video-to-text R@1, R@5, R@10, MdR, MnR.
| Method | T→V R@1 | R@5 | R@10 | MdR | MnR | V→T R@1 | R@5 | R@10 | MdR | MnR |
|---|---|---|---|---|---|---|---|---|---|---|
| CE | 20.9 | 48.8 | 62.4 | 6.0 | 28.2 | 20.6 | 50.3 | 64.0 | 5.3 | 25.1 |
| TACo | 26.7 | 54.5 | 68.2 | 4.0 | - | - | - | - | - | - |
| MMT | 26.6 | 57.1 | 69.6 | 4.0 | 24.0 | 27.0 | 57.5 | 69.7 | 3.7 | 21.3 |
| SUPPORT-SET | 27.4 | 56.3 | 67.7 | 3.0 | - | 26.6 | 55.1 | 67.5 | 3.0 | - |
| TT-CE | 29.6 | 61.6 | 74.2 | 3.0 | - | - | - | - | - | - |
| T2VLAD | 29.5 | 59.0 | 70.1 | 4.0 | - | 31.8 | 60.0 | 71.1 | 3.0 | - |
| HIT-pretrained | 30.7 | 60.9 | 73.2 | 2.6 | - | 32.1 | 62.7 | 74.1 | 3.0 | - |
| Frozen | 31.0 | 59.5 | 70.5 | 3.0 | - | - | - | - | - | - |
| MDMMT | 38.9 | 69.0 | 79.7 | 2.0 | 16.5 | - | - | - | - | - |
| CLIP (ViT-B/32) | 39.7 | 72.3 | 82.2 | 2.0 | 12.8 | 11.3 | 22.7 | 29.2 | 5.0 | - |
| CLIP4Clip | 44.5 | 71.4 | 81.6 | 2.0 | 15.3 | 42.7 | 70.9 | 80.6 | 2.0 | 11.6 |
| CAMoE | 44.6 | 72.6 | 81.8 | 2.0 | 13.3 | 45.1 | 72.4 | 83.1 | 2.0 | 10.0 |
| CLIP2Video | 45.6 | 72.6 | 81.7 | 2.0 | 14.6 | 43.5 | 72.3 | 82.1 | 2.0 | 10.2 |
| TS2-Net (ViT-B/32) | 47.0 | 74.5 | 83.8 | 2.0 | 13.0 | 45.3 | 74.1 | 83.7 | 2.0 | 9.2 |
| CLIP2TV (ViT-B/16) | 48.3 | 74.6 | 82.8 | 2.0 | 14.9 | 46.5 | 75.4 | 84.9 | 2.0 | 10.2 |
| TS2-Net (ViT-B/16) | 49.4 | 75.6 | 85.3 | 2.0 | 13.5 | 46.6 | 75.9 | 84.9 | 2.0 | 8.9 |
Note: the paper says CLIP2TV uses 16×16 patches, so ViT-B/16 TS2-Net is the fair comparison tab. 4.
Text-to-video on VATEX, ActivityNet, LSMDC and DiDeMo, tab. 5. The paper's caption says QB-Norm uses inverted softmax at inference, while the other rows do not.
| Dataset | Method | R@1 | R@5 | R@10 | MdR | MeanR |
|---|---|---|---|---|---|---|
| VATEX | Dual Enc. | 31.1 | 67.5 | 78.9 | 3.0 | - |
| VATEX | HGR | 35.1 | 73.5 | 83.5 | 2.0 | - |
| VATEX | CLIP | 39.7 | 72.3 | 82.2 | 2.0 | 12.8 |
| VATEX | CLIP4Clip | 55.9 | 89.2 | 95.0 | 1.0 | 3.9 |
| VATEX | QB-Norm* | 58.8 | 88.3 | 93.8 | 1.0 | - |
| VATEX | CLIP2Video | 57.3 | 90.0 | 95.5 | 1.0 | 3.6 |
| VATEX | TS2-Net | 59.1 | 90.0 | 95.2 | 1.0 | 3.5 |
| ActivityNet | CE | 20.5 | 47.7 | 63.9 | 6.0 | 23.1 |
| ActivityNet | ClipBERT | 21.3 | 49.0 | 63.5 | 6.0 | - |
| ActivityNet | MMT-Pretrained | 28.7 | 61.4 | - | 3.3 | 16.0 |
| ActivityNet | CLIP4Clip | 40.5 | 73.4 | - | 2.0 | 7.5 |
| ActivityNet | TS2-Net | 41.0 | 73.6 | 84.5 | 2.0 | 8.4 |
| LSMDC | JSFusion | 9.1 | 21.2 | 34.1 | 36.0 | - |
| LSMDC | CE | 11.2 | 26.9 | 34.9 | 25.3 | - |
| LSMDC | Frozen | 15.0 | 30.8 | 39.8 | 20.0 | - |
| LSMDC | CLIP4Clip | 22.6 | 41.0 | 49.1 | 11.0 | 61.0 |
| LSMDC | QB-Norm* | 22.4 | 40.1 | 49.5 | 11.0 | - |
| LSMDC | CAMoE | 22.5 | 42.6 | 50.9 | - | 56.5 |
| LSMDC | TS2-Net | 23.4 | 42.3 | 50.9 | 9.0 | 56.9 |
| DiDeMo | ClipBERT | 20.4 | 48.0 | 60.8 | 6.0 | - |
| DiDeMo | TT-CE | 21.1 | 47.3 | 61.1 | 6.3 | - |
| DiDeMo | Frozen | 31.0 | 59.8 | 72.4 | 3.0 | - |
| DiDeMo | CLIP4Clip | 42.5 | 70.2 | 80.6 | 2.0 | 17.5 |
| DiDeMo | TS2-Net | 41.8 | 71.6 | 82.0 | 2.0 | 14.8 |
Summary rsum values tab. 7: MSR-VTT 205.3 (CLIP2Video 199.9); VATEX 244.3 (CLIP2Video 242.8); LSMDC 116.6 (CAMoE 116.0); DiDeMo 195.4 (CLIP4Clip 193.3); ActivityNet 199.1 (CLIP4Clip rsum not given). Author-reported.
Inverted softmax (appendix A), tab. 6. Text-to-video only, reported separately from the main tables.
| Dataset | Method | R@1 | R@5 | R@10 | MdR | MeanR |
|---|---|---|---|---|---|---|
| MSRVTT-1kA | QB-Norm | 47.2 | 73.0 | 83.0 | 2.0 | - |
| MSRVTT-1kA | CAMoE | 47.3 | 74.2 | 84.5 | 2.0 | 11.9 |
| MSRVTT-1kA | TS2-Net | 51.1 | 76.9 | 85.6 | 1.0 | 11.7 |
| MSRVTT-1kA | CLIP2TV | 52.9 | 78.5 | 86.5 | 1.0 | 12.8 |
| MSRVTT-1kA | TS2-Net (ViT16) | 54.0 | 79.3 | 87.4 | 1.0 | 11.7 |
| DiDeMo | QB-Norm | 43.5 | 71.4 | 80.9 | 2.0 | - |
| DiDeMo | CAMoE | 43.8 | 71.4 | 79.9 | 2.0 | 16.3 |
| DiDeMo | TS2-Net | 47.4 | 74.1 | 82.4 | 2.0 | 12.9 |
Ablations on MSR-VTT 1k-A (ViT-B/32), tab. 1 to tab. 3. Columns: T→V R@1, R@5, R@10, MnR; V→T R@1, R@5, R@10, MnR; rsum. The baseline removes both modules §4.2.
Token shift layers and ratio, tab. 1:
| Layers | Ratio | T→V R@1 | R@5 | R@10 | MnR | V→T R@1 | R@5 | R@10 | MnR | rsum |
|---|---|---|---|---|---|---|---|---|---|---|
| none (baseline) | - | 45.4 | 74.3 | 82.7 | 13.6 | 44.5 | 72.3 | 82.3 | 9.8 | 401.5 |
| 1-12 | 25% | 42.8 | 71.2 | 80.9 | 14.4 | 43.2 | 70.3 | 80.4 | 11.3 | 388.8 |
| 3-12 | 25% | 44.1 | 71.0 | 81.8 | 14.5 | 43.5 | 71.2 | 81.8 | 10.8 | 393.4 |
| 5-12 | 25% | 44.4 | 71.9 | 81.6 | 14.6 | 44.8 | 72.0 | 80.6 | 11.3 | 395.3 |
| 7-12 | 25% | 44.1 | 72.3 | 82.9 | 13.6 | 43.8 | 72.3 | 82.1 | 10.3 | 397.5 |
| 9-12 | 25% | 45.2 | 73.8 | 83.1 | 13.4 | 45.3 | 72.1 | 82.5 | 9.5 | 402.0 |
| 11-12 | 12.5% | 46.0 | 73.3 | 82.2 | 13.8 | 45.8 | 72.9 | 83.0 | 9.5 | 403.2 |
| 11-12 | 50% | 46.1 | 74.5 | 83.3 | 13.3 | 45.6 | 72.9 | 82.2 | 9.5 | 404.6 |
| 11-12 | 25% | 46.2 | 73.9 | 83.8 | 13.0 | 45.6 | 73.5 | 83.2 | 9.3 | 406.2 |
Shift variants, tab. 2 (Channel, [VIS] and [CLS] Channel Shift come from Shift-ViT):
| Method | T→V R@1 | R@5 | R@10 | MnR | V→T R@1 | R@5 | R@10 | MnR | rsum |
|---|---|---|---|---|---|---|---|---|---|
| Baseline | 45.4 | 74.3 | 82.7 | 13.6 | 44.5 | 72.3 | 82.3 | 9.8 | 401.5 |
| Channel Shift | 45.6 | 73.6 | 83.1 | 13.7 | 45.0 | 73.2 | 82.7 | 9.7 | 403.2 |
| [VIS] Channel Shift | 45.1 | 73.8 | 83.5 | 13.9 | 44.7 | 73.3 | 82.2 | 9.8 | 402.6 |
| [CLS] Channel Shift | 45.8 | 74.3 | 83.0 | 13.6 | 44.7 | 72.9 | 82.5 | 9.8 | 403.2 |
| Token Shift | 46.2 | 73.9 | 83.8 | 13.0 | 45.6 | 73.5 | 83.2 | 9.3 | 406.2 |
Token selection, tab. 3 (the first row is labelled top-K 1 in the paper):
| Setting | top-K | T→V R@1 | R@5 | R@10 | MnR | V→T R@1 | R@5 | R@10 | MnR | rsum |
|---|---|---|---|---|---|---|---|---|---|---|
| Token shift only | 1 | 46.2 | 73.9 | 83.8 | 13.0 | 45.6 | 73.5 | 83.2 | 9.3 | 406.2 |
| All tokens | 50 | 45.8 | 73.5 | 83.4 | 13.5 | 44.7 | 73.1 | 82.4 | 9.4 | 402.9 |
| Random select | 4 | 46.4 | 73.9 | 83.5 | 13.1 | 45.1 | 73.5 | 82.1 | 9.5 | 404.5 |
| Select | 2 | 47.0 | 74.2 | 83.6 | 13.1 | 45.6 | 74.0 | 83.5 | 9.3 | 407.9 |
| Select | 6 | 46.6 | 74.4 | 84.3 | 13.2 | 44.5 | 73.8 | 83.2 | 9.2 | 406.8 |
| Select | 8 | 46.4 | 73.9 | 83.5 | 13.2 | 45.0 | 74.1 | 83.9 | 9.2 | 406.8 |
| TS2-Net | 4 | 47.0 | 74.5 | 83.8 | 13.0 | 45.3 | 74.1 | 83.7 | 9.2 | 408.4 |
Other reported results: on a 103-query subset of MSR-VTT with small objects or slight movements, R@1 is 79.6 against 39.8 for CLIP4Clip §4.4. Among the failure cases fig. 9, the paper attributes some errors to vague or empty annotations, which it estimates at 1 to 2% of the dataset app. C.
Paper vs code
Code: https://github.com/yuqi657/ts2_net, commit 1d9d4d53d27057f29fbdba9c4c1586dbda294519. The repository has no LICENSE file, so its code cannot be copied; only pseudocode is used here. The repository's README reports the same ViT-B/32 and ViT-B/16 numbers as the paper; we did not re-run training or evaluation [README.md#L29-L47]. Differences found:
- Selection score. The paper scores all N+1 tokens with softmax eq. 1 §3.2. The code scores patch tokens with tanh, and [CLS] is not scored differential_topk.py differential_topk.py.
- [CLS] is always kept. The paper selects the top-K of N+1 tokens, [CLS] included §3.2. The code always keeps [CLS] and adds the top 3 patch tokens modeling.py differential_topk.py. The README says this on purpose [README.md#L17].
- K on LSMDC. The paper uses K=1 for LSMDC and K=4 elsewhere §4.1. The code fixes topk=3 for every dataset with no flag modeling.py.
- Frame representation. The paper uses the most informative token of each frame §3.2. The code uses the [CLS] token modeling.py.
- Selection at inference. The paper does not say. The code always runs the 500-draw noisy top-k, since the
trainingargument of the selector is never read differential_topk.py. Scores therefore vary with the random draws unless the seed is fixed (seed 42 by default) main_task_retrieval.py. - Frame weights. The paper uses softmax(λ s_i) eq. 5 §3.3. The code divides the similarity vector by its L2 norm over frames, masks padded frames, then applies softmax(4 ·) modeling.py.
- Global branch. The paper has one loss §3.3. The code computes two similarity branches and weights the global one by zero (
frame_match_weight= 1.0) modeling.py modeling.py. By our reading, for the single [EOS] text vector the token-level softmax in the frame branch has one element and returns 1, so the two branches compute the same score modeling.py. - Frame aggregator. The paper names no module for the 4 layers on the selected tokens beyond a joint spatial-temporal transformer §3.2 §4.1. The scripts set
--sim_header seqTransfwith 4 layers, which adds frame position embeddings and a transformer over all selected tokens [scripts/run_msrvtt.sh] modeling.py. The code's default ismeanP, which skips this step main_task_retrieval.py modeling.py. - Initialisation of that aggregator. The paper says CLIP weights of similar dimension are used §4.1. The code copies the first 4 blocks of CLIP's text transformer into it, and the frame position embeddings from CLIP's text positional embedding modeling.py modeling.py.
- Shift ratio. The paper finds 25% best tab. 1. The code has no ratio: the stride is the token count divided by 7, so about 7 positions per direction are shifted until_module.py until_module.py. For the 7×7 grid of ViT-B/32 (not stated in the paper), that is 14 of 49 patches, about 29%, across both directions. For ViT-B/16 it is 14 of 196, about 7%. The 12.5% and 50% sweeps in tab. 1 cannot be reproduced with the released code.
- Edge frames. fig. 3 shows padding for shifted-out positions. The code starts the shifted tensor as a copy of the input, so edge frames keep their own tokens until_module.py.
- Shifted positions. The code never shifts [CLS] (indices start at 1) until_module.py. The paper does not say.
- Shift location. Blocks 11 and 12 (1-indexed) match §4.2 and tab. 1 until_module.py.
- Learning rates. Consistent with §4.1: parameters whose names contain "clip." get 1e-7 and the rest get 1e-4 main_task_retrieval.py. The frame aggregator is named
transformerClip, so it falls in the 1e-4 group. - Loss. Consistent with eq. 6 to eq. 8 until_module.py. Only the frame branch is weighted, as in item 7.
- Perturbed top-k constants. Consistent: ε = 0.05 and 500 samples §3.2 differential_topk.py. The code's forward and backward estimators follow the cited perturbed-optimizer method. The paper's own forward and backward formulas are in a commented-out block and absent from the compiled paper, so the code is the only source for them §3.2 differential_topk.py.
- Optimizer. Not stated in the paper. The code uses BertAdam with 10% warmup, cosine decay, weight decay 0.2, betas (0.9, 0.98), eps 1e-6 and gradient clipping at 1.0 main_task_retrieval.py optimization.py.
- Unused settings.
--lr_decay(default 0.9) is defined but not used in the training code main_task_retrieval.py. The text selector is defined but its call is commented out modeling.py modeling.py. - Missing scripts. The repository gives runs for MSR-VTT and VATEX only [README.md#L54-L62]. There are no ActivityNet, DiDeMo or LSMDC scripts, so the 64-frame and 64-word settings of §4.1 cannot be checked against code.
Pitfalls
- The 25% shift ratio is not a knob. Changing the ratio requires changing the stride rule itself until_module.py. Other ViT patch grids change the effective fraction, so results will not transfer directly.
- Edge handling. The paper's illustration uses padding for shifted-out positions fig. 3. The code keeps each edge frame's own tokens. Use the code's behaviour if you want to match the reported numbers.
- Stochastic selection at inference. The released code samples 500 noisy draws at test time too, so ranking scores can change between runs with different seeds or batch orders differential_topk.py. Fix the seed, or verify run-to-run variance before comparing with table numbers.
- Memory of the perturbed top-k. The one-hot tensor has one entry per draw, per frame, per selected token, and per patch. For 128 videos, 12 frames, 3 selected patches and 49 patches (ViT-B/32 grid, not stated in the paper), that is about 113 million float entries, or about 450 MB in fp32, kept for backward on one device differential_topk.py differential_topk.py. Larger frame counts, as in the 64-frame ActivityNet and DiDeMo settings, multiply this cost.
- λ on normalised scores. The code divides the frame similarities by their L2 norm before the temperature-4 softmax modeling.py. The effective sharpness therefore depends on the spread of the scores and on the number of valid frames. The paper's formula has no such step.
- Frame sampling is unstated. The scripts take 1 fps and sample uniformly across the video to reach 12 frames [scripts/run_msrvtt.sh] dataloader_msrvtt_retrieval.py. The paper gives only the maximum frame count §4.1.
- Initialisation source. The frame aggregator starts from CLIP's text-encoder blocks, not its visual blocks modeling.py. The paper's "similar dimension" wording does not tell you this.
- Warmup and cosine. The schedule is cos(π x) over the whole run with x the fraction of training done, not a cosine after warmup. At the end of warmup the multiplier drops from 1.0 to about 0.976 optimization.py.
- Learning-rate split. The 1e-7 rate applies only to parameters whose names contain "clip." main_task_retrieval.py. Renaming modules changes the schedule without any warning.
- Scores are author-reported. The headline records are on the paper's own benchmarks and splits. Inverted softmax is reported separately and changes the numbers tab. 6.
- Repository licence. No LICENSE file means the code cannot be copied or redistributed; use it as a reference only.
Lineage
- Builds on: CLIP ViT-B/32 and ViT-B/16 weights for initialisation §4.1 tab. 4; ViT §3.1; Shift-Transformer shift variants, which tab. 2 compares against §3.1; the temporal shift module TSM §1; CLIP4Clip, used as a mean-pooling baseline §3.2; the perturbed maximum of Berthet et al. and the backward estimators of Abernethy et al. and Cordonnier et al. §3.2.
- Compared against: CE, TACo, MMT, Frozen, MDMMT, CLIP, CLIP4Clip, CAMoE, CLIP2Video, CLIP2TV, QB-Norm and others tab. 4 tab. 5 tab. 6.
- Code basis: the repository's README says it is based on CLIP4Clip [README.md#L85].
Source and license
Adapted from “TS2-Net: Token Shift and Selection Transformer for Text-Video Retrieval” by Yuqi Liu, Pengfei Xiong, Luhui Xu, Shengming Cao, Qin Jin (arXiv:2207.07852), 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/yuqi657/ts2_net at commit 1d9d4d53d270 (none (no LICENSE file in repository)). Any quoted code is shown under that license.