Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

General——各种压缩方式总结


.tar.gz

.tar.bz2

.tar.xz

.tgz

General——各种包的管理总结


编程语言相关

  • 参考链接: https://help.github.com/en/github/managing-packages-with-github-packages/about-github-packages#supported-clients-and-formats
Package client Language Package format Description
npm JavaScript package.json Node package manager
gem Ruby Gemfile RubyGems package manager
mvn Java pom.xml Apache Maven project management and comprehension tool
gradle Java build.gradle or build.gradle.kts Gradle build automation tool for Java
docker N/A Dockerfile Docker container management platform
nuget .NET nupkg NuGet package management for .NET
pip Python requirements.txt use pip install -r requirements.txt

操作系统

  • 参考链接: https://www.iteye.com/blog/justcoding-1937171
软件管理方式 线下安装命令 线上安装命令 distribution 操作系统
RPM rpm, rpmbuild yum Red Hat/Fedora
DPKG dpkg apt, apt-get Debian/Ubuntu

rpm和dpkg常用命令总结

  • 参考链接: http://cha.homeip.net/blog/archives/2005/08/rpm_vs_dpkg.html
操作描述 rpm dpkg
安装指定套件 rpm -i pkgfile.rpm dpkg -i pkgfile.deb
显示所有已安装的套件名称 rpm -qa dpkg -l
显示套件包含的所有档案 rpm -ql [softwarename] dpkg -L [softwarename]
显示特定档案所属套件名称 rpm -qf [/path/to/file] dpkg -S [/path/to/file]
显示制定套件是否安装 rpm -q [softwarename] dpkg -l [softwarename], -s或-p显示详细咨询, -l只列出简洁咨询
移除指定套件 rpm -e [softwarename] dpkg -r softwarename, -r 留下套件设定, -P完全移除

apt和yum常用命令总结

  • 参考博客: https://cnblogs.com/lanbosm/p/9130211.html
操作描述 yum apt
软件源配置文件路径 /etc/yum.conf /etc/apt/sources.list
安装软件包 yum install [package] apt-get install [package]
删除软件包 yum uninstall [package] apt-get remove [package]
删除有依赖关系的软件包和配置文件 yum uninstall [package] apt-get autoremove [package] –purge
查看安装包信息 yum info [package] apt-cache show [package]
更新软件包列表 yum update apt-get update
清空缓存 yum clean apt-get clean
搜索包名 yum apt-cahce search

一些特殊命令

apt

  • 列出所有可用包名

    1
    apt-cache pkgnames
  • 通过描述列出包名

    1
    apt-cache search [keys]
  • 指定包的版本号

    1
    apt-get install [package]=[version]

yum

  • 搜索包的可用版本

    1
    apt --showduplicates list [package] | expand
    • expand命令用于将文件的制表符tab转换成空格符space
      • 默认一个tab对应8个space
      • 若不指定文件名(或者文件名为-), 则expand会从标准输入读取数据
    • unexpand命令与expand相反
  • 安装时指定包的版本号

    1
    apt install [package]-[version]

yum和apt安装的常用参数

  • -y: 指定在询问是否安装时均选择yes
  • -q: quiet,安装途中不打印log信息

Centos——clamav安装与杀毒

clam是一款Linux上免费的杀毒软件


安装与配置

命令行安装

  • 命令行安装clamav会自动创建clamav用户和clamav组

  • Centos(笔者在Centos7亲测)

    1
    apt install –y clamav clamav-update
  • ubuntu(笔者在Ubuntu16.04上亲测)

    1
    apt-get install clamav

源码安装

  • 如果因为某些原因无法从命令行安装,可以尝试用源码安装,此时需要首先手动创建相关组和用户
安装前的配置
  • 创建clamav组

    1
    groupadd clamav
  • 创建用户并添加到clamav组

    1
    useradd -g clamav clamav
源码下载和安装
  • 下载流程:

    • 下载链接: http://www.clamav.net/downloads, 因为版本可能会有更新,这里我们直接给出网站下载地址,可以随时查看版本信息
    • 找到软件包下载链接后,使用wget下载即可,比如
      1
      wget http://www.clamav.net/downloads/production/clamav-0.102.0.tar.gz
  • 安装流程:

    • 解压

      1
      tar -xf clamav-0.102.0.tar.gz
    • 切换目录

      1
      cd clamav-0.102.0
    • 安装依赖

      1
      yum/apt install gcc openssl openssl-devel

使用

升级病毒库

  • 升级命令

    • 命令行安装后更新命令

      1
      freshclam
    • 源码安装后更新命令, 也可以建立软连接后直接使用上面的命令行启动

      1
      /usr/local/clamav/bin/freshclam
    • 建立链接指令

      1
      ln -s [source path] [target path]

查找病毒文件

  • 常用命令

    1
    nohup clamscan / -r --infected -l clamscan.log > clamscan.out &
    • -r 指明递归查杀
    • --infected 表示仅仅输出被感染的部分文件, 否则没有被感染的文件会输出文件名: OK这样无用的信息
    • -l 指明日志文件路径
    • / 是查找的目标目录,如果是整个机器查找则使用/
    • 由于查杀病毒需要很长时间,所以建议使用后台进程进行, 如果是远程, 建议使用nohup
    • 由于输出非常多,所以一般我们使用clamscan.out和clamscan.log分别存储输出和日志
  • 列出被感染文件

    1
    cat clamscan.out | grep FOUND

DL——Transformer

本文主要介绍Transformer和Attention相关内容

  • 由于LaTex中矩阵的黑体表示过于复杂,在不会引起混淆的情况下,本文中有些地方会被简写为非黑体

相关论文介绍

  • Transformer原始文章:
    • Google Brain, NIPS 2017: Attention Is All You Need
    • 文章中介绍了一种应用Attention机制的新型特征提取器,命名为Transformer, 实验证明Transformer优于RNN(LSTM),CNN等常规的特征提取器
  • Transformer的使用:
    • GPT: Improving Language Understanding by Generative Pre-Training
    • BERT: BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
    • 以上两个工作都使用了Transformer作为特征提取器, 使用两阶段训练的方式实现迁移学习(Pre-Training and Fine-Training)

相关博客介绍

  • 强烈推荐看看jalammar的博客: illustrated-transformer
  • 另一篇不错的Attention和Transformer讲解自然语言处理中的自注意力机制(Self-Attention Mechanism)
  • 一篇很多个人理解的博客 《Attention is All You Need》浅读

Transformer讲解

  • 最直观的动态图理解
  • 本文讲解主要按照Google Brain, NIPS 2017: Attention Is All You Need的思路走,该论文的亮点在于:
    • 不同于以往主流机器翻译使用基于 RNN 的 Seq2Seq 模型框架,该论文用 Attention 机制代替了 RNN 搭建了整个模型框架, 这是一个从换自行车零件到把自行车换成汽车的突破
    • 提出了多头注意力(Multi-Head Attention)机制方法,在编码器和解码器中大量的使用了多头自注意力机制(Multi-Head self-attention)
    • 在WMT2014语料库的英德和英法语言翻译任务上取得了先进结果

Transformer是什么?

  • Transformer 是个序列转换器 :
  • 进一步讲,是个 Encoder-Decoder 模型的序列转换器 :
  • 更进一步的讲,是个 6层Encoder + 6层Decoder 结构的序列转换器:
  • 上面的图中,每个 Encoder 是:
  • 详细的讲, 每个Encoder是
  • 展开看里面 Encoder 中的数据流向
  • 更进一步的展开看 Encoder 中的数据流向
  • 两层 Encoder + 两层Decoder (其中一个Decoder没有完全画出来) 的数据流向
  • 带细节动图查看数据流向
  • 最后,我们给出Transformer的结构图(来自原文中)

Transformer中的Attention

Transformer中使用了 Multi-Head Attention, 同时也是一种 Self Attention

  • 由于Transformer的Multi_Head Attention中 Query == Key == Query , 所以也是一种 Self Attention
    • 即
      $$\boldsymbol{Y_{AttentionOutput}} = Self Attention(\boldsymbol{Q},\boldsymbol{K},\boldsymbol{V}) = Attention(\boldsymbol{X},\boldsymbol{X},\boldsymbol{X})$$
  • 更多关于广义Attention的理解请参考: DL——Attention

Multi-Head Attention

  • Muti-Head Attention,也称为多头Attention,由 \(h\) 个 Scaled Dot-Product Attention和其他线性层和Concat操作等组成
  • Scaled Dot Product Attention中Mask操作是可选的
  • Scaled Dot Product Attention数学定义为(没有Mask操作)
    $$
    \begin{align}
    Attention(\boldsymbol{Q},\boldsymbol{K},\boldsymbol{V}) = softmax\left(\frac{\boldsymbol{Q}\boldsymbol{K}^{\top}}{\sqrt{d_k}}\right)\boldsymbol{V}
    \end{align}
    $$
    • Softmax前除以 \(\sqrt{d_k}\) 的原因是防止梯度消失问题,基本思想是(原始论文脚注中有提到):假设 \(\boldsymbol{Q},\boldsymbol{K}\) 中每个元素是服从均值为0,方差为1的正太分布( \(\sim N(0,1)\) ),那么他们任意取两个列向量 \(\boldsymbol{q}_i,\boldsymbol{k}_i\) 的内积服从均值为0,方差为 \(d_k\) 的正太分布( \(\sim N(0,d_k)\) ),具体证明可参考没有比这更详细的推导 attention为什么除以根号dk——深入理解Bert系列文章,过大的方差会导致softmax后梯度消失
  • Multi-Head Attention的某个输出的数学定义为
    $$
    \begin{align}
    MultiHead(\boldsymbol{Q}, \boldsymbol{K}, \boldsymbol{V}) &= Concat(head_1,\dots,head_h)\boldsymbol{W}^{O} \\
    where \quad head_i &= Attention(\boldsymbol{Q}\boldsymbol{W}_i^Q,\boldsymbol{K}\boldsymbol{W}_i^K,\boldsymbol{V}\boldsymbol{W}_i^V)
    \end{align}
    $$
    • 注意,在一般的Attention中,没有 \(\boldsymbol{W}^{O}\) 这个参数,这个是用于多头Attention中,将多头的输出Concat后映射一下再输出
      • 理解:若不是多头,其实加不加这个参数,本质都是一样的,因为连续的两个权重矩阵线性相乘,本质就是一个权重矩阵而已
      • 补充:在实现时,这里的 \(\boldsymbol{W}^{O}\) 实际上是一个线性层 nn.Linear 的含义,就是一个简单的矩阵,不包含非线性信息的
    • 一般来说, \(head_i\) 的维度是 \(\frac{d_{model}}{N_{head}}=\frac{d_{model}}{h}=d_v = d_k\),所以Multi-Head Attention的参数数量与head的数量无关,且无论多少个头,其的输出结果还是 \(d_{model} = d_v * h\) 维
    • 原始论文中常用 \(d_{model} = h * d_k = h * d_v\),且base模型的参数设置为 \(512 = 8 * 64\)
有关Multi-Head Attention的理解
  • 原论文的描述:

    Multi-head attention allows the model to jointly attend to information from different representation subspaces at different positions,

  • 理解:

    • 所谓多头,就是多做几次(\(h\) 次)同样的事情(参数 \((W_i^Q, W_i^K, W_i^V)\) 不共享, 即当 \(i \neq j \) 时, \((W_i^Q, W_i^K, W_i^V) \neq (W_j^Q, W_j^K, W_j^V)\)),然后把结果拼接
    • Multi-Head Attention中, 每个头(Scaled Dot-Product Attention)负责不同的子空间(subspaces at differect positions)
    • 每个头权重不同, 所以他们的关注点也会不同,注意, 初始化时他们的参数不能相同, 否则会造成他们的参数永远相同, 因为他们是同构的
    • 个人理解: 多头的作用可以类比于CNN中的卷积层, 负责从不同的角度提取原始数据的特征

Self Attention

  • Self Attention是只 Key和Query相同的 Attention, 这里因为 Key 和 Value 也相同,所以有 Query == Key == Query
  • 即$$ \boldsymbol{Y_{AttentionOutput}} = Self Attention(\boldsymbol{Q},\boldsymbol{K},\boldsymbol{V}) = Attention(\boldsymbol{X},\boldsymbol{X},\boldsymbol{X})$$

Transformer中的Attention

  • 既是Multi-Head Attention, 也是 Self Attention
  • 所以有$$\boldsymbol{Y_{AttentionOutput}} = MultiHead(\boldsymbol{X},\boldsymbol{X},\boldsymbol{X})$$

Masked Multi-Head Attetion

  • MaskedMHA,掩码多头Attention,用于Decoder中防止前面的token看到后面的token,Encoder中不需要MaskedMHA
  • 一般性的,Masked Self-Attention是更一般的实现,不一定非要和Multi-Head绑定
  • 代码实现时,主要是在计算Softmax前,按照掩码将看不到的token对应的q,k内积替换为一个大负数,比如 \(-1e9\)

Cross Multi-Head Attention

  • CrossMHA不是Self-Attention,CrossMHA的Q,K是Encoder的输出,V来自Decoder

