【学习笔记】深度学习入门:基于Python的理论与实现-误差反向传播法

news2024/12/25 12:24:19

CONTENTS

    • 五、误差反向传播法
      • 5.1 计算图
      • 5.2 链式法则
      • 5.3 反向传播
      • 5.4 简单层的实现
      • 5.5 激活函数层的实现
      • 5.6 Affine/Softmax层的实现
      • 5.7 误差反向传播法的实现

五、误差反向传播法

5.1 计算图

先引入一个很简单的问题:在超市买了 2 2 2 100 100 100元一个的苹果,消费税是 10 % 10\% 10%,请计算支付金额。我们画出计算图如下:

在这里插入图片描述

接着进行简单的修改,如下图所示:

在这里插入图片描述

现在我们换个问题:在超市买了 2 2 2个苹果、 3 3 3个橘子。其中,苹果每个 100 100 100元,橘子每个 150 150 150元。消费税是 10 % 10\% 10%,请计算支付金额。同样,我们画出相应的计算图如下:

在这里插入图片描述

在计算图上,我们是从左向右进行计算的,是一种正方向上的传播,简称为正向传播 f o r w a r d   p r o p a g a t i o n forward\ propagation forward propagation)。既然有正向传播这个名称,当然也可以考虑反向(从图上看的话,就是从右向左)的传播。实际上,这种传播称为反向传播 b a c k w a r d   p r o p a g a t i o n backward\ propagation backward propagation)。反向传播将在接下来的导数计算中发挥重要作用。

计算图的特征是可以通过传递局部计算获得最终结果。即无论全局发生了什么,都能只根据与自己相关的信息输出接下来的结果。比如,在超市买了 2 2 2个苹果和其他很多东西,可以用以下计算图表示:

在这里插入图片描述

这里的重点是,各个节点处的计算都是局部计算。这意味着,例如苹果和其他很多东西的求和运算( 4000 + 200 = 4200 4000+200=4200 4000+200=4200)并不关心 4000 4000 4000这个数字是如何计算而来的,只要把两个数字相加就可以了。此外,计算图的另一个优点是可以将中间的计算结果全部保存起来。

假设我们想知道苹果价格的上涨会在多大程度上影响最终的支付金额,即求支付金额关于苹果的价格的导数。设苹果的价格为 x x x,支付金额为 L L L,则相当于求 ∂ L ∂ x \frac {\partial L}{\partial x} xL,这个导数的值表示当苹果的价格稍微上涨时,支付金额会增加多少。

这个值可以通过计算图的反向传播求出来。先看一下结果,如下图所示:

在这里插入图片描述

从这个结果中可知,“支付金额关于苹果的价格的导数”的值是 2.2 2.2 2.2。这意味着,如果苹果的价格上涨 1 1 1元,最终的支付金额会增加 2.2 2.2 2.2元。

5.2 链式法则

反向传播将局部导数向正方向的反方向(从右到左)传递,其原理是基于链式法则的。我们先来看一个使用计算图的反向传播的例子。假设存在 y = f ( x ) y=f(x) y=f(x)的计算,其反向传播如下图所示:

在这里插入图片描述

反向传播的计算顺序是,将信号E乘以节点的局部导数 ∂ y ∂ x \frac {\partial y}{\partial x} xy,然后将结果传递给下一个节点。这里所说的局部导数是指正向传播中 y = f ( x ) y=f(x) y=f(x)的导数。

介绍链式法则时,我们需要先从复合函数说起。复合函数是由多个函数构成的函数。比如, z = ( x + y ) 2 z=(x+y)^2 z=(x+y)2是由以下两个式子构成的:

在这里插入图片描述

链式法则是关于复合函数的导数的性质,定义为:如果某个函数由复合函数表示,则该复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示。

以上式为例, ∂ z ∂ x \frac {\partial z}{\partial x} xz可以用 ∂ z ∂ t \frac {\partial z}{\partial t} tz ∂ t ∂ x \frac {\partial t}{\partial x} xt的乘积表示,即:

在这里插入图片描述

