2024年11月

全新向量数据库SQL Server 2025:带你迈入AI驱动的数据未来

上次大家下单的《
微软憋大招:SQL Server + Copilot = 地表最强AI数据库!
》 抱怨迟迟没有发货,这次微软没有食言,
终于发货

前言

随着人工智能技术的普及,客户的数据平台和应用程序正面临新挑战。大多数组织预计会在云、边缘和专用基础设施的混合环境中部署人工智能工作负载,而隐私和安全性比以往任何时候都更重要。

SQL Server 2025预览版已经发布

微软 SQL Server 2025
(目前预览版)是一款支持企业级人工智能、从本地到云的数据库,旨在通过将人工智能引入客户数据来应对这些挑战。本次发布延续了 SQL Server 在性能和安全性上的三十年创新,并新增了人工智能功能。通过与 Microsoft Fabric 集成,客户可以将数据带入下一代数据分析领域。该版本支持混合环境,包括云、本地数据中心和边缘设备,同时利用 Microsoft Azure 的创新技术服务客户的数据库需求。 SQL Server 2025 社区技术预览(CTP)1已经发布

全新的向量数据库

SQL Server 2025 正在转变为一个
向量数据库
,通过内置的过滤能力和高效的向量搜索,为开发人员提供卓越的性能,并支持使用熟悉的 T-SQL 语法轻松调用。

内置高级人工智能

这个新版本集成了人工智能功能,简化了人工智能应用程序开发,并支持基于向量的增强检索生成(RAG)模式,具有安全、高性能且易于使用的向量支持功能。借助此新功能,您可以将向量与 SQL 数据结合,用于混合人工智能向量搜索。

用企业数据库构建人工智能应用程序

SQL Server 2025 是一款具备企业级安全性和合规性的向量数据库,可将企业人工智能引入您的数据中。其原生向量存储和索引由 DiskANN 提供支持,这是一种基于磁盘存储的向量搜索技术,可高效地在大数据集中找到相似的数据点。这些数据库能够高效支持数据分块,并通过语义搜索实现精确的数据检索。在最新版本中,SQL 引擎内还提供了灵活的人工智能模型管理功能,支持基于 REST 接口的从本地到云的人工智能模型调用。

此外,无论客户正在进行数据预处理、模型训练还是 RAG 模式,扩展的低代码工具都提供了灵活的模型接口,支持通过 T-SQL 和外部 REST 端点进行操作。这些工具通过与 LangChain、Semantic Kernel 和 Entity Framework Core 等流行人工智能框架的无缝集成,增强了开发人员创建各种人工智能应用程序的能力。


允许用户使用 T-SQL 命令和 sp_invoke_external_rest_endpoint 直接调用 ChatGPT 等 AI 服务
内置 DiskANN 组件进行向量搜索

数据库内置原生向量数据类型和向量索引

提升开发者生产力

在构建数据密集型应用程序(例如人工智能应用程序)时,扩展性、框架和数据增强是提升开发者生产力的关键。
我们确保 SQL Server 为开发人员提供一流的体验,新增以下功能:

  • REST API 支持
  • 通过 Data API Builder 集成 GraphQL
  • 支持正则表达式
  • 原生的 JSON 支持让开发人员能够更有效地处理频繁变化的模式和层级数据,从而更轻松地创建动态应用程序。所有功能都由 SQL Server 引擎提供的安全性保障,使其成为真正的企业级人工智能平台。

世界一流的安全性和性能

SQL Server 2025 是数据库安全性和性能的行业领导者。

  • 改进的凭据管理:支持 Microsoft Entra 托管身份,减少潜在漏洞并提供合规性和审计能力。

  • 性能和可用性增强:引入从 Azure SQL 测试过的查询优化和执行性能改进功能。

  • 新的优化功能: 可选参数计划优化(OPPO):根据客户提供的运行时参数值选择最佳执行计划,显著减少参数嗅探问题。

  • 辅助副本上的持久统计信息:防止在重新启动或故障转移期间丢失统计信息,避免性能下降。

