基于NumPy构建LSTM模块并进行实例应用(附代码)

news2024/11/14 17:34:10

文章目录

      • 0. 前言
        • 0.1 读本文前的必备知识
      • 1. LSTM架构
      • 2. LSTM正向传播代码实现
        • 2.1 隐藏层正向传播
        • 2.2 输出层正向传播
      • 3. LSTM反向传播代码实现
        • 3.1 输出层反向传播
        • 3.2 隐藏层反向传播
      • 4. 实例应用说明
      • 5. 运行结果
      • 6. 后记
      • 6 完整代码

0. 前言

按照国际惯例,首先声明:本文只是我自己学习的理解,虽然参考了他人的宝贵见解,但是内容可能存在不准确的地方。如果发现文中错误,希望批评指正,共同进步。

本篇文章的宗旨是:通过从零构建LSTM模块,并且应用于实例问题中来加深对LSTM(长短期记忆)神经元网络模型的学习及理解。

RNN与其他常见的神经元网络模型(FNN、CNN、GAN)相比,其数学算法底层是最为复杂的,而LSTM作为RNN的改进变体之一,把这个算法的复杂程度又提升了一个层次。因此有必要仔细学习下LSTM的算法及代码实现过程,以便能加强对LSTM的掌握程度以及做出更底层的算法创新。

0.1 读本文前的必备知识

  1. 本篇文章是 基于Numpy构建RNN模块并进行实例应用(附代码)的姊妹篇。如果对RNN的底层算法实现不太了解,非常建议先学习下RNN的内容,否则很难看懂本篇文章;
  2. LSTM(长短期记忆)网络的算法介绍及数学推导 介绍了LSTM的底层数学算法(如果对于其数学推导过程难以理解,那只要知道其推导结果也可以),本文侧重是基于NumPy来从零构建LSTM,对于LSTM正向反向传播的数学公式会稍微带过。

1. LSTM架构

其实这一块CSDN上有很多的介绍文章,以及colah的著名博客 Understanding LSTM Networks 都已经把LSTM的架构说的很明白了。但是我在编码的时候遇到了一些实际问题,所以还是把这块认真梳理一遍:

在这里插入图片描述
上面这个原理图说明了从0时刻到n时刻LSTM网络正向传播的过程,需要注意各个变量在每个时刻的序列,后面编码要严格按照这个序列进行。

细心的你或许已经发现这里把最后一个时刻输出的细胞状态 C n + 1 C_{n+1} Cn+1也标了出来,后面也会说明这是因为在反向传播计算损失E对 C n C_{n} Cn的偏导数时,需要使用到损失E对 C n + 1 C_{n+1} Cn+1的偏导数进行反向迭代传播。

在LSTM内部的参数传递,仍然参照下面的原理图。

2. LSTM正向传播代码实现

正向传播都没有什么难度,只要严格按照上面的原理图进行即可

2.1 隐藏层正向传播

t t t时刻各个门为:

  • 忘记门: f t = σ ( w f ⋅ x t + v f ⋅ h t − 1 + b f ) f_t = \sigma(w_f·x_t+v_f·h_{t-1}+b_f) ft=σ(wfxt+vfht1+bf)
  • 输入门: i t = σ ( w i ⋅ x t + v i ⋅ h t − 1 + b i ) i_t = \sigma(w_i·x_t+v_i·h_{t-1}+b_i) it=σ(wixt+viht1+bi)
  • 新记忆门: g t = t a n h ( w g ⋅ x t + v g ⋅ h t − 1 + b g ) g_t = tanh(w_g·x_t+v_g·h_{t-1}+b_g) gt=tanh(wgxt+vght1+bg)
  • 输出门: o t = σ ( w o ⋅ x t + v o ⋅ h t − 1 + b o ) o_t = \sigma(w_o·x_t+v_o·h_{t-1}+b_o) ot=σ(woxt+voht1+bo)

t t t时刻的细胞状态 C t C_t Ct为:

C t = f t ⨀ C t − 1 + i t ⨀ g t C_t = f_t \bigodot C_{t-1} + i_t \bigodot g_t Ct=ftCt1+itgt

t t t时刻的隐层输出 h t h_t ht为:

h t = o t ⨀ t a n h ( C t ) h_t = o_t \bigodot tanh(C_t) ht=ottanh(Ct)