Transformer 输入层

  • Transformer的输入层使用了 Word Embedding + Position Embedding
  • 由于Transformer去除RNN的Attention机制完全不考虑词的顺序, 也就是说, 随机打乱句子中词的顺序 (也就是将键值对 \((\boldsymbol{K}, \boldsymbol{V})\) 对随机打乱), Transformer中Attention的结果不变
  • 实际上, 目前为止, Transformer中的Attention模型顶多是个非常精妙的”词袋模型” (这句话来自博客:https://kexue.fm/archives/4765)

Word Embedding

  • 和之前的词嵌入一样, 将One-Hot值映射成词向量嵌入模型中
  • Tie Embedding :嵌入层(Embedding Layer)和输出投影层(Unembedding Layer / Output Projection Layer)绑定(即共享权重)
    • 基本思路:一个是 token 到 Embedding 映射,另一个是 Embedding 到 token 映射,绑定方式是W_out = W_embed^T
    • 这种绑定的优势是节约存储、训练稳定;缺点是表达能力受限、梯度冲突可能严重(比如输入和输入的词分布差异大)
  • 原始论文中关于参数绑定(Weight-Tying)的说明在3.4节:

    In our model, we share the same weight matrix between the two embedding layers and the pre-softmax linear transformation, similar to [24]. In the embedding layers, we multiply those weights by \(\sqrt{d_{\text{model}}}\)

Position Embedding

FaceBook的《Convolutional Sequence to Sequence Learning》中曾经用过Position Embedding

  • 在不使用RNN的情况下建模词的顺序, 弥补”词袋模型”的不足
  • 用 Position Embedding来为每个位置一个向量化表示
    • 将每个位置编号,然后每个编号对应一个向量
    • 通过结合位置向量和词向量,就给每个词都引入了一定的位置信息,这样Attention就可以分辨出不同位置的词了
  • 原始论文中, 作者提出了一种周期性位置编码的表示, 数学公式如下:
    $$
    \begin{align}
    PE(pos,2i) &= sin(pos/10000^{2i/d_{\text{model}}}) \\
    PE(pos, 2i+1) &= cos(pos/10000^{2i/d_{\text{model}}})
    \end{align}
    $$
  • 我觉得上述公式太丑了,转换一下写法可能更容易理解
    $$
    \begin{align}
    PE(pos,2i) &= sin\left (\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right) \\
    PE(pos, 2i+1) &= cos\left (\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right)
    \end{align}
    $$
    • \(pos\) 是位置编号
    • \(i\) 表示位置向量的第 \(i\) 维
    • 从公式来看,为什么选择 \(10000^{\frac{2i}{d_{\text{model}}}}\) ?
      • \(i\) 表示频率随模型embedding维度变动(模型embedding不同维度频率不同,低维度高频,高维度低频)
      • \(pos\) 表示周期,随着位置变化,每个维度的值呈现周期变化,但是不同维度的变化周期(频率)不同
      • 10000是一个放缩因子,理论上可以换,在transformer原始论文实现中用了这个,且效果不错
    • 选择正弦函数的原因是假设这将允许模型学到相对位置信息
      • 因为对于固定的 \(k\), \(PE_{pos+k} = LinearFuction(PE_{pos})\),所以这给模型提供了表达相对位置的可能性
与之前的Position Embedding的区别
  • Position Embedding对模型的意义不同:
    • 以前在RNN、CNN模型中Position Embedding是锦上添花的辅助手段,也就是“有它会更好、没它也就差一点点”的情况,因为RNN、CNN本身就能捕捉到位置信息
    • 在Transformer这个纯Attention模型中,Position Embedding是位置信息的唯一来源,因此它是模型的核心成分之一,并非仅仅是简单的辅助手段
  • Position Embedding的向量构造方式不同
    • 在以往的Position Embedding中,基本都是根据任务训练出来的向量
    • 而Google直接给出了一个构造Position Embedding的公式:
      $$
      \begin{align}
      PE(pos,2i) &= sin\left (\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right) \\
      PE(pos, 2i+1) &= cos\left (\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right)
      \end{align}
      $$
    • Google经过实验, 学到的位置嵌入和这种计算得到的位置嵌入结果很相近
    • Google选用这种嵌入方式的原因是这种方式允许模型以后可以扩展到比训练时遇到的序列长度更长的句子

输入层的输出(Attention的输入)

  • 综合词嵌入和位置嵌入信息,我们可以得到下面的公式
    $$
    \begin{align}
    \boldsymbol{x} = \boldsymbol{x}_{WE} + \boldsymbol{x}_{PE}
    \end{align}
    $$
    • \(\boldsymbol{x}\) 为输入层经过词嵌入和位置嵌入后的 输出, 也就是Attention的输入
    • \(\boldsymbol{x}_{WE}\) 指词嵌入的结果
    • \(\boldsymbol{x}_{PE}\) 指位置嵌入的结果

FFN

  • FFN,Feed Forward Network,前馈网络层
    $$
    FFN(\mathbf{X}) = ReLU(\mathbf{X}\mathbf{W}^U + \mathbf{b}_1)\mathbf{W}^D + \mathbf{b}_2
    $$
  • 原始 Transformer 使用的是 ReLU 作为激活函数,现在很多时候也会选用 sigmoid
  • 可以看到前馈神经网络包含了两层
  • 注:原始 Transformer 论文中,使用的 \(H = d_\text{model} = 512\),\(d_{ff} = 2048\),FFN 中间隐藏维度是 \(d_\text{model} = 512\) 的 4 倍
    • 即 FFN 的参数量为:\(2 \times 4H \times H = 8H^2\)

Layer Normalization

  • 层归一化,是Transformer特有的一种归一化方法
  • Batch Normalization(BN)不适用与Transformer中,至少有以下原因:
    • Transformer训练样本通常(特别是模型很大时)可能会比较小,在Batch较小时BN不再适用
    • BN是按照token维度(特征维度)来归一化的,不利于处理变长输入序列
      $$
      LayerNorm(\mathbf{x}) = \frac{\mathbf{x}-\mathbf{\mu}}{\mathbf{\sigma}}\cdot \mathbf{\gamma} + \mathbf{\beta} \\
      \mathbf{\mu} = \frac{1}{H}\sum_{i=1}^H x_i, \quad \mathbf{\sigma} = \sqrt{\frac{1}{H}\sum_{i=1}^H(x_i-\mathbf{\mu})^2} \\
      $$
  • 代码实现是会在分母的更号内增加一个极小量 \(\epsilon\),防止出现除0的情况
  • 显然,LayerNorm是基于token来归一化的,当前token的归一化结果与其他token无关,不受其他token影响
  • 可能出现不同的token向量LayerNorm归一化以后输出相同的值,比如[1,2,3,4]、[2,3,4,5]和[2,4,6,8]的输出结果都相同
    • 可以看到是因为这些向量维度之间的分布很相似,或者呈现倍数关系,才导致输出值为0,实际模型中,维度一般是64维或者128维等,而且是小数,几乎不会出现两个不同的token经过LayerNorm后输出相同的值

LN是token维度的

  • 按照Transformer源码实现来看,LayerNorm是Token维度的,不是Seq维度,也就是说,token向量LayerNorm的结果只与token向量自身相关,与所在序列的其他token无关

    • 这一点是Decoder可以增量解码的关键,这一点保证了Decoder的前序词不会受到后续词的影响
    • 增量解码是指:Decoder中输出下一个词时,可以使用前序词的缓存结果,由于前面的词看不到后面的词,所以增加词前后Transformer-Decoder中前序每个词的输出在每一层都不会受到影响
  • 一个LayerNorm的示例如下,Transformer源码中实现与这个类似

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import torch.nn as nn
    import torch

    # 假设d_model=4,d_model在有些实现中为hidden_size
    layer_norm = nn.LayerNorm(4) # 这里使用LayerNorm,也可以使用RMSNorm,两者作用维度相同,只是公式不同
    # test case 1:
    input_tensor = torch.Tensor([[[1, 2, 3, 4],
    [2, 3, 4, 5]]])
    output_tensor = layer_norm(input_tensor)
    print(output_tensor)
    # output:
    # tensor([[[-1.3416, -0.4472, 0.4472, 1.3416],
    # [-1.3416, -0.4472, 0.4472, 1.3416]]],
    # grad_fn=<NativeLayerNormBackward0>)

    # test case 2:
    input_tensor = torch.Tensor([[[1, 2, 3, 4],
    [200, 3, 4, 5]]])
    output_tensor = layer_norm(input_tensor)
    print(output_tensor)

    # tensor([[[-1.3416, -0.4472, 0.4472, 1.3416],
    # [ 1.7320, -0.5891, -0.5773, -0.5655]]],
    # grad_fn=<NativeLayerNormBackward0>)
  • 从示例中可以看出:

    • 修改第二个token的某个元素值,只影响第二个token的LN输出,不影响第一个token

Transformer改进-LN

原始LN参见论文之前的内容

LN的改进——RMSNorm

  • RMSNorm 的公式如下:
    $$
    \begin{align}
    RMSNorm(\mathbf{x}) &= \frac{\mathbf{x}-\mathbf{\mu}}{RMS(\mathbf{x})}\cdot \mathbf{\gamma} \\
    RMS(\mathbf{x}) &= \sqrt{\frac{1}{H}\sum_{i=1}^H x_i^2} \\
    \end{align}
    $$
    • 代码实现是会在分母的更号内增加一个极小量 \(\epsilon\),防止出现除 0 的情况
    • 其中 \(\mu\) 和 \(\gamma\) 是可学习参数,但一般的实现中没有 \(\mu\),只有一个参数 \(\gamma\)
  • 截止到24年,PyTorch 官方还没有提供标准的 RMSNorm 实现,下面是 HuggingFace Transformers 中的实现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @use_kernel_forward_from_hub("RMSNorm")
    class LlamaRMSNorm(nn.Module):
    def __init__(self, hidden_size, eps=1e-6):
    """
    LlamaRMSNorm is equivalent to T5LayerNorm
    """
    super().__init__()
    self.weight = nn.Parameter(torch.ones(hidden_size))
    self.variance_epsilon = eps

    def forward(self, hidden_states):
    input_dtype = hidden_states.dtype
    hidden_states = hidden_states.to(torch.float32)
    variance = hidden_states.pow(2).mean(-1, keepdim=True)
    hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
    return self.weight * hidden_states.to(input_dtype)

    def extra_repr(self):
    return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"

LN的改进——DeepNorm

$$
DeepNorm(\mathbf{x}) = LayerNorm(\alpha\cdot \mathbf{x} + \text{Sublayer}(\mathbf{x})) \\
$$

  • 这里的 \(\text{Sublayer}(\mathbf{x})\) 是指Transformer中的前馈神经网络层或自注意力模块(两者都会作为LN的输入)
  • 实际上,原始的Transformer中,每次LN的内容都是加上残差的,这里根据归一化位置的不同还有会有不同的实现
  • 原始的Transformer中,相当于 \(\alpha=1\) 的DeepNorm
  • 这里叫做DeepNorm的原因是因为缩放残差 \(\mathbf{x}\) 可以扩展Transformer的深度,有论文提到利用该方法可将深度提升到1000层(DeepNet: Scaling Transformers to 1,000 Layers)

归一化的位置

  • 归一化的位置包括Post-Norm、Pre-Norm和Sandwich-Norm等
  • Post-Norm
    • 原始Transformer使用的方法
    • 将归一化模块使用到加法(需要把残差加到FFN/MHA的输出上)之后,详细公式是:
      $$
      \text{Post-Norm}(\mathbf{x}) = Norm(\mathbf{x} + \text{Sublayer}(\mathbf{x}))
      $$
    • \(\text{Sublayer}\)
  • Pre-Norm
    • 归一化模块放到FFN/MHA之前,详细公式是:
      $$
      \text{Pre-Norm}(\mathbf{x}) = \mathbf{x} + \text{Sublayer}(Norm(\mathbf{x}))
      $$
  • Sandwich_Norm
    • 三明治归一化,从字面意思可以知道,是两个Norm将某个层夹起来,实际上,该层是前馈神经网络层或自注意力模块
      $$
      \text{Sandwish-Norm}(\mathbf{x}) = \mathbf{x} + Norm(\text{Sublayer}(Norm(\mathbf{x})))
      $$

归一化位置的比较

DeepNet: Scaling Transformers to 1,000 Layers中也有有关Pre-Norm和Post-Norm的探讨

  • 一般来说,使用Post-Norm比较多,效果也更好
  • Pre-Norm在深层Transformer中容易训练(容易训练不代表效果好,Pre-Norm的拟合能力一般不如Post-Norm)
    • 所以有些模型还是会使用Pre-Norm,因为它更稳定
  • 浅层中建议使用Post-Norm
  • 详情参考苏神的回答为什么Pre Norm的效果不如Post Norm?,其中引用到了 如何评价微软亚研院提出的把 Transformer 提升到了 1000 层的 DeepNet? - 唐翔昊的回答 - 知乎

    Pre Norm的深度有“水分”!也就是说,一个 L 层的Pre Norm模型,其实际等效层数不如 L 层的Post Norm模型,而层数少了导致效果变差了
    Pre Norm结构无形地增加了模型的宽度而降低了模型的深度,而我们知道深度通常比宽度更重要,所以是无形之中的降低深度导致最终效果变差了

  • 直观上来说就是:
    • Pre-Norm 是(不严谨的变换)近似将深度转换成了宽度
      $$
      \begin{align}
      \mathbf{x}_{t+1} &= \mathbf{x}_t + f(Norm(\mathbf{x}_t)) \\
      &= \mathbf{x}_{t-1} + f(Norm(\mathbf{x}_{t-1})) + f(Norm(\mathbf{x}_t)) \\
      &\approx \mathbf{x}_{t-1} + 2f(Norm(\mathbf{x}_{t}))
      \end{align}
      $$
      • 注:这里大家认为可假定在 \(t\) 较大时, \(\mathbf{x}_t\) 和 \(\mathbf{x}_{t-1}\) 是基本相似的
      • 我们称 \(x_1,x_2,\cdots,x_t\) 为主干,在 Pre-Norm 中,主干一直在加入新的东西,所以方差越来越大,且单层网络对主干的影响越来越小
      • 此外,如何评价微软亚研院提出的把 Transformer 提升到了 1000 层的 DeepNet? - 唐翔昊的回答 - 知乎中还认为不同层的 \(f\) (MHA 或 FFN)在统计上是相似的
        • TODO:这一点有待考证和理解
    • Post-Norm 则是保持深度
      $$
      \begin{align}
      \mathbf{x}_{t+1} &= Norm(\mathbf{x}_t + f(\mathbf{x}_t)) \\
      &= Norm(Norm(\mathbf{x}_{t-1} + f(\mathbf{x}_{t-1})) + f(\mathbf{x}_t))
      \end{align}
      $$
      • Post-Norm 中,保证了主干方差是恒定的

Transformer改进-激活函数

原始激活函数是ReLU(Rectified Linear Unit)

Swish(SiLU)

  • 参考文章:Sigmoid-Weighted Linear Units for Neural Network Function Approximation in Reinforcement Learning
  • Swish 的全称为 “Scaled Exponential Linear Unit with Squishing Hyperbolic Tangent”,直译为“带有压缩的缩放指数线性单元”
    $$
    \text{Swish}_\beta(x) = x \cdot sigmoid(\beta x)
    $$
    • 许多实现中常常设置 \(\beta=1\)
  • 注:Swish, 简称为 Sigmoid-weighted Linear Unit,所以一些文章中也称为 SiLU,

GELU

  • GELU, Gaussion Error Linear Unit,有时候也写作GeLU
    $$
    \text{GELU}(x) = 0.5x \cdot [1+\text{erf}(\frac{x}{\sqrt{2}})], \quad \text{erf}(x) = \frac{2}{\sqrt{\pi}}\int_1^x e^{-t^2} dt
    $$
    • erf 是 Gauss Error function 的缩写,在很 Torch 和 TensorFlow 中都是定义好的
  • 从公式可以看出GELU的本质是对一个正太分布的概率密度函数进行积分,实际上就是累积分布函数
  • GELU和ReLU的比较如下(图片来自简单理解GELU 激活函数):

FastGELU

  • FastGELU, Fast Gaussion Error Linear Unit,出自论文 CodeGeeX: A Pre-Trained Model for Code Generation with Multilingual Benchmarking on HumanEval-X, KDD 2024, THU & Huawei,该方案作为 GELU 的近似,在升腾 910 芯片上能加速
    $$ \text{FastGELU}(X_i) = \frac{X_i}{1+\exp(-1.702*|X_i|) \cdot \exp(0.851\cdot (X_i - |X_i|))} $$
  • FastGELU vs GELU图像对比如下:
  • 从图中可以看出:\(x\) 在 0 附近,以及 \(x \geq 0\) 时,FastGELU 和 GELU 的曲线几乎一致(未完全重合)

补充:GLU及其变换

  • GLU,Gated Linear Units,是一种利用门的思想实现的激活函数,该激活函数可以理解为对输入进行门控选择,一些维度的值可以通过门,一些则不可以,门一般是一个基础的非线性激活函数
  • 原始GLU形式如下:
    $$
    GLU = \sigma(\mathbf{W}_1\mathbf{x} + \mathbf{b}_1) \odot (\mathbf{W}_2\mathbf{x} + \mathbf{b}_2)
    $$
  • \(\sigma\) 可以替换成其他非线性激活函数
    • 注意整个公式中始终只有一个非线性激活函数,其他部分都是线性映射(线性激活函数)
  • \(\odot\) 表示矩阵按照元素相乘, \(W_1,W_2,b_1,b_2\) 是可学习的参数
  • 该激活函数非常特殊,首先使用两个权重矩阵对输入数据进行线性变换,然后通过sigmoid激活函数进行非线性变换。这种设计使得GLU在前馈传播过程中能够更好地捕捉输入数据的非线性特征,从而提高模型的表达能力和泛化能力
  • 原始论文GLU Variants Improve Transformer中也写作下面的形式(其中 \(W,V,b,c\) 是可学习的参数):
    $$
    GLU(\mathbf{x,W,V,b,c}) = \sigma(\mathbf{W}\mathbf{x} + \mathbf{b}) \odot (\mathbf{V}\mathbf{x} + \mathbf{c})
    $$
  • 去掉激活函数的版本也叫作Bilinear,写作
    $$
    Bilinear(\mathbf{x,W,V,b,c}) = (\mathbf{W}\mathbf{x} + \mathbf{b}) \odot (\mathbf{V}\mathbf{x} + \mathbf{c})
    $$
  • 其他相关形式
    $$
    ReGLU(x, W, V, b, c) = max(0, xW + b) \odot (xV + c) \\
    GEGLU(x, W, V, b, c) = GELU(xW + b) \odot (xV + c) \\
    SwiGLU(x, W, V, b, c, \beta) = Swish_\beta(xW + b) \odot (xV + c) \\
    $$

补充:FFN激活函数形式

  • FFN的ReLU激活函数形式
    $$
    \text{FFN}(x, W_1, W_2, b_1, b_2) = max(0, xW_1 + b_1)W_2 + b_2
    $$
  • 为了表示方便,也因为在一些文章中使用了简化,后续该形式会被简化成没有偏置项(bias)的形式:
    $$
    \text{FFN}_{ReLU}(x, W_1, W_2) = max(xW_1, 0)W_2
    $$

FFN各种激活函数形式

  • 常用FFN的激活函数改进有,GLU,Bilinear,ReGLU,GEGLU(GeGLU),SwiGLU等
    $$
    \begin{align}
    \text{FFN}_{GLU}(x, W, V, W_2) &= (\sigma(xW) \odot xV )W_2 \\
    \text{FFN}_{Bilinear}(x, W, V, W_2) &= (xW \odot xV )W_2 \\
    \text{FFN}_{ReGLU}(x, W, V, W_2) &= (max(0, xW) \odot xV )W_2 \\
    \text{FFN}_{GEGLU}(x, W, V, W_2) &= (GELU(xW) \odot xV )W_2 \\
    \text{FFN}_{SwiGLU}(x, W, V, W_2) &= (Swish_1(xW) \odot xV )W_2 \\
    \end{align}
    $$
  • 可以理解为 \(\mathbf{W}^G,\mathbf{W}^U\) 中包含了偏置项 \(\mathbf{b}\),有些文章/模型中则会将偏置项 \(\mathbf{b}\) 去掉
  • 最常用的是SwiGLU
  • 从形式上看,可以知道相对原始FFN激活函数形式,SwiGLU等改进增加了一个参数矩阵,为了保证原始参数数量不变,原始论文GLU Variants Improve Transformer中提出了一种方法,通过将矩阵设置为如下的大小来保证参数数量相等
    • 原始FFN层参数为(下面 \(d = d_{model}\) 是模型的隐藏层大小,注意,同一层的不同token是共享FFN的):
      $$
      W_1^{d\times d} + W_2^{d\times d}
      $$
    • 使用SwiGLU且对齐参数数量后
      $$
      W^{r\times d} + V^{d\times r} + W_{2}^{r\times d}
      $$
    • 显然,当 \(r=\frac{2}{3}d\) 时,使用 SwiGLU 前后FFN层参数数量相同,都等于 \(2d^2\)
  • 一个疑问:原始的SwiGLU函数会引入两个参数矩阵 \(W,V\),原始的FFN包含两个参数矩阵 \(W_1, W_2\),为什么两者结合以后只剩下 \(\text{FFN}_{SwiGLU}\) 只剩三个参数 \(W,V,W_2\) 呢?
    • 回答:因为两个线性矩阵相乘,可以合并为 \(W = WV\),虽然还叫做 \(W\),但实际上是多了一个矩阵乘进去的,线上训练时也只需要训练这一个矩阵即可

Transformer总结

  • Transformer是一个特征提取能力非常强(超越LSTM)的特征提取器
  • 一些讨论
    • Transformer与CNN没关系,但是Transformer中使用多个 Scaled Dot-Product Attention 来最后拼接的方法(Multi-Head Attention), 就是CNN的多个卷积核的思想
    • Transformer论文原文中提到的残差结构也来源于CNN
    • 无法对位置信息进行很好地建模,这是硬伤。尽管可以引入Position Embedding,但我认为这只是一个缓解方案,并没有根本解决问题。举个例子,用这种纯Attention机制训练一个文本分类模型或者是机器翻译模型,效果应该都还不错,但是用来训练一个序列标注模型(分词、实体识别等),效果就不怎么好了。那为什么在机器翻译任务上好?我觉得原因是机器翻译这个任务并不特别强调语序,因此Position Embedding 所带来的位置信息已经足够了,此外翻译任务的评测指标BLEU也并不特别强调语序
    • Attention如果作为一个和CNN,RNN平级的组件来使用,可能会集成到各自的优点, 而不是”口气”很大的 “Attention is All You Need”

附录:关于 Dropout

  • 在 Transformer 原始论文中,在每个 sub-layer 的输出上使用了 Dropout,即 输出被加到 sub-layer 的输入和 归一化前进行 Dropout,称为 Residual Dropout
  • 同时,原始论文还在 Embedding 和 位置编码的和 上使用了 Dropout(包括 Encoder 和 Decoder)
  • \(P_\text{drop} = 0.1\)

附录:熵、损失和概率的关系图

  • 关键词:entropy curve;loss curve;熵和概率;概率和熵;熵和损失;损失和熵;损失和概率;概率和损失;曲线图;

  • 展示三者关系的代码如下(正确类别分配指定概率(横轴),其余均匀分配剩余概率):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    import numpy as np
    import matplotlib.pyplot as plt

    plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]

    def entropy(prob_dist):
    """计算概率分布的熵"""
    # 避免log(0)的情况
    prob_dist = np.clip(prob_dist, 1e-10, 1.0)
    return -np.sum(prob_dist * np.log(prob_dist))

    def cross_entropy(true_dist, pred_dist):
    """计算两个概率分布之间的交叉熵"""
    # 避免log(0)的情况
    pred_dist = np.clip(pred_dist, 1e-10, 1.0)
    return -np.sum(true_dist * np.log(pred_dist))

    num_classes = 100000 # 分类问题数

    # 创建一个真实分布(one-hot编码,表示第500类为正确类别)
    true_dist = np.zeros(num_classes)
    true_dist[499] = 1.0 # 索引从0开始,第500类

    # 计算真实分布的熵(应该为0,因为是确定的)
    true_entropy = entropy(true_dist)
    print(f"真实分布的熵: {true_entropy:.6f}")

    # 生成一系列预测分布,从非常不准确到非常准确
    num_steps = 100
    accuracies = np.linspace(0.01, 0.99, num_steps)
    entropies = []
    cross_entropies = []

    for acc in accuracies:
    # 创建预测分布:正确类别分配acc的概率,其余均匀分配剩余概率
    pred_dist = np.ones(num_classes) * ((1.0 - acc) / (num_classes - 1))
    pred_dist[499] = acc

    # 计算熵和交叉熵
    entropies.append(entropy(pred_dist))
    cross_entropies.append(cross_entropy(true_dist, pred_dist))

    # 绘制结果
    plt.figure(figsize=(12, 6))

    plt.subplot(1, 2, 1)
    plt.plot(accuracies, entropies, 'b-')
    plt.title('预测分布的熵随准确率的变化')
    plt.xlabel('正确类别的概率')
    plt.ylabel('熵')
    plt.grid(True, alpha=0.3)

    plt.subplot(1, 2, 2)
    plt.plot(accuracies, cross_entropies, 'r-')
    plt.title('交叉熵损失随准确率的变化')
    plt.xlabel('正确类别的概率')
    plt.ylabel('交叉熵损失')
    plt.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.show()

    print("\nExample:")
    example_acc = 0.8
    pred_dist = np.ones(num_classes) * ((1.0 - example_acc) / (num_classes - 1))
    pred_dist[499] = example_acc

    print(f"当正确类别的概率为 {example_acc} 时:")
    print(f"预测分布的熵: {entropy(pred_dist):.6f}")
    print(f"交叉熵损失: {cross_entropy(true_dist, pred_dist):.6f}")
  • 当类别为 1000 时,图像如下

  • 当类别为 10000 时,图像如下

  • 当类别为 100000 时,图像如下

  • 当类别为 150000 时,图像如下

CA——COEC特征

  • 参考: http://d0evi1.com/positionbias/
  • COEC(Click on Expected Click):点击与期望点击的比值

Background

  • 在评价一个广告的质量好坏时,单纯使用广告的点击率作为指标是不可行的
  • 广告的点击率与广告的曝光位置相关【包括slot或position引起的问题】
    • 从position上来讲,越靠前的广告越容易被点击

