激活函数的本质作用:
-
非线性变换:激活函数引入非线性变换,使得神经网络可以学习和表示更复杂的函数关系。没有激活函数,神经网络只会执行线性变换,无法处理复杂的数据。
-
归一化:一些激活函数(如sigmoid和tanh)会将输入值归一化到一定范围内(例如0到1或-1到1),有助于稳定网络的训练过程。
-
导数特性:激活函数的导数用于反向传播算法中,影响梯度的传播和更新。
非线性的作用:
-
提升模型表达能力:非线性变换使得神经网络可以模拟任何复杂的函数关系,而不仅仅是线性的。
-
分离数据:在高维空间中,通过非线性变换,可以更好地将不同类别的数据分离开来。
-
学习复杂特征:使神经网络能够学习复杂的特征和模式,从而在分类和回归任务中表现更好。
ReLU的非线性特性:
-
ReLU(Rectified Linear Unit)定义为:f(x) = max(0, x)。当x小于0时,输出为0;当x大于等于0时,输出为x。
-
这种非线性特性使得网络在不同层之间可以组合出复杂的非线性函数,同时避免了梯度消失问题(梯度消失通常发生在sigmoid和tanh激活函数中,特别是在深层网络中)。
x大于0时x=x的改变:
-
当x大于0时,ReLU的输出等于输入,这使得正值部分可以被直接传递和学习,而不会受到限制。这种特性保留了正值输入的原始信息。
-
相比之下,当x小于0时,输出为0,这相当于将负值输入“剪断”,只保留正值部分。这种选择性保留正值的方式,使得ReLU在实践中表现出色,能够有效避免梯度消失问题,并加速训练收敛。
例子:二维平面上的分类任务
假设我们有一个二维平面上的分类任务,数据点分别属于两类,我们用红色表示类别1,用蓝色表示类别2。数据点分布如下:
-
类别1 (红色): (1, 1), (2, 2), (3, 3), (4, 4)
-
类别2 (蓝色): (1, 4), (2, 3), (3, 2), (4, 1)
情况一:不使用激活函数的神经网络
模型结构:
-
输入层:2个节点(特征x1, x2)
-
隐藏层:2个节点
-
输出层:1个节点
计算流程:
-
输入层到隐藏层:
-
假设权重和偏置为w1, w2, b。
-
隐藏层输出:h = w1x1 + w2x2 + b(这是线性组合)
-
-
隐藏层到输出层:
-
假设权重和偏置为w3, w4, b'。
-
最终输出:y = w3h1 + w4h2 + b'(依然是线性组合)
-
结果:
由于整个网络的计算都是线性变换,最终的输出是输入的线性组合。我们只能得到一条直线作为决策边界。这条直线无法有效区分红色和蓝色的数据点。
情况二:使用ReLU激活函数的神经网络
模型结构:
-
输入层:2个节点(特征x1, x2)
-
隐藏层:2个节点,使用ReLU激活函数
-
输出层:1个节点
计算流程:
-
输入层到隐藏层:
-
假设权重和偏置为w1, w2, b。
-
隐藏层输出:h = max(0, w1x1 + w2x2 + b)(经过ReLU激活函数)
-
-
隐藏层到输出层:
-
假设权重和偏置为w3, w4, b'。
-
最终输出:y = w3max(0, h1) + w4max(0, h2) + b'(非线性组合)
-
具体比较
-
不使用激活函数的网络:
-
决策边界:一条直线。
-
分类效果:无法区分所有的红色和蓝色点。例如,直线可能会错过某些红色点或蓝色点。
-
-
使用ReLU激活函数的网络:
-
决策边界:可以是复杂的曲线或多条线段的组合。
-
分类效果:能够更好地适应数据的分布。例如,网络可以找到一个弯曲的决策边界,使所有红色点和蓝色点都能正确分类。
-
代码实现部分
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
# 增加数据点
X = np.array([[1, 1], [2, 2], [3, 3], [4, 4], [1, 4], [2, 3], [3, 2], [4, 1],
[1.5, 1.5], [2.5, 2.5], [3.5, 3.5], [1, 3.5], [2, 2.5], [3, 1.5]], dtype=np.float32)
y = np.array([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1], dtype=np.float32).reshape(-1, 1)
# 转换数据为张量
X = torch.tensor(X)
y = torch.tensor(y)
# 划分训练集和测试集
X_train, X_test = X[:10], X[10:]
y_train, y_test = y[:10], y[10:]
# 定义带ReLU激活函数的模型
class NeuralNetWithReLU(nn.Module):
def __init__(self):
"""
初始化NeuralNetWithReLU类。
Args:
无参数。
Returns:
无返回值。
"""
super(NeuralNetWithReLU, self).__init__()
self.layer1 = nn.Linear(2, 10)
self.layer2 = nn.Linear(10, 10)
self.layer3 = nn.Linear(10, 1)
self.relu = nn.ReLU()
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.relu(self.layer1(x))
x = self.relu(self.layer2(x))
x = self.sigmoid(self.layer3(x))
return x
# 定义不带激活函数的模型
class NeuralNetWithoutReLU(nn.Module):
def __init__(self):
super(NeuralNetWithoutReLU, self).__init__()
self.layer1 = nn.Linear(2, 10)
self.layer2 = nn.Linear(10, 10)
self.layer3 = nn.Linear(10, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.sigmoid(self.layer3(x))
return x
# 初始化模型
model_with_relu = NeuralNetWithReLU()
model_without_relu = NeuralNetWithoutReLU()
# 定义损失函数和优化器
criterion = nn.BCELoss()
optimizer_with_relu = optim.Adam(model_with_relu.parameters(), lr=0.01)
optimizer_without_relu = optim.Adam(model_without_relu.parameters(), lr=0.01)
# 训练模型
def train(model, optimizer, X_train, y_train, epochs=5000):
"""
训练神经网络模型。
Args:
model (torch.nn.Module): 待训练的神经网络模型。
optimizer (torch.optim.Optimizer): 优化器,用于更新模型参数。
X_train (torch.Tensor): 训练数据集的特征。
y_train (torch.Tensor): 训练数据集的标签。
epochs (int, optional): 训练轮数,默认为5000。
Returns:
None
"""
for epoch in range(epochs): # 控制训练循环的次数,每个epoch代表一次完整的训练集通过
model.train() # 设置模型为训练模式
optimizer.zero_grad() # 清空梯度
output = model(X_train) # 前向传播计算输出
loss = criterion(output, y_train) # 计算损失
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新模型参数
# 打印每个epoch的损失值
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
# 训练带ReLU激活函数的模型
train(model_with_relu, optimizer_with_relu, X_train, y_train)
# 训练不带激活函数的模型
train(model_without_relu, optimizer_without_relu, X_train, y_train)
# 测试模型
def test(model, X_test, y_test):
# 将模型设置为评估模式
model.eval()
# 不计算梯度
with torch.no_grad():
# 将测试数据传入模型进行预测
output = model(X_test)
# 将输出转换为二值预测结果
predicted = (output > 0.5).float()
# 计算准确率
accuracy = (predicted == y_test).sum().item() / y_test.size(0)
return accuracy
accuracy_with_relu = test(model_with_relu, X_test, y_test)
accuracy_without_relu = test(model_without_relu, X_test, y_test)
print(f'Accuracy with ReLU: {accuracy_with_relu * 100:.2f}%')
print(f'Accuracy without ReLU: {accuracy_without_relu * 100:.2f}%')
# 可视化数据点和决策边界
def plot_decision_boundary(model, X, y, title, filename):
# 获取X的最小值和最大值,并分别减去和加上1作为边界范围
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
# 创建网格坐标,并将其转化为扁平化的一维数组
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
np.arange(y_min, y_max, 0.1))
grid = np.c_[xx.ravel(), yy.ravel()]
# 将网格坐标转化为PyTorch张量
grid_torch = torch.tensor(grid, dtype=torch.float32)
with torch.no_grad():
# 使用模型预测网格坐标对应的标签,并将结果转化为二维形状与xx相同的数组
Z = model(grid_torch).reshape(xx.shape)
# 根据预测结果设定阈值,将预测结果转化为二值形式
Z = Z > 0.5
# 绘制决策边界,使用填充的等高线图表示
plt.contourf(xx, yy, Z, alpha=0.8)
# 绘制数据点,使用散点图表示,颜色根据真实标签y进行设定
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolors='k', marker='o')
# 设置图像标题
plt.title(title)
# 保存图像到指定文件
plt.savefig(filename)
# 关闭图像窗口
plt.close()
# 可视化带ReLU激活函数的模型决策边界
plot_decision_boundary(model_with_relu, X.numpy(), y.numpy().ravel(), "Decision Boundary with ReLU", "decision_boundary_with_relu.png")
# 可视化不带激活函数的模型决策边界
plot_decision_boundary(model_without_relu, X.numpy(), y.numpy().ravel(), "Decision Boundary without ReLU", "decision_boundary_without_relu.png")
输出结果: