问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

神经网络入门:从零开始构建和训练神经网络

创作时间:
作者:
@小白创作中心

神经网络入门:从零开始构建和训练神经网络

引用
CSDN
1.
https://blog.csdn.net/zhangxin09/article/details/137890957

神经网络是机器学习领域的重要组成部分,但其原理并不像人们想象的那么复杂。本文将从零开始介绍神经网络的基础知识,包括神经元的工作原理、前馈过程、损失函数的计算以及如何使用随机梯度下降进行训练。通过本文的学习,你将能够理解神经网络的基本概念,并能够使用Python实现一个简单的神经网络。

神经元:神经网络的基本单元

神经网络的基础单元是神经元。一个神经元接收输入,进行数学运算,然后产生输出。下面是一个具有两个输入的神经元示例:

在这个神经元中发生了以下三步操作:

  1. 每个输入乘以一个权重(上图中的红色部分)。
  2. 所有加权输入相加,再加上一个偏差(bias)b(上图中的黄色部分)。
  3. 将求和的结果输入到激活函数(activation function)中(上图中的橙色部分)。

激活函数用于处理无界的输入数据,并将其转换为具有良好可预测形式的输出。常用的激活函数之一是sigmoid函数:

sigmoid函数的输出值仅在(0, 1)之间。它可以将所有实数范围(负无穷到正无穷)压缩到(0, 1)之间。对于非常大的负数,sigmoid函数的输出接近于0;对于非常大的正数,sigmoid函数的输出接近于1。

一个简单的例子

假设我们有一个2输入的神经元,使用sigmoid激活函数,并具有以下参数:w=[0,1] b=4。现在,我们给神经元输入一个x=[2,3]。我们将使用点积来更简洁地表示:

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

class Neuron:
    def __init__(self, weights, bias):
        self.weights = weights
        self.bias = bias

    def feedforward(self, inputs):
        total = np.dot(self.weights, inputs) + self.bias
        return sigmoid(total)

weights = np.array([0, 1])
bias = 4
n = Neuron(weights, bias)
x = np.array([2, 3])
print(n.feedforward(x))

神经元输出0.999,给定输入x=[2,3]。这个过程称为前馈(feedforward)。

组合神经元形成神经网络

一个神经网络就是多个神经元连接起来的集合。一个简单的神经网络看起来像这样:

这个网络有两个输入,一个有两个神经元的隐藏层(h1、h2),以及一个带有一个神经元的输出o1。注意o1的输入是h1及h2的输出,这样就构成了一个网络。

前馈的例子

使用上面的网络图,假设所有的神经元有同样的输入权重w=[0,1],同样的偏差b=0,同样的sigmoid激活函数。h1,h2,o1分别代表它们所表示的神经元的输出。

如果输入x=[2,3]会发生什么?

import numpy as np

class OurNeuralNetwork:
    def __init__(self):
        weights = np.array([0, 1])
        bias = 0
        self.h1 = Neuron(weights, bias)
        self.h2 = Neuron(weights, bias)
        self.o1 = Neuron(weights, bias)

    def feedforward(self, x):
        out_h1 = self.h1.feedforward(x)
        out_h2 = self.h2.feedforward(x)
        out_o1 = self.o1.feedforward(np.array([out_h1, out_h2]))
        return out_o1

network = OurNeuralNetwork()
x = np.array([2, 3])
print(network.feedforward(x))

神经网络的输出为0.7216,非常简单吧?

训练神经网络:损失函数

假设我们有以下测量数据:

Name
Weight (kg)
Height (cm)
Gender
Alice
56
133
Female
Bob
86
180
Male
Charlie
115
150
Male
Diana
60
140
Female

让我们训练我们的网络,根据一个人的体重和身高来预测他们的性别。我们将用0表示男性,用1表示女性,我们还将调整数据以使其更易于使用:

Name
Weight (kg)
Height (cm)
Gender
Alice
-79
-3
1
Bob
51
14
0
Charlie
80
-16
0
Diana
-75
-26
1

损失函数

在训练我们的网络之前,我们首先需要一种方法来量化它的表现如何,以便它可以尝试做得更好。这就是损失的作用。我们将使用均方误差(MSE)损失:

让我们逐步分解一下:

  • n是样本的数量,这里是4(Alice,Bob,Charlie,Diana)。
  • y代表被预测的变量,即性别。
  • y_true是变量的真实值(“正确答案”)。例如,Alice的y_true是1(女性)。
  • y_pred是变量的预测值。它是我们的网络输出的值。

(y_true - y_pred)^2被称为平方误差。我们的损失函数只是对所有平方误差取平均值(因此称为均方误差)。我们的预测越准确,损失就越低!

损失函数的代码实现

下面是我们用于计算损失的代码:

import numpy as np

def mse_loss(y_true, y_pred):
    return ((y_true - y_pred) ** 2).mean()

y_true = np.array([1, 0, 0, 1])
y_pred = np.array([0, 0, 0, 0])
print(mse_loss(y_true, y_pred))

如果你不明白这段代码为什么有效,可以阅读一下NumPy快速入门中的数组操作部分。

训练神经网络:反向传播

我们现在有一个明确的目标:最小化神经网络的损失。我们知道可以通过改变网络的权重和偏差来影响其预测结果,但我们如何以降低损失的方式进行调整呢?

