复现反向传播BP算法:手动实现与Sklearn MLP对比分析【复现】

2024-08-29 16:20

本文主要是介绍复现反向传播BP算法:手动实现与Sklearn MLP对比分析【复现】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

完整代码

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer
from sklearn.metrics import accuracy_score# 生成月亮型二分类数据集
X, Y = make_moons(n_samples=500, noise=0.2, random_state=42)
Y = Y.reshape(-1, 1)  # 将 Y 转换为列向量# 数据集拆分为训练集和测试集
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=42)# 初始化参数
input_size = 2
hidden_size = 4
output_size = 1# 可视化数据集
plt.figure(figsize=(8, 6))
plt.scatter(X[:, 0], X[:, 1], c=Y.ravel(), cmap=plt.cm.Spectral)
plt.title("Moon Dataset")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.show()np.random.seed(42)
W1 = np.random.randn(input_size , hidden_size)
b1 = np.zeros((1,hidden_size))
W2 = np.random.randn(hidden_size , hidden_size)
b2 = np.zeros((1 , hidden_size))
W3 = np.random.randn(hidden_size ,output_size )
b3 = np.zeros((1, output_size))def sigmod(X):return 1 / (1 + np.exp(-X))def sigmoid_derivative(x):return sigmod(x) * ( 1 - sigmod(x))def forward(X, W1, b1, W2, b2 , W3 , b3):# 隐藏层Z1 = np.dot(X,W1) + b1A1 = sigmod(Z1)print(f"输入-隐藏层;A1.shape:{A1.shape}")# 隐藏层Z2 = np.dot(A1 , W2) + b2A2 = sigmod(Z2)print(f"隐藏-隐藏层;A2.shape:{A2.shape}")Z3 = np.dot(A2 , W3) + b3A3 = sigmod(Z3)print(f"隐藏-输出层;A3.shape:{A3.shape}")return A1 , A2 , A3# X = np.array([[0.5 , 0.1]])
print(f"X.shape:{X.shape}")A1 , A2 , A3 = forward(X,W1, b1, W2, b2 , W3 , b3)# print("A1 (Hidden Layer Output):", A1)
# print("A2 (Output Layer Output):", A2)def mean_loss(Y , A3):m = Y.shape[0]print(f"Y.shape[0]:{Y.shape[0]}")loss = np.sum( (Y - A3) ** 2 ) / mreturn lossdef compute_loss(Y , A3):m = Y.shape[0]print(f"Y.shape[0]:{Y.shape[0]}")loss = -np.sum(Y * np.log(A3) + (1 - Y) * np.log(1 - A3 )) / mreturn lossdef backward(X, Y, A1, A2, A3, W1, W2, W3):m = Y.shape[0]dA3 = -(np.divide(Y, A3) - np.divide(1-Y, 1-A3))dZ3 = dA3 * sigmoid_derivative(A3)dW3 = np.dot(A2.T, dZ3) / mdB3 = np.sum(dZ3, axis=0, keepdims=True) / mdA2 = np.dot(dZ3, W3.T)dZ2 = dA2 * sigmoid_derivative(A2)dW2 = np.dot(A1.T, dZ2) / mdB2 = np.sum(dZ2, axis=0, keepdims=True) / mdA1 = np.dot(dZ2, W2.T)dZ1 = dA1 * sigmoid_derivative(A1)dW1 = np.dot(X.T, dZ1) / mdB1 = np.sum(dZ1, axis=0, keepdims=True) / mreturn dW1, dB1, dW2, dB2, dW3, dB3def update_parameters(W1, b1, W2, b2, W3, b3, dW1, db1, dW2, db2, dW3, db3, learning_rate=0.01):W1 = W1 - learning_rate * dW1b1 = b1 - learning_rate * db1W2 = W2 - learning_rate * dW2b2 = b2 - learning_rate * db2W3 = W3 - learning_rate * dW3b3 = b3 - learning_rate * db3return W1, b1, W2, b2, W3, b3def train(X, Y, W1, b1, W2, b2, W3, b3, learning_rate=0.01, num_iterations=100):loss_history = []m = X.shape[0]for i in range(num_iterations):# 随机打乱数据permutation = np.random.permutation(m)X_shuffled = X[permutation, :]Y_shuffled = Y[permutation, :]# 遍历每一个样本for j in range(m):# 获取单个样本X_sample = X_shuffled[j:j + 1]Y_sample = Y_shuffled[j:j + 1]# 前向传播A1, A2, A3 = forward(X_sample, W1, b1, W2, b2, W3, b3)# 计算损失(可选,仅用于监控)loss = compute_loss(Y_sample, A3)# 反向传播dW1, db1, dW2, db2, dW3, db3 = backward(X_sample, Y_sample, A1, A2, A3, W1, W2, W3)# 更新参数W1, b1, W2, b2, W3, b3 = update_parameters(W1, b1, W2, b2, W3, b3, dW1, db1, dW2, db2, dW3, db3,learning_rate)# 计算并存储整个训练集的损失值(用于每个epoch的监控)A1, A2, A3 = forward(X, W1, b1, W2, b2, W3, b3)loss = compute_loss(Y, A3)loss_history.append(loss)if i % 100 == 0:print(f"Iteration {i}, Loss: {loss:.4f}")return W1, b1, W2, b2, W3, b3, loss_historyW1, b1, W2, b2, W3, b3, loss_history = train(X_train, Y_train, W1, b1, W2, b2, W3, b3, learning_rate=0.01, num_iterations=100)# 绘制损失函数随迭代次数的变化曲线
plt.figure(figsize=(8, 6))
plt.plot(loss_history)
plt.title("Loss over Iterations")
plt.xlabel("Iterations")
plt.ylabel("Loss")
plt.show()def predict(X, W1, b1, W2, b2, W3, b3):_, _, A3 = forward(X, W1, b1, W2, b2, W3, b3)predictions = (A3 > 0.5).astype(int)return predictions# 使用训练后的模型进行预测
train_predictions = predict(X_train, W1, b1, W2, b2, W3, b3)
test_predictions = predict(X_test, W1, b1, W2, b2, W3, b3)# 计算准确度
train_accuracy = accuracy_score(Y_train, train_predictions)
test_accuracy = accuracy_score(Y_test, test_predictions)from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score# 创建与手动实现相同结构的 MLPClassifier
# 设置两个隐藏层,每层有4个神经元,激活函数为'sigmoid',对应 'logistic'
model = MLPClassifier(hidden_layer_sizes=(4,),activation='logistic',learning_rate='constant',solver='sgd',learning_rate_init=0.01,max_iter=1000,random_state=42)# 训练模型
model.fit(X_train, Y_train.ravel())  # 注意:sklearn 的 MLPClassifier 期望 Y 是一维数组,因此需要使用 .ravel()# 预测
train_predictions_sklearn = model.predict(X_train)
test_predictions_sklearn = model.predict(X_test)# 计算准确度
train_accuracy_sklearn = accuracy_score(Y_train, train_predictions_sklearn)
test_accuracy_sklearn = accuracy_score(Y_test, test_predictions_sklearn)# 输出对比结果
print(f"Training Accuracy (Manual): {train_accuracy:.4f}")
print(f"Testing Accuracy (Manual): {test_accuracy:.4f}")
print(f"Training Accuracy (Sklearn MLP): {train_accuracy_sklearn:.4f}")
print(f"Testing Accuracy (Sklearn MLP): {test_accuracy_sklearn:.4f}")