代码实现:

  def forward(self, x, h_pre, c_pre):  #h_pre为h_t-1, c_pre为c_t-1

        self.Fgate = sigmoid(np.dot(self.w_f, x) + np.dot(self.v_f, h_pre) + self.b_f)
        self.Igate = sigmoid(np.dot(self.w_i, x) + np.dot(self.v_i, h_pre) + self.b_i)
        self.Ggate = np.tanh(np.dot(self.w_g, x) + np.dot(self.v_g, h_pre) + self.b_g)
        self.Ogate = sigmoid(np.dot(self.w_o, x) + np.dot(self.v_o, h_pre) + self.b_o)

        c_cur = self.Fgate * c_pre + self.Igate * self.Ggate  #c_cur为c_t
        h_cur = self.Ogate * np.tanh(c_cur)

        return h_cur, c_cur

这里可以通过多维列表节省一些代码行数,这里为了更清晰表明各个门,全部拆开来写。

2.2 输出层正向传播

t t t时刻的最终输出为:

y t = w h ⋅ h t + b h y_t = w_h·h_t + b_h yt=whht+bh

输出层的正向传播公式一般写为 y t = s o f t m a x ( w h ⋅ h t + b h ) y_t = softmax(w_h·h_t + b_h) yt=softmax(whht+bh),这里能把softmax去掉就相当于把要学习的数据进行了一次逆softmax操作。

代码实现:

 def forward(self, h_cur):   #h_cur为 h_t
        return np.dot(self.w_h, h_cur) + self.b_h

3. LSTM反向传播代码实现

整个代码的难度都在反向传播这里。

3.1 输出层反向传播

这里损失的计算方式选用MSE(均方误差)来用代码实现,即 E = 0.5 ∗ ( y − y t r a i n ) 2 E = 0.5*(y - y_{train})^2 E=0.5(yytrain)2

这里前面增加一个0.5的系数是为了求导数时和平方项“2”抵消。

代码实现:

    def backward(self,y,h_cur, train_data):
        delta = y - train_data
        self.grad_wh = np.dot(delta, h_cur.T)
        self.grad_hcur = np.dot(self.w_h.T, delta)
        self.grad_bh = delta

在这段代码中,除了计算了损失 E E E对权重 w h w_h wh b h b_h bh的偏导,也对 h h h的偏导做了计算,这个会用于后面隐藏层权重偏导的计算。

3.2 隐藏层反向传播

这是整个LSTM算法中最核心、最难的部分。

在隐藏层的反向传播中,最关键的中间变量即是损失 E E E对细胞状态 C t C_t Ct的偏导:

推导过程请见: LSTM(长短期记忆)网络的算法介绍及数学推导

式中 ∂ E ∂ C t \frac{\partial E}{\partial C_t} CtE通过下一时刻的 ∂ E ∂ C t + 1 \frac{\partial E}{\partial C_{t+1}} Ct+1E迭代计算得出,这就要求在实际编码的时候增加一个变量存储每个时刻的 ∂ E ∂ C t \frac{\partial E}{\partial C_t} CtE

∂ E ∂ h t \frac{\partial E}{\partial h_t} htE也是要通过迭代得出的,在 t t t时刻可以计算出 ∂ E ∂ h t − 1 \frac{\partial E}{\partial h_{t-1}} ht1E,这个值也要存储起来,用于 t − 1 t-1 t1时刻的反向传播计算用。

