news 2026/9/27 23:03:20

医学影像报告多模态检索:从CLIP微调到向量索引落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
医学影像报告多模态检索:从CLIP微调到向量索引落地

简介:一套面向毕业设计场景的深度学习多模态检索项目,聚焦医学影像与报告文本的跨模态匹配,适用于计算机视觉、医学信息处理方向的高年级本科生或研究生参考、复现与二次开发。压缩包共61个文件,约208MB,包含23个Python脚本、可视化搜索界面、训练模型及相关配置文件;其中涵盖多种自编码器结构的训练脚本,搭配数据预处理模块,可完整还原影像特征提取与文本语义分析流程。包内附带影像报告数据集链接、NLTK数据链接及README说明,便于快速搭建依赖环境;另含pyc编译文件与zbak备份文件,便于对照排错修改与调试。当前已有79人学习下载,非常适合需要从零搭建多模态检索系统、理解跨模态特征融合思路的读者参考借鉴。

1. 医学影像报告多模态检索:为什么通用图文模型在这里会失灵

当你面前摆着一张右肺占位CT,想翻一翻既往档案里“和这张最像”的病例,或者反过来输入“磨玻璃影、分叶征”几个词,把库里对应影像一次捞出来,这就是基于深度学习的医学影像报告多模态检索要做的事。它跟“以图搜图”不同,影像和报告要被投影到同一个向量空间,图能查文,文也能查图,而不是靠文件名或标签去碰运气。

这套系统真正解决的是三类人的问题。影像科医生想少翻旧档案;做教学科研的人想快速聚合同类病例;算法团队则把它当成报告生成、辅助诊断的前置召回模块。先给一个反直觉的结论:通用CLIP这类模型直接拿来做医学图文检索,Recall@1往往不到40%,因为医学报告里大量“未见异常”的阴性描述、缩写和术语会稀释掉真正的匹配信号。要让方案落地,必须从数据组织、模型微调到向量索引重新来一遍。

2. 从“以图搜图”到“图文互查”:多模态检索的建模思路与医学适配

2.1 检索的本质是把影像和报告压缩到同一个向量空间

先别急着选模型,想清楚你要在什么空间里做检索。常见做法是双塔结构:一个图像编码器把CT、MRI压成固定长度向量,一个文本编码器把报告也压成向量,然后用对比学习拉近“同一检查的图像向量”和“它的报告向量”。训练完成后,库里的所有报告离线编码成向量存入索引;查询的时候,输入图像得到查询向量,做近邻检索返回一批报告;以文搜图则把流程反过来。整个系统的命门,就是“向量空间里的邻居关系”能否等价于“临床语义的相似关系”。

这里有一个很反直觉的点:医学报告里大量篇幅在描述客观所见,但真正区分病例差异的信号,常常是“哪些表述被写在了一起”。比如“左肺上叶结节伴毛刺”和“右肺下叶磨玻璃影”这两句话句法结构几乎一样,临床语义却天差地别。纯文本检索靠字面共现很容易翻车,而多模态检索能借助图像把这两句的向量拉向完全不同的位置。反向的案例也成立:两例CT在像素上很接近,一个报告写“间质性肺炎”,另一个写“肺纤维化”,如果文本编码器没有医学预训练,它在向量空间里可能把这两个完全不同的诊断叠在一起。

所以,医学影像报告多模态检索的本质不是“给图像打标签”,而是学一个跨模态度量。深度学习在这里的角色,不是端到端输出诊断结论,而是为每个病例的影像模态和文本模态各生成一个可比较的表示。这个设计决定了下游所有环节:向量维度选多少、索引用哪种、检索延迟能不能压进秒级、结果能不能解释,全都取决于编码器的表示质量。

2.2 模型选型:零样本用CLIP、微调用双塔、部署再谈三塔

