• 备战数学建模49-深度学习之长短期记忆网络LSTM(RNN)(攻坚战14)


    我们今天学习一个长短期记忆网络,就是LSTM,它是循环神经网络的一种。在使用深度学习处理时序问题时,RNN是最常使用的模型之一。RNN之所以在时序数据上有着优异的表现是因为RNN在 t 时间片时会将 t−1 时间片的隐节点作为当前时间片的输入。这样有效的原因是之前时间片的信息也用于计算当前时间片的内容,而传统模型的隐节点的输出只取决于当前时间片的输入特征。LSTM的全称是Long Short Term Memory,顾名思义,它具有记忆长短期信息的能力的神经网络。LSTM首先在1997年由Hochreiter & Schmidhuber 提出,由于深度学习在2012年的兴起,LSTM又经过了若干代大牛的发展,由此便形成了比较系统且完整的LSTM框架,并且在很多领域得到了广泛的应用。本文着重介绍深度学习时代的LSTM。

    目录

    一、长短期记忆网络LSTM理论

    1.1、LSTM基本结构

    1.2、LSTM的计算公式

    二、MATLAB深度学习之LSTM时间序列预测(单输入->单输出)

    2.1、数据集介绍

    2.2、Matlab实现LSTM时间序列预测过程

    2.3、matlab代码实现LSTM时序预测

    三、MATLAB深度学习之LSTM时间序列预测(多输入->单输出)

    3.1数据集准备

    3.2、matlab实现lstm模型的多输入单输出预测

    四、MATLAB深度学习之LSTM时间序列预测(多输入->多输出)

    4.1、数据集准备

    4.2、matlab实现lstm模型的多输入多输出预测


    一、长短期记忆网络LSTM理论

    1.1、LSTM基本结构

    长短时记忆网络(Long Short Term Memory Network, LSTM),是一种改进之后的循环神经网络,可以解决RNN无法处理长距离的依赖的问题,目前比较流行。

    长短期记忆网络的设计思路如下:

    原始 RNN 的隐藏层只有一个状态,即h,它对于短期的输入非常敏感。再增加一个状态,即c,让它来保存长期的状态,称为单元状态(cell state)

    上面的是一个粗略的表示形式,下面我们剖析LSTM的结构图,看一下基本结构:

    t 时刻,LSTM 的输入有三个:当前时刻网络的输入值 Xt、上一时刻 LSTM 的输出值 ht-1、以及上一时刻的单元状态 Ct-1
    LSTM 的输出有两个:当前时刻 LSTM 输出值 ht、和当前时刻的单元状态 Ct.

    相比RNN,LSTM加入了一个长期单元C,那么怎样控制长期单元C呢,一般使用三个控制开关进行控制;具体如下:第一个控制开关控制继续保留C的状态,第二个控制把当前状态输入到长期状态C,第三个是控制把长期状态C作为当前LSTM的输出。

    上面我们知道使用控制开关可以实现控制长期状态C,那么怎么在算法中实现上述三个开关呢,是通过门的方式实现的。定义:门(gate) 实际上就是一层全连接层,输入是一个向量,输出是一个 01 之间的实数向量。公式为:

    方法:用门的输出向量按元素乘以我们需要控制的那个向量
    原理:门的输出是 01 之间的实数向量
    当门输出为 0 时,任何向量与之相乘都会得到 0 向量,这就相当于什么都不能通过;
    输出为 1 时,任何向量与之相乘都不会有任何改变,这就相当于什么都可以通过。

    1.2、LSTM的计算公式

    1)向前计算公式

    遗忘门(forget gate
    它决定了上一时刻的单元状态 Ct-1 有多少保留到当前时刻 Ct

    输入门(input gate
    它决定了当前时刻网络的输入 Xt 有多少保存到单元状态 Ct

    输出门(output gate
    控制单元状态 Ct 有多少输出到 LSTM 的当前输出值 ht

    其中遗忘门的计算如下所示,遗忘门的计算公式中:
    Wf 是遗忘门的权重矩阵,[ht-1, Xt] 表示把两个向量连接成一个更长的向量,bf是遗忘门的偏置项,σ  sigmoid 函数。

    下面看输入门的计算,首先第一块的输入门是决定当前网络的输入xt有多少保存到记忆单元ct,可以避免无关紧要的内容进入记忆单元。

     根据上一次的输出ht-1和本次的输入xt计算当前输入的状态,具体如下所示:

    当前时刻的单元状态 Ct 的计算:由上一次的单元状态 Ct-1 按元素乘以遗忘门 ft,再用当前输入的单元状态 Ct 按元素乘以输入门 it,再将两个积加和:
    这样,就可以把当前记忆 Ct 和长期记忆 Ct-1 组合在一起,形成了新的单元状态 Ct
    由于遗忘门的控制,它可以保存很久很久之前的信息,由于输入门的控制,它又可以避免当前无关紧要的内容进入记忆。

    我们最后看一下输出门的计算,就是控制单元状态ct有多少记忆输出到LSTM的当前输出ht。

    总结一下,向前计算公式一共包含如下,i、f、c、o分别为输入门、遗忘门、细胞状态,输出门,W 和 b 分别为对应的权重系数矩阵和偏置项;σ 和 tanh 分别为 sigmoid 和双曲正切激活函数。

     

    2)反向传播训练算法

    LSTM 模型训练过程采用的是与经典的反向传播(Back Propagation,BP) 算法原理类似的
    BPTT 算法。大致可以分为 4 个步骤:①按照前向计算方法计算 LSTM 细胞的输出值;②反向计算每个 LSTM 细胞的误差项,包括按时间和网络层级 2 个反向传播方向;③根据相应的误差项,计算每个权重的梯度;④应用基于梯度的优化算法更新权重。

    按照前向计算方法计算 LSTM 细胞的输出值,就是上面的5个公式,即正向计算的过程,主要有5个部分需要计算。

     ②反向计算每个 LSTM 细胞的误差项,包括按时间和网络层级 2 个反向传播方向; 反向计算每个神经元的误差项值。与 RNN 一样,LSTM 误差项的反向传播也是包括两个方向: 一个是沿时间的反向传播,即从当前 t 时刻开始,计算每个时刻的误差项;一个是将误差项向上一层传播。

    根据相应的误差项,计算每个权重的梯度;gate 的激活函数定义为 sigmoid 函数,输出的激活函数为 tanh 函数。权重矩阵 W 都是由两个矩阵拼接而成,这两部分在反向传播中使用不同的公式,因此在后续的推导中,权重矩阵也要被写为分开的两个矩阵。

     我们继续看一下具体的两个方向误差的计算,以及权重梯度的计算。

    首先定义t时刻的误差项,目的是要计算出 t-1 时刻的误差项,如下所示:

     

    利用 ht Ct 的定义和全导数公式,可以得到将误差项向前传递到任意k时刻的公式:

     另外可以得到将误差项传递到上一层的公式,如下:

    对于各个权重梯度的计算,具体的公式如下:

     ④应用基于梯度的优化算法更新权重。基于梯度的优化算法种类众多,用的比较多的就是随机梯
    度下降 ( Stochastic Gradient Descent,SGD)。

    二、MATLAB深度学习之LSTM时间序列预测(单输入->单输出)

    2.1、数据集介绍

    此示例说明如何使用长短期记忆(LSTM)网络预测时间序列数据。为了预测序列的未来时间步长的值,可以训练一个序列到序列回归LSTM网络,其中的响应是值移动了一个时间步长的训练序列。也就是说,在输入序列的每个时间步长,LSTM网络学习预测下一个时间步长的值。要预测未来多个时间点的值,请使用forectAndUpdateState函数一次预测一个时间点,并在每次预测时更新网络状态。 

    使用的数据集是股票数据,数据是out.txt文件,一共436组数据,数据共享在阿里云盘:阿里云盘分享

    2.2、Matlab实现LSTM时间序列预测过程

    1、加载数据

           加载示例数据。某股票的收盘价,包含单个时间序列,时间步长为每日,值对应于每日收盘价。输出是一个单元数组,其中每个元素都是单个时间步长。将数据重塑为行矢量。

    2、 数据拆分

             划分训练和测试数据。在序列的前90%进行训练,在最后10%进行测试。 

    3、数据归一化

            为了更好地匹配和防止训练发散,将训练数据标准化为零均值和单位方差。在预测时,您必须使用与训练数据相同的参数来标准化测试数据。 

            注意:要预测序列的未来时间步长的值,请将响应指定为值移位一个时间步长的训练序列。也就是说,在输入序列的每个时间步长,LSTM网络学习预测下一个时间步长的值。 

    4、 创建LSTM回归网络。将LSTM层指定为具有128个隐藏单元。训练参数可查看帮助,明白其中含义。

    5、训练网络

              使用TrainNetwork训练LSTM网络。 

    6、预测(使用预测值更新网络状态)

            要预测未来多个时间点的值,请使用forectAndUpdateState函数一次预测一个时间点,并在每次预测时更新网络状态。对于每个预测,使用先前的预测作为函数的输入。使用与训练数据相同的参数对测试数据进行标准化。 

     为了初始化网络状态,首先对训练数据XTrain进行预测。接下来,使用训练响应YTrain的最后一个时间步长进行第一个预测(完)。循环其余的预测,并将先前的预测输入到forectAndUpdateState。
    对于大型数据集合、长序列或大型网络,在GPU上计算预测通常比在CPU上计算预测快。否则,在CPU上进行预测的计算速度通常会更快。对于单时间步长预测,请使用CPU。要使用CPU进行预测,请将recrectAndUpdateState的‘ExecutionEnvironment’选项设置为‘CPU’

            使用先前计算的参数来取消预测的标准化,即反归一化。  用预测值绘制训练时间序列。

    7、预测(使用测试数据更新网络状态) 

    如果您可以访问预测之间时间步长的实际值,则可以使用观测值而不是预测值来更新网络状态。首先,初始化网络状态。要对新序列进行预测,请使用Reset State重置网络状态。重置网络状态可防止先前的预测影响对新数据的预测。重置网络状态,然后通过对训练数据进行预测来初始化网络状态。

    2.3、matlab代码实现LSTM时序预测

    实现上述单输入,单输出的时间序列预测的matlab代码如下:

    1. clear;
    2. clc
    3. %% 加载数据,某股票的收盘价,包含单个时间序列,时间步长为每日,值对应于每日收盘价。
    4. data = importdata('out.txt');
    5. data = data';
    6. figure()
    7. plot(data);
    8. %% 划分训练集和测试集,90%训练,10%测试
    9. numTimeStepsTrain = floor(0.9*numel(data));
    10. dataTrain = data(1:numTimeStepsTrain+1);
    11. dataTest = data(numTimeStepsTrain+1:end);
    12. %% 数据的归一化处理,数据标准化为0均值,单位方差
    13. mu = mean(dataTrain);
    14. sig = std(dataTrain);
    15. dataTrainStandardized = (dataTrain - mu) / sig;
    16. %% 定义预测序列的未来时间步长的值,请将响应指定为值移位一个时间步长的训练序列
    17. XTrain = dataTrainStandardized(1:end-1);
    18. YTrain = dataTrainStandardized(2:end);
    19. %% 定义LSTM网络结构,将LSTM层指定为具有128个隐藏单元
    20. % 定义网络结构
    21. layers = [
    22. sequenceInputLayer(1,'Name','input')
    23. lstmLayer(128,'Name','lstm')
    24. fullyConnectedLayer(1,'Name','fc')
    25. regressionLayer];
    26. % 定义训练参数
    27. options = trainingOptions('adam', ...
    28. 'MaxEpochs',250, ...
    29. 'GradientThreshold',1, ...
    30. 'InitialLearnRate',0.005, ...
    31. 'LearnRateSchedule','piecewise', ...
    32. 'LearnRateDropPeriod',125, ...
    33. 'LearnRateDropFactor',0.2, ...
    34. 'Verbose',0, ...
    35. 'Plots','training-progress');
    36. % 训练网络
    37. net = trainNetwork(XTrain,YTrain,layers,options);
    38. %% 预测,使用预测值更新网络状态
    39. % 测试集归一化
    40. dataTestStandardized = (dataTest - mu) / sig;
    41. XTest = dataTestStandardized(1:end-1);
    42. % 预测
    43. net = predictAndUpdateState(net,XTrain);
    44. [net,YPred] = predictAndUpdateState(net,YTrain(end));
    45. numTimeStepsTest = numel(XTest);
    46. for i = 2:numTimeStepsTest
    47. [net,YPred(:,i)] = predictAndUpdateState(net,YPred(:,i-1),'ExecutionEnvironment','cpu');
    48. end
    49. YPred = sig*YPred + mu; %反归一化
    50. %% 绘图
    51. figure
    52. plot(dataTrain(1:end-1))
    53. hold on
    54. idx = numTimeStepsTrain:(numTimeStepsTrain+numTimeStepsTest);
    55. plot(idx,[data(numTimeStepsTrain) YPred],'k.-'),hold on
    56. plot(idx,data(numTimeStepsTrain:end-1),'r'),hold on
    57. hold off
    58. xlabel('Day')
    59. ylabel('P')
    60. title('Forecast')
    61. legend({'Observed', 'Forecast'})
    62. %% 预测,使用测试数据更新网络状态
    63. net = resetState(net);
    64. net = predictAndUpdateState(net,XTrain);
    65. YPred = [];
    66. numTimeStepsTest = numel(XTest);
    67. for i = 1:numTimeStepsTest
    68. [net,YPred(:,i)] = predictAndUpdateState(net,XTest(:,i),'ExecutionEnvironment','cpu');
    69. end
    70. YPred = sig*YPred + mu;
    71. %% 绘图
    72. figure
    73. plot(dataTrain(1:end-1))
    74. hold on
    75. idx = numTimeStepsTrain:(numTimeStepsTrain+numTimeStepsTest);
    76. plot(idx,[data(numTimeStepsTrain) YPred],'k.-'),hold on
    77. plot(idx,data(numTimeStepsTrain:end-1),'r'),hold on
    78. hold off
    79. xlabel('Day')
    80. ylabel('P')
    81. title('Forecast')
    82. legend({'Observed', 'Forecast'})

    原始数据的时间序列图如下:

    训练的过程如下:

    使用预测数据更新网络进行预测的效果如下:

    使用测试数据更新网络的预测效果如下:

     

    三、MATLAB深度学习之LSTM时间序列预测(多输入->单输出)

    3.1数据集准备

    我们使用matlab模拟输入和输出数据,进行预测,输入三组数据,输出一组数据,即多输入和单输出的数据集。

    1. clear,clc
    2. t = (0.01:0.01:20)';
    3. a = (t.^2 - 10 * t);
    4. b = cos(5*pi*t).*exp(-5*pi*0.001*t);
    5. c = cos(7*pi*t).*exp(-7*pi*0.001*t);
    6. % y = a*0.001 + b + c; % 线性
    7. % y = a*0.001 + b.^2 + c; % 二次非线性
    8. % y = a.*b + c; % 非线性
    9. y = exp(a*0.01).*c.*b; % 高度非线性
    10. subplot(4,1,1)
    11. plot(t,a)
    12. ylabel('a')
    13. subplot(4,1,2)
    14. plot(t,b)
    15. ylabel('b')
    16. subplot(4,1,3)
    17. plot(t,c)
    18. ylabel('c')
    19. subplot(4,1,4)
    20. plot(t,y)
    21. ylabel('y')

    如下a,b,c模拟三组输入信号,y模拟一组输出信号,我们根据三组输入数据完成相应的预测。

    3.2、matlab实现lstm模型的多输入单输出预测

    下面使用lstm模型进行划分数据集并进行预测,matlab代码如下:

    1. clear,clc
    2. %% 模拟数据
    3. t = (0.01:0.01:20)';
    4. a = (t.^2 - 10 * t);
    5. b = cos(5*pi*t).*exp(-5*pi*0.001*t);
    6. c = cos(7*pi*t).*exp(-7*pi*0.001*t);
    7. % y = a*0.001 + b + c; % 线性
    8. % y = a*0.001 + b.^2 + c; % 二次非线性
    9. % y = a.*b + c; % 非线性
    10. y = exp(a*0.01).*c.*b; % 高度非线性
    11. subplot(4,1,1)
    12. plot(t,a)
    13. ylabel('a')
    14. subplot(4,1,2)
    15. plot(t,b)
    16. ylabel('b')
    17. subplot(4,1,3)
    18. plot(t,c)
    19. ylabel('c')
    20. subplot(4,1,4)
    21. plot(t,y)
    22. ylabel('y')
    23. %% 数据集分割与标准化
    24. % 定义测试与训练长度
    25. data = [a,b,c,y];
    26. numTimeStepsTrain = floor(0.9*numel(y));
    27. dataTrain = data(1:numTimeStepsTrain+1,:);
    28. dataTest = data(numTimeStepsTrain+1:end,:);
    29. % 归一化
    30. mu = mean(dataTrain);
    31. sig = std(dataTrain);
    32. dataTrainStandardized = (dataTrain - ones(length(dataTrain(:,1)),1)*mu) ./ (ones(length(dataTrain(:,1)),1)*sig);
    33. % 生成训练集输入与输出
    34. XTrain = dataTrainStandardized(:,1:3)';
    35. YTrain = dataTrainStandardized(:,4)';
    36. %% 建立网络层,配置训练参数
    37. layers = [
    38. sequenceInputLayer(3,"Name","input")
    39. lstmLayer(200,"Name","lstm")
    40. dropoutLayer(0.1,"Name","drop")
    41. fullyConnectedLayer(1,"Name","fc")
    42. regressionLayer("Name","regressionoutput")];
    43. options = trainingOptions('adam', ...
    44. 'MaxEpochs',250, ...
    45. 'GradientThreshold',1, ...
    46. 'InitialLearnRate',0.005, ...
    47. 'LearnRateSchedule','piecewise', ...
    48. 'LearnRateDropPeriod',125, ...
    49. 'LearnRateDropFactor',0.2, ...
    50. 'Verbose',0, ...
    51. 'Plots','training-progress');
    52. %% 测试数据归一化
    53. % 测试集归一化
    54. dataTestStandardized = (dataTest - ones(length(dataTest(:,1)),1)*mu) ./ (ones(length(dataTest(:,1)),1)*sig);
    55. % XTest = dataTestStandardized(1:end-1);
    56. XTest = dataTestStandardized(:,1:3)';
    57. %% 多步预测
    58. net = predictAndUpdateState(net,XTrain);
    59. numTimeStepsTest = numel(XTest(1,:));
    60. YPred = [];
    61. for i = 1:numTimeStepsTest
    62. [net,YPred(i)] = predictAndUpdateState(net,XTest(:,i),'ExecutionEnvironment','cpu');
    63. end
    64. YPred = sig(4)*YPred + mu(4);
    65. %% 可视化
    66. % 绘图
    67. idx = (numTimeStepsTrain+1):(numTimeStepsTrain+numTimeStepsTest);
    68. figure
    69. set(gcf,'position',[1,1,1500,1000])
    70. set(gca,'position',[0.1,0.1,0.8,4])
    71. subplot(2,1,1)
    72. plot(data(1:end,4))
    73. hold on
    74. plot(idx, YPred,'-.k',LineWidth=1.5)
    75. hold off
    76. xlabel("t")
    77. ylabel("y")
    78. % xlim([1800,2000])
    79. subplot(2,1,2)
    80. plot(data(1:end,4))
    81. hold on
    82. plot(idx, YPred,'-.^k')
    83. hold off
    84. xlabel("t")
    85. ylabel("y")
    86. xlim([1800,2000])
    87. title("Forecast")
    88. legend(["Observed" "Forecast"])

    预测结果的可视化如下:

    四、MATLAB深度学习之LSTM时间序列预测(多输入->多输出)

    4.1、数据集准备

    使用matlab模拟出三组输入和三组输出作为预测的数据集。

    4.2、matlab实现lstm模型的多输入多输出预测

    1. %% 模拟数据
    2. clear,clc,close all
    3. t = (0.01:0.01:20)';
    4. a = (t.^2 - 10 * t);
    5. b = cos(5*pi*t).*exp(-5*pi*0.001*t);
    6. c = cos(7*pi*t).*exp(-7*pi*0.001*t);
    7. x = a*0.001 + b + c; % 线性
    8. % x = a*0.001 + b.^2 + c; % 二次非线性
    9. % x = a.*b + c; % 非线性
    10. % x = exp(a*0.01).*c.*b; % 高度非线性
    11. y = b .* sin(x);
    12. z = (a + b + c) .* cos(x + y);
    13. figure
    14. subplot(3,1,1)
    15. plot(t,a)
    16. ylabel('a')
    17. subplot(3,1,2)
    18. plot(t,b)
    19. ylabel('b')
    20. subplot(3,1,3)
    21. plot(t,c)
    22. ylabel('c')
    23. figure
    24. subplot(3,1,1)
    25. plot(t,x)
    26. ylabel('x')
    27. subplot(3,1,2)
    28. plot(t,y)
    29. ylabel('y')
    30. subplot(3,1,3)
    31. plot(t,z)
    32. ylabel('z')
    33. %% 数据的预处理
    34. % 定义测试与训练长度
    35. data = [a,b,c,x,y,z];
    36. numTimeStepsTrain = floor(0.9*numel(y));
    37. dataTrain = data(1:numTimeStepsTrain+1,:);
    38. dataTest = data(numTimeStepsTrain+1:end,:);
    39. % 归一化
    40. mu = mean(dataTrain);
    41. sig = std(dataTrain);
    42. dataTrainStandardized = (dataTrain - ones(length(dataTrain(:,1)),1)*mu) ./ (ones(length(dataTrain(:,1)),1)*sig);
    43. % 生成训练集输入与输出
    44. XTrain = dataTrainStandardized(:,1:3)';
    45. YTrain = dataTrainStandardized(:,4:6)';
    46. %% 建立网络结构,训练网络
    47. layers = [
    48. sequenceInputLayer(3,"Name","input")
    49. lstmLayer(200,"Name","lstm")
    50. dropoutLayer(0.1,"Name","drop")
    51. lstmLayer(200,"Name","lstm")
    52. dropoutLayer(0.1,"Name","drop")
    53. fullyConnectedLayer(50,"Name","fc")
    54. fullyConnectedLayer(3,"Name","fc")
    55. regressionLayer("Name","regressionoutput")];
    56. options = trainingOptions('adam', ...
    57. 'MaxEpochs',250, ...
    58. 'GradientThreshold',1, ...
    59. 'InitialLearnRate',0.005, ...
    60. 'LearnRateSchedule','piecewise', ...
    61. 'LearnRateDropPeriod',125, ...
    62. 'LearnRateDropFactor',0.2, ...
    63. 'Verbose',0, ...
    64. 'MiniBatchSize',3, ...
    65. 'Plots','training-progress');
    66. net = trainNetwork(XTrain,YTrain,layers,options);
    67. %% 预测
    68. % 测试集归一化
    69. dataTestStandardized = (dataTest - ones(length(dataTest(:,1)),1)*mu) ./ (ones(length(dataTest(:,1)),1)*sig);
    70. % XTest = dataTestStandardized(1:end-1);
    71. XTest = dataTestStandardized(:,1:3)';
    72. % 数据存放内存
    73. adsXTrain = arrayDatastore(XTrain,'OutputType','cell','IterationDimension',3);
    74. adsYTrain = arrayDatastore(YTrain,'OutputType','cell','IterationDimension',3);
    75. cdsTrain = combine(adsXTrain,adsYTrain);
    76. net = trainedNetwork_1;
    77. % net = predictAndUpdateState(net,XTrain);
    78. numTimeStepsTest = numel(XTest(1,:));
    79. YPred = [];
    80. for i = 1:numTimeStepsTest
    81. [net,YPred(:,i)] = predictAndUpdateState(net,XTest(:,i),'ExecutionEnvironment','cpu');
    82. end
    83. YPred = diag(sig(4:6))*YPred + ones(length(YPred(:,1)),1).*mu(4:6)';
    84. %% 可视化
    85. idx = (numTimeStepsTrain+1):(numTimeStepsTrain+numTimeStepsTest);
    86. label = ['x','y','z'];
    87. for num=1:3
    88. figure
    89. set(gcf,'position',[1,1,1500,1000])
    90. set(gca,'position',[0.1,0.1,0.8,4])
    91. subplot(2,1,1)
    92. plot(data(1:end,num+3))
    93. hold on
    94. plot(idx, YPred(num,:),'-.k',LineWidth=1.5)
    95. hold off
    96. xlabel("t")
    97. ylabel(label(num))
    98. % xlim([1800,2000])
    99. subplot(2,1,2)
    100. plot(data(1:end,num+3))
    101. hold on
    102. plot(idx, YPred(num,:),'-.^k')
    103. hold off
    104. xlabel("t")
    105. ylabel(label(num))
    106. xlim([1800,2000])
    107. title("Forecast")
    108. legend(["Observed" "Forecast"])
    109. end

    预测结果如下:

     

     

  • 相关阅读:
    问题越古老,解决方案存在的时间就越长
    Spring使用的设计模式
    深入理解java虚拟机:虚拟机类加载机制(2)
    十个最为戳心测试/开程序员笑话,念茫茫人海,该如何寻觅?
    【python海洋专题二十二】在海图上text
    【电机控制】PMSM无感foc控制(二)PID控制(附大厂C源码)
    mysql8 新特性注入
    2022年戈登·贝尔奖授予等离子体加速器突破研究
    【信号处理】基于优化算法的 SAR 信号处理(Matlab代码实现)
    小谈设计模式(24)—命令模式
  • 原文地址:https://blog.csdn.net/nuist_NJUPT/article/details/126877834