代码实现:

 def backward(self, Fgate, Igate, Ggate, Ogate, x, grad_cnext, Fgate_next, grad_hcur, c_cur,c_pre, h_pre):


        self.grad_ccur = grad_cnext * Fgate_next + grad_hcur * Ogate * (1 - np.tanh(c_cur) * np.tanh(c_cur))
        self.grad_hpre = self.grad_ccur*(np.dot(self.v_f.T, c_pre*Fgate*(1-Fgate)) + np.dot(self.v_g.T,Igate*(1-Ggate*Ggate)) + np.dot(self.v_i.T,Ggate*Igate*(1-Igate)))

        self.grad_wf = np.dot(self.grad_ccur * c_pre * Fgate * (1 - Fgate), x.T)  #这里要注意矩阵的转置!!!
        self.grad_wi = np.dot(self.grad_ccur * Ggate * Igate * (1 - Igate), x.T)
        self.grad_wg = np.dot(self.grad_ccur * Igate * (1 - Ggate * Ggate), x.T)
        self.grad_wo = np.dot(grad_hcur*np.tanh(c_cur)*Ogate*(1-Ogate),x.T)


        self.grad_vf = np.dot(self.grad_ccur * c_pre * Fgate * (1 - Fgate), h_pre.T)
        self.grad_vi = np.dot(self.grad_ccur * Ggate * Igate * (1 - Igate), h_pre.T)
        self.grad_vg = np.dot(self.grad_ccur * Igate * (1 - Ggate * Ggate), h_pre.T)
        self.grad_vo = np.dot(grad_hcur * np.tanh(c_cur) * Ogate * (1 - Ogate), h_pre.T)

        self.grad_bf = self.grad_ccur * c_pre * Fgate * (1 - Fgate)
        self.grad_bi = self.grad_ccur * Ggate * Igate * (1 - Igate)
        self.grad_bg = self.grad_ccur * Igate * (1 - Ggate * Ggate)
        self.grad_bo = grad_hcur * np.tanh(c_cur) * Ogate * (1 - Ogate)

4. 实例应用说明

本实例应用是拟合 y = x 2 y = x^2 y=x2曲线,训练组输入数据train_x为0~1等间距取600个数据,每6个数据为1组,即100组数据。输出数据train_y为train_x的平方再加上一个随机噪声数据。
代码实现:

train_x = np.linspace(0.01,1,600).reshape(100,6,1)
train_y = train_x * train_x + np.random.randn(100,6,1)/200

5. 运行结果

设定迭代次数epoch都为5000,选取不同的学习速率learning rate的模型学习过程如下(其中蓝色点为训练组数据,黄色点为网络模型输出数据):

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

6. 后记

首先感谢能看到这里,整篇文章我陆陆续续编码+debug写了一个月,主要是隐藏层的反向传播部分确实不太好计算。在之前做LSTM数学推导时,我立了一个flag要用Python实现LSTM,也算是填了之前挖的坑,但是万万没想到LSTM的代码实现比RNN还要复杂的多。

而且在代码运行的时候非常容易发生计算溢出的问题:在这里插入图片描述
这种情况的输出肯定都是NaN,为此我尝试了很多解决方法都不灵,只能重新运行,期待下次计算不会溢出。

计算溢出的原因是梯度爆炸,梯度爆炸的原因我猜测是LSTM对权重的初始值有所“挑剔”,这个猜测的理由是代码运行只要顺利通过第一个epoch,后面便没问题了。

6 完整代码

import numpy as np
from tqdm import tqdm
import matplotlib.pyplot as plt

train_x = np.linspace(0.01,1,600).reshape(100,6,1)
train_y = train_x * train_x + np.random.randn(100,6,1)/200


def sigmoid(x):
    return 1/(1+np.exp(-x))


