人工智能之神经网络基础

举报
tea_year 发表于 2025/10/31 17:54:38 2025/10/31
【摘要】 一、 神经网络基础1.1 神经网络的构成在生物体中,神经元 是神经系统最基本的结构和功能单位。神经元从树突接收其它神经元细胞发出的电化学刺激脉冲,这些脉冲叠加后,一旦强度达到临界值,这个神经元就会产生动作电位,沿着轴突发送电信号。轴突将刺激传到末端的突触,电信号触发突触上面的电压敏感蛋白,把一个内含神经递质的小泡(突触小体)推到突触的膜上,从而释放出突触小体中的神经递质。这些化学物质会扩散到...

一、 神经网络基础

1.1 神经网络的构成

在生物体中,神经元神经系统最基本的结构和功能单位

神经元从树突接收其它神经元细胞发出的电化学刺激脉冲,这些脉冲叠加后,一旦强度达到临界值,这个神经元就会产生动作电位,沿着轴突发送电信号。

轴突将刺激传到末端的突触,电信号触发突触上面的电压敏感蛋白,把一个内含神经递质的小泡(突触小体)推到突触的膜上,从而释放出突触小体中的神经递质。这些化学物质会扩散到其它神经元的树突或轴突上。

1.1.1 基本概念和结构

人工神经网络(Artificial Neural Network,ANN)简称 神经网络(NN),是一种模仿生物神经网络结构和功能的计算模型。大多数情况下人工神经网络能在外界信息的基础上改变内部结构,是一种自适应系统(adaptive system),通俗地讲就是具备学习功能。


人工神经网络中的神经元,一般可以对多个输入进行加权求和,再经过特定的“激活函数”转换后输出。

使用多个神经元就可以构建多层神经网络,最左边的一列神经元都表示输入,称为 输入层;最右边一列表示网络的输出,称为 输出层;输入层与输出层之间的层统称为 中间层(隐藏层)

相邻层的神经元相互连接(图中下一层每个神经元都与上一层所有神经元连接,称为 全连接),每个连接都会有一个 权重

神经元中的信息逐层传递(一般称为 前向传播forward),上一层神经元的输出作为下一层神经元的输入。

1.1.2 复习感知机

我们先复习一下在机器学习部分学习过的感知机。

感知机(Perceptron)是二分类模型,接收多个信号,输出一个信号。感知机的信号只有0、1两种取值。

下图是一个接收两个输入信号的感知机的例子:

是输入信号,是输出信号,是权重,○ 称为神经元或节点。输入信号被送往神经元时,会分别乘以固定的权重。神经元会计算传送过来的信号的总和,只有当这个总和超过某个界限值时才会输出1,也称之为神经元被激活。

(2.1)

这里将界限的阈值设为0。除了权重之外,还可以增加一个参数,被称为 偏置

感知机的多个输入信号都有各自的权重,这些权重发挥着控制各个信号的重要性的作用,权重越大,对应信号的重要性越高。偏置则可以用来控制神经元被激活的容易程度。

1.1.3 引入激活函数

可以看出,式(2.1)中包含了两步处理操作:首先按照输入各自的权重及偏置,计算出一个加权总和;然后再根据这个总和与阈值的大小关系,决定输出0还是1。

如果定义一个函数:

(2.2)

那么式(2.1)就可以简化为:

(2.3)

为了更明显地表示出两个处理步骤,可以进一步写成:

(2.4)

(2.5)

这里,我们将输入信号和偏置的加权总和,记作

可以将输入信号的加权总和转换为输出信号,起到“激活神经元”的作用,所以被称为 激活函数

1.2 激活函数

1.2.1 激活函数的作用

激活函数是连接感知机和神经网络的桥梁,在神经网络中起着至关重要的作用。

如果没有激活函数,整个神经网络就等效于单层线性变换,不论如何加深层数,总是存在与之等效的“无隐藏层的神经网络”。激活函数必须是非线性函数,也正是激活函数的存在为神经网络引入了非线性,使得神经网络能够学习和表示复杂的非线性关系。

1.2.2 阶跃(Binary step)函数

之前的感知机中,式(2.2)表示的 就是最简单的激活函数,它可以为输入设置一个“阈值”;一旦超过这个阈值,就切换输出(0或者1)。这种函数被称为“阶跃函数”。


阶跃函数的导数恒为0。

阶跃函数可以用代码实现如下:

def step_function(x):
if x > 0:
  return 1
else:
  return 0

这里的x只能取一个数值(浮点数)。如果我们希望直接传入Numpy数组进行批量化的操作,可以改进如下:

def step_function(x):
   return np.array(x > 0, dtype=int)

