AutoCV第十一课:DL基础

目录

  • DL基础
    • 前言
    • 1. BP训练mnist
    • 2. 权重初始化理论分析
    • 总结

DL基础

前言

手写AI推出的全新保姆级从零手写自动驾驶CV课程,链接。记录下个人学习笔记,仅供自己参考。

本次课程我们来了解下 BP 反向传播和学习权重初始化相关知识

课程大纲可看下面的思维导图。

在这里插入图片描述

1. BP训练mnist

BP(back propagation)误差反向传播算法是当前人工智能主要采用的算法,如 CNN、GAN、Transformer 等都是 BP 体系下的算法框架,这节课我们从一个不同的角度来了解下 BP

我们先来回顾下之前学习到的知识

对于给定房屋面积预测房价的任务,属于单个变量和单个输出的模型,其对应表达如下:
y = k x + b y = kx + b y=kx+b
对于给定房屋面积、距离地铁远近、装修程度预测房价的任务,属于多个变量和单个输出的模型,其对应表达如下:
y = k 1 x 1 + k 2 x 2 + k 3 x 3 + b y = k_1x_1+k_2x_2+k_3x_3+b y=k1x1+k2x2+k3x3+b
对于给定房屋面积、距离地铁远近、装修程度预测房价和租金的任务,属于多个变量和多个输出的模型(多模型),其对应表达如下:
y 1 = k 1 x 1 + k 2 x 2 + k 3 x 3 + b 1 y 2 = p 1 x 1 + p 2 x 2 + p 3 x 3 + b 2 y_1 = k_1x_1 + k_2x_2 + k_3x_3 + b_1 \\ y_2 = p_1x_1 + p_2x_2 + p_3x_3 + b_2 y1=k1x1+k2x2+k3x3+b1y2=p1x1+p2x2+p3x3+b2
而我们今天要学习的 BP 可以看作是多模型的堆叠,将中间的表示抽象出来,我们以一次抽象为例来讲解,实际可以进行多次抽象,看作是多个层次的堆叠

中间的抽象层可以定义为:
h 1 = k 1 x 1 + k 2 x 2 + k 3 x 3 + b 1 h 2 = p 1 x 1 + p 2 x 2 + p 3 x 3 + b 2 h_1 = k_1x_1 + k_2x_2 + k_3x_3 + b_1 \\ h_2 = p_1x_1 + p_2x_2 + p_3x_3 + b_2 h1=k1x1+k2x2+k3x3+b1h2=p1x1+p2x2+p3x3+b2
我们往往不只是做简单的堆叠,因为这样堆叠出来的模型没有非线性的特性,表达能力偏弱,因此,我们会在中间层之后额外加一个非线性的表达,增强模型的表达能力。
h 1 = f u n c ( h 1 ) h 2 = f u n c ( h 2 ) h_1 = func(h_1) \\ h_2 = func(h_2) h1=func(h1)h2=func(h2)
最后我们再得到我们想要的目标结果
y 1 = m 1 h 1 + m 2 h 2 + b 3 y 2 = n 1 h 1 + n 2 h 2 + b 4 \begin{aligned} y_1 &= m_1h_1 + m_2h_2 + b3 \\ y_2 &= n_1h_1 + n_2h_2 + b4 \end{aligned} y1y2=m1h1+m2h2+b3=n1h1+n2h2+b4
OK!经过上述分析之后,我们再来看具体的代码实现

我们只需要将上节课中的多分类逻辑回归的示例代码略微修改即可

我们需要加一层隐藏层,假设隐藏层的输出数量为 256, k k k b b b 的定义如下:

# 定义k和b
hidden_size = 256
num_classes = 10

k1 = np.random.randn(784, hidden_size)
b1 = np.zeros((1, hidden_size))
k2 = np.random.randn(hidden_size, num_classes)
b2 = np.zeros((1, num_classes))

模型预测时也需要发生一些变化,输入先经过 k 1 k_1 k1 b 1 b_1 b1 得到隐藏层的输出,随后经过非线性激活函数 relu,最后经过 k 2 k_2 k2 b 2 b_2 b2 得到最终的预测输出。由于多了一层,因此 loss 的求导也发生了对应的变化,但是原理还是一样,示例代码如下:

def relu(x):
    x = x.copy()
    x[x < 0] = 0
    return x

def drelu(x, G):
    G = G.copy()
    G[x < 0] = 0
    return G

for epoch in range(10):
    for images, labels in train_loader:
        niter += 1
        
        # 32x784 @ 784x256
        hidden = images @ k1 + b1
        
        # nonlinear
        nonlinear_hidden = relu(hidden)

        # predict
        predict = nonlinear_hidden @ k2 + b2

        # predict -> logits
        logits = softmax(predict, dim=1)

        # softmax crossentropy loss
        # loss = -(y * ln(p))
        batch = logits.shape[0]
        onehot_labels = np.zeros_like(logits)

        # labels(32,) -> onehot(32,10)
        onehot_labels[np.arange(batch), labels] = 1
        
        loss = crossentropy_loss(logits, onehot_labels)

        if niter % 100 == 0:
            print(f"Epoch: {epoch}, Iter: {niter}, Loss: {loss:.3f}")

        G = (logits - onehot_labels) / batch
        
        # C = AB
        # dA = G @ B.T
        # dB = A.T @ G
        delta_k2 = nonlinear_hidden.T @ G
        delta_b2 = G.sum(axis=0, keepdims=True)

        delta_nonlinear_hidden = G @ k2.T
        delta_hidden = drelu(hidden, delta_nonlinear_hidden)

        delta_k1 = images.T @ delta_hidden
        delta_b1 = delta_hidden.sum(axis=0, keepdims=True)

        k1 = k1 - lr * delta_k1
        b1 = b1 - lr * delta_b1
        k2 = k2 - lr * delta_k2
        b2 = b2 - lr * delta_b2

验证部分也需要相应的修改,代码如下:

# evaluate
all_predict = []
for images, labels in test_loader:

    hidden = images @ k1 + b1

    # nonlinear
    nonlinear_hidden = relu(hidden)

    predict = nonlinear_hidden @ k2 + b2

    logits = softmax(predict, dim=1)

    predict_labels = logits.argmax(axis=1)
    all_predict.extend(predict_labels == labels)

    accuracy = np.sum(all_predict) / len(all_predict) * 100
    print(f"Epoch: {epoch}, Evaluate Test Set, Accuracy: {accuracy:.3f} %")

运行效果如下:

在这里插入图片描述

图1-1 BP输出1

可以看到 loss 直接飞了,这是因为 k k k 采用正态分布的初始化,其值比较大,当计算 loss 时 ln 直接跑飞了,因此,我们可以将 k 1 k_1 k1 k 2 k_2 k2 缩小,在下次课程中我们会对权重的初始化进行详细的分析,具体代码如下:

k1 = np.random.randn(784, hidden_size) * 0.1
b1 = np.zeros((1, hidden_size))
k2 = np.random.randn(hidden_size, num_classes) * 0.1
b2 = np.zeros((1, num_classes))

修改后运行的效果如下:

在这里插入图片描述

图1-2 BP输出2

可以看到此时的 loss 输出值正常,且第一个 epoch 准确率就达到了 92.93%,效果还是不错的。

接下来我们可以将模型预测的结果进行简单的可视化,代码如下:

for images, labels in test_loader:
    
    hidden = images @ k1 + b1

    nonlinear_hidden = relu(hidden)

    predict = nonlinear_hidden @ k2 + b2

    logits = softmax(predict, dim=1)

    predict_labels = logits.argmax(axis=1)

    pixels = (images * train_dataset.std + train_dataset.mean).astype(np.uint8).reshape(-1, 28, 28)

    for image, predict, gt in zip(pixels, predict_labels, labels):

        plt.imshow(image)
        plt.title(f"Predict: {predict}, GT: {gt}")
        plt.show()

可视化的部分结果如下所示:

在这里插入图片描述

图1-3 预测结果1

在这里插入图片描述

图1-4 预测结果2

可以看到模型预测的结果还是比较准确的。

对于 BP 反向传播算法的不同理解可以参考 彻底理解BP反向传播,BP实战图像分类98.3%

完整的示例代码如下所示:

import numpy as np
import matplotlib.pyplot as plt