Microsoft Fabric 和 Azure Arc 集成

  • 实时分析能力:Fabric 的 Mirrored SQL Server Database 功能提供了近实时的数据复制能力,使 SQL Server 数据可以轻松集成到 Microsoft OneLake 统一数据平台中。

  • 简化管理:通过 Azure Arc,SQL Server 2025 提供自动补丁、自动备份和许可证管理等功能。


参考文章

https://www.brentozar.com/archive/2024/11/whats-new-in-sql-server-2025/

https://www.linkedin.com/pulse/announcing-sql-server-2025-bob-ward-6s0hc

https://redmondmag.com/Articles/2024/11/19/SQL-Server-2025-Announced-at-Microsoft-Ignite-2024.aspx

本文版权归作者所有,未经作者同意不得转载。

与关系型数据库事务的区别

Redis事务是指将多条命令加入队列,一次批量执行多条命令,每条命令会按顺序执行,事务执行过程中不会被其他客户端发来的命令所打断。也就是说,Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

Redis事务和关系型数据库的事务不太一样,它
不保证原子性,也没有隔离级别
的概念。

事务不保证原子性,但是Redis命令本身是原子性的

  1. Redis事务没有隔离级别的概念
    批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务里的查询要看到本事务的更新或其它事务的修改更新操作的问题。(Mysql里的事务的语句不是放入队列,而是直接执行)
  2. Redis不保证原子性
    Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

Redis事务的运行流程

Redis事务相关命令

  • Multi :开始事务
  • Exec :执行事务中的所有命令,即提交;
  • discard :放弃事务;和回滚不一样,Redis事务不支持回滚。
  • WATCH:监视Key改变,用于实现乐观锁。如果监视的Key的值改变,事务最终会执行失败。
  • UNWATCH:放弃监视。

没有隔离级别

当事务开启时,事务期间的命令并没有执行,而是加入队列,只有执行EXEC命令时,事务中的命令才会按照顺序执行,也就是说事务间就不会存在数据脏读、不可重复读、幻读的问题,因此就没有隔离级别。

事务不保证原子性

如上图所示,在通过EXEC执行事务时,其中命令执行失败不会影响到其他命令的执行,因此并没有保证同时成功和同时失败的原子操作,尽管这样,Redis事务中也
没有提供回滚
的支持

官方理由为:保证Redis的性能

  • 事实上如果使用Redis命令语法错误,或是将命令运用在错误的数据类型键上(如对字符串进行加减乘除等),从而导致业务数据有问题,这种情况认为是编程导致的错误,应该在开发过程中解决,避免在生产环境中发生;
  • 由于不用支持回滚功能,Redis内部简单化,而且还比较快;

多数事务失败是由语法错误或者数据结构类型错误导致的,语法错误说明在命令入队前就进行检测的,而类型错误是在执行时检测的,Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的,特别要注意区分。Redis之所以保持这样简易的事务,完全是为了保证高并发下的核心问题——性能。

语法错误(编译器错误)

在开启事务后,A的转出操作命令打成了
DECRBYa
,最终会导致事务提交失败,所有命令都不会执行,A、B保留原值。

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBYa A 500
(error) ERR unknown command 'DECRBYa', with args beginning with: 'A' '500'
127.0.0.1:6379(TX)> INCRBY B 500
QUEUED
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> mget A B
1) "1000"
2) "100"
127.0.0.1:6379>

类型错误(运行时错误)

在运行时检测类型错误,此时事务并没有回滚,而是跳过错误命令继续执行, 结果B值改变、A保留原值。

小结

  • 当事务中命令语法使用错误时,最终会导致事务执行不成功,即事务内所有命令都不执行;
  • 当事务中命令知识逻辑错误,就比如给字符串做加减乘除操作时,只能在执行过程中发现错误,这种事务执行中失败的命令不影响其他命令的执行。

