神经网络中的注意力机制与外部记忆

神经网络中的注意力机制与外部记忆

什么是注意力

注意力是一种人类不可或缺的复杂认知功能,指人可以在关注一些信息的同时忽略另一些信息选择能力

注意力一般分为两种:

  • 自上而下的有意识的注意力,称为聚焦式注意力(Focus Attention)。聚焦式注意力也常称为选择性注意力(Selective Attention)。聚焦式注意力是指有预定目的、依赖任务的,主动有意识地聚焦于某一对象的注意力。
  • 自下而上的无意识的注意力,称为基于显著性的注意力(SaliencyBased Attention)。基于显著性的注意力是由外界刺激驱动的注意,不需要主动干预,也和任务无关

**鸡尾酒会效应:**当一个人在吵闹的鸡尾酒会上和朋友聊天时,尽管周围噪音干扰很多,他还是可以听到朋友的谈话内容,而忽略其他人的声音(聚焦式注意力)。同时, 如果背景声中有重要的词( 比如他的名字),他会马上注意到( 显著性注意力)。

神经网络中的注意力机制

神经网络中的注意力机制与人类处理事情时常说的“注意力”意思类似,即着重关注一些列信息中的部分信息,并对这部分信息进行处理分析。

在神经网络中使用注意力机制,可以达到更好的拟合效果。

注意力机制可以使神经网络忽略不重要的特征向量,而重点计算有用的特征向量。在抛弃无用特征向量对拟合结果干扰的同时,又提升了运算速度。

定位感兴趣的信息,排除不感兴趣的信息。

在计算能力有限的情况下,注意力机制(Attention Mechanism)作为一种资源分配方案,将有限的计算资源用来处理更重要的信息,是解决信息超载问题的主要手段。

注意力机制的实现

神经网络中的注意力机制主要是通过注意力分数来实现的,注意力分数是一个0~1的值,在注意力机制的作用下所有的分数和为1加权和为1

每个注意力分数代表当前项被分配的注意力权重

注意力分数常由神经网络的权重参数在模型的训练中学习得到的,并最终使用SoftMax函数进行计算。这种注意力机制可以使用在任何神经网络模型中。

  1. 注意力机制可以作用在RNN模型中的每个序列上,令RNN模型对序列中的单个样本给予不同的关注度

    Predictingacontinuous-valuedassociatedwithanobject
    0.40.010.40.060.020.010.1
  2. 注意力机制也可以应用在模型输出的特征向量中。

举一个例子,说明注意力在翻译任务中是如何工作的。假设我们有一句话“你今天过得怎么样”,我们想将其翻译成法语版本“ Comment se passe ta journée ”。网络的注意力组件将为输出句子中的每个单词做的是映射输入句子中重要且相关的单词,并为这些单词分配更高的权重,从而提高输出预测的准确性。

在文本翻译时使用注意力机制

在翻译的每一步都为输入词分配权重。

虽然注意力机制在计算机视觉等深度学习的其他领域也有应用,但其主要突破和成功来自于在自然语言处理(NLP)任务中的应用。引入注意力机制是为了解决机器翻译中的长序列问题,这也是大多数其他 NLP 任务的问题。

注意力机制模型简易理解

注意力机制模型的原理可以简单描述为:将具体的任务看做由**query, key, value三个“角色”**来完成(分别用Q、K、V代替)。其中,Q代表要查询的任务,**K,V表示一一对应的键值对,任务目的就是使用Q在K中国找到对应的V值。**原理公式:
D v = A t t e n t i o n ( Q t , K , V ) = S o f t m a x ( ( Q t , K s ) d k ) v s = ∑ s = 1 n 1 z e x p ( ( Q t , K s ) d k ) V s D_v = Attention(Q_t,K,V)=Softmax(\frac{(Q_t,K_s)}{\sqrt[]{d_k} } )v_s=\sum_{s=1}^{n}\frac{1}{z}exp(\frac{(Q_t,K_s)}{\sqrt[]{d_k} } )V_s Dv=Attention(Qt,K,V)=Softmax(dk (Qt,Ks))vs=s=1nz1exp(dk (Qt,Ks))Vs
式中, z z z是归一化因子, Q t Q_t Qt是含有t个查询条件的矩阵, K s K_s Ks是含有 s s s个键值的矩阵, d k d_k dk Q t Q_t Qt中每个查询条件的维度, V s V_s Vs是含有 s s s个元素的值, v s v_s vs V s V_s Vs的一个元素, D V D_V DV是注意力结果。该公式可以拆分成以下计算步骤:
(1) Q t Q_t Qt K s K_s Ks进行内积计算;
(2) 将(1)步的结果除以 d \sqrt{d} d ,这里 d \sqrt{d} d 其调节参数数值的作用,使内积不至于太大;
(3) 使用Softmax函数对第(2)步的结果进行计算,即从取值矩阵 V V V中获取权重,得到的权重为注意了分数;
(4) 使用第(3)步的结果与 v s v_s vs相乘,得到 Q t Q_t Qt与各个 v s v_s vs的相似度;
(5) 对第(4)步的结果加权求和,得到 D v D_v Dv