class HiddenLayer():
    def __init__(self,input_size, hidden_size):
        self.w_f = np.random.randn(hidden_size, input_size) #定义各个门的权重, 忘记门
        self.w_i = np.random.randn(hidden_size, input_size)  #输入门
        self.w_g = np.random.randn(hidden_size, input_size)    #新记忆门
        self.w_o = np.random.randn(hidden_size, input_size)   #输出门

        self.v_f = np.random.randn(hidden_size,hidden_size)
        self.v_i = np.random.randn(hidden_size,hidden_size)
        self.v_g = np.random.randn(hidden_size,hidden_size)
        self.v_o = np.random.randn(hidden_size,hidden_size)

        self.b_f = np.zeros([hidden_size, 1])    #输入限定为一维向量
        self.b_i = np.zeros([hidden_size, 1])
        self.b_g = np.zeros([hidden_size, 1])
        self.b_o = np.zeros([hidden_size, 1])

    def forward(self, x, h_pre, c_pre):  #h_pre为h_t-1, c_pre为c_t-1

        self.Fgate = sigmoid(np.dot(self.w_f, x) + np.dot(self.v_f, h_pre) + self.b_f)
        self.Igate = sigmoid(np.dot(self.w_i, x) + np.dot(self.v_i, h_pre) + self.b_i)
        self.Ggate = np.tanh(np.dot(self.w_g, x) + np.dot(self.v_g, h_pre) + self.b_g)
        self.Ogate = sigmoid(np.dot(self.w_o, x) + np.dot(self.v_o, h_pre) + self.b_o)

        c_cur = self.Fgate * c_pre + self.Igate * self.Ggate  #c_cur为c_t
        h_cur = self.Ogate * np.tanh(c_cur)

        return h_cur, c_cur

    def backward(self, Fgate, Igate, Ggate, Ogate, x, grad_cnext, Fgate_next, grad_hcur, c_cur,c_pre, h_pre):


        self.grad_ccur = grad_cnext * Fgate_next + grad_hcur * Ogate * (1 - np.tanh(c_cur) * np.tanh(c_cur))
        self.grad_hpre = self.grad_ccur*(np.dot(self.v_f.T, c_pre*Fgate*(1-Fgate)) + np.dot(self.v_g.T,Igate*(1-Ggate*Ggate)) + np.dot(self.v_i.T,Ggate*Igate*(1-Igate)))

        self.grad_wf = np.dot(self.grad_ccur * c_pre * Fgate * (1 - Fgate), x.T)  #这里要注意矩阵的转置!!!
        self.grad_wi = np.dot(self.grad_ccur * Ggate * Igate * (1 - Igate), x.T)
        self.grad_wg = np.dot(self.grad_ccur * Igate * (1 - Ggate * Ggate), x.T)
        self.grad_wo = np.dot(grad_hcur*np.tanh(c_cur)*Ogate*(1-Ogate),x.T)


        self.grad_vf = np.dot(self.grad_ccur * c_pre * Fgate * (1 - Fgate), h_pre.T)
        self.grad_vi = np.dot(self.grad_ccur * Ggate * Igate * (1 - Igate), h_pre.T)
        self.grad_vg = np.dot(self.grad_ccur * Igate * (1 - Ggate * Ggate), h_pre.T)
        self.grad_vo = np.dot(grad_hcur * np.tanh(c_cur) * Ogate * (1 - Ogate), h_pre.T)

        self.grad_bf = self.grad_ccur * c_pre * Fgate * (1 - Fgate)
        self.grad_bi = self.grad_ccur * Ggate * Igate * (1 - Igate)
        self.grad_bg = self.grad_ccur * Igate * (1 - Ggate * Ggate)
        self.grad_bo = grad_hcur * np.tanh(c_cur) * Ogate * (1 - Ogate)

    def step(self, lr=0.01):
        self.w_f = self.w_f - lr * self.grad_wf
        self.w_i = self.w_i - lr * self.grad_wi
        self.w_g = self.w_g - lr * self.grad_wg
        self.w_o = self.w_o - lr * self.grad_wo

        self.v_f = self.v_f - lr*self.grad_vf
        self.v_i = self.v_i - lr * self.grad_vi
        self.v_g = self.v_g - lr * self.grad_vg
        self.v_o = self.v_o - lr * self.grad_vo

        self.b_f = self.b_f - lr*self.grad_bf
        self.b_i = self.b_i - lr * self.grad_bi
        self.b_g = self.b_g - lr * self.grad_bg
        self.b_o = self.b_o - lr * self.grad_bo


class OutputLayer():
    def __init__(self, hidden_size, output_size):

        self.w_h = np.ones([output_size, hidden_size])
        self.b_h = np.zeros([output_size, 1])

    def forward(self, h_cur):
        return np.dot(self.w_h, h_cur) + self.b_h

    def backward(self,y,h_cur, train_data):
        delta = y - train_data
        self.grad_wh = np.dot(delta, h_cur.T)
        self.grad_hcur = np.dot(self.w_h.T, delta)
        self.grad_bh = delta

    def step(self, lr=0.001):
        self.w_h = self.w_h - lr * self.grad_wh
        self.b_h = self.b_h - lr * self.grad_bh

#---------------------------------------------------
LstmHidden = HiddenLayer(6, 10)
LstmOut = OutputLayer(10, 6)

Fgate_data = np.zeros([101,10,1])  #这些都是要存储的数据
Igate_data = np.zeros([100,10,1])
Ggate_data = np.zeros([100,10,1])
Ogate_data = np.zeros([100,10,1])
gradc_data = np.zeros([101,10,1])  #这里是101是因为c和h都多一个第0时刻的数据
gradh_data = np.zeros([101,10,1])
c_data = np.zeros([101,10,1])
h_data = np.zeros([101,10,1])
y = np.zeros([100,6,1])