使用WATCH实现乐观锁

WATCH通过监视指定Redis Key,如果没有改变,就执行成功,如果发现对应值发生改变,事务就会执行失败,如下图:

三种方式可以取消监视:

  • 事务执行之后,不管是否执行成功还好是失败,都会取消对应的监视;
  • 当监视的客户端断开连接时,也会取消监视;
  • 可以手动UNWATCH取消所有Key的监视;

面试题专栏

Java面试题专栏
已上线,欢迎访问。

  • 如果你不知道简历怎么写,简历项目不知道怎么包装;
  • 如果简历中有些内容你不知道该不该写上去;
  • 如果有些综合性问题你不知道怎么答;

那么可以私信我,我会尽我所能帮助你。

开心一刻

上午一好哥们微信我
哥们:哥们在干嘛,晚上出来吃饭
我:就我俩吗
哥们:对啊
我:那多没意思,我叫俩女的出来
哥们:好啊,哈哈哈
晚上吃完饭到家后,我给哥们发消息
我:今天吃的真开心,下次继续
哥们:开心尼玛呢!下次再叫你老婆和你女儿来,我特么踢死你

开心一刻

写在前面

正文开始之前了,我们先来正确审视下文章标题:
不依赖 Spring,你会如何自实现 RabbitMQ 消息的消费
,主要从两点来进行审视

  1. 不依赖 Spring

    作为一个
    Javaer
    ,关于
    Spring
    的重要性,我相信你们都非常清楚;回头看看你们开发的项目,是不是都是基于 Spring 的?如果不依赖 Spring,你们还能继续开发吗?不过话说回来,既然 Spring 能带来诸多便利,该用还得用,不要头铁,不要造低效轮子!


    如果能造出比 Spring 优秀的轮子,那你应该造!


    你们可能会说:不依赖 Spring 就不依赖嘛,我可以依赖
    Spring Boot
    噻;你们要是这么聊天,那就没法聊了


    这还咋聊

    Spring Boot 是不是基于 Spring 的?没有 Spring,Spring Boot 也是跑不起来的;
    不依赖 Spring
    的言外之意就是不依赖 Spring 生态,当然也包括 Spring Boot

    关于
    不依赖 Spring
    ,我就当你们审视清楚了哦

  2. 依赖 RabbitMQ Java Client

    与 RabbitMQ 服务端的交互,咱们就不要逞强去自实现了,老实点用官方提供的 Java Client 就好

    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.7.3</version>
    </dependency>
    

    注意 client 版本要与 RabbitMQ 版本兼容

所以文章标题就可以转换成

只依赖 RabbitMQ Java Client,不依赖 Spring,如何自实现 RabbitMQ 消息的消费

另外,我再带你们回顾下 RabbitMQ 的 Connection 和 Channel

  1. Connection

    Connection
    是客户端与 RabbitMQ 服务器之间的一个 TCP 连接,它是进行通信的基础,允许客户端发送命令到 RabbitMQ 服务器并接收响应;Connection 是比较重的资源,不能随意创建与关闭,一般会以

    的方式进行管理。每个 Connection 可以包含多个 Channel

  2. Channel

    Channel

    多路复用
    连接(Connection)中的一条独立的双向数据流通道。客户端与 RabbitMQ 服务端之间的大多数操作都是在 Channel 上进行的,而不是在 Connection 上直接进行。Channel 比 Connection 更轻量级,可以在同一连接中创建多个 Channel 以实现并发处理

    Channel 与 Consumer 之间的关系是一对多的,具体来说,一个 Channel 可以绑定多个 Consumer,但每个 Consumer 只能绑定到一个

自实现

我们采取主干到枝叶的实现方式,逐步实现并完善 RabbitMQ 消息的消费

主流程