现在做图文检索,绕不开预训练视觉语言模型。CLIP这套双塔范式之所以流行,是因为它把图像和文本分别编码后在同一个归一化空间里做对比学习,天然适合检索。但直接把通用CLIP用在医学上,问题在于它的图像塔被自然图像主导,对胸片、CT的窗宽窗位、纹理特征不敏感;文本塔也缺乏医学语料里的缩写和术语。更有意思的是,医学报告里的高频词是“未见明确异常”这类否定结构,通用模型的文本塔很容易把“无”“未见”当成无关停用词忽略掉。

我一般把选型分成三个场景。第一种是拿开源医学图文checkpoint做零样本验证,最快但上限低,适合先看数据可行性。第二种是微调双塔:把预训练的图像塔和文本塔拆出来,用院内脱敏的图文对做对比学习微调,这是目前性价比最高的路线。第三种是“三塔”或加一个cross-attention融合模块,检索阶段用双塔做粗筛,再用融合模型对Top-N结果重排。重排能显著提升最终精度,代价是推理多一步,适合对延迟不敏感的教学科研检索,或者作为报告生成的前置召回。

下表是我在方案选型时常用的一张对比表,维度按检索场景来定。

方案适用阶段优点主要坑
通用CLIP零样本快速可行性验证不用训练、几百行代码出结果医学术语和影像模态不适应,R@1偏低
医学域预训练模型小样本基线比通用CLIP高出一截,省时间中文报告覆盖有限,需要看训练语料来源
微调双塔正式部署能贴合院内报告风格,精度最高需要高质量图文对、调参经验
双塔粗排+融合重排高精度场景把粗排Top-50重排到Top-10推理延迟增加,融合模型要另维护一套

参数选择上,向量维度我常用256或512。维度太低,区分度不够;维度太高,索引内存和计算成本涨得快,对医学这种动辄几十万病例的库不划算。温度参数在对比学习里是另一个关键点,CLIP默认从0.07附近开始,微调数据噪音大时可以放宽到0.1,后面再降。你记住一个原则:温度越小,模型对难分样本越敏感;温度太大,loss会被压得很平,模型懒得学。

2.3 数据准备:从DICOM到图文对的关键步骤

做医学多模态检索,数据整理的时间通常会占掉整个项目的一半。首先得弄清楚DICOM和报告靠什么字段关联。常见做法是用StudyInstanceUID或AccessionNumber把一次检查的图像和报告对应起来,但这个看似简单的关联,在真实医院数据里会有各种意外。我见过不少导出的DICOM文件名被PACS重新编号,跟报告系统里的检查号对不上,这时候就别靠文件名,要用DICOM tag里的StudyInstanceUID和AccessionNumber做双重校验。

报告文本也需要结构化。医学影像报告一般分“影像所见”和“诊断意见”两部分,前者是客观描述,后者是结论。检索训练时这两部分的权重完全不同,如果把它们混在一起喂给模型,长报告会让模型分不清重点。下面是我常用的一个轻量解析函数,不依赖复杂的NLP框架:

import re from dataclasses import dataclass @dataclass class RadiologyReport: study_uid: str findings: str conclusion: str SECTION_PATTERNS = [ (r"影像所见|影像表现|所见", "findings"), (r"诊断意见|诊断结论|结论|印象", "conclusion"), ] def parse_report(study_uid: str, raw_text: str) -> RadiologyReport: title_pos_list = [] for pattern, section in SECTION_PATTERNS: for match in re.finditer(pattern, raw_text): title_pos_list.append((match.start(), section, match.end())) title_pos_list.sort(key=lambda x: x[0]) findings_parts, conclusion_parts = [], [] for idx, (pos, section, end) in enumerate(title_pos_list): # 下一个标题出现的位置就是当前小节的末尾 upper = title_pos_list[idx + 1][0] if idx + 1 < len(title_pos_list) else len(raw_text) content = raw_text[end:upper].strip() # 去掉“:”和换行,保留原文完整语义 content = re.sub(r"^[::\s]+", "", content) if section == "findings": findings_parts.append(content) else: conclusion_parts.append(content) return RadiologyReport( study_uid=study_uid, findings="\n".join(findings_parts), conclusion="\n".join(conclusion_parts), )