class MNISTDataset:
    def __init__(self, images_path, labels_path, train, mean=None, std=None):
        self.images_path = images_path
        self.labels_path = labels_path
        self.images, self.labels = self.load_mnist_data()

        # flatten Nx28x28 -> Nx784
        self.images =  self.images.reshape(len(self.images), -1)
        
        if train:
            self.images, self.mean, self.std = self.normalize(self.images)
        else:
            self.images, self.mean, self.std = self.normalize(self.images, mean, std)

    @staticmethod
    def normalize(x, mean=None, std=None):
        if mean is None:
            mean = x.mean()
        
        if std is None:
            std = x.std()
        
        x = (x - mean) / std
        return x, mean, std

    def __len__(self):
        return len(self.images)

    def __getitem__(self, index):
        image, label = self.images[index], self.labels[index]
        return image, label

    def load_mnist_data(self):
        # load labels
        with open(self.labels_path, "rb") as f:
            magic_number, num_of_items = np.frombuffer(f.read(8), dtype=">i", count=2, offset=0)
            labels = np.frombuffer(f.read(), dtype=np.uint8, count=-1, offset=0)

        # load images
        with open(self.images_path, "rb") as f:
            magic_number, num_of_images, rows, cols = np.frombuffer(f.read(16), dtype=">i", count=4, offset=0)
            pixels = np.frombuffer(f.read(), dtype=np.uint8, count=-1, offset=0)
            images_matrix = pixels.reshape(num_of_images, rows, cols)

        return images_matrix, labels

class MNISTDataLoader:
    def __init__(self, dataset, batch_size, shuffle=True):
        self.dataset = dataset
        self.batch_size = batch_size
        self.shuffle = shuffle
        
    def __iter__(self):
        self.indexs = np.arange(len(self.dataset))
        if self.shuffle:
            np.random.shuffle(self.indexs)
        self.cursor = 0
        return self
    
    def __next__(self):
        begin = self.cursor
        end   = self.cursor + self.batch_size
        if end > len(self.dataset):
            raise StopIteration()
        self.cursor = end
        batched_data = []
        for index in self.indexs[begin:end]:
            item = self.dataset[index]
            batched_data.append(item)
        # return batched_data
        return [np.stack(item, axis=0) for item in list(zip(*batched_data))]      

# 训练集
train_dataset = MNISTDataset("mnist/train-images-idx3-ubyte", "mnist/train-labels-idx1-ubyte", train=True)
train_loader  = MNISTDataLoader(train_dataset, batch_size=32, shuffle=True)

# 测试集
test_dataset = MNISTDataset(
    "mnist/t10k-images-idx3-ubyte", 
    "mnist/t10k-labels-idx1-ubyte",
    train=False,
    mean=train_dataset.mean,
    std=train_dataset.std
)
test_loader = MNISTDataLoader(test_dataset, 10)

# 定义k和b
hidden_size = 256
num_classes = 10

k1 = np.random.randn(784, hidden_size) * 0.1
b1 = np.zeros((1, hidden_size))
k2 = np.random.randn(hidden_size, num_classes) * 0.1
b2 = np.zeros((1, num_classes))

def relu(x):
    x = x.copy()
    x[x < 0] = 0
    return x

def drelu(x, G):
    G = G.copy()
    G[x < 0] = 0
    return G

def softmax(x, dim):
    x = np.exp(x - x.max())
    return x / x.sum(axis=dim, keepdims=True)

def crossentropy_loss(logits, onehot_labels):
    batch = logits.shape[0]
    return -(onehot_labels * np.log(logits)).sum() / batch

lr = 1e-2
niter = 0