依赖 RabbitMQ Java Client 来消费 RabbitMQ 的消息,代码实现非常简单,网上一搜一大把

/**
 * @author: 青石路
 */
public class RabbitTest1 {

    private static final String QUEUE_NAME = "qsl.queue";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = initConnectionFactory();
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.basicConsume(QUEUE_NAME, false, new QslConsumer(channel));
        System.out.println(Thread.currentThread().getName() + " 线程执行完毕,消费者:" + consumerTag + "已经就绪");
    }

    public static ConnectionFactory initConnectionFactory() {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("10.5.108.226");
        factory.setPort(5672);
        factory.setUsername("admin");
        factory.setPassword("admin");
        factory.setVirtualHost("/");
        factory.setConnectionTimeout(30000);
        return factory;
    }

    static class QslConsumer extends DefaultConsumer {

        QslConsumer(Channel channel) {
            super(channel);
        }

        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            String message = new String(body, StandardCharsets.UTF_8);
            System.out.println(Thread.currentThread().getName()+ " 收到消息:" + message);
            this.getChannel().basicAck(envelope.getDeliveryTag(), false);
        }
    }
}

是不是很简单?

这里我得补充下,
exchange

queue
没有在代码中声明,绑定关系也没有声明,是为了简化代码,因为文章标题是
消费
;实际
exchange

queue

binding
这些已经存在,如下图所示

exchange_queue声明

上述代码,我相信你们都能看懂,主要强调下 2 点

  1. 消息是否自动 Ack

    对应代码

    channel.basicConsume(QUEUE_NAME, false, new QslConsumer(channel));
    

    basicConsume
    的第二个参数,其注释如下


    basicConsume_消息确认方式

    autoAck

    true
    表示消息在送达到 Consumer 后被 RabbitMQ 服务端确认,消息就会从队列中剔除了;
    autoAck

    false
    表示 Consumer 需要显式的向 RabbitMQ 服务端进行消息确认

    因为我们将
    autoAck
    设置成了
    true
    ,所以 main 线程存活的时间内,5 个消息被送达到 main 线程后就被 RabbitMQ 服务端确认了,也就从队列中删除了

  2. 手动确认

    如果 Consumer 的
    autoAck
    设置的是
    false
    ,那么需要显示的进行消息确认

    this.getChannel().basicAck(envelope.getDeliveryTag(), false);
    

    否则 RabbitMQ 服务端会将消息一直保留在队列中,反复投递

执行 main 方法,控制台输出如下

消费者就绪

我们去 RabbitMQ 控制台看下队列
qsl.queue
的消费者

RabbitMQ消费者

Consumer tag
值是:
amq.ctag-PxjqYiujeCvyYlgtvMz9EQ
,与控制台的输出一致;我们手动往队列中发送一条消息

发送消息

控制台输出如下

手动发送一条消息_控制台输出

自此,主流程就通了,此时已经实现 RabbitMQ 消息的消费

多消费者

单消费者肯定存在性能瓶颈,所以我们需要支持多消费者,并且是同个队列的多消费者;实现方式也很简单,只需要调整下 main 方法即可

public static void main(String[] args) throws Exception {
    ConnectionFactory factory = initConnectionFactory();
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();
    QslConsumer qslConsumer = new QslConsumer(channel);
    String consumerTag1 = channel.basicConsume(QUEUE_NAME, false, qslConsumer);
    String consumerTag2 = channel.basicConsume(QUEUE_NAME, false, qslConsumer);
    String consumerTag3 = channel.basicConsume(QUEUE_NAME, false, qslConsumer);
    System.out.println(Thread.currentThread().getName() + " 线程执行完毕,消费者["
            + Arrays.asList(consumerTag1, consumerTag2, consumerTag3) + "]已经就绪");
}

执行main 方法后 Channel 与 Consumer 关系如下

同个Channel_3个Consumer

