尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

CLIP模型:从图文对齐到多模态AI的范式革命

发布时间:2026/9/16 16:03:31

资讯中心
01
ARTICLE

CLIP模型:从图文对齐到多模态AI的范式革命

CLIP模型:从图文对齐到多模态AI的范式革命
1. 项目概述从“看图说话”到“图文对齐”的范式革命几年前如果你想让计算机理解一张图片的内容最主流的做法是训练一个专门的图像分类模型比如在ImageNet数据集上训练一个ResNet。这个模型很擅长告诉你图片里是“猫”还是“狗”但它对“猫”的理解仅限于从1000个预设类别中选一个。如果你想让它理解“一只在沙发上打盹的橘猫”或者回答“这张图片适合用作温馨家庭博客的封面吗”这类开放性问题传统模型就束手无策了。同样一个训练来给图片生成标题的模型也很难去判断一段文本描述是否准确地匹配了另一张它从未见过的图片。这个问题的核心在于传统的计算机视觉和自然语言处理模型是在各自独立的“数据孤岛”和“任务孤岛”上训练的。视觉模型只懂像素语言模型只懂词向量它们之间缺乏一个统一的、可互通的“理解”。直到OpenAI在2021年扔出了一颗“重磅炸弹”——CLIPContrastive Language-Image Pre-training情况才开始发生根本性的转变。CLIP本质上是一个多模态预训练模型。简单来说它通过海量的“图片-文本对”进行训练目标不再是预测图片的固定标签而是学习一个共享的“语义空间”。在这个空间里一张图片和描述它的文本它们的特征向量会非常接近而不相关的图片和文本其特征向量则相距甚远。这就像教会了模型一种“图文对齐”的通用能力它不再需要针对每个新任务重新训练而是可以直接“理解”图片和文本在语义上的关联。对于任何从事AI应用开发、研究或者对AIGC人工智能生成内容感兴趣的朋友来说理解CLIP都至关重要。它不仅是DALL-E、Stable Diffusion等文生图模型的“基石”之一更开启了一种全新的模型设计范式——从任务特定的监督学习转向开放世界的多模态对比学习。接下来我将结合论文和大量实践为你拆解CLIP的核心思想、实现细节以及它带来的深远影响。2. 核心思想与架构设计抛弃分类标签拥抱自然监督CLIP论文的标题直击要害《Learning Transferable Visual Models From Natural Language Supervision》从自然语言监督中学习可迁移的视觉模型。这短短几个词包含了三个关键信息可迁移、视觉模型、自然语言监督。我们逐一拆解。2.1 范式转换从N选1到N选N的对比学习传统图像分类模型的训练范式是“N选1”。给定一张图片模型需要从一个固定的、封闭的类别集合比如ImageNet的1000类中选出唯一正确的那个标签。这种范式的局限性非常明显泛化性差模型学到的知识被禁锢在训练时的类别词汇表中无法识别新概念如“新冠病毒”、“元宇宙”。标注成本高需要为海量图片人工打上精确的类别标签耗时耗力。任务单一训练出的模型通常只擅长这一个特定任务。CLIP则采用了“N选N”的对比学习范式。在训练时模型会同时看到一个批次Batch的N个图片-文本对。它的目标不是去预测“图片A属于文本A”的概率而是去判断这N×N种可能的图文配对中哪些是真实的配对对角线上的配对哪些是虚假的配对非对角线上的配对。模型需要将配对正确的图文特征拉近将配对错误的图文特征推远。注意这里的“自然语言监督”指的是互联网上天然存在的、海量的图片及其对应描述如网页的alt文本、图片的标题、社交媒体配文。这些文本描述是开放式的、多样化的包含了远比“狗”、“车”这类简单标签丰富得多的信息如“一只正在飞跃水坑的柯基犬”、“一辆在夕阳下闪着金光的复古跑车”。CLIP巧妙地利用这些免费、大规模的数据作为监督信号。2.2 双塔架构编码器的分工与协作为了实现对比学习CLIP采用了经典的“双塔”架构图像编码器负责将输入图像I编码为一个高维特征向量。论文中探索了多种视觉主干网络如ResNet和Vision Transformer。最终ViT的表现更优。图像编码器的输出会通过一个线性投影层映射到多模态共享的语义空间。文本编码器负责将输入文本T编码为一个高维特征向量。论文采用了Transformer架构具体是基于GPT-2的修改版。文本编码器的输出同样会通过一个线性投影层映射到同一个语义空间。两个编码器是分开独立工作的但它们的训练目标是一致的让配对I_i,T_i的特征点积相似度尽可能大让非配对I_i,T_j, i≠j的特征点积尽可能小。损失函数是核心驱动力。CLIP使用了一个对称的交叉熵损失分别计算以图像为基准和以文本为基准的损失然后取平均。具体来说计算批次内所有图像特征和文本特征的余弦相似度矩阵。将每一行每一张图片的相似度看作一个logits真实的标签是对角线位置即配对的文本计算图像到文本的交叉熵损失。同样将每一列每一段文本的相似度看作logits真实的标签是对角线位置即配对的图片计算文本到图像的交叉熵损失。将两个损失相加或平均。这个设计非常巧妙它迫使模型学习到一种“双向对齐”的能力而不仅仅是单向的图片检索文本或文本检索图片。2.3 为什么是对比学习而不是生成式学习一个很自然的疑问是既然有文本描述为什么不训练一个生成模型让模型直接根据图片生成描述Image Captioning呢或者根据描述生成图片论文作者也探讨了这种方案但实验发现在同等计算规模下对比学习的目标能更高效地学习到高质量的视觉表示。原因在于生成每一个单词是一个更复杂、不确定性更高的任务。模型可能会把很多计算资源浪费在学习语言的语法细节或生成流畅但语义模糊的句子上。而对比学习的目标更“集中”和“明确”它只关心图片和文本的语义是否匹配不关心文本的具体措辞。这使得模型能更高效地利用计算资源专注于学习跨模态的语义关联从而在下游任务中表现出更强的泛化能力。3. 训练细节与规模化效应数据与算力的“暴力美学”CLIP的成功除了架构设计的巧妙很大程度上归功于“大力出奇迹”——极大规模的数据和算力投入。这是理解其性能边界和局限性的关键。3.1 数据集4亿对图文构成的“世界知识”CLIP训练所使用的数据集是从互联网上收集的约4亿个图像文本对。这些数据没有经过精细的人工清洗和标注充满了噪声比如图文不相关、文本包含无关链接或广告。但正是这种规模巨大、覆盖范围极广几乎涵盖人类知识的所有领域的数据赋予了CLIP强大的“常识”和开放域理解能力。实操心得当你自己尝试构建多模态数据集时会深刻体会到高质量对齐数据的稀缺。CLIP的成功路径暗示在多数情况下“规模”可能比“绝对洁净”更重要。当然对于垂直领域如医疗影像高质量、精准标注的小规模数据仍然不可替代。3.2 模型规模与训练策略论文中训练了多个不同规模的CLIP模型图像编码器从ResNet-50到ResNet-50x64文本编码器也相应调整。最大的ViT-L/14模型在256个V100 GPU上训练了约2周。关键的训练技巧包括大批次训练对比学习受益于更大的批次大小Batch Size因为这样能在单个批次内提供更多的负样本即不匹配的图文对让模型更好地学习区分。CLIP使用了非常大的批次可达32,768。学习率预热与余弦退火这是训练大型Transformer模型的标配用于稳定训练初期并让模型在后期更好地收敛。梯度裁剪防止梯度爆炸。混合精度训练使用FP16精度加速训练并节省显存。一个重要的细节是文本提示工程。在训练时文本并非直接输入原始描述。为了提升泛化性作者将文本描述统一包装成一个模板例如“A photo of a {label}”。在预测时也会采用类似的“提示模板”Prompt Template比如将“狗”变成“A photo of a dog”。这种做法极大地统一了文本的分布减少了模型因措辞变化而产生的困惑是CLIP实现零样本迁移的关键技巧之一。3.3 规模化定律性能随计算量平滑增长论文中一个令人印象深刻的结论是CLIP的性能在多个下游任务上的平均表现随着模型训练所用的计算量Compute呈平滑的幂律增长。这意味着只要投入更多的数据和算力性能就能可预测地提升。这为后续更大规模的多模态模型如谷歌的PaLI、PaLM-E以及OpenAI自家的GPT-4V的发展指明了道路也确立了“预训练缩放”在这一领域的核心地位。4. 零样本迁移与下游任务应用开箱即用的超能力训练好的CLIP模型最惊艳的能力就是零样本Zero-Shot分类与检索。这是其“可迁移”能力的直接体现。4.1 零样本图像分类动态构建分类器传统分类器需要一个固定的类别列表和对应的权重。CLIP完全颠覆了这一过程。假设你想让CLIP判断一张图片是不是“柯基犬”、“布偶猫”还是“荷兰猪”。构建文本提示为每个类别创建描述性文本。例如[“A photo of a Corgi.”, “A photo of a Ragdoll cat.”, “A photo of a Guinea pig.”]。更好的提示可以是[“A cute photo of a Corgi dog.”, “A fluffy photo of a Ragdoll cat.”, “A small photo of a Guinea pig.”]。提示工程的质量直接影响结果。文本编码用CLIP的文本编码器将这些提示文本编码成特征向量[t1, t2, t3]。图像编码用CLIP的图像编码器将待分类的图片编码成特征向量i。计算相似度计算图像特征i与每个文本特征t的余弦相似度。归一化与预测通常会对所有相似度分数进行softmax归一化得到每个类别的概率。概率最高的即为预测结果。这个过程相当于为每个任务动态生成了一个线性分类器。分类器的“权重”就是文本特征而“类别”可以是任何能用自然语言描述的概念数量可以任意多。这使得CLIP能够处理成千上万、甚至开放集合的分类任务。4.2 图文检索与搜索这是CLIP最直观的应用。给定一段文本可以在图库中检索出最相关的图片文搜图给定一张图片可以在文本库中检索出最相关的描述图搜文。其精度远高于传统基于关键词或手工特征的方法因为它理解的是深层的语义。应用场景智能相册管理搜索“去年夏天在海边的合影”、电商平台用生活化语言搜索商品、内容审核识别违规图文内容。实操要点对于大规模图库需要预先用图像编码器提取所有图片的特征并建立向量索引如使用FAISS、Milvus等向量数据库。查询时只需计算查询文本的特征并在索引中进行近邻搜索效率极高。4.3 作为其他模型的“视觉理解基石”CLIP学到的强大视觉表示可以被“冻结”后作为特征提取器接入到其他下游任务的模型中显著提升其性能。图像生成如Stable Diffusion这是CLIP影响最深远的领域之一。Stable Diffusion中的关键组件——CLIP Text Encoder就是冻结的CLIP文本编码器。它将用户输入的文本提示词如“an astronaut riding a horse”编码成一系列富含语义的特征向量这些向量作为条件Condition引导扩散模型去噪和生成图像。没有CLIP对文本的深度理解文生图模型很难达到如此精准的语义控制。视觉问答VQA与视觉推理可以将CLIP的图像特征和文本特征拼接作为多模态融合的输入喂给一个轻量级的推理模型如Transformer来回答关于图片的复杂问题。少样本学习当某个下游任务只有极少量的标注样本时可以基于CLIP提取的特征仅训练一个简单的分类头如逻辑回归或SVM就能获得非常好的效果极大降低了数据需求。5. 局限性、常见问题与实战避坑指南尽管CLIP非常强大但它并非万能。理解其局限性才能更好地应用它。5.1 已知局限性分析抽象与组合推理能力弱CLIP在识别具体物体上表现卓越但对于需要复杂逻辑、抽象概念或数量关系的问题表现不佳。例如它很难准确回答“图片中有几个比左边那个更高的物体”或“这张图表达了怎样的讽刺意味”。对文本提示极其敏感零样本性能高度依赖于提示词Prompt的措辞。“a photo of a dog”和“a picture of a canine”可能得到不同的相似度分数。这需要使用者具备一定的“提示工程”技巧。数据偏见与公平性问题由于训练数据来自互联网CLIP不可避免地继承了其中的社会偏见。例如在“CEO”这个提示词下它可能更倾向于返回男性形象的照片在“护士”提示词下更倾向于返回女性形象。这在生产应用中需要谨慎对待可能需要进行去偏处理。细粒度分类和OCR能力有限对于区分不同品种的狗、识别模糊文字等需要极高细节分辨率的任务CLIP的表现不如专门训练的模型。计算开销大型CLIP模型的前向推理需要一定的计算资源在移动端或实时性要求高的场景下需要优化或使用轻量化版本。5.2 实战常见问题与排查技巧问题1零样本分类效果不稳定时好时坏。排查思路检查提示词这是最常见的原因。尝试使用更自然、更具体的提示模板。例如对于艺术品分类用“A painting in the style of {label}”可能比“A photo of {label}”更好。可以尝试多个提示词并集成结果Prompt Ensemble。检查数据分布CLIP在训练数据丰富的领域如常见物体、动物表现好在冷门领域如特殊仪器、稀有植物表现差。考虑是否需要进行少量数据的微调Fine-tuning。温度参数Temperature在计算softmax时# 1. 概述本文我们来分享 MyBatis 的日志模块对应logging包。如下图所示logging包在 《精尽 MyBatis 源码解析 —— 项目结构一览》 中简单介绍了这个模块如下无论在开发测试环境中还是在线上生产环境中日志在整个系统中的地位都是非常重要的。良好的日志功能可以帮助开发人员和测试人员快速定位 Bug 代码也可以帮助运维人员快速定位性能瓶颈等问题。目前的 Java 世界中存在很多优秀的日志框架例如 Log4j、 Log4j2、Slf4j 等。MyBatis 作为一个设计优良的框架除了提供详细的日志输出信息还要能够集成多种日志框架其日志模块的一个主要功能就是集成第三方日志框架。本文涉及的类如下图所示类图正如上文所说MyBatis 的logging包是集成第三方日志框架。所以logging包下的代码整体是适配器模式的实现。所以本文更多的内容是分享适配器模式的应用。当然也会分享 MyBatis 是如何实现该模式的。下面让我们开始遨游logging包。2. LogFactoryorg.apache.ibatis.logging.LogFactoryLog 工厂类。2.1 构造方法// LogFactory.java /** * Marker to be used by logging implementations that support markers */ public static final String MARKER MYBATIS; /** * 使用的 Log 的构造方法 */ private static Constructor? extends Log logConstructor; static { // 1 逐个尝试判断使用哪个 Log 的实现类即初始化 logConstructor 属性 tryImplementation(LogFactory::useSlf4jLogging); tryImplementation(LogFactory::useCommonsLogging); tryImplementation(LogFactory::useLog4J2Logging); tryImplementation(LogFactory::useLog4JLogging); tryImplementation(LogFactory::useJdkLogging); tryImplementation(LogFactory::useNoLogging); }1处在静态代码块中通过调用#tryImplementation(Runnable runnable)方法逐个尝试判断使用哪个 Log 的实现类即初始化logConstructor属性。代码如下// LogFactory.java private static void tryImplementation(Runnable runnable) { // 若 logConstructor 为空则执行 runnable if (logConstructor null) { try { runnable.run(); } catch (Throwable t) { // ignore } } }1处我们可以看到按照SLF4J、Apache Commons Logging、Log4j 2、Log4j、JDK logging、No logging的顺序尝试。也就是说SLF4J的优先级最高。当然如果都未引入的包的情况下最终会使用No logging实现类即不打印日志。另外1处我们可以看到#useSlf4jLogging()、#useCommonsLogging()、#useLog4J2Logging()、#useLog4JLogging()、#useJdkLogging()、#useNoLogging()方法是分别尝试使用对应的 Log 的实现类。以#useSlf4jLogging()方法为例代码如下// LogFactory.java public static synchronized void useSlf4jLogging() { setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class); }在该方法内部会调用#setImplementation(Class? extends Log implClass)方法尝试初始化logConstructor属性。代码如下// LogFactory.java private static void setImplementation(Class? extends Log implClass) { try { // 获得参数为 String 的构造方法 Constructor? extends Log candidate implClass.getConstructor(String.class); // 创建 Log 对象 Log log candidate.newInstance(LogFactory.class.getName()); if (log.isDebugEnabled()) { log.debug(Logging initialized using implClass adapter.); } // 创建成功意味着可以使用设置为 logConstructor logConstructor candidate; } catch (Throwable t) { throw new LogException(Error setting Log implementation. Cause: t, t); } }在该方法中我们可以看到创建的 Log 对象传入的参数是LogFactory.class.getName()。也就是说Log 对象打印的日志是org.apache.ibatis.logging.LogFactory。 当然实际上还是以每个类的全限定类名作为 Logger 的名称例如说我们熟悉的org.apache.ibatis.logging.jdbc.BaseJdbcLogger。为什么呢继续往下看。2.2 getLog#getLog(...)方法获得 Log 对象。代码如下// LogFactory.java public static Log getLog(Class? aClass) { return getLog(aClass.getName()); } public static Log getLog(String logger) { try { return logConstructor.newInstance(logger); } catch (Throwable t) { throw new LogException(Error creating logger for logger logger . Cause: t, t); } }通过logConstructor属性创建 Log 对象。而传入的参数是logger日志名。也就是说创建的 Log 对象最终打印的日志名就是logger。例如说org.apache.ibatis.logging.jdbc.BaseJdbcLogger。2.3 小结LogFactory 负责创建 Log 对象并基于适配器模式根据引入的日志框架创建对应的 Log 实现类。3. Logorg.apache.ibatis.logging.LogMyBatis Log 接口。代码如下// Log.java public interface Log { boolean isDebugEnabled(); boolean isTraceEnabled(); void error(String s, Throwable e); void error(String s); void debug(String s); void trace(String s); void warn(String s); }定义了 Log 的接口。3.1 Log 的实现类在logging包下有多个 Log 的实现类如下图所示Log 的实现类每个实现类对应一种日志框架。以Slf4jImpl举例子代码如下// Slf4jImpl.java public class Slf4jImpl implements Log { private Log log; public Slf4jImpl(String clazz) { // 使用 SLF4J 的 LoggerFactory 创建 org.slf4j.Logger 对象 Logger logger LoggerFactory.getLogger(clazz); // 如果是 LocationAwareLogger 类型则创建 Slf4jLocationAwareLoggerImpl 对象 if (logger instanceof LocationAwareLogger) { try { // check for slf4j 1.6 method signature logger.getClass().getMethod(log, Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class); log new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger); return; } catch (SecurityException e) { // fail-back to Slf4jLoggerImpl } catch (NoSuchMethodException e) { // fail-back to Slf4jLoggerImpl } } // Logger is not LocationAwareLogger or slf4j version 1.6 // 否则创建 Slf4jLoggerImpl 对象 log new Slf4jLoggerImpl(logger); } Override public boolean isDebugEnabled() { return log.isDebugEnabled(); } // ... 省略其它方法 }在构造方法中我们可以看到传入的clazz参数作为创建org.slf4j.Logger对象的日志名。这样org.apache.ibatis.logging.LogFactory就被替换成具体的类名了。并且根据创建的org.slf4j.Logger的类型创建对应的log属性。那么Slf4jLocationAwareLoggerImpl 和 Slf4jLoggerImpl 有什么区别呢因为org.slf4j.Logger接口的实现类可能不是org.slf4j.spi.LocationAwareLogger类型那么就无法使用LocationAwareLogger#log(Marker marker, String fqcn, int level, String message, Object[] argArray, Throwable t)方法那么就只能使用Slf4jLoggerImpl类基于org.slf4j.Logger来实现。 可能有点绕胖友自己瞅瞅代码就明白了。其它 Log 的实现类也是类似的思路胖友自己查看。4. 代理在logging包下还有基于 JDK 实现代理的类打印 Connection、Statement、PreparedStatement、ResultSet、JDBC 等等的日志。这块我们在后续的文章中详细解析。5. 总结本文我们分享了 MyBatis 的日志模块对应logging包。总结如下1、LogFactory 负责创建 Log 对象并基于适配器模式根据引入的日志框架创建对应的 Log 实现类。2、每个 Log 的实现类对应一种日志框架。并且在构造方法中会传入日志名从而实现打印的日志名是具体的类名。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。