我们一步一步来实现反向传播算法(Backpropagation,简称BP)。这个过程将分成多个步骤,每一步都会解释相应的原理和代码实现。

第一步:理解BP算法的目标

反向传播算法用于训练神经网络,通过最小化损失函数来调整网络的权重。主要过程包括:

  1. 前向传播:计算输入数据通过网络的输出。
  2. 计算损失:衡量预测输出与真实标签之间的差异。
  3. 反向传播:计算损失函数对每个权重的梯度,并利用这些梯度更新权重。

计划的步骤

  1. 构建网络结构:我们首先需要定义一个简单的前馈神经网络(Feedforward Neural Network)。
  2. 前向传播:实现前向传播,计算每一层的输出。
  3. 计算损失函数:选择一个损失函数,比如均方误差(Mean Squared Error, MSE),并计算损失值。
  4. 反向传播计算梯度:计算损失对每个权重的梯度(即偏导数)。
  5. 更新权重:使用梯度下降法更新网络中的权重。

网络结构定义

我们从最简单的网络结构开始:一个输入层、两个隐藏层和一个输出层。假设我们要解决的是一个二分类问题,输入层有2个节点,隐藏层有4个节点,输出层有1个节点。激活函数我们先使用Sigmoid;网络结构如下所示
在这里插入图片描述