此时是同个 Channel 绑定了 3 个不同的 Consumer;当然也可以一对一绑定,main 方法调整如下

public static void main(String[] args) throws Exception {
    ConnectionFactory factory = initConnectionFactory();
    Connection connection = factory.newConnection();
    Channel channel1 = connection.createChannel();
    Channel channel2 = connection.createChannel();
    Channel channel3 = connection.createChannel();
    QslConsumer qslConsumer1 = new QslConsumer(channel1);
    QslConsumer qslConsumer2 = new QslConsumer(channel2);
    QslConsumer qslConsumer3 = new QslConsumer(channel3);
    String consumerTag1 = channel1.basicConsume(QUEUE_NAME, false, qslConsumer1);
    String consumerTag2 = channel2.basicConsume(QUEUE_NAME, false, qslConsumer2);
    String consumerTag3 = channel3.basicConsume(QUEUE_NAME, false, qslConsumer3);
    System.out.println(Thread.currentThread().getName() + " 线程执行完毕,消费者["
            + Arrays.asList(consumerTag1, consumerTag2, consumerTag3) + "]已经就绪");
}

执行 main 方法后 Channel 与 Consumer 关系如下

3个Channel_3个Consumer

既然两种方式都可以实现多消费者,哪那种方式更好呢

Channel 与 Consumer 一对一绑定更好!

Channel 之间是线程安全的,同个 Channel 内非线程安全,所以同个 Channel 上同时处理多个消费者存在并发问题;另外 RabbitMQ 的消息确认机制是基于Channel 的,如果一个 Channel 上绑定多个消费者,那么消息确认会变得复杂,非常容易导致消息重复消费或丢失

也许你们会觉得
一对一
的绑定相较于
一对多
的绑定,存在资源浪费问题;确实是有这个问题,但我们要知道,Channel 是 Connection 中的一条独立的双向数据流通道,非常轻量级,相较于并发带来的一系列问题而言,这点小小的资源浪费可以忽略不计了

消费者数量能不能配置化呢,当然可以,调整非常简单

private static final int concurrency = 3;

public static void main(String[] args) throws Exception {
    ConnectionFactory factory = initConnectionFactory();
    Connection connection = factory.newConnection();
    for (int i = 0; i < concurrency; i++) {
        Channel channel = connection.createChannel();
        QslConsumer qslConsumer = new QslConsumer(channel);
        String consumerTag = channel.basicConsume(QUEUE_NAME, false, qslConsumer);
        System.out.println("消费者:" + consumerTag + " 已经就绪");
    }
}

concurrency
的值是从数据库读取,还是从配置文件中获取,就可以发挥你们的想象呢;如果依赖 Spring 的话,往往会用配置文件的方式注入进来

消费者预取数

队列
qsl.queue
没有消费者的情况下,我们往队列中添加 5 条消息:我是消息1 ~ 我是消息5,然后调整下
handleDelivery

@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    String message = new String(body, StandardCharsets.UTF_8);
    System.out.println(consumerTag + " 收到消息:" + message);
    this.getChannel().basicAck(envelope.getDeliveryTag(), false);
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

最后执行 main,控制台输出如下

5条消息同个消费者消费

大家注意看框住的那部分,5 条消息被同一个消费者给消费了!5 条消息为什么不是负载均衡到 3 个消费者呢?这是因为消费者的
prefetch count
(即
预取数
)没有设置

prefetch count 是消费者在接收消息时,告诉 RabbitMQ 一次最多可以发送多少条消息给该消费者。默认情况下,这个值是 0,这意味着 RabbitMQ 会尽可能快地将消息分发给消费者,而不考虑消费者当前的处理能力

再回过头去看控制台的输出,是不是就能理解了?一旦某个消费者就绪,队列中的 5 条消息全部推给它了,后面就绪的 2 个消费者就没有消息可消费了;所以我们需要配置
prefetch count
以实现负载均衡,调整很简单