epoch = 5001
total_time = len(train_x)

for e in tqdm(range(epoch)):
    for t in range(total_time):

        h_data[t + 1],c_data[t + 1] = LstmHidden.forward(train_x[t], h_data[t], c_data[t])
        Fgate_data[t] = LstmHidden.Fgate
        Igate_data[t] = LstmHidden.Igate
        Ggate_data[t] = LstmHidden.Ggate
        Ogate_data[t] = LstmHidden.Ogate


        y[t] = LstmOut.forward(h_data[t + 1])




    LstmOut.backward(y[total_time-1], h_data[total_time], train_y[total_time-1])
    gradh_data[total_time]=LstmOut.grad_hcur
    gradc_data[total_time] =gradh_data[total_time]  * Ogate_data[total_time-1]* (1 - c_data[total_time] * c_data[total_time])

    LstmOut.backward(y[total_time-2], h_data[total_time-1], train_y[total_time-2])
    gradh_data[total_time-1]=LstmOut.grad_hcur

    for t in reversed(range(total_time-1)):
        LstmOut.backward(y[t], h_data[t + 1], train_y[t])

        LstmHidden.backward(Fgate_data[t],Igate_data[t],Ggate_data[t],Ogate_data[t],train_x[t],
                            gradc_data[t+2],Fgate_data[t+1], gradh_data[t+1], c_data[t+1], c_data[t], h_data[t])
        gradc_data[t+1] = LstmHidden.grad_ccur
        gradh_data[t] = LstmHidden.grad_hpre


        LstmHidden.step(lr=0.00037)
        LstmOut.step(lr=0.00037)

    if e%200 == 0 :
        plt.clf()
        plt.scatter(train_x, train_y, c="blue", s=15)  # 蓝色线为真实值
        plt.scatter(train_x, y, c="orange", s=15)  # 黄色线为预测值
        plt.savefig('x^2_epoch5000_lr00037_%s'%e)


loss = (y-train_y)**2

print(loss)

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

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

相关文章

目标跟踪--卡尔曼滤波 与 匈牙利算法

目前主流的目标跟踪算法都是基于Tracking-by-Detecton策略,即基于目标检测的结果来进行目标跟踪。 跟踪结果中,每个bbox左上角的数字是用来标识某个人的唯一ID号。那么问题就来了,视频中不同时刻的同一个人,位置发生了变化&#x…

西瓜书读书笔记整理(三)—— 第二章 模型评估与选择

第二章 模型评估与选择 第 2 章 模型评估与选择2.1 经验误差与过拟合1. 错误率 / 精度 / 误差2. 训练误差 / 经验误差 / 泛化误差3. 过拟合 / 欠拟合4. 学习能力5. 模型选择 2.2 评估方法1. 评估方法概述2. 留出法3. 交叉验证法4. 自助法5. 调参 / 最终模型 2.3 性能度量1. 回归…

【JavaEE】UDP数据报套接字—实现回显服务器(网络编程)

博主简介:想进大厂的打工人博主主页:xyk:所属专栏: JavaEE初阶 本篇文章将带你了解什么是网络编程? 网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输&am…

中断-STM32

中断-STM32 中断:在主程序运行过程中,出现了特定的中断触发条件 (中断源),使得CPU暂停当前正在运行的程序转而去处理中断程序处理完成后又返回原来被暂停的位置继续运行。 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓…

Java程序猿搬砖笔记(十一)

文章目录 Hexo博客 Next主题图片防盗链问题Springboot Druid数据库密码加密配置步骤Java统计字符串出现的次数Java获取某个字符在字符串中出现第N次的位置Maven激活指定profileMaven中resources标签的用法详解MySQL 字符集不一致报错EasyExcel日期格式化Configuration、Compone…

gradle Task 详解

Task定义和配置 查看工程下所有的task,使用如下命令 gradle tasks 定义一个task task创建的源码 参数分别是 task 名称,和一个 closure。groovy语法的closure可以写在小括号外面,小括号可以省略 task的源码 public interface Task extends…

【Java笔试强训 25】

🎉🎉🎉点进来你就是我的人了博主主页:🙈🙈🙈戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔🤺🤺🤺 目录 一、选择题 二、编程题 🔥星际密码…