这里需要最好还是使用两个隐藏层,因为较好的提取对应的特征

代码实现:

我们先定义网络的权重和偏置:

import numpy as np# 网络结构
input_size = 2  # 输入层节点数
hidden_size = 4  # 隐藏层节点数
output_size = 1  # 输出层节点数# 初始化权重和偏置
np.random.seed(0)
W1 = np.random.randn(input_size, hidden_size)  # 输入层到隐藏层的权重
b1 = np.zeros((1, hidden_size))  # 隐藏层的偏置
W2 = np.random.randn(hidden_size, output_size)  # 隐藏层到隐藏层的权重
b2 = np.zeros((1, output_size))  # 隐藏层的偏置
W3 = np.random.randn(hidden_size ,output_size ) # 隐藏层到输出层的权重
b3 = np.zeros((1, output_size)) # 输出层的偏置

解释

  1. W1:表示输入层到隐藏层的权重矩阵,大小为(input_size, hidden_size)
  2. b1:隐藏层的偏置向量,大小为(1, hidden_size)
  3. W2:隐藏层到隐藏层的权重矩阵,大小为(hidden_size, hidden_size)
  4. b1:隐藏层的偏置向量,大小为(1, hidden_size)
  5. W3:隐藏层到输出层的权重矩阵,大小为(hidden_size, output_size)
  6. b3:输出层的偏置向量,大小为(1, output_size)

这些参数是通过随机初始化的,并且会在反向传播过程中被更新。

第二步:前向传播(继续)

我们已经定义了 Z1A1 计算隐藏层的激活值,现在继续定义第二层隐藏层的激活值 Z2A2,以及输出层的激活值 Z3A3。具体代码如下:

def sigmoid(x):return 1 / (1 + np.exp(-x))def forward_propagation(X):# 第一隐藏层Z1 = np.dot(X, W1) + b1A1 = sigmoid(Z1)# 第二隐藏层Z2 = np.dot(A1, W2) + b2A2 = sigmoid(Z2)# 输出层Z3 = np.dot(A2, W3) + b3A3 = sigmoid(Z3)return A1, A2, A3

解释

  • Z1A1:表示输入层到第一隐藏层的线性组合结果和激活值。
  • Z2A2:表示第一隐藏层到第二隐藏层的线性组合结果和激活值。
  • Z3A3:表示第二隐藏层到输出层的线性组合结果和激活值。

这样,通过调用 forward_propagation 函数,我们可以从输入数据 X 中得到每一层的激活值,最终的 A3 是网络的预测输出。

第三步:计算损失函数

在这个步骤中,我们选择一个适合二分类问题的损失函数。常用的选择是二分类交叉熵损失(Binary Cross-Entropy Loss)。

公式

二分类交叉熵损失的公式为:
L ( Y , A 3 ) = − 1 m ∑ i = 1 m [ Y ( i ) log ⁡ ( A 3 ( i ) ) + ( 1 − Y ( i ) ) log ⁡ ( 1 − A 3 ( i ) ) ] L(Y, A3) = -\frac{1}{m} \sum_{i=1}^{m} \left[Y^{(i)} \log(A3^{(i)}) + (1 - Y^{(i)}) \log(1 - A3^{(i)})\right] L(Y,A3)=m1i=1m[Y(i)log(A3(i))+(1Y(i))log(1A3(i))]
其中:

  • (Y) 是真实标签,(A3) 是预测输出。
  • (m) 是样本数量。

