如何在 Keras 中开发具有注意力的编码器-解码器模型

news2025/1/17 21:52:46

link

         【翻译自 : How to Develop an Encoder-Decoder Model with Attention in Keras     】

         【说明:Jason Brownlee PhD大神的文章个人很喜欢,所以闲暇时间里会做一点翻译和学习实践的工作,这里是相应工作的实践记录,希望能帮到有需要的人!】

          事实证明,循环神经网络的编码器-解码器架构在自然语言处理领域(例如机器翻译和字幕生成)中的许多序列到序列预测问题上非常强大。

        注意是一种机制,它解决了编码器-解码器架构对长序列的限制,并且通常可以加速学习并提升模型的技能,无序列到序列预测问题。

 

       在本教程中,您将了解如何使用 Keras 在 Python 中开发具有注意力的编码器-解码器循环神经网络。完成本教程后,您将了解:


  
  
  1. 如何设计一个小且可配置的问题来评估编码器 - 解码器循环神经网络有 /无注意力。
  2. 如何设计和评估编码器-解码器网络,注意和不注意序列预测问题。
  3. 如何在有注意力和无注意力的情况下稳健地比较编码器-解码器网络的性能。

教程概述

          本教程分为6个部分; 他们是:


  
  
  1. 带注意力的编码器-解码器
  2. 注意力测试
  3. 无需注意的编码器-解码器
  4. 自定义 Keras 注意力层
  5. 带注意力的编码器-解码器
  6. 模型比较

Python环境

        本教程假设您已安装 Python 3 SciPy 环境。您必须使用 TensorFlow 或 Theano 后端安装 Keras(2.0 或更高版本)。本教程还假设您已安装 scikit-learn、Pandas、NumPy 和 Matplotlib。

带注意力的编码器-解码器

         循环神经网络的编码器-解码器模型是一种用于序列到序列预测问题的架构。顾名思义,它由两个子模型组成:


  
  
  1. 编码器:
  2. 编码器负责逐步遍历输入的时间步长,并将整个序列编码成一个固定长度的向量,称为上下文向量。
  3. 解码器:
  4. 解码器负责在从上下文向量中读取时逐步执行输出时间步骤。

        该架构的一个问题是在长输入或输出序列上的性能很差。原因被认为是因为编码器使用了固定大小的内部表示。注意力是解决此限制的体系结构的扩展。它的工作原理是首先提供从编码器到解码器的更丰富的上下文,以及一种学习机制,其中解码器可以在预测输出序列中的每个时间步长时,在更丰富的编码中学习要注意的位置。

        有关编码器-解码器架构的更多关注,请参阅以下帖子:

                               长短期记忆循环神经网络中的注意力
                     注意力在编码器-解码器循环神经网络中是如何工作的

注意力测试

        在我们开发带有注意力的模型之前,我们将首先定义一个人为的可扩展测试问题,我们可以用它来确定注意力是否提供任何好处。在这个问题中,我们将生成随机整数序列作为输入,并匹配由输入序列中整数子集组成的输出序列。

      例如,输入序列可能是 [1, 6, 2, 7, 3],预期输出序列可能是序列 [1, 6] 中的前两个随机整数。我们将定义问题,使得输入和输出序列的长度相同,并根据需要用“0”值填充输出序列。

       首先,我们需要一个函数来生成随机整数序列。我们将使用 Python randint() 函数生成 0 到最大值之间的随机整数,并将此范围用作问题的基数。下面的函数 generate_sequence() 将生成一个具有固定长度和指定基数的随机整数序列。


  
  
  1. from random import randint
  2. # generate a sequence of random integers
  3. def generate_sequence( length, n_unique):
  4. return [randint( 0, n_unique- 1) for _ in range(length)]
  5. # generate random sequence
  6. sequence = generate_sequence( 5, 50)
  7. print(sequence)

       运行此示例会生成 5 个时间步长的序列,其中序列中的每个值都是 0 到 49 之间的随机整数。