for epoch in range(1):
    for images, labels in train_loader:
        niter += 1
        
        # 32x784 @ 784x256
        hidden = images @ k1 + b1
        
        # nonlinear
        nonlinear_hidden = relu(hidden)

        # predict
        predict = nonlinear_hidden @ k2 + b2

        # predict -> logits
        logits = softmax(predict, dim=1)

        # softmax crossentropy loss
        # loss = -(y * ln(p))
        batch = logits.shape[0]
        onehot_labels = np.zeros_like(logits)

        # labels(32,) -> onehot(32,10)
        onehot_labels[np.arange(batch), labels] = 1
        
        loss = crossentropy_loss(logits, onehot_labels)

        if niter % 100 == 0:
            print(f"Epoch: {epoch}, Iter: {niter}, Loss: {loss:.3f}")

        G = (logits - onehot_labels) / batch
        
        # C = AB
        # dA = G @ B.T
        # dB = A.T @ G
        delta_k2 = nonlinear_hidden.T @ G
        delta_b2 = G.sum(axis=0, keepdims=True)

        # dloss / dhidden
        delta_nonlinear_hidden = G @ k2.T
        delta_hidden = drelu(hidden, delta_nonlinear_hidden)

        delta_k1 = images.T @ delta_hidden
        delta_b1 = delta_hidden.sum(axis=0, keepdims=True)

        k1 = k1 - lr * delta_k1
        b1 = b1 - lr * delta_b1
        k2 = k2 - lr * delta_k2
        b2 = b2 - lr * delta_b2

    # evaluate
    all_predict = []
    for images, labels in test_loader:
        
        hidden = images @ k1 + b1

        # nonlinear
        nonlinear_hidden = relu(hidden)

        predict = nonlinear_hidden @ k2 + b2

        logits = softmax(predict, dim=1)

        predict_labels = logits.argmax(axis=1)
        all_predict.extend(predict_labels == labels)
    
    accuracy = np.sum(all_predict) / len(all_predict) * 100
    print(f"Epoch: {epoch}, Evaluate Test Set, Accuracy: {accuracy:.3f} %")

for images, labels in test_loader:
    
    hidden = images @ k1 + b1

    nonlinear_hidden = relu(hidden)

    predict = nonlinear_hidden @ k2 + b2

    logits = softmax(predict, dim=1)

    predict_labels = logits.argmax(axis=1)

    pixels = (images * train_dataset.std + train_dataset.mean).astype(np.uint8).reshape(-1, 28, 28)

    for image, predict, gt in zip(pixels, predict_labels, labels):

        plt.imshow(image)
        plt.title(f"Predict: {predict}, GT: {gt}")
        plt.show()

2. 权重初始化理论分析

我们先来对上节课 BP 训练 mnist 做一个回顾,在上节课中我们发现直接采用正态分布的随机数来初始权重 k k k 会导致最终的 loss 直接跑飞,因此我们将其值乘了 0.1 进行缩小,发现缩小后的初始化权重 k k k 能达到一个比较好的结果。

为什么一定要使用正态分布的随机数来对权重进行初始化呢?使用其他分布可以吗?🤔

下面我们对比了采用均匀分布随机数和正态分布随机数来初始化权重 k k k 的损失和模型的性能

rand  -> loss: 2.127, accuracy: 87.010%
randn -> loss: 0.365, accuracy: 93.100%

可以看到二者差别还是比较大的,均匀分布的随机数初始化的 loss 要高,且准确率也偏低。这说明不同的权重初始化方法会影响最终的模型性能,为什么正态分布的随机数初始化会比均匀分布的随机数初始化效果要好呢?这就是我们接下来要讨论的问题。

我们可以回想下之前求取 2 \sqrt 2 2 的例子中,我们将 t t t 初始化为 x / 2 x/2 x/2,因为这个数更能够接近目标值一些,所以它的开始 loss 会更低,迭代次数也会更少。

现在考虑线性回归模型
y = k x + b y = kx + b y=kx+b
问题:如何定义 k k k b b b,在理论上认为是相对比较好的初始点?

我们需要分析 y = k x + b y = kx + b y=kx+b 这个函数以及所有变量的性质,下面我们一个个分析

x x x:输入的数据

  • x x x 通常来自于统计得到的数据,来自于日常生活中产生的数据。我们可以通过数据产生的概率来描述它,我们认为 x x x 通常会满足极端数据少,平均数据多的情况
  • 既然 x x x 符合我们日常生活中的数据,那不妨我们使用正态分布来描述 x x x,这其实是对日常收集到的数据的一个假设,因为日常生活中的很多数据比如一个班级的成绩、身高、体重等都是符合正态分布的,因此,对 x x x 做正态分布假设是一个非常具有通用型的设计
  • 在之前的实现中,我们通常会对 x x x 做正则化,即 x -> normalize(x), (x - mean) / std,它其实是把 x x x 正则化到一个标准的正态分布下。因此最终的 x ∼ N ( 0 , 1 ) x\sim N\left(0,1\right) xN(0,1)