COEC

  • 为了抵消广告曝光位置对广告的点击率的影响,我们引入期望点击
  • 在计算商家的点击率时,使用点击与期望点击的比值作为商家点击率质量的衡量指标
    $$
    \begin{align}
    COEC = \frac{\sum_{i=1}^N isClick_i}{\sum_{i=1}^N expCTR_i}
    \end{align}
    $$
    • \(isClick_i\) 为0或1,表示真实点击情况
    • \(expCTR_i\) 为广告真实曝光位置的期望点击率,不同slot和position对应的期望点击率不同

COEC特征

  • 在广告相关指标预估模型中,使用COEC作为广告的特征可提升模型的效果
  • 由于COEC的分母上考虑了位置因素,使得COEC更能真实的反应广告实际点击率的真实质量

NLP——UNILM论文阅读笔记

本文介绍了MSRA今年的一篇文章: UNILM: Unified Language Model Pre-training for Natural Language Understanding and Generation


UNILM 基于三个预训练目标

  • Unidirectional LM:
  • Bidirectional LM:
  • Sequence2Sequence LM:

贡献

和 BERT 对比

  • BERT 是双向模型,所以自然语言的生成(NLG)任务上不适用
  • UNILM 采用三种(无监督的)LM目标,其中的 Sequence2Sequence LM 能够解决文本生成问题

其他一些优点

  • 仅使用一个 LM (由 Transformer 构成), 在三个不同的预训练任务上共享参数, 泛化能力强
    • 参数共享: 不需要在不同的语言模型(对应不同的预训练任务)上使用不同的模型参数
    • 泛化能力强: 多个训练目标同时优化模型,使得模型能够避开因为训练目标引起的过拟合问题

模型结构介绍

结构图

  • 输入向量为 \(\vec{x} = (x_1, x_2,,,x_{|x|}\))
  • 输入向量表征与 BERT 相同,由 Embedding 层(三个 Embedding 层之和, 和 BERT 一样):
    • Token Embedding,WordPiece
    • Position Embedding
    • Segment Embedding,由于 UNILM 会使用多个 LM 任务训练,所以 Segment Embedding 在模型中也扮演着 LM 标识的作用(对不同的 LM 目标使用不同的Segment Embeddings)
  • 主要网络(Backbone Network)为多层Transformer:
    • 每两个Transformer Blocks之间的Self-Attention Masks: For Each Transformer Block, 使用多头 Self-Attention 来聚合之前层出现的输入向量.(这里的 Self-Attention 使用了)
    • Transformer 之间的 Self-Attention 机制决定了模型什么语言模型(任务)
    • 如图所示:
      • Bidirectional LM 对应的 Self-Attention Masks 为全0的矩阵,表示所有的 Attention 都不屏蔽 (attend to all tokens)
      • Left-to-Right LM 对应的 Self-Attention Masks 为拼屏蔽右上三角的矩阵(左下三角全为0)
      • Right-to-Left LM 对应的 Self-Attention Masks 为拼屏蔽左下三角的矩阵(右上三角全为0)
      • Seq-to-Seq LM 对应的 Self-Attention 为
        • \(S_1\) - \(S_1\) 为全0(0表示开放), 对应为Bidirectional
        • \(S_1\) - \(S_2\) 为负无穷(负无穷表示屏蔽)
        • \(S_2\) - \(S_1\) 为全0
        • \(S_2\) - \(S_2\) 为屏蔽右上三角的矩阵(这里与Left-to-Right的情况相同)

按照结构图分析数据流

  • 对于一组输入向量 \(\{\vec{x}\}_{i=1}^{|x|} \),初始编码为 \(\mathbf{H}^0 = [\vec{x}_1, \vec{x}_2,,,\vec{x}_{|\vec{x}|}]\)
  • 第一层后编码为上下文表征 \(\mathbf{H}^1 = [\vec{h}_1^1, \vec{h}_2^1,,,\vec{h}_{|\vec{x}|}^1]\)
  • 第 \(l\) 层后编码为上下文表征 \(\mathbf{H}^l = [\vec{h}_1^l, \vec{h}_2^l,,,\vec{h}_{|\vec{x}|}^l]\)
  • 每一层的 Transformer Block, 使用 multiple self-attention heads去聚合上一层的输出: L-layer的 Transformer对应的数学表达式为 \(\mathbf{H}^l = Transformer_l(\mathbf{H}^{l-1}), l \in [1, L]\)
  • Self-Attention Head \(\mathbf{A}_l\) 的详细计算公式如下:
    $$
    \begin{align}
    \mathbf{H} = \mathbf{H}^{l-1}\mathbf{W}_l^Q \\
    \mathbf{K} = \mathbf{H}^{l-1}\mathbf{W}_l^K \\
    \mathbf{V} = \mathbf{H}^{l-1}\mathbf{W}_l^V \\
    \mathbf{A} = softmax(\frac{\mathbf{Q}\mathbf{K}^T}{\sqrt{d_k}} + \mathbf{M})\mathbf{V}_l \\
    where, \quad \mathbf{M}_{ij} = 0 \ or \ -\infty
    \end{align}
    $$
    • \(\mathbf{M}\) 中的值
      • \(-\infty\) 表示屏蔽Attention (prevent from Attention)
      • 0 表示允许 Attention (allow to Attention)

NLP——LLM位置编码-DCA

  • 参考链接:
    • 原始论文:(DCA)Training-Free Long-Context Scaling of Large Language Models, ICML 2024, Alibaba

DCA 整体说明

  • 双块注意力(Dual Chunk Attention, DCA)是一种无需训练的大语言模型长上下文扩展框架
  • TLDR:通过将长序列的注意力计算分解为块内(Intra-Chunk)、块间(Inter-Chunk)和连续块(Successive-Chunk)三种注意力机制,有效捕捉短程和长程依赖关系
  • DCA 可保证位置编码不会超过预训练 :通过重新设计相对位置矩阵 \( M \) 反映 tokens 间的真实相对位置,避免传统方法(如位置插值PI、NTK)对引入未见过的位置信息
  • 保留预训练的位置编码,每个块的大小 \( s \) 小于模型预训练长度 \( c \),确保块内相对位置不超过预训练范围的降维损失
  • DCA 可同时与 Flash Attention 结合实现高效计算
  • 实验表明 DCA 在长上下文中的表现显著优于传统位置插值方法,如 PI 和 NTK-aware Scored RoPE

三大注意力机制

Intra-Chunk Attention(块内注意力)

  • 作用 :处理同一块内的 tokens,捕捉短程依赖
  • 位置编码 :块内查询和键的位置索引均为原序列索引对块大小取模,即:
    $$
    P_q^{\text{Intra} } = P_k = [0, 1, \dots, l-1] \mod s
    $$
    • 其中,\( l \) 为序列长度,\( s \) 为块大小
  • 相对位置矩阵 :同一块内的相对位置为查询与键的位置索引差:
    $$
    M[i][j] = P_q^{\text{Intra} }[i] - P_k[j] \quad (\text{where} \ \lfloor i/s \rfloor = \lfloor j/s \rfloor)
    $$
    • 确保块内相对位置在 \([0, s-1]\) 范围内,与预训练一致,同一块内的 tokens 距离完全精确

Inter-Chunk Attention(块间注意力)

  • 作用 :处理不同块间的 tokens,捕捉长程依赖
  • 位置编码 :查询的位置索引统一设为预训练最大位置 \( c-1 \),键的位置索引为原块内索引,即:
    $$
    P_q^{\text{Inter} } = \underbrace{[c-1, c-1, \dots, c-1]}_{l \ \text{elements}}
    $$
  • 相对位置矩阵 :不同块间的相对位置为预训练最大位置与键的位置索引差:
    $$
    M[i][j] = c-1 - P_k[j] \quad (\text{when} \ \lfloor i/s \rfloor > \lfloor j/s \rfloor)
    $$
    • 确保块间相对位置在 \([c-s, c-1]\) 范围内,避免超出预训练范围

Successive-Chunk Attention(连续块注意力)

  • 作用 :处理相邻块间的 tokens,保留局部性(如相邻 tokens 的相对距离为1)
  • 位置编码 :对当前块的前 \( w \) 个查询,位置索引设为前一块的末尾位置递增,即:
    $$
    P_q^{\text{Succ} } = [s, s+1, \dots, s+w-1, c-1, \dots, c-1]
    $$
    • 其中 \( w = c - s \) 为局部窗口大小
    • 显然:每个块前 \(w\) 个位置为递增索引
    • 对所有的 chunk 上述连续块注意力编码都一样
  • 相对位置矩阵 :相邻块间前 \( w \) 个查询的相对位置为真实距离,其余为块间注意力的固定值:
    $$
    M[i][j] =
    \begin{cases}
    (s + i’ ) - P_k[j], & \text{when} \ i’ < w \ (\text{前} \ w \ \text{个位置}) \\
    c-1 - P_k[j], & \text{otherwise}
    \end{cases}
    $$
    • 确保相邻块的局部依赖关系(如 \( q_6 \) 与前一块的 \( k_5 \) 相对距离为1)

组合计算与归一化

  • DCA 中不同块关系下的相对位置矩阵 \( M[i][j] \),表达式如下:
    $$
    M[i][j]=
    \begin{cases}
    P_{q}^{\text{Intra}}[i] - P_{k}[j], & \text{if } \lfloor i / s \rfloor - \lfloor j / s \rfloor = 0 \quad \text{同一块内}\\
    P_{q}^{\text{Succ}}[i] - P_{k}[j], & \text{if } \lfloor i / s \rfloor - \lfloor j / s \rfloor = 1 \quad \text{相邻块间}\\
    P_{q}^{\text{Inter}}[i] - P_{k}[j], & \text{if } \lfloor i / s \rfloor - \lfloor j / s \rfloor > 1 \quad \text{非相邻块间}
    \end{cases}
    $$
    • ( s ) 为块大小,( \lfloor i/s \rfloor ) 表示查询 ( i ) 所属的块索引,( \lfloor j/s \rfloor ) 表示键 ( j ) 所属的块索引
    • 同一块内(索引差为0):使用块内注意力的位置索引 ( P_{q}^{\text{Intra}} ) 和 ( P_{k} ) 计算相对位置
    • 相邻块间(索引差为1):使用连续块注意力的位置索引 ( P_{q}^{\text{Succ}} ) 计算相对位置,保留局部性
    • 非相邻块间(索引差大于1):使用块间注意力的位置索引 ( P_{q}^{\text{Inter}} ) 计算相对位置,捕捉长程依赖
  • 注意力权重计算 :根据 tokens 所属块的关系,选择对应的位置编码计算内积:
    $$
    q_i^\top k_j =
    \begin{cases}
    f(q, P_q^{\text{Intra} }[i])^\top f(k, P_k[j]), & \text{if } \lfloor i / s \rfloor - \lfloor j / s \rfloor = 0 \quad \text{同一块内} \\
    f(q, P_q^{\text{Succ} }[i])^\top f(k, P_k[j]), & \text{if } \lfloor i / s \rfloor - \lfloor j / s \rfloor = 1 \quad \text{相邻块间} \\
    f(q, P_q^{\text{Inter} }[i])^\top f(k, P_k[j]), & \text{if } \lfloor i / s \rfloor - \lfloor j / s \rfloor > 1 \quad \text{非相邻块间}
    \end{cases}
    $$
    • 其中 \( f \) 为RoPE位置编码函数
  • 归一化 :通过Softmax对三种注意力的结果加权求和,确保数值稳定性:
    $$
    p_i = \text{softmax}\left( \left[ \frac{q_i^\top k_0}{\sqrt{d} }, \frac{q_i^\top k_1}{\sqrt{d} }, \dots, \frac{q_i^\top k_i}{\sqrt{d} } \right] \right)
    $$
    • 其中 \( d \) 为隐藏层维度

DCA 的优势讨论

  • 无需训练 :直接复用预训练模型的权重和位置编码,仅修改推理代码,避免昂贵的微调成本
  • 高效扩展 :结合Flash Attention,显存占用和推理速度与原始模型相当,支持Llama2 70B处理超过100k tokens(PPL仅从5.24升至5.59)
  • 正交兼容 :可与PI、NTK等位置编码方法结合,进一步扩展上下文(如从32k到192k)
  • 性能优势 :在长上下文任务(如语言建模、密钥检索、问答)中,性能接近或超过微调模型,例如Llama2 70B-DCA在零样本任务上达到GPT-3.5的94%性能

一些思考

  • 问题:为什么只关注连续的块?仅一跳的块间是否可以设置一些特定的设计?
    • 回答:在当前设定(不超出预训练位置编码)下,不可以,因为会超过预训练位置编码
    • 补充:如果跳开不超出预训练位置编码的假设,将 DCA 和 PI 或 NTK 做融合(PI 或 NTK 主要修改的是为止编码的旋转角度,DCA 则仅在 Attention 相对位置设计上做文章,相当于重构了 Attention 的位置编码矩阵),理论上可以拿到更好的效果,论文中还对这种组合进行了实验,给出了结果

附录:相隔很远的 tokens 会被误编码为距离很近吗?

  • 结论:在 DCA 中,相隔很远的 tokens 可能被编码为较大的相对距离,不会被误以为距离很近
    • 其核心在于通过块间注意力和连续块注意力机制区分不同距离的依赖关系
  • 块间相对位置的唯一性 :块间注意力中,查询的位置索引固定为预训练最大位置 \( c-1 \),键的位置索引为原块内索引(范围 \([0, s-1]\))。因此,不同块间的相对位置为:
    $$
    M[i][j] = c-1 - P_k[j] \quad (\text{块间}, \ i > j)
    $$
    • 该值的范围为 \([c-s, c-1]\),每个块对应唯一的相对位置区间。例如,若预训练长度 \( c=10 \),块大小 \( s=6 \),则:
      • 第1块的键位置为 \([0,5]\),块间相对位置为 \( 9-0=9, \ 9-1=8, \ \dots, \ 9-5=4 \)(区间 \([4,9]\))
      • 第2块的键位置为 \([0,5]\),块间相对位置为 \( 9-0=9, \ \dots, \ 9-5=4 \)(同样区间 \([4,9]\))
  • 补充问题 :不同块的键可能映射到相同的相对位置(如第1块的键5和第2块的键5,相对位置均为4),块间注意力主要用于捕捉“跨块”的全局依赖,而不区分具体是哪一块的键,如何保证长距离和更长距离的相对关系被识别到?
    • 虽然不同块的键可能有相同的相对位置,但模型通过注意力权重自动学习跨块的依赖强度,例如较远的块(如第3块)的键会与查询形成相同的相对位置,但模型会根据上下文语义调整注意力权重,而非仅依赖位置编码
    • 进一步理解:实际上,自回归模型自动带有位置编码信息,这里就全靠自回归模型自己悟了

附录:连续块注意力强化局部性的细节

  • 相邻块的精确距离保留 :连续块注意力针对相邻块的前 \( w=c-s \) 个查询,设置递增的位置索引(如 \( s, s+1, \dots, s+w-1 \)),确保相邻块的局部tokens相对位置正确。例如:
    • 第1块末尾的键5(位置5)与第2块开头的查询6(位置6),在连续块注意力中相对位置为 \( 6-5=1 \)(真实距离),而非块间注意力的 \( 9-5=4 \)
    • 当 \( w=c-s \) 时,相邻块的前 \( w \) 个查询会覆盖前一块的全部键(如 \( c=10, s=6 \) 时,\( w=4 \),第2块前4个查询的位置为6、7、8、9,可与第1块的键0-5形成相对位置6-0=6、7-1=6等,但更接近真实距离)
  • 效果* :通过保留相邻块的局部位置信息,模型能正确捕捉“邻近跨块”的短距离依赖(如对话中的连续句子),避免将相邻块的tokens 误判为远距离

附录:潜在局限性与实验验证

  • 远距离块的位置歧义 :对于非相邻块(如第1块和第3块),它们的键在块间注意力中均映射到 \([c-s, c-1]\) 区间,可能导致位置编码的歧义(如第1块的键0和第3块的键0,相对位置均为 \( c-1-0 \))
  • 实验结果 :
    • 在密钥检索任务中,DCA 在 192k 上下文中仍能保持 90% 以上准确率,表明模型能区分不同块的全局位置(见论文图7)
    • 语言建模任务中,DCA 的困惑度(PPL)增长缓慢(如 Llama2-70B 从 4k 到 100k+,PPL仅从 5.24 升至 5.59),说明位置编码的歧义对长程依赖的影响有限

NLP——UI-TARS

注:本文包含 AI 辅助创作

  • 参考链接:
    • 原始论文:UI-TARS:Pioneering Automated GUI Interaction with Native Agents, arXiv 20250621, ByteDance Seed & THU
    • UI-TARS 开源地址:https://github.com/bytedance/UI-TARS

Paper Summary

  • 前置评价:

    • 论文是字节 Seed 团队的大作,是这个领域的惊喜,非常值得一读
  • UI-TARS 的关键创新总结:

    • (1)增强感知:利用大规模的 ** GUI 屏幕截图**数据集,实现对 UI 元素的上下文感知理解和精确描述;
    • (2)统一动作建模,将动作标准化到跨平台的统一空间,并通过大规模动作轨迹实现精确的定位和交互;
    • (3)System 2 推理,将审慎推理(deliberate reasoning)融入多步决策过程,涉及任务分解、反思性思考、里程碑识别等多种推理模式;
    • (4)基于反思性在线轨迹的迭代训练,通过在数百台虚拟机上自动收集、筛选和反思性优化新的交互轨迹,解决数据瓶颈问题
  • 论文介绍了 UI-TARS,这是一种原生图形用户界面(Graphical User Interface, GUI )智能体模型,它仅将屏幕截图作为输入,并执行类似人类的交互操作(例如键盘和鼠标操作)

  • 与目前流行的依赖大量封装的商业模型(如 GPT-4o)以及专家精心设计的提示和工作流程的智能体框架不同,UI-TARS 是一个端到端的模型 ,性能优于这些复杂的框架

  • 实验证明了 UI-TARS 的卓越性能:

    • UI-TARS 在十多个评估感知、定位和 GUI 任务执行的 GUI 智能体基准测试中取得了最优成绩(见下文)
    • 在 OSWorld 基准测试中,UI-TARS 在 50 步内得分为 24.6,15 步内得分为 22.7,分别超过 Claude 的 22.0 和 14.9
    • 在 AndroidWorld 基准测试中,UI-TARS 得分为 46.6,超过 GPT-4o 的 34.5
  • 通过迭代训练和反思调整,UI-TARS 不断从错误中学习,并在极少的人工干预下适应意外情况

  • 论文还分析了 GUI 智能体的发展路径,为该领域的进一步发展提供指导