代码实现

def compute_loss(Y, A3):m = Y.shape[0]loss = -np.sum(Y * np.log(A3) + (1 - Y) * np.log(1 - A3)) / mreturn loss

解释

  • YA3:分别是真实标签和预测输出。
  • loss:表示整体的损失值,反映了模型的预测能力。

第四步:反向传播计算梯度

在反向传播过程中,我们计算损失函数相对于每个参数的梯度,然后利用这些梯度更新权重。反向传播使用链式法则(Chain Rule)来计算梯度。

1. 输出层到第二隐藏层的梯度计算

从输出层开始,我们已经知道:

d Z 3 = A 3 − Y dZ3 = A3 - Y dZ3=A3Y

接下来,我们需要计算损失函数相对于输出层权重 (W3) 和偏置 (b3) 的梯度。具体的计算步骤如下:

  1. 计算 (W3) 的梯度:
    d W 3 = ∂ L ∂ W 3 = 1 m ⋅ A 2 T ⋅ d Z 3 dW3 = \frac{\partial L}{\partial W3} = \frac{1}{m} \cdot A2^T \cdot dZ3 dW3=W3L=m1A2TdZ3
    这里, A 2 T A2^T A2T 是第二隐藏层的激活值的转置。
  2. 计算 (b3) 的梯度:
    d b 3 = ∂ L ∂ b 3 = 1 m ⋅ ∑ d Z 3 db3 = \frac{\partial L}{\partial b3} = \frac{1}{m} \cdot \sum dZ3 db3=b3L=m1dZ3
    这里,我们对所有样本的梯度进行求和,并取平均值。

2. 第二隐藏层到第一隐藏层的梯度计算

接着,我们计算第二隐藏层的梯度 d Z 2 dZ2 dZ2

  1. 计算 (dA2)(对第二隐藏层激活值的导数):
    d A 2 = d Z 3 ⋅ W 3 T dA2 = dZ3 \cdot W3^T dA2=dZ3W3T
    这里,(W3^T) 是输出层权重的转置。
  2. 计算 (dZ2)(对第二隐藏层线性组合的导数):
    d Z 2 = d A 2 ⋅ sigmoid_derivative ( Z 2 ) dZ2 = dA2 \cdot \text{sigmoid\_derivative}(Z2) dZ2=dA2sigmoid_derivative(Z2)
    其中, sigmoid_derivative ( Z 2 ) \text{sigmoid\_derivative}(Z2) sigmoid_derivative(Z2) 是对 Z 2 Z2 Z2 进行激活函数求导的结果。
  3. 计算 (W2) 的梯度:
    d W 2 = ∂ L ∂ W 2 = 1 m ⋅ A 1 T ⋅ d Z 2 dW2 = \frac{\partial L}{\partial W2} = \frac{1}{m} \cdot A1^T \cdot dZ2 dW2=W2L=m1A1TdZ2
    这里, A 1 T A1^T A1T 是第一隐藏层的激活值的转置。
  4. 计算 (b2) 的梯度:
    d b 2 = ∂ L ∂ b 2 = 1 m ⋅ ∑ d Z 2 db2 = \frac{\partial L}{\partial b2} = \frac{1}{m} \cdot \sum dZ2 db2=b2L=m1dZ2

3. 第一隐藏层到输入层的梯度计算

