LSTM返向传播代码实现——LSTM从零实现系列(4)

news2024/11/25 6:37:44

一、前言

         这个LSTM系列是在学习时间序列预测过程中的一些学习笔记,包含理论分析和源码实现两部分。本质属于进阶内容,因此神经网络的基础内容不做过多讲解,想学习基础,可看之前的神经网络入门系列文章:

https://blog.csdn.net/yangwohenmai1/category_9126892.html?spm=1001.2014.3001.5482https://blog.csdn.net/yangwohenmai1/category_9126892.html?spm=1001.2014.3001.5482

        本系列重心放在解析LSTM算法逻辑、前向和反向传播数学原理、推导过程、以及LSTM模型的源码实现上。        本文详细讲解了LSTM源码的实现过程,以及数据在LSTM网络中流转的全过程,尽量做到每一行代码都讲解清楚,即是自己对知识做总结,也方便大家学习。本文是建立在前两篇文章的基础上,很多数学表达式不在重新推导,详细过程可查阅本系列第一篇和第二篇文章。

二、前情回顾

        阅读本文前务必要先看上一篇文章,上篇文章讲解了LSTM网络的前向传播流程,本文接着上篇文章继续讲解LSTM网络的反向传播流程,用到的数据和变量均为上一篇文章的延续,所以要结合上篇文章才能看懂本文

LSTM内部结构及前向传播原理——LSTM从零实现系列(1)_量化交易领域专家YangZongxian的博客-CSDN博客LSTM前向传播原理https://forecast.blog.csdn.net/article/details/127958263?spm=1001.2014.3001.5502LSTM反向传播原理——LSTM从零实现系列(2)_量化交易领域专家YangZongxian的博客-CSDN博客lstm反向传播原理https://forecast.blog.csdn.net/article/details/128047225?spm=1001.2014.3001.5502

LSTM前向传播代码实现——LSTM从零实现系列(3)_量化交易领域专家YangZongxian的博客-CSDN博客lstm前向传播代码实现https://forecast.blog.csdn.net/article/details/128154254?spm=1001.2014.3001.5502

        开始讲解反向传播之前,再来回顾一下这篇文章的模型结构,如下图所示:

三、损失函数

        关于损失函数,上篇文章我们已经描述过代码如何实现,本模型用的是[softmax+二元交叉熵]的组合,反向传播时softmax损失函数的求导过程比较复杂,之前的文章里已经详细讲解过,可以直接参看下面给出的连接:

手动推导softmax神经网络反向传播求导过程——softmax前世今生系列(6)_量化交易领域专家YangZongxian的博客-CSDN博客导读:softmax的前世今生系列是作者在学习NLP神经网络时,以softmax层为何能对文本进行分类、预测等问题为入手点,顺藤摸瓜进行的一系列研究学习。其中包含:1.softmax函数的正推原理,softmax的代数和几何意义,softmax为什么能用作分类预测,softmax链式求导的过程。2.从数学的角度上研究了神经网络为什么能通过反向传播来训练网络的原理。3.结合信息熵理论...https://forecast.blog.csdn.net/article/details/96741328        损失函数和SoftMax反向传播的结果会首先传递到[全连接层]中,所以下面我们就从全连接层为起点讲解反向传播的代码实现。

四、全连接层的反向传播代码实现

4.1.误差矩阵的方向传播

        下面代码中的self.deltaOri就是上篇文章中损失函数回传的误差矩阵deltaOri.shape(32, 2),w是全连接层在前向传播过程中所使用的权重矩阵w.shape(30, 2)。

        从代码中可以看出,误差矩阵在全连接层的反向传播其实是进行了一次矩阵乘法运算,即仿射变换。即误差矩阵deltaOri与全连接层的权重矩阵w的转置矩阵w.T进行相乘:

deltaOri.shape(32, 2) * w.T.shape(2, 30) => newdeltaOri.shape(32, 30)

        完成仿射变换后,将新的误差结果向LSTM继续反向传播。

仿射变换后调整数据的维度:(32,30)=>(32,1,30)