y y y:输出的值

  • y y y 是对于输入数据 x x x 的映射后的一个值,我们从统计的角度来看, y y y 依旧是随着 x x x 而言的,依旧是正态分布
  • y -> normalize(y), (y - mean) / std y ∼ N ( 0 , 1 ) y\sim N\left(0,1\right) yN(0,1)

因此我们可以得出结论,对于 k k k b b b 的设计而言,如果能够使得 x x x 经过 k k k b b b 过后的分布不产生变化,那么 k k k b b b 将会是一个比较好的初始化位置。

具体我们该如何设计 k k k b b b 的初始值使得分布不会产生变化呢?

在开始之前我们需要对 np.random.randn 有一个简单的了解

import numpy as np

x = np.random.randn(784, 256)

# mean -> 均值
# std  -> 标准差
# var  -> 方差
# 其中 var = std^2
print(x.mean(), x.std(), x.var())

如果对服从正态分布的变量乘以某个常数或者加上某个常数后的分布又是怎样的呢?

那这就不得不提关于正态分布的一些特性了

  • 如果 X ∼ N ( μ , σ 2 ) X\sim N\left(\mu,{\sigma}^2\right) XN(μ,σ2) a a a 是实数,那么 a X ∼ N ( a μ , ( a σ ) 2 ) aX \sim N\left(a\mu,(a\sigma)^2\right) aXN(aμ,()2)
  • 如果 X ∼ N ( μ , σ 2 ) X\sim N\left(\mu,{\sigma}^2\right) XN(μ,σ2) b b b 是实数,那么 X + b ∼ N ( μ + b , σ 2 ) X+b \sim N\left(\mu+b,{\sigma}^2\right) X+bN(μ+b,σ2)
  • 更多细节可参考 wiki

根据正态分布的上面两条特性我们可以完成对 k k k b b b 的初始值的设计了

首先 predict = x @ k + b,我们需要确保输入 predict 的分布与 x 保持一致都为标准正态分布。当输入 x 乘以 k 后其标准差为发生变化,我们目的当然是希望它不发生变化,乘完之后还是和 x 保持一致

我们先来看一个简单的例子:

x = np.random.randn(784, 256)
k = np.random.randn(256, 64)

predict = x @ k

# 0.04331550600632987 16.002218532647994 256.0709979666229
print(predict.mean(), predict.std(), predict.var())

可以看到二者相乘后依旧服从正态分布,其均值依旧是 0,而标准差则为 256 = 16 \sqrt {256} = 16 256 =16

为什么结果矩阵会服从均值为 0,标准差为 16 的正态分布呢?以下是 chatGPT 给出的解释

首先,对于标准正态分布的随机变量 x x x k k k,它们的均值为 0,标准差为 1

x x x k k k 相乘后,得到一个新的矩阵 m m m 其维度为 (784,64)。 m m m 中的每个元素 m ( i , j ) m (i,j) m(i,j) 是由 x x x 的第 i i i 行与 k k k 的第 j j j 列进行内积运算得到的

由于 x x x k k k 都服从标准正态分布,它们的每个维度都是独立的随机变量。在独立随机变量相乘的情况下,我们可以应用卷积和中心极限定理。

根据卷积和中心极限定理的原理,当进行大量独立随机变量的相乘时,结果的分布会趋近于正态分布。

在当前的情况下, m m m 中的每个元素都是由 256 个独立的随机变量相乘得到的,而这些随机变量都来自于标准正态分布,根据中心极限定理,这样的相乘操作会导致结果的分布趋近于正态分布。因此, m m m 的元素也会服从正态分布

对于均值的推导,由于 x x x k k k 的均值为 0,它们的内积的期望值也为 0。因此, m m m 的每个元素的期望值为 0,即 m m m 的均值为 0

对于标准差的推导,由于每个 x x x k k k 的元素独立的且标准差均为 1,内积的标准差会变为相乘元素个数的平方根。在当前情况下, x x x k k k 的维度均为 256,所以相乘后的标准差为 256 \sqrt {256} 256 ,即 16。