[43, 3, 28, 34, 33]
  
  

         接下来,我们需要一个函数来将离散整数值编码为二进制向量。如果使用基数 50,则每个整数将由指定整数值索引中的 0 值和 1 的 50 元素向量表示。下面的 one_hot_encode() 函数将对给定的整数序列进行one-hot编码。


  
  
  1. # one hot encode sequence
  2. def one_hot_encode( sequence, n_unique):
  3. encoding = list()
  4. for value in sequence:
  5. vector = [ 0 for _ in range(n_unique)]
  6. vector[value] = 1
  7. encoding.append(vector)
  8. return array(encoding)

        我们还需要能够解码编码序列。 这将需要将来自模型的预测或编码的预期序列转换回我们可以读取和评估的整数序列。下面的 one_hot_decode() 函数会将编码序列解码回整数序列。


  
  
  1. # decode a one hot encoded string
  2. def one_hot_decode( encoded_seq):
  3. return [argmax(vector) for vector in encoded_seq]

        我们可以在下面的示例中测试这些操作。


  
  
  1. from random import randint
  2. from numpy import array
  3. from numpy import argmax
  4. # generate a sequence of random integers
  5. def generate_sequence( length, n_unique):
  6. return [randint( 0, n_unique- 1) for _ in range(length)]
  7. # one hot encode sequence
  8. def one_hot_encode( sequence, n_unique):
  9. encoding = list()
  10. for value in sequence:
  11. vector = [ 0 for _ in range(n_unique)]
  12. vector[value] = 1
  13. encoding.append(vector)
  14. return array(encoding)
  15. # decode a one hot encoded string
  16. def one_hot_decode( encoded_seq):
  17. return [argmax(vector) for vector in encoded_seq]
  18. # generate random sequence
  19. sequence = generate_sequence( 5, 50)
  20. print(sequence)
  21. # one hot encode
  22. encoded = one_hot_encode(sequence, 50)
  23. print(encoded)
  24. # decode
  25. decoded = one_hot_decode(encoded)
  26. print(decoded)

          运行该示例首先打印一个随机生成的序列,然后打印一个编码版本,最后再次打印解码序列。


  
  
  1. [ 3, 18, 32, 11, 36]
  2. [[ 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
  3. [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
  4. [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
  5. [ 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
  6. [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0]]
  7. [ 3, 18, 32, 11, 36]

        最后,我们需要一个函数来创建输入和输出序列对来训练和评估模型。

       下面名为 get_pair() 的函数将返回一个输入和输出序列对,给定指定的输入长度、输出长度和基数。 输入和输出序列的长度相同,即输入序列的长度,但输出序列将作为输入序列的前 n 个字符,并用零值填充到所需长度。然后对整数序列进行编码,然后重新整形为循环神经网络所需的 3D 格式,其维度为:样本、时间步长和特征。 在这种情况下,样本始终为 1,因为我们只生成一对输入-输出,时间步长是输入序列长度,特征是每个时间步长的基数。


  
  
  1. # prepare data for the LSTM
  2. def get_pair( n_in, n_out, n_unique):
  3. # generate random sequence
  4. sequence_in = generate_sequence(n_in, n_unique)
  5. sequence_out = sequence_in[:n_out] + [ 0 for _ in range(n_in-n_out)]
  6. # one hot encode
  7. X = one_hot_encode(sequence_in, n_unique)
  8. y = one_hot_encode(sequence_out, n_unique)
  9. # reshape as 3D
  10. X = X.reshape(( 1, X.shape[ 0], X.shape[ 1]))
  11. y = y.reshape(( 1, y.shape[ 0], y.shape[ 1]))
  12. return X,y

        完整代码如下:


  
  
  1. from random import randint
  2. from numpy import array
  3. from numpy import argmax
  4. # generate a sequence of random integers
  5. def generate_sequence( length, n_unique):
  6. return [randint( 0, n_unique- 1) for _ in range(length)]
  7. # one hot encode sequence
  8. def one_hot_encode( sequence, n_unique):
  9. encoding = list()
  10. for value in sequence:
  11. vector = [ 0 for _ in range(n_unique)]
  12. vector[value] = 1
  13. encoding.append(vector)
  14. return array(encoding)
  15. # decode a one hot encoded string
  16. def one_hot_decode( encoded_seq):
  17. return [argmax(vector) for vector in encoded_seq]
  18. # prepare data for the LSTM
  19. def get_pair( n_in, n_out, n_unique):
  20. # generate random sequence
  21. sequence_in = generate_sequence(n_in, n_unique)
  22. sequence_out = sequence_in[:n_out] + [ 0 for _ in range(n_in-n_out)]
  23. # one hot encode
  24. X = one_hot_encode(sequence_in, n_unique)
  25. y = one_hot_encode(sequence_out, n_unique)
  26. # reshape as 3D
  27. X = X.reshape(( 1, X.shape[ 0], X.shape[ 1]))
  28. y = y.reshape(( 1, y.shape[ 0], y.shape[ 1]))
  29. return X,y
  30. # generate random sequence
  31. X, y = get_pair( 5, 2, 50)
  32. print(X.shape, y.shape)
  33. print( 'X=%s, y=%s' % (one_hot_decode(X[ 0]), one_hot_decode(y[ 0])))

       运行该示例会生成一个输入-输出对并打印两个数组的形状。生成的对然后以解码的形式打印,我们可以看到序列的前两个整数在输出序列中被复制,然后是零值的填充。


  
  
  1. ( 1, 5, 50) ( 1, 5, 50)
  2. X=[ 12, 20, 36, 40, 12], y=[ 12, 20, 0, 0, 0]

无需注意力的编码器-解码器

        在本节中,我们将在没有注意力的情况下使用编码器 - 解码器模型开发该问题的性能基线。我们将在 5 个时间步长的输入和输出序列、输出序列中输入序列的前 2 个元素和基数 50 处修复问题定义。


  
  
  1. # configure problem
  2. n_features = 50
  3. n_timesteps_in = 5
  4. n_timesteps_out = 2

          我们可以在 Keras 中开发一个简单的编码器 - 解码器模型,方法是从编码器 LSTM 模型中获取输出,针对输出序列中的时间步数重复 n 次,然后使用解码器来预测输出序列。我们将使用相同数量的单元配置编码器和解码器,在本例中为 150。我们将使用梯度下降的高效 Adam 实现并优化分类交叉熵损失函数,因为该问题在技术上是一个多类分类问题 .该模型的配置是经过一些试验和错误后发现的,并没有经过优化。

      下面列出了 Keras 中编码器-解码器架构的代码。 


  
  
  1. # define model
  2. model = Sequential()
  3. model.add(LSTM( 150, input_shape=(n_timesteps_in, n_features)))
  4. model.add(RepeatVector(n_timesteps_in))
  5. model.add(LSTM( 150, return_sequences= True))
  6. model.add(TimeDistributed(Dense(n_features, activation= 'softmax')))
  7. model. compile(loss= 'categorical_crossentropy', optimizer= 'adam', metrics=[ 'accuracy'])

       我们将在 5,000 个随机输入-输出整数序列对上训练模型。


  
  
  1. # train LSTM
  2. for epoch in range( 5000):
  3. # generate new random sequence
  4. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  5. # fit model for one epoch on this sequence
  6. model.fit(X, y, epochs= 1, verbose= 2)

         训练完成后,我们将在 100 个新的随机生成的整数序列上评估模型,并且仅在整个输出序列与预期值匹配时才将预测标记为正确。


  
  
  1. # evaluate LSTM
  2. total, correct = 100, 0
  3. for _ in range(total):
  4. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  5. yhat = model.predict(X, verbose= 0)
  6. if array_equal(one_hot_decode(y[ 0]), one_hot_decode(yhat[ 0])):
  7. correct += 1
  8. print( 'Accuracy: %.2f%%' % ( float(correct)/ float(total)* 100.0))

        最后,我们将打印 10 个预期输出序列和模型预测序列的示例,下面列出了完整的示例。


  
  
  1. from random import randint
  2. from numpy import array
  3. from numpy import argmax
  4. from numpy import array_equal
  5. from keras.models import Sequential
  6. from keras.layers import LSTM
  7. from keras.layers import Dense
  8. from keras.layers import TimeDistributed
  9. from keras.layers import RepeatVector
  10. # generate a sequence of random integers
  11. def generate_sequence( length, n_unique):
  12. return [randint( 0, n_unique- 1) for _ in range(length)]
  13. # one hot encode sequence
  14. def one_hot_encode( sequence, n_unique):
  15. encoding = list()
  16. for value in sequence:
  17. vector = [ 0 for _ in range(n_unique)]
  18. vector[value] = 1
  19. encoding.append(vector)
  20. return array(encoding)
  21. # decode a one hot encoded string
  22. def one_hot_decode( encoded_seq):
  23. return [argmax(vector) for vector in encoded_seq]
  24. # prepare data for the LSTM
  25. def get_pair( n_in, n_out, cardinality):
  26. # generate random sequence
  27. sequence_in = generate_sequence(n_in, cardinality)
  28. sequence_out = sequence_in[:n_out] + [ 0 for _ in range(n_in-n_out)]
  29. # one hot encode
  30. X = one_hot_encode(sequence_in, cardinality)
  31. y = one_hot_encode(sequence_out, cardinality)
  32. # reshape as 3D
  33. X = X.reshape(( 1, X.shape[ 0], X.shape[ 1]))
  34. y = y.reshape(( 1, y.shape[ 0], y.shape[ 1]))
  35. return X,y
  36. # configure problem
  37. n_features = 50
  38. n_timesteps_in = 5
  39. n_timesteps_out = 2
  40. # define model
  41. model = Sequential()
  42. model.add(LSTM( 150, input_shape=(n_timesteps_in, n_features)))
  43. model.add(RepeatVector(n_timesteps_in))
  44. model.add(LSTM( 150, return_sequences= True))
  45. model.add(TimeDistributed(Dense(n_features, activation= 'softmax')))
  46. model. compile(loss= 'categorical_crossentropy', optimizer= 'adam', metrics=[ 'accuracy'])
  47. # train LSTM
  48. for epoch in range( 5000):
  49. # generate new random sequence
  50. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  51. # fit model for one epoch on this sequence
  52. model.fit(X, y, epochs= 1, verbose= 2)
  53. # evaluate LSTM
  54. total, correct = 100, 0
  55. for _ in range(total):
  56. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  57. yhat = model.predict(X, verbose= 0)
  58. if array_equal(one_hot_decode(y[ 0]), one_hot_decode(yhat[ 0])):
  59. correct += 1
  60. print( 'Accuracy: %.2f%%' % ( float(correct)/ float(total)* 100.0))
  61. # spot check some examples
  62. for _ in range( 10):
  63. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  64. yhat = model.predict(X, verbose= 0)
  65. print( 'Expected:', one_hot_decode(y[ 0]), 'Predicted', one_hot_decode(yhat[ 0]))

         运行这个例子不会花很长时间,也许在 CPU 上几分钟,不需要 GPU。

        注意:由于算法或评估程序的随机性或数值精度的差异,您的结果可能会有所不同。 考虑多次运行该示例并比较平均结果。

我      们可以从样本输出中看到,对于大多数或所有情况,模型确实在输出序列中得到了一个正确的数字,并且只在第二个数字上挣扎。 所有零填充值都被正确预测。


  
  
  1. Expected: [ 47, 0, 0, 0, 0] Predicted [ 47, 47, 0, 0, 0]
  2. Expected: [ 43, 31, 0, 0, 0] Predicted [ 43, 31, 0, 0, 0]
  3. Expected: [ 14, 22, 0, 0, 0] Predicted [ 14, 14, 0, 0, 0]
  4. Expected: [ 39, 31, 0, 0, 0] Predicted [ 39, 39, 0, 0, 0]
  5. Expected: [ 6, 4, 0, 0, 0] Predicted [ 6, 4, 0, 0, 0]
  6. Expected: [ 47, 0, 0, 0, 0] Predicted [ 47, 47, 0, 0, 0]
  7. Expected: [ 39, 33, 0, 0, 0] Predicted [ 39, 39, 0, 0, 0]
  8. Expected: [ 23, 2, 0, 0, 0] Predicted [ 23, 23, 0, 0, 0]
  9. Expected: [ 19, 28, 0, 0, 0] Predicted [ 19, 3, 0, 0, 0]
  10. Expected: [ 32, 33, 0, 0, 0] Predicted [ 32, 32, 0, 0, 0]

自定义 Keras 注意力层

      现在我们需要关注编码器-解码器模型。在撰写本文时,Keras 还没有内置于库中的注意力功能,但即将推出。在 Keras 中正式提供注意力之前,我们可以开发自己的实现或使用现有的第三方实现。为了加快速度,让我们使用现有的第三方实现。

         Datalogue 的实习生 Zafarali Ahmed 在 2017 年的一篇题为“如何在 Keras 中使用注意力可视化您的循环神经网络”和名为“keras-attention”的 GitHub 项目中为 Keras 开发了一个自定义层,为注意力提供支持。

         自定义注意力层称为 AttentionDecoder,可在 GitHub 项目的 custom_recurrents.py 文件中找到。我们可以在项目的 GNU Affero General Public License v3.0 许可下重用此代码。为完整起见,下面列出了自定义图层的副本。将其复制并粘贴到当前工作目录中名为“attention_decoder.py”的一个新的单独文件中。


  
  
  1. import tensorflow as tf
  2. from keras import backend as K
  3. from keras import regularizers, constraints, initializers, activations
  4. from keras.layers.recurrent import Recurrent, _time_distributed_dense
  5. from keras.engine import InputSpec
  6. tfPrint = lambda d, T: tf.Print(input_=T, data=[T, tf.shape(T)], message=d)
  7. class AttentionDecoder( Recurrent):
  8. def __init__( self, units, output_dim,
  9. activation='tanh',
  10. return_probabilities=False,
  11. name='AttentionDecoder',
  12. kernel_initializer='glorot_uniform',
  13. recurrent_initializer='orthogonal',
  14. bias_initializer='zeros',
  15. kernel_regularizer=None,
  16. bias_regularizer=None,
  17. activity_regularizer=None,
  18. kernel_constraint=None,
  19. bias_constraint=None,
  20. **kwargs):
  21. """
  22. Implements an AttentionDecoder that takes in a sequence encoded by an
  23. encoder and outputs the decoded states
  24. :param units: dimension of the hidden state and the attention matrices
  25. :param output_dim: the number of labels in the output space
  26. references:
  27. Bahdanau, Dzmitry, Kyunghyun Cho, and Yoshua Bengio.
  28. "Neural machine translation by jointly learning to align and translate."
  29. arXiv preprint arXiv:1409.0473 (2014).
  30. """
  31. self.units = units
  32. self.output_dim = output_dim
  33. self.return_probabilities = return_probabilities
  34. self.activation = activations.get(activation)
  35. self.kernel_initializer = initializers.get(kernel_initializer)
  36. self.recurrent_initializer = initializers.get(recurrent_initializer)
  37. self.bias_initializer = initializers.get(bias_initializer)
  38. self.kernel_regularizer = regularizers.get(kernel_regularizer)
  39. self.recurrent_regularizer = regularizers.get(kernel_regularizer)
  40. self.bias_regularizer = regularizers.get(bias_regularizer)
  41. self.activity_regularizer = regularizers.get(activity_regularizer)
  42. self.kernel_constraint = constraints.get(kernel_constraint)
  43. self.recurrent_constraint = constraints.get(kernel_constraint)
  44. self.bias_constraint = constraints.get(bias_constraint)
  45. super(AttentionDecoder, self).__init__(**kwargs)
  46. self.name = name
  47. self.return_sequences = True # must return sequences
  48. def build( self, input_shape):
  49. """
  50. See Appendix 2 of Bahdanau 2014, arXiv:1409.0473
  51. for model details that correspond to the matrices here.
  52. """
  53. self.batch_size, self.timesteps, self.input_dim = input_shape
  54. if self.stateful:
  55. super(AttentionDecoder, self).reset_states()
  56. self.states = [ None, None] # y, s
  57. """
  58. Matrices for creating the context vector
  59. """
  60. self.V_a = self.add_weight(shape=(self.units,),
  61. name= 'V_a',
  62. initializer=self.kernel_initializer,
  63. regularizer=self.kernel_regularizer,
  64. constraint=self.kernel_constraint)
  65. self.W_a = self.add_weight(shape=(self.units, self.units),
  66. name= 'W_a',
  67. initializer=self.kernel_initializer,
  68. regularizer=self.kernel_regularizer,
  69. constraint=self.kernel_constraint)
  70. self.U_a = self.add_weight(shape=(self.input_dim, self.units),
  71. name= 'U_a',
  72. initializer=self.kernel_initializer,
  73. regularizer=self.kernel_regularizer,
  74. constraint=self.kernel_constraint)
  75. self.b_a = self.add_weight(shape=(self.units,),
  76. name= 'b_a',
  77. initializer=self.bias_initializer,
  78. regularizer=self.bias_regularizer,
  79. constraint=self.bias_constraint)
  80. """
  81. Matrices for the r (reset) gate
  82. """
  83. self.C_r = self.add_weight(shape=(self.input_dim, self.units),
  84. name= 'C_r',
  85. initializer=self.recurrent_initializer,
  86. regularizer=self.recurrent_regularizer,
  87. constraint=self.recurrent_constraint)
  88. self.U_r = self.add_weight(shape=(self.units, self.units),
  89. name= 'U_r',
  90. initializer=self.recurrent_initializer,
  91. regularizer=self.recurrent_regularizer,
  92. constraint=self.recurrent_constraint)
  93. self.W_r = self.add_weight(shape=(self.output_dim, self.units),
  94. name= 'W_r',
  95. initializer=self.recurrent_initializer,
  96. regularizer=self.recurrent_regularizer,
  97. constraint=self.recurrent_constraint)
  98. self.b_r = self.add_weight(shape=(self.units, ),
  99. name= 'b_r',
  100. initializer=self.bias_initializer,
  101. regularizer=self.bias_regularizer,
  102. constraint=self.bias_constraint)
  103. """
  104. Matrices for the z (update) gate
  105. """
  106. self.C_z = self.add_weight(shape=(self.input_dim, self.units),
  107. name= 'C_z',
  108. initializer=self.recurrent_initializer,
  109. regularizer=self.recurrent_regularizer,
  110. constraint=self.recurrent_constraint)
  111. self.U_z = self.add_weight(shape=(self.units, self.units),
  112. name= 'U_z',
  113. initializer=self.recurrent_initializer,
  114. regularizer=self.recurrent_regularizer,
  115. constraint=self.recurrent_constraint)
  116. self.W_z = self.add_weight(shape=(self.output_dim, self.units),
  117. name= 'W_z',
  118. initializer=self.recurrent_initializer,
  119. regularizer=self.recurrent_regularizer,
  120. constraint=self.recurrent_constraint)
  121. self.b_z = self.add_weight(shape=(self.units, ),
  122. name= 'b_z',
  123. initializer=self.bias_initializer,
  124. regularizer=self.bias_regularizer,
  125. constraint=self.bias_constraint)
  126. """
  127. Matrices for the proposal
  128. """
  129. self.C_p = self.add_weight(shape=(self.input_dim, self.units),
  130. name= 'C_p',
  131. initializer=self.recurrent_initializer,
  132. regularizer=self.recurrent_regularizer,
  133. constraint=self.recurrent_constraint)
  134. self.U_p = self.add_weight(shape=(self.units, self.units),
  135. name= 'U_p',
  136. initializer=self.recurrent_initializer,
  137. regularizer=self.recurrent_regularizer,
  138. constraint=self.recurrent_constraint)
  139. self.W_p = self.add_weight(shape=(self.output_dim, self.units),
  140. name= 'W_p',
  141. initializer=self.recurrent_initializer,
  142. regularizer=self.recurrent_regularizer,
  143. constraint=self.recurrent_constraint)
  144. self.b_p = self.add_weight(shape=(self.units, ),
  145. name= 'b_p',
  146. initializer=self.bias_initializer,
  147. regularizer=self.bias_regularizer,
  148. constraint=self.bias_constraint)
  149. """
  150. Matrices for making the final prediction vector
  151. """
  152. self.C_o = self.add_weight(shape=(self.input_dim, self.output_dim),
  153. name= 'C_o',
  154. initializer=self.recurrent_initializer,
  155. regularizer=self.recurrent_regularizer,
  156. constraint=self.recurrent_constraint)
  157. self.U_o = self.add_weight(shape=(self.units, self.output_dim),
  158. name= 'U_o',
  159. initializer=self.recurrent_initializer,
  160. regularizer=self.recurrent_regularizer,
  161. constraint=self.recurrent_constraint)
  162. self.W_o = self.add_weight(shape=(self.output_dim, self.output_dim),
  163. name= 'W_o',
  164. initializer=self.recurrent_initializer,
  165. regularizer=self.recurrent_regularizer,
  166. constraint=self.recurrent_constraint)
  167. self.b_o = self.add_weight(shape=(self.output_dim, ),
  168. name= 'b_o',
  169. initializer=self.bias_initializer,
  170. regularizer=self.bias_regularizer,
  171. constraint=self.bias_constraint)
  172. # For creating the initial state:
  173. self.W_s = self.add_weight(shape=(self.input_dim, self.units),
  174. name= 'W_s',
  175. initializer=self.recurrent_initializer,
  176. regularizer=self.recurrent_regularizer,
  177. constraint=self.recurrent_constraint)
  178. self.input_spec = [
  179. InputSpec(shape=(self.batch_size, self.timesteps, self.input_dim))]
  180. self.built = True
  181. def call( self, x):
  182. # store the whole sequence so we can "attend" to it at each timestep
  183. self.x_seq = x
  184. # apply the a dense layer over the time dimension of the sequence
  185. # do it here because it doesn't depend on any previous steps
  186. # thefore we can save computation time:
  187. self._uxpb = _time_distributed_dense(self.x_seq, self.U_a, b=self.b_a,
  188. input_dim=self.input_dim,
  189. timesteps=self.timesteps,
  190. output_dim=self.units)
  191. return super(AttentionDecoder, self).call(x)
  192. def get_initial_state( self, inputs):
  193. # apply the matrix on the first time step to get the initial s0.
  194. s0 = activations.tanh(K.dot(inputs[:, 0], self.W_s))
  195. # from keras.layers.recurrent to initialize a vector of (batchsize,
  196. # output_dim)
  197. y0 = K.zeros_like(inputs) # (samples, timesteps, input_dims)
  198. y0 = K. sum(y0, axis=( 1, 2)) # (samples, )
  199. y0 = K.expand_dims(y0) # (samples, 1)
  200. y0 = K.tile(y0, [ 1, self.output_dim])
  201. return [y0, s0]
  202. def step( self, x, states):
  203. ytm, stm = states
  204. # repeat the hidden state to the length of the sequence
  205. _stm = K.repeat(stm, self.timesteps)
  206. # now multiplty the weight matrix with the repeated hidden state
  207. _Wxstm = K.dot(_stm, self.W_a)
  208. # calculate the attention probabilities
  209. # this relates how much other timesteps contributed to this one.
  210. et = K.dot(activations.tanh(_Wxstm + self._uxpb),
  211. K.expand_dims(self.V_a))
  212. at = K.exp(et)
  213. at_sum = K. sum(at, axis= 1)
  214. at_sum_repeated = K.repeat(at_sum, self.timesteps)
  215. at /= at_sum_repeated # vector of size (batchsize, timesteps, 1)
  216. # calculate the context vector
  217. context = K.squeeze(K.batch_dot(at, self.x_seq, axes= 1), axis= 1)
  218. # ~~~> calculate new hidden state
  219. # first calculate the "r" gate:
  220. rt = activations.sigmoid(
  221. K.dot(ytm, self.W_r)
  222. + K.dot(stm, self.U_r)
  223. + K.dot(context, self.C_r)
  224. + self.b_r)
  225. # now calculate the "z" gate
  226. zt = activations.sigmoid(
  227. K.dot(ytm, self.W_z)
  228. + K.dot(stm, self.U_z)
  229. + K.dot(context, self.C_z)
  230. + self.b_z)
  231. # calculate the proposal hidden state:
  232. s_tp = activations.tanh(
  233. K.dot(ytm, self.W_p)
  234. + K.dot((rt * stm), self.U_p)
  235. + K.dot(context, self.C_p)
  236. + self.b_p)
  237. # new hidden state:
  238. st = ( 1-zt)*stm + zt * s_tp
  239. yt = activations.softmax(
  240. K.dot(ytm, self.W_o)
  241. + K.dot(stm, self.U_o)
  242. + K.dot(context, self.C_o)
  243. + self.b_o)
  244. if self.return_probabilities:
  245. return at, [yt, st]
  246. else:
  247. return yt, [yt, st]
  248. def compute_output_shape( self, input_shape):
  249. """
  250. For Keras internal compatability checking
  251. """
  252. if self.return_probabilities:
  253. return ( None, self.timesteps, self.timesteps)
  254. else:
  255. return ( None, self.timesteps, self.output_dim)
  256. def get_config( self):
  257. """
  258. For rebuilding models on load time.
  259. """
  260. config = {
  261. 'output_dim': self.output_dim,
  262. 'units': self.units,
  263. 'return_probabilities': self.return_probabilities
  264. }
  265. base_config = super(AttentionDecoder, self).get_config()
  266. return dict( list(base_config.items()) + list(config.items()))

        我们可以通过如下导入来在我们的项目中使用这个自定义层:

from attention_decoder import AttentionDecoder
  
  

         该层实现了 Bahdanau 等人所描述的注意力。 在他们的论文“通过联合学习对齐和翻译的神经机器翻译”中。该代码在原始帖子中得到了很好的解释,并链接到 LSTM 和注意力方程。这种实现的一个限制是它必须输出与输入序列长度相同的序列,这是编码器-解码器架构旨在克服的特定限制。

        重要的是,层由第二个 LSTM 执行的解码的重复,以及由编码器 - 解码器模型中的密集输出层执行的模型的 softmax 输出,而无需注意。 这大大简化了模型的代码。需要注意的是,自定义层建立在 Keras 中的 Recurrent 层之上,在撰写本文时,该层被标记为遗留代码,并且可能会在某个时候从项目中删除。

编码器-解码器注意力

       现在我们有了一个可以使用的注意力实现,我们可以开发一个编码器-解码器模型,并针对我们人为的序列预测问题进行关注。具有注意力层的模型定义如下。 我们可以看到该层处理编码器-解码器模型本身的一些机制,从而使模型定义更简单。


  
  
  1. # define model
  2. model = Sequential()
  3. model.add(LSTM( 150, input_shape=(n_timesteps_in, n_features), return_sequences= True))
  4. model.add(AttentionDecoder( 150, n_features))
  5. model. compile(loss= 'categorical_crossentropy', optimizer= 'adam', metrics=[ 'accuracy'])

      完整代码如下:


  
  
  1. from random import randint
  2. from numpy import array
  3. from numpy import argmax
  4. from numpy import array_equal
  5. from keras.models import Sequential
  6. from keras.layers import LSTM
  7. from attention_decoder import AttentionDecoder
  8. # generate a sequence of random integers
  9. def generate_sequence( length, n_unique):
  10. return [randint( 0, n_unique- 1) for _ in range(length)]
  11. # one hot encode sequence
  12. def one_hot_encode( sequence, n_unique):
  13. encoding = list()
  14. for value in sequence:
  15. vector = [ 0 for _ in range(n_unique)]
  16. vector[value] = 1
  17. encoding.append(vector)
  18. return array(encoding)
  19. # decode a one hot encoded string
  20. def one_hot_decode( encoded_seq):
  21. return [argmax(vector) for vector in encoded_seq]
  22. # prepare data for the LSTM
  23. def get_pair( n_in, n_out, cardinality):
  24. # generate random sequence
  25. sequence_in = generate_sequence(n_in, cardinality)
  26. sequence_out = sequence_in[:n_out] + [ 0 for _ in range(n_in-n_out)]
  27. # one hot encode
  28. X = one_hot_encode(sequence_in, cardinality)
  29. y = one_hot_encode(sequence_out, cardinality)
  30. # reshape as 3D
  31. X = X.reshape(( 1, X.shape[ 0], X.shape[ 1]))
  32. y = y.reshape(( 1, y.shape[ 0], y.shape[ 1]))
  33. return X,y
  34. # configure problem
  35. n_features = 50
  36. n_timesteps_in = 5
  37. n_timesteps_out = 2
  38. # define model
  39. model = Sequential()
  40. model.add(LSTM( 150, input_shape=(n_timesteps_in, n_features), return_sequences= True))
  41. model.add(AttentionDecoder( 150, n_features))
  42. model. compile(loss= 'categorical_crossentropy', optimizer= 'adam', metrics=[ 'accuracy'])
  43. # train LSTM
  44. for epoch in range( 5000):
  45. # generate new random sequence
  46. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  47. # fit model for one epoch on this sequence
  48. model.fit(X, y, epochs= 1, verbose= 2)
  49. # evaluate LSTM
  50. total, correct = 100, 0
  51. for _ in range(total):
  52. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  53. yhat = model.predict(X, verbose= 0)
  54. if array_equal(one_hot_decode(y[ 0]), one_hot_decode(yhat[ 0])):
  55. correct += 1
  56. print( 'Accuracy: %.2f%%' % ( float(correct)/ float(total)* 100.0))
  57. # spot check some examples
  58. for _ in range( 10):
  59. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  60. yhat = model.predict(X, verbose= 0)
  61. print( 'Expected:', one_hot_decode(y[ 0]), 'Predicted', one_hot_decode(yhat[ 0]))

       运行该示例会在 100 个随机生成的输入-输出对上打印模型的输出。

        注意:由于算法或评估程序的随机性或数值精度的差异,您的结果可能会有所不同。 考虑多次运行该示例并比较平均结果。

         在相同的资源和相同的训练量下,有注意力的模型表现要好得多。

Accuracy: 95.00%
  
  

       抽查一些样本输出和预测序列,我们可以看到很少的错误,即使在前两个元素中存在零值的情况下也是如此。


  
  
  1. Expected: [ 48, 47, 0, 0, 0] Predicted [ 48, 47, 0, 0, 0]
  2. Expected: [ 7, 46, 0, 0, 0] Predicted [ 7, 46, 0, 0, 0]
  3. Expected: [ 32, 30, 0, 0, 0] Predicted [ 32, 2, 0, 0, 0]
  4. Expected: [ 3, 25, 0, 0, 0] Predicted [ 3, 25, 0, 0, 0]
  5. Expected: [ 45, 4, 0, 0, 0] Predicted [ 45, 4, 0, 0, 0]
  6. Expected: [ 49, 9, 0, 0, 0] Predicted [ 49, 9, 0, 0, 0]
  7. Expected: [ 22, 23, 0, 0, 0] Predicted [ 22, 23, 0, 0, 0]
  8. Expected: [ 29, 36, 0, 0, 0] Predicted [ 29, 36, 0, 0, 0]
  9. Expected: [ 0, 29, 0, 0, 0] Predicted [ 0, 29, 0, 0, 0]
  10. Expected: [ 11, 26, 0, 0, 0] Predicted [ 11, 26, 0, 0, 0]

模型比较

        尽管我们通过注意力从模型中获得了更好的结果,但结果是从每个模型的单次运行中报告的。在这种情况下,我们通过多次重复评估每个模型并报告这些运行的平均性能来寻求更可靠的发现。 有关这种评估神经网络模型的稳健方法的更多信息,请参阅帖子:

                                          如何评估深度学习模型的技能
      我们可以定义一个函数来创建每种类型的模型,如下所示。


  
  
  1. # define the encoder-decoder model
  2. def baseline_model( n_timesteps_in, n_features):
  3. model = Sequential()
  4. model.add(LSTM( 150, input_shape=(n_timesteps_in, n_features)))
  5. model.add(RepeatVector(n_timesteps_in))
  6. model.add(LSTM( 150, return_sequences= True))
  7. model.add(TimeDistributed(Dense(n_features, activation= 'softmax')))
  8. model. compile(loss= 'categorical_crossentropy', optimizer= 'adam', metrics=[ 'accuracy'])
  9. return model
  10. # define the encoder-decoder with attention model
  11. def attention_model( n_timesteps_in, n_features):
  12. model = Sequential()
  13. model.add(LSTM( 150, input_shape=(n_timesteps_in, n_features), return_sequences= True))
  14. model.add(AttentionDecoder( 150, n_features))
  15. model. compile(loss= 'categorical_crossentropy', optimizer= 'adam', metrics=[ 'accuracy'])
  16. return model

        然后我们可以定义一个函数来拟合和评估拟合模型的准确性并返回准确性分数。


  
  
  1. # train and evaluate a model, return accuracy
  2. def train_evaluate_model( model, n_timesteps_in, n_timesteps_out, n_features):
  3. # train LSTM
  4. for epoch in range( 5000):
  5. # generate new random sequence
  6. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  7. # fit model for one epoch on this sequence
  8. model.fit(X, y, epochs= 1, verbose= 0)
  9. # evaluate LSTM
  10. total, correct = 100, 0
  11. for _ in range(total):
  12. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  13. yhat = model.predict(X, verbose= 0)
  14. if array_equal(one_hot_decode(y[ 0]), one_hot_decode(yhat[ 0])):
  15. correct += 1
  16. return float(correct)/ float(total)* 100.0

         将这些放在一起,我们可以多次重复创建、训练和评估每种类型模型的过程,并报告重复的平均准确度。 为了减少运行时间,我们将每个模型评估重复 10 次,但如果您有资源,您可以将次数增加到 30 或 100 次。下面列出了完整的示例。


  
  
  1. from random import randint
  2. from numpy import array
  3. from numpy import argmax
  4. from numpy import array_equal
  5. from keras.models import Sequential
  6. from keras.layers import LSTM
  7. from keras.layers import Dense
  8. from keras.layers import TimeDistributed
  9. from keras.layers import RepeatVector
  10. from attention_decoder import AttentionDecoder
  11. # generate a sequence of random integers
  12. def generate_sequence( length, n_unique):
  13. return [randint( 0, n_unique- 1) for _ in range(length)]
  14. # one hot encode sequence
  15. def one_hot_encode( sequence, n_unique):
  16. encoding = list()
  17. for value in sequence:
  18. vector = [ 0 for _ in range(n_unique)]
  19. vector[value] = 1
  20. encoding.append(vector)
  21. return array(encoding)
  22. # decode a one hot encoded string
  23. def one_hot_decode( encoded_seq):
  24. return [argmax(vector) for vector in encoded_seq]
  25. # prepare data for the LSTM
  26. def get_pair( n_in, n_out, cardinality):
  27. # generate random sequence
  28. sequence_in = generate_sequence(n_in, cardinality)
  29. sequence_out = sequence_in[:n_out] + [ 0 for _ in range(n_in-n_out)]
  30. # one hot encode
  31. X = one_hot_encode(sequence_in, cardinality)
  32. y = one_hot_encode(sequence_out, cardinality)
  33. # reshape as 3D
  34. X = X.reshape(( 1, X.shape[ 0], X.shape[ 1]))
  35. y = y.reshape(( 1, y.shape[ 0], y.shape[ 1]))
  36. return X,y
  37. # define the encoder-decoder model
  38. def baseline_model( n_timesteps_in, n_features):
  39. model = Sequential()
  40. model.add(LSTM( 150, input_shape=(n_timesteps_in, n_features)))
  41. model.add(RepeatVector(n_timesteps_in))
  42. model.add(LSTM( 150, return_sequences= True))
  43. model.add(TimeDistributed(Dense(n_features, activation= 'softmax')))
  44. model. compile(loss= 'categorical_crossentropy', optimizer= 'adam', metrics=[ 'accuracy'])
  45. return model
  46. # define the encoder-decoder with attention model
  47. def attention_model( n_timesteps_in, n_features):
  48. model = Sequential()
  49. model.add(LSTM( 150, input_shape=(n_timesteps_in, n_features), return_sequences= True))
  50. model.add(AttentionDecoder( 150, n_features))
  51. model. compile(loss= 'categorical_crossentropy', optimizer= 'adam', metrics=[ 'accuracy'])
  52. return model
  53. # train and evaluate a model, return accuracy
  54. def train_evaluate_model( model, n_timesteps_in, n_timesteps_out, n_features):
  55. # train LSTM
  56. for epoch in range( 5000):
  57. # generate new random sequence
  58. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  59. # fit model for one epoch on this sequence
  60. model.fit(X, y, epochs= 1, verbose= 0)
  61. # evaluate LSTM
  62. total, correct = 100, 0
  63. for _ in range(total):
  64. X,y = get_pair(n_timesteps_in, n_timesteps_out, n_features)
  65. yhat = model.predict(X, verbose= 0)
  66. if array_equal(one_hot_decode(y[ 0]), one_hot_decode(yhat[ 0])):
  67. correct += 1
  68. return float(correct)/ float(total)* 100.0
  69. # configure problem
  70. n_features = 50
  71. n_timesteps_in = 5
  72. n_timesteps_out = 2
  73. n_repeats = 10
  74. # evaluate encoder-decoder model
  75. print( 'Encoder-Decoder Model')
  76. results = list()
  77. for _ in range(n_repeats):
  78. model = baseline_model(n_timesteps_in, n_features)
  79. accuracy = train_evaluate_model(model, n_timesteps_in, n_timesteps_out, n_features)
  80. results.append(accuracy)
  81. print(accuracy)
  82. print( 'Mean Accuracy: %.2f%%' % ( sum(results)/ float(n_repeats)))
  83. # evaluate encoder-decoder with attention model
  84. print( 'Encoder-Decoder With Attention Model')
  85. results = list()
  86. for _ in range(n_repeats):
  87. model = attention_model(n_timesteps_in, n_features)
  88. accuracy = train_evaluate_model(model, n_timesteps_in, n_timesteps_out, n_features)
  89. results.append(accuracy)
  90. print(accuracy)
  91. print( 'Mean Accuracy: %.2f%%' % ( sum(results)/ float(n_repeats)))

        注意:由于算法或评估程序的随机性或数值精度的差异,您的结果可能会有所不同。 考虑多次运行该示例并比较平均结果。

       运行此示例会打印每个模型重复的准确度,让您了解运行的进度。


  
  
  1. Encoder-Decoder Model
  2. 20.0
  3. 23.0
  4. 23.0
  5. 18.0
  6. 28.000000000000004
  7. 28.999999999999996
  8. 23.0
  9. 26.0
  10. 21.0
  11. 20.0
  12. Mean Accuracy: 23.10%
  13. Encoder-Decoder With Attention Model
  14. 98.0
  15. 91.0
  16. 94.0
  17. 93.0
  18. 96.0
  19. 99.0
  20. 97.0
  21. 94.0
  22. 99.0
  23. 96.0
  24. Mean Accuracy: 95.70%

       我们可以看到,即使平均超过 10 次运行,注意力模型仍然比没有注意力的编码器-解码器模型表现出更好的性能,分别为 23.10% 和 95.70%。这种评估的一个很好的扩展是捕获每个模型每个时期的模型损失,取平均值,并比较有和没有注意的架构的损失如何随时间变化。我希望这个轨迹会显示注意力比非注意力模型更快、更快地获得更好的技能,进一步突出了这种方法的好处。

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

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

相关文章

大数据Doris(八):启动FE步骤

文章目录 启动FE步骤 一、配置环境变量 二、​​​​​​​创建doris-mate

变分自动编码器 (VAE)02/2 PyTorch 教程

一、说明 在自动编码器中,来自输入数据的信息被映射到固定的潜在表示中。当我们旨在训练模型以生成确定性预测时,这特别有用。相比之下,变分自动编码器(VAE)将输入数据转换为变分表示向量(顾名思义&#xf…

气象台卫星监测vr交互教学增强学生的学习兴趣和动力

对地观测是以地球为研究对象,依托卫星、飞船等光电仪器,进行各种探测活动,其核心是遥感技术,因此为了让遥感专业学员能提前熟悉对地观测规则、流程、方法及注意事项,借助VR虚拟现实制作的三维仿真场景,能让…

全新彩虹商城时光模板知识付费系统源码+内有5000多商品+易支付源码

源码简介: 全新彩虹商城时光模板知识付费系统源码,这是最新的彩虹知识付费商城系统,具备众多强大且实用的功能。首先,它支持二级分类和多级分销,使得商品分类更为清晰,销售网络更具扩展性。 其次&#xf…

机器人轨迹规划算法的研究现状

近年来,随着机器人技术的迅速发展,机器人在工业、医疗、军事等领域的应用越来越广泛。机器人轨迹规划是机器人控制的重要环节之一,它决定了机器人在执行任务时的运动轨迹,直接影响机器人的精度、速度和稳定性。因此,机…

【PCIE720】基于PCIe总线架构的高性能计算(HPC)硬件加速卡

PCIE720是一款基于PCI Express总线架构的高性能计算(HPC)硬件加速卡,板卡采用Xilinx的高性能28nm 7系列FPGA作为运算节点,在资源、接口以及时钟的优化,为高性能计算提供卓越的硬件加速性能。板卡一共具有5个FPGA处理节…

代码混淆界面介绍

代码混淆界面介绍 代码混淆功能包括oc,swift,类和函数设置区域。其他flutter,混合开发的最终都会转未oc活着swift的的二进制,所以没有其他语言的设置。 代码混淆功能分顶部的显示控制区域:显示方式,风险等…

python 深度学习 解决遇到的报错问题6

目录 一、解决报错HTTPSConnectionPool(hosthuggingface.co, port443): Max retries exceeded with url: /bert-base-uncased/resolve/main/vocab.txt (Caused by ConnectTimeoutError(, Connection to huggingface.co 如何从huggingface官网下载模型 二、nx.draw if cf._ax…

jupyter 切换虚拟环境

当前只有两个环kernel 我已经创建了很多虚拟环境,如何在notebook中使用这些虚拟环境呢?请看下面 比如说我要添加nlp 这个虚拟环境到notebook中 1. 切换到nlp环境 2. 安装如下模块 pip install ipykernel 3. 执行如下命令 python -m ipykernel install …

VS2019如何显示和去除控制台页面

这是控制台页面: 方法: 选中目标项目,右键--->属性--->配置属性--->链接器--->系统--->子系统--->(窗口/控制台)

地级市HVV | 未授权访问合集

在网站前后端分离盛行下,将大部分权限控制交给前端,导致js中隐藏未授权或者可绕过的前端鉴权。前后端分离的好处是提高开发效率,同时防止黑客更直接的对服务器造成危害,但权限控制的工作量全部交给前端会导致大量页面未授权或者后…

面试经典 150 题 1 —(双指针)— 125. 验证回文串

125. 验证回文串 方法一 class Solution { public:bool isPalindrome(string s) {string newStr "";for(int fast 0; fast < s.size(); fast){if(isalnum(s[fast]))){newStr tolower(s[fast]);}}string tmp newStr;reverse(tmp.begin(), tmp.end());if(strcm…

【计算机网络】TCP协议与UDP协议详解

文章目录 一、传输层 1、1 再次理解传输层 1、2 再次理解端口号 1、2、1 端口号范围划分 1、2、2 认识知名端口号 1、3 网络常用指令netstat 与 pidof 二、UDP协议 2、1 UDP协议的报文 2、2 UDP的特点 2、3 UDP的缓冲区 三、TCP协议 3、1 TCP协议的报文 3、2 确认应答 3、3 按…

计算机毕业设计选什么题目好?springboot 个人健康信息管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

基于SpringBoot的抗疫物资管理系统

目录 前言 一、技术栈 二、系统功能介绍 用户管理 公告信息管理 轮播图管理 物质分类管理 物质信息管理 物质入库管理 物质出库管理 个人信息 前台首页功能实现 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着现在网络的快速发展&#xff0c;网…

简单制作RT-Thread Studio的CH32V303的BSP支持包

简单制作RT-Thread Studio的CH32V303的BSP支持包 开原仓库链接在此&#xff1a;RTT_Studio_BSP_CH32V303 参考 CH32V307V-R1&#xff08;V1.0.8&#xff09;的 BSP&#xff0c;更新了外设驱动库之类的。 可以在 RT-Thread SDK 管理器中导入离线资源包&#xff0c;可以新建 RT…

CSS学习基础知识

CSS学习笔记 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width,…

Linux Centos7 下使用yum安装的nginx平滑升级

1. 查看当前nginx版本 1nginx -v2. 查看centos版本 1cat /etc/redhat-release3. 创建一个新的文件nginx.repo&#xff0c;其中第三行的7是因为我的centos版本是7点多的&#xff0c;你看自己是多少就改多少 1vim /etc/yum.repos.d/nginx.repo23[nginx]4namenginx repo 5baseu…

基于 ACK Fluid 的混合云优化数据访问(三):加速第三方存储的读访问,降本增效并行

作者&#xff1a;车漾 前文回顾&#xff1a; 本系列将介绍如何基于 ACK Fluid 支持和优化混合云的数据访问场景&#xff0c;相关文章请参考&#xff1a; 基于 ACK Fluid 的混合云优化数据访问&#xff08;一&#xff09;&#xff1a;场景与架构 基于 ACK Fluid 的混合云优化…

react antd table表格点击一行选中数据的方法

一、前言 antd的table&#xff0c;默认是点击左边的单选/复选按钮&#xff0c;才能选中一行数据&#xff1b; 现在想实现点击右边的部分&#xff0c;也可以触发操作选中这行数据。 可以使用onRow实现&#xff0c;样例如下。 二、代码 1.表格样式部分 //表格table样式部分{…