提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- [1. 简介](#1. 简介)
-
- [1.1 感知机](#1.1 感知机)
- [1.2 激活函数](#1.2 激活函数)
- [1.3 阶跃(Binary step)函数](#1.3 阶跃(Binary step)函数)
- [1.4 Sigmoid函数](#1.4 Sigmoid函数)
- [1.5 Tanh函数](#1.5 Tanh函数)
- [1.6 ReLU函数](#1.6 ReLU函数)
- [1.7 Softmax函数](#1.7 Softmax函数)
- [1.8 其它激活函数](#1.8 其它激活函数)
- [1.9 如何选择激活函数](#1.9 如何选择激活函数)
- [2. 神经网络的简单实现](#2. 神经网络的简单实现)
-
- [2.1 代码实现](#2.1 代码实现)
- [3. 应用案例:手写数字识别](#3. 应用案例:手写数字识别)
-
- [3.1 具体实现](#3.1 具体实现)
- [3.2 小批量计算](#3.2 小批量计算)
- 总结
前言
1. 简介
使用深度神经网络进行学习,所以叫做是深度学习
1.2深度学习的特点
使用多层神经网络,能够自动提取数据的多层次特征。机器学习需要我们来提取特征
适合处理非结构化数据,如图像、音频、文本等。
依赖大量数据和计算资源,训练时间较长。
模型复杂,通常被视为"黑箱",解释性较差。
相邻层的神经元相互连接(图中下一层每个神经元都与上一层所有神经元连接,称为 全连接),每个连接都会有一个 权重。
神经元中的信息逐层传递(一般称为 前向传播forward),上一层神经元的输出作为下一层神经元的输入。
1.1 感知机
感知机(Perceptron)是二分类模型,接收多个信号,输出一个信号。感知机的信号只有0、1两种取值。


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

可以将输入信号的加权总和转换为输出信号,起到"激活神经元"的作用,所以被称为 激活函数。
1.2 激活函数
激活函数是连接感知机和神经网络的桥梁,在神经网络中起着至关重要的作用。
如果没有激活函数,整个神经网络就等效于单层线性变换,不论如何加深层数,总是存在与之等效的"无隐藏层的神经网络"。激活函数必须是非线性函数,也正是激活函数的存在为神经网络引入了非线性,使得神经网络能够学习和表示复杂的非线性关系。
1.3 阶跃(Binary step)函数
它可以为输入设置一个"阈值";一旦超过这个阈值,就切换输出(0或者1)。这种函数被称为"阶跃函数"。

java
#阶跃函数
def step_function0(x):
if x > 0:
return 1
else:
return 0
import numpy as np
def step_function(x):
# 大于0的为true,但是由于是int类型,所以为1
return np.array(x>0, dtype=int)
if __name__ == '__main__':
x = np.array([1, 2, 3, 4, 5,-1,-2,-3,-4])
print(step_function(x))

1.4 Sigmoid函数
Sigmoid(也叫Logistic函数)是平滑的、可微的,能将任意输入映射到区间(0,1)。常用于二分类的输出层。但因其涉及指数运算,计算量相对较高。
Sigmoid的输入在[-6,6]之外时,其输出值变化很小,可能导致信息丢失。
Sigmoid的输出并非以0为中心,其输出值均>0,导致后续层的输入始终为正,可能影响后续梯度更新方向。
Sigmoid的导数范围为(0,0.25),梯度较小。当输入在[-6,6]之外时,导数接近0,此时网络参数的更新将会极其缓慢。使用Sigmoid作为激活函数,可能出现梯度消失(在逐层反向传播时,梯度会呈指数级衰减)。
java
def sigmoid(x):
return 1/(1+np.exp(-x))

正数都在0.5以上,负数都在0.5以下
1.5 Tanh函数


Tanh(双曲正切)将输入映射到区间(-1,1)。其关于原点中心对称。常用在隐藏层。
输入在[-3,3]之外时,Tanh的输出值变化很小,此时其导数接近0。
Tanh的输出以0为中心,且其梯度相较于Sigmoid更大,收敛速度相对更快。但同样也存在梯度消失现象。导数为0了
java
print(np.tanh(x))

1.6 ReLU函数

ReLU(Rectified Linear Unit,修正线性单元)会将小于0的输入转换为0,大于等于0的输入则保持不变。ReLU定义简单,计算量小。常用于隐藏层。
ReLU作为激活函数不存在梯度消失。当输入小于0时,ReLU的输出为0,这意味着在神经网络中,ReLU激活的节点只有部分是"活跃"的,这种稀疏性有助于减少计算量和提高模型的效率
当神经元的输入持续为负数时,ReLU的输出始终为0。这意味着神经元可能永远不会被激活,从而导致"神经元死亡"问题。这会影响模型的学习能力,特别是如果大量的神经元都变成了"死神经元"。为解决此问题,可使用Leaky ReLU来代替ReLU作为激活函数。Leaky ReLU在负数区域引入一个小的斜率来解决"神经元死亡"问题。

java
print(np.maximum(0,x))

1.7 Softmax函数

Softmax将一个任意的实数向量转换为一个概率分布,确保输出值的总和为1,是二分类激活函数Sigmoid 在多分类上的推广。Softmax常用于多分类问题的输出层,用来表示类别的预测概率。
Softmax会放大输入中较大的值,使得最大输入值对应的输出概率较大,其他较小的值会被压缩。即在类别之间起到了一定的区分作用。

java
def softmax(x):
return np.exp(x) / np.sum(np.exp(x), axis=0)

java
#考虑输入为矩阵
def softmax1(x):
if x.ndim == 2:
# 对行求和
x = x - np.max(x,axis=1)
y = np.exp(x) / np.sum(np.exp(x), axis=1)
return y
x = x - np.max(x)
# 防止e的x次方太大溢出了
return np.exp(x) / np.sum(np.exp(x))
对于二维数据进行softmax这样是不行的
java
# np.max(x, axis=1)返回(2,)形状(一维数组),本质是NumPy 在按指定轴计算后,会自动 "挤压" 掉长度为 1 的维度
# 原始形状:(行数, 列数) = (2, 3)
# 按axis=1计算后:移除 "列维度",仅剩 "行维度",最终形状变为(2,)(一维数组)。所以这个不能进行广播运算
# NumPy 的广播运算(Broadcasting)本质是让形状不同但 "兼容" 的数组,在不复制数据的前提下,按规则扩展成相同形状,再进行逐元素操作
# 判断两个数组能否广播,以及如何扩展,只看从后往前对比它们的维度,满足以下 2 个条件即可:
# 两个数组的对应维度大小相等;
# 或者其中一个数组的对应维度大小为 1(可以被扩展成另一个数组的维度大小)。
java
场景 2:正确场景((2,3) 和 (2,1) 可以广播)
数组 A:形状 (2, 3)
数组 B:形状 (2, 1)(用keepdims=True得到的二维数组)
第一步:从后往前对比维度:
第 2 维:A 的 3 vs B 的 1 → B 的维度为 1,满足规则,可扩展成 3;
第 1 维:A 的 2 vs B 的 2 → 维度相等,满足规则;
第二步:扩展 B 的第 2 维(不复制真实数据,只在逻辑上扩展):
原 B:[[3], [6]] → 扩展后逻辑上变为 [[3,3,3], [6,6,6]](形状和 A 一致);
第三步:逐元素相减:A 的每个元素减去 B 对应位置的元素,得到结果。
java
如果想让结果保持二维形状(比如你之前需要的(2,1)),只需给np.max加一个keepdims=True参数,它会强制保留被计算的维度(即使长度为 1)
np.sum也是这个道理
java
#考虑输入为矩阵
def softmax1(x):
if x.ndim == 2:
# 对行求和
x = x - np.max(x,axis=1,keepdims=True)
y = np.exp(x) / np.sum(np.exp(x), axis=1,keepdims=True)
return y
x = x - np.max(x)
# 防止e的x次方太大溢出了
return np.exp(x) / np.sum(np.exp(x))
java
X = np.array([[0,1,2],[3,4,5]])
print(softmax1(X))
这样就计算出了,每个数据在每行中出现的概率值
每一行和为1,而且值越大,概率值就越大
1.8 其它激活函数
隐藏层用的最多的就是ReLU
输出层,用的最多的是,二分类的话,就是Sigmoid,多分类是Softmax

java
def identity(x):
return x

这个也是用于隐藏层的



1.9 如何选择激活函数
1)隐藏层
首选ReLU,如果效果不好可尝试Leaky ReLU等。
Sigmoid在隐藏层易导致梯度消失,应尽量避免。
Tanh的输出均值为0,对中心化数据更友好,但仍可能引发梯度消失,仅适用于浅层网络。
2)输出层
二分类选择Sigmoid。
多分类选择Softmax。
回归默认选择Identity。
2. 神经网络的简单实现
深度神经网络由多个层(layer)组成,通常将其称之为 模型(Model)。整个模型接受原始 输入(特征),生成 输出(预测),并包含一些 参数。而在模型内部,每个单独的层都会接受一些输入(由前一层提供),生成输出(到下一层的输入),并包含一组参数;层层向下传递,就可以得到最终的输出值。
神经网络中的参数,就是每一层的权重和偏置。

几次激活,就是几层,或者说输入层就是0层
权值w一般都是不一样的
h就是激活函数
W是2*3的,因为输入层有两个,输出层有三个
输出层有三个,所以偏置就是三个
2.1 代码实现
init_network():对参数进行初始化,每一个权重参数都是一个矩阵(二维),每一个偏置参数则是一个数组(一维);
forward():前向传播,将输入信号转换为输出信号的处理操作。
这里的激活函数,隐藏层用sigmoid,输出层用identity(恒等函数)。

就是做一个这个神经网络,我们只需要定义好每层的W和B就可以了
java
import numpy as np
from functions import sigmoid,identity
#初始化神经网络
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']
#逐层进行计算传递,dot就是矩阵乘法
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(a3)
return y
#测试
network = init_network()
x = np.array([1.0,0.5])
y = forward(network,x)
print(y)

3. 应用案例:手写数字识别
我们依然使用Digit Recognizer数据集来进行手写数字识别:https://www.kaggle.com/competitions/digit-recognizer。
文件train.csv中包含手绘数字(从0到9)的灰度图像,每张图像为28×28像素,共784像素。每个像素有一个0到255的值表示该像素的亮度。
文件第1列为标签,之后784列分别为784个像素的亮度值。
就是直接把28*28放在一行了
我们的任务,就是要搭建一个神经网络,实现它的前向传播;也就是要根据输入的数据(28×28 = 784数据点表示的图像),推断出它到底是哪个数字,这个过程也被称为"推理"。
这里,我们构建的也是一个三层神经网络,输入层应该有784个神经元,输出层有10个神经元(表示0~9的分类结果);中间设置2个隐藏层,第一个隐藏层有50个神经元,第二个隐藏层有100个神经元。

这里的参数是需要 学习 得到的;我们假设已经学习完毕,直接从保存好的文件nn_sample中进行读取即可。
nn_sample里面存储的就是W1,W2,W3,b1,b2,b3这里我们直接给出来
这是一个多分类问题,所以最后一个激活函数应该为softmax
3.1 具体实现
java
import numpy as np
import pandas as pd
#加载network
import joblib
#数据集划分
from sklearn.model_selection import train_test_split
#归一化
from sklearn.preprocessing import MinMaxScaler
#激活函数
from 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, random_state=42)
#特征工程:归一化
scaler = MinMaxScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.transform(x_test)
return x_test, y_test
#初始化神经网络
def init_network():
#直接从文件 中加载----》是已经经过模型训练好的最佳的参数,所以我们不用训练模型了,就不用训练数据了
network = joblib.load("./data/nn_sample")
return network
#前向传播
def forward(network,x):
w1,w2,w3 = network['W1'],network['W2'],network['W3']
b1,b2,b3 = network['b1'],network['b2'],network['b3']
#逐层进行计算传递,dot就是矩阵乘法
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
java
#获取测试数据
x, y = get_data()
#创建模型加载参数
network = init_network()
y_predict = forward(network,x)
print(x.shape)
print(y.shape)
print(y_predict.shape)

这里的10表示每个分类的概率
然后才是预测分类
java
#获取测试数据
x, y = get_data()
#创建模型加载参数
network = init_network()
y_proba = forward(network,x)
print(x.shape)
print(y.shape)
print(y_proba.shape)
#取出每一行最大值得到索引----》就是概率最大的预测值--》索引就是数值
y_pre = np.argmax(y_proba, axis=1)
#计算分类准确率----》sum计算预测正确的个数
accuracy_cnt = np.sum(y_pre == y)
n = x.shape[0]
print(accuracy_cnt/n)

3.2 小批量计算
就是把测试集划分为小的数据
java
#获取测试数据
x, y = get_data()
#创建模型加载参数
network = init_network()
#定义变量,100份小数据
batch_size = 100
accuracy_cnt = 0
n = x.shape[0]
# batch_size是步长
for i in range(0,n,batch_size):
#取出当前批次数据
x_batch = x[i:i+batch_size]
y_batch = forward(network,x_batch)
# 计算分类准确率
y_pre = np.argmax(y_batch, axis=1)
accuracy_cnt += np.sum(y_pre == y[i:i+batch_size])
print(accuracy_cnt/n)
