神经网络入门:从零开始构建和训练神经网络
神经网络入门:从零开始构建和训练神经网络
神经网络是机器学习领域的重要组成部分,但其原理并不像人们想象的那么复杂。本文将从零开始介绍神经网络的基础知识,包括神经元的工作原理、前馈过程、损失函数的计算以及如何使用随机梯度下降进行训练。通过本文的学习,你将能够理解神经网络的基本概念,并能够使用Python实现一个简单的神经网络。
神经元:神经网络的基本单元
神经网络的基础单元是神经元。一个神经元接收输入,进行数学运算,然后产生输出。下面是一个具有两个输入的神经元示例:
在这个神经元中发生了以下三步操作:
- 每个输入乘以一个权重(上图中的红色部分)。
- 所有加权输入相加,再加上一个偏差(bias)b(上图中的黄色部分)。
- 将求和的结果输入到激活函数(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减小。
如果我们对网络中的每个权重和偏差都这样操作,损失将逐渐减小,我们的网络将得到改进。
我们的训练过程将如下所示:
- 从数据集中选择一个样本。这就是所谓的随机梯度下降 - 我们一次只操作一个样本。
- 计算损失对权重或偏差的所有偏导数,例如∂L/∂w1、∂L/∂w2
- 使用更新方程更新每个权重和偏差
- 返回到步骤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/