最后,我们计算第一隐藏层的梯度 d Z 1 dZ1 dZ1

  1. 计算 (dA1)(对第一隐藏层激活值的导数):
    d A 1 = d Z 2 ⋅ W 2 T dA1 = dZ2 \cdot W2^T dA1=dZ2W2T
    这里, W 2 T W2^T W2T 是第二隐藏层权重的转置。
  2. 计算 (dZ1)(对第一隐藏层线性组合的导数):
    d Z 1 = d A 1 ⋅ sigmoid_derivative ( Z 1 ) dZ1 = dA1 \cdot \text{sigmoid\_derivative}(Z1) dZ1=dA1sigmoid_derivative(Z1)
  3. 计算 (W1) 的梯度:
    d W 1 = ∂ L ∂ W 1 = 1 m ⋅ X T ⋅ d Z 1 dW1 = \frac{\partial L}{\partial W1} = \frac{1}{m} \cdot X^T \cdot dZ1 dW1=W1L=m1XTdZ1
    这里, X T X^T XT 是输入数据的转置。
  4. 计算 (b1) 的梯度:
    d b 1 = ∂ L ∂ b 1 = 1 m ⋅ ∑ d Z 1 db1 = \frac{\partial L}{\partial b1} = \frac{1}{m} \cdot \sum dZ1 db1=b1L=m1dZ1

代码实现

def backward_propagation(X, Y, A1, A2, A3):m = X.shape[0]# 输出层梯度dZ3 = A3 - YdW3 = np.dot(A2.T, dZ3) / mdb3 = np.sum(dZ3, axis=0, keepdims=True) / m# 第二隐藏层梯度dA2 = np.dot(dZ3, W3.T)dZ2 = dA2 * A2 * (1 - A2)dW2 = np.dot(A1.T, dZ2) / mdb2 = np.sum(dZ2, axis=0, keepdims=True) / m# 第一隐藏层梯度dA1 = np.dot(dZ2, W2.T)dZ1 = dA1 * A1 * (1 - A1)dW1 = np.dot(X.T, dZ1) / mdb1 = np.sum(dZ1, axis=0, keepdims=True) / mreturn dW1, db1, dW2, db2, dW3, db3

解释

  • dZ3dZ2dZ1:分别是输出层和隐藏层的梯度。
  • dW3dW2dW1:分别是输出层和隐藏层的权重梯度。
  • db3db2db1:分别是输出层和隐藏层的偏置梯度。

这些梯度将用于更新网络的参数,以减少损失函数的值。

第五步:更新权重

利用梯度下降法,我们使用上一步计算出的梯度更新每个权重和偏置。

公式

权重更新公式为:
W = W − α ⋅ d W W = W - \alpha \cdot dW W=WαdW
b = b − α ⋅ d b b = b - \alpha \cdot db b=bαdb
其中, α \alpha α 是学习率,即每次调整的步长。

代码实现

def update_parameters(W1, b1, W2, b2, W3, b3, dW1, db1, dW2, db2, dW3, db3, learning_rate=0.01):W1 -= learning_rate * dW1b1 -= learning_rate * db1W2 -= learning_rate * dW2b2 -= learning_rate * db2W3 -= learning_rate * dW3b3 -= learning_rate * db3return W1, b1, W2, b2, W3, b3

解释

  • 这里的 learning_rate 是一个超参数,用于控制每次更新的步长。

第六步:训练模型

将前向传播、损失计算、反向传播和权重更新组合起来,我们就可以训练整个模型了。

代码实现

def train(X, Y, W1, b1, W2, b2, W3, b3, learning_rate=0.01, num_iterations=100):loss_history = []for i in range(num_iterations):# 前向传播A1, A2, A3 = forward_propagation(X)# 计算损失loss = compute_loss(Y, A3)loss_history.append(loss)# 反向传播dW1, db1, dW2, db2, dW3, db3 = backward_propagation(X, Y, A1, A2, A3)# 更新参数W1, b1, W2, b2, W3, b3 = update_parameters(W1, b1, W2, b2, W3, b3, dW1, db1, dW2, db2, dW3, db3, learning_rate)# 每隔100次迭代输出一次损失值if i % 100 == 0:print(f"Iteration {i}, Loss: {loss:.4f}")return W1, b1, W2, b2, W3, b3, loss_history

解释

  • num_iterations:表示训练的迭代次数。
  • loss_history:用于记录每次迭代后的损失值,方便后续分析。
  • print 语句会在每100次迭代时输出当前的损失值,方便跟踪模型的训练过程。

