当前位置: 首页 > news >正文

Chronos:学习时间序列的大语言模型(代码解析)

前言

  • 《Chronos: Learning the Language of Time Series》原文地址,Github开源代码地址
  • Chronos:学习时间序列的大语言模型(论文解读)CSDN地址
  • GitHub项目地址Some-Paper-CN。本项目是译者在学习长时间序列预测、CV、NLP和机器学习过程中精读的一些论文,并对其进行了中文翻译。还有部分最佳示例教程
  • 如果有帮助到大家,请帮忙点亮Star,也是对译者莫大的鼓励,谢谢啦~
  • 本文代码已同步至项目Some-Paper-CN,后续可能会根据热度发布使用LoRA微调Chronos模型教程,浅浅期待一下吧~

先验知识

  • 建议先阅读Chronos论文解读篇,对大致原理有所了解,阅读代码效果会更好。
  • 在论文解读篇中,我们已经知道了Chronos是基于Google的开源模型T5(Huggingface)。因受篇幅影响,有关T5模型的解析不在本次讨论范围内,感兴趣的小伙伴可以去查询相关资料。
  • 论文基于Transformers框架,在阅读代码前,最好有一定Transformers库的基础知识。
  • 虽然本文模型为时间序列模型,但不管是在模型架构、训练方式还是数据组织上都与大语言模型几乎一致,在阅读代码前,最好有一定大语言模型领域的知识,比如术语tonkentokenizer

代码解析

  • 将开源代码从Github上下载到本地,关键文件在chronos-forecasting/src/chronos下,chronos.py文件。
  • ChronosConfig用于加载模型参数(注意!是参数不是权重),类ChronosTokenizer用于加载模型Tokenizer,类ChronosModel用于根据模型参数构建模型。上述类为Transformers库基础类,这里不多赘述。
  • 论文中的核心在类MeanScaleUniformBins用于数据均值缩放和量化分箱,类ChronosPipeline用于构架数据预测管道。

MeanScaleUniformBins

class MeanScaleUniformBins(ChronosTokenizer):def __init__(self, low_limit: float, high_limit: float, config: ChronosConfig) -> None:self.config = config# 线性平分向量torch.linspace(start, end, steps)self.centers = torch.linspace(low_limit,high_limit,config.n_tokens - config.n_special_tokens - 1,)# 首尾元素分别为-1e20、1e20# self.centers[1:]除第1个元素外的所有元素# self.centers[:-1]除最后1个元素外的所有元素# (self.centers[1:] + self.centers[:-1]) / 2表示相邻元素平均值self.boundaries = torch.concat((torch.tensor([-1e20], device=self.centers.device),(self.centers[1:] + self.centers[:-1]) / 2,torch.tensor([1e20], device=self.centers.device),))def input_transform(self, context: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:batch_size, length = context.shapeif length > self.config.context_length:# 保留最后context_length个元素context = context[..., -self.config.context_length :]# 空值的反向布尔值attention_mask = ~torch.isnan(context)# context绝对值和attention_mask的点积,除以attention_mask的和scale = torch.nansum(torch.abs(context) * attention_mask, dim=-1) / torch.nansum(attention_mask, dim=-1)# scale是0或空值设为1.0scale[~(scale > 0)] = 1.0# 将context按scale缩放scaled_context = context / scale.unsqueeze(dim=-1)# torch.bucketize根据边界值将输入映射到相应bucket(桶)中token_ids = (torch.bucketize(input=scaled_context,boundaries=self.boundaries,right=True,)+ self.config.n_special_tokens)# 不需要关注的地方使用paddingtoken_ids[~attention_mask] = self.config.pad_token_id# 如果需要在末尾添加eos符if self.config.use_eos_token:eos_tokens = torch.full((batch_size, 1), fill_value=self.config.eos_token_id)token_ids = torch.concat((token_ids, eos_tokens), dim=1)# mask置为trueeos_mask = torch.full((batch_size, 1), fill_value=True)attention_mask = torch.concat((attention_mask, eos_mask), dim=1)return token_ids, attention_mask, scaledef output_transform(self, samples: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:# 将scale扩展两个维度scale_unsqueezed = scale.unsqueeze(-1).unsqueeze(-1)# 将值限制在0和centers长度间,确保索引值不超出centersindices = torch.clamp(samples - self.config.n_special_tokens,min=0,max=len(self.centers) - 1,)# 返回在原始context缩放级别下分桶值return self.centers[indices] * scale_unsqueezed
  • low_limithigh_limit包含在模型参数中,根据论文分别为-1515

  • input_transform函数中scale = torch.nansum(torch.abs(context) * attention_mask, dim=-1) / torch.nansum(attention_mask, dim=-1)看上去非常复杂,实际上在没有空值的情况下,相当于对序列求平均值。

  • input_transform函数中分箱函数torch.bucketize的使用可以参考官方文档。

  • input_transform函数中空值使用padding填充,并使用mask进行遮掩,是大语言模型训练的常用操作。

  • 在论文中,作者表示为了保持与大语言模型训练方式保持一致,会在序列结束后放置eos标识符,所以模型参数use_eos_token是为True的。

  • output_transform函数是input_transform函数的反操作,需要注意的是torch.clamp函数,确保token_id在词表中,否则就无法反归一化得到正常的值了。