一切似乎变得清晰明了了呀😄,简单来说 m m m 中的每个元素都是由 256 个独立的随机变量相乘得到。而这些随机变量来自于标准正态分布

假设 X X X Y Y Y 服从标准正态分布且独立,现在需要计算它们的乘积 Z = X Y Z = XY Z=XY 的均值和标准差
E ( Z ) = E ( X Y ) = E ( X ) E ( Y ) = 0 V a r ( Z ) = V a r ( X Y ) = E [ ( X Y − E [ X Y ] ) 2 ] = E [ ( X Y ) 2 ] = E [ X 2 Y 2 ] = E ( X 2 ) E ( Y 2 ) = V a r ( X ) V a r ( Y ) = 1 \begin{aligned} E(Z) &=E(XY)=E(X)E(Y)=0 \\ Var(Z) &=Var(XY)=E[(XY - E[XY])^2] \\ &=E[(XY)^2]=E[X^2Y^2] \\ &=E(X^2)E(Y^2) \\ &= Var(X)Var(Y) \\ &= 1 \end{aligned} E(Z)Var(Z)=E(XY)=E(X)E(Y)=0=Var(XY)=E[(XYE[XY])2]=E[(XY)2]=E[X2Y2]=E(X2)E(Y2)=Var(X)Var(Y)=1
因此 256 个独立的均值为 0,方差为 1 的随机变量相加后的均值为 0,方差为 256,标准差为 16

根据正态分布的第一条特性,要想让其标准差变为 1,只需要除以 16 即可

D = 256
x = np.random.randn(784, D)
k = np.random.randn(D, 64)

predict = x @ k 
predict = predict / np.sqrt(D)
# 0.0029998421515664217 0.993879430767524 0.9877963229027776
print(predict.mean(), predict.std(), predict.var())

可以看到结果符合我们的预期,那么关于 k k k 的设计我们就清楚了

k k k 的设计: k ∼ N ( 0 , 1 D ) k \sim N(0, \frac{1}{\sqrt D}) kN(0,D 1) 其中 D = k.rows

接下来我们看下 b b b 的设计,根据正态分布特性第二条,当加上一个随机数时其均值会发生变化,而方差不变,为了让最终的 predictx 的分布保持一致,我们需要将这个均值去除,很简单,我们将 b b b 设置为 0 即可。

b b b 的设计:0

k k k b b b 的设计符合上述条件时,predict = x @ k + b 就会实现 x x x 经过 k k k b b b 后的分布不产生变化,满足我们的要求。

对于线性回归模型的权重初始化比较简单,而对于 BP 神经网络来说可能稍微复杂点

h = x @ k1 + b1
ha = nonlinear(h)
predict = ha @ k2 + b2

对于模型的参数设计而言,我们依旧希望 x x x 经过 k 1 k_1 k1 b 1 b_1 b1 n o n l i n e a r nonlinear nonlinear k 2 k_2 k2 b 2 b_2 b2 后,得到的 p r e d i c t predict predict 保持分布不变

由于 n o n l i n e a r nonlinear nonlinear 会改变分布的性质,因此在对 k 1 k_1 k1 b 1 b_1 b1 k 2 k_2 k2 b 2 b_2 b2 做初始化时需要将其考虑进去

对于 n o n l i n e a r nonlinear nonlinear 的权重初始化我们可以采用凯明初始化

凯明初始化

fan_in,fan_out

fan_in -> k.rows fan_out -> k.cols

mean = 0

std = gain / sqrt(fan_in + fan_out)

关于不同的非线性函数对应的增益可参考 pytorch 官网 torch.nn.init,非线性函数不同对应的增益也将不同,比如 ReLU 的增益为 2 \sqrt 2 2

关于凯明初始化的更多细节需要大家自行补充

  • Xavier 初始化 Paper:Understanding the difficulty of training deep feedforward neural networks

  • Kaiming 初始化 Paper:Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification

  • 几种常见的权重初始化方法

  • 一文搞懂深度网络初始化(Xavier and Kaiming initialization)

总结