Loss 函数下降趋势分析

在这里插入图片描述

从图像中可以观察到以下几个关键点和趋势:

  1. 初始快速下降 (0 - 20次迭代):

    • 在训练的初始阶段,损失函数呈现出快速下降的趋势。这是因为在训练初期,权重更新幅度较大,模型对输入数据进行大幅调整,迅速减少预测误差。
    • 这是正常现象,说明学习率设置适当,模型正在有效地学习。
  2. 损失函数平稳期 (20 - 40次迭代):

    • 在迭代20次左右,损失函数的下降速度明显放缓,并进入一个较为平稳的阶段。此时模型已经逐渐接近局部最优,调整的步伐变小。
    • 这是一个典型的现象,表明模型接近收敛。
  3. 微小波动期 (40次迭代后):

    • 从40次迭代后,损失函数继续缓慢下降,但图像上出现了一些微小的波动。这些波动可能是由于学习率较大或SGD中随机性的影响所导致的。模型在局部最优附近小范围波动。

特别点及原因分析

  • 波动原因: 这些微小的波动可能是由于学习率较大,导致模型在接近局部最优时,未能完全稳定在最优点,而是在最优点附近来回波动。同时,SGD本身的随机性也可能引入一定的波动性。
  • 损失不再下降: 在迭代大约40次后,损失函数基本保持在0.45左右。这说明当前的优化可能已经达到了某种局部最优,难以进一步下降。

第七步:模型预测

最后,我们使用训练好的模型进行预测。预测函数通过前向传播得到输出值,然后根据阈值 (0.5) 将输出转化为二分类的预测结果。

代码实现

def predict(X, W1, b1, W2, b2, W3, b3):_, _, A3 = forward_propagation(X)predictions = (A3 > 0.5).astype(int)return predictions

解释

  • predictions:预测结果,表示模型对于输入 X X X 的预测类别;阈值设置为0.5(Sigmoid激活函数的输出在区间 [ 0 , 1 ] [0, 1] [0,1] 内)。

结果对比

使用我们手动实现的模型和 sklearnMLPClassifier 进行对比,观察训练和测试集上的准确率。

from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score# 使用手动实现的模型进行预测
train_predictions = predict(X_train, W1, b1, W2, b2, W3, b3)
test_predictions = predict(X_test, W1, b1, W2, b2, W3, b3)# 计算手动实现的模型的准确度
train_accuracy = accuracy_score(Y_train, train_predictions)
test_accuracy = accuracy_score(Y_test, test_predictions)# 使用 sklearn 的 MLPClassifier 进行对比
model = MLPClassifier(hidden_layer_sizes=(4,),activation='logistic',learning_rate_init=0.01,max_iter=1000,solver='sgd',random_state=42)model.fit(X_train, Y_train.ravel())
train_predictions_sklearn = model.predict(X_train)
test_predictions_sklearn = model.predict(X_test)# 计算 sklearn 模型的准确度
train_accuracy_sklearn = accuracy_score(Y_train, train_predictions_sklearn)
test_accuracy_sklearn = accuracy_score(Y_test, test_predictions_sklearn)print(f"Training Accuracy (Manual): {train_accuracy:.4f}")
print(f"Testing Accuracy (Manual): {test_accuracy:.4f}")
print(f"Training Accuracy (Sklearn MLP): {train_accuracy_sklearn:.4f}")
print(f"Testing Accuracy (Sklearn MLP): {test_accuracy_sklearn:.4f}")

对应的结果如下:
在这里插入图片描述

总结

我们已经详细实现了一个三层神经网络的反向传播算法,并逐步拆解了各个过程。整个过程包括:

  1. 前向传播计算输出。
  2. 计算损失函数。
  3. 反向传播计算梯度。
  4. 利用梯度更新参数。
  5. 重复上述步骤训练网络。

这篇关于复现反向传播BP算法:手动实现与Sklearn MLP对比分析【复现】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1118357

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time