private static final String QUEUE_NAME = "qsl.queue";
private static final int concurrency = 3;
private static final int prefetchCount = 1;

public static void main(String[] args) throws Exception {
    ConnectionFactory factory = initConnectionFactory();
    Connection connection = factory.newConnection();
    for (int i = 0; i < concurrency; i++) {
        Channel channel = connection.createChannel();
        channel.basicQos(prefetchCount);
        QslConsumer qslConsumer = new QslConsumer(channel);
        String consumerTag = channel.basicConsume(QUEUE_NAME, false, qslConsumer);
        System.out.println("消费者:" + consumerTag + " 已经就绪");
    }
}

然后重复如上的测试,控制台输出如下

负载均衡消费

是不是实现了我们想要的
负载均衡

prefetch count 的设置需要根据实际的业务需求和消费者的处理能力进行调整;如果设置得太高,可能会导致内存占用过多;如果设置得太低,则可能无法充分利用消费者的处理能力

其他完善

限于篇幅,我就只列举几个还待完善的点

  1. 目前只支持单个队列,需要支持多个队列
  2. 目录消费逻辑单一固定,需要支持动态指定逻辑,不同的队列对应不同的消费逻辑
  3. 消费者支持停止和重启
  4. ...

关于这些点,我们下篇不见不散

总结

  1. Connection、Channel、Consumer 之间的关系需要理清楚

    Connection 是 TCP 连接;Channel 是 Connection 中的双向数据流通道;Channel 可以绑定多个 Consumer,但推荐一个 Channel 只绑定一个 Consumer

    IO 多路复用
    是网络编程中常用的技术,建议大家掌握

  2. 基于 RabbitMQ Java Client 提供的 API,实现了消息消费、多消费者以及负载均衡

    没有 Spring,我们照样可以很优雅的消费 RabbitMQ 的消息

来源:晓飞的算法工程笔记 公众号,转载请注明出处

论文: Parameter Competition Balancing for Model Merging

创新点


  • 重新审视了现有的模型合并方法,强调参数竞争意识的关键作用。
  • 提出了一种名为
    \({\tt Pcb-Merging}\)
    的新方法,通过平衡参数竞争有效地调整参数系数。
  • 提出的方法在各种应用场景中稳定并提升了模型合并性能,无需额外训练。

内容概述


尽管对预训练模型进行微调已成为一种常见做法,但这些模型在其特定领域之外往往表现不佳。最近模型融合技术使得能够将多个经过不同任务微调的模型直接集成到一个模型中,集成模型具备多任务能力而无需在原始数据集上进行重新训练。然而,现有方法在解决任务之间潜在冲突和复杂相关性方面存在不足,特别是在参数级别调整中,造成在不同任务间有效平衡参数竞争的挑战。

论文提出了轻量级且无需训练的创新模型融合技术
\({\tt Pcb-Merging}\)

Parameter Competition Balancing
),通过调整每个参数的系数实现有效的模型融合。
\({\tt Pcb-Merging}\)
采用内部平衡来评估各个任务中参数的重要性,并采用外部平衡来评估不同任务间的参数相似性。重要性分数较低的参数被舍弃,其余参数被重新缩放,以形成最终的融合模型。

论文在多种融合场景中评估了该方法,包括跨任务、跨领域和跨训练配置,以及领域外泛化。实验结果表明,该方法在多个模态、领域、模型大小、任务数量、微调形式以及大型语言模型中实现了显著的性能提升,超越了现有的模型融合方法。

PCB-Merging


