3b1b自注意力机制讲解记录

本文是对视频【官方双语】直观解释注意力机制,Transformer的核心 | 【深度学习第6章】的整理

1 背景

要解决的问题:接收一段文本,预测下一个词
在这里插入图片描述
句子会被切成许多小块,这些小块称为token。token可以是单词也可以是词组。为了简单起见,本文假设所有token是单词。
在这里插入图片描述
第一步就是把每个token映射到一个嵌入向量(embedding)
在这里插入图片描述
需要了解的是,在这个高维空间中,每个可能的嵌入向量的方向代表了语义。

在这里插入图片描述
Transformer的目的是为了不断调整这些嵌入向量,使得他不仅仅对这个单独的单词进行编码,同时还要融入更多的上下文语义。
在这里插入图片描述

2 self-attention(单头)

2.1 Q和K,得到注意力模式(attention pattern)

同一个单词在不同的语境中有不同的含义
在这里插入图片描述
在第一步切分文本并把每个token映射到嵌入向量后,三个“mole”对应的是同一个向量。初始的token嵌入没有考虑任何上下文信息。
在这里插入图片描述
想象一下,在嵌入空间有多个方向,编码了“mole”这个单词的不同含义。

在这里插入图片描述
训练好的注意力模块可以计算出需要给初始的泛型嵌入(generic embedding)加上什么向量,才能把它移动到考虑了上下文的对应的方向上
在这里插入图片描述
注意力模块不仅精细化了一个单词的含义,还允许模型相互传递这些嵌入向量所蕴含的信息。

在这里插入图片描述
在这里插入图片描述
甚至可以传递的很远,而且新的嵌入向量的信息比单个词丰富的多。
在这里插入图片描述
所有向量流经网络之后,经历了很多层注意力模块。此时预测下一个token的计算过程,完全基于序列中最后一个向量
在这里插入图片描述
举例来说,输入了一整篇文章,最后是“the murderer was”.如果模型要准确预测下一个词,该序列的最后一个向量,也就是“was”对应的向量,必须经过所有注意力模块的更新,已包含远超单个词的信息量。也就是设法编码整个上下文窗口中与预测下一个词相关的所有信息。

在这里插入图片描述
在这里插入图片描述
举例如下:假设有下面这段话,重点关注形容词对名词的改变。
在这里插入图片描述
每个词的初始嵌入是一个高维向量。只编码了这个词的泛型含义,没有考虑上下文。同时也编码了位置信息(位置编码有很多种),告诉模型单词在序列中的位置。
在这里插入图片描述
在这里插入图片描述
E ⃗ \vec{E} E 来表示嵌入向量,最后的目标就是通过计算,产生一组新的,更为精准的嵌入向量(考虑了上下文)。

在这里插入图片描述
这个过程的第一步可以想象成“creature”这个单词在问:“我的前面有形容词吗?”
在这里插入图片描述
然后fluffy和blue回答:“是的,我们在这里”。
在这里插入图片描述
这样的提问被编码成了另外一个向量,称之为查询(Query)
在这里插入图片描述
查询向量的维度比嵌入向量的维度要小得多。要计算查询向量,先取一个矩阵 W Q W_Q WQ,乘以嵌入向量

在这里插入图片描述

查询向量 Q 4 → = W Q E 4 → \overrightarrow{Q_4}=W_Q \overrightarrow{E_4} Q4 =WQE4
在这里插入图片描述
把查询矩阵与所有嵌入向量相乘,得到对应的查询向量。 W Q W_Q WQ是从数据中学习得到的。
在这里插入图片描述
还需要第二个矩阵,键矩阵 W K W_K WK,也会与每个嵌入向量相乘,产生键向量序列

在这里插入图片描述
W Q W_Q WQ W K W_K WK都把嵌入向量映射到了低维度空间。在低维度空间,查询向量与键向量方向对齐的时候,认为匹配。对于本例,键矩阵会把fluffy和blue映射到与creature的查询向量高度一致的方向上。

在这里插入图片描述
为了衡量每个键向量与每个查询向量的匹配程度,计算所有可能的键向量与查询向量之间的内积。内积越大,键向量与查询向量越对齐(方向一致),
在这里插入图片描述
上面形容词的例子就像这样。fluffy和blue的键向量与creature的查询向量高度对齐。fluffy和blue注意到了creature的嵌入.其他的词的键向量与creature的查询向量内积很小,说明他们与他的关联不大。
在这里插入图片描述
内积可以是负无穷到正无穷的任何数字。这个内积得分可以看作是每个词与更新其他词的含义有多相关
在这里插入图片描述
这些得分最后要当作权重使用,所以要映射到(0,1)之间,而且每列总和为1,就像概率分布。
在这里插入图片描述