注意力分布

为了从N个输入向量 [ x 1 , x 2 , . . . , x n ] [x_1, x_2,...,x_n] [x1,x2,...,xn]中选择出和某个特定任务相关的信息,我们需要引入一个和任务相关的表示,称为查询向量q,并通过一个**打分函数 s ( x , q ) s(x,q) s(x,q)**来计算每个输入向量和查询向量之间的相关性。以下是软性选择机制:
α n = p ( z = n ∣ X , q ) = s o f t m a x ( s ( x n , q ) ) = e x p ( s ( x n , q ) ) ∑ j = 1 N e x p ( s ( x j , q ) ) \alpha_n=p(z=n|X,q)=softmax(s(x_n, q))=\frac{exp(s(x_n,q))}{\sum_{j=1}^{N}exp(s(x_j, q))} αn=p(z=nX,q)=softmax(s(xn,q))=j=1Nexp(s(xj,q))exp(s(xn,q))
α n \alpha_n αn为注意力分布, s ( x , q ) s(x, q) s(x,q)为注意力打分函数,q是查询向量。

注意力分布可以理解为在给定任务相关查询q时,第n个输入向量受关注的程度。

注意力机制的软硬模式

**软模式(Soft Attention):**表示所有的数据都会注意,都会计算出相应的注意力权重,不会设置筛选条件。可以采用加权平均的方式得到:
a t t ( X , q ) = ∑ n = 1 N α n ⋅ x n att(X, q)=\sum_{n=1}^{N}\mathbf{\alpha_n} \cdot \mathbf{x_n} att(X,q)=n=1Nαnxn

在这里插入图片描述

**硬模式(Hard Attention):**会在生成注意力权重后筛选并舍弃一部分不符合条件的注意力,让它的注意力权重为0,可以理解为不再注意不符合条件的部分。

自注意力模型

变长序列:长短不一的序列,即模型的输入长度不同

基于卷积或循环网络的序列编码都是一种局部的编码方式,只建模了输入信息的局部依赖关系。虽然循环网络理论上可以建立长距离依赖关系,但是由于信息传递的容量以及梯度消失问题,实际上也只能建立短距离依赖关系。(长程依赖问题)

全连接网络是一种非常直接的建模远距离依赖的模型,但是无法处理变长的输入序列。不同的输入长度,其连接权重的大小也是不同的。这时我们就可以利用注意力机制来“动态”地生成不同连接的权重,这就是自注意力模型(Self-Attention Model)。

在这里插入图片描述