这一部分涉及到一些多元微积分。如果你对微积分不太熟悉,可以跳过数学部分。

为简单起见,让我们假装数据集中只有Alice。那么均方误差损失就只是Alice的平方误差:

想象一下,如果我们想要微调w1,如果我们改变了w1,损失L会如何变化?这是偏导数∂L/∂w1可以回答的问题。那么我们如何计算它呢?

例子:计算偏导数

我们将继续假设我们的数据集中只有Alice:

让我们将所有权重初始化为1,所有偏置初始化为0。如果我们通过网络进行前向传播,我们会得到:

网络输出ypred=0.524,这并不明显偏向于男性(0)或女性(1)。让我们计算∂L/∂w1:

这告诉我们,如果我们增加w1,损失函数L会因此稍微增加一点点。

训练:随机梯度下降

我们现在拥有所有训练神经网络所需的工具!我们将使用一种名为随机梯度下降(SGD)的优化算法,它告诉我们如何调整权重和偏差以最小化损失。其基本更新方程如下:

其中,η是一个称为学习率的常数,控制着训练的速度。我们所做的就是从w1中减去η(∂L/∂w1):

  • 如果∂L/∂w1是正数,w1将减小,从而使损失L减小。
  • 如果∂L/∂w1是负数,w1将增加,从而使损失L减小。

如果我们对网络中的每个权重和偏差都这样操作,损失将逐渐减小,我们的网络将得到改进。

我们的训练过程将如下所示:

  1. 从数据集中选择一个样本。这就是所谓的随机梯度下降 - 我们一次只操作一个样本。
  2. 计算损失对权重或偏差的所有偏导数,例如∂L/∂w1、∂L/∂w2
  3. 使用更新方程更新每个权重和偏差
  4. 返回到步骤1

完整的神经网络代码实现

最后实现一个完整的神经网络:

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def deriv_sigmoid(x):
    fx = sigmoid(x)
    return fx * (1 - fx)

def mse_loss(y_true, y_pred):
    return ((y_true - y_pred) ** 2).mean()

class OurNeuralNetwork:
    def __init__(self):
        self.w1 = np.random.normal()
        self.w2 = np.random.normal()
        self.w3 = np.random.normal()
        self.w4 = np.random.normal()
        self.w5 = np.random.normal()
        self.w6 = np.random.normal()
        self.b1 = np.random.normal()
        self.b2 = np.random.normal()
        self.b3 = np.random.normal()

    def feedforward(self, x):
        h1 = sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)
        h2 = sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)
        o1 = sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)
        return o1

    def train(self, data, all_y_trues):
        learn_rate = 0.1
        epochs = 1000
        for epoch in range(epochs):
            for x, y_true in zip(data, all_y_trues):
                sum_h1 = self.w1 * x[0] + self.w2 * x[1] + self.b1
                h1 = sigmoid(sum_h1)
                sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2
                h2 = sigmoid(sum_h2)
                sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3
                o1 = sigmoid(sum_o1)
                y_pred = o1

                d_L_d_ypred = -2 * (y_true - y_pred)
                d_ypred_d_w5 = h1 * deriv_sigmoid(sum_o1)
                d_ypred_d_w6 = h2 * deriv_sigmoid(sum_o1)
                d_ypred_d_b3 = deriv_sigmoid(sum_o1)
                d_ypred_d_h1 = self.w5 * deriv_sigmoid(sum_o1)
                d_ypred_d_h2 = self.w6 * deriv_sigmoid(sum_o1)
                d_h1_d_w1 = x[0] * deriv_sigmoid(sum_h1)
                d_h1_d_w2 = x[1] * deriv_sigmoid(sum_h1)
                d_h1_d_b1 = deriv_sigmoid(sum_h1)
                d_h2_d_w3 = x[0] * deriv_sigmoid(sum_h2)
                d_h2_d_w4 = x[1] * deriv_sigmoid(sum_h2)
                d_h2_d_b2 = deriv_sigmoid(sum_h2)

                self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1
                self.w2 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2
                self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1
                self.w3 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3
                self.w4 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4
                self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2
                self.w5 -= learn_rate * d_L_d_ypred * d_ypred_d_w5
                self.w6 -= learn_rate * d_L_d_ypred * d_ypred_d_w6
                self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3

            if epoch % 10 == 0:
                y_preds = np.apply_along_axis(self.feedforward, 1, data)
                loss = mse_loss(all_y_trues, y_preds)
                print("Epoch %d loss: %.3f" % (epoch, loss))

data = np.array([
    [-2, -1],
    [25, 6],
    [17, 4],
    [-15, -6]
])
all_y_trues = np.array([
    1,
    0,
    0,
    1
])

network = OurNeuralNetwork()
network.train(data, all_y_trues)

现在我们可以用该网络预测性别:

emily = np.array([-7, -3])
frank = np.array([20, 2])
print("Emily: %.3f" % network.feedforward(emily))
print("Frank: %.3f" % network.feedforward(frank))

这将输出:

Emily: 0.951
Frank: 0.039

这表明Emily被预测为女性(接近1),而Frank被预测为男性(接近0)。

本文原文来自Victor Zhou的博客:https://victorzhou.com/blog/intro-to-neural-networks/

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号