softmax可以达到这个目的
在这里插入图片描述
利用softmax进行归一化后,每一列可以看作权重,表示左侧的键向量与顶部的查询向量的相关程度。

在这里插入图片描述
我们称这个网格为attention pattern

在这里插入图片描述
论文中把attention写的很简洁, Q Q Q是查询向量, K K K是键向量 V V V是值向量。如下:
在这里插入图片描述
Q K T QK^T QKT表达了键向量与查询向量的内积得到的表格,为了数值稳定性,除以键-查询空间的维度的平方根。然后整个表达式的最外层的softmax是逐列进行的。
在这里插入图片描述

在这里插入图片描述

2.2 掩码

[to DO]

2.3 V,更新语义

算出这个模式,就可以让模型推断出每个词与其他哪些词(在精细化语义上)有关,
在这里插入图片描述
然后要做的就是去更新嵌入向量,把各个词的信息传递给与之相关的其他词。
例如要让fluffy这个词的嵌入向量改变creature这个词的嵌入向量,使其移动到这个12000多维的词嵌入空间的另一个方向。从而更具体的表示“fluffy creature”
在这里插入图片描述
这会用到第三个矩阵,值矩阵(value matrix) W V W_V WV,将 W V W_V WV乘以第一个词“fluffy”的嵌入,得到的就是值向量(value vector),这也就是加到后面的“creature”的嵌入向量的向量,所以这个值向量处于与嵌入向量相同的高维空间。
在这里插入图片描述
这个过程可以理解为“如果这个词需要调整目标词的含义(这个由attention pattern决定),那么,要使得目标词能反映出语义的变化,需要加上什么向量呢?”
在这里插入图片描述
每个嵌入向量乘以 W V W_V WV,得到对应的值向量。拿creature这个词举例,他所对应的attention权重乘以对应的值向量,得到想要引入的变化量,记为 Δ E ⃗ \Delta \vec{E} ΔE ,然后把他加到原始嵌入向量上。预期是得到一个考虑了上下文的更精准的向量。

在这里插入图片描述
当然,需要对所有的词都要做这个操作,得到每个token考虑上下文后更精准的编码。这就是单头注意力机制。
在这里插入图片描述
整个过程由三个可学习的矩阵实现: W Q W_Q WQ W K W_K WK W V W_V WV
在这里插入图片描述
算一下GPT3的总参数量:键矩阵和查询矩阵都有12288个列,对应了嵌入向量空间维度。128行,对应了键-查询空间维度。这样就各有1572864个参数
在这里插入图片描述
按照之前说的,值矩阵行和列都是12288维,这就是1.5亿 的参数。可以看到值矩阵的参数数量比查询矩阵和键矩阵多好几个数量级。

在这里插入图片描述
有没有更好的处理方式呢?将值矩阵分解成两个矩阵的乘积。
在这里插入图片描述
解释1:第一个矩阵把较大的嵌入向量映射到低维度的空间,称为“value down matrix”,另一个矩阵把向量从低维空间映射回高维嵌入空间,称为“value up matrix”
解释2:线性代数术语:对大矩阵的“低秩分解”(low rank transformation)
在这里插入图片描述
这样四个矩阵加起来一共约630万个参数。
在这里插入图片描述

3 交叉注意力:cross-attention

GPT只需要自注意力机制,不需要交叉注意力机制。交叉注意力机制涉及的模型会处理两种不同类型的数据。比如机器翻译和音频转文字。
在这里插入图片描述
交叉注意力和自注意力几乎相同,唯一的区别是:键和查询矩阵作用于不同的数据集。例如机器翻译中,键来自一种语言,而查询来自另一种语言。
在这里插入图片描述
而这个注意力模式就可以描述,一种语言中的哪些词对应另一种语言的哪些词。这种情况下通常不会用到掩码。因为不存在后面的token影响前面的token 的问题。
在这里插入图片描述

4 多头注意力机制

上面的例子里讲的是形容词影响名词的语义,实际上,上下文中影响词义的方式有多种。每一种不同的上下文更新方式,键矩阵和查询矩阵都会有所变化,以捕捉不同的注意力模式。值矩阵也会不同(要加到嵌入向量上的向量不同)

transformer完整的注意力模块由多头注意力组成,每个头都有不同的键、值、查询矩阵。GPT3每个注意力模块有96个注意力头。