这段代码的逻辑不算复杂:用正则把报告里的小节标题位置找出来,按位置排序,再按标题之间的区间切分文本。有个细节是同时记录match.start()和match.end(),因为标题“影像所见”后面的冒号不应该混进正文;切完内容后再去掉行首的冒号和空白。SECTION_PATTERNS是核心参数,每家医院的报告模板措辞不同,你需要先拿一批真实脱敏报告跑一遍,把小节标题的别名都补进去,比如“检查所见”和“影像表现”其实是同一类。

这里要强调,预处理别做过头。停用词、词形还原、分词归一化这些通用NLP操作,在医学报告上很容易帮倒忙。比如“未见明显异常”里的“未见”,如果分词后当成停用词删掉,整句话的否定语义就丢了;“左肺上叶”如果被拆成“左肺、上叶”,解剖位置的层级关系也没了。我的一般做法是只做小标题切分、全角转半角、去掉多余换行,剩下的交给文本编码器自己去学。

还有一个容易被忽略的步骤:一次检查可能包含多个影像序列,比如胸部CT平扫加增强,可能有五六个体位。这种情况下,一份报告对应多张图像,训练时该怎么做?常见做法有两种:一是只挑主序列参与训练,比如平扫序列;二是把所有序列分别与报告配对。前者数据干净,后者能扩大训练样本,但会引入噪声,因为增强序列和报告描述的对应关系未必明确。我的建议是先做主序列,跑通之后再尝试多序列版本,不要一上来就追求样本数量。

3. 三件套落地:训练医学双塔、建向量索引、算Recall@k

3.1 训练模型最小能跑的流程

进到训练环节,最核心的是对比损失。下面是一段简化的双塔训练伪代码,我在实际项目里也是从这个骨架改出来的。这里图像编码器可以用ResNet或ViT的预训练权重,文本编码器用BERT类模型,关键是把两位各自的最后一层分类头去掉,改成输出固定维度的embedding:

import torch import torch.nn as nn import torch.nn.functional as F class MedRetrievalModel(nn.Module): def __init__(self, image_encoder, text_encoder, embed_dim=256, temperature=0.07): super().__init__() self.image_encoder = image_encoder self.text_encoder = text_encoder self.temperature = temperature self.image_proj = nn.Linear(image_encoder.out_features, embed_dim) self.text_proj = nn.Linear(text_encoder.out_features, embed_dim) def encode_image(self, images): feat = self.image_proj(self.image_encoder(images)) return F.normalize(feat, p=2, dim=-1) def encode_text(self, texts): feat = self.text_proj(self.text_encoder(texts)) return F.normalize(feat, p=2, dim=-1) def forward(self, images, texts): q = self.encode_image(images) # (B, embed_dim) k = self.encode_text(texts) # (B, embed_dim) logits = (q @ k.T) / self.temperature # (B, B) labels = torch.arange(q.size(0), device=q.device) loss = F.cross_entropy(logits, labels) return loss

逻辑说明:这里不再单独取图像塔的某个中间层特征,而是给预训练编码器接一个Linear Proj,把特征压到embed_dim。logits矩阵里每个元素表示第i张图和第j条报告的相似度,因为同一个batch里第i张图对应的报告就在第i行,所以标签天然是[0, 1, 2, ..., B-1]。对比损失会拉大正样本对的相似度,同时压低其他组合。

参数说明:temperature初始化0.07,如果训练初期loss不下降,先试探着调到0.1,因为医学图文对本身噪声大,过于尖锐的目标分布会让模型困在局部最优。batch_size尽量不小于32,对比学习非常依赖batch内的样本多样性,batch太小,负样本没有区分度。显存不够的时候,不要一味用梯度累积,梯度累积并不能增加有效负样本数量;更好的办法是用梯度检查点或者减小图像分辨率,把batch_size保住。

另外一个训练中的实用细节:同一患者如果在同一个batch里出现两次,两张图像和两条报告会形成一组混淆的负样本,因为它们的语义高度相似,模型很容易把其中一个误当成另一个的正确答案。我一般会在构建batch时按patient_id做分桶,保证同一个batch内不同患者的数量尽量多,这是低成本提升效果的技巧。先冻结图像塔和文本塔的底层,只微调最后的transformer层和两个Proj层,等验证集指标不再涨了,再解冻深层继续训练一小段。这样做能避免小样本医学数据把预训练权重洗坏。