1.2.3 Sigmoid函数



Sigmoid(也叫Logistic函数)是平滑的、可微的,能将任意输入映射到区间(0,1)。常用于二分类的输出层。但因其涉及指数运算,计算量相对较高。

Sigmoid的输入在[-6,6]之外时,其输出值变化很小,可能导致信息丢失。

Sigmoid的输出并非以0为中心,其输出值均>0,导致后续层的输入始终为正,可能影响后续梯度更新方向。

Sigmoid的导数范围为(0,0.25),梯度较小。当输入在[-6,6]之外时,导数接近0,此时网络参数的更新将会极其缓慢。使用Sigmoid作为激活函数,可能出现梯度消失(在逐层反向传播时,梯度会呈指数级衰减)。

Sigmoid函数可以用代码实现如下:

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

1.2.4 Tanh函数



Tanh(双曲正切)将输入映射到区间(-1,1)。其关于原点中心对称。常用在隐藏层。

输入在[-3,3]之外时,Tanh的输出值变化很小,此时其导数接近0。

Tanh的输出以0为中心,且其梯度相较于Sigmoid更大,收敛速度相对更快。但同样也存在梯度消失现象。

1.2.5 ReLU函数

注意:x=0时ReLU函数不可导,此时我们默认使用左侧的函数。

ReLU(Rectified Linear Unit,修正线性单元)会将小于0的输入转换为0,大于等于0的输入则保持不变。ReLU定义简单,计算量小。常用于隐藏层。

ReLU作为激活函数不存在梯度消失。当输入小于0时,ReLU的输出为0,这意味着在神经网络中,ReLU激活的节点只有部分是“活跃”的,这种稀疏性有助于减少计算量和提高模型的效率。

当神经元的输入持续为负数时,ReLU的输出始终为0。这意味着神经元可能永远不会被激活,从而导致“神经元死亡”问题。这会影响模型的学习能力,特别是如果大量的神经元都变成了“死神经元”。为解决此问题,可使用Leaky ReLU来代替ReLU作为激活函数。Leaky ReLU在负数区域引入一个小的斜率来解决“神经元死亡”问题。

ReLU函数可以用代码实现如下:

def relu(x):
   return np.maximum(0, x)

1.2.6 Softmax函数

Softmax将一个任意的实数向量转换为一个概率分布,确保输出值的总和为1,是二分类激活函数Sigmoid在多分类上的推广。Softmax常用于多分类问题的输出层,用来表示类别的预测概率。

Softmax会放大输入中较大的值,使得最大输入值对应的输出概率较大,其他较小的值会被压缩。即在类别之间起到了一定的区分作用。

Softmax函数可以用代码实现如下:

def softmax(x):
  return np.exp(x) / np.sum(np.exp(x))

考虑到x较大时,指数函数的值会非常大,容易溢出,可以改进为:

def softmax(x):
   x = x - np.max(x) # 溢出对策
   return np.exp(x) / np.sum(np.exp(x))

考虑到x为二维数组(矩阵)的情况,可以进一步写为:

def softmax(x):
   if x.ndim == 2:
      x = x.T
      x = x - np.max(x, axis=0)
     y = np.exp(x) / np.sum(np.exp(x), axis=0)
   return y.T

   x = x - np.max(x) # 溢出对策
   return np.exp(x) / np.sum(np.exp(x))

1.2.7 其他常见激活函数

%5) Identity(恒等函数)


%5) Leaky ReLULeaky Rectified Linear Unit


%5) PReLUParametric Rectified Linear Unit


这里是一个可训练的参数,而非固定的常数。

%5) RReLU(Randomized Leaky ReLU

这里是一个在训练时从一个均匀分布中随机选择的参数。

%5) ELUExponential Linear Unit


%5) Swish(也称Sigmoid Linear UnitSiLU


%5) Softplus


1.2.8 如何选择激活函数

%5) 隐藏层

首选ReLU,如果效果不好可尝试Leaky ReLU等。

Sigmoid在隐藏层易导致梯度消失,应尽量避免。

Tanh的输出均值为0,对中心化数据更友好,但仍可能引发梯度消失,仅适用于浅层网络。

%5) 输出层

二分类选择Sigmoid。

多分类选择Softmax。

回归默认选择Identity。

1.3 神经网络的简单实现

深度神经网络由多个层(layer)组成,通常将其称之为 模型(Model)。整个模型接受原始 输入(特征),生成 输出(预测),并包含一些 参数。而在模型内部,每个单独的层都会接受一些输入(由前一层提供),生成输出(到下一层的输入),并包含一组参数;层层向下传递,就可以得到最终的输出值。

神经网络中的参数,就是每一层的权重和偏置。