# 误差矩阵反向传播
def bpDelta(self):
    # 将通过激活函数求导后的误差矩阵deltaOri,和当前FCNN层的权重矩阵转置w.T相乘,来将误差向前传播
    deltaPrevReshapped = Tools.matmul(self.deltaOri, self.w.T)
    # 误差矩阵恢复之前Flatten()的拉伸变形,适配上层网络shape以便向上层网络继续反向传播
    self.deltaPrev = deltaPrevReshapped if self.needReshape is False else deltaPrevReshapped.reshape(self.shapeOfOriIn)
    return self.deltaPrev

4.2.权重矩阵参数的更新

        计算得到新的误差矩阵后,还要对全连接层的权重矩阵和偏置项进行更新,这里使用的更新函数是getUpdWeights((w, b),(dw, db), lrt)。

dw.shape(30,2)=inputReshaped.T.(30,32)*deltaOri.shape(32,2)

db.shape(2,)=sum((32,2),axis=0)

首先将当前的权重矩阵(w, b)和(dw, db)传入getUpdWeights,使用Adam优化器进行梯度更新,也就是更新[全连接层]中的权重矩阵w.shape(, )和偏置项b.shape(,)的参数。

# 计算反向传播权重梯度w,b
def bpWeights(self, input, lrt):
    # dw = Tools.matmul(input.T, self.deltaOri)
    # inputReshaped是正向传播时上层网络传到本层的输入矩阵,deltaOri是本层反向传播激活函数求导后的误差矩阵
    dw = Tools.matmul(self.inputReshaped.T, self.deltaOri)
    # 误差矩阵deltaOri.shape->(32,10),进行sum计算后将32个sample向量对应位置求和后db.shape->(1,10),再reshape成b.shape用于后续梯度优化
    db = np.sum(self.deltaOri, axis=0, keepdims=True).reshape(self.b.shape)
    # 当前层网络权重(w,b)元组
    weight = (self.w,self.b)
    # 反向传播的权重(dw,db)元组
    dweight = (dw,db)
    # 元组按引用对象传递,值在方法内部已被更新
    # 将两个元组代入优化器,求出梯度方向,根据梯度更新参数,更新了self.lstmParams[i][Wx,Wh,b]的权重矩阵
    self.optimizerObj.getUpdWeights(weight,dweight, lrt)

五、LSTM层的反向传播代码实现

        本文最核心的就是LSTM层的反向传播代码实现部分,由上一节可知全连接层传回的误差矩阵是deltaOri.shape(32,1,30)。

        3层LSTM反向传播的代码中包含两个for循环,第一个for循环控制3个LSTM层之间的反向传播计算,每一个LSTM层中包含32个sample;第二个for循环控制每一个sample中包含的T个时间步,之间的反向传播计算。需要说明的是这32个sample的计算时并行进行的。即当计算第一个时间步时,是将32个sample的第一个时间步T1一起传入计算。

5.1.层间循环

        在每个LSTM层计算前,首先要重新初始化C参数为零矩阵,作为状态参数传入;同时传入的还有前向传播过程中产生的中间参数cache[];还要传入一个最重要的参数dh,dh在第一个LSTM层反向传播时传入的是上个FNN层计算得到的误差矩阵,后续的LSTM层反向传播时传入的dh其实是上一个LSTM层的最后一个时间步的输出dx。

        第二个for循环内每次输出参数列举如下:

  • dx:反向传播到输入x位置的误差,每个时间步的计算结果按时间步维度保存,作为下个LSTM层的状态输入dh
  • dh:反向传播到输入状态h位置的误差,这个值在每层LSTM循环完全结束后会被抛弃,本质是计算过程的中间变量
  • dc:反向传播到输入状态c位置的误差,这个值在每层LSTM循环完全结束后会被抛弃,本质是计算过程的中间变量
  • dWx:输入x对应的权重矩阵Wx,每个时间步计算结果累加
  • dWh:输入h对应的权重矩阵Wh,每个时间步计算结果累加
  • db:偏置项对应的权重矩阵b,每个时间步计算结果累加

        程序中定义了很多零矩阵,只有dprev_c,dprev_h作为入参进行计算,其他都是负责接收输出的值。