Introduction and Discussion

  • 自主智能体 (2024b; 2023; 2024) 被期望能够在极少的人工监督下运行,感知环境、做出决策并执行动作以实现特定目标
  • 在该领域的众多挑战中,让智能体与图形用户界面(GUI)实现无缝交互已成为一个关键的前沿课题 (2024; 2024a; 2024; 2024e; 2024)
    • GUI 智能体旨在数字环境中执行任务,这些环境严重依赖按钮、文本框和图像等图形元素
    • 通过利用先进的感知和推理能力,这些智能体有潜力彻底改变任务自动化方式、提高可访问性,并简化各种应用程序的工作流程
    • GUI 智能体的发展历来依赖于结合文本表示(例如 HTML 结构和可访问性树)的混合方法 (2018; 2023; 2023)
    • 虽然这些方法推动了显著的进展,但它们存在一些局限性,如特定平台的不一致性、冗长性和有限的可扩展性 (2024)
    • 基于文本的方法通常需要系统级权限来访问底层系统信息,如 HTML 代码,这进一步限制了它们在不同环境中的适用性和通用性
  • 另一个关键问题是:许多现有的 GUI 系统遵循智能体框架范式 (2023; 2024a; 2024a; 2024b; 2024; 2024),其中关键功能被模块化到多个组件中
    • 这些组件通常依赖于专门的视觉语言模型(Vision-Language Model, VLM),如GPT-4o (2024) 进行理解和推理 (2024b) ,而定位 (2024b) 或记忆 (2023) 模块则通过额外的工具或脚本来实现
    • 尽管这种模块化架构有助于特定领域任务的快速开发,但它依赖于手工制作的方法,这些方法依赖于专家知识、模块化组件和特定任务的优化,与端到端模型相比,可扩展性和适应性较差
    • 这使得该框架在面对不熟悉的任务或动态变化的环境时容易失败 (2024)
  • 这些挑战促使向原生 GUI 智能体模型(native GUI agent model)发生了两个关键转变:
    • (1)从依赖文本(textual-dependent)的 GUI 智能体转变为基于纯视觉(pure-vision-based)的 GUI 智能体 (2023; 2024)
      • “纯视觉(Pure-vision)” 意味着模型仅依赖界面的屏幕截图作为输入,而不是文本描述(例如 HTML)
      • 这绕过了文本表示的复杂性和特定平台的限制,更符合人类的认知过程
    • (2)从模块化智能体框架演变为端到端的智能体模型 (2024b; 2024; 2024b; 2024a; Anthropic, 2024b)
      • 端到端设计将传统的模块化组件统一到单个架构中,实现了模块之间的信息流畅传递
      • 从理念上讲:
        • 智能体框架是设计驱动的,需要大量的手动工程和预定义的工作流程来维持稳定性并防止意外情况;
        • 智能体模型本质上是数据驱动的,能够通过大规模数据和迭代反馈进行学习和适应 (2024)
  • 尽管原生 GUI 智能体模型在概念上具有优势,但在实际应用中往往存在不足,导致其在现实世界中的影响力落后于预期。这些限制主要源于两个方面:
    • (1) GUI 领域本身带来了独特的挑战,增加了开发强大智能体的难度
      • (1.a)在感知方面,智能体不仅要识别,还要有效地解释不断演变的用户界面中高信息密度的内容
      • (1.b)推理和规划机制同样重要,以便有效地浏览、操作这些界面并做出响应
      • (1.c)这些机制还必须利用记忆,考虑过去的交互和经验来做出明智的决策
      • (1.d)除了高层次的决策,智能体还必须执行精确的低层次动作,例如输出点击或拖动的精确屏幕坐标,并在适当的字段中输入文本
    • (2)从智能体框架向智能体模型的转变引入了一个根本性的数据瓶颈
      • 模块化框架传统上依赖为单个组件定制的单独数据集:这些数据集相对容易整理,因为它们处理的是孤立的功能
      • 训练一个端到端的智能体模型需要在统一的工作流程中整合所有组件的数据,捕捉感知、推理、记忆和动作之间的无缝交互
        • 这类数据包含了人类专家丰富的工作流程知识,但在历史上很少被记录下来
      • 这种全面、高质量数据的缺乏限制了原生智能体在各种现实场景中的泛化能力,阻碍了它们的可扩展性和稳健性
  • 为了解决这些挑战,论文致力于推进原生 GUI 智能体模型的发展
    • 论文首先回顾 GUI 智能体的发展路径(章节2)
      • 通过根据人工干预程度和泛化能力将 GUI 智能体的发展划分为关键阶段,论文进行了全面的文献综述
      • 从传统的基于规则的智能体开始,论文重点介绍了从僵化的、基于框架的系统到能够无缝集成感知、推理、记忆和动作的自适应原生模型的演变
    • 论文还展望了具有主动和终身学习能力的 GUI 智能体的未来潜力,这种智能体能够在最大限度减少人工干预的同时最大化泛化能力
    • 为了加深理解,论文详细分析了原生智能体模型的核心能力,包括:
      • (1)感知,实现对环境的实时理解以提高态势感知能力;
      • (2)动作,要求原生智能体模型在预定义空间中准确预测和定位动作;
      • (3)推理,模仿人类思维过程,包括 System 1 和 System 2 思维;
      • (4)记忆,存储特定任务信息、先前经验和背景知识
    • 论文还总结了 GUI 智能体的主要评估指标和基准测试
  • 基于这些分析,论文提出了一种原生 GUI 智能体模型 UI-TARS,图1展示了一个示例案例
  • UI-TARS 有以下核心贡献:
    • 增强 GUI 屏幕截图感知(Enhanced Perception for GUI Screenshots,章节4.2) :
      • GUI 环境信息密度高、布局复杂且风格多样,需要强大的感知能力
      • 论文使用专门的解析工具收集屏幕截图,构建了一个大规模数据集,以提取网站、应用程序和操作系统中的元素类型、边界框和文本内容等元数据
      • 以上数据集针对以下任务:
        • (1)元素描述,提供对 GUI 组件的细粒度、结构化描述;
        • (2)密集标题,旨在通过描述整个 GUI 布局(包括空间关系、层次结构和元素之间的交互)来实现对界面的整体理解;
        • (3)状态转换标题,捕捉屏幕上的细微视觉变化;
        • (4)问答,旨在增强智能体的视觉推理能力;
        • (5)标记集提示,使用视觉标记将 GUI 元素与特定的空间和功能上下文相关联
        • 这些精心设计的任务共同使 UI-TARS 能够以极高的精度识别和理解 GUI 元素,为进一步的推理和动作提供了坚实的基础
    • 多步执行的统一动作建模(Unified Action Modeling for Multi-step Execution,章节4.3) :
      • 论文设计了一个统一的动作空间,以标准化跨平台语义等效的动作
      • 为了改进多步执行,论文创建了一个大规模的动作轨迹数据集,结合了论文标注的(annotated)轨迹和标准化的开源数据
      • 通过整理将元素描述与其空间坐标配对的大量数据集,提高了定位能力,即准确找到特定 GUI 元素并与之交互的能力
      • 这些数据使 UI-TARS 能够实现精确可靠的交互
    • 审慎决策的 System 2 推理(System 2 Reasoning for Deliberate Decision-making,章节4.4) :
      • 在动态环境中实现稳健的性能需要先进的推理能力
      • 为了丰富推理能力,论文爬取了 6M 个 GUI 教程 ,并进行了精心筛选和优化,为逻辑决策提供 GUI 知识
      • 在此基础上,论文通过将任务分解、长期一致性、里程碑识别、试错和反思等多种推理模式注入模型,增强了对所有收集到的动作轨迹的推理能力
      • UI-TARS 通过在每次动作前生成明确的 “思考”,将感知和动作与审慎决策联系起来,整合了这些能力
    • 从先前经验中学习的迭代优化(Iterative Refinement by Learning from Prior Experience,章节4.5) :
      • GUI 智能体开发中的一个重大挑战在于缺乏用于训练的大规模、高质量动作轨迹
      • 为了克服这个数据瓶颈,UI-TARS 采用了一种迭代改进框架,动态收集和优化新的交互轨迹
      • 利用数百台虚拟机,UI-TARS 根据构建的指令探索各种现实世界任务,并生成大量轨迹
        • 通过基于规则的启发式方法、VLM 评分和人工审核等严格的多阶段筛选,确保轨迹质量
        • 然后将这些优化后的轨迹反馈到模型中,使智能体在连续的训练周期中不断迭代提升性能
      • 这个在线自训练过程的另一个核心组件是反思调整,即智能体通过分析自己的次优动作来识别和纠正错误。论文为这个过程标注(annotate)了两种类型的数据:
        • (1)错误纠正,标注者指出智能体生成的轨迹中的错误并标记纠正动作;
        • (2)反思后处理,标注者模拟恢复步骤,展示智能体在出错后应如何重新调整任务进度
      • 这两种类型的数据创建了配对样本,用于使用直接偏好优化(Direct Preference Optimization, DPO) (2023) 训练模型
      • 这种策略确保智能体不仅学会避免错误,还能在错误发生时动态适应
      • 这些策略共同使 UI-TARS 能够在极少的人工监督下实现稳健、可扩展的学习
  • 论文在大约 50B 个标记上持续训练 Qwen-2-VL 7B 和 72B (2024c) ,以开发 UI-TARS-7B 和 UI-TARS-72B
  • 通过广泛的实验,论文得出以下结论:
    • Overall Performance :
      • UI-TARS 在十多个 GUI 智能体基准测试中展示了最优性能,涵盖了对感知、定位和智能体任务执行的评估
      • 这些结果验证了论文方法的有效性,在推理密集型和动态场景中显著优于 GPT-4o 和 Claude Computer Use (Anthropic, 2024b) 等竞争基线
    • 感知(Perception) :
      • UI-TARS在 GUI 感知方面表现出色,能够有效处理高信息密度和复杂布局。实验证实了它提取精确元数据、描述 GUI 元素以及生成详细的、具有上下文感知的标题的能力
      • 例如,UI-TARS-72B 在 VisualWebBench (2024c) 中得分 82.8,高于 GPT-4o 的 78.5
    • 定位(Grounding) :
      • UI-TARS 通过准确地将 GUI 元素与其空间坐标相关联,在移动、桌面和网络环境中实现了高精度定位
      • 例如,它在最近发布的具有挑战性的基准测试 ScreenSpot Pro (2025) 中获得了 38.1 分(最优成绩)
    • 智能体能力(Agent Capabilities) :
      • 广泛的评估突出了 UI-TARS 卓越的智能体能力
      • 实验表明,它在推理密集型基准测试中表现出色,72B 版本在多步和动态任务中表现尤为突出
      • 值得注意的是,UI-TARS 在 OSWorld (2024) 和 AndroidWorld (2024a) 等具有挑战性的基准测试中取得了优异成绩
      • 在 OSWorld 中,UI-TARS-72B 在 50步 内得分为 24.6,15步 内得分为 22.7,分别超过 Claude 的 22.0 和 14.9
      • 在 AndroidWorld 中,它的得分达到 46.6,超过 GPT-4o 的 34.5,进一步强调了它处理高复杂性现实场景的能力

Evolution Path of GUI Agents

  • 在工作流自动化方面, GUI 智能体的意义尤为重大,它们有助于简化重复性任务、减少人力投入并提高生产力
  • GUI 智能体的核心设计目标是促进人机交互,简化任务执行过程
  • 其发展历程体现了从僵化的、人为定义的启发式方法到日益自主的系统的演进,这些系统能够适应环境、自主学习,甚至独立识别任务
  • 在此背景下,GUI 智能体的角色已从简单的自动化工具转变为成熟的、自我改进的智能体,它们与人类工作流的融合程度不断加深,不仅是工具,更成为任务执行过程中的协作伙伴
  • 多年来,智能体已从基本的基于规则的自动化系统发展为先进、高度自动化且灵活的系统,其行为越来越接近人类,执行任务时只需极少的人工干预
  • 如图2所示,GUI 智能体的发展可分为几个关键阶段,每个阶段都代表着在自主性、灵活性和泛化能力方面的飞跃
  • 每个阶段的特点体现在工作流设计和学习过程中所需的人工干预程度上

Rule-based Agents

Stage 1: Rule-based Agents
  • 在初始阶段,像机器人流程自动化(Robotic Process Automation, RPA)系统这样的智能体 (2022; 2020) 旨在高度结构化的环境中复制人类动作,通常与 GUI 和企业软件系统进行交互
  • 这些智能体通常通过将用户指令与预定义规则匹配并调用相应的 AP I来处理指令
  • 尽管这类系统在处理定义明确的重复性任务时很有效,但它们受限于对人为定义的启发式方法和明确指令的依赖,难以处理新颖和复杂的场景
  • 在这个阶段,智能体无法从环境或以往经验中学习,工作流的任何变更都需要人工干预
  • 此外,这些智能体需要直接访问 API 或底层系统权限,如 DART (2003)、WoB (2017)、Roscript (2020) 和 FLIN (2021) 等系统所展示的那样
    • 这使得它们不适用于那些限制或不提供此类访问权限的场景
    • 这种固有的僵化限制了它们在不同环境中的扩展应用
  • 基于规则的智能体的局限性凸显了向基于 GUI 的智能体转变的重要性,这种智能体依赖视觉信息并直接对 GUI 进行操作,而无需获取系统的低级访问权限
  • 通过与界面进行视觉交互,GUI 智能体获得了更大的灵活性和适应性,显著扩展了它们能够完成的任务范围,不再受预定义规则或明确系统访问权限的限制
  • 这种范式转变为智能体自主与陌生或新开发的界面进行交互开辟了道路

From Modular Agent Framework to Native Agent Model

  • 近年来,利用大型模型(Large Language Model, LLM)能力的智能体框架迅速流行起来
  • 这种流行源于基础模型能够深度理解多种数据类型,并通过多步推理生成相关输出
  • 与基于规则的智能体需要为每个特定任务手工设计规则不同,基础模型可以在不同环境中进行泛化,并通过与环境的多次交互有效处理任务
  • 这消除了人类为每个新场景费力定义规则的需求,显著简化了智能体的开发和部署
Stage 2: Agent Framework
  • 具体而言,这些智能体系统主要利用先进基础模型(如 GPT-4 (OpenAI, 2023b) 和 GPT-4o (2024))的理解和推理能力来增强任务执行的灵活性,从而成为更灵活的、基于框架的智能体
  • 早期研究主要集中在文本界面内调用特定 API 或执行代码片段等任务上 (2023; 2023a, 2023; 2021)
  • 这些智能体标志着从纯基于规则的系统向更自动化、更灵活的交互方式的重大进步
  • AutoGPT (2023a) 和 LangChain 等自主框架允许智能体集成多个外部工具、API 和服务,实现更动态、更具适应性的工作流
  • 增强基于基础模型的智能体框架的性能通常涉及设计特定任务的工作流并优化每个组件的提示词
    • 例如,一些方法通过为这些框架增加专门的模块(如短期或长期记忆)来提供特定任务的知识或存储操作经验以实现自我改进
      • Cradle (2024) 通过存储和利用任务执行经验来增强基础智能体的多任务处理能力
      • Song等人 (2024) 提出了一个基于API的网络智能体框架,该框架利用特定任务的背景知识来执行复杂的网络操作
    • 智能体工作流记忆(Agent Workflow Memory, AWM)模块 (2024g) 通过选择性地提供相关工作流来指导智能体的后续动作,进一步优化了记忆管理
    • 另一种提高任务成功率的常见策略是融入基于反思的多步推理,以改进动作规划和执行
    • 广为人知的 ReAct 框架 (2023) 将推理与动作结果相结合,实现更动态、更具适应性的规划
    • 对于多模态任务:
      • MMNavigator (2023) 利用总结的上下文动作和标记来生成准确的、可执行的动作
      • SeeAct (2024b) 则采用不同的方法,明确指示 GPT-4V 模仿人类浏览行为,同时考虑任务、网页内容和先前动作
    • 此外,多智能体协作已成为提高任务完成率的有效技术。例如:
      • MobileExperts (2024c) 通过整合工具构建和促进多个智能体之间的协作,解决了移动环境的独特挑战
    • 总之,当前智能体框架的进步在很大程度上依赖于通过提示词工程优化计划和动作生成,其核心是底层基础模型的能力,最终实现任务完成度的提升
  • 智能体框架的主要局限性(Key Limitations of Agent Frameworks) :尽管与基于规则的系统相比,智能体框架具有更强的适应性,但它们仍然依赖人为定义的工作流来规划动作;“智能体工作流知识(agentic workflow knowledge)” (2024g) 通过自定义提示词、外部脚本或工具使用启发式方法进行手动编码;这种知识的外部化带来了几个缺点:
    • 脆弱性和维护开销(Fragility and Maintenance Overhead) :每当任务、界面或使用场景发生变化时,开发人员必须重新设计或扩展工作流的手动规则或提示词——这是一个容易出错且耗时费力的过程
    • 脱节的学习范式(Disjoint Learning Paradigms) :基于框架的方法很少整合新的经验数据来更新底层 LLM/VLM 参数
      • 相反,它们依赖离线提示词工程或工作流设计
      • 当任务偏离原始领域时,这些框架往往会失效 ,限制了适应性
    • 模块不兼容性(Module Incompatibility) :复杂任务需要多个模块(如视觉解析、记忆存储、长期规划),这些模块必须通过提示词或桥接代码进行协调
      • 任何模块中的不一致或错误都可能破坏整个流程,而诊断这些问题通常需要领域专家进行流程调试
  • 因此,尽管智能体框架能快速演示,并且在狭窄范围内具有灵活性,但在任务和界面不断演变的现实场景中部署时,它们最终仍然很脆弱
    • 这种对预编程工作流的依赖(由人类专业知识驱动)使得框架本质上不具备可扩展性。它们依赖开发人员的远见来预测所有未来的变化,这限制了它们处理不可预见的变化或自主学习的能力。框架是设计驱动的,这意味着如果没有持续的人类参与,它们无法跨任务进行学习和泛化
第三阶段:原生智能体模型
  • 相比之下,自主智能体开发的未来在于创建原生智能体模型 ,其中工作流知识通过定向学习直接嵌入智能体的模型中
  • 在这种范式中,任务以端到端的方式进行学习和执行,将感知、推理、记忆和动作统一在一个不断进化的单一模型中
  • 这种方法本质上是数据驱动的,使智能体能够适应新任务、新界面或新用户需求,而无需依赖手工设计的提示词或预定义规则
  • 原生智能体具有几个显著优势,有助于提高其可扩展性和适应性:
    • 整体学习和适应(Holistic Learning and Adaptation) :由于智能体的策略是端到端学习的,它可以在内部参数中统一来自感知、推理、记忆和动作的知识
      • 当新数据或用户演示可用时,整个系统(而不仅仅是单个模块或提示词)会更新其知识
      • 这使模型能够更无缝地适应不断变化的任务、界面或用户需求
    • 减少人工工程 :原生模型从大规模演示或在线经验中学习与任务相关的工作流,而不是仔细编写如何在每个节点调用 LLM/VLM 的脚本
      • “硬编码工作流”的负担被数据驱动的学习所取代
      • 这显著减少了环境演变时领域专家手工设计启发式方法的需求
    • 通过统一参数实现强泛化 :尽管手动提示词工程可以使模型适应用户定义的新工具,但模型本身无法进化
      • 在一个参数化策略和统一的数据构建与训练流程下 ,不同环境中的知识(如某些应用程序功能、导航策略或 UI 模式)可以在任务之间转移,使其具备强大的泛化能力
    • 持续自我改进 :原生智能体模型自然适用于在线或终身学习范式
      • 通过在现实世界的 GUI 环境中部署智能体并收集新的交互数据,可以对模型进行微调或进一步训练以应对新挑战
  • 这种数据驱动的、以学习为导向的方法与设计驱动的、静态的智能体框架形成对比
  • 目前, GUI 智能体的发展逐渐达到了这个阶段,代表性作品包括 Claude Computer-Use (Anthropic, 2024b)、Aguvis (2024)、ShowUI (2024b)、OS-Atlas (2024b)、Octopus v2-4 (2024) 等
    • 这些模型主要利用现有的世界数据,专门为 GUI 交互领域定制大型 VLM

Active and Lifelong Agent (Prospect)

Stage 4:Active and Lifelong Agent
  • 尽管在适应性方面有所改进,原生智能体仍然严重依赖人类专家进行数据标注和训练指导
    • 这种依赖性本质上限制了它们的能力,使它们的性能取决于人类提供的数据和知识的质量与广度
  • 向主动和终身学习 (2022; 2024) 的转变代表了 GUI 智能体发展的关键下一步
    • 在这种范式中,智能体主动与环境交互,提出任务、执行任务并评估结果
    • 这些智能体可以根据动作的成功与否自主分配自我奖励,强化积极行为,并通过持续的反馈循环逐步完善自身能力
    • 这种自主探索和学习过程使智能体能够发现新知识、改进任务执行,并增强问题解决策略,而无需大量依赖手动标注或明确的外部指导
  • 这些智能体像机器人领域的持续学习 (2024; 2024) 一样,迭代地发展和修改自己的技能,它们可以从成功和失败中学习,逐步提高在越来越广泛的任务和场景中的泛化能力
  • 原生智能体模型与主动终身学习智能体之间的关键区别在于学习过程的自主性:
    • 原生智能体仍然依赖人类,而主动智能体通过识别自身知识差距并通过自主发起的探索来填补这些差距,从而驱动自己的学习
  • 在这项工作中,论文专注于构建一个可扩展的数据驱动原生智能体模型,为这个主动和终身学习智能体阶段奠定基础
  • 论文首先探索这种框架所需的核心能力(章节3),然后介绍 UI-TARS,论文对这种方法的具体实现(章节4)