现在我们尝试将以上链式法则的计算用计算图表示出来:

在这里插入图片描述

在这里插入图片描述

根据链式法则, ∂ z ∂ z ∂ z ∂ t ∂ t ∂ x = ∂ z ∂ t ∂ t ∂ x = ∂ z ∂ x \frac {\partial z}{\partial z}\frac {\partial z}{\partial t}\frac {\partial t}{\partial x}=\frac {\partial z}{\partial t}\frac {\partial t}{\partial x}=\frac {\partial z}{\partial x} zztzxt=tzxt=xz成立,对应“ z z z关于 x x x的导数”。也就是说,反向传播是基于链式法则的。

5.3 反向传播

首先来考虑加法节点的反向传播。这里以 z = x + y z=x+y z=x+y为对象,观察它的反向传播。 z = x + y z=x+y z=x+y的导数可由下式(解析性地)计算出来:

在这里插入图片描述

我们假定一个最终输出值为 L L L的大型计算图。 z = x + y z=x+y z=x+y的计算位于这个大型计算图的某个地方,从上游会传来 ∂ L ∂ z \frac {\partial L}{\partial z} zL的值,并向下游传递 ∂ L ∂ x \frac {\partial L}{\partial x} xL ∂ L ∂ y \frac {\partial L}{\partial y} yL,如下图所示:

在这里插入图片描述

用计算图表示的话,如下图所示,因为加法节点的反向传播只乘以 1 1 1,所以输入的值会原封不动地流向下一个节点:

在这里插入图片描述

接下来,我们看一下乘法节点的反向传播。这里我们考虑 z = x y z=xy z=xy。这个式子的导数用下式表示:

在这里插入图片描述

乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值”后传递给下游,如下图所示:

在这里插入图片描述

假设有 10 × 5 = 50 10\times 5=50 10×5=50这一计算,反向传播时,从上游会传来值 1.3 1.3 1.3。用计算图表示的话如下图所示:

在这里插入图片描述

乘法的反向传播需要正向传播时的输入信号值。因此,实现乘法节点的反向传播时,要保存正向传播的输入信号。

使用反向传播计算最初的两个问题时,可画出计算图如下:

在这里插入图片描述

在这里插入图片描述

5.4 简单层的实现

我们把要实现的计算图的乘法节点称为“乘法层”(MulLayer),加法节点称为“加法层”(AddLayer)。层的实现中有两个共通的方法(接口)forward()backward()forward()对应正向传播,backward()对应反向传播。

现在来实现乘法层。乘法层作为MulLayer类,其实现过程如下所示:

class MulLayer:
	def __init__(self):
		self.x = None
		self.y = None

	def forward(self, x, y):
		self.x = x
		self.y = y
		out = x * y
		return out

	def backward(self, dout):
		dx = dout * self.y  # 翻转x和y
		dy = dout * self.x
		return dx, dy

现在我们使用MulLayer实现前面的购买苹果的例子:

apple = 100
apple_num = 2
tax = 1.1

# layer
mul_apple_layer = MulLayer()
mul_tax_layer = MulLayer()

# forward
apple_price = mul_apple_layer.forward(apple, apple_num)
price = mul_tax_layer.forward(apple_price, tax)
print(price)  # 220

# backward
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print(dapple, dapple_num, dtax)  # 2.2 110 200

接下来,我们实现加法节点的加法层:

class AddLayer:
	def __init__(self):
		pass  # 加法层不需要特意进行初始化

	def forward(self, x, y):
		out = x + y
		return out

	def backward(self, dout):
		dx = dout * 1
		dy = dout * 1
		return dx, dy

使用MulLayerAddLayer实现前面的购买苹果和橘子的例子:

apple = 100
apple_num = 2
orange = 150
orange_num = 3
tax = 1.1

# layer
mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_apple_orange_layer = AddLayer()
mul_tax_layer = MulLayer()