最近的模型合并研究基于任务向量完成各种任务算术操作和模型合并。对于任务
\(T_i\)
,任务向量
\(\tau_{i} \in \mathbb{R}^\textrm{d}\)
定义为通过从微调权重
\(\theta_\textrm{i}\)
中减去预训练权重
\(\theta_\textrm{pre}\)
所得到的向量,即
\(\tau_{i} = \theta_\textrm{i} - \theta_\textrm{pre}\)
,用于专注每个任务特定模型微调阶段发生的变化。基于任务向量的多任务模型合并方法可以表达为
\(\theta_m = \theta_\textrm{pre} + \lambda * \sum_{i=1}^{n}\tau_i\)
,其中系数
\(\lambda\)
表示合并任务向量
\(\tau_m\)
的重要性。这个概念简单而有效,显著优于简单的权重平均方案,即
\(\theta_m = (1/N)\sum_{i=1}^{n}\theta_i\)

平衡参数竞争

PCB-Merging
旨在调节每个任务和参数的缩放因子,实现任务内部和平衡任务之间的相互平衡。具体而言,使用参数竞争平衡(
PCB
)矩阵
\(\beta_i \in \mathbb{R}^{d}\)
来调整每个任务模型
\(\theta_i \in \mathbb{R}^{d}\)
中参数的规模,从而得到最终的融合模型,具体如下:

  • Intra-Balancing

首先,通过对任务向量的幅度应用非线性激活函数(即
softmax
)来实现
self-awareness
,强调重要参数,同时在一定程度上抑制冗余参数。

随着融合任务数量的增加,参数之间的竞争加剧。因此,使用任务数量
\(N\)
来调节冗余参数的抑制程度。

\[\begin{equation}
\beta_{intra, i} = \text{Softmax}(N*\text{Norm}({\tau}_i \odot{\tau}_i))
\end{equation}
\]

  • Inter-Balancing

接下来,使用
cross-awareness
来使一组任务内的参数能够与其他参数互动,从而解决任务之间潜在的冲突和复杂的相关性。

为了实现这一目标,计算不同任务向量中相同位置参数之间的相似度,使得每个参数能够基于来自其他任务的信息更新其分数。计算过程如下:

\[\begin{equation}
\beta_{inter, i} = \sum\nolimits_{j=1}^{n} \text{Softmax}(\text{Norm}({\tau}_i \odot{\tau}_j))
\end{equation}
\]

  • Drop and Rescale

得到
\(\beta_{i} = \beta_{intra, i} \odot \beta_{inter, i}\)
后,基于
\(\beta_i\)
构建一个掩码
\(m_i \in \mathbb{R}^{d}\)
以关注更重要的参数。具体而言,这个掩码
\(m_i\)
用于从
\(\beta_i\)

\(D\)
个元素中选择高分数元素。

定义掩码比例为
\(r\)
,其中
\(0 < r \leq 1\)
。掩码
\(m_i\)
可以通过以下公式推导得出:

\[\begin{equation}
m_{i, d} = \begin{cases}
1,& \text{if } \beta_{i, d} \geq\text{sorted}(\beta_i)[(1-r) \times D] \\
0,& \text{otherwise}
\end{cases}
\end{equation}
\]

重要性分数定义为
\(\hat{\beta} = m_i \odot \beta_i\)
,使用掩码平衡矩阵的分数来加权每个任务向量中每个参数的重要性,得到最终合并的任务向量
\(\tau_m\)

\[\begin{equation}
\tau_m = \sum\nolimits_{i=1}^{n}(\hat{\beta}_i \odot{\tau}_i) / \sum\nolimits_{i=1}^{n}\hat{\beta}_i
\end{equation}
\]

最终合并的任务向量
\(\tau_m\)
可以进一步按比例调整其幅度,并将其与初始参数值结合以生成融合后的模型参数
\(\theta_m\)
,表示为
\(\theta_m = \theta_\textrm{pre} + \lambda * \tau_m\)
,其中
\(\lambda\)
是一个缩放超参数。

系数搜索

先前的研究表明,基于任务向量的模型合并方法对合并系数
\(\lambda\)
非常敏感。即便选取了合适的统一
\(\lambda\)
,要进一步提高融合性能仍然需要对每个任务向量进行合并系数的网格搜索。这个过程复杂且繁琐,特别是在处理大量任务时。