#############################################################################
# TODO: 两个for循环实现:N层堆叠LSTM模型,每个sample包含T个时间步,的backward计算
# Inputs:
# - dh: 全连接层反向传入堆叠LSTM中的,最后一个LSTM层的,误差矩阵(N, T, H)
# - cache: 前向传播过程中产生的中间变量,如f,i,g,o,H,C等
# Returns a tuple of:
# - dx: 存储 包含T个时间步的误差矩阵(N, T, D)
# - dWx: 存储 输入x<-控制门 权重矩阵的梯度(D, 4H)
# - dWh: 存储 状态h<-控制门 权重矩阵的梯度(H, 4H)
# - db: 偏置项的梯度(4H,)
#############################################################################
def lstm_backward(self, dh):
    dx, dh0, dWx, dWh, db = None, None, None, None, None
    #(32, 1, 30)
    N, T, H = dh.shape
    # 获取第一层输入的shape,第一层输入矩阵和隐藏层前向传播矩阵shape不同,lstmParams包含Wx Wh b h c cache
    x, _, _, _, _, _, _, _, _, _ = self.lstmParams[0]['cache'][0]
    #(32, 2)
    D = x.shape[1]
    # dh是全连接层传入的误差矩阵(32, 1, 30)
    dh_prevl = dh
    # 保存各层dwh,dwx,和db
    dweights = []

    for layer in range(self.layersNum - 1, -1, -1):
        # 得到前向传播保存的cache数组
        cache = self.lstmParams[layer]['cache']
        # 如果是第一层网络layer==0,则矩阵shape(DH=D)要与输入矩阵相同;
        # 如果是中间隐藏层矩阵,则shape(DH=H)替换成hidden神经节点数
        DH = D if layer == 0 else H
        # dWx(32, 1, 30)
        dx = np.zeros((N, T, DH))
        # dWx(30, 120)
        dWx = np.zeros((DH, 4 * H))
        # dWh(30, 120)
        dWh = np.zeros((H, 4 * H))
        # db(120,)
        db = np.zeros((4 * H))
        # dprev_h(32, 30)
        dprev_h = np.zeros((N, H))
        # dprev_c(32, 30),上述所有初始化的零矩阵,只有dprev_c,dprev_h是作为参数传入,其他主要作为接收输出用
        dprev_c = np.zeros((N, H))
        # T是一个sample中包含的时间timestep数,对全连接层传入的误差矩阵dh_prevl,按照每个时间步timesteps循环(倒序),进行反向传播计算
        # 每步计算后将对应的状态存入dx对应的时间步位置,作为下个反向传播层的输入状态dh_prevl,cache是前向传播时记录的中间参数,用于后续求导
        for t in range(T - 1, -1, -1):
            dx[:, t, :], dprev_h, dprev_c, dWx_t, dWh_t, db_t = self.lstm_step_backward(dh_prevl[:, t, :] + dprev_h, dprev_c, cache[t])  # 注意此处的叠加
            dWx += dWx_t
            dWh += dWh_t
            db += db_t

        # 本层求得的n维timestep反向传播误差矩阵dx,作为下一层的输入dh_prevl继续传递;其他dprev_h,dprev_c等变量在当前sample所有时间步计算结束时舍弃。
        # 因为LSTM堆叠时,前向传播除了首层LSTM的输入是训练数据x,后续层的输入都是将上层的输出状态H作为下层的输入x,所以反向传播只用回传dx即可
        dh_prevl = dx
        # 存入dweight后续用梯度下降更新矩阵参数
        dweight = (dWx, dWh, db)
        dweights.append(dweight)
    ##############################################################################
    #                               END OF YOUR CODE                             #
    ##############################################################################
    # 返回dx误差和各层参数误差
    return dx, dweights

5.2.步间循环

        LSTM反向传播具体的数学推导过程有一定复杂性,参考文章链接如下:

LSTM反向传播原理——LSTM从零实现系列(2)_量化交易领域专家YangZongxian的博客-CSDN博客lstm反向传播原理https://forecast.blog.csdn.net/article/details/128047225?spm=1001.2014.3001.5502

        程序里首先定义了变量dnext_c = dnext_c + o*(1-np.tanh(next_c)**2)*dnext_h,其中o*(1-np.tanh(next_c)**2)*dnext_h其实是对应的是下面这个表达式的求导结果:

H_t=O_t\odot tan(C_t)

        求导结果如下:

\frac{\partial E}{\partial C_{t}}+\frac{\partial E}{\partial H_{t}}\cdot O_t\left [ 1-tan^{2}(C_t) \right ]

        di,df,do,dg分别对应四个控制门的求导结果,在最后合并成dz时根据每个控制门使用的激活函数不同,乘以对应的求导结果,如:o*(1 - o) 对应的是sigma导数,(1-g^2) 对应的是tanh导数。

        对于dxdW_xdW_h等,出现左乘转置和右乘转置的表达式,也是LSTM不同路径中误差反向传播对应的一部分,其转置原理类似矩阵的逆计算: 

\left\{\begin{matrix} x*w=z \\ x=z*w^T \\ w=x^T*z \end{matrix}\right.

#############################################################################
# TODO: 每一个Timestep中的LSTM反向传播具体算法实现 
# Inputs:
# - dnext_h: 反向传播参数H_t(N, H)
# - dnext_c: 反向传播参数C_t(N, H)
# - cache: 前向传播过程中产生的中间参数
# Returns a tuple of:
# - dx: 反向传播到x的误差(N, D)
# - dprev_h: 反向传播到H_{t-1}的误差(N, H)
# - dprev_c: 反向传播到C_{t-1}的误差(N, H)
# - dWx: 输入x->控制门的梯度矩阵(D, 4H)
# - dWh: 状态h->控制门的梯度矩阵(H, 4H)
# - db: 偏置项梯度(4H,)
#############################################################################
def lstm_step_backward(self, dnext_h, dnext_c, cache):
    """
    LSTM计算技巧说明 :
    i_t = σ(W_xi*x_t + W_hi*h_(t-1) + b_i)
    f_t = σ(W_xf*x_t + W_hf*h_(t-1) + b_f)
    o_t = σ(W_xo*x_t + W_ho*h_(t-1) + b_o)
    c^_t = tanh(W_xc*x_t + W_hc*h_(t-1) + b_c)
    // g_t = tanh(W_ig*x_t + b_ig + W_hg*h_(t-1) + b_hg)
    // c_t = f_t ⊙ c_(t-1) + i_t ⊙ g_t
    c_t = f_t ⊙ c_(t-1) + i_t ⊙ c^_t
    h_t = o_t ⊙ tanh(c_t)
    1.四个参数矩阵di,df,do,dg 都要向前传播,所以合并成一个矩阵dz,在统一和权重矩阵做计算,
      所以我们可以构建一个4倍大小的W,将4个W矩阵拼接起来,计算之后再将4个矩阵分别分离出来,这样可以减少计算量
    2.针对ΔE/ΔC_t_new导数过程示例,除o以外,f i g的反向传播都包含两条求导链,来自C_t和h_t,所以先求出ΔE/ΔC_t_new导数,再代入f i g求导过程
      (1)ht = o_t ⊙ tanh(Ct)
      (2)ΔE/ΔC_t_new = ΔE/ΔC_t + ΔE/(Δh->ΔC)  #根据LSTM模型,可发现ΔE/ΔC_t有两条求导链,都要进行计算,其中一条就是ΔE/ΔC_t
      (3)ΔE/ΔC_t_new = ΔE/ΔC_t + ΔE/Δh*Δh/ΔC_t = ΔE/ΔC_t + ΔE/Δh * (o_t ⊙ tanh(Ct))' = ΔE/ΔC_t + ΔE/Δh * [o_t ⊙ (1-tanh2(C_t))]
    3.针对ΔE/ΔWo_t导数过程示例,先求ΔE/Δo_t,再求ΔE/ΔWo_t <=> [σ(x)]' = σ(x)*(1-σ(x))
      (1)ht = o_t ⊙ tanh(Ct)
      (2)ΔE/Δo_t = ΔE/Δh * Δh/Δo_t = ΔE/Δh * Δ[o_t ⊙ tanh(Ct)]/Δo_t = ΔE/Δh * tanh(Ct)
      (3)o_t = σ(W_xo*x_t + W_ho*h_(t-1) + b_o)
      (4)σ' = σ(1-σ)
      (5)ΔE/ΔWo_t = ΔE/Δh * Δh/Δo_t * Δo_t/ΔWo_t = ΔE/Δo_t * Δo_t/ΔWo_t = ΔE/Δo_t * (o_t)' = ΔE/Δo_t * [σ(W...)]' = (ΔE/Δo_t) * {σ(W+...) * [1-σ(W+...)]} = (ΔE/Δo_t) * o_t * (1-o_t)
      (6)ΔE/ΔWo_t = (ΔE/Δo_t) * o_t * (1-o_t) = ΔE/Δh * tanh(Ct) * o_t * (1-o_t)
    4.针对ΔE/ΔWc^_t(即ΔE/ΔWg_t)导数过程示例,先求ΔE/Δc^_t(即ΔE/Δg_t),再求ΔE/ΔWc^_t = ΔE/ΔWg_t <=> [tanh(x)]' = 1-tanh2(x)
      (1)c_t = f_t ⊙ c_(t-1) + i_t ⊙ c^_t
      (2)ΔE/Δg_t = ΔE/Δc_t * Δc_t/Δg_t = ΔE/Δc_t * Δ[f_t ⊙ c_(t-1) + i_t ⊙ g_t]/Δg_t = ΔE/Δc_t * i_t
      (3)g_t = tanh(W_ig*x_t + b_ig + W_hg*h_(t-1) + b_hg)
      (4)tanh'(x) = 1-tanh2(x)
      (5)ΔE/ΔWg_t = ΔE/Δc_t * Δc_t/Δg_t * Δg_t/ΔWg_t = ΔE/Δg_t * Δg_t/ΔWg_t = ΔE/Δg_t * (g_t)' = ΔE/Δg_t * [tanh(W...)]' = (ΔE/Δc_t * i_t) * (1-tanh2(W...)) = (ΔE/Δc_t * i_t) * (1 - g_t^2)
    5.在求出上述三个导数ΔE/ΔC_t_new ~ dnext_c、ΔE/ΔWo_t ~ do、ΔE/ΔWc^_t ~ dg后,代入后续表达式即可
    """
    dx, dprev_h, dprev_c, dWx, dWh, db = None, None, None, None, None, None
    #x(32, 30)prev_h(32, 30)(32, 30)(30, 120)(30, 120)i(32, 30)(32, 30)(32, 30)(32, 30)(32, 30)
    x, prev_h, prev_c, Wx, Wh, i, f, o, g, next_c = cache

    # ΔE/ΔC_t_new = ΔE/ΔC_t + (Δh->ΔC),每个LSTM单元反向传播起始点包含C_t和H_t两条路径,所以此处进行了一个求和操作
    # next_h=o*np.tanh(next_c),将求出的Δh/ΔC_t代入上式可得 => ΔE/(Δh->ΔC)=ΔE/Δh*Δh/ΔC_t = o*(1-np.tanh(next_c)**2)*ΔE/Δh
    dnext_c = dnext_c + o * (1 - np.tanh(next_c) ** 2) * dnext_h  
    # next_c = f*prev_c + i*g
    di = dnext_c * g
    df = dnext_c * prev_c
    # next_h = o*np.tanh(next_c)
    do = dnext_h * np.tanh(next_c)
    dg = dnext_c * i
    # next_c = f*prev_c + i*g
    dprev_c = f * dnext_c
    # 水平合并figo四部分(32, 120)
    dz = np.hstack((i * (1 - i) * di, f * (1 - f) * df, o * (1 - o) * do, (1 - g ** 2) * dg))  

    # 1.(32, 120)*(30,120).T = (32, 30) 
    # 2.(32, 120)*(30,120).T = (32, 30) 
    # 3.(32, 120)*(2,120).T=(32,2)
    dx = Tools.matmul(dz, Wx.T)
    # 1.(32, 120)*(30,120).T = (32, 30)
    # 2.(32, 120)*(30,120).T = (32, 30)
    # 3.(32, 120)*(30,120).T = (32, 30) 
    dprev_h = Tools.matmul(dz, Wh.T)
    # 1.(32, 30).T * (32, 120)=(30, 120)
    # 2.(32, 30).T * (32, 120)=(30, 120)
    # 3.(32,2).T * (32, 120)=(2,120)
    dWx = Tools.matmul(x.T, dz)
    # 1.(32, 30).T * (32, 120)=(30, 120)
    # 2.(32, 30).T * (32, 120)=(30, 120)
    # 3.(32, 30).T * (32, 120)=(30, 120)
    dWh = Tools.matmul(prev_h.T, dz)
    # 1,2,3:(32, 120)=>(120,)
    db = np.sum(dz, axis=0)

    ##############################################################################
    #                               END OF YOUR CODE                             #
    ##############################################################################
    return dx, dprev_h, dprev_c, dWx, dWh, db

        3层LSTM全部循环计算完成后,最后一层的输出参数如下所示,这里要注意首层对应的dW_x和其它层层对应的dW_x的shape不一样:  

dx.shape(32,1,2)

dWx.shape(2,120)

dWh.shape(30,120)

db.shape(123,)

5.3.权重矩阵参数的更新

        误差矩阵计算完成后,还要对全连接层的权重矩阵和偏置项进行更新,这里使用的更新函数是getUpdWeights((Wx,Wh,b),(dWx,dWh,db), lrt)。

        首先将当前的权重矩阵(Wx,Wh,b)和误差矩阵(dWx,dWh,db)传入getUpdWeights,配合着学习率lr,使用Adam优化器进行梯度更新,也就是更新LSTM层中的三个权重矩阵Wx.shape(, ),Wh.shape(, )和偏置项b.shape(,)的参数。

        我们也正是通过 误差矩阵在每一层进行反向传播计算过程中得到的dW_xdW_hdb,结合Adma等算法,来更新每一层权重矩阵W_xW_hb的参数。其中Adma等算法也只是通过调整权重参数的更新方式,从而提升了损失函数的收敛速度。

# 计算反向传播权重梯度w,b
def bpWeights(self, input, lrt):
    # dw = Tools.matmul(input.T, self.deltaOri)
    # inputReshaped是正向传播时上层网络传到本层的输入矩阵,deltaOri是本层反向传播激活函数求导后的误差矩阵
    dw = Tools.matmul(self.inputReshaped.T, self.deltaOri)
    # 误差矩阵deltaOri.shape->(32,10),进行sum计算后将32个sample向量对应位置求和后db.shape->(1,10),再reshape成b.shape用于后续梯度优化
    db = np.sum(self.deltaOri, axis=0, keepdims=True).reshape(self.b.shape)
    # 当前层网络权重(w,b)元组
    weight = (self.w,self.b)
    # 反向传播的权重(dw,db)元组
    dweight = (dw,db)
    # 元组按引用对象传递,值在方法内部已被更新
    # 将两个元组代入优化器,求出梯度方向,根据梯度更新参数,更新了self.lstmParams[i][Wx,Wh,b]的权重矩阵
    self.optimizerObj.getUpdWeights(weight,dweight, lrt)

        到这里,一个完整的反向传播过程就完成了:这个过程通过将损失函数计算出的损失值不断向前传递,传递的过中模型不断的更新其每一层包含的权重矩阵中的参数;通过不断修改权重矩阵的参数使得后续预测产生的损失值越来越小,从而使得模型预测的结果越来越准确。

        权重参数更新也是反向传播的意义和最终目的。 

六、总结

        LSTM网络模型相较于普通RNN网络还是相对复杂的,本系列用了相当长的时间进行整理和总结到最后落地成文字,每一次重新梳理都有对细节理解方面都有新收获。把脑海中的理解用代码实现,再用文字对逐行代码逐个过程进行描述,这个过程也修正了作者之前对细节上的一些错误理解。

        网上有大量LSTM原理和实现的文章,有很多写的都是挺不错的。本系列希望把LSTM的原理和计算过程呈现出来,即是给大家做参考,也是给自己做备忘。然而这个过程实现起来并不容易,详细导致繁琐,文章实现的效果和预想也还有差距。

        由于篇幅限制,很多神经网络中过于基础的东西和细节就省略了,本系列算是中阶难度,所以阅读过程中有疑问的地方可以直接留言提出,作者看到会尽快回复。源码从0实现没有调用任何第三方机器学习类库,所以源代码部分内容较多,完整贴上来篇幅太大,后续会上传到GitHub,地址先占坑:{addr}。

        如果有等不及想要代码的,可以先私信作者,代码无偿提供。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/101051.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IntelliJ IDEA建立SSM论文基本增删改查管理系统

IntelliJ IDEA建立SSM论文基本增删改查管理系统 1、启动IntelliJ IDEA程序 2、点击File----->New ------>Project建立项目 3、在弹出的对话框中&#xff0c;左边点击”maven”建立maven项目&#xff0c;右边的选择框不要选择&#xff0c;选择maven-archetype-webapp不能…

GAN2 ~

这也是第二季了 近年来&#xff0c;基于生成对抗式网络&#xff08;Generative Adversarial Network, GAN&#xff09;的图片生成研究工作取得了显著的进展。除了能够生成高分辨率、逼真的图片之外&#xff0c;许多创新应用也应运而生&#xff0c;诸如图片个性化编辑、图片动画…

C++ STL算法(一)利用STL算法解决很常见的一些子问题

文章目录next_permutationlower_bound 与 upper_boundpartial_sumsort 与 uniquenext_permutation cplusplus&#xff1a; next_permutation 作用&#xff1a;得到所有的全排列 例题&#xff1a; P1706 全排列问题 void test1() {int n;cin >> n;int* arr new int[n…

Oracle基础版

这是上上周的事情&#xff0c;我们甲方强烈要求使用oracle数据库&#xff0c;也就上学的时候玩过Oracle也忘得差不多了&#xff0c;所以一直不想弄&#xff0c;我们开会产品说要提测了&#xff0c;我还没弄&#xff0c;这不得哐哐开始干活&#xff0c;过程吧还算顺利&#xff0…

Java学习之第八章练习题-1

目录 第一题 题目 我的代码 Person类 错误 正确写法 输出结果 附加要求 代码 结果 第二题 题目 答案 第三题 题目 代码 总结不足 创建对象并运行 第四题 题目 运行结果​编辑 第五题 题目 第六题 题目 第一题 题目 我的代码 Person类 package com.hspedu…

DBCO-NHS 1353016-71-3,二苯基环辛炔-活性酯 可用于以高特异性和反应性标记叠氮化物修饰的生物分子

名称 DBCO-NHS ester 中文名称 二苯基环辛炔-活性酯 英文名称 DBCO-NHS NHS-DBCO 分子量 402.40 CAS 1353016-71-3 溶剂 溶于DMSO, DMF, DCM, THF, Chloroform 存储条件 -20冷冻保存 保存时间 一年 结构式 DBCO&#xff08;二苯并环辛炔&#xff09;是一种环炔烃&…

怎么将视频转为音频mp3格式?这些转换方法一分钟就能学会

随着现在娱乐方式的多样化&#xff0c;我们可以在闲暇时间做一些令人放松的事情。对于我来说&#xff0c;就很喜欢一边听歌一边发呆。我之前喜欢的一位歌手&#xff0c;他的翻唱歌曲以及原创音乐都得到了网友很高的评价&#xff0c;但是有些歌曲在平台上没有音源&#xff0c;我…

【内网安全-CS】Cobalt Strike启动运行上线方法

目录 一、启动运行 1、第一步&#xff1a;进入cs目录 2、第二步&#xff1a;查看本机ip 3、第三步&#xff1a;启动"团队服务器" 4、第四步&#xff1a;客户端连接 二、上线方法 1、第一步&#xff1a;生成监听器 2、第二步&#xff1a;生成木马 3、第三步&…

如何将智能设备关联至云开发中的项目?

将应用中已经连接的设备关联至云项目后&#xff0c;就可以在 涂鸦 IoT 开发平台 通过云开发主动管理和控制对应的设备。云开发提供多种应用中的设备关联方式&#xff1a; 关联自有 App 账号关联自有小程序关联涂鸦 App 账号关联 SaaS 方式一&#xff1a;关联自有 App 大家可以…

深度学习入门(六十)循环神经网络——门控循环单元GRU

深度学习入门&#xff08;六十&#xff09;循环神经网络——门控循环单元GRU前言循环神经网络——门控循环单元GRU课件关注一个序列门候选隐状态隐状态总结教材1 门控隐状态1.1 重置门和更新门1.2 候选隐状态1.4 隐状态2 从零开始实现2.1 初始化模型参数2.2 定义模型2.3 训练与…

前端本地存储数据库 IndexedDB 存储文件

介绍 IndexedDB 是一种底层 API&#xff0c;用于在客户端存储大量的结构化数据。目前各浏览器都已支持&#xff0c;兼容性很好。 特点 IndexedDB 是一个基于 JavaScript 的面向对象数据库&#xff0c;IndexedDB 允许您存储和检索用键索引的对象&#xff1b;可以存储结构化克隆…

MySQL8.0基础篇

文章目录一、MySQL概述1、数据库概述1.1 数据库作用1.2 数据库的相关概念2、MySQL概述2.1 概述2.2 RDBMS与非RDBMS3、MySQL环境安装3.1 MySQL的下载、安装、配置(win)3.2 MySQL登录3.3 MySQL演示使用3.4 MySQL目录结构与源码二、SQL查询1、SQL详情1.1 SQL分类1.2 SQL语言的规则…

Docker和docker-compose中部署nginx-rtmp实现流媒体服务与oob和ffmpeg推流测试

场景 Windows上搭建Nginx RTMP服务器并使用FFmpeg实现本地视频推流&#xff1a; Windows上搭建Nginx RTMP服务器并使用FFmpeg实现本地视频推流_霸道流氓气质的博客-CSDN博客_nginx-rtmp-win64 上面讲的是在windows中搭建nginx-rtmp&#xff0c;如果实在centos中使用docker或…

使用Git拉取和推送到仓库

使用Git拉取和推送到仓库 0、前置工作 首先安装和配置git &#xff0c;参考&#xff1a; git安装教程_嘴巴嘟嘟的博客-CSDN博客_全局安装gitGit上传文件代码到GitHub&#xff08;超详细&#xff09;_蓝布棉的博客-CSDN博客_git上传文件到github仓库 没有仓库的情况 创建仓…

项目总结篇

注意会话管理&#xff1a;cookie,session的作用&#xff1b;(Redis等) 过滤敏感词&#xff08;相关算法&#xff09;&#xff0c;事务&#xff08;Spring怎么管理&#xff09; Redis的数据结构适合那种情况 kafka&#xff1a;框架背后通用的原则&#xff0c;模式&#xff0c;生…

jsp+ssm计算机毕业设计房屋租赁管理系统【附源码】

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JSPSSM mybatis Maven等等组成&#xff0c;B/S模式 Mave…

大数据 集群测试部分

查看HDFS集群状态 在浏览器里访问http://master:9870 不能通过主机名master加端口9870的方式&#xff0c;原因在于没有在hosts文件里IP与主机名的映射&#xff0c;现在只能通过IP地址加端口号的方式访问&#xff1a;http://192.168.1.101:9870 修改宿主机的C:\Windows\System…

2023年大学毕业生,我有话想对你说

虽然每年都说大学毕业生有多少多少&#xff0c;就业难&#xff0c;但貌似以往的经济寒冬&#xff0c;互联网寒冬都不如2022年2023年这么寒冷。 可以说&#xff0c;2022年一整年都是在裁员的声音中度过的&#xff0c;有的公司逐渐取消年终奖&#xff0c;原本熙熙攘攘的办公室&am…

看看欧洲国际学校的IB分数排名

大家好&#xff0c;今天为大家整理了欧洲的国际学校IB分数排名&#xff0c;信息搬运自IB分数网站。如果有偏差还请好心人出来指正。 可以看到&#xff0c;整个榜单瑞士的国际学校数量最多。确实&#xff0c;其实大部分国家的一线国际学校都是集齐在首都城市。 而瑞士的国际学校…

BFS算法-leetcode java题解

BFS算法-leetcode java题解 本文目录BFS算法-leetcode java题解BFS算法思想leetcode 111. 二叉树的最小深度leetcode 1091. 二进制矩阵中的最短路径leetcode 752. 打开转盘锁leetcode 127. 单词接龙leetcode 433. 最小基因变化leetcode 1162. 地图分析leetcode 695. 岛屿的最大…