Core Capabilities of Native Agent Model

  • 原生智能体模型将先前智能体框架中的模块化组件内化为几项核心能力,从而向端到端结构转变
  • 为了更深入地理解原生智能体模型,本节将详细分析其核心能力,并回顾当前的评估指标和基准测试

Core Capabilities

  • 如图3所示,论文的分析围绕四个主要方面展开:感知(Perception)、动作(Action)、推理(Reasoning,System 1 和 System 2 thinking)以及记忆(Memory)
Perception
  • 高效的 GUI 智能体的一个基本方面在于其实时精确感知和解释图形用户界面的能力
  • 这不仅包括理解静态截图,还包括随着界面演变动态适应变化
  • 论文根据现有研究使用的输入特征对其进行回顾:
    • 结构化文本(Structured Text) :
      • 早期由 LLM 驱动的 GUI 智能体版本 (2023a; 2023; 2024a) 受限于 LLM 只能处理文本输入的局限性
      • 因此,这些智能体依赖于将 GUI 页面转换为结构化文本表示,如 HTML、可访问性树或文档对象模型(Document Object Model, DOM)
      • 对于网页,一些智能体使用 HTML 数据作为输入,或者利用 DOM 来分析页面布局(DOM 提供了一种层次化组织元素的树状结构)
      • 为了减少输入噪声,Agent-E (2024) 采用了 DOM 蒸馏技术,以实现更有效的截图表示
      • Tao等人 (2023) 引入了 WebWISE,它基于从过滤后的 DOM 元素中观察到的信息迭代生成小程序,并按顺序执行任务
    • 视觉截图(Visual Screenshot) :
      • 随着计算机视觉和视觉语言模型(Vision-Language Model, VLM)的发展,智能体现在能够利用屏幕上的视觉数据来解释其屏幕环境
      • 很大一部分研究依赖于标记集(Set-of-Mark, SoM)提示技术 (2023b) 来提高视觉定位能力
      • 为了增强视觉理解,这些方法经常将光学字符识别(Optical Character Recognition, OCR)与 GUI 元素检测模型(包括ICONNet (2022) 和DINO (2025))结合使用
      • 这些算法用于通过边界框识别和勾勒交互元素,随后将这些边界框映射到特定的图像区域,丰富智能体的上下文理解
      • 一些研究还通过添加截图中这些交互元素的描述来提高元素的语义定位能力和理解能力
      • 例如,SeeAct (2024a) 通过将视觉元素与其在HTML网页中所代表的内容相关联,增强了对截图内容的细粒度理解
    • 全面界面建模(Comprehensive Interface Modeling) :
      • 最近,一些研究采用结构化文本、视觉快照和元素的语义轮廓来实现对外部感知的全面理解
      • Gou 等人 (2024a) 合成了大规模的 GUI 元素数据,并训练了一个视觉定位模型 UGround,以获取不同平台上 GUI 页面中元素的相关引用
      • OSCAR (Wang和Liu, 2024) 利用 Windows API 生成的 A11y 树来表示 GUI 组件,并融入描述性标签以促进语义定位
      • DUALVCR (2024) 捕捉截图的视觉特征和相关 HTML 元素的描述,以获得对视觉截图的稳健表示
  • 另一个重要点是实时交互能力
    • GUI 本质上是动态的,元素经常会响应用户操作或系统进程而发生变化
    • GUI 智能体必须持续监控这些变化,以保持对界面状态的最新理解
    • 这种实时感知对于确保智能体能够及时、准确地响应不断变化的情况至关重要
    • 例如,如果出现 loading spinner,智能体应该将其识别为一个待处理进程的指示,并相应地调整其动作
      • 这里的 loading spinner 指的是加载旋转图标,表示正在加载
    • 同样,智能体必须检测并处理界面无响应或行为异常的情况
  • 通过有效结合上述方面,强大的感知系统确保 GUI 智能体能够保持态势感知,并对用户界面的不断变化的状态做出适当响应,使其动作与用户目标和应用程序要求保持一致
  • 然而,隐私问题以及 DOM 引入的额外感知噪声,使得将纯文本描述和混合文本-视觉感知扩展到任何 GUI 环境都面临挑战
  • 因此,与人类与周围环境的交互类似,原生智能体模型应该通过视觉感知直接理解外部环境 ,并将其动作准确地定位到原始截图上
  • 通过这种方式,原生智能体模型可以泛化各种任务,并提高每一步动作的准确性
Action
  • 有效的动作机制必须具有通用性、精确性,并能适应各种 GUI 环境。关键方面包括:
    • 统一多样的动作空间(Unified and Diverse Action Space) :
      • GUI 智能体 (2023; 2024) 在多个平台上运行,包括移动设备、桌面应用程序和网页界面,每个平台都有其独特的交互范式
      • 建立统一的动作空间将特定平台的动作抽象为一组通用操作,如点击、输入、滚动和拖动(click, type, scroll, and drag)
      • 此外,整合来自语言智能体的动作(如 API 调用 (2024b; 2023a)、代码解释 (2024a) 和命令行界面(Command-Line Interface, CLI)操作 (2024))增强了智能体的多功能性
      • 动作可以分为原子动作(执行单个操作)和组合动作(将多个原子动作排序以简化任务执行)
      • 平衡原子动作和组合动作可优化效率并减少认知负荷,使智能体能够无缝处理简单交互和多个步骤的协调执行
    • 坐标定位挑战(Challenges in Grounding Coordinates) :
      • 准确确定点击、拖动和滑动等动作的坐标具有挑战性,这是由于 GUI 布局的可变性 (2024; 2020)、不同设备的宽高比差异以及内容的动态变化
      • 不同设备的宽高比可能会改变界面元素的空间排列,使精确定位变得复杂
      • 坐标定位需要先进的技术来准确解释来自截图或实时界面流的视觉线索
  • 由于不同操作空间中的动作具有相似性,智能体模型可以将来自各种 GUI 环境的动作标准化为统一的动作空间
    • 将动作分解为原子操作降低了学习复杂性,促进原子动作在不同平台之间的更快适应和转移
Reasoning with System 1&2 Thinking
  • 推理是一种复杂的能力,整合了多种认知功能,人类与 GUI 的交互依赖于两种不同类型的认知过程 (1970):System 1 和 System 2 思维
    • System 1 指的是快速、自动和直觉性的思维 ,通常用于简单和常规任务,例如点击熟悉的按钮或无需有意识思考就将文件拖到文件夹中
    • System 2 包括缓慢、审慎和分析性的思维 ,这对于解决复杂任务(如规划整体工作流或通过反思排查错误)至关重要
  • 同样,自主 GUI 智能体必须具备模仿 System 1 和 System 2 思维的能力,才能在各种任务中有效发挥作用
    • 通过学习识别何时应用快速的、基于启发式的响应,何时进行详细的、逐步的推理,这些智能体可以在动态环境中实现更高的效率、适应性和可靠性
  • System 1 推理(System 1 Reasoning) :
    • 代表智能体通过识别界面中的模式并将预先学到的知识应用于观察到的情况来执行快速、直觉性响应的能力
    • 这种推理形式类似于人类与 GUI 的熟悉元素的交互
      • 例如认识到在文本字段中按“Enter”键会提交表单,或者理解点击某个按钮会进入工作流的下一步
    • 这些基于启发式的动作使智能体能够快速响应并在常规场景中保持操作效率,但对预定 mapping 的依赖限制了其决策范围(只能在即时的、反应性的行为上)
      • 例如,大型动作模型等模型 (2024b; 2024a) 擅长通过利用环境观察生成快速响应,但它们通常缺乏更复杂的推理能力
    • 在需要规划和执行多步骤操作的任务中,这种问题尤为明显,这些任务超出了 System 1 的反应性、单步推理范围
      • 虽然 System 1 为快速高效的操作提供了基础,但它凸显了智能体需要向 System 2 推理中所见的更审慎和反思性的能力发展的必要性
  • System 2 推理(System 2 Reasoning) :
    • 代表审慎、结构化和分析性的思维,使智能体能够处理超出 System 1 反应性行为的复杂、多步骤任务
    • 与基于启发式的推理不同, System 2 涉及明确生成中间思维过程,通常使用 CoT (2022) 或 ReAct (2023) 等技术,这些技术弥合了简单动作与复杂工作流之间的差距
    • 这种推理范式由几个基本组件组成:
      • 第一,任务分解(task decomposition) :
        • 任务分解(task decomposition)侧重于通过将任务分解为更小的、可管理的子任务来制定实现总体目标的计划 (2023; 2023; 2024)
        • 例如,填写多字段表单涉及一系列步骤,如输入姓名、地址和其他详细信息,所有这些都在结构良好的计划指导下进行
      • 第二,长期一致性(long-term consistency) :
        • 在整个任务完成过程中,长期一致性至关重要
        • 通过始终参考初始目标,智能体模型可以有效避免在复杂的多阶段任务中可能出现的任何潜在偏差,从而确保从开始到结束的连贯性和连续性
      • 第三,里程碑识别(milestone recognition) :
        • 里程碑识别(milestone recognition)使智能体模型能够估计当前的进展状态、分析观察结果并确定后续目标
        • 这确保多步骤工作流能够有效执行而不会迷失方向
      • 第四,试错(trial and error) :
        • 试错(trial and error)赋予智能体模型更多机会来假设、测试和评估潜在动作,从而提高决策的精确性,特别是在模糊和复杂的场景中(如无需直接交互即可验证搜索结果)
      • 第五,反思(reflection) :
        • 反思(reflection)使智能体模型能够评估过去的动作、识别错误并进行调整以改进未来的性能 (2023; Renze和Guven, 2024)
        • 这个迭代过程提高了可靠性,并有助于防止重复出现错误
  • UI-TARS 的开发着重于为模型配备强大的 System 2 推理能力,使其能够更精确、更灵活地处理复杂任务
    • 通过整合高级规划机制,UI-TARS 擅长将总体目标分解为更小的、可管理的子任务
    • 这种结构化方法使模型能够系统地处理需要跨多个步骤协调的复杂工作流
  • UI-TARS 融入了长文本 CoT 推理过程,这有助于在执行特定动作之前进行详细的中间思考
  • UI-TARS 采用了基于反思的训练过程
    • 通过融入反思性思维,模型不断评估其过去的动作,识别潜在错误,并调整其行为以随着时间的推移提高性能
  • 模型的迭代学习方法带来了显著的好处,提高了其可靠性,并使其能够应对动态环境和意外障碍
Memory
  • 记忆主要用于存储智能体在制定决策时参考的支持性显性知识和历史经验
  • 对于智能体框架,通常会引入额外的记忆模块来存储先前的交互和任务级知识,然后,智能体在决策过程中检索和更新这些记忆模块
  • 记忆模块可以分为两类:
    • 短期记忆(Short-term Memory) :
      • 短期记忆(Short-term Memory)作为特定任务信息的临时存储库 ,捕捉智能体的即时上下文
      • 这包括智能体的动作历史、当前状态详情以及任务的正在进行的执行轨迹,实现实时态势感知和适应性
      • 通过对上下文截图进行语义处理,CoAT (2024d) 提取关键界面细节,从而增强对任务环境的理解
      • CoCo-Agent (2024) 通过综合环境感知(Comprehensive Environment Perception, CEP)记录布局和动态状态
    • 长期记忆(Long-term Memory) :
      • 长期记忆(Long-term Memory)作为长期数据储备,捕捉并保护先前交互、任务和背景知识的记录
      • 它保留诸如先前任务的执行路径等细节,提供支持未来任务推理和决策的综合知识库
      • 通过整合包含用户偏好和任务操作经验的累积知识
        • OS-copilot (2024a) 随着时间的推移改进其任务执行,以更好地符合用户需求并提高整体效率
        • Cradle (2024) 专注于通过为基础智能体配备存储和利用任务执行经验的能力来增强其多任务处理能力
        • Song等人 (2024) 引入了一个基于API的网络智能体框架,该框架利用特定任务的背景知识来执行复杂的网络操作
  • 记忆反映了利用背景知识和输入上下文的能力。短期和长期记忆存储之间的协同作用显著提高了智能体决策过程的效率
  • 与智能体框架不同,原生智能体模型将任务的长期操作经验编码在其内部参数中,将可观察的交互过程转换为隐式的、参数化的存储
  • 可以采用上下文学习(In-Context Learning, ICL)或 CoT 推理等技术来激活这种内部记忆

Capability Evaluation

  • 为了评估 GUI 智能体的有效性,人们精心设计了众多基准测试,重点关注感知、定位和智能体能力(perception, grounding, and agent capabilities)等各个方面的能力
  • 具体而言:
    • 感知评估反映对 GUI 知识的理解程度
    • 定位评估验证智能体是否能够在不同的 GUI 布局中准确定位坐标
    • 智能体能力主要可分为两类:
      • 离线智能体能力评估 ,在预定义的静态环境中进行,主要关注评估 GUI 智能体执行的各个步骤;
      • 在线智能体能力评估 ,在交互式动态环境中进行,评估智能体成功完成任务的整体能力
  • 感知评估(Perception Evaluation) :感知评估评估智能体对用户界面(User Interface, UI)知识的理解及其对环境的感知能力
    • VisualWebBench (2024c) 专注于智能体的网页理解能力
    • WebSRC (2021) 和ScreenQA (2022) 通过问答(Question-Answering, QA)任务评估网页结构理解和移动屏幕内容理解
    • GUI-World (2024a) 提供多种选择题、自由形式和对话形式的查询,以评估 GUI 理解能力,根据不同的问题形式,采用了一系列指标
      • 例如,准确性用于选择题(Multiple-Choice Question, MCQ)任务作为关键指标
      • 在标题生成或光学字符识别(OCR)任务中,采用 ROUGE-L 指标来评估性能
  • 定位评估(Grounding Evaluation) :给定指令,定位评估侧重于精确找到 GUI 元素的能力
    • ScreenSpot (2024) 评估多个平台上的单步 GUI 定位性能
    • ScreenSpot v2 (2024b) 是重新标注的版本,解决了原始ScreenSpot中存在的标注错误
    • ScreenSpot Pro (2025) 通过整合来自各种高分辨率专业桌面环境的现实世界任务,促进定位评估
    • 定位评估的指标通常根据模型的预测位置是否准确位于目标元素的边界框内来确定
  • 离线智能体能力评估(Offline Agent Capability Evaluation) :离线评估衡量 GUI 智能体在静态、预定义环境中的性能
    • 每个环境通常包括输入指令和环境的当前状态(例如截图或先前动作的历史记录),要求智能体产生正确的输出或动作,这些环境在整个评估过程中保持一致
    • 许多离线评估基准测试,包括 AITW (2023)、Mind2Web (2023)、MT-Mind2Web (2024)、AITZ (2024e)、AndroidControl (2024c) 和 GUI-Odyssey (2024a),为智能体提供任务描述、当前截图和先前动作历史,旨在使智能体能够准确预测下一步动作
    • 这些基准测试通常采用步骤级指标,提供对其特定行为的细粒度监督。例如:
      • 动作匹配分数(Action-Matching Score) (2023; 2024e; 2024c; 2024a) 仅在动作类型及其具体细节(如输入内容或滚动方向等参数)与真实情况一致时,才认为动作是正确的
      • 一些基准测试 (2020a; 2022) 要求智能体根据提供的指令和截图生成一系列可自动执行的动作
        • 这些基准测试主要使用任务级指标来评估性能,通过输出结果是否与预定义标签精确匹配(如完整和部分动作序列匹配准确性)来确定任务是否成功 (2020a; 2022; 2023)
  • 在线智能体能力评估(Online Agent Capability Evaluation) :在线评估提供动态环境,每个环境都设计为模拟现实世界场景的交互式模拟
    • 在这些环境中,GUI 智能体可以通过实时执行动作来修改环境状态
    • 这些动态环境跨越多个平台:
      • (1)网页:WebArena (2023) 和 MMInA (2024g) 提供逼真的网页环境
      • (2)桌面:OSWorld (2024)、OfficeBench (2024f)、ASSIST GUI (2023) 和WindowsAgentArena (2024) 在真实的计算机桌面环境中运行
      • (3)移动设备:AndroidWorld (2024a)、LlamaTouch (2024f) 和B-MOCA (2024) 建立在Android等移动操作系统上
    • 为了评估在线评估中的性能,采用了任务级指标,提供对智能体有效性的全面衡量
    • 具体而言,在在线智能体能力评估领域,这些任务级指标主要根据智能体是否成功达到目标状态来确定任务成功与否
    • 这个验证过程检查预期结果是否实现,或者生成的输出是否与标签精确匹配 (2023; 2024; 2024f; 2023)

UI-TARS

  • 在本节中,论文介绍 UI-TARS,这是一种原生图形用户界面(Graphical User Interface, GUI)智能体模型,其设计不依赖繁琐的手动规则或传统智能体框架中典型的级联模块
  • UI-TARS 直接感知截图、应用推理过程并自主生成有效动作
  • UI-TARS 可以从先前经验中学习,通过利用环境反馈迭代优化自身性能

Architecture Overview

  • 下面,论文首先描述 UI-TARS 的整体架构(章节4.1),然后介绍如何增强其感知(章节4.2)和动作(章节4.3)能力
  • 接着,论文重点阐述如何为 UI-TARS 注入 System 2 推理能力(章节4.4),以及如何通过经验学习实现迭代改进(章节4.5)
  • 如图4所示,给定初始任务指令,UI-TARS迭代接收来自设备的观察结果并执行相应动作以完成任务
  • 这一序列过程可以形式化表示为:
    $$(instruction, (o_{1}, a_{1}), (o_{2}, a_{2}), \cdots, (o_{n}, a_{n}))$$
    • \(o_{i}\) 表示在时间步 \(i\) 的观察结果(设备截图)
    • \(a_{i}\) 表示智能体执行的动作
  • 在每个时间步
    • UI-TARS 将任务指令、先前交互历史 \((o_{1}, a_{1}, \cdots, o_{i-1}, a_{i-1})\) 以及当前观察结果 \(o_{i}\) 作为输入
    • 基于这些输入,模型从预定义的动作空间中输出动作 \(a_{i}\)
    • 执行动作后,设备提供下一个观察结果,这些过程迭代进行
  • 为了进一步增强智能体的推理能力并促进更审慎的决策,论文以“思考”\(t_{i}\) 的形式整合了一个推理组件,该组件在每个动作 \(a_{i}\) 之前生成
    • 这些思考反映了“ System 2 ”思维的反思特性
    • 它们作为关键的中间步骤,指导智能体在前进之前重新考虑先前的动作和观察结果,从而确保每个决策都经过深思熟虑
    • 这种方法受到 ReAct 框架(2023)的启发,该框架引入了类似的反思机制,但形式更简单
  • 相比之下,论文对“思考”的整合涉及更结构化、面向目标的思考过程
    • 这些思考是更明确的推理过程,指导智能体做出更好的决策,尤其是在复杂或模糊的情况下
    • 该过程现在可以形式化表示为:
      $$(instruction, (o_{1}, t_{1}, a_{1}), (o_{2}, t_{2}, a_{2}), \cdots, (o_{n}, t_{n}, a_{n}))$$
    • 这些中间思考指导模型的决策,并实现与环境更细致、更具反思性的交互
  • 为了优化内存使用并在通常受限的 Token 预算(例如 32k 序列长度)内保持效率,论文将输入限制为最后的 \(N\) 个观察结果
    • 这一约束确保模型能够处理必要的上下文,而不会使其内存容量不堪重负
    • 先前动作和思考的完整历史被保留为短期记忆
    • UI-TARS 迭代预测思考 \(t_{n}\) 和动作 \(a_{n}\) 的输出,其条件是任务指令和先前的交互:
      $$P(t_{n}, a_{n} | instruction, t_{1}, a_{1}, \cdots, (o_{n-i}, t_{n-i}, a_{n-i})_{i=1}^{N}, o_{n})$$