3.2 把向量落库:FAISS还是Milvus

模型训练好之后,难点从“怎么学”变成“怎么存”。检索库的大小直接影响索引方案选择。几十万条向量以内,FAISS就够了,它不需要额外部署服务,一个本地索引文件搞定;到了百万级或者要求多团队共享,Milvus这类向量数据库更合适,因为支持动态增删、多副本和权限控制。我个人的习惯是先上FAISS,等到检索请求量和数据规模逼到架构迁移再说。

以图搜报告时,库里的文本向量离线算好;以文搜图则反过来,离线算图像向量。下面是FAISS的内积索引例子:

import faiss import numpy as np embed_dim = 256 # 假设已经算好库里所有报告的向量 text_vectors = np.vstack(all_text_vecs) # shape (N, 256) text_ids = np.arange(len(all_text_vecs)) index_text = faiss.IndexIDMap2(faiss.IndexFlatIP(embed_dim)) index_text.add_with_ids(text_vectors, text_ids) # 查询图像编码成向量后搜索 query_vec = model.encode_image(query_img_tensor) # (1, 256) query_vec = query_vec.detach().cpu().numpy() scores, ids = index_text.search(query_vec, k=10)

逻辑说明:因为训练时输出的向量已经做过L2归一化,点积等价于余弦相似度,所以用IndexFlatIP就够了,不需要再包一层IndexNormalize。IndexIDMap2的作用是让我们自定义每条向量的ID,检索出的ids可以直接映射到报告库里的study_uid或report_id,不用操心原始下标迁移。query_vec的shape必须是(1, 256),FAISS不接受一维数组。

参数说明:embed_dim要和模型输出对齐,很多踩坑案例都是模型输出512,索引建在256,检索时shape校验直接报错。k=10是召回条数。如果数据量超过50万,把IndexFlatIP换成IndexIVFFlat,训练一个粗聚类,检索时只扫描最近的几个聚类,速度能上去,但别忽略nprobe参数——它控制扫描多少个聚类单元,调太大会损失性能提升,调太小会召回下降。这里没有绝对答案,我一般先设nprobe=16,再看Recall@10的波动决定要不要加大。

3.3 评估:Recall@k和MRR怎么算

检索模型的评估不能只看准确率,因为检索问题里“正确答案”不止一个。常用的两个指标是Recall@k和MRR。Recall@k衡量“正确答案是否出现在前k条结果里”,MRR看“第一个正确答案排第几”。这两者能互补:一个只看命中,一个看排序质量。

import numpy as np def recall_at_k(queries, ground_truth_ids, retrieved_ids, k): """queries: 查询的id列表; ground_truth_ids: 每个查询对应的唯一真实报告id""" hits = 0 for idx, gt in enumerate(ground_truth_ids): if gt in retrieved_ids[idx][:k]: hits += 1 return hits / len(queries) def mean_reciprocal_rank(queries, ground_truth_ids, retrieved_ids): rr_list = [] for idx, gt in enumerate(ground_truth_ids): ranks = [pos + 1 for pos, rid in enumerate(retrieved_ids[idx]) if rid == gt] rr_list.append(1.0 / ranks[0] if ranks else 0.0) return float(np.mean(rr_list))

参数说明:ground_truth_ids在图文检索任务里一般取“同一个检查的对应报告ID”。如果一次检查有多个报告,或者一份报告对应多个序列,评估会和训练一样遇到一对多问题。我的做法是:为每个query只保留一个golden ID,优先选择“报告结论最明确、覆盖面最广”的那条;否则MRR会被一对多灌水,看起来很高,实际检索体验很差。另外,评估集的时间线也要注意,不要拿同一患者随访多次的图像做查询和答案,否则模型靠患者级别相似性就能拿到高指标,掩盖了真正的语义匹配能力。