在这里插入图片描述GPT3每个注意力模块有96个注意力头。每个头都有不同的键、值、查询矩阵,产生96个不同的注意力模式。每个注意力头都有不同的值矩阵,产生96个值向量。与对应注意力模式分别加权求和。每个token都会产生96个要加到这个token嵌入向量上的变化量。而你要做的就是把各个头给出的变化量加起来。然后加上初始的嵌入向量
在这里插入图片描述
这个总和就是多头注意力模块输出的一列。得到更精准的向量。
在这里插入图片描述
这样一共有6亿个参数。
在这里插入图片描述

4.1 额外说明

在论文和实际的实践中,多头注意力机制中,value up矩阵会合到一起,称为output矩阵,与整个多头注意力模块相关联,
在这里插入图片描述
而值矩阵就指的是value down矩阵。以下还有其他额外的技巧
在这里插入图片描述
多头注意力之后,还要经过MLP模块。然后会多次重复这两个模块。使得所提取的概念从低级走向高级。比如情感,语气,诗意。
在这里插入图片描述
在这里插入图片描述
96个层,这样GPT3的参数量来到了580亿。很多了,但是只占总参数量1750亿的三分之一。其他参数在MLP。
在这里插入图片描述

<think>好的,我现在需要详细讲解Flux和Mono,这两个都是Project Reactor中的核心类,用于响应式编程。首先,我应该回顾一下响应式编程的基本概念,这样用户能更好地理解Flux和Mono的上下文。 用户可能已经知道,响应式编程是基于数据流和变化传播的编程范式。Project Reactor是Reactive Streams规范的实现,而Flux和Mono就是其中的发布者(Publisher)。接下来,我需要分别讲解Flux和Mono的区别,它们的用途以及常用操作。 首先,Flux代表的是0到N个元素的异步序列,而Mono代表的是0或1个元素的异步结果。这时候要举例子,比如Flux可以用于处理多个数据项,比如从数据库查询多条记录,或者处理一个事件流;而Mono通常用于返回单个结果,比如根据ID查询一条记录,或者执行一个返回void的操作。 然后需要详细说明两者的创建方式。例如,Flux可以通过just、fromIterable、range等方法创建;Mono则可以通过just、empty、error等。同时,可以提到一些工厂方法,比如Flux.interval用于生成周期性序列。 接下来是操作符部分。Flux和Mono都支持丰富的操作符,比如map、filter、flatMap等。这里需要强调两者的不同之处,比如当使用flatMap时,Flux的flatMap会将每个元素转换为一个Publisher,然后合并这些Publisher,而Mono的flatMap则是将单个元素转换为另一个Mono。另外,要注意到Mono的一些特有操作,比如defaultIfEmpty、switchIfEmpty等。 错误处理也是一个重要的部分。需要讲解onErrorReturn、onErrorResume、retry等操作符,说明它们在Flux和Mono中的应用。比如,当处理Mono时,如果出现错误,可以使用onErrorReturn来提供一个默认值。 背压(Backpressure)的概念也需要提及,因为Reactor支持背压,而Flux作为发布者能够处理下游的请求压力。可以举例说明如何使用onBackpressureBuffer等操作符来处理背压。 另外,需要讨论两者的订阅机制。Flux和Mono都是惰性的,只有在订阅时才会开始发射数据。要说明subscribe方法的几种重载形式,以及如何通过订阅触发数据流。 还需要比较两者的转换,比如如何将Mono转换为Flux,或者将Flux转换为Mono。例如,Flux.take(1)可以得到一个Mono,而Mono.flux()可以将Mono转为Flux。同时,像Flux.collectList()可以将Flux转为Mono<List<T>>。 在应用场景上,可以举一些实际的例子。比如,在WebFlux中,控制器方法可以返回Mono或Flux来处理异步请求。当处理一个HTTP请求时,如果返回单个对象,使用Mono;如果是流式响应,比如SSE(Server-Sent Events),则使用Flux。 另外,测试方面可以提到使用StepVerifier来验证Flux和Mono的行为,比如验证元素的数量、顺序以及是否完成等。 可能还需要提到一些高级特性,比如调度器(Schedulers)的使用,切换执行上下文,比如使用publishOn和subscribeOn来控制线程池。这对于理解如何避免阻塞操作很重要。 最后,总结两者的区别和适用场景,帮助用户根据需求选择使用Flux还是Mono。需要强调,正确使用这两个类型可以提升应用的响应性和资源利用率,特别是在高并发场景下。 在整理这些内容时,需要确保结构清晰,分点讲解,同时结合实际代码示例(用LaTeX格式),让用户更容易理解。同时要注意用户可能对某些概念不熟悉,比如背压或操作符,需要用简单的语言解释清楚。</think>Flux和Mono是Project Reactor中实现响应式编程的核心类,遵循Reactive Streams规范。以下是详细讲解: --- ### 一、核心区别 | 特性 | Flux | Mono | |----------------|-------------------------------|--------------------------------| | **数据量** | 0-N个元素的异步序列 | 0或1个元素的异步结果 | | **典型场景** | 流式数据(如数据库查询多条) | 单结果操作(如根据ID查询单条) | | **完成信号** | 发送`onComplete`结束 | 发送`onComplete`立即结束 | --- ### 二、创建方式 #### 1. Flux创建 ```java // 直接创建 Flux<String> flux1 = Flux.just("A", "B", "C"); Flux<Integer> flux2 = Flux.range(1, 5); // 从集合创建 List<String> list = Arrays.asList("X", "Y"); Flux<String> flux3 = Flux.fromIterable(list); // 生成周期性序列(每秒发射一个递增数) Flux<Long> flux4 = Flux.interval(Duration.ofSeconds(1)); ``` #### 2. Mono创建 ```java // 直接创建 Mono<String> mono1 = Mono.just("Hello"); Mono<Object> mono2 = Mono.empty(); // 空序列 // 延迟创建(订阅时生成数据) Mono<String> mono3 = Mono.fromSupplier(() -> "Data"); // 错误处理 Mono<Object> mono4 = Mono.error(new RuntimeException("Error")); ``` --- ### 三、常用操作符 #### 1. 通用操作符 - **转换**:`map`, `flatMap`, `flatMapMany`(Mono转Flux) ```java Flux<Integer> squared = Flux.just(1,2,3).map(n -> n * n); ``` - **过滤**:`filter`, `take`, `skip` ```java Flux<Integer> even = Flux.range(1,10).filter(n -> n % 2 == 0); ``` - **错误处理**: ```java Flux<Integer> safe = flux.onErrorResume(e -> Flux.just(-1)); Mono<String> fallback = mono.onErrorReturn("default"); ``` #### 2. Flux特有 - **缓冲**:`buffer(3)` 将元素分组为列表 - **合并**:`mergeWith`, `zipWith` 合并多个Flux ```java Flux<Integer> merged = flux1.mergeWith(flux2); ``` #### 3. Mono特有 - **默认值**:`defaultIfEmpty("unknown")` - **条件转换**:`switchIfEmpty(Mono.just("backup"))` --- ### 四、背压处理 Flux支持背压策略,通过操作符控制下游压力: ```java flux.onBackpressureBuffer(10) // 缓冲10个元素 .onBackpressureDrop() // 丢弃超限元素 .onBackpressureLatest() // 保留最新元素 ``` --- ### 五、订阅与触发 数据流是惰性的,需通过订阅触发: ```java // 基础订阅 flux.subscribe( data -> System.out.println(data), // onNext err -> err.printStackTrace(), // onError () -> System.out.println("Done") // onComplete ); // 带背压控制的订阅 flux.subscribe(new BaseSubscriber<>() { @Override protected void hookOnSubscribe(Subscription s) { s.request(3); // 每次请求3个元素 } }); ``` --- ### 六、转换与互操作 #### 1. Flux ⇄ Mono ```java // Flux转Mono Mono<List<Integer>> monoList = flux.collectList(); Mono<Integer> firstElement = flux.next(); // Mono转Flux Flux<String> fluxFromMono = mono.flatMapMany(s -> Flux.just(s, s)); ``` #### 2. 与其他库交互 - **Spring WebFlux**:控制器可直接返回`Mono<ResponseEntity>`或`Flux<Data>` - **Reactive MongoDB**:`ReactiveMongoTemplate`返回Flux查询结果 --- ### 七、调度与线程 通过调度器控制执行线程: ```java flux.publishOn(Schedulers.parallel()) // 后续操作在并行线程 .subscribeOn(Schedulers.immediate()) // 订阅触发线程 .subscribe(); ``` --- ### 八、典型应用场景 | 类型 | 场景示例 | |-------|-----------------------------------------| | Flux | 实时股票报价推送、日志流处理 | | Mono | HTTP请求响应、单条数据库记录保存/查询 | --- ### 九、测试验证 使用`StepVerifier`进行流测试: ```java StepVerifier.create(flux) .expectNext("A", "B") .expectError(RuntimeException.class) .verify(); ``` --- 通过合理选择Flux和Mono,开发者可以高效构建非阻塞、高并发的响应式系统。两者的互补性使其适用于从数据流处理到异步IO操作的各种场景。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值