Enhancing GUI Perception

  • 改进 GUI 感知面临几个独特挑战:
    • (1)截图稀缺性 :虽然大规模的通用场景图像广泛可用,但特定于 GUI 的截图相对稀少
    • (2)信息密度和精度要求 :GUI 图像本质上比通用场景图像具有更高的信息密度和结构性,通常包含数百个元素,这些元素以复杂的布局排列
      • 模型不仅要识别单个元素,还要理解它们的空间关系和功能交互
      • 此外,GUI 图像中的许多元素很小(例如,1920×1080 图像中的 10×10 像素图标),这使得准确感知和定位这些元素变得困难
    • 与依赖单独的、模块化感知模型的传统框架不同,原生智能体通过直接处理来自 GUI 截图的原始输入来克服这些挑战
    • 这种方法使它们能够通过利用大规模、统一的数据集更好地进行扩展,从而更有效地应对 GUI 感知的独特挑战
      Screenshot Collection
  • 为了解决数据稀缺问题并确保多样化覆盖,论文构建了一个大规模数据集,其中包含来自网站、应用程序和操作系统的截图和元数据
    • 论文使用专门的解析工具,在渲染截图时自动提取丰富的元数据——例如每个元素的元素类型、深度、边界框和文本内容
    • 论文的方法结合了自动爬取和人工辅助探索,以捕获广泛的内容
    • 论文包括了主要界面以及通过重复交互访问的更深层次的嵌套页面
    • 所有数据都以结构化格式(截图、元素框、元素元数据)记录,以全面覆盖各种界面设计
  • 论文采用自下而上的数据构建方法,从单个元素开始,逐步发展到整体界面理解
    • 通过在将 GUI 的小局部部分整合到更广泛的上下文中之前专注于它们,这种方法最大限度地减少了错误,同时平衡了识别组件的精度和解释复杂布局的能力
  • 基于收集的截图数据,论文精心设计了五种核心任务数据(图5):
  • 元素描述(Element Description)
    • 为了增强对 GUI 中特定元素(特别是微小元素)的识别和理解,论文专注于为每个元素创建详细的结构化描述
    • 这些描述基于使用解析工具提取的元数据,并由视觉语言模型(Vision-Language Model, VLM)进一步合成,涵盖四个方面:
      • (1)元素类型(例如,窗口控制类型):论文根据视觉线索和系统信息对元素(例如按钮、文本字段、滚动条)进行分类
      • (2)视觉描述,描述元素的外观,包括其形状、颜色、文本内容和样式,直接从图像中提取;
      • (3)位置信息:论文描述每个元素相对于其他元素的空间位置;
      • (4)元素功能,描述元素的预期功能和可能的交互方式
      • 论文训练 UI-TARS 枚举截图中的所有可见元素,并生成它们的元素描述,条件是截图
  • 密集标题(Dense Captioning)
    • 论文训练 UI-TARS 理解整个界面,同时保持准确性并最大限度地减少幻觉
    • 密集标题的目标是提供 GUI 截图的全面、详细描述,不仅捕获元素本身,还包括它们的空间关系和界面的整体布局
    • 对于截图中记录的每个元素,论文首先获取它们的元素描述
    • 对于通常缺乏详细元数据的嵌入图像,论文也生成它们的描述性标题
    • 之后,论文使用VLM将所有图像和元素描述整合到一个连贯、高度详细的标题中,该标题保留了 GUI 布局的结构
    • 在训练期间,只给 UI-TARS 提供图像,并要求其输出相应的密集标题
  • 状态转换标题(State Transition Captioning)
    • 虽然密集标题提供了 GUI 界面的全面描述,但它没有捕获状态转换 ,特别是动作(例如,微小按钮被按下)对界面的细微影响
    • 为了解决这一限制,论文训练模型识别和描述两个连续截图之间的差异,并确定是否发生了诸如鼠标点击或键盘输入之类的动作
    • 论文还纳入了对应于非交互式 UI 变化(例如动画、屏幕刷新或背景更新)的截图对
    • 在训练期间,向 UI-TARS 展示一对图像,并要求其预测这两个图像的特定视觉变化(以及可能的原因)
    • 通过这种方式,UI-TARS 学习细微的 UI 变化,包括用户发起的动作和非交互式转换
    • 这种能力对于需要细粒度交互理解和动态状态感知的任务至关重要
  • 问答(Question Answering, QA)
    • 虽然密集标题和元素描述主要关注对 GUI 的布局和元素的理解,但问答提供了一种更动态、更灵活的方法,将这些任务与推理能力相结合
    • 论文合成了多样化的问答数据集,涵盖广泛的任务,包括界面理解、图像解释、元素识别和关系推理
    • 这增强了 UI-TARS 处理涉及更高程度抽象或推理的查询的能力
  • 标记集(Set-of-Mark, SoM)
    • 论文还增强了 UI-TARS 的标记集提示能力(2023b)
    • 论文根据 GUI 截图中解析元素的空间坐标,为这些元素绘制视觉上不同的标记
    • 这些标记在形式、颜色和大小等属性上有所不同,为模型提供清晰、直观的视觉线索,以定位和识别特定元素
    • 通过这种方式,UI-TARS 更好地将视觉标记与其对应的元素相关联
    • 论文将标记集标注与密集标题和问答等任务相结合
    • 例如,可以训练模型描述由标记突出显示的元素

Unified Action Modeling and Grounding

  • 改进动作能力的实际方法包括训练模型模仿人类在任务执行中的行为,即行为克隆(Bain和Sammut, 1995)
  • 虽然单个动作是离散和孤立的,但现实世界的智能体任务本质上涉及执行一系列动作,因此必须在多步轨迹上训练模型
  • 这种方法使模型不仅能够学习如何执行单个动作,还能学习如何有效地对它们进行排序( System 1 思维)
  • 统一动作空间(Unified Action Space)
    • 与先前的工作类似,论文设计了一个通用动作空间,标准化跨设备的语义等效动作(表1),例如 Windows 上的“点击”与移动设备上的“轻触”,实现跨平台的知识转移
    • 由于设备特定的差异,论文还引入了为每个平台量身定制的可选动作
    • 这确保模型能够处理每个设备的独特要求,同时在各种场景中保持一致性
    • 论文还定义了两个终端动作:Finished(),表示任务完成;以及 CallUser(),在需要用户干预的情况下调用,例如登录或身份验证
  • 动作轨迹收集(Action Trace Collection)
    • 训练模型执行任务的一个重大挑战在于多步轨迹数据的可用性有限,这些数据在历史上记录不足且稀少
    • 为了解决这个问题,论文依赖两个主要数据源:
      • (1)论文的标注数据集:
        • 论文开发了专门的标注工具,以捕获PC环境中各种软件和网站上的用户动作
        • 标注过程始于创建初始任务指令,标注者对其进行审查和完善,以确保清晰度并与预期目标保持一致
        • 然后,标注者执行任务,确保他们的动作满足指定的要求
        • 每个任务都经过严格的质量筛选;
      • (2)开源数据:
        • 论文还整合了多个现有数据集( MM-Mind2Web(2024b)、 GUI Act(2024c)、AITW(2023)、AITZ(2024d)、AndroidControl(2024c)、 GUI-Odyssey(2024a)、AMEX(2024)),并将它们标准化为统一的动作空间格式
        • 这涉及将不同的动作表示协调为一致的模板,以便与标注数据无缝集成。在表2中,论文列出了动作轨迹数据的基本统计信息
  • 提高定位能力(Improving Grounding Ability)
    • 定位(Grounding) ,即准确找到特定 GUI 元素并与之交互的能力,对于诸如点击或拖动之类的动作至关重要
    • 与多步动作数据不同,定位数据更容易扩展,因为它主要依赖于元素的视觉和位置属性,这些属性可以有效地合成或提取(2024; 2024a; 2024b)
    • 论文训练 UI-TARS 直接预测它需要交互的元素的坐标
    • 这涉及将 GUI 中的每个元素与其空间坐标和元数据相关联
  • 如章节4.2所述,论文收集了截图,并使用专门的解析工具提取了元数据,包括元素类型、深度、边界框和文本内容
  • 对于记录有边界框的元素,论文计算角的平均值以得出单个点坐标,表示边界框的中心
  • 为了构建训练样本,每个截图都与从元数据派生的单个元素描述配对
  • 模型的任务是输出归一化到屏幕尺寸的相对坐标,确保在具有不同分辨率的设备上保持一致性
    • 例如,给定描述“右上角标有‘提交’的红色按钮”,模型预测该按钮的归一化坐标
    • 这种描述和坐标之间的直接映射增强了模型理解和准确定位视觉元素的能力
  • 为了进一步扩充论文的数据集,论文整合了开源数据(Seeclick(2024)、 GUI Act(2024c)、MultiUI(2024b)、Rico-SCA(2020a)、WidgetCaption(2020b)、MUG(2024b)、Rico Icon(2022)、CLAY(2022)、UIBERT(2021)、OmniACT(2024)、Auto GUI (Anonymous, 2024)、OS-ATLAS(2024b)),并将它们标准化为论文的统一动作空间格式
  • 论文在表2中提供了用于训练的定位数据的基本统计信息
  • 这个组合的数据集使 UI-TARS 能够实现高精度定位,显著提高其在点击和拖动等动作中的有效性

Infusing System 2 Reasoning

  • 仅依靠 System 1 的直觉决策不足以处理复杂场景和不断变化的环境
  • 因此,论文旨在让UI-TARS结合 System 2 级别的推理,通过理解任务的全局结构灵活规划动作步骤
Reasoning Enrichment with GUI Tutorials
  • 第一步侧重于推理丰富(reasoning enrichment),论文利用公开可用的教程,这些教程交织了文本和图像,演示了在各种软件和网络环境中的详细用户交互
    • 这些教程为建立基础 GUI 知识提供了理想的来源,同时引入了任务执行中固有的逻辑推理模式
  • 论文选择 MINT(2024)和 OmniCorpus(2024a)这两个广受认可的图像-文本交错预训练数据集作为论文的初始数据源
    • 然而,这些数据集包含大量噪声,只有一小部分符合 GUI 教程标准
    • 为了提取高质量的教程数据,论文实施了多阶段数据收集和筛选流程:
      • (1)粗粒度筛选:
        • 为了分离类似教程的内容,论文使用手动精心挑选的高质量教程正集和来自 MINT 和 OmniCorpus 的随机样本作为负集,训练了一个fastText分类器(2016)
        • 然后应用训练好的分类器进行初步筛选,过滤掉不相关的样本并生成候选数据集
      • (2)细粒度筛选:
        • 为了进一步细化候选数据集,论文使用大型语言模型(Large Language Model, LLM)识别并移除假阳性样本
        • 这一步确保剩余样本符合 GUI 教程的特征。粗筛选和细筛选过程经过多轮迭代,以最大限度地提高高质量 GUI 教程的召回率
      • (3)去重和数据优化:
        • 对筛选后的数据集进行进一步优化,以处理重复项、广告和残留噪声
        • 使用基于 URL 的方法和局部敏感哈希(Locality-Sensitive Hashing, LSH)方法进行去重
        • 最后,论文提示大型语言模型重新表述教程中的所有文本内容,在优化内容的同时消除不相关或低质量的内容
  • 通过这个多阶段过程,论文精心挑选了大约 6M 个高质量的 GUI 教程
  • 平均而言,每个教程包含 510 个文本 Token 和 3.3 个图像。这些数据不仅增强了模型对 GUI 操作的理解,还为注入推理能力奠定了坚实的基础