ChronosPipeline

  • from_pretrained函数用于加载模型预训练权重,这里不在过多赘述,关键在于predict函数。
    def predict(self,context: Union[torch.Tensor, List[torch.Tensor]],prediction_length: Optional[int] = None,num_samples: Optional[int] = None,temperature: Optional[float] = None,top_k: Optional[int] = None,top_p: Optional[float] = None,limit_prediction_length: bool = True,) -> torch.Tensor:"""Get forecasts for the given time series.Parameters----------contextInput series. This is either a 1D tensor, or a listof 1D tensors, or a 2D tensor whose first dimensionis batch. In the latter case, use left-padding with``torch.nan`` to align series of different lengths.prediction_lengthTime steps to predict. Defaults to what specifiedin ``self.model.config``.num_samplesNumber of sample paths to predict. Defaults to whatspecified in ``self.model.config``.temperatureTemperature to use for generating sample tokens.Defaults to what specified in ``self.model.config``.top_kTop-k parameter to use for generating sample tokens.Defaults to what specified in ``self.model.config``.top_pTop-p parameter to use for generating sample tokens.Defaults to what specified in ``self.model.config``.limit_prediction_lengthForce prediction length smaller or equal than thebuilt-in prediction length from the model. True bydefault. When true, fail loudly if longer predictionsare requested, otherwise longer predictions are allowed.Returns-------samplesTensor of sample forecasts, of shape(batch_size, num_samples, prediction_length)."""context_tensor = self._prepare_and_validate_context(context=context)if prediction_length is None:prediction_length = self.model.config.prediction_lengthif prediction_length > self.model.config.prediction_length:msg = (f"We recommend keeping prediction length <= {self.model.config.prediction_length}. ""The quality of longer predictions may degrade since the model is not optimized for it. ")if limit_prediction_length:msg += "You can turn off this check by setting `limit_prediction_length=False`."raise ValueError(msg)warnings.warn(msg)predictions = []remaining = prediction_lengthwhile remaining > 0:# 根据MeanScaleUniformBins类对数据进行缩放和分箱token_ids, attention_mask, scale = self.tokenizer.input_transform(context_tensor)# 输入模型得到结果samples = self.model(token_ids.to(self.model.device),attention_mask.to(self.model.device),min(remaining, self.model.config.prediction_length),num_samples,temperature,top_k,top_p,)prediction = self.tokenizer.output_transform(samples.to(scale.device), scale)predictions.append(prediction)remaining -= prediction.shape[-1]# 判断是否预测完if remaining <= 0:break# 拼接操作context_tensor = torch.cat([context_tensor, prediction.median(dim=1).values], dim=-1)return torch.cat(predictions, dim=-1)
  • 作者建议将prediction length保持在64以下,因为模型没有针对较长的预测长度进行优化,因此预测质量可能会下降。
  • 预测过程为:根据MeanScaleUniformBins类中input_transform函数对数据进行缩放和分箱,得到token_id、掩码矩阵attention_mask, 均值scale;将token_id和掩码矩阵attention_mask输入模型,得到输出samples。根据MeanScaleUniformBins类中output_transform函数和均值scale将输出samples反归一化得到实际值。
  • remaining变量用于检验prediction length是否全部预测完。