对于输入序列 X = [ x 1 , . . . , x n ] \mathbf{X}=[\mathbf{x_1},...,\mathbf{x_n}] X=[x1,...,xn],线性映射过程为:
Q = W q X K = W k X V = W v X \mathbf{Q}=\mathbf{W_q X}\\ \mathbf{K}=\mathbf{W_k X}\\ \mathbf{V}=\mathbf{W_v X} Q=WqXK=WkXV=WvX
最终通过公式计算出输出向量 h n \mathbf{h_n} hn:
h n = a t t ( ( K , V , q n ) = ∑ j = 1 N α n j ⋅ v j = ∑ j = 1 N s o f t m a x ( s ( k j , q n ) ) v j \mathbf{h_n}=att((\mathbf{K, V}, \mathbf{q_n})=\sum_{j = 1}^{N}\alpha_{nj}\cdot\mathbf{v_j}=\sum_{j=1}^{N}softmax(s(\mathbf{k}_j, \mathbf{q}_n))\mathbf{v}_j hn=att((K,V,qn)=j=1Nαnjvj=j=1Nsoftmax(s(kj,qn))vj
自注意力机制又叫内部注意力机制,用于发现序列数据的内部特征

注意生成的权重需要通过softmax函数做一次归一化处理

  • 全连接模型:无法处理变长序列;
  • 自注意力模型:连接权重 α i j \alpha_{ij} αij由注意力机制动态生成。

自注意力模型可以作为神经网络中的一层来使用,既可以用来替代卷积层和循环层,也可以和它们一起交替使用。

关于记忆

人脑记忆的一个特点是,记忆一般分为长期记忆短期记忆

长期记忆,也称为结构记忆或知识,体现为神经元之间的连接形态,其更新速度比较慢。

短期记忆体现为神经元的活动,更新较快, 维持时间为几秒至几分钟。短期记忆是神经连接的暂时性强化,通过不断巩固、强化可形成长期记忆。

短期记忆、长期记忆的动态更新过程称为记忆演化过程

长期记忆可以类比于人工神经网络中的权重参数,而短期记忆可以类比于人工神经网络中的隐状态。

联想记忆是指一种可以通过内容匹配的方法进行寻址的信息存储方式,也称为基于内容寻址的存储。作为对比,现代计算机的存储方式是根据地址来进行存储的,称为随机访问存储

记忆周期计算机人脑神经网络
短期寄存器短期记忆神经元活性
中期内存工作记忆外部记忆
长期外存长期记忆可学习的参数
存储方式随机寻址内容寻址内容寻址为主

记忆增强网络

为了增强网络容量,我们可以引入辅助记忆单元,将一些和任务相关的信息保存在辅助记忆中, 在需要时再进行读取,这样可以有效地增加网络容量。这个引入的辅助记忆单元一般称为外部记忆,以区别于循环神经网络的内部记忆。以循环神经网络为例,其内部记忆可以类比于计算机的寄存器, 外部记忆可以类比于计算机的内存。这种装备外部记忆的神经网络也称为记忆增强神经网络,或简称为记忆网络。

以循环神经网络为例,其内部记忆可以类比于计算机的寄存器,外部记忆可以类比于计算机的内存。

在这里插入图片描述

这种结构化的外部记忆是带有地址的,即每个记忆片段都可以按地址读取和写入。要实现类似于人脑神经网络的联想记忆能力,就需要按内容寻址的方式进行定位,然后进行读取或写入操作。

按内容寻址通常使用注意力机制来进行。通过注意力机制可以实现一种软性的寻址方式,即计算一个在所有记忆片段上的分布, 而不是一个单一的绝对地址。
r = ∑ n = 1 N α n ⋅ m n \mathbf{r} = \sum_{n=1}^{N}\alpha_n\cdot\mathbf{m}_n r=n=1Nαnmn
类比于计算机的存储器读取,计算注意力分布的过程相当于是计算机的寻址过程,信息加权平均的过程相当于计算机的内容读取过程。

通过引入外部记忆,可以将神经网络的参数和记忆容量“分离”,即在少量增加网络参数的条件下可以大幅增加网络容量。因此,我们可以将注意力机制看作一个接口,将信息的存储与计算分离。

端到端记忆网络

端到端记忆网络 MemN2N 采用一种可微的网络结构,可以多次从外部记忆中读取信息

可以理解为分组记忆查询的神经网络,通过分组的形式方便查询,一组用来寻址一组用来查询,这两组的内容是相同的,但是采用了不同的数据结构来存储。

给定一组需要存储的信息𝑚1∶𝑁 = {𝑚1, ⋯ , 𝑚𝑁}, 为简单起见, 这两组记忆单元可以合并, 即𝐴 = 𝐶。首先将其转换成两组记忆片段 𝐴 = [𝒂1, ⋯ , 𝒂𝑁] 和 𝐶 = [𝒄1, ⋯ , 𝒄𝑁],分别存放在两个外部记忆单元中,其中𝐴用来进行寻址,𝐶 用来进行输出

在端到端记忆网络中, 外部记忆单元是只读的。主网络根据输入𝒙生成𝒒, 并使用键值对注意力机制来从外部记忆中读取相关信息𝒓并产生输出:
r = ∑ n = 1 N s o f t m a x ( a n T q ) c n y = f ( q + r ) \mathbf{r}=\sum_{n=1}^{N}softmax(\mathbf{a}_n^T\mathbf{q})\mathbf{c_n}\\ \mathbf{y}=f(\mathbf{q+r}) r=n=1Nsoftmax(anTq)cny=f(q+r)
为了实现更复杂的计算,我们可以让主网络和外部记忆进行多轮交互。在第𝑘轮交互中, 主网络根据上次从外部记忆中读取的信息𝒓(𝑘-1),产生新的查询向量。
q ( k ) = r k − 1 + q k − 1 \mathbf{q}^{(k)}=\mathbf{r}^{k-1}+\mathbf{q}^{k-1} q(k)=rk1+qk1
在 𝐾 轮交互后, 用$𝒚 = 𝑓(𝒒^{(k)} + 𝒓^{(k)}) $进行预测,这种多轮的交互方式也称为多跳操作

在这里插入图片描述

多跳操作中的参数通常是共享的。

神经图灵机

神经图灵机 NTM 主要由两个部件构成:控制器和外部记忆。主要有读写两个操作,可以完成对外部记忆的修改

外部记忆定义为矩阵 𝑀∈ R D × N R^{D\times N} RD×N, 这里𝑁是记忆片段的数量,𝐷 是每个记忆片段的大小,控制器为一个前馈或循环神经网络。神经图灵机中的外部记忆是可读写的。

同时生成和读写外部记忆相关的三个向量:查询向量 𝒒𝑡、删除向量 𝒆𝑡和增加向量 𝒂𝑡。然后对外部记忆ℳ𝑡 进行读写操作,生成读向量𝒓𝑡 和新的外部记忆𝑀𝑡+1。

在这里插入图片描述

  • **读操作:**首先通过注意力机制来进行基于内容的寻址:
    α t , n = s o f t m a x ( s ( m t , n , q t ) ) \alpha_{t,n}=softmax(s(\mathbf{m}_{t,n},\mathbf{q}_t)) αt,n=softmax(s(mt,n,qt))
    根据注意力分布可以计算出读向量 r t r_{t} rt作为下一时刻的输入:
    r t = ∑ n = 1 N α n m t , n \mathbf{r}_t=\sum_{n=1}^{N}\alpha_n\mathbf{m}_{t,n} rt=n=1Nαnmt,n

  • **写操作:**外部记忆的写操作可以分解为两个子操作:删除和增加
    首先,控制器产生删除向量𝒆𝑡 和增加向量𝒂𝑡,分别表示要从外部记忆中删除的信息和要增加的信息。删除操作是根据注意力分布来按比例地在每个记忆片段中删除 𝒆𝑡, 增加操作是根据注意力分布来按比例地给每个记忆片段加入𝒂𝑡。
    m t + 1 , n = m t , n ( 1 − α t , n e t ) + α t , n a t \mathbf{m}_{t+1,n}=\mathbf{m}_{t,n}(1-\alpha_{t,n}\mathbf{e}_t)+\alpha_{t,n}\mathbf{a}_t mt+1,n=mt,n(1αt,net)+αt,nat

神经动力学的联想记忆

联想记忆模型主要是通过神经网络的动态演化来进行联想,有两种应用场景:
1) 输入的模式和输出的模式在同一空间,这种模型叫作自联想模型。自联想模型可以通过前馈神经网络或者循环神经网络来实现,也常称为自编码器。
2) 输入的模式和输出的模式不在同一空间,这种模型叫作异联想模型,从广义上讲,大部分机器学习问题都可以被看作异联想,因此异联想模型可以作为分类器使用。联想记忆模型可以利用神经动力学的原理来实现按内容寻址的信息存储和检索。

Hopfield 网络

Hopfield 网络是循环人工神经网络的一种特殊形式,由 John Hopfield 在 1982 年的论文中首次描述,论文标题为:“神经网络和物理系统具有涌现的集体计算能力”。

在这里插入图片描述

与其说是循环神经网络,个人感觉更像是一种图神经网络,每个节点代表一个神经元,每个神经元都可以单独作为输入和输出。

值得注意的是,Hopfield 网络是联想神经网络的第一个实例:能够产生紧急联想记忆的 RNN 架构。联想记忆或内容可寻址记忆是一种系统,在该系统中,通过输入模式与记忆模式的关联性来启动记忆回忆。换句话说,联想记忆允许仅使用记忆的不完整或嘈杂部分来检索和完成记忆。例如,一个人可能会听到一首他们喜欢的歌曲,然后被“带回到”他们第一次听到这首歌的记忆中。该记忆中的背景、人物、环境和情绪可以通过随后仅暴露于原始刺激的一部分:单独的歌曲来检索。

每个神经元既是输入单元,又是输出单元,没有隐藏神经元。一个神经元和自身没有反馈相连, 不同神经元之间连接权重是对称的。

在这里插入图片描述

网络中每一组不同的输出取值可以表示一种状态 \ 分类

假设一个Hopfield网络有𝑀个神经元,第 𝑖 个神经元的更新规则为:
s i = { 1  if  ∑ j = 1 M w i j s j + b i ≥ 0 0  otherwise  s_i=\begin{cases} 1 & \text{ if } \sum_{j=1}^{M}w_{ij}s_j +b_i\ge 0\\ 0 & \text{ otherwise } \end{cases} si={10 if j=1Mwijsj+bi0 otherwise 
该网络的连接权重组成的邻接矩阵为对称矩阵。

网络状态的更新有同步和异步两种方式。

能量降低的过程

能量函数

在Hopfield网络中,我们给每个不同的网络状态定义一个标量属性,称为“能量”:
E = − 1 2 ∑ i , j w i j s i s j − ∑ i b i s i E=-\frac{1}{2}\sum_{i,j}w_{ij}s_is_j-\sum_ib_is_i E=21i,jwijsisjibisi
Hopfield网络是稳定的,即能量函数经过多次迭代后会达到收敛状态。权重对称是一个重要特征, 因为它保证了能量函数在神经元激活时单调递减,而不对称的权重可能导致周期性振荡或者混乱。

给定一个外部输入,网络经过演化,会达到某个稳定状态。这些稳定状态称为吸引点,在一个Hopfield网络中,通常有多个吸引点,每个吸引点为一个能量的局部最优点

在这里插入图片描述

联想记忆

Hopfield网络存在有限的吸引点,即能量函数的局部最小点。每个吸引点𝒖都对应一个“管辖”区域ℛ𝒖。若输入向量𝒙落入这个区域,网络最终会收敛到𝒖。因此,我们可以把吸引点看作网络中存储的模式

将网络输入𝒙作为起始状态,随时间收敛到吸引点 𝒖 上的过程作为检索过程。

即使输入向量 𝒙 只包含部分信息或包含噪声,只要其位于对应存储模式的“吸引”区域内,那么随着时间演化,网络最终会收敛到其对应的存储模式。因此,Hopfield的检索是基于内容寻址的检索,具有联想记忆能力。

存储容量

对于联想记忆模型来说,存储容量为其能够可靠地存储和检索模式的最大数量。

对于数量为𝑀 的互相连接的二值神经元网络,其总状态数为2𝑀,其中可以作为有效稳定点的状态数量就是其存储容量。模型容量一般与网络结构和学习方式有关。

Python实现

在这里插入图片描述

我们看到E在随后的更新世代中减少,同时网络记住了一张蝴蝶的图片。图中标记为“收敛”的部分是静态的,因为网络中的每个神经元在状态上都与其各自输入的符号一致。此时,网络状态与学习到的图像相匹配,因此已经收敛到它的记忆中。

神经网络训练过程中能量逐渐收敛的过程

导入和构建部分:以MNIST手写数字集为例:

#imports
import numpy as np
#for visualization
import matplotlib.pyplot as plt

class Hopfield_Net: #network class
    #init ialize network variables and memory
    def __init__(self,input):
        
        #patterns for network training / retrieval
        self.memory = np.array(input)
        #single vs. multiple memories
        if   self.memory.size > 1:
             self.n = self.memory.shape[1] 
        else:
             self.n = len(self.memory)
        #network construction
        self.state = np.random.randint(-2,2,(self.n,1)) #state vector
        self.weights = np.zeros((self.n,self.n)) #weights vector
        self.energies = [] #container for tracking of energy
        
       
    def network_learning(self): #learn the pattern / patterns
        self.weights = (1 / self.memory.shape[0]) * self.memory.T @ self.memory #hebbian learning
        np.fill_diagonal(self.weights, 0)


    def update_network_state(self,n_update): #update network
        for neuron in range(n_update): #update n neurons randomly
            self.rand_index = np.random.randint(0,self.n) #pick a random neuron in the state vector
            #Compute activation for randomly indexed neuron
            self.index_activation = np.dot(self.weights[self.rand_index,:],
                                           self.state) 
            #threshold function for binary state change
            if self.index_activation < 0: 
                self.state[self.rand_index] = -1
            else:
                self.state[self.rand_index] =  1

            
    def compute_energy(self): #compute energy
        self.energy = -0.5*np.dot(np.dot(self.state.T,self.weights),self.state)
        self.energies.append(self.energy)

选择一个随机数字作为网络的记忆,然后使用 pygame 为网络的更新步骤设置动画:
使用时将下面路径"/tmp"改为自己内存中MINST数据集的位置。

#for MNIST fetch
import requests, gzip, os, hashlib
import pygame

#Fetch MNIST dataset from the ~SOURCE~
def fetch_MNIST(url):
  fp = os.path.join("/tmp", hashlib.md5(url.encode('utf-8')).hexdigest())
  if os.path.isfile(fp):
    with open(fp, "rb") as f:
      dat = f.read()
  else:
    with open(fp, "wb") as f:
      dat = requests.get(url).content
      f.write(dat)
  
  return np.frombuffer(gzip.decompress(dat), dtype=np.uint8).copy()

def MNIST_Hopfield(): 
    #test out the Hopfield_Network object on some MNIST data
    #fetch MNIST dataset for some random memory downloads
    
    X = fetch_MNIST(
        "http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz"
        )[0x10:].reshape((-1,784))
    
    #convert to binary
    X_binary = np.where(X>20, 1,-1)

    #Snag a memory from computer brain
    memories_list = np.array([X_binary[np.random.randint(len(X))]])
    
    #initialize Hopfield object
    H_Net = Hopfield_Net(memories_list)
    H_Net.network_learning()


    #Draw it all out, updating board each update iteration
    cellsize = 20
   
    pygame.init() #initialize pygame
    #set dimensions of board and cellsize -  28 X 28  ~ special display surface
    surface = pygame.display.set_mode((28*cellsize,28*cellsize)) 
    pygame.display.set_caption("   ")
    

    #kill pygame if user exits window
    Running = True
    #main animation loop
    while Running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                Running = False
               
                #plot weights matrix
                plt.figure("weights", figsize=(10,7))
                plt.imshow(H_Net.weights,cmap='RdPu') #
                plt.xlabel("Each row/column represents a neuron, each square a connection")
        
                plt.title(" 4096 Neurons - 16,777,216 unique connections",fontsize=15)
                plt.setp(plt.gcf().get_axes(), xticks=[], yticks=[])

                #plot energies
                plt.figure("Energy",figsize=(10,7))
                x = np.arange(len(H_Net.energies))
                plt.scatter(x,np.array(H_Net.energies),s=1,color='red')
                plt.xlabel("Generation")
                plt.ylabel("Energy")
                plt.title("Network Energy over Successive Generations",fontsize=15)
                plt.setp(plt.gcf().get_axes(), xticks=[], yticks=[])

                #quit pygame
                pygame.quit()
    
                
        cells = H_Net.state.reshape(28,28).T
       
        #fills surface with color
        surface.fill((211,211,211)) 
       
        #loop through network state array and update colors for each cell
        for r, c in np.ndindex(cells.shape): #iterates through all cells in cells matrix
            if cells[r,c] == -1:
                col = (135,206,250)
            
            elif cells[r,c] == 1:
                col = (0,0,128)
            
            else: 
                col = (255,140,0)
            pygame.draw.rect(surface, col, (r*cellsize, c*cellsize, \
                                              cellsize, cellsize)) #draw new cell_                 
        
        #update network state
        H_Net.update_network_state(16)
        H_Net.compute_energy()
        pygame.display.update() #updates display from new .draw in update function
        pygame.time.wait(50)



MNIST_Hopfield()
plt.show()
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WhenXuan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值