本次课程从一个新奇的角度带我们了解了 BP 算法,我们可以将其理解为多模型的堆叠,将中间的隐藏层抽象出来,可以不断的叠加,也比较符合人类社会的阶级制度。但值得注意的是我们往往会将中间隐藏层的输出经过一个非线性变换,在本次课程中我们是使用 relu 函数来实现的,这样可以加强模型的非线性表达能力,增强模型的性能。
本次课程还学习了权重初始化的相关理论知识,我们从线性回归模型出发最终得出了 k k k b b b 的设计,二者的设计思路是确保 x x x 经过 k k k b b b 后其分布不发生变化。根据这个设计思路我们可以推广到 BP 神经网络中权重的设计,由此引出了 Kaming 初始化,其均值为 0,方差为 gain/(fan_in+fan_out),不同激活函数对应的增益也不尽相同。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis的持久化方式

为什么要持久化 Redis是内存数据库&#xff0c;宕机后数据会消失&#xff0c;Redis重启后快速恢复数据&#xff0c;要提供持久化机制。 把内存中的数据持久化到磁盘中&#xff0c;防止数据丢失。 —当redis服务器开启时&#xff0c;会把磁盘中的数据加载到内存中进行计算。 …

docker搭建nginx

一、安装Docker 1、安装&#xff1a; yum install docker 2、启动/停止/重启docker服务 systemctl docker start systemctl docker stop systemctl docker restart #开机自启动 systemctl enable docker#设置容器自启动 1.创建容器时设置 docker run -d --restartalways …

vue使用mapbox地图

1、下载依赖 npm install --save mapbox-gl mapbox/mapbox-gl-language 2、引入mapBox&#xff0c;将引入的内容封装为js文件 在api/map文件夹下新建mapbox.js文件 代码&#xff1a; import mapboxgl from mapbox-gl import mapbox-gl/dist/mapbox-gl.css import MapboxLang…

Redis缓存问题与缓存更新机制

目录 ​编辑 一、缓存问题 1.1 缓存穿透 1.1.1 问题来源 1.1.2 解决方案 1.1.2.1 缓存空对象 1.1.2.2 使用布隆过滤器 1.2 缓存击穿 1.2.1 问题来源 1.2.2 解决方案 1.2.2.1 设置热点数据永远不过期 1.2.2.2 新增后台定时更新缓存线程&#xff08;逻辑不过期&#xff09; 1.2.…

MFC项目添加外部头文件和源文件后编译出现C1010错误

出现这个问题的主要原因是如果使用VC向生成工程的话&#xff0c;默认使用预编译头文件“stdafx.h”&#xff0c;这样做的目的是为了加快编译速度。 如果加入第三方c/cpp文件没有#include “stdafx.h” &#xff0c;就会报此错误。 在<解决方案管理器中>(就是可以看到工程…

Git 常用操作总结

版本控制系统&#xff08;VCS&#xff09;是管理文件和目录所做的更改的工具&#xff0c;每一次提交便记录下目录及其文件的内容&#xff0c;以及较上一版本的更改。通过这样去跟踪项目的更改过程&#xff0c;方便与他人进行协作&#xff0c;或者撤销不想要的更改以回退到此前的…

10 Web全栈 组件化设计

前端架构层次设计 前端技术体系庞大&#xff0c;层级也非常分明&#xff0c;在架构设计领域中不能一概而论&#xff0c;任何应用种类都有自己独立的架构体系。比如在前端开发领域&#xff0c;在框架基础上进行应用构建的开发者锁思考的问题&#xff0c;与在组件库设计方面的开…

【文件 part 6 - 格式化读写文件函数 随机读写】