1.3.1 三层神经网络

我们这里以一个三层神经网络为例,实现从输入到输出的处理计算,这个过程就是 前向传播(forward)

简单起见,我们的输入层(第0层)有2个神经元;第1个隐藏层(第1层)有3个神经元;第2个隐藏层(第2层)有2个神经元;输出层(第3层)有2个神经元。

1.3.2 各层之间的信号传递

上面只是三层网络的示意图,实际上每层还应该有偏置,各输入信号加权总和还要经过激活函数的处理。接下来逐层进行分析,考察信号在各层之间传递的过程。

%5) 输入层(第0层)→ 第1层

权重和神经元的上标(1)表示网络层号。而下标对于神经元来说,就是这一层内的“索引号”;对于权重来说则包含两个数字,分别代表前一层和后一层神经元的索引号。所以, 就表示这是第1层的权重(输入层到第1层),并且是从第2个输入节点到第1层第1个节点。偏置的下标只有1个,因为前一层的偏置节点只有一个。

同样,对于第1层的第2个、第3个神经元,有:

我们可以直接写成矩阵乘法的形式。其中,可以看到,由于有2个输入节点、3个第1层节点,所以全连接层的权重W就应该是一个2×3的矩阵。这样,我们就可以很容易地利用Numpy中的矩阵乘法计算出输出信号值了。

%5) 第1层 → 第2层

第1层到第2层的处理类似,权重参数应该是一个3×2的矩阵。

%5) 第2层 → 输出层(第3层)

第2层到输出层的处理也类似,权重参数为2×2的矩阵;不过输出层的激活函数一般与隐藏层是不同的,这里用 表示。

1.3.3 代码实现

我们可以将神经网络的所有参数(每一层的权重w和偏置b),保存在一个字典network中;并定义函数:

init_network():对参数进行初始化,每一个权重参数都是一个矩阵(二维),每一个偏置参数则是一个数组(一维);

forward():前向传播,将输入信号转换为输出信号的处理操作。

这里的激活函数,隐藏层用sigmoid,输出层用identity(恒等函数)。

具体代码如下:

import numpy as np
from common.functions import sigmoid, identity_function

def init_network():
network = {}
network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
network['b1'] = np.array([0.1, 0.2, 0.3])
network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
network['b2'] = np.array([0.1, 0.2])
network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
network['b3'] = np.array([0.1, 0.2])

return network

def forward(network, x):
w1, w2, w3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']

a1 = np.dot(x, w1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, w2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, w3) + b3
y = identity_function(a3)

return y

network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)

print(y)

1.4 应用案例:手写数字识别

我们依然使用Digit Recognizer数据集来进行手写数字识别:https://www.kaggle.com/competitions/digit-recognizer

文件train.csv中包含手绘数字(从0到9)的灰度图像,每张图像为28×28像素,共784像素。每个像素有一个0到255的值表示该像素的亮度。

文件第1列为标签,之后784列分别为784个像素的亮度值。

我们的任务,就是要搭建一个神经网络,实现它的前向传播;也就是要根据输入的数据(28×28 = 784数据点表示的图像),推断出它到底是哪个数字,这个过程也被称为“推理”。

这里,我们构建的也是一个三层神经网络,输入层应该有784个神经元,输出层有10个神经元(表示0~9的分类结果);中间设置2个隐藏层,第一个隐藏层有50个神经元,第二个隐藏层有100个神经元。这里的参数是需要 学习 得到的;我们假设已经学习完毕,直接从保存好的文件nn_sample中进行读取即可。

具体代码如下:

import numpy as np
import pandas as pd
import joblib
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from common.functions import sigmoid, softmax

def get_data():

# 加载数据集
data = pd.read_csv("../data/train.csv")
# 划分训练集和测试集
X = data.drop("label", axis=1)
y = data["label"]
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
# 归一化
preprocessor = MinMaxScaler()
x_train = preprocessor.fit_transform(x_train)
x_test = preprocessor.transform(x_test)
return x_test, t_test

def init_network():

# 加载模型
network = joblib.load("../data/nn_sample")

return network

def predict(network, x):
w1, w2, w3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']

a1 = np.dot(x, w1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, w2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, w3) + b3
y = softmax(a3)

return y

x, t = get_data()
network = init_network()

batch_size = 100 # 批数量
accuracy_cnt = 0

for i in range(0, len(x), batch_size):
x_batch = x[i:i+batch_size]
y_batch = predict(network, x_batch)
p = np.argmax(y_batch, axis=1)
accuracy_cnt += np.sum(p == t[i:i+batch_size])

print("Accuracy:" + str(float(accuracy_cnt) / len(x)))


【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。