- The idea is in the wrong example. For each training image, RRG-DPO retrieves the 20 reports that read most like the true one, keeps the one whose findings disagree most, and trains the model to prefer the true report. No LLM writes or judges anything.
- It mostly recovers missed findings. Recall rises in all six models and significantly in 5 of the 6. Precision rises significantly in 3 of the 6, and by about a point or less in the other three.
- The model with the highest F1 barely moves. PromptMRG goes from 0.476 to 0.483 with no significant change on any metric. The paper’s “state of the art” claim is that row.
- The same labeller picks the pairs and scores the result. CheXbert, a chest X-ray labeller, does both, and by the paper’s description on CT reports too. RadGraph and RaTEScore, which it doesn’t control, move in the same direction, mostly by much less.
01What the experiment measures
Report generators are trained to reproduce the reference report word by word, so a model can write fluent reports and still miss findings or invent them. The usual fixes are reinforcement learning on a clinical reward, or preference training on pairs that an LLM writes or grades.
Direct preference optimisation (DPO) needs neither a reward model nor sampling: give the model an image, a preferred report and a dispreferred one, and raise the probability of the first relative to the second, measured against a frozen copy of the starting model. The open question is where the dispreferred report comes from. RRG-DPO’s answer is the training set itself.
The experiment is clean in one respect: each of six existing generators is first trained as usual (official code; the released checkpoint for EKAGen; a re-implementation for 3D-CT-GPT), then only its text decoder is fine-tuned for three more epochs with the new objective, and the model is scored before and after. Four models write chest X-ray reports (MIMIC-CXR, 3,858 test samples) and two write chest CT reports (CT-RATE, 1,204 test volumes).
(y_w)
14 labels compared
(y_l+)
one V100 GPU
02The pair and the loss, as code
# our reconstruction of Section 2 and Eq. 2, not the authors' code
def pick_wrong_report(i, emb, labels, k=20):
near = top_k(cosine(emb[i], emb), k) # look-alikes
dist = [jaccard_dist(labels[i], labels[j]) for j in near]
return near[argmax(dist)] # disagrees most
def rrg_dpo_loss(lp, ref, beta=0.3, lam=1.0, gamma=0.1):
# lp[.]: log-likelihood under the model being trained
# ref[.]: the same under the frozen starting model
# w: true report, l: wrong report, l+: l minus conflicts
margin = (lp["w"] - ref["w"]) - (lp["l"] - ref["l"])
keep_w = relu(ref["w"] - lp["w"]) # DPOP term
keep_l = relu(ref["l+"] - lp["l+"]) # the new term
z = beta * margin - lam * keep_w - gamma * keep_l
return -logsigmoid(z).mean()
Plain DPO is the margin alone. DPOP, an existing variant, adds keep_w: a penalty when the true report becomes less likely than it was. RRG-DPO adds keep_l: most sentences of the wrong report would be fine for this image, so pushing the whole report down also pushes down correct content. Eq. 2 as printed applies β to the margin only; DPOP’s own formula and the released code apply it to the penalties too.
03Which wrong report? The ablation
Show all nine metrics as a table
| Row | Pre | Rec | F1 | BL-4 | MTR | RG-L | RadGraph | RadCliQ ↓ | RaTEScore |
|---|---|---|---|---|---|---|---|---|---|
| (a) EKAGen as released | 0.456* | 0.367* | 0.380* | 0.119 | 0.156* | 0.286 | 0.201* | 2.685* | 0.487* |
| (b) DPO, random wrong report | 0.413* | 0.360* | 0.359* | 0.082* | 0.154* | 0.256* | 0.142* | 3.023* | 0.474* |
| (c) DPO, random one of the 20 look-alikes | 0.445* | 0.425* | 0.407* | 0.101* | 0.163 | 0.277* | 0.186* | 2.750* | 0.478* |
| (d) DPO, the look-alike that disagrees most | 0.441* | 0.459* | 0.420* | 0.094* | 0.168 | 0.267* | 0.196* | 2.713* | 0.496 |
| (e) = (d) + keep the true report likely | 0.455 | 0.467* | 0.427* | 0.107 | 0.172 | 0.283 | 0.200* | 2.687 | 0.496 |
| (f) = (e) + keep the harmless sentences | 0.462 | 0.487 | 0.443 | 0.112 | 0.166 | 0.286 | 0.207 | 2.674 | 0.502 |
* differs from row (f) at p < 0.05 (Wilcoxon signed-rank), as printed.
- With plain DPO, random negatives make this model worse on all nine metrics. Precision falls from 0.456 to 0.413 and RadGraph from 0.201 to 0.142. This is one model, with no repeat reported; a later preprint (Random DPO, arXiv:2509.21351) reports gains from random reports on three other generators, so “random hurts” is this table’s result, not a law.
- Most of the recall comes from the choice of pair, with plain DPO. A random look-alike takes recall to 0.425, the most conflicting look-alike to 0.459: that is 0.092 of the final 0.120 gain, about three quarters derived.
- The loss terms are there for precision and wording. Precision stays below the released model in every row but the last, and so does ROUGE-L. DPOP (e) closes most of the precision gap, 0.441 to 0.455; the full objective brings both back to the starting level, and recall to 0.487. The new term’s own share, (e) to (f), is +0.020 recall, starred, and +0.007 precision, not starred.
One inconsistency our observation: the paper’s Tables 1 and 2 both test the released model against the full method. Table 1 prints EKAGen’s precision change, 0.456 to 0.462, without a significance star; Table 2 stars it, and the same goes for METEOR. One of the two is off. The change is small, 0.006, but by Table 2 precision would be significant in four models, not three.
04Six models, before and after
Show the six clinical metrics as a table
| Model | Precision | Recall | F1 | RadGraph | RadCliQ ↓ | RaTEScore |
|---|---|---|---|---|---|---|
| R2Gen (X-ray) | 0.405* from 0.347 | 0.364* from 0.258 | 0.360* from 0.276 | 0.197* from 0.172 | 2.714* from 2.788 | 0.505* from 0.483 |
| EKAGen (X-ray) | 0.462 from 0.456 | 0.487* from 0.367 | 0.443* from 0.380 | 0.207* from 0.201 | 2.674* from 2.685 | 0.502* from 0.487 |
| PromptMRG (X-ray) | 0.508 from 0.504 | 0.516 from 0.507 | 0.483 from 0.476 | 0.191 from 0.190 | 2.731 from 2.733 | 0.488 from 0.484 |
| R2GenGPT (X-ray) | 0.409 from 0.398 | 0.296* from 0.265 | 0.343* from 0.318 | 0.159* from 0.118 | 2.772* from 2.864 | 0.454* from 0.430 |
| CT2Rep (CT) | 0.275* from 0.206 | 0.232* from 0.073 | 0.233* from 0.099 | 0.442* from 0.430 | 1.978 from 2.001 | 0.652 from 0.647 |
| 3D-CT-GPT (CT) | 0.342* from 0.335 | 0.263* from 0.197 | 0.252* from 0.225 | 0.442 from 0.435 | 2.075* from 2.104 | 0.632* from 0.619 |
After RRG-DPO, with the base model’s value under it. * significant against the base model, as printed. RadCliQ is lower-is-better. PromptMRG, highlighted, has the highest F1 before and after. BLEU-4, METEOR and ROUGE-L change by 0.015 or less in every model.
Averaged over the six models, recall gains +0.082 and precision +0.026 derived. The abstract promises fewer false-positive and fewer false-negative findings; the table mostly delivers the second. Precision improves clearly for R2Gen and CT2Rep, by about 6 and 7 points, and by about a point or less in the other four. It is a hit rate: a gain means a larger share of the written findings is right, not necessarily fewer false ones.
The abstract also says every model improves on six clinical metrics. That holds for all 36 cells; 25 of the 36 carry a star derived. CT2Rep, which starts at the lowest recall, gains the most (0.073 to 0.232); PromptMRG, which starts at the highest, gains 0.009.
The paper presents PromptMRG + RRG-DPO (precision 0.508, recall 0.516, F1 0.483) as a “new state-of-the-art” on MIMIC-CXR among peer-reviewed work, as far as the authors know. The authors’ own PromptMRG run scores an F1 of 0.476, the same as PromptMRG’s published number, so the claim is a lead of 0.007 that the paper’s own test doesn’t find significant. Read it as “matches PromptMRG”, and look for the method’s value on the models that start with a lower F1.
05The labeller is both teacher and judge
CheXbert reads a report and outputs 14 chest X-ray labels. In this paper it does three jobs: it picks the wrong report, it marks the sentences to delete, and it computes the precision, recall and F1 of the results. A model trained to separate reports that CheXbert labels differently should improve most on CheXbert’s own score, and it does in five of the six models derived. For EKAGen, F1 rises 17% while RadGraph and RaTEScore, which the pipeline doesn’t use, each rise 3%. They do rise, and significantly in four of the six models each, so the gain is not only the labeller agreeing with itself. But the size of the clinical gain is better read from those columns than from the headline recall.
The paper describes one pipeline for both datasets: the BiomedVLP text encoder, a chest X-ray language model, for retrieval, and CheXbert’s 14 chest X-ray labels for selection and scoring. It doesn’t mention CT-RATE’s own 18 abnormality labels. Scored that way, CT2Rep’s F1 goes from 0.099 to 0.233, more than double, while its RadGraph rises 3% and its RadCliQ and RaTEScore changes are not significant. CT2Rep’s own paper reports an F1 of 0.534 with a classifier trained for CT-RATE’s labels, averaged per abnormality, on a different test set. So the CT rows show that the step transfers to a 3D model; they can’t be compared with other CT results, and they say little about findings CheXbert has no label for.
06What the paper leaves out
- Intervals, repeats and cost. Each row is a single result with stars: no repeated runs, no confidence intervals. The cost of the three training epochs isn’t given, only the 4.6 hours of pair building.
- Other generators. A reviewer asked for CXRMate and for the newer LLM-based MAIRA-2, LLaVA-Rad and CXR-LLaVA; the authors left them to future work.
- Sensitivity to the retriever and the labeller. The rebuttal mentions preliminary runs with another retriever, with the CheXpert labeller, and with the new loss on random negatives. None of these numbers is in the paper.
- A look at the images. A reviewer noted that a “false positive” can be a true finding the reference report omits; the authors agreed they had seen such cases. The paper shows no generated report, and we found none of the visual analysis its text mentions in the repository.
07The released code against the paper
The repository applies the method to one model, R2Gen. Its values differ from the paper’s our observation, read at commit bc7138a of 12 Jan 2026:
| Setting | Paper | Repository, R2Gen example |
|---|---|---|
| β, strength of the preference term | 0.3 | 0.1 |
| λ, keep the true report likely | 1 | 0.1 |
| γ, keep the harmless sentences likely | 0.1 | 0: the term is computed, then multiplied by zero |
| Weight of the conflicting sentences | not mentioned | 5× in every log-likelihood |
| Optimiser, learning rate | AdamW, 10−7 | Adam, 10−6 |
| Length of training | three epochs (about 50,800 steps at batch 16) | script: 5 epochs; README: 6,000–9,000 steps, 0.35 to 0.53 of an epoch |
Table 1 — Settings in the paper and in the released example. Paper: Section 3. Repository: modules/dpo_loss.py, modules/trainer.py, modules/datasets.py, main_dpo.py, run_mimic_cxr_dpo.sh and the README. Steps per epoch assume all 270,790 training pairs at batch 16 derived.
Two differences matter. The new term is switched off in the example: its weight is 0, so what runs is a DPOP-style loss on the chosen pairs. And the tokens of the conflicting sentences count five times in each report’s average log-likelihood, a weighting the paper doesn’t describe. Whether the paper’s results were produced with these values is not stated; the example may simply be a different configuration: main.py tests by default a checkpoint from a run named …p1p2_newweight01_01, while the training script writes to …p1_newweight01.
08Takeaways for training a report generator
- If you add preference training, spend the effort on the negatives. On EKAGen with plain DPO, random negatives lowered all nine metrics; chosen ones gave 0.092 of the 0.120 recall gain before any change to the loss.
- Expect recall more than precision. Recall gained +0.082 on average, precision +0.026.
- Expect little where the labeller’s F1 is already high. No significant change on PromptMRG, the one such model here.
- Score with a tool you didn’t train with. The labeller’s own F1 rose 17% on EKAGen; RadGraph and RaTEScore rose 3%.
09What you can use today
- The code. github.com/ccarliu/RRG-DPO, licence AGPL-3.0: commercial use is allowed, but if you distribute a modified version or run one as a network service, you must offer its source under the same licence. Open
modules/datasets.py(the pair selection) andmodules/dpo_loss.py(the loss); runrun_mimic_cxr_dpo.shafter training R2Gen withmain.py(as committed it only tests: re-enabletrainer.train()). - What isn’t there. No trained RRG-DPO weights, no code for the other five models or for CT, and no script for the retrieval or the sentence labels, which the loader reads from files. The README links a “preference dataset” on Google Drive; we didn’t open it. You need your own credentialed copy of MIMIC-CXR and PromptMRG’s annotation file.
- The recipe, without the code. Embed your training reports; for each one take the 20 nearest; keep the neighbour whose finding labels differ most; train with DPO plus a term that keeps the true report likely.
- The rule. A wrong example teaches what it differs on: make it differ on findings, and score the result with a metric that didn’t choose the examples.
ASources
- Liu, H., Wei, D., Xu, Z., Wu, X., Zheng, Y., Wang, L. RRG-DPO: Direct Preference Optimization for Clinically Accurate Radiology Report Generation. MICCAI 2025, LNCS 15964, pp. 552–562: paper page with reviews and rebuttal · PDF · DOI. We found no arXiv version.
- The repository ccarliu/RRG-DPO, read on 13 Oct 2026 at commit
bc7138a. No data file was opened. - Rafailov, R., et al. Direct Preference Optimization. NeurIPS 2023: arXiv:2305.18290. Pal, A., et al. DPO-Positive: arXiv:2402.13228.
- Smit, A., et al. CheXbert. EMNLP 2020: arXiv:2004.09167.
- Jin, H., et al. PromptMRG. AAAI 2024: arXiv:2308.12604 (Table 1).
- Hamamci, I. E., et al. CT2Rep. MICCAI 2024: arXiv:2403.06801 (Tables 1 and 2).
- Samokhin, V., et al. Random Direct Preference Optimization for Radiography Report Generation: arXiv:2509.21351 (2025).