RNN并行化——《Were RNNs All We Needed?》论文解读
Info | |
---|---|
Paper | https://arxiv.org/abs/2410.01201 |
GitHub | https://github.com/lucidrains/minGRU-pytorch |
个人博客地址 | http://myhz0606.com/article/mini_rnn |
最近在看并行RNN相关的paper,发现很多都利用了Parallel Scanning算法。本文将从Parallel Scanning算法开始,介绍Bengio团队不久前发表的《Were RNNs All We Needed?》
1 Parallel Scanning算法介绍
首先来看定义。Parallel Scanning字面意思,就是对scan操作进行并行化,那么什么是scan
(扫描)操作呢?
1.1 Scan的定义
1.1.1 inclusive scan
scan
(inclusive scan)也称为all-prefix-sum,其定义如下:
若给定:
- 有序集合(order set) A = [ a 0 , a 1 , . . . , a n − 1 ] A=[ a _ { 0 } , a _ { 1 } , . . . , a _ { n - 1 } ] A=[a0,a1,...,an−1] ,
- 二元结合运算符(binary associative operation) ⊕ \oplus ⊕ ,并且 ⊕ \oplus ⊕ 的单位元 I \mathcal{I} I存在
输出一个order set,并满足
B = [ a 0 , ( a 0 ⊕ a 1 ) , . . . , ( a 0 ⊕ a 1 ⊕ . . . ⊕ a n − 1 ) ] B=[ a _ { 0 } , ( a _ { 0 } \oplus a _ { 1 } ) , . . . , ( a _ { 0 } \oplus a _ { 1 } \oplus . . . \oplus a _ { n - 1 } ) ] B=[a0,(a0⊕a1),...,(a0⊕a1⊕...⊕an−1)] .
将满足上述规则的操作称为scan
。
显然上式可以写成递归形式,时间复杂度为 O ( n ) \mathcal{O}(n) O(n)
B [ i ] = { A [ 0 ] i f i = 0 B [ i − 1 ] ⊕ A [ i ] i f 0 < i < n (1) B [ i ] = \left\{ \begin{array} { r c l } { A [ 0 ] } & { \mathrm { i f } } & { i = 0 } \\ { B [ i - 1 ] \oplus A [ i ] } & { \mathrm { i f } } & { 0 \lt i \lt n } \\ \end{array} \right. \tag{1} B[i]={A[0]B[i−1]⊕A[i]ififi=00<i<n(1)
注1:二元结合运算符作用于两个操作数返回一个结果,且运算满足结合率。常见的二元结合运算符包括加法( + + +)、乘法( ∗ * ∗)、逻辑与( & \& &)和逻辑或( ∣ | ∣)等. 注2: ⊕ \oplus ⊕ 的单位元 I \mathcal{I} I:若: a ⊕ I = a a \oplus \mathcal{I} = a a⊕I=a,则称 I \mathcal{I} I是运算 ⊕ \oplus ⊕ 的单位元。例如,加法的单位元是0,乘法的单位元是1,向量点乘的单位元是单位向量。
1.1.2 exclusive scan
实践中,scan
另一种变体prescan
(也叫exclusive scan)也经常用到,输入和scan
一致,输出为:
C = [ I , a 0 , ( a 0 ⊕ a 1 ) , . . . , ( a 0 ⊕ a 1 ⊕ . . . ⊕ a n − 2 ) ] . C =[ \mathcal{I}, a _ { 0 } , ( a _ { 0 } \oplus a _ { 1 } ) , . . . , ( a _ { 0 } \oplus a _ { 1 } \oplus . . . \oplus a _ { n - 2 } ) ] . C=[I,a0,(a0⊕a1),...,(a0⊕a1⊕...⊕an−2)].
其递归形式为
C [ i ] = { I i f i = 0 C [ i − 1 ] ⊕ A [ i − 1 ] i f 0 < i < n (2) C [ i ] = \left\{ \begin{array} { r c l } { \mathcal{I} } & { \mathrm { i f } } & { i = 0 } \\ { C [ i - 1 ] \oplus A [ i - 1 ] } & { \mathrm { i f } } & { 0 \lt i \lt n } \\ \end{array} \right. \tag{2} C[i]={IC[i−1]⊕A[i−1]ififi=00<i<n(2)
inclusive scan与exclusive scan可以很方便的转化,
inclusive scan → exclusive scan,只需将输出序列向右移一个单位,并且在序列第一个元素填充单位元。
exclusive scan → inclusive scan,只需将输出序列向左移一个单位,并且用最后一个输入元素加上最后一个输出元素的结果填充最后一个元素。
1.1.3 例子: prefix sum
已知输入有序集合 A = [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ] A= [0, 1, 2, 3, 4, 5, 6, 7] A=[0,1,2,3,4,5,6,7],二元结合运算符为加法 + + +,计算A在 + + +下的inclusive scan和exclusive scan
根据式1,易得inclusive scan的结果为: [ 0 , 1 , 3 , 6 , 10 , 15 , 21 , 28 ] [0, 1, 3, 6, 10, 15, 21, 28] [0,1,3,6,10,15,21,28]
根据式2,易得exclusive scan的结果为: [ 0 , 0 , 1 , 3 , 6 , 10 , 15 , 21 ] [0, 0, 1, 3, 6, 10, 15, 21] [0,0,1,3,6,10,15,21]
代码实现:
def inclusive_scan_recursive(ls: list) -> list:n = len(ls)if n <= 1:return lsoutput = [ls[0]] * nfor i in range(1, n):output[i] = ls[i] + output[i - 1]return output
1.2 Parallel Scanning
前文所述基于递归式计算scan的算法称之为sequential algorithm,其计算复杂度为 O ( n ) \mathcal{O}(n) O(n),并且无法并行化。那么如何并行化计算scan呢?
1.2.1 Kogge-Stone
Parallel Scanning algorithm[2]
Kogge-Stone
并行扫描算法的基本计算流程如下图所示(从最底部往上看)
总计分为 ⌊ log 2 ( N ) ⌋ \lfloor \log_2 (N) \rfloor ⌊log2(N)⌋个阶段,在每一个阶段并行计算 a [ i + 2 d ] = a [ i ] + a [ i + 2 d ] a[i+2^d] = a[i] + a[i+2^d] a[i+2d]=a[i]+a[i+2d] ( d d d表示阶段, 从0开始取)。该方法的加法运算次数为 ∑ d = 0 ⌊ log 2 ( N ) ⌋ − 1 ( N − 2 d ) \sum _{d=0}^ {\lfloor \log_2 (N) \rfloor - 1}(N - 2^d) ∑d=0⌊log2(N)⌋−1(N−2d)多于顺序算法的 N N N,不考虑并行的情况下时间复杂度为 O ( n log n ) \mathcal{O}(n\log n) O(nlogn)。但在processor足够时,Kogge-Stone
的时间复杂度为 O ( log n ) \mathcal{O}(\log n) O(logn)。
python代码实现如下:
注意由于python原生的多线程存在GIL,无法利用多核优势,故使用numpy实现
def inclusive_scan_kogge_stone(ls: list) -> list:n = len(ls)if n <= 1:return lsstages = math.floor(math.log2(n))ls_arr = np.array(ls)for i in range(stages):stride = 2 ** ils_arr[stride:] += ls_arr[:n-stride] # 并行计算return ls_arr.tolist()
1.2.2 Brent-Kung
Parallel Scanning algorithm[3]
从上文中,Kogge-Stone
算法虽然在并行的情况下将scan的时间复杂度从 O ( n ) \mathcal{O}(n) O(n)降到了 O ( log n ) \mathcal{O}(\log n) O(logn),但Kogge-Stone
算法实际的计算量是比顺序执行多不少的。下面来看计算效率更高的Brent-Kung
算法。
Kogge-Stone
算法分为两个阶段
stage1: 上行阶段,计算reduce (up sweep)
上行阶段有 log 2 ( N ) \log_2 (N) log2(N) 个阶段,每个阶段 ( d = 0 , 1 , . . . , log 2 ( N ) ) (d=0,1,...,\log_2(N)) (d=0,1,...,log2(N))执行
a [ i + 2 d + 1 − 1 ] = a [ i + 2 d − 1 ] + a [ i + 2 d + 1 − 1 ] a[i + 2^{d+1}-1] = a[i + 2^{d}-1] + a[i + 2^{d+1}-1] a[i+2d+1−1]=a[i+2d−1]+a[i+2d+1−1]
算法流程:
f o r d f r o m 0 t o ( l o g 2 N ) − 1 i n p a r a l l e l f o r i f r o m 0 t o N − 1 b y 2 d + 1 a [ i + 2 d + 1 − 1 ] ← a [ i + 2 d − 1 ] + a [ i + 2 d + 1 − 1 ] \begin{array} { l } { { \bf { f o r } } \; \; d \; \; { \bf { f r o m } } \; \; 0 \; \; { \bf { t o } } \; \; ( { \bf { log_{2} } } \; N ) - 1 } \\ { \quad { \bf { i n } } \; { \bf { p a r a l l e l } } \; \; { \bf { f o r } } \; \; i \; \; { \bf { f r o m } } \; \; 0 \; \; { \bf { t o } } \; \; N - 1 \; \; { \bf { b y } } \; \; 2 ^ { d + 1 } } \\ { \quad \quad a [ i + 2 ^ { d + 1 } - 1 ] \gets a [ i + 2 ^ { d } - 1 ] + a [ i + 2 ^ { d + 1 } - 1 ] } \\ \end{array} fordfrom0to(log2N)−1inparallelforifrom0toN−1by2d+1a[i+2d+1−1]←a[i+2d−1]+a[i+2d+1−1]
下面来分析一下up sweep的时间复杂度
up sweep的计算量为
∑ d = 0 log 2 N − 1 N 2 d + 1 = ∑ d = 0 log 2 N − 1 1 2 d N 2 = N 2 ∑ ∗ d = 0 log 2 N − 1 1 2 d ⏟ 等比数列求和 = N 2 1 − ( 1 2 ) log 2 N 1 − 1 2 = N ( 1 − ( 1 2 ) log 2 N ) = N ( 1 − 1 N ) = N − 1 (3) \begin{aligned} \sum _{d=0} ^ {\log_2{N} - 1} \frac{N}{2 ^ {d + 1}} & = \sum _{d=0} ^ {\log_2{N} - 1} \frac{1}{2 ^ {d}} \frac{N}{2} \\ & = \frac{N}{2} \underbrace{\sum *{d=0} ^ {\log_2{N} - 1} \frac{1}{2 ^ {d}}}_{等比数列求和} \\ & = \frac{N}{2} \frac{1 - (\frac{1}{2})^{\log_2{N}}}{1 - \frac{1}{2}} \\ & = N(1 - (\frac{1}{2})^{\log_2{N}}) \\ & = N(1 - \frac{1}{N}) \\ & = N - 1 \end{aligned} \tag{3} d=0∑log2N−12d+1N=d=0∑log2N−12d12N=2N等比数列求和 ∑∗d=0log2N−12d1=2N1−211−(21)log2N=N(1−(21)log2N)=N(1−N1)=N−1(3)
不做并行的时间复杂度为 O ( n ) \mathcal{O}(n) O(n),并行时的时间复杂度为 O ( log n ) \mathcal{O}(\log n) O(logn)
python代码如下:
此处为了便于理解,第二个循环没有用并行
def brent_kung_up_sweep(ls: list) -> list:n = len(ls)if n <= 1:return lsstages = math.floor(math.log2(n))assert 2 ** stages == nfor d in range(stages):# parallelfor i in range(0, n, 2 ** (d + 1)):ls[i + 2 ** (d + 1) - 1] = ls[i + 2 ** d - 1] + ls[i + 2 ** (d + 1) - 1]return ls
通过up sweep 我们可以得到reduce的结果,但无法得到完整的scan结果,需要继续进行down sweep。
stage2: 下行阶段(down sweep)
算法流程:
p r o c e d u r e d o w n − s w e e p ( A ) a [ n − 1 ] ← 0 f o r d f r o m ( log 2 N ) − 1 d o w n t o 0 i n p a r a l l e l f o r i f r o m 0 t o N − 1 b y 2 d + 1 t ← a [ i + 2 d − 1 ] a [ i + 2 d − 1 ] ← a [ i + 2 d + 1 − 1 ] \begin{array} { r l } & { \mathbf { p r o c e d u r e \ \, d o w n - s w e e p } ( \mathtt { A } ) } \\ & { \quad a [ n - 1 ] \leftarrow 0 } \\ & { \quad \mathbf { f o r } \ \ d \ \mathbf { f r o m } \ \left( \log_2 N \right) - 1 \ \mathbf { \ d o w n t o \ } 0 } \\ & { \quad \quad \mathbf { i n \ p a r a l l e l \ f o r } \ \ i \ \mathbf { f r o m } \ \ 0 \ \mathbf { \ t o } \ \ N - 1 \ \mathbf { \ b y } \ \ 2 ^ { d + 1 } } \\ & { \quad \quad \quad t \gets a [ i + 2 ^ { d } - 1 ] } \\ & { \quad \quad \quad a [ i + 2 ^ { d } - 1 ] \gets a [ i + 2 ^ { d + 1 } - 1 ] } & { \quad \quad } \end{array} procedure down−sweep(A)a[n−1]←0for d from (log2N)−1 downto 0in parallel for i from 0 to N−1 by 2d+1t←a[i+2d−1]a[i+2d−1]←a[i+2d+1−1]
计算复杂度与up-sweep一致
python代码如下:
def brent_kung_down_sweep(ls: list) -> list:n = len(ls)if n <= 0:return lsls[-1] = 0stages = math.floor(math.log2(n))assert 2 ** stages == nfor d in range(stages - 1, -1, -1):# parallelfor i in range(0, n, 2 ** (d + 1)):ls[i + 2 ** d - 1], ls[i + 2 ** (d + 1) - 1] = ls[i + 2 ** (d + 1) - 1], ls[i + 2 ** d - 1] + ls[i + 2 ** (d + 1) - 1]return ls
综上所述,我们详细介绍了Kogge-Stone
算法,它分为up sweep和down sweep两个阶段,每个阶段的计算量为 N − 1 N-1 N−1,不做并行的计算时间复杂度为: O ( n ) \mathcal{O}(n) O(n),并行时的计算复杂度为 O ( log n ) \mathcal{O}(\log{n}) O(logn)
def inclusive_scan_brent_kung(ls):n = len(ls)if n <= 1:return lsarr = np.array(ls)logn = int(math.log2(n))# 确保输入长度是2的幂if n & (n - 1) != 0:raise ValueError("Input length must be a power of 2")# Up-sweep阶段for d in range(logn):i = np.array(list(range(0, n, 2 ** (d + 1))))arr[i + 2 ** (d + 1) - 1] = arr[i + 2 ** d - 1] + arr[i + 2 ** (d + 1) - 1]# Down-sweep阶段final_item = arr[-1]arr[-1] = 0for d in range(logn - 1, -1, -1):i = np.array(list(range(0, n, 2 ** (d + 1))))# numpy based parallelarr[i + 2 ** d - 1], arr[i + 2 ** (d + 1) - 1] = arr[i + 2 ** (d + 1) - 1], arr[i + 2 ** d - 1] + arr[i + 2 ** (d + 1) - 1]ls = arr.tolist()inclusive_scan_res = ls[1:] + [final_item]return inclusive_scan_res
❓小练习
不妨尝试回答一下几个问题:
- 当输入序列的长度并不是2的N次幂,如何用
Brent-Kung
算法进行并行? - 如果系统的processor有限,此时的时间复杂度是多少?
2 并行RNN
通过上文的介绍我们可以用并行的方法计算递归式 b ( t ) = b ( t − 1 ) ⊕ a ( t ) , g i v e n b ( 0 ) , a ( t ) b(t) = b(t-1) \oplus a(t), \mathrm{given} \, b(0), a(t) b(t)=b(t−1)⊕a(t),givenb(0),a(t)。那如何将其与RNN建立起联系呢?
先来回顾一下两个经典的RNN算法,1)LSTM, 2)GRU
2.1 经典RNN回顾
2.1.1 LSTM
LSTM引入记忆细胞C(t)来存储长期信息,解决传统RNN无法处理长程依赖的问题。并引入3个门(遗忘门、输入门、输出门)来控制新老信息的交互。
下面来详细看其计算流程:
给定
- 输入序列: X = [ X ( 0 ) , X ( 1 ) , ⋯ , X ( t ) , ⋯ , X ( T ) ] , X ∈ R T × d , X ( t ) × R d X=[X(0), X(1), \cdots, X(t), \cdots, X(T)], X \in \mathbb{R} ^{T \times d}, X(t) \times \mathbb{R}^{d} X=[X(0),X(1),⋯,X(t),⋯,X(T)],X∈RT×d,X(t)×Rd
- 初始化隐藏状态 H ( 0 ) ∈ R d H(0) \in \mathbb{R}^{d} H(0)∈Rd
- 初始化记忆细胞 C ( 0 ) ∈ R d C(0) \in \mathbb{R}^{d} C(0)∈Rd
Forget Gate: F ( t ) = S i g m o i d ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) Input Gate: I ( t ) = S i g m o i d ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) Output Gate: O ( t ) = S i g m o i d ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) Candidate Cell: C ~ ( t ) = tanh ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) ⇒ U p d a t e Memory Cell: C ( t ) = C ( t − 1 ) ⊙ F ( t ) + I ( t ) ⊙ C ~ ( t ) Hidden State: H ( t ) = tanh ( C ( t ) ) ⊙ O ( t ) (4) \begin{aligned} &\textbf{Forget Gate:} &F(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ &\textbf{Input Gate:} &I(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ &\textbf{Output Gate:} &O(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ &\textbf{Candidate Cell:} &\widetilde{C}(t) &= \mathrm{\tanh}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ \stackrel{\mathrm{Update}}{\Rightarrow } \\ & \textbf{Memory Cell:} &C(t) & =C(t-1) \odot F(t) + I(t) \odot \widetilde{C}(t) \\ & \textbf{Hidden State:} &H(t) & = \tanh(C(t)) \odot O(t) \end{aligned} \tag{4} ⇒UpdateForget Gate:Input Gate:Output Gate:Candidate Cell:Memory Cell:Hidden State:F(t)I(t)O(t)C (t)C(t)H(t)=Sigmoid(Linear(Cat([H(t−1),X(t)])))=Sigmoid(Linear(Cat([H(t−1),X(t)])))=Sigmoid(Linear(Cat([H(t−1),X(t)])))=tanh(Linear(Cat([H(t−1),X(t)])))=C(t−1)⊙F(t)+I(t)⊙C (t)=tanh(C(t))⊙O(t)(4)
三个门的输出在0~1之间,通过点乘来控制信息的流入量。
2.1.2 GRU
GRU简化了LSTM的门控机制达到和LSTM类似的效果。GRU主要通过两个门(重置门、更新门)来控制信息的交互。
下面来详细看其计算流程:
给定
- 输入序列: X = [ X ( 0 ) , X ( 1 ) , ⋯ , X ( t ) , ⋯ , X ( T ) ] , X ∈ R T × d , X ( t ) × R d X=[X(0), X(1), \cdots, X(t), \cdots, X(T)], X \in \mathbb{R} ^{T \times d}, X(t) \times \mathbb{R}^{d} X=[X(0),X(1),⋯,X(t),⋯,X(T)],X∈RT×d,X(t)×Rd
- 初始化隐藏状态 H ( 0 ) ∈ R d H(0) \in \mathbb{R}^{d} H(0)∈Rd
Reset Gate: R ( t ) = S i g m o i d ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) Update Gate: Z ( t ) = S i g m o i d ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) Candidate Hidden State: H ~ ( t ) = tanh ( L i n e a r ( C a t ( [ H ( t − 1 ) ⊙ R ( t ) , X ( t ) ] ) ) ) ⇒ U p d a t e Hidden State: H ( t ) = H ( t − 1 ) ⊙ ( 1 − Z ( t ) ) + H ~ ( t ) ⊙ Z ( t ) (5) \begin{aligned} &\textbf{Reset Gate:} &R(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ &\textbf{Update Gate:} &Z(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ &\textbf{Candidate Hidden State:} &\widetilde {H}(t) &= \mathrm{\tanh} \bigl(\mathrm{Linear}(\mathrm{Cat}([H(t-1) \odot R(t), X(t)] ))\bigr) \\ \stackrel{\mathrm{Update}}{\Rightarrow } \\ & \textbf{Hidden State:} &H(t) & = H(t-1) \odot (1 - Z(t)) + \widetilde {H}(t) \odot Z(t) \end{aligned} \tag{5} ⇒UpdateReset Gate:Update Gate:Candidate Hidden State:Hidden State:R(t)Z(t)H (t)H(t)=Sigmoid(Linear(Cat([H(t−1),X(t)])))=Sigmoid(Linear(Cat([H(t−1),X(t)])))=tanh(Linear(Cat([H(t−1)⊙R(t),X(t)])))=H(t−1)⊙(1−Z(t))+H (t)⊙Z(t)(5)
2.2 经典RNN并行化
2.2.1 理论基础
通过前文介绍,我们回顾了经典RNN的递归更新公式,但显然,无法直接沿用parallel scan算法进行并行
递归更新公式 | |
---|---|
LSTM | Memory Cell: C ( t ) = C ( t − 1 ) ⊙ F ( t ) + I ( t ) ⊙ C ~ ( t ) Hidden State: H ( t ) = tanh ( C ( t ) ) ⊙ O ( t ) \begin{aligned}& \textbf{Memory Cell:} &C(t) & =C(t-1) \odot F(t) + I(t) \odot \widetilde{C}(t) \\ & \textbf{Hidden State:} &H(t) & = \tanh(C(t)) \odot O(t) \\ \end{aligned} Memory Cell:Hidden State:C(t)H(t)=C(t−1)⊙F(t)+I(t)⊙C (t)=tanh(C(t))⊙O(t) |
GRU | Hidden State: H ( t ) = H ( t − 1 ) ⊙ ( 1 − Z ( t ) ) + H ~ ( t ) ⊙ Z ( t ) \begin{aligned} \textbf{Hidden State:} \quad \quad H(t) & = H(t-1) \odot (1 - Z(t)) + \widetilde {H}(t) \odot Z(t) \\ \end{aligned} Hidden State:H(t)=H(t−1)⊙(1−Z(t))+H (t)⊙Z(t) |
- 对于LSTM而言 F ( t ) , I ( t ) , C ~ ( t ) , O ( t ) F(t), I(t), \widetilde{C}(t), O(t) F(t),I(t),C (t),O(t)依赖上一个时间步的 H ( t − 1 ) H(t-1) H(t−1)的计算,且其递归式的形式并非 y ( t ) = y ( t − 1 ) ⊕ a ( t ) , a ( t ) y(t)=y(t-1) \oplus a(t), a(t) y(t)=y(t−1)⊕a(t),a(t)已知。
- 对于GRU而言, Z ( t ) , H ~ ( t ) Z(t), \widetilde {H}(t) Z(t),H (t)同样依赖上一个时间步的 H ( t − 1 ) H(t-1) H(t−1)的计算,且其递归式的形式并非 y ( t ) = y ( t − 1 ) ⊕ a ( t ) , a ( t ) y(t)=y(t-1) \oplus a(t), a(t) y(t)=y(t−1)⊕a(t),a(t)已知。
故他们都无法利用parallel scan算法进行并行化。
如何让LSTM,GRU能够使用parallel scan算法进行并行呢?
不考虑对以往时间步的依赖,LSTM,GRU的递归更新公式形如:
y ( t ) = y ( t − 1 ) ⊙ a ( t ) + b ( t ) (6) y(t)=y(t-1) \odot a(t) + b(t) \tag{6} y(t)=y(t−1)⊙a(t)+b(t)(6)
对 ∀ t , a ( t ) , b ( t ) \forall t, a(t), b(t) ∀t,a(t),b(t)已知。这个式子和标准的scan多了一个偏置项 b ( t ) b(t) b(t)。文献[6]指出,只需对式6进行适当变形,即可用两次parallel scan算法对式6进行并行计算。
推导前,不妨将式(6)简写为: y t = y t − 1 a t + b t y_t=y_{t-1} a_t + b_t yt=yt−1at+bt
t=1 y 1 = y 0 a 1 + b 1 t=2 y 2 = y 1 a 2 + b 2 = ( y 0 a 1 + b 1 ) ⊙ a 2 + b 2 = a 1 a 2 [ y 0 + b 1 a 1 + b 2 a 1 a 2 ] t=3 y 3 = y 2 a 3 + b 3 = a 1 a 2 a 3 [ y 0 + b 1 a 1 + b 2 a 1 a 2 ] + b 3 = a 1 a 2 a 3 [ y 0 + b 1 a 1 + b 2 a 1 a 2 + b 3 a 1 a 2 a 3 ] (7) \begin{aligned} &\textbf{t=1} & y_1&=y_0 a_1 + b_1 \\ &\textbf{t=2} &y_2 &= y_1 a_2 + b_2 \\ &&& = (y_0 a_1 + b_1) \odot a_2 + b_2 \\ &&& = a_1 a_2 [y_0 + \frac {b_1}{a_1} + \frac{b_2}{a_1 a_2}] \\ &\textbf{t=3} &y_3 &= y_2 a_3 + b_3 \\ &&& = a_1 a_2 a_3[y_0 + \frac {b_1}{a_1} + \frac{b_2}{a_1 a_2}] + b_3 \\ &&& = a_1 a_2 a_3[y_0 + \frac {b_1}{a_1} + \frac{b_2}{a_1 a_2} + \frac{b_3}{a_1 a_2 a_3}] \\ \end{aligned} \tag{7} t=1t=2t=3y1y2y3=y0a1+b1=y1a2+b2=(y0a1+b1)⊙a2+b2=a1a2[y0+a1b1+a1a2b2]=y2a3+b3=a1a2a3[y0+a1b1+a1a2b2]+b3=a1a2a3[y0+a1b1+a1a2b2+a1a2a3b3](7)
通过归纳,不难得出
y n = y n − 1 a n + b n = ∏ t = 1 n a t ( y 0 + ∑ t = 1 n b t ∏ i = 1 t a i ) = ∏ t = 1 n a t ( y 0 + ∑ t = 1 n exp ( log b t ∏ i = 1 t a i ) ) = ∏ t = 1 n a t ( y 0 + ∑ t = 1 n exp ( log b t − ∑ i = 1 t log a i ) ) (8) \begin{aligned} y_n &= y_{n-1} a_n + b_n \\ &= \prod_{t=1}^{n}a_t \left(y_0 + \sum_{t=1}^{n}\frac{b_t}{\prod_{i=1}^{t}a_i } \right) \\ &= \prod_{t=1}^{n}a_t \left(y_0 + \sum_{t=1}^{n} \exp \left(\log {\frac{b_t}{\prod_{i=1}^{t}a_i }} \right) \right) \\ &= \prod_{t=1}^{n}a_t \left(y_0 + \sum_{t=1}^{n} \exp \left(\log b_t - {\sum_{i=1}^{t}\log a_i }\right)\right) \\ \end{aligned} \tag{8} yn=yn−1an+bn=t=1∏nat(y0+t=1∑n∏i=1taibt)=t=1∏nat(y0+t=1∑nexp(log∏i=1taibt))=t=1∏nat(y0+t=1∑nexp(logbt−i=1∑tlogai))(8)
对上式子两边取对数,有
log y n = ∑ t = 1 n log a t + log ( y 0 + ∑ t = 1 n exp ( log b t − ∑ i = 1 t log a i ) ) (9) \log y_n = \sum_{t=1}^{n} \log a_t + \log \left(y_0 + \sum_{t=1}^{n} \exp \left(\log b_t - {\sum_{i=1}^{t}\log a_i }\right)\right) \tag{9} logyn=t=1∑nlogat+log(y0+t=1∑nexp(logbt−i=1∑tlogai))(9)
从上述递归式可以看出,有两处可以用两次parallel scan算法
第一次parallel scan计算有序集合 { ∑ t = 1 n log a t ∣ n = 1 , 2 , ⋯ , n } \{\sum_{t=1}^{n} \log a_t | n=1, 2, \cdots , n\} {∑t=1nlogat∣n=1,2,⋯,n},
第二次parallel scan计算有序集合 { ∑ t = 1 n exp ( log b t − ∑ i = 1 t log a i ) ∣ n = 1 , 2 , ⋯ , n } \{\sum_{t=1}^{n} \exp \left(\log b_t - {\sum_{i=1}^{t}\log a_i }\right)|n=1, 2, \cdots , n\} {∑t=1nexp(logbt−∑i=1tlogai)∣n=1,2,⋯,n}
有了他们,我们可以并行计算有序集合 { y n ∣ n = 1 , 2 , ⋯ , n } \{ y_n | n=1, 2, \cdots , n \} {yn∣n=1,2,⋯,n}
下面来看,如何将LSTM,GRU转变为式(6)的形式
2.2.2 LSTM的并行化
Step 1: Drop previous hidden state dependencies from gates
F ( t ) = S i g m o i d ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) I ( t ) = S i g m o i d ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) O ( t ) = S i g m o i d ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) C ~ ( t ) = tanh ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) ⇒ F ( t ) = S i g m o i d ( L i n e a r X ( t ) ) ) I ( t ) = S i g m o i d ( L i n e a r ( X ( t ) ) ) O ( t ) = S i g m o i d ( L i n e a r ( X ( t ) ) ) C ~ ( t ) = tanh ( L i n e a r ( X ( t ) ) ) (10) \boxed{\begin{aligned} F(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ I(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ O(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ \widetilde{C}(t) &= \mathrm{\tanh}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ \end{aligned}} \Rightarrow \begin{aligned} F(t) &= \mathrm{Sigmoid}(\mathrm{Linear}X(t))) \\ I(t) &= \mathrm{Sigmoid}(\mathrm{Linear}( X(t))) \\ O(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(X(t))) \\ \widetilde{C}(t) &= \mathrm{\tanh}(\mathrm{Linear}(X(t))) \\ \end{aligned} \tag{10} F(t)I(t)O(t)C (t)=Sigmoid(Linear(Cat([H(t−1),X(t)])))=Sigmoid(Linear(Cat([H(t−1),X(t)])))=Sigmoid(Linear(Cat([H(t−1),X(t)])))=tanh(Linear(Cat([H(t−1),X(t)])))⇒F(t)I(t)O(t)C (t)=Sigmoid(LinearX(t)))=Sigmoid(Linear(X(t)))=Sigmoid(Linear(X(t)))=tanh(Linear(X(t)))(10)
Step 2: Drop range restriction of candidate states
C ~ ( t ) = tanh ( L i n e a r ( X ( t ) ) ) H ( t ) = tanh ( C ( t ) ) ⊙ O ( t ) ⇒ C ~ ( t ) = L i n e a r ( X ( t ) ) H ( t ) = C ( t ) ⊙ O ( t ) (11) \boxed{\begin{aligned} \widetilde{C}(t) &= \mathrm{\tanh}(\mathrm{Linear}(X(t))) \\ H(t) & = \tanh(C(t)) \odot O(t) \end{aligned}} \Rightarrow \begin{aligned} \widetilde{C}(t) &= \mathrm{Linear}(X(t)) \\ H(t) & = C(t) \odot O(t) \end{aligned} \tag{11} C (t)H(t)=tanh(Linear(X(t)))=tanh(C(t))⊙O(t)⇒C (t)H(t)=Linear(X(t))=C(t)⊙O(t)(11)
Step 3: Ensure output is time-independent in scale
F ( t ) = S i g m o i d ( L i n e a r X ( t ) ) ) I ( t ) = S i g m o i d ( L i n e a r ( X ( t ) ) ) O ( t ) = S i g m o i d ( L i n e a r ( X ( t ) ) ) C ~ ( t ) = L i n e a r ( X ( t ) ) C ( t ) = C ( t − 1 ) ⊙ F ( t ) + I ( t ) ⊙ C ~ ( t ) H ( t ) = C ( t ) ⊙ O ( t ) ⇒ H ~ ( t ) = S i g m o i d ( L i n e a r ( X ( t ) ) ) F ′ ( t ) = F ( t ) F ( t ) + I ( t ) I ′ ( t ) = I ( t ) F ( t ) + I ( t ) H ( t ) = F ′ ( t ) ⊙ H ( t − 1 ) + I ′ ( t ) ⊙ H ~ ( t ) (12) \boxed{\begin{aligned} F(t) &= \mathrm{Sigmoid}(\mathrm{Linear}X(t))) \\ I(t) &= \mathrm{Sigmoid}(\mathrm{Linear}( X(t))) \\ O(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(X(t))) \\ \widetilde{C}(t) &= \mathrm{Linear}(X(t)) \\ \hline C(t) & =C(t-1) \odot F(t) + I(t) \odot \widetilde{C}(t) \\ H(t) & = C(t) \odot O(t) \end{aligned}} \Rightarrow \begin{aligned} \widetilde{H}(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(X(t))) \\ F'(t) & = \frac{F(t)}{F(t) + I(t)} \\ I'(t) & = \frac{I(t)}{F(t) + I(t)} \\ \hline H(t) & = F'(t) \odot H(t-1) + I'(t) \odot \widetilde{H}(t) \\ \end{aligned} \tag{12} F(t)I(t)O(t)C (t)C(t)H(t)=Sigmoid(LinearX(t)))=Sigmoid(Linear(X(t)))=Sigmoid(Linear(X(t)))=Linear(X(t))=C(t−1)⊙F(t)+I(t)⊙C (t)=C(t)⊙O(t)⇒H (t)F′(t)I′(t)H(t)=Sigmoid(Linear(X(t)))=F(t)+I(t)F(t)=F(t)+I(t)I(t)=F′(t)⊙H(t−1)+I′(t)⊙H (t)(12)
通过上述的操作,结合文献[6]的技巧(式9)完成LSTM的并行化。
2.2.3 GRU的并行化
GRU的并行化的操作和LSTM类似
Step 1: Drop previous hidden state dependencies from gates
R ( t ) = S i g m o i d ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) Z ( t ) = S i g m o i d ( L i n e a r ( C a t ( [ H ( t − 1 ) , X ( t ) ] ) ) ) H ~ ( t ) = tanh ( L i n e a r ( C a t ( [ H ( t − 1 ) ⊙ R ( t ) , X ( t ) ] ) ) ) ⇒ Z ( t ) = S i g m o i d ( L i n e a r ( X ( t ) ) ) H ~ ( t ) = tanh ( L i n e a r ( X ( t ) ) ) (13) \boxed{\begin{aligned} R(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ Z(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(\mathrm{Cat}([H(t-1), X(t)]))) \\ \widetilde {H}(t) &= \mathrm{\tanh} \bigl(\mathrm{Linear}(\mathrm{Cat}([H(t-1) \odot R(t), X(t)] ))\bigr) \\ \end{aligned}} \Rightarrow \begin{aligned} Z(t) &= \mathrm{Sigmoid}(\mathrm{Linear}(X(t))) \\ \widetilde {H}(t) &= \mathrm{\tanh} (\mathrm{Linear}( X(t) ))\\ \end{aligned} \tag{13} R(t)Z(t)H (t)=Sigmoid(Linear(Cat([H(t−1),X(t)])))=Sigmoid(Linear(Cat([H(t−1),X(t)])))=tanh(Linear(Cat([H(t−1)⊙R(t),X(t)])))⇒Z(t)H (t)=Sigmoid(Linear(X(t)))=tanh(Linear(X(t)))(13)
Step 2: Drop range restriction of candidate states
H ~ ( t ) = tanh ( L i n e a r ( X ( t ) ) ) ⇒ H ~ ( t ) = L i n e a r ( X ( t ) ) (14) \boxed{\begin{aligned} \widetilde {H}(t) &= \mathrm{\tanh} (\mathrm{Linear}( X(t) )) \end{aligned}} \Rightarrow \widetilde {H}(t)= \mathrm{Linear}( X(t) ) \tag{14} H (t)=tanh(Linear(X(t)))⇒H (t)=Linear(X(t))(14)
3 小结
本文从parallel scan算法出发,介绍了如何将经典RNN算法——LSTM,GRU进行变换,使其能够并行化。实验结果本文不做介绍,请参考原论文。
Reference:
[1] Prefix Sums and Their Applications
[2] A parallel algorithm for the efficient solution of a general class of recurrence equations
[3] A Regular Layout for Parallel Adders
[4] LONG SHORT-TERM MEMORY
[5] Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling
[6] Efficient Parallelization of a Ubiquitous Sequential Computation
[7] https://www.csd.uwo.ca/~mmorenom/HPC-Slides/Parallel_prefix_sum.pdf
[8] https://people.cs.vt.edu/yongcao/teaching/cs5234/spring2013/slides/Lecture10.pdf
[9] https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-39-parallel-prefix-sum-scan-cuda
相关文章:
RNN并行化——《Were RNNs All We Needed?》论文解读
InfoPaperhttps://arxiv.org/abs/2410.01201GitHubhttps://github.com/lucidrains/minGRU-pytorch个人博客地址http://myhz0606.com/article/mini_rnn 最近在看并行RNN相关的paper,发现很多都利用了Parallel Scanning算法。本文将从Parallel Scanning算法开始&…...
机器学习周志华学习笔记-第6章<支持向量机>
机器学习周志华学习笔记-第6章<支持向量机> 卷王,请看目录 6支持向量机6.1 函数间隔与几何间隔6.1.1 函数间隔6.1.2 几何间隔 6.2 最大间隔与支持向量6.3 对偶问题6.4 核函数6.5 软间隔支持向量机6.6 支持向量机6.7核方法 6支持向量机 支持向量机是一种经典…...
IP反向追踪技术,了解一下?
DOSS(拒绝服务)攻击是现在比较常见的网络攻击手段。想象一下,有某个恶意分子想要搞垮某个网站,他就会使用DOSS攻击。这种攻击常常使用的方式是IP欺骗。他会伪装成正常的IP地址,让网络服务器以为有很多平常的请求&#…...
2025蓝桥杯(单片机)备赛--扩展外设之UART1的原理与应用(十二)
一、串口1的实现原理 a.查看STC15F2K60S2数据手册: 串口一在590页,此款单片机有两个串口。 串口1相关寄存器: SCON:串行控制寄存器(可位寻址) SCON寄存器说明: 需要PCON寄存器的SMOD0/PCON.6为0,使SM0和SM…...
Linux 使用gdb调试core文件
core文件和gdb调试 什么是 core 文件?产生core文件的原因?core 文件的控制和生成路径gdb 调试core 文件引用和拓展 什么是 core 文件? 当程序运行过程中出现Segmentation fault (core dumped)错误时,程序停止运行,并产…...
Python后端flask框架接收zip压缩包方法
一、用base64编码发送,以及接收 import base64 import io import zipfile from flask import request, jsonifydef unzip_and_find_png(zip_data):# 使用 BytesIO 在内存中处理 zip 数据with zipfile.ZipFile(io.BytesIO(zip_data), r) as zip_ref:extracted_paths…...
【21-30期】Java技术深度剖析:从分库分表到微服务的核心问题解析
🚀 作者 :“码上有前” 🚀 文章简介 :Java 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 文章题目:Java技术深度剖析:从分库分表到微服务的核心问题解析 摘要: 本…...
Linux 中 find 命令使用详解
目录 一:基本语法二:搜索路径1、限制递归层级2、排除指定路径 三:匹配条件1、按照文件名搜索2、按文件类型搜索3、按文件大小搜索4、按文件权限搜索5、按文件所有者或所属组搜索6、按文件修改时间搜索 四:执行操作1、输出满足条件…...
云服务器部署WebSocket项目
WebSocket是一种在单个TCP连接上进行全双工通信的协议,其设计的目的是在Web浏览器和Web服务器之间进行实时通信(实时Web) WebSocket协议的优点包括: 1. 更高效的网络利用率:与HTTP相比,WebSocket的握手只…...
林业产品智能推荐引擎:Spring Boot篇
1 绪论 1.1 选题背景 网络技术和计算机技术发展至今,已经拥有了深厚的理论基础,并在现实中进行了充分运用,尤其是基于计算机运行的软件更是受到各界的关注。计算机软件可以针对不同行业的营业特点以及管理需求,设置不同的功能&…...
【C++】LeetCode:LCR 077. 排序链表
题干 LCR 077. 排序链表 给定链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 解法:归并排序 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(null…...
git教程
文章目录 简介:使用教程:(1)安装git:(2)设置用户名和邮箱作为标识符:(3)建立本地仓库:本地仓库作用:(1)将文件…...
报表工具功能对比:免费易上手的山海鲸报表 vs 庞大用户群体的Tableau
在数据报表与分析领域,随着大数据技术的不断发展和企业数字化转型的深入,市面上涌现出了众多报表工具,为用户提供多元化的选择。对于企业数据分析师、IT人员及管理层来说,选择一款适合自己的报表工具至关重要。本文将从多个角度对…...
鸿蒙原生应用开发及部署:首选华为云,开启HarmonyOS NEXT App新纪元
目录 前言 HarmonyOS NEXT:下一代操作系统的愿景 1、核心特性和优势 2、如何推动应用生态的发展 3、对开发者和用户的影响 华为云服务在鸿蒙原生应用开发中的作用 1、华为云ECS C系列实例 (1)全维度性能升级 (2ÿ…...
CSS之3D转换
三维坐标系 三维坐标系其实就是指立体空间,立体空间是由3个轴共同组成的。 x轴:水平向右注意:x右边是正值,左边是负值 y轴:垂直向下注意:y下面是正值,上面是负值 z轴:垂直屏幕注意:往外面是正值,往里面是负值 3D移动 translat…...
uni-app初学笔记:文件路径与作用
components:可复用的组件pages:页面(可见/不可见)static:静态资源,存放图片视频等 (相当于vue项目的 assets)mainjs:Vue初始化入口文件App.vue:应用配置,用来配置App全局样式以及监听pages.json :配置页面路…...
子组件中$emit和update更新传递变量
vue2.6之后才可以使用update更新,vue2.6以下版本使用input和v-model 需求描述:蒙层上展示弹窗,弹窗点击关闭,需要向父传递关闭的信息 方法1,简便直接传递变量visible(或者不改名isModalVisible也是可以的…...
浅谈Python库之lxml
一、基本介绍 lxml 是一个用 Python 编写的库,它提供了对 XML 和 HTML 文档的解析和操作功能。它使用 C 语言编写的 libxml2 和 libxslt 库作为后端,因此解析速度非常快,并且能够处理大型文档。lxml 支持 XPath 和 XSLT,这使得它在…...
spring boot框架漏洞复现
spring - java开源框架有五种 Spring MVC、SpringBoot、SpringFramework、SpringSecurity、SpringCloud spring boot版本 版本1: 直接就在根下 / 版本2:根下的必须目录 /actuator/ 端口:9093 spring boot搭建 1:直接下载源码打包 2:运行编译好的jar包:actuator-testb…...
IDEA插件CamelCase,快速转变命名格式
在IDEA上大小写转换的快捷键是 CtrlshitU 其它的格式转换的快捷键是 shitaltu 安装方法: file-settings-plugins-在marketplace搜索“CamelCase”-点击安装。 安装成功设置后,重新打开idea 下载完成后 点击 Apply 和OK 此刻就可以选中命名 并使用快捷…...
Elasticsearch中的节点(比如共20个),其中的10个选了一个master,另外10个选了另一个master,怎么办?
大家好,我是锋哥。今天分享关于【Elasticsearch中的节点(比如共20个),其中的10个选了一个master,另外10个选了另一个master,怎么办?】面试题。希望对大家有帮助; Elasticsearch中的节…...
Spring Boot 集成 Knife4j 的 Swagger 文档
在开发微服务应用时,API 文档的生成和维护是非常重要的一环。Swagger 是一个非常流行的 API 文档工具,可以帮助我们自动生成 RESTful API 的文档,并提供了一个友好的界面供开发者测试 API。本文将介绍如何在 Spring Boot 项目中集成 Knife4j …...
C# 创建快捷方式文件和硬链接文件
C# 创建快捷方式文件和硬链接文件 引言什么是快捷方式什么是硬链接文件硬链接与快捷方式不同 实现创建快捷方式文件实现创建硬链接文件小结 引言 什么是快捷方式 平常我们最常window桌面上点击的左下角带小箭头的文件就是快捷方式了,大家都很熟悉它。快捷方式是Wi…...
Linux高阶——1123—服务器基础服务器设备服务器基础能力
目录 1、服务器基础 1、服务器基本概述 2、服务器设计之初解决的问题 网络穿透 网络数据设备间的收发 3、服务器的类型C/S、B/S 2、服务器设备 将自己的服务器软件部署上线 3、代理服务器负载均衡,以及地址绑定方式 4、服务器的基础能力 1、服务器基础 1…...
LabVIEW串口通讯速度
LabVIEW串口通讯能达到的速度 LabVIEW支持高效的串口通讯,通过优化设置,理论上可以实现每次接收一个字节时达到1ms甚至更短的周期。不过,实际性能会受到以下因素的限制: 波特率(Baud Rate):…...
Jmeter中的监听器
3)监听器 1--查看结果树 用途 调试测试计划:查看每个请求的详细信息,帮助调试和修正测试计划。分析响应数据:查看服务器返回的响应数据,验证请求是否成功。检查错误:识别和分析请求失败的原因。 配置步骤…...
缺失的第一个正数(java)
题目描述: 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 示例 1: 输入:nums [1,2,0] 输出:3 解释:范围 […...
跨部门文件共享安全:平衡协作与风险的关键策略
在现代企业中,跨部门协作已成为推动业务发展的关键因素。然而,随着信息的自由流动和共享,文件安全风险也随之增加。如何在促进跨部门协作的同时,确保文件共享的安全性,成为了一个亟待解决的问题。 一、明确文件分类与…...
一键AI换脸软件,支持表情控制,唇形同步Facefusion-3.0.0发布!支持N卡和CPU,一键启动包
嗨,小伙伴们!还记得小编之前介绍的FaceFusion 2.6.1吗?今天给大家带来超级exciting的消息 —— FaceFusion 3.0.0闪亮登场啦! 🌟 3.0.0版本更新 🏗️ 全面重构:修复了不少小虫子,运行更稳定,再也不怕突然罢工啦! 😀 Live Portrait功能:新增…...
我要成为算法高手-递归篇
目录 题目1:汉诺塔题目2:合并两个有序链表题目3:反转链表题目4:两两交换链表中的结点题目5:Pow(x,n) 题目1:汉诺塔 面试题 08.06. 汉诺塔问题 - 力扣(LeetCode) 解题思路࿱…...
Git 提交的相对引用
Git 提交的相对引用 在 Git 中,使用 ~ 和 ^ 符号可以帮助你更灵活地引用提交历史中的特定提交。以下是这些符号的具体用法和示例: 1. ~(波浪号) ~ 符号用于指向上一个或多个父提交。它总是沿着第一个父提交的链向上追溯。 HEA…...
国内首家! 阿里云人工智能平台 PAI 通过 ITU 国际标准测评
近日,阿里云人工智能平台 PAI 顺利通过中国信通院组织的 ITU-T AICP-GA(Technical Specification for Artificial Intelligence Cloud Platform:General Architecture)国际标准和《智算工程平台能力要求》国内标准一致性测评&…...
CDAF / PDAF 原理 | PDAF、CDAF 和 LAAF 对比 | 图像清晰度评价指标
注:本文为 “CDAF / PDAF 原理 | PDAF、CDAF 和 LAAF 对比 | 图像清晰度评价指标” 几篇相关文章合辑。 文章中部分超链接、图片异常受引用之前的原文所限。 相机自动对焦原理 TriumphRay 于 2020-01-16 18:59:41 发布 凸透镜成像原理 这一部分大家中学应该就学过…...
小米C++ 面试题及参考答案下(120道面试题覆盖各种类型八股文)
指针和引用的区别?怎么实现的? 指针和引用有以下一些主要区别。 从概念上来说,指针是一个变量,它存储的是另一个变量的地址。可以通过指针来间接访问所指向的变量。例如,我们定义一个整型指针int *p;,它可以指向一个整型变量的内存地址。而引用是一个别名,它必须在定义的…...
WPF异步UI交互功能的实现方法
前面的文章我们提及过,异步UI的基础实现。基本思路主要是开启新的UI线程,并通过VisualTarget将UI线程上的Visual(即RootVisual)连接到主线程上的UI上即可渲染显示。 但是,之前的实现访问是没有交互能力的,视觉树上的UI并不能实现…...
2024 java大厂面试复习总结(一)(持续更新)
10年java程序员,2024年正好35岁,2024年11月公司裁员,记录自己找工作时候复习的一些要点。 java基础 hashCode()与equals()的相关规定 如果两个对象相等,则hashcode一定也是相同的两个对象相等,对两个对象分别调用eq…...
TCP/IP学习笔记
TCP\IP从实际应用的五层结构开始,自顶而下的去分析每一层。 TCP/IP五层架构概述 学术上面是TCP/IP四层架构,OSI/ISO是七层架构,实际中使用的是TCP/IP五层架构。 数据链路层 ICMP数据包分析 Wireshark抓包分析ICMP协议_wireshark抓ping包分析…...
基于IPMI的服务器硬件监控指标解读
在现代化数据中心中,服务器的稳定运行对于保障业务连续性至关重要。为了实时掌握服务器的健康状况,运维团队需要借助高效的监控工具。监控易作为一款功能强大的监控软件,支持使用IPMI(Intelligent Platform Management Interface&…...
相亲交友小程序项目介绍
一、项目背景 在当今快节奏的社会生活中,人们忙于工作和事业,社交圈子相对狭窄,寻找合适的恋爱对象变得愈发困难。相亲交友作为一种传统而有效的社交方式,在现代社会依然有着巨大的需求。我们的相亲交友项目旨在为广大单身人士提…...
Day3 洛谷Day3 1161+1179+1200+1304
零基础洛谷刷题记录 Day1 2024.11.18 Day2 2024.11.25 Day3 2024.11.26 文章目录 零基础洛谷刷题记录1161:题目描述1161:解题代码1161:学习成果1179:题目描述(成功写出)1179:解题代码1179&…...
【通俗理解】ELBO(证据下界)——机器学习中的“情感纽带”
【通俗理解】ELBO(证据下界)——机器学习中的“情感纽带” 关键词提炼 #ELBO #证据下界 #变分推断 #机器学习 #潜变量模型 #KL散度 #期望 #对数似然 第一节:ELBO的类比与核心概念【尽可能通俗】 ELBO,即证据下界,在…...
Vue: computed 计算属性
在Vue中,computed属性是用于计算和返回基于其他响应式数据的值的功能。 适合在模板中使用,因为能够根据依赖的数据自动更新。 当依赖的数据变化时,computed属性会重新计算,并且会将结果缓存,以提高性能。 computed的…...
【自动化Selenium】Python 网页自动化测试脚本(上)
目录 1、Selenium介绍 2、Selenium环境安装 3、创建浏览器、设置、打开 4、打开网页、关闭网页、浏览器 5、浏览器最大化、最小化 6、浏览器的打开位置、尺寸 7、浏览器截图、网页刷新 8、元素定位 9、元素交互操作 10、元素定位 (1)ID定位 &…...
数据库命令规范、数据库基本设计规范
所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必…...
php常用伪协议整理
前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文整理php常见的伪协议 php伪协议介绍 直观点,就是php可以识别的协议。 类似于我们访问网站的http协议,我们用浏览器访问我们自己本地文件的file协议等。 php可以识别这些协议…...
Redis与MySQL如何保证数据一致性
Redis与MySQL如何保证数据一致性 简单来说 该场景主要发生在读写并发进行时,才会发生数据不一致。 主要流程就是要么先操作缓存,要么先操作Redis,操作也分修改和删除。 一般修改要执行一系列业务代码,所以一般直接删除成本较低…...
NIO三大组件
现在互联网环境下,分布式系统大相径庭,而分布式系统的根基在于网络编程,而netty恰恰是java领域的网络编程的王者,如果要致力于并发高性能的服务器程序、高性能的客户端程序,必须掌握netty网络编程。 NIO基础 NIO是从ja…...
智能呼叫中心是什么?
智能呼叫中心是什么? 作者:开源智能呼叫中心系统 FreeIPCC,Github地址:https://github.com/lihaiya/freeipcc 智能呼叫中心是指运用人工智能、大数据分析等技术,对来电进行智能分析和处理的客户服务中心。以下是对智能…...
LSTM原理解读与实战
在RNN详解及其实战中,简单讨论了为什么需要RNN这类模型、RNN的具体思路、RNN的简单实现等问题。同时,在文章结尾部分我们提到了RNN存在的梯度消失问题,及之后的一个解决方案:LSTM。因此,本篇文章主要结构如下ÿ…...
24.11.26 神经网络 参数初始化
神经网络 感知神经网络 神经网络(Neural Networks)是一种模拟人脑神经元网络结构的计算模型,用于处理复杂的模式识别、分类和预测等任务 生物学: 人脑可以看做是一个生物神经网络,由众多的神经元连接而成 树突&#…...