神经网络是深度学习的基础,正是深度学习的兴起,让停滞不前的人工智能再一次的取得飞速的发展。
其实神经网络的理论由来已久,灵感来自仿生智能计算,只是以前限于硬件的计算能力,没有突出的表现,
直至谷歌的AlphaGO的出现,才让大家再次看到神经网络相较于传统机器学习的优异表现。
本文主要介绍神经网络中的重要基础概念,然后基于这些概念手工实现一个简单的神经网络。
希望通过理论结合实践的方式让大家更容易的理解神经网络。
1. 神经网络是什么
神经网络就像人脑一样,整体看上去非常复杂,但是其基础组成部分并不复杂。
其组成部分中最重要的就是
神经元
(
neural
),
sigmod函数
和
层
(
layer
)。
1.1. 神经元
神经元(
neural
)是神经网络最基本的元素,一个神经元包含3个部分:
- 获取输入 :获取多个输入的数据
- 数学处理 :对输入的数据进行数学计算
- 产生输出 :计算后 多个 输入数据变成 一个 输出数据
从上图中可以看出,
神经元
中的处理有2个步骤。
第一个步骤:
从蓝色框变成红色框,是对输入的数据进行加权计算后合并为一个值(
N
)。
\(N = x_1w_1 + x_2w_2\)
其中,
\(w_1,w_2\)
分别是输入数据
\(x_1,x_2\)
的权重。
一般在计算
\(N\)
的过程中,除了权重,还会加上一个偏移参数
\(b\)
,最终得到:
\(N = x_1w_1 + x_2w_2+b\)
第二个步骤
:从红色框变成绿色框,通过
sigmoid函数
是对
N
进一步加工得到的神经元的最终输出(
M
)。
1.2. sigmoid函数
sigmoid
函数也被称为
S函数
,因为的形状类似
S形
。
它是神经元中的重要函数,能够将输入数据的值映射到
\((0,1)\)
之间。
最常用的
sigmoid
函数是
\(f(x)=\frac{1}{1+e^{-x}}\)
,当然,不是只有这一种
sigmoid
函数。
至此,神经元通过两个步骤,就把输入的多个数据,转换为一个 \((0,1)\) 之间的值。
1.3. 层
多个神经元可以组合成一层,一个神经网络一般包含一个输入层和一个输出层,以及多个隐藏层。
比如上图中,有2个隐藏层,每个隐藏层中分别有4个和2个神经元。
实际的神经网络中,
隐藏层数量
和其中的
神经元数量
都是不固定的,根据模型实际的效果来进行调整。
1.4. 网络
通过神经元和层的组合就构成了一个网络,神经网络的名称由此而来。
神经网络可大可小,可简单可复杂,不过,太过简单的神经网络模型效果一般不会太好。
因为一只果蝇就有10万个神经元,而人类的大脑则有大约1000亿个神经元,
这就是为什么训练一个可用的神经网络模型需要庞大的算力,这也是为什么神经网络的理论
1943年
就提出了,
但是基于深度学习的
AlphaGO
却诞生于
2015年
。
2. 实现一个神经网络
了解上面的基本概念只能形成一个感性的认知。
下面通过自己动手实现一个最简单的神经网络,来进一步认识
神经元
,
sigmoid函数
以及
隐藏层
是如何发挥作用的。
2.1. 准备数据
数据使用
sklearn
库中经典的鸢尾花数据集,这个数据集中有3个分类的鸢尾花,每个分类50条数据。
为了简化,只取其中前
100
条数据来使用,也就是取
2个分类
的鸢尾花数据。
from sklearn.datasets import load_iris
ds = load_iris(as_frame=True, return_X_y=True)
data = ds[0].iloc[:100]
target = ds[1][:100]
print(data)
print(target)
变量
data
中
100
条数据,每条数据包含
4个属性
,分别是花萼的宽度和长度,花瓣的宽度和长度。
变量
target
中也是
100
条数据,只有
0和1
两种值,表示两种不同种类的鸢尾花。
2.2. 实现神经元
准备好了数据,下面开始逐步实现一个简单的神经网络。
首先,实现最基本的单元--
神经元
。
本文第一节中已经介绍了神经元中主要的2个步骤,分别计算出
\(N\)
和
\(M\)
。
计算
\(N\)
时,依据每个输入元素的权重(
\(w_1,w_2\)
)和整体的偏移
\(b\)
;
计算
\(M\)
时,通过
sigmoid
函数。
def sigmoid(x):
return 1 / (1 + np.exp(-1 * x))
@dataclass
class Neuron:
weights: list[float] = field(default_factory=lambda: [])
bias: float = 0.0
N: float = 0.0
M: float = 0.0
def compute(self, inputs):
self.N = np.dot(self.weights, inputs) + self.bias
self.M = sigmoid(self.N)
return self.M
上面的代码中,
Neuron
类表示神经元,这个类有4个属性:
其中属性
weights
和
bias
是计算
\(N\)
时的权重和偏移;
属性
N
和
M
分别是神经元中两步计算的结果。
Neuron
类的
compute
方法根据输入的数据计算神经元的输出。
2.3. 实现神经网络
神经元实现之后,下面就是构建神经网络。
我们的输入数据是带有
4个属性
(花萼的宽度和长度,花瓣的宽度和长度)的鸢尾花数据,
所以神经网络的输入层有4个值(
\(x_1,x_2,x_3,x_4\)
)。
为了简单起见,我们的神经网络只构建一个
隐藏层
,其中包含
3个神经元
。
最后就是输出层,输出层最后输出一个值,表示鸢尾花的种类。
由此形成的简单神经网络如下图所示:
实现的代码:
@dataclass
class MyNeuronNetwork:
HL1: Neuron = field(init=False)
HL2: Neuron = field(init=False)
HL3: Neuron = field(init=False)
O1: Neuron = field(init=False)
def __post_init__(self):
self.HL1 = Neuron()
self.HL1.weights = np.random.dirichlet(np.ones(4))
self.HL1.bias = np.random.normal()
self.HL2 = Neuron()
self.HL2.weights = np.random.dirichlet(np.ones(4))
self.HL2.bias = np.random.normal()
self.HL3 = Neuron()
self.HL3.weights = np.random.dirichlet(np.ones(4))
self.HL3.bias = np.random.normal()
self.O1 = Neuron()
self.O1.weights = np.random.dirichlet(np.ones(3))
self.O1.bias = np.random.normal()
def compute(self, inputs):
m1 = self.HL1.compute(inputs)
m2 = self.HL2.compute(inputs)
m3 = self.HL3.compute(inputs)
output = self.O1.compute([m1, m2, m3])
return output
MyNeuronNetwork
类是自定义的神经网络,其中的属性是
4个神经元
。
HL1
,
HL2
,
HL3
是
隐藏层
的3个神经元;
O1
是
输出层
的神经元。
__post__init__
函数是为了初始化各个神经元。
因为输入层是4个属性(
\(x_1,x_2,x_3,x_4\)
),所以神经元
HL1
,
HL2
,
HL3
的
weights
初始化为
4个
随机数组成的列表,
偏移(
bias
)用一个随时数来初始化。
对于神经元
O1
,它的输入是隐藏层的3个神经元,所以它的
weights
初始化为
3个
随机数组成的列表,
偏移(
bias
)还是用一个随时数来初始化。
最后还有一个
compute
函数,这个函数描述的就是整个神经网络的计算过程。
首先,根据输入层(
\(x_1,x_2,x_3,x_4\)
)的数据计算隐藏层的神经元(
HL1
,
HL2
,
HL3
);
然后,以隐藏层的神经元(
HL1
,
HL2
,
HL3
)的输出作为输出层的神经元(
O1
)的输入,并将
O1
的计算结果作为整个神经网络的输出。
2.4. 训练模型
上面的神经网络中各个神经元的中的参数(主要是
weights
和
bias
)都是随机生成的,
所以直接使用这个神经网络,效果一定不会很好。
所以,我们需要给神经网络(
MyNeuronNetwork
类)加一个训练函数,用来训练神经网络中各个神经元的参数(也就是个各个神经元中的
weights
和
bias
)。
@dataclass
class MyNeuronNetwork:
# 略...
def train(self, data: pd.DataFrame, target: pd.Series):
## 使用 随机梯度下降算法来训练
pass
上面的
train
函数有两个参数
data
(训练数据)和
target
(训练数据的标签)。
我们使用
随机梯度下降算法
来训练模型的参数。
这里略去了具体的代码,完整的代码可以在文章的末尾下载。
此外,再实现一个预测函数
predict
,传入测试数据集,
然后用我们训练好的神经网络模型来预测测试数据集的标签。
@dataclass
class MyNeuronNetwork:
# 略...
def predict(self, data: pd.DataFrame):
results = []
for idx, row in enumerate(data.values):
pred = self.compute(row)
results.append(round(pred))
return results
2.5. 验证模型效果
最后就是验证模型的效果。
def main():
# 加载数据
ds = load_iris(as_frame=True, return_X_y=True)
# 只用前100条数据
data = ds[0].iloc[:100]
target = ds[1][:100]
# 划分训练数据,测试数据
# test_Size=0.2 表示80%作为训练数据,20%作为测试数据
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.2)
# 创建神经网络
nn = MyNeuronNetwork()
# 用训练数据集来训练模型
nn.train(X_train, y_train)
# 检验模型
# 用训练过的模型来预测测试数据的标签
results = nn.predict(X_test)
df = pd.DataFrame()
df["预测值"] = results
df["实际值"] = y_test.values
print(df)
运行结果可以看出,模型的效果还不错,20条测试数据的预测结果都正确。
3. 总结
本文中的的神经网络示例是为了介绍神经网络的一些基本概念,所以对神经网络做了尽可能的简化,为了方便去手工实现。
而实际环境中的神经网络,不仅神经元的个数,隐藏层的数量极其庞大,而且其计算和训练的方式也很复杂,手工去实现不太可能,
一般会借助
TensorFlow
,
Keras
和
PyTorch
等等知名的python深度学习库来帮助我们实现。
4. 代码下载
代码量不大,总共也就200行不到,感兴趣的话可以下载后运行试试。
simple_nn.zip:
https://url11.ctfile.com/f/45455611-1242350800-e67991?p=6872
(访问密码: 6872)