Reasoning Stimulation with Thought Augmentation
  • 论文在章节4.3中收集的动作轨迹数据本质上是以动作为中心的,包含观察和动作序列 \((o_{i-1}, a_{i-1}, o_{i}, a_{i}, …)\),但缺乏明确的推理思考
  • 为了刺激 UI-TARS 的推理能力,论文通过标注“思考”来扩充数据集,以弥合感知和动作之间的差距
  • 这将数据格式转换为 \((o_{i-1}, t_{i-1}, a_{i-1}, o_{i}, t_{i}, a_{i}, …)\),其中 \(t\) 表示推理思考
  • 这些思考使模型能够明确表达其决策过程,促进与任务目标的更好对齐
  • 为了构建这些思考,论文采用两个标注阶段:
  • 阶段(1)ActRe(2024b):如(4)所示,对于在章节4.3中收集的每个轨迹,论文将它们分成多个步骤
    • 对于每个步骤 \(n\),其思考 \(t_{n}\) 通过用先前的上下文和当前的目标动作 \(a_{n}\) 提示视觉语言模型来迭代生成
    • 这种方法试图使生成的思考在逻辑上基于先前的上下文,并与当前动作对齐
      $$
      \left\{
      \begin{array}{l}
      t_{n} = VLM(instruction, (o_{1}, t_{1}, a_{1}), (o_{2}, t_{2}, a_{2}), …, o_{n}, a_{n}) \\
      t_{n+1} = VLM(instruction, (o_{1}, t_{1}, a_{1}), (o_{2}, t_{2}, a_{2}), …, (o_{n}, t_{n}, a_{n}), o_{n+1}, a_{n+1}) \\
      \vdots
      \end{array}
      \right. \tag{4}
      $$
    • 在ActRe标注期间,论文提示视觉语言模型表现出更高阶的 System 2 推理,这涉及审慎的、逐步的决策和反思
      • 通过促进这些推理模式,论文鼓励模型进行深思熟虑的长期规划和反思,以解决复杂任务
      • 如图6所示,论文提示视觉语言模型遵循的推理模式包括:
        • 任务分解:指导模型将复杂任务分解为更小的、可管理的子任务,使其能够逐步处理复杂的工作流
        • 长期一致性:确保模型在整个任务过程中保持一致的目标,参考整体目标和操作历史,以避免在复杂的多步骤任务中出现偏差
        • 里程碑识别:使模型能够识别中间目标的完成情况,促进向后续目标的平稳过渡
        • 试错:使模型能够假设、测试和评估潜在动作,特别是在模糊情况下(如无需直接交互即可验证搜索结果)
        • 反思:使模型能够在操作失败时识别并纠正错误,通过反思性推理鼓励适应性和错误恢复
  • 阶段(2)思考自举(Thought Bootstrapping) :
    • 基于真实动作反向标注思考(即ActRe)可能导致假阳性,因为生成的思考可能在表面上与相应动作匹配,但没有建立真正的因果关系
      • 具体而言,动作背后的推理过程可能被忽略,导致思考仅通过巧合与动作对齐,而不是通过逻辑推理
      • 出现这个问题是因为标注过程依赖于预先知道动作,这可能会使思考偏向于符合动作,而不是反映导致动作的实际决策过程
    • 为了解决这个问题,论文采用了一种自举方法,在不预先知道真实动作的情况下生成思考
      • 通过采样多个思考-动作对,如(5)所示,论文识别出导致正确动作的思考,确保推理与所选动作存在因果对齐
      • 这种方法产生更高质量的标注,因为它迫使模型模拟真正的决策过程,而不仅仅是为预先确定的动作辩护(\(UI-TARS_{early}\) 表示早期模型检查点)
        $$
        \left\{
        \begin{array}{l}
        (\hat{t}_{n_{i} }, \hat{a}_{n_{i} })_{i=1}^{max-try} = UI-TARS_{early}(instruction, (o_{1}, t_{1}, a_{1}), (o_{2}, t_{2}, a_{2}), …, o_{n}) \\
        Select(\hat{t}_{n_{i} }, \hat{a}_{n_{i} }), where \hat{a}_{n_{i} } = a_{n}
        \end{array}
        \right. \tag{5}
        $$
    • 论文用中文和英文两种语言标注思考,以扩大语言多样性
    • 尽管论文为所有轨迹增强了思考,但在训练期间论文也会涉及 vanilla 动作轨迹(没有思考)

Learning from Prior Experience in Long-term Memory

  • GUI 智能体在扩展到 LLM 级别方面面临重大挑战,主要原因是缺乏用于 GUI 操作的大规模、标准化、真实世界流程数据
  • 虽然大型语言模型可以利用丰富的文本数据,这些数据捕获了各种知识和推理模式,但详细记录 GUI 环境中用户交互和决策序列的流程数据很少被记录或系统地组织
  • 这种数据的缺乏阻碍了 GUI 智能体有效扩展和在广泛任务中泛化的能力
  • 一个有前景的解决方案在于从存储在长期记忆中的先前经验中学习
    • 通过捕获和保留来自先前任务的知识,智能体可以利用这些过去的经验为未来的决策提供信息,使其动作更具适应性和效率
  • 为了促进这一过程,论文让 UI-TARS 能够从与真实世界设备的交互中动态学习
    • 通过半自动化的数据收集、筛选和优化,该模型不断改进,同时最大限度地减少对人工干预的需求
    • 通过利用长期记忆,UI-TARS 建立在其积累的知识基础上,随着时间的推移优化其性能,并更有效地适应新任务
    • 这个过程的每次迭代都会产生一个更强大的模型
Online Trace Bootstrapping
  • 如图7所示,论文首先获得多样化的任务目标集,结合人工标注的指令和模型生成的指令
  • 在迭代 \(n\) 时,智能体 \(\psi_{n}\) 在目标 GUI 环境(例如虚拟PC)中执行这些指令,生成原始轨迹集:
    $$\mathcal{T}_{raw, n} = \{(o_{1}, t_{1}, a_{1}, o_{2}, t_{2}, a_{2}, …, o_{n}, t_{n}, a_{n}), \cdots\}$$
  • 为了确保高质量的数据,论文应用多级筛选函数:
    $$Filter(\mathcal{T}_{raw, n}, \mathcal{I}_{n}) = \mathcal{T}_{filtered, n}$$
  • 通过以下步骤丢弃有噪声或无效的轨迹:
    • (1)基于规则的奖励:启发式规则移除具有明显异常的轨迹(例如,不改变环境的冗余动作);
    • (2)视觉语言模型评分:视觉语言模型为剩余轨迹分配质量分数,移除得分低于预定义阈值的轨迹;
    • (3)人工审核:部分轨迹由标注者(annotators)进一步检查,他们识别出发生错误的步骤,丢弃后续动作,并只保留有效的前缀
  • UI-TARS利用得到的筛选轨迹集 \(\mathcal{T}_{filtered, n}\) 进行自我改进:
      $$M_{n+1} = FineTune(M_{n}, \mathcal{T}\_{filtered, n})$$
  • 对于每一轮,论文让标注者优化或扩展指令集:
    $$\mathcal{I}_{n+1} = HumanRefine(\mathcal{I}_{n}, \mathcal{T}_{filtered, n})$$
  • 论文在数百台虚拟 PC 上多轮迭代上述过程,不断利用最新模型 \(M_{n}\) 生成新轨迹,从而扩展和优化数据
Reflection Tuning
  • 在现实的在线部署中,智能体经常会遇到因缺乏自我反思和错误纠正能力而陷入困境的情况
    • 例如,智能体可能会反复点击无响应的按钮,或者由于对界面的误解而尝试无效操作
    • 如果没有识别这些错误或调整策略的能力,智能体将陷入无效动作的循环,无法向任务目标推进
  • 然而,大多数离线数据集包含理想化的、无错误的轨迹,因为标注者在数据标注过程中确保每个动作都符合预期
    • 虽然此类数据有助于减少模型训练期间的噪声,但也阻止了智能体学习如何从错误中恢复
  • 为了解决这一限制,论文提出了一种反思调整协议,使模型接触到自身犯下的真实世界错误及其纠正方法,使 UI-TARS 能够学习如何从次优决策中恢复
  • 对于 UI-TARS 生成的在线轨迹:
    $$ \mathcal{T} = (instruction, (o_{1}, t_{1}, a_{1}), (o_{2}, t_{2}, a_{2}), …, (o_{t}, t_{t}, a_{t})$)$$
    • 假设在步骤 \(\tau\) 发生错误,其中动作 \(a_{\tau}\) 被认为是无效的或次优的
    • 论文要求标注者识别此错误,并标记纠正后的思考和动作 \(t_{\tau}^{*}\)、\(a_{\tau}^{*}\)
    • 这产生了一个错误纠正轨迹对:
      $$
      \left\{
      \begin{array}{l}
      \mathcal{T}_{-} = (instruction, (o_{1}, t_{1}, a_{1}), (o_{2}, t_{2}, a_{2}), …, (o_{\tau}, t_{\tau}, a_{\tau})) \\
      \mathcal{T}_{+} = (instruction, (o_{1}, t_{1}, a_{1}), (o_{2}, t_{2}, a_{2}), …, (o_{\tau}, t_{\tau}^{*}, a_{\tau}^{*}))
      \end{array}
      \right.
      $$
  • 论文的创新是,进一步要求标注者基于错误动作 \(a_{\tau}\) 继续标记后续步骤,模拟错误已经发生的场景
    • 在确定下一步的思考 \(t_{\tau+1}^{*}\) 时,标注者必须承认先前错误的影响,弥补其影响,并提供正确的动作 \(a_{\tau+1}^{*}\) 以重新调整任务进度
    • 例如,如果前一步旨在将网页添加到书签,但错误地点击了关闭按钮,那么下一步应该包括重新打开最近关闭的网页,以重新尝试点击书签按钮
    • 形式上,论文有一个反思后轨迹对:
      $$
      \left\{
      \begin{array}{l}
      \mathcal{T}_{-} = (instruction, (o_{1}, t_{1}, a_{1}), (o_{2}, t_{2}, a_{2}), …, (o_{\tau}, t_{\tau}, a_{\tau}), (o_{\tau+1}, t_{\tau+1}, a_{\tau+1})) \\
      \mathcal{T}_{+} = (instruction, (o_{1}, t_{1}, a_{1}), (o_{2}, t_{2}, a_{2}), …, (o_{\tau}, t_{\tau}, a_{\tau}), (o_{\tau+1}, t_{\tau+1}^{*}, a_{\tau+1}^{*}))
      \end{array}
      \right.
      $$
  • 论文利用正样本 \(T_{+}\) 进行有 SFT 训练,并且只对纠正后的步骤(即 \((t_{\tau}^{*}, a_{\tau}^{*})\) 和 \((t_{\tau+1}^{*}, a_{\tau+1}^{*})\))计算损失,而错误步骤(即 \((t_{\tau}, a_{\tau})\))不用于训练
  • 通过这个过程,UI-TARS逐渐提高其识别和从错误中恢复的能力,使其能够在面对不完善或不确定的条件时做出有效调整
  • 培养这种反思能力增强了智能体对动态环境和任务的适应性
Agent DPO
  • 在在线自举期间,自然会产生大量错误步骤(负例) ,但 SFT 仅利用纠正后的步骤(即“正”例),而忽略负样本
    • 这限制了其明确引导智能体远离次优动作的能力
  • 为了解决这一限制,论文转向直接偏好优化(2023),它通过引入基于参考的目标函数来利用纠正后的动作和错误动作
    • 这种方法通过直接编码对纠正动作(正例)而非错误动作(负例)的偏好来优化 UI-TARS,从而更好地利用可用数据
  • 考虑智能体最初执行了错误动作 \(a_{\tau}\) 后来被纠正为优选动作 \(a_{\tau}’\) 的状态 \(s_{\tau}\)
    • 这里,状态 \(s_{\tau}\) 由指令及其到当前步骤的交互历史 \((o_{1}, t_{1}, a_{1}, …, o_{\tau-1}, t_{\tau-1}, a_{\tau-1})\) 组成
    • 这种全面的表示为智能体做出明智决策提供了必要的上下文
  • 关键思想是定义一个偏好可能性,量化模型对纠正动作 \(a_{\tau}’\) 相对于原始动作 \(a_{\tau}\) 的偏好程度
  • 形式上,论文定义一个学习到的奖励函数 \(r_{\theta}(s, a)\),估计在状态 \(s\) 中采取动作 \(a\) 的可取性
  • 基于 Bradley-Terry Model(1952),我们可以将成对偏好可能性表示为:
    $$P_{\theta}(a_{\tau}’ \succ a_{\tau} | s_{\tau}) = \frac{exp(r_{\theta}(s_{\tau}, a_{\tau}’))}{exp(r_{\theta}(s_{\tau}, a_{\tau})) + exp(r_{\theta}(s_{\tau}, a_{\tau}’))}$$
    • 其中,\(a_{\tau}’ \succ a_{\tau}\) 表示 \(a_{\tau}’\) 比 \(a_{\tau}\) 更受偏好
    • 分子表示分配给纠正动作的奖励的指数,而分母是两个动作的奖励的指数之和,确保可能性得到适当归一化
  • 直接偏好优化从具有KL散度约束的强化学习目标中导出解析最优策略。论文遵循直接偏好优化,用最优策略替换奖励函数 \(r_{\theta}\),并直接在偏好数据集上优化直接偏好优化目标:
    $$\mathcal{L}_{DPO}(\theta) = -\underset{\tau}{\mathbb{E} }\left[log \sigma\left(\beta log \frac{\pi_{\theta}(a_{\tau}’ | s_{\tau})}{\pi_{SFT}(a_{\tau}’ | s_{\tau})} - \beta log \frac{\pi_{\theta}(a_{\tau} | s_{\tau})}{\pi_{SFT}(a_{\tau} | s_{\tau})}\right)\right]$$
    • \(\tau\) 遍历所有有错误纠正对的时间步
    • \(\pi_{\theta}\) 表示最优智能体
    • \(\pi_{SFT}\) 表示有监督微调智能体
    • \(\beta\) 作为超参数控制最优智能体和有监督微调智能体之间的差异
  • 通过最小化直接偏好优化损失,论文使智能体增加纠正动作的可能性,同时减少错误动作的可能性,并使用隐式奖励函数

Training

  • 为了确保与现有工作(如 Aguvis(2024)和 OS-Atlas(2024b))进行公平比较,论文使用相同的视觉语言模型骨干 Qwen-2-VL(2024c),并采用三阶段训练过程
  • 该过程在各种 GUI 任务中优化模型的能力,使用的总数据量约为 50B 个 Token
  • 每个阶段逐步纳入更高质量的数据,以提高模型在复杂推理任务上的性能
  • 持续预训练阶段(Continual Pre-training Phase) :
    • 论文使用章节4中描述的全套数据(不包括反思调整数据)进行持续预训练,采用恒定的学习率
    • 这个基础阶段使模型能够学习自动 GUI 交互所需的所有知识,包括感知、定位和动作轨迹,确保对各种 GUI 元素和交互的稳健覆盖
  • 退火阶段(Annealing Phase) :
    • 然后,论文选择感知、定位、动作轨迹、反思调整数据的高质量子集进行退火
    • 退火过程逐渐调整模型的学习动态,促进更有针对性的学习,并更好地优化其在真实世界 GUI 交互场景中的决策策略
    • 论文将此阶段后训练的模型表示为 UI-TARS-SFT
  • 直接偏好优化阶段 :
    • 最后,论文使用来自在线自举数据的标注反思对进行直接偏好优化训练
    • 在此过程中,模型优化其决策,强化最优动作,同时惩罚次优动作
    • 这个过程提高了模型在真实世界 GUI 交互中做出精确、上下文感知决策的能力
    • 最终模型表示为 UI-TARS-DPO

Experiment

  • 在本节中,论文评估 UI-TARS 的性能
  • 该模型在第4节所述的包含约 50B 词元的数据集上进行训练
  • 论文选择 Qwen-2-VL (2024c) 作为训练的基础模型,并开发了三个模型变体:UI-TARS-2B、UI-TARS-7B 和 UI-TARS-72B
  • 论文进行了大量实验来验证所提出模型的优势
    • 这些实验旨在评估模型在感知、定位和智能体能力这三个关键维度上的能力
  • 最后,论文进行了对比分析,以进一步研究 System 1 和 System 2 推理对下游任务的影响
  • 在本节中,论文将公式(3)中的 \(N\) 设置为5
  • 在5.4节中,论文同时评估了 UI-TARS-SFT 和 UI-TARS-DPO 在 OSWorld 基准测试中的表现,因为该基准测试从 DPO 阶段的迭代改进中获益最大
  • 然而,对于其他基准测试,论文报告的是经过退火阶段训练后的模型(即 UI-TARS-SFT)的结果
  • Baseline :
    • 论文将 UI-TARS 与各种基线模型进行比较,包括:
      • 商业模型,如 GPT-4o (2024)、Claude-3.5-Sonnet (2024a)、Gemini-1.5-Pro (2024) 和 Gemini-2.0 (Project Mariner) (2024)
      • 学术模型,如 CogAgent (2024)、OminiParser (2024b)、InternVL (2024d)、Aria-UI (2024a)、Aguvis (2024)、OS-Atlas (2024b)、UGround (2024b)、ShowUI (2024a)、SeeClick (2024)、Qwen系列模型QwenVL-7B (2023b)、Qwen2-VL (7B和72B) (2024c)、UIX-Qwen2-7B (2024a) 和 Qwen-VL-Max (2023a)

Perception Capability Evaluation

  • 论文使用三个关键基准测试来评估 UI-TARS 模型的感知能力:VisualWebBench (2024c)、WebSRC (2021) 和ScreenQA-short (2022)
  • VisualWebBench 衡量模型理解和定位网页元素的能力,涵盖网页问答、网页光学字符识别(OCR)和动作预测等任务
  • UI-TARS 模型取得了出色的成绩,其 72B 变体得分达到 82.8,显著超过了 GPT-4o(78.5)和 Claude 3.5(78.2)等闭源模型,如表3所示
  • 对于 WebSRC 和 ScreenQA-short,这两个基准测试通过问答任务评估网页结构理解和移动屏幕内容理解能力,UI-TARS模型展现出明显的优势
    • WebSRC 专注于理解网页上下文中网页的语义内容和布局
    • ScreenQA-short 评估对复杂移动屏幕布局和界面相关问题的解读能力
  • UI-TARS-7B 在 WebSRC 上获得了领先的 93.6 分,而 UI-TARS-72B 在 ScreenQA-short 中以 88.6 分的成绩表现出色
  • 这些结果证明了 UI-TARS 在网页和移动环境中卓越的感知和理解能力
  • 这种感知能力为智能体任务奠定了基础,因为准确的环境理解对于任务执行和决策至关重要

Grounding Capability Evaluation

  • 为了评估 UI-TARS 的定位能力,论文重点关注三个基准测试:ScreenSpot Pro (2025)、ScreenSpot (2024) 和ScreenSpot v2 (2024b)
    • 这些基准测试评估在图形用户界面(GUI)中理解和定位元素的能力
    • ScreenSpot Pro 是为高分辨率专业环境设计的,该基准测试包括来自五个行业和三个操作系统的23个应用程序的专家标注任务
      • 它为在专业、高复杂度场景中评估模型的定位性能提供了严格的标准
    • ScreenSpot 和 ScreenSpot v2 在移动、桌面和网页平台上测试 GUI 定位能力
      • ScreenSpot 使用直接指令和自生成计划来评估模型
      • ScreenSpot v2通过纠正标注错误提高了评估的准确性
  • UI-TARS 在多个基准测试中始终优于基线模型
    • 具体来说,在表4中,UI-TARS-72B 在 ScreenSpot Pro 上获得了 38.1 分,显著超过 UGround-V1-7B(31.1)和OS-Atlas-7B(18.9)的性能
    • 值得注意的是,论文观察到在 ScreenSpot Pro 上提高输入图像分辨率会导致性能显著提升
    • 此外,UI-TARS-7B 在 ScreenSpot 上获得了领先的 89.5 分,如表5所示
    • 在ScreenSpot v2上,如表6所示,UI-TARS-7B(91.6)和UI-TARS-72B(90.3)均超过了现有基线模型,如OS-Atlas-7B(87.1),进一步凸显了论文方法的稳健性
    • 此外,结果显示,从UI-TARS-2B 到 UI-TARS-7B,在所有三个定位数据集上,定位性能都有显著提升
    • 比较 UI-TARS-7B 和 UI-TARS-72B,虽然 ScreenSpot v1 和 v2 没有显示出显著的性能变化,但 ScreenSpot Pro 显示出模型在规模扩大时的显著改进
    • 这表明 ScreenSpot v1 和 v2 可能不够稳健,无法充分捕捉更高规模下模型的定位能力

Offline Agent Capability Evaluation

  • 为了评估 UI-TARS 在静态、预定义环境中的 GUI 智能体能力,论文在三个基准测试上进行评估:
    • Multimodal Mind2Web (2024a)旨在创建和评估执行语言指令的通用网页智能体,主要评估模型在基于网页的环境中的性能,指标包括元素准确率(Ele.Acc)、操作F1分数(Op.F1)和步骤成功率(Step SR),如表7所示;
    • Android Control (2024c)评估移动环境中的规划和动作执行能力,该数据集包括两种类型的任务,高级任务要求模型自主规划和执行多步动作,低级任务指示模型为每个步骤执行预定义的、人工标注的动作(表8);
    • GUI Odyssey (2024a)专注于移动环境中的跨应用导航任务,每个任务平均有15个以上的步骤,任务涵盖各种导航场景,指令由预定义模板生成,该数据集包括在安卓模拟器上记录的人工演示,为每个任务情节提供详细且经过验证的元数据
    • 对于Multimodal Mind2Web,论文遵循原始框架中指定的设置和指标
    • 对于Android Control和 GUI Odyssey(表8),论文遵循OS-Atlas (2024b)中概述的设置和指标
  • 在这三个评估数据集中,UI-TARS 在推理和执行能力方面展现出明显的进步
    • 在 Multimodal Mind2Web(表7)中,大多数智能体模型显著优于基于框架的方法(使用 GPT-4o 或 GPT-4V 作为核心规划器)
      • 比较不同的智能体模型,UI-TARS-72B 在关键指标上实现了最优性能
      • UI-TARS-7B 尽管参数较少,但超过了 Aguvis-72B 模型和 Claude 等强大的基线模型
    • 在 AndroidControl 和 GUI Odyssey(表7)上,UI-TARS-7B 和 UI-TARS-72B 超过了之前的最优方法(OS-Atlas-7B),绝对性能提升了25,在多步离线任务中表现出显著的优势
    • 论文还发现,Claude Computer-Use 在基于网页的任务中表现强劲,但在移动场景中明显吃力,这表明 Claude 的 GUI 操作能力没有很好地转移到移动领域
    • 相比之下,UI-TARS 在网站和移动领域均表现出色,突出了其适应性和泛化能力

Online Agent Capability Evaluation

  • 在线评估模拟动态环境,每个环境都设计为一个交互式模拟,反映现实世界的场景
  • 在这些环境中,GUI 智能体可以通过实时执行动作来改变环境状态
  • 论文使用两个基准测试在在线环境中评估不同的模型:
    • OSWorld (2024) 提供了一个可扩展且多样化的环境,用于评估多模态智能体在 Ubuntu、Windows 和 macOS 平台上的复杂任务
      • 它由 369 个涉及现实世界网页和桌面应用程序的任务组成,具有详细的设置和评估脚本
      • 评估在仅使用截图的模式下进行
      • 为了减少网络不稳定和环境因素的潜在干扰,最终得分是3次运行的平均值
      • 论文也将模型决定 “CallUser” 的轨迹或模型最终未能输出 “Finish” 的轨迹视为不可行任务进行评估
    • AndroidWorld (2024b) 是一个用于在实时安卓模拟器上开发和基准测试自主智能体的环境。它包括来自20个移动应用程序的116个任务,通过随机参数生成动态任务变体。这个数据集非常适合评估智能体在移动环境中的适应性和规划能力。结果列于表9
      • 在OSWorld上,当给定15步的预算时,UI-TARS-7B-DPO(18.7)和UI-TARS-72B-DPO(22.7)显著超过Claude(14.9),展示了其强大的推理能力。此外,UI-TARS-72B-DPO在15步预算下(22.7)的表现与Claude在50步预算下(22.0)的表现相当,显示出很高的执行效率。值得注意的是,UI-TARS-72B-DPO在OSWorld上以50步预算获得了24.6的新最优结果,超过了所有现有的智能体框架(如搭配Aria-UI的GPT-4o),突出了智能体模型在以更高效率和效果处理复杂桌面任务方面的巨大潜力
      • AndroidWorld上的结果得出了类似的结论,UI-TARS-72B-SFT达到了46.6的性能,超过了之前最好的智能体框架(搭配Aria-UI的GPT-4o,44.8)和智能体模型(Aguvis-72B,26.1)
      • 比较SFT模型和DPO模型的结果,论文发现DPO显著提高了在OSWorld上的性能,这表明在训练过程中引入 “负样本” 能使模型更好地区分最优和次优动作
      • 此外,比较UI-TARS-72B和UI-TARS-7B,论文发现72B模型在在线任务中的表现比7B模型好得多,并且与离线任务相比差距更大(表7和表8)
        • 这表明扩大模型规模显著提高了 System 2 推理能力,使决策更加深思熟虑和符合逻辑
        • 而且,这种差异表明仅基于离线基准测试的评估可能无法准确反映模型在实时、动态环境中的能力
  • 总的来说,这些结果验证了智能体模型在推理密集型任务中的潜力,并强调了利用更大规模模型应对在线环境挑战的优势

Comparing System 1 and System 2 Reasoning

  • 论文比较 System 1 和 System 2 推理对模型性能的影响
    • System 1 推理是指模型直接生成动作而不进行思维链推理
    • System 2 推理则涉及更审慎的思维过程,模型在选择动作之前会生成推理步骤
  • 论文训练 UI-TARS-7B 以获得这两种能力,但在推理过程中通过提示工程修改模型的推理行为
In-domain Evaluation
  • 论文首先在三个域内智能体基准测试中评估性能:Multimodal Mind2Web、Android Control 和 GUI Odyssey,这些基准测试在 UI-TARS 中都有相应的训练数据
  • 为了提高评估效率,论文在 Android Control 和 GUI Odyssey 基准测试中随机抽取 1000 个示例
  • 论文使用最佳 \(N\) 选 1(Best-of-N,BoN)采样方法,即 UI-TARS 为每个输入采样 \(N\) 个候选输出, \(N\) 设置为 1、16 和 64
  • 评估指标采用步骤成功率
  • 如图8所示:
    • 当 \(N = 1\) 时,System 2 推理在所有三个域内基准测试中的表现略逊于 System 1 推理
      • 虽然通常期望 System 2 推理通过引入反思性的多步过程来提高任务执行能力,但这个结果表明,在单样本条件下, System 2 推理的复杂性可能导致次优的推理步骤
    • 具体来说,模型可能会引入无关或错误的推理步骤,例如引用不存在的对象或做出错误的推断,这增加了产生幻觉或无法生成正确动作的风险
    • 在没有多样化候选输出的情况下,模型可能会陷入有缺陷的推理路径,导致选择正确动作的可能性降低
  • 然而,当 \(N\) 增加到 16 和 64 时,System 2 模型开始显示出相对于 System 1 推理的明显优势。候选输出数量的增加在决策空间中提供了更大的多样性,使模型能够克服次优的推理路径。特别是, System 2 模型受益于探索多个推理链的机会,这弥补了N = 1时出现的问题。候选的多样性增加了正确动作出现在采样输出中的可能性,即使一些中间推理步骤并不理想。这种性能变化尤为显著,因为它表明当有足够的候选输出时, System 2 的审慎多步推理可以有效地弥补其最初的劣势
  • 一个关键的发现是,虽然 System 2 推理在多样性充足的情况下表现出色,但要在单一样本输出(如Bo1)中实现最佳性能仍然是一个重大挑战。未来的理想方向是利用 System 2 推理在多样化现实场景中的优势,同时尽量减少对多个样本的需求。这可以通过强化微调(2024)等技术来实现,该技术将引导模型在单次传递中高置信度地生成正确动作
域外评估
  • 接下来,论文在 AndroidWorld 上评估这两种推理方法,AndroidWorld 是一个域外(OOD)基准测试,在 UI-TARS 中没有相应的训练数据
  • 论文在 Bo1 设置下评估 UI-TARS-7B 和 UI-TARS-72B
  • 有趣的是,AndroidWorld 的结果与域内基准测试相比有显著差异
  • 虽然 System 1 推理在域内场景(Mind2Web、Android Control和 GUI Odyssey)中表现良好,但 System 2 推理在 OOD 设置(AndroidWorld)中显著优于 System 1
    • 这表明,尽管 System 2 在域内场景中可能面临挑战,特别是在单样本条件下,但其更深层次的推理能力在 OOD 情况下具有明显优势
    • 在这些情况下,增加的推理深度有助于模型泛化到以前未见过的任务 ,突出了 System 2 推理在现实世界多样化场景中的更广泛适用性和潜力

Conclusion

  • 论文介绍了 UI-TARS(一种原生 GUI 智能体模型),它将感知、动作、推理和记忆集成到一个可扩展且具有适应性的框架中
  • UI-TARS 在 OSWorld 等具有挑战性的基准测试中取得了 SOTA 性能,优于 Claude 和 GPT-4o 等现有系统
  • 论文提出了几项新颖的创新,包括增强的感知能力、统一的动作建模、System 2 推理以及利用在线轨迹进行迭代优化,所有这些创新都使该智能体能够在最少的人工干预下有效处理复杂的 GUI 任务
  • 论文还回顾了 GUI 智能体的发展路径,从基于规则的系统到具有适应性的原生模型
  • 论文根据人工干预程度和泛化能力,将发展过程划分为几个关键阶段,强调了从基于文本的方法到纯视觉、端到端智能体模型的转变
  • 论文还探讨了原生智能体模型的核心能力,包括感知、动作、推理和记忆,这些能力构成了 GUI 智能体未来发展的基础
  • 展望未来,虽然原生智能体代表了一大步进步,但未来的发展方向在于整合主动学习和终身学习,使智能体能够通过持续的现实世界交互自主驱动自身的学习

附录 A:Case Study

  • 论文列出了一些基于 UI-TARS 执行的 Case,详情见下面的图片
  • 图9:捕捉颜色并修改 PPT 背景颜色
  • 图10:打开 APP 播放歌曲
  • 图11:VSCode 安装插件
  • 图12:

Python——协程asyncio库的使用


整体说明

  • asyncio 是 Python 内置的 异步 I/O 框架 ,核心用于编写高效的并发代码,尤其适合处理 I/O 密集型任务(如网络请求、文件读写、数据库操作等)
  • asyncio 基于 协程(coroutine) 实现,通过事件循环(Event Loop)调度任务,避免了多线程的上下文切换开销,效率更高
  • asyncio 在较高的 Python 版本中才可以使用,建议在 Python 3.7 以上使用
  • 常见应用场景包括
    • 网络请求:并发调用 API(如结合 aiohttp 库)
    • 文件操作:异步读写文件(如结合 aiofiles 库)
    • 数据库操作:异步操作数据库(如 asyncpg 用于 PostgreSQL,motor 用于 MongoDB)
    • WebSocket 服务:实现高并发的实时通信(如 websockets 库)
    • 定时任务:通过 asyncio.create_task() + 循环实现简单定时任务
  • 仅适用于 I/O 密集型任务:
    • asyncio 是单线程的,CPU 密集型任务会阻塞事件循环,需结合 loop.run_in_executor() 提交到线程池/进程池
    • await 只能在协程中使用:await 关键字不能在普通函数中使用,必须在 async def 定义的协程中
    • 事件循环是单线程的:协程的并发是“协作式”的,需通过 await 主动交出执行权,否则会独占事件循环

asyncio 相关核心概念

协程(Coroutine)

  • 协程是可暂停、可恢复的函数,用 async def 定义(语法),是 asyncio 的核心执行单元
  • 协程函数调用后不会立即执行 ,而是返回一个协程对象(coroutine object),需通过事件循环调度才能运行

事件循环(Event Loop)

  • asyncio 的“大脑”,负责调度所有协程任务:
    • 管理任务的暂停/恢复、监听 I/O 事件、分发任务执行权
  • 通常通过 asyncio.run() 自动创建和管理事件循环(推荐用法)

等待对象(Awaitable)

  • 可被 await 关键字修饰的对象
    • 包括:协程对象、Task、Future
  • await 会暂停当前协程,等待目标对象完成后再恢复,期间事件循环可调度其他协程执行(实现并发)

Task(任务)

  • 对协程的封装,将协程注册到事件循环中,使其可被调度执行
  • 通过 asyncio.create_task() 创建,会自动加入事件循环并运行

Future

  • 表示异步操作的“未来结果”,是低层级的对象(通常无需手动创建,Task 继承自 Future)

asyncio 基础用法

  • 定义和运行协程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import asyncio

    # 定义协程函数(async def 关键字)
    async def hello(name):
    print(f"Hello, {name}! (开始)")
    # 模拟 I/O 等待(必须用 await 修饰可等待对象)
    await asyncio.sleep(1) # 暂停 1 秒,期间事件循环可执行其他任务
    print(f"Hello, {name}! (结束)")

    # 运行协程(Python 3.7+ 推荐用 asyncio.run())
    asyncio.run(hello("asyncio"))
    # Hello, asyncio! (开始)
    ## 【等待】... 1s
    # Hello, asyncio! (结束)

并发执行多个协程

  • 通过 asyncio.gather() 或 asyncio.create_task() 实现并发(多个任务同时执行,总耗时接近最长任务的耗时)

  • 方式 1:asyncio.gather()(批量等待多个协程)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import asyncio

    async def task1():
    await asyncio.sleep(2)
    return "Task 1 完成"

    async def task2():
    await asyncio.sleep(1)
    return "Task 2 完成"

    async def main():
    # 并发执行 task1 和 task2,等待所有完成后返回结果(按传入顺序)
    result1, result2 = await asyncio.gather(task1(), task2())
    print(result1)
    print(result2)

    # 总耗时大约 2 秒(而非 2+1=3 秒)
    asyncio.run(main())

    # Task 1 完成
    # Task 2 完成
  • 方式 2:asyncio.create_task()(手动创建任务)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import asyncio

    async def task(name, delay):
    await asyncio.sleep(delay)
    print(f"Task {name} 完成(延迟 {delay} 秒)")

    async def main():
    # 创建任务并自动加入事件循环
    task1 = asyncio.create_task(task("A", 2))
    task2 = asyncio.create_task(task("B", 1))

    # 等待任务完成(可单独等待,也可一起等待)
    await task1
    await task2

    asyncio.run(main())

    # Task B 完成(延迟 1 秒)
    # Task A 完成(延迟 2 秒)

附录:处理异常

  • 协程中的异常需通过 try/except 捕获,或在 gather() 中通过 return_exceptions=True 收集异常
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import asyncio

    async def task1():
    await asyncio.sleep(2)
    return "Task 1 完成"

    async def faulty_task():
    await asyncio.sleep(1)
    raise ValueError("任务执行失败!") # 模拟抛出异常

    async def main():
    # 方式1:捕获单个协程的异常
    try:
    await faulty_task()
    except ValueError as e:
    print(f"捕获异常:{e}") # 捕获异常:任务执行失败!

    # 方式2:批量捕获多个协程的异常(return_exceptions=True)
    results = await asyncio.gather(
    task1(), # 正常任务,输出 "Task 1 完成"
    faulty_task(), # 异常任务,抛出异常 ValueError("任务执行失败!")
    return_exceptions=True # 不终止,返回异常对象
    )
    print(results) # 输出:["Task 1 完成", ValueError("任务执行失败!")]

    asyncio.run(main())

协程的进阶特性

超时控制(asyncio.wait_for())

  • 限制协程的执行时间,超时则抛出 TimeoutError
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import asyncio

    async def long_task():
    await asyncio.sleep(3) # 模拟耗时 3 秒的任务

    async def main():
    try:
    # 限制任务 2 秒内完成,超时则取消任务并抛出异常
    result = await asyncio.wait_for(long_task(), timeout=2)
    except asyncio.TimeoutError:
    print("任务超时被取消!")

    asyncio.run(main())

任务取消(Task.cancel())

  • 手动取消正在执行的任务,被取消的任务会抛出 CancelledError
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import asyncio

    async def endless_task():
    try:
    while True:
    print("任务运行中...")
    await asyncio.sleep(1)
    except asyncio.CancelledError:
    print("任务被取消!")
    raise # 可选:重新抛出,让调用方知道任务被取消

    async def main():
    task = asyncio.create_task(endless_task())
    await asyncio.sleep(2) # 运行 2 秒后取消
    task.cancel()
    await task # 必须等待任务处理取消逻辑

    asyncio.run(main())

异步上下文管理器(async with)

  • 用于异步资源的获取和释放(如异步数据库连接、异步文件),需实现 __aenter__ 和 __aexit__ 方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import asyncio

    class AsyncResource:
    async def __aenter__(self):
    print("获取异步资源")
    await asyncio.sleep(0.5)
    return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
    print("释放异步资源")
    await asyncio.sleep(0.5)

    async def main():
    async with AsyncResource() as res:
    print("使用异步资源")

    asyncio.run(main())

    # 获取异步资源
    # 使用异步资源
    # 释放异步资源

异步迭代器(async for)

  • 用于迭代异步生成的数据(如异步流、分页接口),需实现 __aiter__ 和 __anext__ 方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import asyncio

    class AsyncIterator:
    def __init__(self, limit):
    self.limit = limit
    self.count = 0

    def __aiter__(self):
    return self

    async def __anext__(self):
    if self.count >= self.limit:
    raise StopAsyncIteration
    self.count += 1
    await asyncio.sleep(0.5) # 模拟异步获取数据
    return self.count

    async def main():
    async for num in AsyncIterator(3):
    print(f"迭代得到:{num}")

    asyncio.run(main())

    # 迭代得到:1
    # 迭代得到:2
    # 迭代得到:3

Python——多重继承


Python多重继承简单示例

  • Python多重继承简单示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Parent1:
    def method1(self):
    print("This is method 1 from Parent1")

    class Parent2:
    def method2(self):
    print("This is method 2 from Parent2")

    class Child(Parent1, Parent2):
    def child_method(self):
    print("This is a method from Child")

    # 创建Child类的实例
    child = Child()
    # 调用从Parent1继承的方法
    child.method1()
    # 调用从Parent2继承的方法
    child.method2()
    # 调用Child类自身的方法
    child.child_method()

super().__init__()的调用规则:

  • super().__init__()前,需要先构造整个MRO,super().__init__()本质实在调用MRO中该类的下一个

  • 所以:在单继承时会调用当前类的父类,但在调用MRO中的第一个方法父类的初始化,且仅调用第一个

  • 多重继承时,父类的MRO相对顺序会得到保障,但是可能会插入其他类(注:此时父类调用super().__init__()时可能不再直接调用其真实父类的初始化函数,而是MRO中的下一个)

    • 比如没有D时,B的直接父类是A(B的MRO是B-next->A),但是因为在D的MRO中,B的后一个是C(D的MRO是B-next->C),此时如果B没有初始化super().__init__(),则# Initializing C不会打印
  • 代码示例1:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    class Base1():
    def __init__(self):
    self.name = "Base1"
    print("Initializing Base1")

    def show(self):
    print(f"Name from Base1: {self.name}")

    class Base2():
    def __init__(self):
    self.name = "Base2"
    print("Initializing Base2")

    def show(self):
    print(f"Name from Base2: {self.name}")

    class DerivedUsingInit(Base1, Base2):
    def __init__(self):
    Base1.__init__(self)
    Base2.__init__(self)
    print("Initializing DerivedUsingInit")


    print("\nUsing init for initialization:")
    derived1 = DerivedUsingInit()
    derived1.show()
    print("DerivedUsingInit.__mro__:", DerivedUsingInit.__mro__)

    print("------------------")
    class DerivedUsingSuper(Base1, Base2):
    def __init__(self):
    super().__init__() # 仅调用Base1的__init__函数,不会调用Base2的
    print("Initializing DerivedUsingSuper")

    print("\nUsing super() for initialization:")
    derived2 = DerivedUsingSuper()
    derived2.show()
    print("DerivedUsingSuper.__mro__:", DerivedUsingSuper.__mro__)

    # Initializing Base1
    # Initializing Base2
    # Initializing DerivedUsingInit
    # Name from Base1: Base2
    # DerivedUsingInit.__mro__: (<class '__main__.DerivedUsingInit'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class 'object'>)
    # ------------------
    #
    # Using super() for initialization:
    # Initializing Base1
    # Initializing DerivedUsingSuper
    # Name from Base1: Base1
    # DerivedUsingSuper.__mro__: (<class '__main__.DerivedUsingSuper'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class 'object'>)
  • 代码示例2:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class A:
    def __init__(self):
    print("Initializing A")

    class B(A):
    def __init__(self):
    super().__init__() # 单继承时调用A的init,多继承时可能调用其他的类的init
    print("Initializing B")

    class C(A):
    def __init__(self):
    super().__init__()
    print("Initializing C")

    class D(B, C):
    def __init__(self):
    super().__init__()
    print("Initializing D")

    d = D()
    print("D.__mro__: ", D.__mro__)
    print("B.__mro__: ", B.__mro__)

    # Initializing A
    # Initializing C
    # Initializing B
    # Initializing D
    # D.__mro__: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
    # B.__mro__: (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
  • 代码示例3:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class A:
    def __init__(self):
    print("Initializing A")

    class B(A):
    def __init__(self):
    # super().__init__()
    print("Initializing B")

    class C(A):
    def __init__(self):
    super().__init__()
    print("Initializing C")

    class D(B, C):
    def __init__(self):
    super().__init__()
    print("Initializing D")

    d = D()
    print("D.__mro__: ", D.__mro__)
    print("B.__mro__: ", B.__mro__)

    # Initializing B
    # Initializing D
    # D.__mro__: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
    # B.__mro__: (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

MRO的简单理解

  • Python 2.3 及以后的版本采用 C3 线性化算法来确定 MRO,C3 线性化算法的核心目标是保证 MRO 满足三个重要特性:
    • 单调性:子类必须在父类之前被检查
    • 局部优先性:类定义中父类的顺序会被保留
    • 一致性:如果一个类继承自多个父类,那么 MRO 必须保证所有父类的 MRO 顺序一致
    • 注意:对于相同名称的同一个函数,MRO顺序靠前的生效,靠后的会被靠前的覆盖
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class A:
    pass

    class B(A):
    pass

    class C(A):
    pass

    class D(B, C):
    pass

    # 打印 D 类的 MRO
    print(D.mro())

继承顺序要满足MRO规则

  • 错误示例,下面的GrandChild1不满足MRO规则,会报错:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Parent:
    def __init__(self):
    self.value = "I'm from Parent"

    class Child1(Parent):
    def __init__(self):
    self.value = "I'm from Child1"

    class Child2(Parent):
    def __init__(self):
    self.value = "I'm from Child2"

    class GrandChild(Child1, Child2, Parent): # OK
    def __init__(self):
    self.value = "I'm from GrandChild"

    class GrandChild1(Parent, Child1, Child2): # 报错
    def __init__(self):
    self.value = "I'm from GrandChild1"
    # TypeError: Cannot create a consistent method resolution order (MRO) for bases Parent, Child1, Child2

补充:super()函数详细说明

  • super()函数本质是返回了当前类的 MRO 的一个下一个对象,对于单继承模式而言,就是当前类的父类

super()函数的高阶用法

  • super()函数还可以传入参数,示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class A:
    def method(self):
    print("Method in A")

    class B(A):
    def method(self):
    print("Method in B")

    class C(A):
    def method(self):
    print("Method in C")

    class D(B, C):
    def method(self):
    super(B, self).method() # 调用C类的method方法
    # super(B, self).method1() # 报错,因为C类没有method1方法
    print("Method in D")

    d = D()
    d.method()
    print(D.__mro__)
    print(B.__mro__)

    # Method in C
    # Method in D
    # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
    # (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
    • super(B, self) 是按照方法解析顺序(MRO)来查找 B 类在当前实例 self 的继承关系中的下一个类
    • Python会根据类的MRO来确定方法的调用顺序,MRO是一个列表,它定义了类及其父类的搜索顺序。当使用 super(B, self) 时,它会在 self 所属类的MRO中,从 B 类的下一个位置开始查找方法。例如在多重继承中,通过这种方式可以明确指定从某个类之后的MRO顺序中查找方法,以实现特定的方法调用逻辑
    • 在这个例子中, D 类继承自 B 和 C 类,而 B 和 C 又都继承自 A 类。在 D 类的 method 方法中,使用 super(B, self).method() 明确指定跳过 B 类,调用 C 类中继承自 A 类的 method 方法
  • 注意:在Python的 super() 函数中,默认是调用自身类和对象作为参数的 super 函数,例如:

    1
    2
    3
    4
    class A(B):
    def __init__(self):
    # 等价于调用super().__init__(),
    super(A, self).__init__()
    • 注:Python3中调用super(A, self).__init__() 等价于调用super().__init__(),但在部分Python2旧版本中必须明确指明类和对象
  • 自定义类层次结构中的方法调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    class Base:
    def operation(self):
    print("Base operation")

    class Derived1(Base):
    def operation(self):
    print("Derived1 operation")

    class Derived2(Base):
    def operation(self):
    print("Derived2 operation")

    class Composite:
    def __init__(self):
    self.derived1 = Derived1()
    self.derived2 = Derived2()
    def operation(self):
    super(Derived1, self.derived1).operation() # 调用Base的operation方法
    super(Derived2, self.derived2).operation() # 调用Base的operation方法
    composite = Composite()
    composite.operation()
    print(Composite.__mro__)
    print(Derived1.__mro__)
    print(Derived2.__mro__)

    # Base operation
    # Base operation
    # (<class '__main__.Composite'>, <class 'object'>)
    # (<class '__main__.Derived1'>, <class '__main__.Base'>, <class 'object'>)
    # (<class '__main__.Derived2'>, <class '__main__.Base'>, <class 'object'>)
    • 在 Composite 类中,通过 super(cls, instance) 的方式,分别调用了 Derived1 和 Derived2 类的父类 Base 中的 operation 方法。这种方式可以在自定义的类层次结构中,灵活地控制方法的调用路径
    • 不建议使用过于复杂的操作,非必要也不做多重继承,通常情况下,使用默认的 super() 函数调用方式就能满足大多数的编程需求,但是很多官方的库中,会明确显式知名其自身类和对象,比如PyTorch的 nn.Linear 类的实现:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      class Linear(Module):
      """...comments..."""
      # ...
      def __init__(self, in_features: int, out_features: int, bias: bool = True,
      device=None, dtype=None) -> None:
      factory_kwargs = {'device': device, 'dtype': dtype}
      super(Linear, self).__init__() # 这里是显示调用其父类的初始化函数,等价于 super().__init__()
      self.in_features = in_features
      self.out_features = out_features
      self.weight = Parameter(torch.empty((out_features, in_features), **factory_kwargs))
      if bias:
      self.bias = Parameter(torch.empty(out_features, **factory_kwargs))
      else:
      self.register_parameter('bias', None)
      self.reset_parameters()
1…242526…61
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

608 posts
49 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4