Bi-encoder training
Properties
(Source: ChatGPT51)
You’re right that in a cross-encoder, it’s easy to see how the error is propagated: the model jointly encodes $(q,d)$ into one forward pass, produces a similarity score (or classification probability), and directly backpropagates the loss.
The bi-encoder case feels more mysterious because query and document are processed separately, and the network never literally “sees them together” before the similarity computation. But the key is:
# 1. Architecture Recap
A bi-encoder has two encoders:
- $f_\theta(q) \to \mathbf{q} \in \mathbb{R}^d$
- $g_\theta(d) \to \mathbf{d} \in \mathbb{R}^d$
In many cases, $f$ and $g$ share weights ($f=g$), but they don’t have to.
Similarity is computed via some differentiable function $s(\mathbf{q},\mathbf{d})$, often:
$$
s(\mathbf{q},\mathbf{d}) = \cos(\mathbf{q},\mathbf{d}) \quad \text{or} \quad s(\mathbf{q},\mathbf{d}) = \mathbf{q}^\top \mathbf{d}
$$
# 2. Training Objective
The bi-encoder is typically trained with pairwise or contrastive loss functions.
Two common ones:
# a. Binary classification loss (pairwise)
Given $(q,d^+)$ as a positive pair and $(q,d^-)$ as a negative pair, the loss could be:
$$
\mathcal{L} = - \log \sigma(s(\mathbf{q}, \mathbf{d}^+)) - \log (1 - \sigma(s(\mathbf{q}, \mathbf{d}^-)))
$$
Here, $s(\mathbf{q},\mathbf{d})$ is differentiable w.r.t. both $\mathbf{q}$ and $\mathbf{d}$.
# b. Softmax-based contrastive loss (InfoNCE)
With a batch of $B$ positive pairs $(q_i,d_i)$, treat all other $d_j, j \neq i$ as negatives:
$$
\mathcal{L} = -\frac{1}{B} \sum_{i=1}^B \log \frac{\exp(s(\mathbf{q}_i, \mathbf{d}i) / \tau)}{\sum{j=1}^B \exp(s(\mathbf{q}_i, \mathbf{d}_j) / \tau)}
$$
# 3. How the Error Propagates
Let’s say $s(\mathbf{q},\mathbf{d}) = \mathbf{q}^\top \mathbf{d}$.
For a single pair $(q,d)$:
$$
\frac{\partial s}{\partial \mathbf{q}} = \mathbf{d} \quad \text{and} \quad \frac{\partial s}{\partial \mathbf{d}} = \mathbf{q}
$$
Now, apply the chain rule through the loss:
Forward pass:
- Encode $q \to \mathbf{q}$
- Encode $d \to \mathbf{d}$
- Compute score $s(\mathbf{q},\mathbf{d})$
- Compute loss $\mathcal{L}$
Backward pass:
Loss gives $\frac{\partial \mathcal{L}}{\partial s}$
That flows into $\frac{\partial s}{\partial \mathbf{q}}$ and $\frac{\partial s}{\partial \mathbf{d}}$
Then into each encoder separately:
$$
\frac{\partial \mathcal{L}}{\partial \theta_q} = \frac{\partial \mathcal{L}}{\partial \mathbf{q}} \cdot \frac{\partial \mathbf{q}}{\partial \theta_q}
$$$$
\frac{\partial \mathcal{L}}{\partial \theta_d} = \frac{\partial \mathcal{L}}{\partial \mathbf{d}} \cdot \frac{\partial \mathbf{d}}{\partial \theta_d}
$$If weights are shared, the gradients from both flows are summed before updating.
# 4. Why This Works Without Joint Encoding
The model doesn’t see the pair concatenated, but it does receive a gradient signal for:
- Pushing query and positive doc vectors closer (higher dot product / cosine)
- Pulling apart query and negative doc vectors
This is essentially metric learning:
The encoders learn embeddings such that semantically similar texts live nearby in vector space.
The cross-encoder optimizes direct classification; the bi-encoder optimizes representation space geometry.
# 5. Example in Pseudocode (PyTorch)
| |
Here:
logits[i, j]= similarity between $q_i$ and $d_j$- Gradient flows from
loss→logits→ bothq_vecsandd_vecs→ encoder weights.
Prompts: “How is the bi-encoder fine-tuned to align it to produce correct similarity scores? I understand it for the cross-encoder since the model sees both the query and the document. I do not understand how is the error propagated in the bi-ecnoder. I am CS graduate so you can be technical.”, “Same output but fixed the mathematical notations with $ dollar sign without spaces around so it renders in my markdown.” ↩︎