left_pad_and_stack_1D

  • 上述代码中函数predict调用了_prepare_and_validate_context函数,本质是left_pad_and_stack_1D函数。
def left_pad_and_stack_1D(tensors: List[torch.Tensor]):# tensors中最长元素的长度max_len = max(len(c) for c in tensors)padded = []# 遍历tensors中元素for c in tensors:assert isinstance(c, torch.Tensor)# c为一维张量assert c.ndim == 1# 填充torch.nanpadding = torch.full(size=(max_len - len(c),), fill_value=torch.nan, device=c.device)# 拼接(c长度被扩展为max_len),并添加到列表padded中padded.append(torch.concat((padding, c), dim=-1))# 将padded列表中的所有元素沿着新维度折叠,形成二维张量return torch.stack(padded)
  • 该函数是大语言模型训练过程中为了补齐长度做的操作,如果不理解也没事,只要明白在干什么就行。

测试Demo

  • 如果想要进一步了解代码,还是希望大家用一个轻量的测试Demo从头到尾Debug一下。
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
from chronos import ChronosPipelinepipeline = ChronosPipeline.from_pretrained("amazon/chronos-t5-tiny",device_map="cpu",torch_dtype=torch.float16,
)df = pd.read_csv("AirPassengers.csv")# context must be either a 1D tensor, a list of 1D tensors,
# or a left-padded 2D tensor with batch as the first dimension
context = torch.tensor(df["#Passengers"])
prediction_length = 12
forecast = pipeline.predict(context,prediction_length,num_samples=20,temperature=1.0,top_k=50,top_p=1.0,
) # forecast shape: [num_series, num_samples, prediction_length]# visualize the forecast
forecast_index = range(len(df), len(df) + prediction_length)
low, median, high = np.quantile(forecast[0].numpy(), [0.1, 0.5, 0.9], axis=0)plt.figure(figsize=(8, 4))
plt.plot(df["#Passengers"], color="royalblue", label="historical data")
plt.plot(forecast_index, median, color="tomato", label="median forecast")
plt.fill_between(forecast_index, low, high, color="tomato", alpha=0.3, label="80% prediction interval")
plt.legend()
plt.grid()
plt.show()
  • 预测结果效果图

请添加图片描述

相关文章:

Chronos:学习时间序列的大语言模型(代码解析)

前言 《Chronos: Learning the Language of Time Series》原文地址&#xff0c;Github开源代码地址Chronos&#xff1a;学习时间序列的大语言模型&#xff08;论文解读&#xff09;CSDN地址GitHub项目地址Some-Paper-CN。本项目是译者在学习长时间序列预测、CV、NLP和机器学习…...

云南区块链商户平台优化开发

背景 云南区块链商户平台是全省统一区块链服务平台。依托于云南省发改委、阿里云及蚂蚁区块链的国内首个省级区块链平台——云南省区块链平台同步上线&#xff0c;助力数字云南整体升级。 网页版并不适合妈妈那辈人使用&#xff0c;没有记忆功能&#xff0c;于是打算自己开发…...

深圳六西格玛培训:引领职场“薪”途无限

在追求职业发展和薪资增长的道路上&#xff0c;不断学习和提升自我是至关重要的。深圳&#xff0c;这座充满活力和创新精神的城市&#xff0c;为职场人士提供了众多学习和提升的机会。其中&#xff0c;六西格玛培训以其独特的价值&#xff0c;吸引了众多职场人士的目光。张驰咨…...

Spark云计算平台Databricks使用,创建workspace和Compute计算集群(Spark集群)

Databricks&#xff0c;是属于 Spark 的商业化公司&#xff0c;由美国加州大学伯克利 AMP 实验室的 Spark 大数据处理系统多位创始人联合创立。Databricks 致力于提供基于 Spark 的云服务&#xff0c;可用于数据集成&#xff0c;数据管道等任务。 1 创建workspace 点击创建wor…...