4. 多模态检索避坑手册:四类让Recall@k暴跌的数据陷阱

4.1 图文错配:报告是对的,图像却张冠李戴

现象:训练集和验证集上loss降得很顺,检索时却经常返回风马牛不相及的病例,比如报告写“左肺下叶结节”,检索结果却是一堆正常胸片。把样本拉出来一看,发现“报告A”和“图像B”根本不是同一次检查。

原因:这是在数据组装阶段埋下的雷。很多医院PACS和RIS系统的检查号不一致,导出时如果只用AccessionNumber做关联,会出现同一个检查号对应多份历史报告的情况;或者DICOM文件被批次重命名,文件名里的编号和报告系统的编号脱钩。

解决:用StudyInstanceUID加AccessionNumber双重字段配对,配对之后还要做一次内容抽查。具体做法是写一个校验脚本,随机抽50对图文,让模型算相似度,相似度分布异常的样本单独检查。还有一招,对比DICOM里的检查日期和报告里的检查日期,日期相差超过一天的直接踢掉。这一步听起来笨,但能避免后面的整个项目在脏数据上白跑。

4.2 过度预处理:把“左肺上叶”拆得模型都不认识了

现象:把报告做了分词和停用词过滤之后,检索精度反而比不处理还低,尤其是“未见明确异常”这类高频句,检索时模型把正常病例和阳性病例混在一起。

原因:通用NLP预处理是给搜索词匹配用的,不是给语义编码用的。分词会把“左肺上叶”切成“左肺”“上叶”,模型丢了“这是一个解剖区域”的完整概念;停用词表会把“未见”“无”删掉,否定信息直接消失。医学报告最重要的是否定、位置、程度和对照信息,这些恰恰被传统预处理破坏。

解决:预处理最多做到全角转半角、去掉多余换行、保留标点。中文报告里的逗号、句号对BERT类模型是有效的分割信号,不要动。缩写统一要谨慎,比如“CA”在不同语境下可能是“癌”也可能是“钙化”,贸然替换会造成新噪声。遇到不确定的术语,宁可保留原文让模型去学,也不要自己造一套规则去映射。

4.3 长报告截断截错位置:模型根本没看过诊断结论

现象:训练时用的是报告的“影像所见”+“诊断意见”全文拼接,但文本编码器最大长度128或512,于是自动截断了后半段。结果检索时模型学到的是“影像所见”的部分特征,诊断结论里的最终判断完全没参与向量计算。

原因:BERT类模型的输入长度有硬上限,而一份完整影像报告动辄几千字。按从头截断的默认方式,保留下来的大部分是“影像所见”的客观描述,结论丢在后面。

解决:调整文本拼接顺序,把“诊断意见”放到最前面,截断时优先保住结论。如果报告解析结构做得细,可以单独用“结论”字段做训练文本,把“影像所见”附加在后面,长度不够再加。另一个办法是分段编码后再做平均池化,但会增大推理成本,我的习惯是先做结论优先截断,验证效果不够,再升级到分段编码。

4.4 随访患者刷屏:检索结果被同一个人的多次复查占了半页

现象:检索返回的前10条里,有6条来自同一位患者不同时间点的随访检查。表面上它们确实得了同一种病,但对当前查询的参考价值很低,因为病灶大小、位置和报告写法都可能已经变了。

原因:向量索引按“检查实例”入库,没有对患者维度做去重或过滤。同病种的语义在向量空间里天然聚集,一个患者多次随访的向量点就会挤在同一片区域。

解决:在索引阶段给每条向量额外附加patient_id,检索得到Top-K之后,按patient_id做一次去重,再展示给用户。更激进的做法是训练时就把同一患者的多次检查作为特殊负样本,让模型学会区分“同一患者的不同时间点”和“不同患者的相似病例”,这样模型本身就不会被随访序列带偏。我一般先做检索后去重,它见效快,不影响离线训练。

5. 本地到科室:检索服务、索引更新与隐私合规的落地细节

5.1 服务接口怎么设计:一个最小的以图搜报告端点