论文采用智能优化算法来搜索混合系数,旨在比使用统一系数获得更大的改进。这一优化过程旨在寻找最佳的集合
\(\{\lambda_1, \cdots, \lambda_n\}\)
,以增强验证准确性,最终目标是最大化合并模型的验证准确性。

\[\begin{equation}
\theta_m = \theta_\textrm{pre} + \sum\nolimits_{i=1}^{n}(\hat{\beta}_i \odot \lambda_i {\tau}_i) / \sum\nolimits_{i=1}^{n}\hat{\beta}_i
\end{equation}
\]

在大多数实验设置中,主要使用协方差矩阵自适应进化策略(
CMA-ES
)。作为一种基于概率的种群优化算法,
CMA-ES
动态调整由协方差矩阵定义的搜索分布。它在每次迭代中系统地更新该分布的均值和协方差,以学习并利用搜索空间的潜在结构,从而提高优化效率。

主要结果




如果本文对你有帮助,麻烦点个赞或在看呗~
更多内容请关注 微信公众号【晓飞的算法工程笔记】

work-life balance.

粘贴控件是一种特殊的系统安全控件,它允许应用在用户的授权下无提示地读取剪贴板数据。

在应用集成粘贴控件后,用户点击该控件,应用读取剪贴板数据时不会弹窗提示。可以用于任何应用需要读取剪贴板的场景,避免弹窗提示对用户造成干扰。

例如,用户在应用外(如短信)复制了验证码,要在应用内粘贴验证码。用户原来在进入应用后,还需要长按输入框、在弹出的选项中点击粘贴,才能完成输入。而使用粘贴控件,用户只需进入应用后直接点击粘贴按钮,即可一步到位。

粘贴控件效果如图所示。
img

约束与限制

  • 临时授权会持续到灭屏、应用切后台、应用退出情况发生。
  • 应用在授权期间没有调用次数限制。
  • 为了保障用户的隐私不被恶意应用获取,应用需确保安全控件是可见的且用户能够识别的。开发者需要合理的配置控件的尺寸、颜色等属性,避免视觉混淆的情况,如果发生因控件的样式不合法导致授权失败的情况,请检查设备错误日志。

开发步骤

以简化用户填写验证码为例,参考以下步骤,实现效果:点击控件获取临时授权,粘贴内容到文本框,效果图请见上文。

  1. 导入剪贴板依赖。
import { pasteboard } from '@kit.BasicServicesKit';
  1. 添加输入框和粘贴控件。

    粘贴控件是由图标、文本、背景组成的类似Button的按钮,其中图标、文本两者至少有其一,背景必选。图标和文本不支持自定义,仅支持在已有的选项中选择。

    应用申明安全控件的接口时,分为传参和不传参两种,不传参默认创建图标+文字+背景的按钮,传参根据传入的参数创建,不包含没有配置的元素。

    当前示例使用默认参数。具体请参见PasteButton控件。此外,所有安全控件都继承安全控件通用属性,可用于定制样式。

import { pasteboard, BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct Index {
  @State message: string = '';

  build() {
    Row() {
      Column({ space: 10 }) {
        TextInput({ placeholder: '请输入验证码', text: this.message })
        PasteButton()
          .padding({top: 12, bottom: 12, left: 24, right: 24})
          .onClick((event: ClickEvent, result: PasteButtonOnClickResult) => {
            if (PasteButtonOnClickResult.SUCCESS === result) {
              pasteboard.getSystemPasteboard().getData((err: BusinessError, pasteData: pasteboard.PasteData) => {
                if (err) {
                  console.error(`Failed to get paste data. Code is ${err.code}, message is ${err.message}`);
                  return;
                }
                // 剪贴板内容为 '123456'
                this.message = pasteData.getPrimaryText();
              });
            }
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}