# forward
apple_price = mul_apple_layer.forward(apple, apple_num)  # (1)
orange_price = mul_orange_layer.forward(orange, orange_num)  # (2)
all_price = add_apple_orange_layer.forward(apple_price, orange_price)  # (3)
price = mul_tax_layer.forward(all_price, tax)  # (4)

# backward
dprice = 1
dall_price, dtax = mul_tax_layer.backward(dprice)  # (4)
dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price)  # (3)
dorange, dorange_num = mul_orange_layer.backward(dorange_price)  # (2)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)  # (1)

print(price)  # 715
print(dapple_num, dapple, dorange, dorange_num, dtax)  # 110 2.2 3.3 165 650

5.5 激活函数层的实现

这里,我们把构成神经网络的层实现为一个类。先来实现激活函数的 R e L U ReLU ReLU层和 S i g m o i d Sigmoid Sigmoid层。 R e L U ReLU ReLU如下式所示:

在这里插入图片描述

可以求出 y y y关于 x x x的导数如下:

在这里插入图片描述

因此 R e L U ReLU ReLU层的计算图如下图所示:

在这里插入图片描述

使用Python实现代码如下:

class Relu:
	def __init__(self):
		self.mask = None

	def forward(self, x):
		self.mask = (x <= 0)
		out = x.copy()
		out[self.mask] = 0
		return out

	def backward(self, dout):
		dout[self.mask] = 0
		dx = dout
		return dx

Relu类有实例变量mask。这个变量是由True/False构成的NumPy数组,它会把正向传播时的输入 x x x的元素中小于等于 0 0 0的地方保存为True,其他地方(大于 0 0 0的元素)保存为False

接下来,我们来实现 S i g m o i d Sigmoid Sigmoid函数,其公式如下:

在这里插入图片描述

其正向传播的计算图如下图所示,除了 “ × ” “\times” × “ + ” “+” +节点外,还出现了新的 “ e x p ” “exp” exp “ / ” “/” /节点。 “ e x p ” “exp” exp节点会进行 y = e x p ( x ) y=exp(x) y=exp(x)的计算, “ / ” “/” /节点会进行 y = 1 x y=\frac {1}{x} y=x1的计算:

在这里插入图片描述

现在我们来分析其反向传播流程:

(1) “ / ” “/” /节点表示 y = 1 x y=\frac {1}{x} y=x1它的导数可以解析性地表示为下式:

在这里插入图片描述

反向传播时,会将上游的值乘以 − y 2 -y^2 y2后,再传给下游,计算图如下图所示:

在这里插入图片描述

(2) “ + ” “+” +节点将上游的值原封不动地传给下游,计算图如下图所示:

在这里插入图片描述

(3) “ e x p ” “exp” exp节点表示 y = e x p ( x ) y=exp(x) y=exp(x),它的导数由下式表示:

在这里插入图片描述

计算图中,上游的值乘以正向传播时的输出(这个例子中是 e x p ( − x ) exp(-x) exp(x))后,再传给下游,计算图如下图所示:

在这里插入图片描述

(4) “ × ” “×” ×节点将正向传播时的值翻转后做乘法运算。因此,这里要乘以 − 1 -1 1。至此, S i g m o i d Sigmoid Sigmoid层整体的计算图如下图所示:

在这里插入图片描述

从上图的结果可知,反向传播的输出为 ∂ L ∂ y y 2 e x p ( − x ) \frac {\partial L}{\partial y}y^2exp(-x) yLy2exp(x),这个值会传播给下游的节点。这里要注意, ∂ L ∂ y y 2 e x p ( − x ) \frac {\partial L}{\partial y}y^2exp(-x) yLy2exp(x)这个值只根据正向传播时的输入 x x x和输出 y y y就可以算出来。因此,以上的计算图可以画成下图所示的集约化的 “ S i g m o i d ” “Sigmoid” Sigmoid节点:

在这里插入图片描述

另外, ∂ L ∂ y y 2 e x p ( − x ) \frac {\partial L}{\partial y}y^2exp(-x) yLy2exp(x)可以进一步整理如下:

在这里插入图片描述

使用Python实现Sigmoid层代码如下:

class Sigmoid:
	def __init__(self):
		self.out = None

	def forward(self, x):
		out = 1 / (1 + np.exp(-x))
		self.out = out
		return out

	def backward(self, dout):
		dx = dout * (1.0 - self.out) * self.out
		return dx

5.6 Affine/Softmax层的实现

神经网络的正向传播中,为了计算加权信号的总和,使用了矩阵的乘积运算。现在将这里进行的求矩阵的乘积与偏置的和的运算用计算图表示出来。将乘积运算用 “ d o t ” “dot” dot节点表示的话,则 n p . d o t ( X , W ) + B np.dot(X,W)+B np.dot(X,W)+B的运算可用下图所示的计算图表示出来(注意: X , W , B X,W,B X,W,B是矩阵):

在这里插入图片描述

神经网络的正向传播中进行的矩阵乘积运算的层就称为 A f f i n e Affine Affine层。

现在我们来考虑上图的反向传播。以矩阵为对象的反向传播,按矩阵的各个元素进行计算时,步骤和以标量为对象的计算图相同。实际写一下的话,可以得到下式:

在这里插入图片描述

式中 W T W^T WT T T T表示转置。转置操作会把 W W W的元素 ( i , j ) (i,j) (i,j)换成元素 ( j , i ) (j,i) (j,i)。用数学式表示的话,可以写成下面这样:

在这里插入图片描述

现在我们尝试写出计算图的反向传播,如下图所示:

在这里插入图片描述

我们看一下上图中各个变量的形状。尤其要注意, X X X ∂ L ∂ X \frac {\partial L}{\partial X} XL形状相同, W W W ∂ L ∂ W \frac {\partial L}{\partial W} WL形状相同。从下面的数学式可以很明确地看出 X X X ∂ L ∂ X \frac {\partial L}{\partial X} XL形状相同。

在这里插入图片描述

为什么要注意矩阵的形状呢?因为矩阵的乘积运算要求对应维度的元素个数保持一致,通过确认一致性,就可以很容易地推导出公式,如下图所示:

在这里插入图片描述

现在我们考虑 N N N个数据一起进行正向传播的情况,也就是批版本的 A f f i n e Affine Affine层。先给出批版本的 A f f i n e Affine Affine层的计算图如下图所示:

在这里插入图片描述

加上偏置时,需要特别注意。正向传播时,偏置被加到 X ⋅ W X\sdot W XW的各个数据上。比如, N = 2 N=2 N=2(数据为 2 2 2个)时,偏置会被分别加到这 2 2 2个数据(各自的计算结果)上,具体的例子如下所示:

X_dot_W = np.array([[0, 0, 0], [10, 10, 10]])
B = np.array([1, 2, 3])
X_dot_W + B  # array([[1, 2, 3], [11, 12, 13]])

正向传播时,偏置会被加到每一个数据(第 1 1 1个、第 2 2 2个、 … \dots )上。因此,反向传播时,各个数据的反向传播的值需要汇总为偏置的元素。用代码表示的话,如下所示:

dY = np.array([[1, 2, 3], [4, 5, 6]])
dB = np.sum(dY, axis=0)  # array([5, 7, 9])

综上所述, A f f i n e Affine Affine的实现如下所示:

class Affine:
	def __init__(self, W, b):
		self.W = W
		self.b = b
		self.x = None
		self.dW = None
		self.db = None

	def forward(self, x):
		self.x = x
		out = np.dot(x, self.W) + self.b
		return out

	def backward(self, dout):
		dx = np.dot(dout, self.W.T)
		self.dW = np.dot(self.x.T, dout)
		self.db = np.sum(dout, axis=0)
		return dx

最后介绍一下输出层的 S o f t m a x Softmax Softmax函数。前面我们提到过, S o f t m a x Softmax Softmax函数会将输入值正规化之后再输出。比如手写数字识别时, S o f t m a x Softmax Softmax层的输出如下图所示:

在这里插入图片描述