银河麒麟服务器系统audit服务组件升级、进程彻底关闭介绍

银河麒麟服务器系统audit服务组件升级、进程彻底关闭介绍 一 系统环境二 组件升级2.1 联网升级audit2.1.1 配置外网源&#xff08;默认配置如下&#xff0c;不用修改&#xff09;2.1.2 通过dnf命令进行升级&#xff08;未指定版本的话会升级到最新se.12版本&#xff0c;建议升级…...

设计模式——装饰者模式(Decorator)

装饰者模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许你动态地给一个对象添加一些额外的职责&#xff0c;就增加功能来说&#xff0c;装饰者模式相比生成子类更为灵活。在装饰者模式中&#xff0c;一个装饰类会包装一个对象&#xff08…...

力扣:406. 根据身高重建队列

406. 根据身高重建队列 假设有打乱顺序的一群人站成一个队列&#xff0c;数组 people 表示队列中一些人的属性&#xff08;不一定按顺序&#xff09;。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi &#xff0c;前面 正好 有 ki 个身高大于或等于 hi 的人。 请你重新构…...

Docker 怎么将映射出的路径设置为非root用户权限

在Docker中&#xff0c;容器的根文件系统默认是由root用户拥有的。如果想要在映射到宿主机的路径时设置为非root用户权限&#xff0c;可以通过以下几种方式来实现&#xff1a; 1. 使用具有特定UID和GID的非root用户运行容器&#xff1a; 在运行容器时&#xff0c;你可以使用-u…...

Linux——进程的优先级、ACL

一、系统性能调优 Redhat7和centos7默认安装并启动了tuned服务 实验 [rootuser ~]# tuned-adm list //查看所有的调优方案 [rootuser ~]# tuned-adm recommend // 查看推荐的调优方案 virtual-guest 适用于作为虚拟机客户机运行的设备&#xff0…...

【C++】STL-list模拟实现

目录 1、本次需要实现的3个类即接口总览 2、list的模拟实现 2.1 链表结点的设置以及初始化 2.2 链表的迭代器 2.3 容量接口及默认成员函数 1、本次需要实现的3个类即接口总览 #pragma once #include<iostream> #include<assert.h> using namespace std; templ…...

Java 7大排序

&#x1f435;本篇文章将对数据结构中7大排序的知识进行讲解 一、插入排序 有一组待排序的数据array&#xff0c;以升序为例&#xff0c;从第二个数据开始&#xff08;用tmp表示&#xff09;依次遍历整组数据&#xff0c;每遍历到一个数据都再从tmp的前一个数据开始&#xff0…...

vue3 - 图灵

目录 vue3简介整体上认识vue3项目创建Vue3工程使用官方脚手架创建Vue工程[推荐] 主要⼯程结构 数据双向绑定vue2语法的双向绑定简单表单双向绑定复杂表单双向绑定 CompositionAPI替代OptionsAPICompositionAPI简单不带双向绑定写法CompositionAPI简单带双向绑定写法setup简写⽅…...

java设计模式八 享元

享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过共享技术有效地支持大量细粒度的对象。这种模式通过存储对象的外部状态在外部&#xff0c;而将不经常变化的内部状态&#xff08;称为享元&#xff09;存储在内部&#xff0c;以此来减…...

ELK原理详解

ELK原理详解 一、引言 在当今日益增长的数据量和复杂的系统环境中&#xff0c;日志数据的收集、存储、分析和可视化成为了企业运营和决策不可或缺的一部分。ELK&#xff08;Elasticsearch、Logstash、Kibana&#xff09;堆栈凭借其高效的性能、灵活的扩展性和强大的功能&…...

多线程学习Day09

10.Tomcat线程池 LimitLatch 用来限流&#xff0c;可以控制最大连接个数&#xff0c;类似 J.U.C 中的 Semaphore 后面再讲 Acceptor 只负责【接收新的 socket 连接】 Poller 只负责监听 socket channel 是否有【可读的 I/O 事件】 一旦可读&#xff0c;封装一个任务对象&#x…...