把模型和索引部署成服务,才算真正能用。这里我给一个用FastAPI搭建的最小接口示例,只保留“传图像、返回报告列表”这一个核心动作:

from fastapi import FastAPI, UploadFile, File from pydantic import BaseModel app = FastAPI() class ReportHit(BaseModel): report_id: str patient_id: str study_date: str conclusion: str score: float @app.post("/retrieve/report-by-image") async def retrieve_report(file: UploadFile = File(...)): # 1. 读图和预处理 image_bytes = await file.read() image_tensor = preprocess_image(image_bytes) # 变成模型输入格式 # 2. 编码 query_vec = retrieval_model.encode_image(image_tensor) # (1, embed_dim) query_vec = query_vec.detach().cpu().numpy() # 3. 检索 scores, ids = index_text.search(query_vec, k=20) # 4. 按患者去重后取前10 results = deduplicate_by_patient(ids[0], scores[0]) return {"hits": [ReportHit(**hit).model_dump() for hit in results]}

逻辑说明:接口的输入是原始图像文件,输出是报告命中列表。preprocess_image要做DICOM窗宽窗位标准化,转成模型训练时一致的三通道或单通道格式,这一步不能省。deduplicate_by_patient就是前面说的按患者去重,确保一个患者最多占一条结果。代码里的retrieval_model建议在服务启动时加载一次,不要每次请求都重新初始化,不然延迟会高得没法看。

参数说明:k=20是召回数,最后展示前10,因为去重会砍掉一部分结果,留出冗余空间。在实际接口里需要加一个超时控制,DICOM文件大的时候预处理速度会变慢,建议设置10秒上限,超时返回提示而不是让请求一直挂着。

部署环境方面,很多人以为要配上一整套深度学习环境配置才能跑,其实推理服务只需要模型权重、PyTorch或ONNX Runtime以及FAISS就够了。我一般会用ONNX导出图像塔和文本塔,这样服务端不依赖深度学习训练框架,包体更小、启动更快。

5.2 索引更新策略:增量追加、定期合并、按患者去重

医院的影像数据是每日增长的,索引不能只建一次。这里最常见的错误是每天全量重建索引,随着数据变多,重建时间越来越长,最终占用大量计算资源。我的建议是分两层:新数据先追加到一个增量索引,每周或每月把增量合并进全量索引。

FAISS支持add_with_ids直接追加,但多次追加会导致索引内部结构退化,检索速度变慢。此时可以用IndexIDMap2包一层IndexIVF,然后在合并时做一次重新训练。合并前要处理两个问题:一是同一个患者在增量阶段出现了新检查,旧向量要不要保留;二是报告和图像的关联变更后,旧向量是否要失效。我的做法是:在向量元数据里维护一份valid_until字段,检索时过滤已失效向量;真正做全量合并时,把失效向量踢掉,避免索引越来越大。

更新频率取决于科室的业务节奏。门诊量大的影像科,至少每天追加一次增量索引,否则当天的新报告检索不到;科研用途的数据可以一周一更。这里没有标准答案,但可以盯一个指标:从DICOM到达检索库可用的时间延迟,它直接决定了医生愿不愿意用这个系统。

5.3 合规边界:院内脱敏、审计日志与访问控制

医学影像报告属于敏感的医疗数据,部署时把合规问题想清楚,远比把Recall@k调高0.5个点重要。首先是脱敏:DICOM文件里的PatientName、PatientID、InstitutionName这些tag在入库前必须删除或替换,报告里的姓名、住院号、身份证号要做正则匹配和打码。模型训练只能使用脱敏后的数据,这个是底线。

其次是审计日志。检索系统应该记录每一个查询是谁、什么时间、查了什么图像、返回了哪些报告。很多医院信息科对这一点有硬性要求,因为检索行为可能涉及患者隐私。实现上,日志只需要记查询者的用户ID和受控的检索内容,不要存完整图像,更不要存原始报告全文。