下面来实现 S o f t m a x Softmax Softmax层。考虑到这里也包含作为损失函数的交叉熵误差( c r o s s   e n t r o p y   e r r o r cross\ entropy\ error cross entropy error),所以称为 S o f t m a x − w i t h − L o s s Softmax-with-Loss SoftmaxwithLoss层,其化简后的计算图如下图所示:

在这里插入图片描述

图中要注意的是反向传播的结果, ( y 1 − t 1 , y 2 − t 2 , y 3 − t 3 ) (y_1-t_1,y_2-t_2,y_3-t_3) (y1t1,y2t2,y3t3) S o f t m a x Softmax Softmax层的输出和教师标签的差分。神经网络的反向传播会把这个差分表示的误差传递给前面的层,这是神经网络学习中的重要性质。

现在来进行 S o f t m a x − w i t h − L o s s Softmax-with-Loss SoftmaxwithLoss层的实现,实现过程如下所示,请注意反向传播时,将要传播的值除以批的大小( b a t c h _ s i z e batch\_size batch_size)后,传递给前面的层的是单个数据的误差:

class SoftmaxWithLoss:
	def __init__(self):
		self.loss = None  # 损失
		self.y = None  # softmax的输出
		self.t = None  # 监督数据(one-hot vector)

	def forward(self, x, t):
		self.t = t
		self.y = softmax(x)
		self.loss = cross_entropy_error(self.y, self.t)
		return self.loss

	def backward(self, dout=1):
		batch_size = self.t.shape[0]
		dx = (self.y - self.t) / batch_size
		return dx

5.7 误差反向传播法的实现

现在我们可以通过上一节实现的层来构建神经网络,误差反向传播法会在上一章构建神经网络的步骤(2)中出现,之前利用数值微分求得了这个梯度,现在使用误差反向传播法可以更高效地求解梯度。

同样我们实现一个 2 2 2层的神经网络TwoLayerNet

class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # initialize the weight
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)
        
        # creat the layers
        self.layers = OrderedDict()
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
        self.lastLayer = SoftmaxWithLoss()
        
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
        return x
    
    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)
    
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        if t.ndim != 1:
            t = np.argmax(t, axis=1)
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy
        
    # 数值微分计算梯度
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        return grads
    
    # 误差反向传播计算梯度
    def gradient(self, x, t):
        # forward
        self.loss(x, t)
        
        # backward
        dout = 1
        dout = self.lastLayer.backward(dout)
        
        layers = list(self.layers.values())
        layers.reverse()  # 从后往前传播
        for layer in layers:
            dout = layer.backward(dout)
        
        # setting grads
        grads = {}
        grads['W1'] = self.layers['Affine1'].dW
        grads['b1'] = self.layers['Affine1'].db
        grads['W2'] = self.layers['Affine2'].dW
        grads['b2'] = self.layers['Affine2'].db
        return grads

将神经网络的层保存为OrderedDict这一点非常重要。OrderedDict是有序字典,“有序”是指它可以记住向字典里添加元素的顺序。因此反向传播只需要按照相反的顺序调用各层即可。

最后,我们来看一下使用了误差反向传播法的神经网络的学习的实现,和之前的实现相比,不同之处仅在于通过误差反向传播法求梯度这一点:

# 导入模块
...

# 读入数据
(x_train, t_train), (x_test, t_test) = load_mnist(normalize = True, one_hot_label = True)

# 创建网络
network = TwoLayerNet(input_size = 784, hidden_size = 50, output_size = 10)

# 设定超参数
...

for i in range(iters_num):
    # mini-batch
    ...
    
    # 计算梯度
    # grad = network.numerical_gradient(x_batch, t_batch)
    grad = network.gradient(x_batch, t_batch)  # 误差反向传播法计算梯度
    
    # 更新参数
    ...
    
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)
    
    if i % iter_per_epoch == 0:
    	# 记录accuracy
        ...
        
# 绘制图形
...

下一节:【学习笔记】深度学习入门:基于Python的理论与实现-与学习相关的技巧。

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

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

相关文章

[附源码]JAVA毕业设计个人饮食营养管理信息系统(系统+LW)

