最近很流行这些,出于好奇,我也想知道这些技术背后的原理是什么。而且我感觉很多知识可能之后会用到,所以我打算浅浅的了解一下。最终的目标是实现一个手写数字识别的神经网络吧。试试看吧。
神经网络简介
基础构件:神经元
神经元是神经网络的基本单元。它接受输入,对数据进行计算从而产生一个输出。比如下面的一个二元输入神经元样例:

这个神经元进行了以下操作:
-
输入乘以权重w:
x~1~ --> x~1~ * w~1~ x~2~ --> x~2~ * w~2~
-
加权输入与偏置b相加:
( x~1~ * w~1~) + (x~2~ * w~2~) + b
-
最后将总和传递给激活函数:(其中f是激活函数)
y = f(x~1~ * w~1~ + x~2~ * w~2~ + b)
对于任意输入的神经元,我们的输出是:
对于激活函数f我们需要额外了解到,在不引入激活函数的情况下,我们的输出和下一个输入的结果之间总是线性的关系。我们使用激活函数则可以将无界的输入转换成良好的、可以预测形式的输出。这里我们使用的激活函数是sigmoid函数:

sigmoid函数只输出(0,1)范围内的数值,它将
简单的举例
假设我们现在有一个使用sigmoid激活函数的二元输入神经元,其w=[0,1] b=4
若我们想神经元输入x = [2,3],我们可以得到
(w * x) + b = 0*2 + 1*3 + 4
= 7
y = f(w*x+b) = f(7) = 0.999
我们向前传递输入以获取输出,这个过程我们称之为前馈(feedforward)
神经元的代码实现
我们使用Python中的numpy来实现这个功能:
import numpy as np
def sigmoid(x: float) -> float:
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.9990889488055994
可以看到我们的输出和我们的计算是吻合的
将神经元组合成神经网络
神经网络实际上是许多相互连接的神经元,一个简单的神经元长这样:

这个网络有两个输入组成的输入层,还有两个神经元(h~1~,h~2~)组成的隐藏层,以及一个神经元(o~1~)组成的输出层。其中隐藏层指的是位于输入层和输出层之间的任何层,可以有多个隐藏层。
简单的举例:前馈计算
我们使用上述的网络,令每个神经元都是使用sigmoid激活函数且w=[0,1] b=0,用h1 h2 o1来表示神经元的输出,则有:
h1 = h2 = f(w*x+b)
=f((0*2)+(1*3)+0)
=f(3)
=0.9526
o1 = f(w*x+b)
= f(0.9526)
= 0.7216
此时我们的神经网络的前馈就是0.7216,整个过程简而言之就是,将输入信息通过网络中的神经元向前传递,最终得到输出信息,作为整个神经网络的前馈
神经网络的代码实现
现在我们为这个简单的神经网络实现前向传播
class NeuralNetwork:
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 feedforword(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 = NeuralNetwork()
x = np.array([2,3])
print(network.feedforword(x))
# 0.7216325609518421
和我预期的答案是吻合的
训练神经网络
损失
训练神经网络意味着,有预测的答案和实际的答案。训练的过程就是让网络预测的结果贴合真实的答案。那么首先我们就需要知道,预测的答案和真实的答案差距有多大,我们需要将它量化。
假设有以下测量值:

我们用0表示男性,用1表示女性。我们要训练我们的网络,根据体重和身高预测某人的性别。我们通过对数据设置偏移,让它更容易被处理:

现在我们需要找到一个方法量化它的"好坏",以训练它做的更好。这里我们使用均方误差损失(MSE)来衡量它的好坏:
- n是样本数量,这里是4
- y代表被预测的变量,这里是性别
- y~true~是变量的真实值(“正确答案”)
- y~pred~是网络输出的结果,即预测值
我们可以用代码实现MSE的计算:
def mse_loss(y_true,y_pred):
return ((y_true - y_pred)**2).mean()
反向传播
我们现在已经量化了我们的损失,我们现在需要通过调整网络的权重和偏差从而使得预测更加准确。我们该怎么做呢?
我们从下面这个最简单的情况开始,一点一点反推整个训练的过程

在这次训练中,正确答案为1,预测结果为y~pred~。此时有:

假如我们调整w1,那么损失L会怎么变化呢?也就是说我们需要求出
我们可以用下列过程来求出它:
训练
于是我们可以制定我们的训练过程了。在这里我们使用一种名为随机梯度下降的算法,它将告诉我们如何调整权重和偏差以最小化损失。它实际上就是这么个更新公式:
我们的徐连过程将如下:
- 从数据集中选择一个样本(随机梯度下降的原理就是一次只操作一个样本)
- 计算损失相对于权重或偏差的所有偏导数
- 使用更新方程来更新每个权重和偏差
- 重复
实现一个完整的神经网络
现在我们可以实现一个完整的神经网络来实现这个训练过程了
这是我们的数据集和网络结构:


import numpy as np
def sigmoid(x):
return 1/(1+np.exp(-x))
def deriv_sigmoid(x):
return sigmoid(x)*(1-sigmoid(x))
def mse_loss(y_true,y_pred):
return ((y_true - y_pred)**2).mean()
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)
class NeuralNetwork:
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.05
epochs = 5000
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)
# o1
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)
# h1
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)
# h2
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)
# h1 train
self.w1 -= d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1 * learn_rate
self.w2 -= d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2 * learn_rate
self.b1 -= d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1 * learn_rate
# h2 train
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
# o1 train
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))
# 来源:国家体育总局《第五次国民体质监测公报》2022[^44^]
# cm-150 kg-50
data = np.array([
[10.6, 5.7], # 女 20-24 岁
[9.8, 6.7], # 女 25-29 岁
[9.1, 8.0], # 女 30-34 岁
[8.6, 9.1], # 女 35-39 岁
[8.0, 9.7], # 女 40-44 岁
[7.5, 10.1], # 女 45-49 岁
[7.2, 10.8], # 女 50-54 岁
[7.0, 10.7], # 女 55-59 岁
[22.6, 20.4], # 男 20-24 岁
[22.1, 22.8], # 男 25-29 岁
[21.4, 24.3], # 男 30-34 岁
[20.4, 24.0], # 男 35-39 岁
[19.4, 23.2], # 男 40-44 岁
[18.7, 22.5], # 男 45-49 岁
[17.9, 21.6], # 男 50-54 岁
[17.5, 21.0] # 男 55-59 岁
])
all_y_trues = np.array([
1,1,1,1,1,1,1,1, # 8 位女性
0,0,0,0,0,0,0,0 # 8 位男性
])
network = NeuralNetwork()
network.train(data,all_y_trues)
print(network.feedforward([161-150,65-50]))
找了下几个热心嘉宾试了一下还是很准确滴