格式化读写文件函数 /* 函数调用: */ fprintf ( 文件指针&#xff0c;格式字符串&#xff0c;输出表列&#xff09;&#xff1b; fscanf ( 文件指针&#xff0c;格式字符串&#xff0c;输入表列&#xff09;&#xff1b;/** 函数功能:* 从磁盘文件中读入或输出字符* fprint…

SAP从入门到放弃系列之生产订单结算

文章目录概览 一、概述二、结算的模式2.1、订单相关的成本归集2.2、产品相关的成本归集 最后 一、概述 当生产从计划到订单&#xff0c;生产领料、投料、报工、入库整个生产流程完成后&#xff0c;生产订单中会产生对应的财务数据&#xff0c;或者说借贷项数据&#xff08;材料…

npm启动,node.js版本过高

“dev_t”: “set NODE_OPTIONS”–openssl-legacy-provider" & npm run dev\n"

Ubuntu安装碰撞检测库FCL以及前置依赖libccd, OctoMap

Ubuntu安装碰撞检测库FCL以及前置依赖libccd, OctoMap 大致安装流程见FCL github地址&#xff0c;但是在安装libccd时存在一些问题。 建议完整浏览后再进行安装 1.编译libccd的报错 首先FCL页面已经说明libccd要直接克隆源码&#xff0c;不要下载压缩包。 其次&#xff0c;在…

MAYA传送带上放石头(新旧粒子系统)

播放试试 使用老的粒子系统 particleShape1.shuliangrand(0,5); particleShape1.daxiao<<rand(0.2,0.5),rand(0.2,0.5),rand(0.2,0.5)>>; particleShape1.xuanzhuan<<rand(360),rand(360),rand(360)>>; 使用新的粒子系统 粒子向后滑落 新粒子系统能进行…

SSMP整合案例(11) 在界面中实现添加操作

上文 SSMP整合案例(10) vue端调整项目环境 发送请求 基本界面编写我们搭建了基本的页面结构 然后 我们来做个新增的功能 首先 新增 我们肯定是用户点击了这个新建之后 我们再来处理这个逻辑 我们之前的代码 新增是有绑定 一个事件的 但是这个 AddBook中并没有内容 首先 我们…

4.44ue4:相机抖动

1.创建相机抖动类 右键内容面板&#xff0c;点击创建蓝图类&#xff0c;搜索shake&#xff08;camera shake&#xff09; 2.使用相机抖动&#xff1a; 节点&#xff1a;play world .. api解释&#xff1a; epicenter&#xff1a;震源 inner Radius&#xff1a;内圈范围&a…

【梦辛工作室】java实现简易消息队列处理器 可分区 分区顺序消费MxMQ

大家好哇&#xff0c;又是我&#xff0c;梦辛工作室的灵&#xff0c;最近在巩固JUC并发包&#xff0c;突然想到如果自己的应用体量不大&#xff0c;但有需要消息队列来实现应用解耦和削峰来缓解服务器突增压力&#xff0c;比如抢票时&#xff0c;突然有比较用户同时抢票&#x…

分布式运用——rsync远程同步

分布式运用——rsync远程同步 一、rsync的背景和原理1.rsync的功能2.rsync的应用场景3.使用rsync的基本命令4.scp与rsync的区别 二、配置rsync源服务器1.关闭防火墙2.建立/etc/rsyncd.conf 配置文件3.保证所有用户对源目录/var/www/html 都有读取权限4.启动 rsync 服务程序5.关…

flutter3.7版本下使用flutter boost解决使用platview崩溃或异常问题

背景 工程使用了混合开发&#xff0c;使用flutter boost插件&#xff0c;flutter 的activity1 frament1 跳转activity2 frament2&#xff0c;frament1 包含platformView&#xff0c;按照上面老哥解决崩溃问题的基础上&#xff0c;出现activity2 frament2返回activity1 framen…

“未来之光:揭秘创新科技下的挂灯魅力“

写在前面&#xff1a; 高度信息化当下时代&#xff0c;对电脑及数字设备的需求与日俱增无处不在&#xff0c;随之而来的视觉疲劳和眼睛问题也攀升到了前所未有的高度。传统台灯对于长时间使用电脑的人群来说是完全无法解决这些问题的。一款ScreenBar Halo 屏幕挂灯&#xff0c;…

数学建模——插值(下)

本文是面向数学建模准备的&#xff0c;是介绍性文章&#xff0c;没有过多关于原理的说明&#xff01;&#xff01;&#xff01; 目录 一、2维插值原理及公式 1、二维插值问题 2、最邻近插值 3、分片线性插值 4、双线性插值 5、二维样条插值 二、二维插值及其Matlab工具箱…

微信小程序

私人博客 许小墨のBlog —— 菜鸡博客直通车 系列文章完整版&#xff0c;配图更多&#xff0c;CSDN博文图片需要手动上传&#xff0c;因此文章配图较少&#xff0c;看不懂的可以去菜鸡博客参考一下配图&#xff01; 系列文章目录 前端系列文章——传送门 后端系列文章——传送…
最新文章