[附源码]JAVA毕业设计个人饮食营养管理信息系统&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 …

云原生|kubernetes|kubernetes集群使用私有镜像仓库拉取镜像(harbor或者官方的registry私有镜像仓库)

前言&#xff1a; 在实际的生产中&#xff0c;我们可能会有许多的由开发制作的docker镜像&#xff0c;这也就造成使用这些镜像需要打包成tar文件&#xff0c;然后上传到服务器内然后在导入并使用&#xff0c;但&#xff0c;kubernetes节点很多&#xff0c;有时候并不是明确的要…

13.前端笔记-CSS-盒子样式应用(圆角、阴影)

1、圆角边框 border-radius属性&#xff0c;用于设置元素的外边框圆角 原理&#xff1a;(椭)圆和矩形的两条边相切&#xff08;圆的半径就是length&#xff09;&#xff0c;形成圆角效果 属性&#xff1a; border-top-left-radius;左上 border-top-right-radius:右上 border…

B-神经网络模型复杂度分析

B-神经网络模型复杂度分析 前言一&#xff0c;模型计算量分析 卷积层 FLOPs 计算全连接层的 FLOPs 计算二&#xff0c;模型参数量分析 卷积层参数量BN 层参数量全连接层参数量三&#xff0c;模型内存访问代价计算 卷积层 MAC 计算四&#xff0c;一些概念 双精度、单精度和半精…

数苹果-第12届蓝桥杯Scratch选拔赛真题精选

[导读]&#xff1a;超平老师计划推出Scratch蓝桥杯真题解析100讲&#xff0c;这是超平老师解读Scratch蓝桥真题系列的第91讲。 蓝桥杯选拔赛每一届都要举行4~5次&#xff0c;和省赛、国赛相比&#xff0c;题目要简单不少&#xff0c;再加上篇幅有限&#xff0c;因此我精挑细选…

【Android】Fragment使用

使用Fragment 我们可以把页面结构划分成几块&#xff0c;每块使用一个Fragment来管理。这样我们可以更加方便的在运行过程中动态地更新Activity中的用户界面&#xff0c;日后迭代更新、维护也是更加方便。 Fragment并不能单独使用&#xff0c;他需要嵌套在Activity 中使用&…

Redis最佳实践(上)

引言 尽管 redis 是一款非常优秀的 NoSQL 数据库&#xff0c;但更重要的是&#xff0c;作为使用者我们应该学会在不同的场景中如何更好的使用它&#xff0c;更大的发挥它的价值。主要可以从这四个方面进行优化&#xff1a;Redis键值设计、批处理优化、服务端优化、集群配置优化…

某些设置由你的组织来管理

今天莫名其妙Windows更新出现&#xff1a;*某些设置由你的组织来管理 我们来看看如何恢复吧。 根据上面的图片我们可以知道&#xff0c; 可查看配置的更新策略&#xff1a; 可以看到设备设置的策略有下面几个&#xff1a; 解决方案 这个时候我们就要进入设置更改那些策略即…

Java企业微信对接(二)微信端回调到企业端

准备工作 先下载demo 下载完成后的目录,把这些类之间copy到工程里面就行,都是封装好的加密算法 回调配置 什么时候需要回调 在集成企业微信与内部系统时,我们往往需要搭建一个回调服务。回调服务,可以实现: 自定义丰富的服务行为。比如,用户向应用发消息时,识别消…

RNA-seq 详细教程:count 数据探索(4)

学习目标 了解 RNA-seq count 数据的特征比较 count 数据的不同数学模型确定最适合 RNA-seq count 数据的模型了解设置生物学重复对于鉴定样本间差异的好处1. 计数矩阵 当开始差异表达基因分析时&#xff0c;先从一个矩阵开始&#xff0c;该矩阵总结了数据集每个样本中的基因水…

ZMQ请求应答模式之无中间件的可靠性--自由者模式

