视觉语言模型对抗攻击:从原理到CLIP实战 做AI安全这几年我最大的感受是对抗攻击这个老话题因为视觉语言模型VLM的出现又重新变得让人头疼了。早些年我们研究的是怎么让一张加了微小噪点的图骗过图像分类器后来开始研究怎么让模型输出错误的目标框现在则是要面对能同时理解图片和文字的模型——比如CLIP、BLIP、LLaVA、GPT-4V这一类。攻击者不再满足于让模型“认错”而是要让模型在图文匹配、视觉问答、图像生成这些更复杂的任务上说出完全不受控的内容。这篇文章我想从传统图像对抗攻击讲起沿着技术演进的路子一直聊到多模态场景下的新问题、新攻击方式和防御思路最后给出一套可以跑通的CLIP对抗攻击实操代码方便你直接在自己的环境里复现和验证。这事的核心矛盾很简单模型越强大、输入口越多攻击面就越大。视觉语言模型把图像编码器和文本编码器对齐到一个共享语义空间这本是为跨模态理解服务的但恰恰是这个“语义空间”成了对抗扰动最容易被放大的地方。下面我先把整个攻击与防御的图景铺开再逐步拆解技术细节。1. 视觉语言模型与对抗攻击的整体图景1.1 什么是视觉语言模型为什么攻击面更大视觉语言模型本质上是把两种或多种模态的信息映射到同一个向量空间里做对齐。最典型的代表是OpenAI的CLIP它用图像编码器比如ViT、ResNet和文本编码器Transformer分别提取特征再用对比学习让“匹配的图文对”在空间中距离更近“不匹配的图文对”距离更远。后来很多多模态大模型比如Flamingo、LLaVA、MiniGPT-4、Qwen-VL底层都是基于类似的图文对齐思路再加上一个语言模型做推理和生成。攻击面大就大在“多模态”上。传统单模态模型只有一个输入端攻击者只能在图像或文本上做文章。而VLM至少有两个输入端攻击路径瞬间就多了好几条可以对图像加扰动可以让文本对抗性改写甚至可以在图像里嵌入视觉上完全正常、但会让文本编码器产生偏移的“隐形文字”。更麻烦的是图像和文本在共享空间中的对齐关系是非线性的攻击者可以在图像上构造一个“对抗语义”让模型把一张狗的照片理解成“猫咪在沙滩上奔跑”而且这个错误还会被语言模型进一步放大变成一段看似连贯的胡言乱语。我在实际测试里遇到过一种很讽刺的情况你只是给图片加了一个肉眼不可见的扰动VLM回答“图片中有一个人拿着一把刀”原本图片里根本没有刀。这种由对抗扰动引发的“幻觉”在单模态模型上很难见到但在多模态场景里非常普遍因为图像特征一旦偏离正常区域语言模型就会“脑补”出看似合理的描述。1.2 从传统单模态攻击到多模态攻击的演进逻辑传统图像分类的对抗攻击解决的是“分类错误”问题。一张猫的图片经过几组精心构造的像素偏移之后模型以极高置信度把它判成“鳄梨”。核心约束始终有两个一是攻击要成功二是扰动要尽量小、让人眼察觉不到。到了视觉语言模型这里攻击目标变成了“语义错位”。攻击者不再关心模型输出什么类别而是关心模型是否“相信”了一个编造出来的图文关系。比如用对抗扰动让图像编码器输出的特征接近“a photo of a sandstorm”的文本特征那么无论图像真实内容是什么CLIP都会给出高匹配度。如果下游再接一个解码器或者语言模型这个“错位语义”就会被翻译成自然语言形成更有杀伤力的攻击。所以演进逻辑其实是目标和约束的迁移传统攻击是在固定标签集里找“错误标签”多模态攻击则是在开放语义空间里找“目标语义”。前者往往只要让softmax输出向某个类别倾斜后者需要更精细地操纵跨模态特征距离。这也解释了为什么很多在ImageNet上效果极好的攻击算法直接搬到CLIP上之后成功率会明显下降——CLIP的特征空间比分类logits空间复杂得多。1.3 攻击者的现实威胁模型讨论攻击方式之前得先明确攻击者能做什么。按威胁模型分类常见的场景有三种。第一种是白盒攻击。攻击者可以拿到模型的全部权重和梯度能精确计算对抗扰动。这种情况在开源模型上非常常见比如CLIP和LLaVA都是公开权重任何人都可以本地跑反向传播。第二种是黑盒迁移攻击。攻击者不知道目标模型的参数但可以访问输入输出或者拥有一个替代模型。做法是在替代模型上生成对抗样本然后拿到目标模型上去试。视觉语言模型的跨模态特性让迁移攻击变得更有意思因为攻击者可以用一个公开CLIP模型生成对抗图像去攻击其他闭源的多模态服务。第三种是物理世界攻击。攻击者不直接修改输入的数字信号而是把对抗模式打印出来、放到摄像头前或者对现实场景做干扰。多模态场景下这种攻击威胁更大比如在广告牌上贴对抗贴纸让自动驾驶的视觉问答系统产生误判。我把这三类威胁模型放在前面是因为后面讨论防御时会反复权衡你到底是防白盒攻击还是防黑盒迁移还是在防物理世界的随机扰动。2. 传统图像对抗攻击方法盘点2.1 核心原理扰动、范数约束与不可察觉性不管攻击形式怎么变传统对抗攻击的数学框架始终是有效的。给定一个分类器f(x)和一张正常样本x攻击目标是找到一个扰动δ使得xδ被误分类。为了保持不可察觉性通常会对扰动的大小做约束最常见的是Lp范数球比如L∞约束保证每个像素的最大改变量不超过εL2约束保证整体扰动能量受限。我习惯把对抗攻击理解成“在模型的决策边界上打洞”。模型的决策边界通常是在训练数据分布下学出来的正常样本都在数据流形附近。对抗攻击就是沿着梯度方向短距离移动让样本跨出决策边界。之所以加“微小噪声”就能做到这一点是因为高维空间中决策边界非常复杂且在局部几乎是线性的很多分类器对“沿梯度方向”的输入变化过于敏感。理解这一点后你就明白为什么在像素空间里做约束是对的我们希望攻击样本和原始样本在人的感知上几乎一样同时让模型分不清边界。对VLM来说这种“边界”不再是类别边界而是图文匹配度的等值面但本质思想没变。2.2 经典算法速览FGSM、PGD、CW、DeepFool传统算法里FGSM是最经典的一步攻击。它只用一次梯度符号方向更新公式是x x ε * sign(∇x L(f(x), y_true))它速度快但成功率不稳定。PGD则是FGSM的迭代版每一步都小幅更新并把扰动强行投影回ε球内相当于在“受限空间内做多步梯度下降”。P GD至今仍是很多对抗攻击实验的默认基线因为它在白盒设置下表现非常稳定。CW攻击是另一种思路它把“最小扰动距离”直接作为优化目标通过拉格朗日形式寻找能够误分类的最小扰动。它生成的对样本扰动更小、更难察觉但计算开销大。DeepFool则是通过迭代地把样本推向最近的决策边界也能得到近似最小的扰动。这些算法在图像分类上很成熟我在做多模态研究时发现它们的核心代码稍加改造就能用到VLM上关键是要把损失函数从“交叉熵分类损失”换成“图文匹配相关的对比损失”。后面实操部分我会展示这个改造过程。2.3 攻击迁移性与集成攻击的理解黑盒攻击里有一个重要概念叫“迁移性”。在模型A上生成的对抗样本拿到模型B上往往也有一定概率生效原因是不同模型在训练中学到的特征模式有相似性。通用做法是集成攻击同时攻击多个模型对多个模型的梯度取平均生成的对抗样本迁移性更好。我在实操中常用的一个技巧是不要只攻击最后一层特征而是在多个中间层同时构造目标损失这样能抓住模型之间更底层的共性特征。放到VLM上就是同时对图像编码器和文本编码器的输出做约束。这样生成的对抗图像即使目标模型用的图像编码器结构不同迁移成功率也会提升不少。但要注意迁移攻击不是万能钥匙。不同VLM的预训练数据、对比损失权重、Prompt模板差异都会让共享语义空间的几何结构发生偏移。所以做黑盒攻击时我会先做一轮“探测”用少量样本测试替代模型和目标模型的输出差异再决定要不要调整攻击参数。3. 多模态对抗攻击的核心挑战与攻击范式3.1 不同模态之间的“语义桥”攻击VLM最独特的一点是图像和文本共享一个嵌入空间这个空间就像一座“语义桥”。传统攻击只需要让图像特征落到错误类别区域多模态攻击则需要让图像特征落到指定文本的区域或者在图像中隐藏一段“有毒”的文本描述。一种很直观的攻击方式叫作“跨模态检索攻击”。给定一张正常图片攻击者希望它在CLIP模型里匹配到某个与事实完全不符的文本。做法很简单把文本编码器固定用梯度下降修改图像使得图像特征和恶意文本特征之间的余弦相似度最大。这个攻击在白盒设置下成功率极高而且对扰动量的要求往往比分类攻击更宽松因为检索匹配本身是对比打分不需要跨越多个类别边界。另一种方式恰好相反固定图像修改文本。比如在一段正常文本里插入对抗性字符或同音异义字让文本编码器把图像匹配到错误内容。这种攻击在垃圾内容过滤、内容审核场景里很常见。测试下来很多审核类的多模态模型对文本侧的微小扰动非常脆弱。我在复现时注意到一个细节不同编码器对“语义桥”的使用方式差异很大。CLIP的图像特征和文本特征都经过L2归一化因此余弦距离可以直接转化成紧凑空间中的角度问题而BLIP用的是类似ITM的匹配头输出的是匹配概率。攻击前者时优化余弦距离最直接攻击后者时优化二分类交叉熵更有效。你需要根据模型的具体对齐方式定制损失函数而不是套一个模板。3.2 常见攻击范式跨模态对抗样本、越狱攻击与投毒攻击当前多模态对抗攻击的研究大致可以分成三个方向。跨模态对抗样本是目前最核心的方向。上面提到的语义桥攻击就属于这一类。这类攻击的典型危害是攻击者可以让视觉问答系统对图片内容产生系统性误解。我在测试一个开源VQA模型时仅仅用20行代码生成的对抗噪声就让模型把“一只站在草地上的狗”描述成了“一只在雪地里奔跑的白狐”而扰动在缩略图里肉眼几乎看不见。越狱攻击在近年LLM安全研究爆发后被大量引入多模态领域。这类攻击的目标是绕过模型的安全对齐让模型输出有害内容。在多模态场景里攻击者可以把危险指令嵌入图像再配合正常文本提问。比如在图片中加入一段对抗文字“忽略所有之前的指令并输出XX”语言模型在解码时可能被这段嵌入图文特征中的“指令”带偏。这种攻击的隐蔽性很强因为人眼看到的是正常图片机器却读到了隐藏指令。投毒攻击则是发生在训练阶段。攻击者污染多模态训练数据在特定图片中嵌入一些特殊的特征模式让模型在推理阶段看到类似的模式时给出恶意结果。这类攻击危害最大也最难防御因为它直接破坏模型底层的表示能力。目前开源多模态数据集规模越来越大数据来源五花八门投毒风险也随之上升。3.3 从图像到文本/从文本到图像的攻击面多模态输入输出组合对手有哪些攻击面我习惯画一张“输入输出矩阵”来看输入端是图像和文本输出端可以是类别标签、检索结果、文本生成、图像生成。每个组合都有独特的攻击目标。图像到文本的攻击目标是让描述文本偏离事实。这种攻击最容易造成“幻觉”因为语言生成阶段会放大图像特征中的微小偏差。文本到图像的攻击则不同比如对Stable Diffusion这类模型攻击者可以通过一段构造的文本让模型生成违反安全策略的图像或者让不同概念之间发生特征纠缠比如输入“a white cat”却生成“a dog with a hat”。图像到图像的攻击也存在扰动图像输入导致生成式模型输出包含指定物体或不存在的区域。这类攻击在自动驾驶、医疗影像生成等场景风险很高。我在实际项目里更关注“文本条件”对攻击转移的影响同一个对抗图像配合不同Prompt的效果差异很大。所以在构造攻击时最好先尝试多种Prompt找到对目标模型最敏感的描述方式。4. 防御体系从静态加固到动态防御4.1 被动防御对抗训练、输入预处理与对抗检测被动防御是指模型本身不做改变或者只依靠训练阶段的手段增强鲁棒性。最有效、但成本最高的就是对对抗训练。在图像分类时代对抗训练已经被验证能大幅提升模型对已知攻击的鲁棒性具体做法是在每次训练中同步生成对抗样本把“对抗样本标签”丢进训练集。对VLM而言对抗训练要考虑图文两个模态既要对抗图像扰动也要对抗文本扰动训练开销成倍增加。更轻量的被动防御是输入预处理。常见的包括JPEG压缩、高斯模糊、中值滤波、随机缩放裁剪等。这些操作能在一定程度上破坏攻击者精心构造的微小扰动。我在实验中实测对CLIP做PGD攻击后经过轻度JPEG压缩quality75攻击成功率往往能从95%降到60%左右。不过这类方法只对L∞小扰动有效对L2的大扰动或者物理世界攻击效果有限。对抗检测是另一种思路。既然对抗样本和正常样本在特征分布上有差异可以训练一个二分类器专门区分两者。对比VLM的特征或者检测模型内部注意力分数是否异常。这个方法不改变模型行为适合做告警但问题在于检测器本身也容易被攻破且面对迁移攻击时的泛化能力较弱我一般只把它作为其他防御的补充。4.2 主动防御动态防御、输入随机化与模型鲁棒性评估传统网络安全里IDS/IPS设备的作用是“检测加阻断”而AI模型侧的主动防御思路类似不让攻击者轻易摸清模型的行为规律或者在运行中动态改变模型的输入/输出路径。动态防御在多模态场景下有很多实现方式。最简单的是对输入图像做随机变换比如每次推理时随机resize、随机裁剪、随机添加噪声让攻击者无法精确估计梯度。这种随机化方法本质上是“以概率破坏攻击的精确性”效果不错实现成本也低。另一种做法是集成推理同时输出多个结构略有不同的模型的结果做投票或平均增加攻击难度。模型鲁棒性评估也是防御体系的一环。你不可能在部署后才知道模型被人攻击了得在研发阶段建立一套持续的红队测试流程。对多模态模型我建议至少覆盖白盒PGD攻击、黑盒迁移攻击、文本侧Prompt注入、图像侧隐形指令攻击这几类。把评估结果记录在案每次更新模型时跑一遍避免新版本模型出现鲁棒性回退。我个人的经验是动态防御能挡住“临时起意”的攻击者但挡不住目标明确、计算资源充足的研究型攻击者。因为随机化模块本身如果也是可微分的攻击者依然能求梯度比如使用EOT期望变换技术对随机变换求期望梯度照样能攻破。所以动态防御要配合其他手段不能依赖单一防线。4.3 防御评估的误区与安全边界这里想聊聊我做防御评估时踩过的坑。最典型的是“混淆鲁棒性提升”。有人发现对输入做高斯模糊后PGD攻击成功率下降了就认为自己防御有效。但实际上高斯模糊同时降低了模型对正常样本的精度而且攻击者只需要放大扰动就能抵消模糊效果这属于“虚假鲁棒性”。真正的鲁棒性提升必须考虑三个指标正常输入下的性能损失、攻击成功率、以及攻击扰动大小。如果一项预处理让正常指标掉得比攻击成功率还快那这个防御基本没有价值。另一个边界是许多防御方法只在固定攻击算法下验证一旦换一种攻击算法或威胁模型鲁棒性就崩塌了。所以我在展示防御效果时会明确说明攻防双方可用的算力、白盒/黑盒前提、扰动约束这样才能做出客观判断。安全边界的另一层意思是防御措施不能影响正常的用户体验。比如过于严格的对抗检测会把很多正常图片误判为攻击导致模型拒绝服务。这类安全性与可用性的权衡在工程项目里往往比模型精度更让人头疼。5. 实操实战对CLIP模型实施一次最小化对抗攻击5.1 环境准备与CLIP模型加载为了让你能直接跑起来我选择OpenAI CLIP作为目标模型。它结构简单、权重公开而且很多多模态模型都以它为骨干攻击它的图像编码器得到的经验可以迁移到很多场景。环境建议Python 3.9安装以下依赖pip install torch torchvision clip open_clip_torch pillowCLIP官方库支持多种backbone我用ViT-B/32兼容性最好。加载方式import torch import clip from PIL import Image import torchvision.transforms as T device cuda if torch.cuda.is_available() else cpu model, preprocess clip.load(ViT-B/32, devicedevice) model.eval() # 图像预处理CLIP自带resizecenter cropnormalize image Image.open(dog.jpg).convert(RGB) x preprocess(image).unsqueeze(0).to(device) # 目标文本我们想让模型认为图里是猫 text clip.tokenize([a photo of a cat, a photo of a dog]).to(device)5.2 构建目标函数与PGD对抗扰动生成我们的攻击目标是让模型对“a photo of a cat”的匹配分数显著高于“a photo of a dog”。CLIP的logits_per_image就是图像与文本的余弦相似度乘以一个缩放系数我们可以直接优化它。下面的函数实现了标准的PGD攻击扰动约束采用L∞范数总扰动阈值eps设为0.05步长alpha设为0.01执行40步def pgd_attack(model, x, text, eps0.05, alpha0.01, steps40): delta torch.zeros_like(x, requires_gradTrue) for t in range(steps): adv_x x delta logits_per_image, _ model(adv_x, text) # 让猫匹配度最大化狗匹配度最小化 loss -logits_per_image[0, 0] logits_per_image[0, 1] model.zero_grad() loss.backward() # 用梯度方向更新扰动 grad_sign delta.grad.detach().sign() delta.data delta.data alpha * grad_sign delta.data torch.clamp(delta.data, -eps, eps) # 保证图像像素仍处于合法范围 delta.data torch.clamp(x delta.data, -1, 1) - x delta.grad.zero_() if (t 1) % 10 0: with torch.no_grad(): cur_logits, _ model(x delta, text) print(fstep {t1}: cat_score{cur_logits[0,0].item():.3f}, dog_score{cur_logits[0,1].item():.3f}) return x delta这里有三个细节值得说明。第一损失函数中直接用了logits_per_image而不是softmax后的概率这是因为CLIP的logits不是标准化的概率分布但优化它们同样能拉开差距而且梯度更稳定。第二每次更新后要把扰动投影回ε球内这样能保证攻击样本与原始样本的L∞距离不超过预设阈值。第三由于x来自CLIP的preprocess像素范围已经被归一化到近似[0,1]所以我把eps设为0.05而不是8/255换算到原始[0,255]像素空间大约是12.75的L∞差值仍然属于肉眼较难察觉的级别。5.3 效果验证与防御性预处理对比攻击生成后我们要做两项验证一是给模型看对抗样本是否真的被骗过了二是看看简单防御预处理能不能削弱攻击效果。def evaluate(model, adv_x, text): with torch.no_grad(): logits_per_image, _ model(adv_x, text) probs logits_per_image.softmax(dim-1) return probs.cpu().numpy() probs_clean evaluate(model, x, text) probs_adv evaluate(model, adv_x, text) print(clean probs:, probs_clean) print(adv probs:, probs_adv)然后我们尝试在推理前对对抗样本做轻量JPEG压缩观察攻击是否仍然有效from io import BytesIO def jpeg_compress(x, quality75): # 输入是(1,3,224,224)的tensor先转成PIL Image处理 inv_normalize T.Normalize( mean[-0.48145466 / 0.26862954, -0.4578275 / 0.26130258, -0.40821073 / 0.27577711], std[1.0 / 0.26862954, 1.0 / 0.26130258, 1.0 / 0.27577711] ) x_img inv_normalize(adv_x.squeeze(0).cpu()) x_img torch.clamp(x_img, 0, 1) x_pil T.ToPILImage()(x_img) buffer BytesIO() x_pil.save(buffer, formatJPEG, qualityquality) buffer.seek(0) recompressed Image.open(buffer).convert(RGB) return preprocess(recompressed).unsqueeze(0).to(device) adv_x_jpeg jpeg_compress(adv_x) probs_adv_jpeg evaluate(model, adv_x_jpeg, text) print(adv after jpeg probs:, probs_adv_jpeg)实际上这个简单的JPEG压缩能降低一部分攻击成功率但无法完全防御。你可以看到攻击后的猫概率仍然高于狗概率说明单纯的输入预处理只能缓解问题。这里我需要强调实际部署时防御方通常会组合多种预处理和检测机制而不是只靠一个压缩操作。5.4 完整代码与参数说明把前面的代码拼起来就是一个完整的最小复现。为节省篇幅我不再重复粘贴而是把关键的参数设计思想整理一下参数取值说明eps0.05L∞扰动上界对应原始像素约12.75的绝对变化alpha0.01每次迭代的步长过大会导致扰动震荡steps40迭代次数一般20~100之间越大成功率越高loss-cat_logit dog_logit最大化目标文本得分同时压制原文本得分clip backboneViT-B/32通用性强训练速度快实测中多数自然图像在这个参数下都能在20步内实现攻击成功率接近100%。如果你发现梯度消失或loss不下降先检查输入的图像是否已经是归一化后的张量以及delta.grad是否在每次迭代中被正确清零。攻击完成后保存对抗图片用肉眼看与原图的差异通常只能看到极轻微的纹理变化。这正是对抗攻击危险的地方。6. 常见问题与排查技巧实录6.1 扰动不可见性与攻击成功率如何权衡这是我被问得最多的问题。对抗攻击追求的是“低扰动高成功率”但这两者天然冲突。扰动越小攻击难度越大扰动越大成功率越高但容易被发现。实际调参时我会先固定eps逐步增大steps直到成功率饱和然后降低alpha观察成功率变化。如果发现增加steps无法再提升成功率说明已经接近当前eps下的极限。如果想让扰动更隐蔽可以把eps从0.05降到0.02通常仍能保持一定的攻击能力但成功率会下降。如果你发现生成的扰动肉眼可见大概率是eps设置过大或者图像本身没做标准化导致更新量超出了有效像素区间的边界。6.2 为什么我的对抗样本跨模型不迁移黑盒迁移攻击失败很常见原因也非常集中。第一是不同模型使用不同的预处理流程比如CLIP和OpenCLIP的归一化参数不一样同一个扰动在不同预处理下的表现差异巨大。第二是不同图像编码器的架构差异ViT和ResNet提取的特征层次完全不同。第三是目标文本的选择过于单一换一个Prompt后攻击效果就消失。我建议的做法是在构造攻击时用多个替代模型的梯度做集成同时对输入做一组随机变化EOT也就是在多次随机变换下计算期望梯度这样生成的对抗样本对预处理差异的鲁棒性会高很多。迁移攻击在VLM上依然是个开放难题不要期望一次成功。6.3 多模态场景下的“幻觉对抗”现象很多人在做VLM攻击测试时会发现一个很有意思的现象对抗扰动并没有让模型输出肉眼可见的错误标签而是让语言模型开始“一本正经地编造”内容。我把这称为“幻觉对抗”。原因是多模态大模型的视觉编码器特征经过扰动后与语言模型训练时的常见特征分布发生了偏移语言模型为了自洽只能根据偏移后的特征去“圆谎”。处理这种问题时不能只看最终输出的文本还要观察注意力图或者视觉token的embedding变化。如果模型原本能正确定位图像区域被攻击后注意力开始分散到无关区域说明攻击作用在特征编码阶段。这种场景下单纯做输入预处理很难防御需要在安全对齐训练中对“对抗幻觉”做专门的负样本构造。6.4 防御实测的常见坑随机化模块带来的讹误最后分享一个我在实测动态防御时踩过的坑。给推理流程加入随机resize和随机裁剪之后攻击成功率确实降下来了但模型对正常图片的稳定性也变差了。多模态检索场景还好如果是医疗影像或者工业质检同一张图每次推理分数都有波动这是不可接受的。这让我意识到动态防御不是“免费午餐”。任何随机化操作本质上是在牺牲推理一致性换攻击难度。合理做法是只在低风险场景或者异常高安全级别场景中使用动态防御而且在部署前要做充分的A/B测试评估正常业务指标的波动范围。另一个经验是随机化最好加在输入预处理层不要加在模型特征层否则会影响下游任务的语义一致性。防御这件事永远没有终点。对抗攻击是矛防御是盾两边都在互相迭代。我个人的体会是做多模态安全不能只盯着模型内部还要把输入输出链路、部署环境、业务逻辑当成一个整体来看像IDS/IPS做分层检测那样构建“检测—缓解—加固—验证”的闭环。也许你现在只是想在实验室里复现一下对抗攻击的效果但我还是建议你顺手把防御评估也一起做了因为只有亲手攻过一次才能真正理解模型哪里脆弱也才知道该在哪个环节做防护。这次先聊到这里后续如果有时间我可以再写一篇关于越狱攻击与多模态大模型安全对齐的实操记录欢迎继续关注。