第33次CSP认证Q1:词频统计

&#x1f344;题目描述 在学习了文本处理后&#xff0c;小 P 对英语书中的 &#x1d45b;n 篇文章进行了初步整理。 具体来说&#xff0c;小 P 将所有的英文单词都转化为了整数编号。假设这 &#x1d45b;n 篇文章中共出现了 &#x1d45a;m 个不同的单词&#xff0c;则把它们…...

pytorch加载模型出现错误

大概的错误长下面这样&#xff1a; 问题出现的原因&#xff1a; ​很明显&#xff0c;我就是犯了第一种错误。 网上的修改方法&#xff1a; 我觉得按道理哈&#xff0c;确实&#xff0c;蓝色部分应该是可以把问题解决了的​。​但是我没有解决&#xff0c;因为我犯了另外一个错…...

如何在Mac上恢复格式化硬盘的数据?

“嗨&#xff0c;我格式化了我的一个Mac硬盘&#xff0c;而没有使用Time Machine备份数据。这个硬盘被未知病毒感染了&#xff0c;所以我把它格式化为出厂设置。但是&#xff0c;我忘了备份我的文件。现在&#xff0c;我想恢复格式化的硬盘驱动器并恢复我的文档&#xff0c;您能…...

华为OD机试 - 手机App防沉迷系统(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…...

搜维尔科技:光学动作捕捉系统用于城市公共安全智慧感知实验室

用户名称&#xff1a;西安科技大学 主要产品&#xff1a;Optitrack Priime41 光学动作捕捉系统&#xff08;8头&#xff09; 在6米8米的空间内&#xff0c;通过8个Optitrack Priime41光学动作捕捉镜头&#xff0c;对人体动作进行捕捉&#xff0c;得到用户想要的人体三维空间坐…...

保研面试408复习 4——操作系统、计网

文章目录 1、操作系统一、文件系统中文件是如何组织的&#xff1f;二、文件的整体概述三、UNIX外存空闲空间管理 2、计算机网络一、CSMA/CD 协议&#xff08;数据链路层协议&#xff09;二、以太网MAC帧MTU 标记文字记忆&#xff0c;加粗文字注意&#xff0c;普通文字理解。 1、…...

实战攻防中关于文档的妙用

一、PPT钓鱼 简单制作一个用于钓鱼的PPTX文件 一般那种小白不知道PPT也能拿来钓鱼&#xff0c;这里主要是借用PPT中的”动作按钮”, 我们在插入的地方&#xff0c;选择“动作按钮” 然后在弹出的窗口处&#xff1a; 比如填入上线CS的语句&#xff1a;powershell.exe -nop -w …...

【使用ChatGPT的API之前】OpenAI API提供的可用模型

文章目录 一. ChatGPT基本概念二. OpenAI API提供的可用模型1. InstructGPT2. ChatGPT3. GPT-4 三. 在OpenAI Playground中使用GPT模型-ing 在使用GPT-4和ChatGPT的API集成到Python应用程序之前&#xff0c;我们先了解ChatGPT的基本概念&#xff0c;与OpenAI API提供的可用模型…...

【C语言】模拟实现深入了解:字符串函数

&#x1f525;引言 本篇将模拟实现字符串函数&#xff0c;通过底层了解更多相关细节 &#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自…...

钩子函数onMounted定义了太多访问MySQL的操作 导致数据库异常

先放几种后端遇到的异常&#xff0c;多数和数据库有关 pymysql.err.InternalError: Packet sequence number wrong - got 102 expected 1 127.0.0.1 - - [09/May/2024 17:49:37] "GET /monitorLastTenList HTTP/1.1" 500 AttributeError: NoneType object has no at…...

Excel文件解析---超大Excel文件读写

1.使用POI写入 当我们想在Excel文件中写入100w条数据时&#xff0c;使用XSSFWorkbook进行写入时会发现&#xff0c;只有将100w条数据全部加载到内存后才会用write()方法统一写入&#xff0c;效率很低&#xff0c;所以我们引入了SXXFWorkbook进行超大Excel文件读写。 通过设置 …...

TypeScript基础:类型系统介绍

TypeScript基础&#xff1a;类型系统介绍 引言 TypeScript&#xff0c;作为JavaScript的一个超集&#xff0c;引入了类型系统&#xff0c;这为开发大型应用程序带来了诸多好处。本文将介绍TypeScript类型系统的基础知识&#xff0c;帮助初学者理解其概念和用法。 基础知识 …...

【Unity】Unity项目转抖音小游戏(一) 项目转换

UnityWEBGL转抖音小游戏流程 业务需求&#xff0c;开始接触一下抖音小游戏相关的内容&#xff0c;开发过程中记录一下流程。 相关参考&#xff1a; 抖音文档&#xff1a;https://developer.open-douyin.com/docs/resource/zh-CN/mini-game/develop/guide/game-engine/rd-to-SC…...

element-ui 中修改loading加载样式

element-ui 中的 loading 加载功能&#xff0c;默认是全屏加载效果 设置局部&#xff0c;需要自定义样式或者修改样式&#xff0c;方法如下&#xff1a; import { Loading } from element-uiVue.prototype.$baseLoading (text) > {let loadingloading Loading.service({…...

QT登录界面,(页面的切换)

以登陆界面为例&#xff0c;&#xff08;QDialog&#xff09; 1.主界面先构造login 的对话框类 int main(int argc, char *argv[]) {QApplication a(argc, argv);//先显示Login的界面Study_Login_Dialog login;............ }2.Login的类&#xff0c;可以用自定义的信号&#…...

网站建设发票/制作公司官网多少钱

目录 摘要 I ABSTRACT II 第一章 设计任务及方案分析 1 1.1 设计任务及要求 1 1.2 设计总体方案及方案论证 1 1.3 温度测量的方案与分析 1 1.31芯片选择 1 1.32实现方法简介 2 1.33 方案设计 2 第二章 芯片简介 4 2.1 STC89C52芯片简介 4 2.11引脚功能说明 4 2.2 DS18B20简介 7…...

外网访问wordpress版式不对/百度关键词下拉有什么软件

链表 在内存空间中&#xff0c;数组和链表都是基本的数据结构&#xff0c;都是【表】&#xff0c;或者叫【线性表】。线性表是一个线性结构&#xff0c;它是一个含有n≥0个结点的有限序列&#xff0c;对于其中的结点&#xff0c;有且仅有一个开始结点没有前驱但有一个后继结点…...

深圳国资委/seo流量的提升的软件

这里写目录标题下面是目录可跳转对应页面学习&#xff1b;1.MySQL环境1.1.环境安装1.2.安装位置1.3.修改字符集1.4.配置文件2.MySQL逻辑架构MySQL逻辑架构逻辑架构分层MySQL逻辑架构3.存储引擎下面是目录可跳转对应页面学习&#xff1b; 1. 简介 1.1 安装1.2 MySQL逻辑架构 1.…...

网站改版计划/今日国内新闻大事件

java基础之““ 与 ”equals ”的区别 前言&#xff1a; 作为Java的基础知识&#xff0c;我相信可能还有许多朋友对于 "" 与 "equals " 之间的关系还不是很明白&#xff0c;今天就总结一下两者的区别&#xff1a; 知识点一&#xff1a;“”与 equals 比…...

哪些网站用黑体做的/云搜索app下载

&#x1f320; 『精品学习专栏导航帖』 &#x1f433;最适合入门的100个深度学习实战项目&#x1f433;&#x1f419;【PyTorch深度学习项目实战100例目录】项目详解 数据集 完整源码&#x1f419;&#x1f436;【机器学习入门项目10例目录】项目详解 数据集 完整源码&…...

wordpress放在其他端口/网络推广方法怎么做

第一步&#xff1a;OTA升级原理解释 TI官方WIKI详细介绍 http://processors.wiki.ti.com/index.php/OAD 1 解释&#xff1a;2 第一步&#xff1a;红色方框 1 Boot就像PC的BIOS&#xff0c;负责选择要运行的Image&#xff0c;是Image-A&#xff0c;还是Image-B.就像…...