一、引言 我们讲了那么多关于中间件的示例&#xff0c;好像有些违背“ZMQ是无中间件”的说法。但要知道在现实生活中&#xff0c;中间件一直是让人又爱又恨的东西。实践中的很多消息架构能都在使用中间件进行分布式架构的搭建&#xff0c;所以说最终的决定还是需要你自己去权衡…

3.8、集线器与交换机的区别

1、早期总线型以太网 最初使用粗同轴电缆作为传输媒体&#xff0c;后来是用相对便宜的细同轴电缆 普遍认为有源器件不可靠&#xff0c;无缘的电缆线最可靠&#xff08;并没有那么可靠&#xff09; 2、只用双绞线和集线器 HUB 的星型以太网 主机中的以太网卡及集线器个接口使…

Old money风盛行,柯罗芭KLOVA演绎中式奢华

Ralph Lauren先生说过&#xff1a;“奢侈是一种感性的生活方式。它和本季推出什么新品无关。它更关乎个人风格和舒适、轻松的环境。奢侈品是质量和永恒的优雅”。Ralph lauren以一己之力托起Old money风格的半壁江山&#xff0c;它属于带着一丝上流社会的雅痞绅士&#xff0c;优…

一起学时序分析之建立/保持时间裕量

何为裕量&#xff1f; 裕量&#xff0c;英文名称叫做“Slack”。我们在Vivado实现后的报告中常常能看到这样一栏&#xff1a; 因为都是缩写&#xff0c;所以我们来解释一下前四栏的含义&#xff1a; WNS&#xff0c;即Worst Negative Slack&#xff0c;最差负时序裕量。这个表…

leetcode:1579. 保证图可完全遍历【并查集思路】

目录题目截图题目分析ac code总结题目截图 题目分析 从删除比较难&#xff0c;考虑增加增加的过程中无用的边就可以删除考虑alice和bob各自的联通分量最后希望都是1&#xff0c;一开始都是n如果将两个独立的联通分量连起来了&#xff0c;那么连通分量个数减1这里很明显就是用并…

kubernetes-Pod详解2

kubernetes-Pod详解2 文章目录kubernetes-Pod详解2Pod生命周期创建和终止pod的创建过程pod的终止过程初始化容器钩子函数容器探测方式一&#xff1a;Exec方式二&#xff1a;TCPSocket方式三&#xff1a;HTTPGet重启策略Pod调度定向调度NodeSelector亲和性调度NodeAffinityPodAf…

Kamiya丨Kamiya艾美捷AREG酶联免疫吸附试验原理

Kamiya艾美捷AREG酶联免疫吸附试验预期用途&#xff1a; 该试剂盒是一种用于体外定量测量大鼠AREG的夹心酶免疫测定法血清、血浆和其他生物流体。仅供研究使用。不用于诊断程序。 存储&#xff1a; 所有试剂应按照小瓶上的标签保存。校准器、检测试剂A、检测试剂B和96孔带板应…

ZMQ之高可靠对称节点--双子星模式

一、概览 双子星模式是一对具有主从机制的高可靠节点。任一时间&#xff0c;某个节点会充当主机&#xff0c;接收所有客户端的请求&#xff1b;另一个则作为一种备机存在。两个节点会互相监控对方&#xff0c;当主机从网络中消失时&#xff0c;备机会替代主机的位置。 双子星模…

gateway网关聚合knife4j文档,同时兼容swagger2与swagger3

基于前两篇文章&#xff0c;进行整合 springcloud-gateway 聚合swagger3请求接口丢失appliactionName解决 springcloud-gateway聚合knife4j接口文档 为何要兼容&#xff1f;微服务开发者有的使用了swagger2版本&#xff0c;有的使用了swagger3版本&#xff0c;但暴露外部给前…

聊一聊我的第一个开源项目

项目地址&#xff1a;https://github.com/kpretty/hdd 我在21年的国庆写过一篇文章&#xff1a;《Docker 实战&#xff1a;部署hadoop集群》&#xff0c;当时也是刚接触docker&#xff0c;作为docker第一个练手项目对很多概念理解的不是很到位&#xff0c;因此那篇文章所使用的…