从逻辑回归到浅层神经网络:深度学习入门核心概念详解(附源码)
在人工智能飞速发展的今天,深度学习已成为推动计算机视觉、自然语言处理、语音识别等领域的核心技术。但对于刚入门的同学来说,面对“激活函数”“反向传播”“梯度下降”等术语,常常感到一头雾水。本文将从最基础的逻辑回归出发,逐步带你理解深度学习的核心思想,并解释为什么我们需要非线性、为什么向量化如此重要、以及浅层神经网络是如何工作的。
在人工智能飞速发展的今天,深度学习已成为推动计算机视觉、自然语言处理、语音识别等领域的核心技术。但对于刚入门的同学来说,面对“激活函数”“反向传播”“梯度下降”等术语,常常感到一头雾水。本文将从最基础的逻辑回归出发,逐步带你理解深度学习的核心思想,并解释为什么我们需要非线性、为什么向量化如此重要、以及浅层神经网络是如何工作的。
1.机器学习 vs 深度学习:有何不同?
很多人会混淆“机器学习”和“深度学习”。其实,深度学习是机器学习的一个子集。
- 机器学习:依赖人工设计特征(feature engineering),使用如逻辑回归、支持向量机(SVM)、决策树等算法进行建模。适用于中小规模数据。
- 深度学习:通过多层神经网络自动从原始数据中学习特征,无需大量人工干预。擅长处理图像、语音、文本等高维复杂数据,但需要大量数据和强大算力支持。
2.逻辑回归:不只是“回归”
尽管名字里有“回归”,但逻辑回归实际上是一个分类算法,尤其用于二分类问题(比如判断邮件是否为垃圾邮件)。
2.1核心:Sigmoid 函数
逻辑回归通过 Sigmoid 函数将线性输出映射为概率:
y^=σ(z)=1+e−z1,其中 z=wTx+b
输出 y^ 表示 P(y=1∣x),即在给定输入 x 的条件下,标签为 1 的概率。
2.2为什么不用平方误差做损失函数?
如果用平方误差(MSE)作为损失函数,代价函数 J(w,b) 会是非凸的,存在多个局部最小值,导致梯度下降难以找到全局最优解。
因此,逻辑回归采用对数损失函数(Log Loss):
L(y^,y)=−[ylog(y^)+(1−y)log(1−y^)]
这个函数是凸函数,只有一个全局最小值,非常适合优化。
最终的代价函数是对所有训练样本的平均损失:
J(w,b)=m1i=1∑mL(y^(i),y(i))
3.梯度下降:寻找最优参数
我们的目标是找到一组参数 (w,b),使得 J(w,b) 最小。这时就用到梯度下降法。
基本更新规则:
w:=w−α∂w∂J,b:=b−α∂b∂J
其中 α 是学习率。
- 学习率太大:可能“一步跨过”最优解,甚至发散;
- 学习率太小:收敛速度慢,训练时间长。
实践中常通过实验或学习率调度策略(如衰减)来平衡。
4.向量化:告别 for 循环
在实现神经网络时,如果对每个样本都用 for 循环计算,效率极低。而向量化编程利用 NumPy 等库,将整个批量(batch)的数据一次性送入计算,实现并行加速。
例如,原本需要循环 m 次的前向传播:
for i in range(m):
z[i] = np.dot(w.T, X[:, i]) + b
可以简化为一行:
Z = np.dot(W.T, X) + b
不仅代码简洁,运行速度也提升数十倍!
5.正向传播 vs 反向传播
- 正向传播(Forward Propagation):从输入层开始,逐层计算每一层的激活值,直到输出层得到预测结果。
- 反向传播(Backpropagation):从输出层开始,利用链式法则计算损失函数对每个参数的梯度,用于更新权重。
反向传播的本质是高效计算梯度,它是深度学习得以训练深层网络的关键。
6.为什么必须用非线性激活函数?
这是很多初学者容易忽略的关键点!
假设我们只使用线性激活函数(比如 a=z),那么无论神经网络有多少层,最终的输出仍然是输入的线性组合:
y=W2(W1x+b1)+b2=(W2W1)x+(W2b1+b2)
这等价于一个单层线性模型——多层网络失去了意义!
因此,非线性激活函数是神经网络表达复杂函数能力的源泉。没有它,深度学习就只是“深度”的幻觉。
7.常用激活函数对比
1. Sigmoid
- 输出范围:(0, 1)
- 缺点:梯度在两端接近 0,易导致梯度消失;输出不是以 0 为中心,影响收敛。
2. Tanh(双曲正切)
- 输出范围:(-1, 1),以 0 为中心,比 Sigmoid 更好。
- 但同样存在梯度消失问题。
3. ReLU(Rectified Linear Unit)
- 定义:ReLU(z)=max(0,z)
- 优点:计算简单,梯度恒为 1(当 z > 0),极大缓解梯度消失,显著加快训练速度。
- 缺点:存在“神经元死亡”问题(负区间梯度为 0),但实践中效果极佳。
目前,ReLU 及其变体(如 Leaky ReLU、ELU)已成为默认选择。
8.浅层神经网络的反向传播:直观理解
对于一个含一个隐藏层的浅层网络,反向传播可以这样理解:
- 先写出最终损失 L 关于输出 y^ 的表达式;
- 利用链式法则,一层层“回溯”到输入层,求出对 W[2],b[2],W[1],b[1] 的偏导;
- 用这些梯度更新参数。
虽然公式看起来复杂,但一旦掌握链式法则和矩阵求导规则,就能轻松推导。更重要的是,现代框架(如 PyTorch、TensorFlow)已自动完成这一过程,我们只需关注模型结构和数据。
10.浅层神经网络举例实现
'''
O
O O
O O O
O
n_x = 2
n_h = 4
n_y = 1
'''
import numpy as np
import matplotlib.pyplot as plt
import sklearn
import sklearn.datasets
import sklearn.linear_model
from load_data import load_planar_dataset
def sigmoid(z):
s = 1/(1+np.exp(-z))
return s
# 定义网络结构
# 网络输入输出以及隐藏层神经元个数
def layer_sizes(X, Y):
n_x = X.shape[] # 输入层神经元个数
n_h = 4 # 隐藏层神经元个数
n_y = Y.shape[] # 输出层神经元个数
return (n_x,n_h,n_y)
# 初始化模型参数
# 随机初始化权重以及偏置为0
def initialize_parameters(n_x,n_h,n_y):
'''
输入每层的神经元数量
返回:隐藏层、输出层的参数
'''
# 1.初始化权重
np.random.seed(2) #指定随机种子
# 2.创建隐藏层的两个参数
# randn:标准正态分布
# rand:均匀分布[0,1)
# 0.01权重缩放因子,激活值更合适,训练更稳定
# 为什么是(n_h,n_x)? 矩阵乘法 W·X + b X是(n_x,2)
W1 = np.random.randn(n_h,n_x)*0.01
# zeros :接受一个矩阵元组
b1 = np.zeros((n_h,1)) # 4行1列
# 3.创建输出层的两个参数
W2 = np.random.randn(n_y,n_h)*0.01
b2 = np.randm.zeros((n_y,1))
# 4.使用断言确保数据格式正确
assert(W1.shape == (n_h,n_x))
assert(b1.shape == (n_h,1))
assert(W2.shape == (n_y,n_h))
assert(b2.shape == (n_y,1))
parameters = {
"W1":W1,
"b1":b1,
"W2":W2,
"b2":b2
}
return parameters
# 循环中的第一步:前向传播
def forward_propagation(X,parameters):
'''
输入:(n_x,)
'''
# 1.取出每一层的参数
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
# 2.计算隐藏层的激活值
Z1 = np.dot(W1,X) + b1
A1 = np.tanh(Z1) # 激活函数tanh
# 3.计算输出层的激活值
Z2 = np.dot(W2,A1) + b2
A2 = sigmoid(Z2) # 激活函数sigmoid
assert(A2.shape == (1,X.shape[1]))
cache = {
"Z1":Z1,
"A1":A1,
"Z2":Z2,
"A2":A2
}
return A2,cache
# 计算损失函数
def compute_cost(A2,Y,parameters):
m = Y.shape[1] # 样本数量
logpro = np.multiply(np.log(A2),Y) + np.multiply(np.log(1-A2),1-Y)
cost = (-1 / m)* np.sum(logpro)
cost = np.squeeze(cost) #确保cost是一个标量
assert(isinstance(cost,float))
return cost
# 循环中的第二步:反向传播
def backward_propagation(parameters,cache,X,Y):
# 1.获取参数和缓存中的输出
W1 = parameters["W1"]
W2 = parameters["W2"]
A1 = cache["A1"]
A2 = cache["A2"]
m = X.shape[1]
# 2.反向传播计算梯度
# 最后一层的参数梯度计算
dZ2 = A2 - Y
dW2 = (1/m) * np.dot(dZ2,A1.T)
db2 = (1/m) * np.sum(dZ2,axis=1)
# 隐藏层的参数梯度计算
dZ1 = np.dot(W2.T,dZ2) * (1 - np.power(A1,2))
dW1 = (1/m) * np.dot(dZ1,X.T)
db1 = (1/m) * np.sum(dZ1,axis=1)
grads = {
"dW1":dW1,
"db1":db1,
"dW2":dW2,
"db2":db2
}
return grads
# 循环中的第三步:参数更新
def update_parameters(parameters,grads,learning_rate=1.2):
# 1.获取参数和梯度
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
dW1 = grads["dW1"]
db1 = grads["db1"]
dW2 = grads["dW2"]
db2 = grads["db2"]
# 2.更新参数
W1 = W1 - learning_rate * dW1
b1 = b1 - learning_rate * db1.reshape(b1.shape)
W2 = W2 - learning_rate * dW2
b2 = b2 - learning_rate * db2.reshape(b2.shape)
parameters = {
"W1":W1,
"b1":b1,
"W2":W2,
"b2":b2
}
return parameters
# 整合模型
def nn_model(X,Y,n_h,num_iterations=10000,print_cost=False):
np.random.seed(3)
n_x = layer_sizes(X,Y)[0]
n_y = layer_sizes(X,Y)[2]
# 1.初始化参数
parameters = initialize_parameters(n_x,n_h,n_y)
# 2.循环迭代
for i in range(0,num_iterations):
# 前向传播
A2,cache = forward_propagation(X,parameters)
# 计算损失函数
cost = compute_cost(A2,Y,parameters)
# 反向传播
grads = backward_propagation(parameters,cache,X,Y)
# 参数更新
parameters = update_parameters(parameters,grads,learning_rate=1.2)
# 每1000次打印一次损失函数
if print_cost and i % 1000 == 0:
print("迭代第 %i 次,损失函数为:%f" % (i,cost))
return parameters
从逻辑回归到浅层神经网络,我们看到了深度学习如何一步步构建起强大的表达能力。理解这些基础概念,不仅能帮助你写出更高效的代码,更能让你在面对复杂模型时保持清晰的思路。
希望这篇文章能为你打开深度学习的大门。如果你觉得有收获,欢迎点赞、收藏、转发!也欢迎在评论区交流你的学习心得~
更多推荐



所有评论(0)