访问控制也是容易被忽视的环节。检索服务不能直接暴露在公网,至少要限制在院内网络;对外提供接口时,要用token校验用户身份。我经历过一个项目,系统已经在影像科内部试用,结果某个接口忘了加鉴权,科室外的同事也能直接调,最后被信息科叫停整改。这种问题在技术上不难解决,难的是意识。从第一天起就把鉴权、脱敏、日志三条写进代码规范里,后面能省很多麻烦。

6. 二十条金标query:让检索效果在每一次模型替换后都可对比

模型的迭代不是一次性的,你迟早要换预训练权重、调温度参数、优化数据。为了不被每次调参后的“感觉变好了”误导,我建议在项目第一天就准备一张金标query表:人工选定20条典型病例,每条包含一张代表图像或一段典型报告描述,以及期望返回的正确答案ID。这个集合不用大,但一定要覆盖你业务里最常见的几类情况:结节、磨玻璃影、肺炎、骨折、术后复查、正常对照,每类两三条。

每次模型训练完,跑一遍这个固定query集,记录两个数:Recall@5和MRR。把这两个数写进一个简单的对比表,作为模型替换的准入条件。我个人的教训是,曾经因为肉眼看了几个样例觉得新模型“效果不错”就上线了,结果过了半个月发现某类少见病种的召回掉了一半,而金标集里没有覆盖那类病例。后来我把金标集扩到50条,每个病种都留了底,才真正避免了这种翻车。

还要注意金标集本身也会过期。医院术语习惯会变,报告模板会改,新的扫描设备可能带来新的影像风格。金标集建议每季度审一次,把已经不适合的query换成当下更有代表性的病例。检索是一个长期维护的系统,不是训练完就结束的模型项目。希望这个“小成本、制度化验证”的思路能帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 23:03:07

PHP Smarty制作响应式布局的步骤

欢迎来到PHP Smarty的世界&#xff01;让我们一起探索如何使用这个强大的工具来制作响应式布局。首先&#xff0c;我要告诉你&#xff0c;响应式布局是一种网页设计技术&#xff0c;它可以使你的网站在各种设备上&#xff08;从台式机到平板电脑和手机&#xff09;都看起来很棒…

作者头像 李华
网站建设 2026/9/27 22:59:35

python中def的含义

在编程语言里, def 这个关键字是拿来定义函数用的, 所谓函数, 其实就是那些已经组织得妥妥当当的、能够被反复使用的代码块儿, 它们的作用是可以去执行一些特定的任务或者是进行某些计算操作, 最后再把结果给返还回去, 所以, 程序员要是用了 def 这个关键字的话, 是有助于他们把…

作者头像 李华
网站建设 2026/9/27 22:58:23

第18篇:天气特效-浓度积分高度雾——把雾看成一整段空气,沿视线把浓度积出来

还记得第 4 篇收尾时,本猿在天气线的那一行里撂下的半句话吗? 天气线还能挖:用 depthTexture 恢复世界坐标做距离衰减与室内遮蔽;雨/雪/雾/沙尘组合成可切换的天气系统。 那一篇我们做的是「深度高度雾」:从深度纹理反算出每个像素的世界坐标,再拿相机高度、像素高度、雾…

作者头像 李华
网站建设 2026/9/27 22:58:06

大模型架构演进史:从 Transformer 到 Jev

本文基于公众号「淚笑的赛博日记-起零衍迹实验室」的文章《从 Transformer 到 Jev —— 大模型架构的演进》扩写&#xff0c;并对原文涉及的关键事实做了一轮联网核对&#xff0c;核查结果列在文末附录。全文含 11 张图解。2026 年 9 月 15 日&#xff0c;一家叫 TypeSafe AI 的…

作者头像 李华
网站建设 2026/9/27 22:57:41

STM32嵌入式开发导论与芯片本体认知

第一部分 学习总览与方法 1.1 解决三个根本问题 学什么&#xff1a;以 STM32 为核心的嵌入式 MCU 开发。 用什么学&#xff1a;C 语言 Keil MDK VS Code STM32CubeMX HAL 库。 怎么学&#xff1a;工具使用 源码研读 官方文档查阅 工程实践。 1.2 核心学习路线 建立…

作者头像 李华