RabbitMQ死信队列延迟交换机

RabbitMQ死信队列&延迟交换机 1.什么是死信 死信&死信队列 死信队列的应用: 基于死信队列在队列消息已满的情况下,消息也不会丢失实现延迟消费的效果。比如:下订单时,有15分钟的付款时间 2. 实现死信队列 2.1 准备E…

网络编程代码实例:IO复用版

文章目录 前言代码仓库内容代码(有详细注释)server.cclient_select.cclient_poll.cclient_epoll.c 结果总结参考资料作者的话 前言 网络编程代码实例:IO复用版。 代码仓库 yezhening/Environment-and-network-programming-examples: 环境和…

[Linux]网络连接、资源共享

​⭐作者介绍:大二本科网络工程专业在读,持续学习Java,输出优质文章 ⭐作者主页:逐梦苍穹 ⭐所属专栏:Linux基础操作。本文主要是分享一些Linux系统常用操作,内容主要来源是学校作业,分享出来的…

详解c++---vector模拟实现

目录标题 准备工作构造函数迭代器的完善性质相关的函数实现reservepush_back[ ]emptyresizeinserteraseerase后迭代器失效问题swapclear~vector老式拷贝构造迭代器构造新式拷贝构造老式赋值重载新式赋值重载N个数据的构造vector的浅拷贝问题 准备工作 首先我们知道vector是一个…

HTB靶机06-Beep-WP

beep 靶机IP:10.10.10.7 攻击机IP:10.10.14.6 web RCE漏洞利用、nmap提权 扫描 nmap 常规扫描: ┌──(xavier㉿xavier)-[~/HTB/005-Beep] └─$ sudo nmap -sSV -sC 10.10.10.7 -oN nmap1.out Starting Nmap 7.91 ( https://nmap.org …

《道德经》

《道德经》是春秋时期老子(李耳)的哲学作品,又称《道德真经》、《老子》、《五千言》、《老子五千文》,是中国古代先秦诸子分家前的一部著作,是道家哲学思想的重要来源。 道德经分上下两篇,原文上篇《德经…

网络安全: CIDR无类别路由

网络安全: CIDR无类别路由 CIDR是无类别路由,出现CIDR的原因是因为ipv4的地址被使用完客,CIDR的出现暂缓了ipv4用完的速度。 原本的ipv4很刻板,网络号分成8位,16位,24位作为掩码,也就是 xxx.0…

DRY编码原则

基本情况 DRY,Don’t repeat yourself,就是不要重复你自己的意思。 不要重复,是多么简单的意思了,重复就是多了一个一样的东西,为什么多一个呢,一个就可以了,这样才简单,这是一个常…

【报错】arXiv上传文章出现XXX.sty not found

笔者在overleaf上编译文章一切正常,但上传文章到arxiv时出现类似于如下报错: 一般情况下观察arxiv的编译log,不通过的原因,很多时候都是由于某一行导入了啥package,引起的报错;但是如果没有任何一个具体的…

AppSmith(安装与练习4套)

AppSmith官网文档: https://docs.appsmith.com/getting-started/setup/installation-guides/docker安装前需要已经安装好docker,需要版本如下: Docker ( 20.10.7或者更高) Docker-Compose ( 1.29.2或者更高) 安装Appsmith: 准备…

【Linux】第二站:Linux基本指令(一)

文章目录 一、操作系统OS概念1.OS是什么?2.为什么要有OS?1.一个好的操作系统,他的衡量指标是什么?2.操作系统的核心工作 3.理解我们在计算机上的操作4.Linux和Windows的特点 二、Linux基本指令1. 指令概述2.ls指令1> ls -l2> ls -a3&g…

ChatGPT其实并不想让开发人员做这5件事情

前言 ChatGPT已经火爆了快半年了吧,紧接着国内也开始推出了各种仿制品,我甚至一度怀疑,如果人家没有推出ChatGPT,这些仿制品会不会出现。而很多人也嗨皮得不行,利用各种方法开始科学上网,用ChatGPT做各种觉…

不得不说的行为型模式-解释器模式

解释器模式: 解释器模式(Interpreter Pattern)是一种行为型设计模式,它定义了一种语言,用于解释执行特定的操作,例如正则表达式、查询语言、数学表达式等。该模式通过定义一个解释器来解释语言中的表达式…