跳转到内容

机器学习基础

概论

机器学习(Machine Learning)是人工智能的一个分支,它的核心思想是让计算机从数据中自动学习规律,而不是由人手工编写规则。

监督学习(Supervised Learning)

监督学习是指:训练数据中每个样本都带有"标准答案"(标签 label),模型通过学习输入特征 X 到输出 y 的映射关系,从而对新数据做出预测

形式化地说:给定训练集 D={(x1,y1),(x2,y2),...,(xn,yn)},学习一个函数 f:XY,使得对新的 x 能准确预测 y

  • 回归(Regression): 预测连续数值
  • 分类(Classification):预测离散类别

无监督学习(Unsupervised Learning)

无监督学习是指:训练数据没有标签(没有"标准答案"),模型只能从数据本身的分布和结构中发现规律。

形式化地说:给定训练集 D={x1,x2,...,xn}(没有 y),学习数据中隐含的结构、模式或表示。

  • 聚类(Clustering):把相似样本归为一组,组内相似、组间差异大。如用户分群、新闻聚合、图像分割。
  • 降维(Dimensionality Reduction):把高维数据压缩到低维,保留主要信息。目的:去噪、加速计算、可视化、缓解"维度灾难"。
  • 异常检测(Anomaly Detection):找出与大多数样本明显不同的"离群点"。如信用卡欺诈检测、设备故障预警。
  • 关联规则挖掘(Association Rules):发现事物之间的共现关系。经典例子:"啤酒与尿布"购物篮分析。

回归(Regression)

一元线性回归 Linear Regression with One Variable

线性回归的模型表示为 fw,b(x(i))=wx(i)+b

  • w:权重,x 每增加 1 个单位,y 平均变化 w
  • b:偏置,x=0 时的预测值
  • (x(i), y(i)):表示第 i 个训练样本

只有一个特征 x,用一条直线去拟合它与目标值 y 的关系称为一元线性回归

代价函数 Cost Function

Cost Function是机器学习中用来衡量模型预测结果与真实结果之间差距的函数。用来评价模型的好坏

一元线性回归中我们选择的参数 wb 决定了我们得到的直线相对于我们的训练集的准确程度,模型所预测的值与训练集中实际值之间的差距就是建模误差损失)。

使用平方误差代价函数(Squared Error Cost Function) 作为代价函数,公式为:

J(w,b)=12mi=0m1(fw,b(x(i))y(i))2

其中: fw,b(x(i))=wx(i)+b

  • fw,b(x(i)) 是我们使用参数 w,b 对第 i 个样本的预测值。
  • (fw,b(x(i))y(i))2 是目标值与预测值之间的平方差。
  • 将所有 m 个样本的这些差值求和,再除以 2m,即得到代价 J(w,b)

QQ20260730-152225

NOTE

损失函数对损失值做平方处理这一特性,能保证 “误差曲面” 呈汤碗状的凸曲面。该曲面始终存在最小值,沿所有维度的梯度方向迭代都能找到这个最小值。

J与w、b关系

梯度下降 Gradient Descent

求解最小二乘法就是在线性回归中,它对应的就是让 Cost Function 最小。这个优化问题可以通过解析方法(正规方程)直接求解,也可以通过迭代优化方法(如梯度下降)求解。在机器学习实践中,当数据规模较大或模型复杂时,通常更倾向使用梯度下降

梯度下降背后的思想是:开始时我们随机选择一个参数的组合,计算代价函数,然后我们寻找下一个能让代价函数值下降最多的参数组合。我们持续这么做直到到到一个局部最小值(local minimum)。因为我们并没有尝试完所有的参数组合,所以不能确定我们得到的局部最小值是否便是全局最小值(global minimum),选择不同的初始参数组合,可能会找到不同的局部最小值。

代价函数: J(w,b)=12mi=0m1(fw,b(x(i))y(i))2

批量梯度下降(batch gradient descent)算法的公式为:

重复执行直到收敛:{w=wαJ(w,b)wb=bαJ(w,b)b}

梯度的定义为:

J(w,b)w=1mi=0m1(fw,b(x(i))y(i))x(i)J(w,b)b=1mi=0m1(fw,b(x(i))y(i))

在梯度下降中,在计算微分求导项时,我们需要进行求和运算,所以,在每一个单独的梯度下降中,我们最终都要计算所有个训练样本求和。因此,批量梯度下降法这个名字说明了我们需要考虑所有这一"批"训练样本,而事实上,有时也有其他类型的梯度下降法,不是这种"批量"型的,不考虑整个的训练集,而是每次只关注训练集中的一些小的子集,称为小批量梯度下降(Mini-batch Gradient Descent)

一元回归代码实现

python
import numpy as np
import matplotlib.pyplot as plt

x = np.array([])
y = np.array([])

with open("./dataset/ex1data1.txt", "r", encoding="utf-8") as f:
    for line in f.readlines():
        data = line.strip().split(',')
        x = np.append(x, float(data[0]))
        y = np.append(y, float(data[1]))

m = len(x)
# print(np.hstack([np.ones((m, 1)), x.reshape(-1, 1)]))
X = np.c_[np.ones(m), x]

n = 5000
a = 0.01
# [b, w]
W = np.zeros((2, 1))
# print(W)
for _ in range(n):
    y_pred = X @ W
    # print(X.shape)
    # print(W.shape)
    grad = X.T @ (y_pred - y.reshape(-1, 1))
    W = W - (grad / m) * a

print(W)
plt.scatter(x, y)
x_plot = np.array([np.min(x), np.max(x)])
y_plot = W[0] + x_plot * W[1]
plt.plot(x_plot, y_plot, c="m")
plt.show()

image-20260731170248822

多元线性回归 Multiple Linear Regression

模型表示为: fw,b(x)=w0x0+w1x1+...+wn1xn1+b

向量表示为 fw,b(x)=wx+b ,where is a vector dot product

代价函数 J(w,b)=12mi=0m1(fw,b(x(i))y(i))2 。其中: fw,b(x(i))=wx(i)+b

梯度下降算法

重复执行直到收敛:{wj=wjαJ(w,b)wjfor j = 0..n-1b  =bαJ(w,b)b}

特征数量为n, 参数wj, b, 同步更新如下

J(w,b)wj=1mi=0m1(fw,b(x(i))y(i))xj(i)J(w,b)b=1mi=0m1(fw,b(x(i))y(i))

特征缩放与标准化

当不同自变量取值范围相差较大时,绘制的等高线图上的椭圆会变得瘦长,而梯度下降算法收敛将会很慢,因为每一步都可能会跨过这个椭圆导致振荡。此时,我们需要把所有自变量进行缩放、标准化,使其落在 -1 到 1 之间。

NOTE

标准化改变数据分布,让均值为 0、标准差为 1;归一化改变数据范围,把数据压缩到固定区间。

在机器学习中,标准化是更常用的手段,归一化的应用场景是有限的。因为「仅由极值决定」这个做法过于危险,如果样本中有一个异常大的值,则会将所有正常值挤占到很小的区间,而标准化方法则更加「弹性」,会兼顾所有样本

  1. 特征缩放(Feature scaling)
    • 简单版:将每个正特征除以其最大值 ,结果 [0, 1]
    • 通用版:(x - min) / (max - min),适用于任意特征,结果 [0, 1]
    • 两种方式都将特征归一化到 -1 到 1 范围内。
  2. 均值归一化(Mean normalization)xi:=xiμimaxmin ,结果约 [-0.5, 0.5],数据居中
  3. Z-score 标准化(Z-score normalization)xj(i)=xj(i)μjσj ,$$j$$ 为某个特征,结果均值 0、方差 1,最常用,其中
(5)μj=1mi=0m1xj(i)(6)σj2=1mi=0m1(xj(i)μj)2

其中 μi 是第 i 个特征的均值,σi 是标准差。

WARNING

此外,线性回归并不适用于所有情形,有时我们需要曲线来适应我们的数据,这时候我们也要对特征进行构造,如二次函数、三次函数、幂函数、对数函数等。构造后的新变量就可以当作一个新的特征来使用,这就是多项式回归(Polynomial Regression)。新变量的取值范围可能更大,此时,特征缩放就非常有必要!

多元回归代码实现

python
import numpy as np
import matplotlib.pyplot as plt

x = []
y = []

with open("./dataset/ex1data2.txt", "r", encoding="utf-8") as f:
    for line in f.readlines():
        data = list(map(float, line.strip().split(',')))
        x.append(data[:-1])
        y.append(data[-1])

x = np.array(x)
y = np.array(y)
# print(x.shape, y.shape)

# 标准化
u = np.mean(x, axis=0)
sigma = np.std(x, axis=0)
x = (x - u) / sigma
# print(x)

m, n = x.shape
X = np.c_[np.ones(m), x]
W = np.zeros((n + 1, 1))

niter = 1500
a = 0.01
J_history = []
for i in range(niter):
    y_pred = X @ W
    error = y_pred - y.reshape(-1, 1)
    grad = X.T @ error
    W = W - a * (grad / m)
    J_history.append(np.sum(error ** 2) / (2 * m))

print(W)
# 面积,房间数
input_data = np.array([1650, 3])
input_data = (input_data - u) / sigma
pred = np.r_[np.ones(1), input_data] @ W
print(pred.sum())


# plot the convergence graph
plt.plot(np.arange(len(J_history)), J_history)
plt.xlabel('Number of iterations')
plt.ylabel('Cost J')
plt.show()

Cost Function

分类(Classification)

在分类问题中,我们尝试预测的结果是否属于某一个类,最基础的就是二元的分类问题,更为复杂的则是预测多元的分类问题。

如果用线性回归来解决,即用一条直线拟合结果,当预测值大于阈值时归为正向类,反之归为负向类。然而,当阈值确定时,「反常样本」用于拟合直线时会对其决策边界造成一定偏移,以至于正常样本被归为错误类别。

决策边界右移

逻辑回归 Logistic Regression

模型

线性回归和逻辑回归都属于广义线性模型(Generalized Linear Model)的特殊形式。但由于逻辑函数(Losistic Function,也叫 Sigmoid 函数)将结果映射到 Bernoulli 分布,因此逻辑回归更常用于分类问题。

模型表示为:

fw,b(x(i))=g(wx(i)+b)g(z)=11+ez

if fw,b(x)>=0.5, z>=0 ,预测 y=1

if fw,b(x)<0.5, z<0,预测 y=0

Sigmoid

z=wx(i)+b=0 解出来的线被称为决策边界,它将整个空间划分成两块区域(region),各自属于一个分类。

线性的决策边界多项式的决策边界

代价函数

从训练集中拟合逻辑回归的参数 w b。仍然采用代价函数的思想——找到使代价最小的参数即可。

IMPORTANT

本文定义:损失用于衡量单个样本与其目标值之间的差异,而代价则是训练集上所有损失的综合度量

在线性回归中我们使用平方误差函数作为损失函数,如果继续使用会造成损失曲面非凸(坑坑洼洼很多局部最优),梯度下降会卡住。

平方误差函数损失平面

广义上定义的代价函数如下

J(w,b)=1mi=0m1[loss(fw,b(x(i)),y(i))]

在逻辑回归中,我们使用二元交叉熵作为代价函数,而交叉熵与 Sigmoid复合后,始终保持凸性

loss(fw,b(x(i)),y(i))={log(fw,b(x(i))),y(i)=1log(1fw,b(x(i))),y(i)=0
  • fw,b(x(i)) 为模型预测, y(i) 为第 i 个标签
  • fw,b(x(i))=g(wx(i)+b) where function g is the sigmoid function.

写成一个式子后如下

loss(fw,b(x(i)),y(i))=y(i)log(fw,b(x(i)))(1y(i))log(1fw,b(x(i)))

其中

fw,b(x(i))=g(z(i)),g(z(i))=11+ez(i),z(i)=wx(i)+b

交叉熵函数的损失平面

梯度下降

J(w,b)=1mi=0m1[(y(i)log(fw,b(x(i)))(1y(i))log(1fw,b(x(i)))]

对代价函数求偏导后和线性回归的偏导形式完全相同,但fw,b(x(i)) 的定义不同,它在线性回归的函数基础上套了一层sigmoid函数

(2)J(w,b)wj=1mi=0m1(fw,b(x(i))y(i))xj(i)(3)J(w,b)b=1mi=0m1(fw,b(x(i))y(i))

代码实现

python
import numpy as np
import matplotlib.pyplot as plt

data = np.loadtxt("./dataset/ex2data1.txt", delimiter=",")

# print(type(data))
x = data[:,0:-1]
y = data[:,-1].reshape(-1, 1)
m, n = x.shape

x = (x - x.mean(axis=0)) / x.std(axis=0)
X = np.c_[np.ones(m), x]


a = 0.01
niter = 10000
W = np.zeros((n + 1, 1))
J_history = []

for _ in range(niter):
    z = X @ W
    y_pred = 1 / (1 + np.exp(-z))
    grad = X.T @ (y_pred - y)
    W = W - a * (grad / m)

    cost = -np.mean(y * np.log(y_pred) + (1 - y) * np.log(1 - y_pred))
    J_history.append(cost)


print(W)

fig, (p1, p2) = plt.subplots(1, 2,figsize=(12, 5))
neg_pos = np.where(y == 0)[0]
pos_neg = np.where(y == 1)[0]
p1.scatter(x[neg_pos, 0], x[neg_pos, 1], c='red')
p1.scatter(x[pos_neg, 0], x[pos_neg, 1], c='blue')
p1.set_xlabel('feature 1 - norm')
p1.set_ylabel('feature 2 - norm')
x_plot = np.linspace(x[:, 0].min(), x[:, 0].max(), 100)
p1.plot(x_plot, (-W[1] * x_plot - W[0]) / W[2])

p2 = plt.subplot(1, 2, 2)
p2.plot(np.arange(len(J_history)), J_history)
p2.set_xlabel('Number of iterations')
p2.set_ylabel('Cost J')
plt.show()

决策边界和收敛结果如图

image-20260812160006114

欠拟合和过拟合

欠拟合(Underfitting) 就是模型过于简单,没有充分学习训练数据中的规律。

过拟合(Overfitting) 就是模型把训练数据学得“太细”了,甚至把数据中的噪声和偶然性也当成了规律。

可以从以下几个方面解决过拟合

  • 从模型层面,可以通过 Early Stop、L1/L2 Regularization、Batchnorm、Dropout 等方法;
  • 从特征层面,可以丢弃一些不能帮助我们正确预测的特征,通过手工筛选或 PCA 等降维方法;
  • 从数据层面,可以获取更大的数据集,也可以进行数据增强(Data Augmentation),通过一定规则来扩充数据。

正则化(Regularization)

正则化是一种能够保留所有特征(不必降维而丢失信息)的有效解决过拟合的方法。其思想是在损失函数上加上某些规则(限制),限制参数的解空间,从而减少求出过拟合参数的可能性。

以线性回归为例,在代价函数后加入正则项 λ2mj=0n1wj2,加入这一项会促使梯度下降尽可能减小参数的大小。

J(w,b)=12mi=0m1(fw,b(x(i))y(i))2+λ2mj=0n1wj2

λ正则化参数,如果 λ很大,意味着正则化项占主要地位,有可能导致所有的 wj 都太小了而欠拟合;如果 λ很小,意味着损失函数占主要地位,就有可能过拟合

NOTE

根据正则项的形式,又可分为:二次正则项、一般正则项。二次正则项即为前文提到的形式,更一般的形式为 λj=0n1wjq,当 q 取不同值等高线图的形状为:

正则项的边缘直观表示

从几何空间上来看,损失函数的碗状曲面和正则化项的曲面叠加之后,就是我们要求极值的曲面。特别地,当 q=1 时,称其为 L1 正则化,也叫 Lasso 回归;当 q=2 时,称其为 L2 正则化,也叫岭回归。L2 由于其处处可微的特性,在实际中更常用。

线性回归的正则化

含有正则化的代价函数为

J(w,b)=12mi=0m1(fw,b(x(i))y(i))2+λ2mj=0n1wj2

梯度(求导后)

J(w,b)wj=1mi=0m1(fw,b(x(i))y(i))xj(i)+λmwjJ(w,b)b=1mi=0m1(fw,b(x(i))y(i))

逻辑回归的正则化

含有正则化的代价函数为

J(w,b)=1mi=0m1[y(i)log(fw,b(x(i)))(1y(i))log(1fw,b(x(i)))]+λ2mj=0n1wj2

梯度表达形式同线性回归

J(w,b)wj=1mi=0m1(fw,b(x(i))y(i))xj(i)+λmwjJ(w,b)b=1mi=0m1(fw,b(x(i))y(i))

代码

python
import numpy as np
import matplotlib.pyplot as plt

data = np.genfromtxt("./dataset/ex2data2.txt", delimiter=',')
x = data[:,:-1]
x1 = data[:, 0]
x2 = data[:, 1]
y = data[:,-1].reshape(-1,1)

neg_pos = np.where(y == 0)[0]
pos_pos = np.where(y == 1)[0]
plt.scatter(x[neg_pos,0], x[neg_pos,1], c='red', marker='o')
plt.scatter(x[pos_pos,0], x[pos_pos,1], c='blue', marker='x')


m, n = x.shape
def map_feature(x1, x2):
    X = np.ones((len(x1),1))
    for i in range(1, 6 + 1):
        for j in range(0, i + 1):
            X = np.c_[X, (x1 ** j) * (x2 ** (i - j))]
    return X

X = map_feature(x1, x2) # (118, 28)
W = np.zeros((28, 1))
a = 0.01
lamda = 5
niter = 10000

for _ in range(niter):
    Z = X @ W
    y_pred = 1 / (1 + np.exp(-Z))
    grad = X.T @ (y_pred - y) / m
    regular = (lamda / m) * np.r_[np.zeros((1,1)), W[1:, :]]
    W = W - a * (grad + regular)

print(W)
f1 = np.linspace(-1, 1.25, 50)
f2 = np.linspace(-1, 1.25, 50)
xv,yv = np.meshgrid(f1, f2)
Z = map_feature(xv.flatten(), yv.flatten()) @ W
plt.contour(xv,yv,Z.reshape(50, 50), levels=[0], colors='pink')
plt.title(f"$\lambda = {lamda}$")
plt.xlabel("x0")
plt.ylabel("x1")
plt.show()

增大正则化参数 λ 可以防止模型过拟合

image-20260814145217525

image-20260814145302594

image-20260814145318278

深度学习(Deep Learning)

神经元

神经网络的灵感来自大脑神经元:接收多个输入信号,加权整合,超过阈值就"激活"输出。数学上一个人工神经元就是:z=w1x1+w2x2++wdxd+b=wTx+ba=f(z)

  • w:权重(连接强度),b:偏置(激活难易程度)
  • f激活函数(非线性变换)
  • 一个神经元 + sigmoid 激活 = 逻辑回归。神经网络本质上就是把成千上万个"逻辑回归单元"层层堆叠、首尾相连。

如果没有激活函数(或用线性激活),无论堆多少层,整体都等价于一个单层线性模型(矩阵连乘仍是矩阵)。非线性激活是神经网络能拟合复杂函数的根本原因。

激活函数公式导数特点
ReLUmax(0,z)z>0 为 1,否则 0隐藏层默认选择;缺点:z<0 时梯度为 0("死亡 ReLU")
Leaky ReLUmax(αz,z)z>0 为 1,否则 α给负区间一个小斜率(如 0.01),解决死亡 ReLU
Sigmoid11+ezσ(z)(1σ(z))输出 (0,1) 可作概率;两端梯度≈0 导致梯度消失;现主要用于输出层(二分类)
Softmaxezkjezj多分类输出层专用,输出概率分布

神经网络与前向传播

  • 单层感知机:单个神经元模型(只有输入层 + 输出层,无隐藏层,激活函数为跃迁函数,无法解决XOR线性不可分)
  • 多层感知机(MLP):感知机叠加隐藏层 + 非线性激活 + 反向传播训练,即"经典的前馈全连接神经网络"
  • 神经网络:一般由输入层、隐藏层、输出层构成——MLP 是最基础的一种,还有 CNN、RNN、Transformer 等变体

QQ20260818-095303

  • 输入层:接收特征,不算"层数"
  • 隐藏层:自动学习数据的中间特征表示(深度学习的"深度"就指隐藏层多)
  • 输出层:按任务设计

NOTE

理论上:一个隐藏层 + 足够多的神经元 + 非线性激活,可以逼近任意连续函数。但"能逼近"不等于"容易训练",实践中用更深的网络换取参数效率。

以一个隐藏层的网络为例,前向传播(矩阵形式,X 形状 n×dn 样本,d 特征):

Z[1]=XW[1]+b[1],A[1]=ReLU(Z[1])Z[2]=A[1]W[2]+b[2],Y^=A[2]=g(Z[2])
python
import matplotlib.pyplot as plt
import numpy as np
def load_coffee_data():
    """ Creates a coffee roasting data set.
        roasting duration: 12-15 minutes is best
        temperature range: 175-260C is best
    """
    rng = np.random.default_rng(2)
    X = rng.random(400).reshape(-1, 2)
    X[:, 1] = X[:, 1] * 4 + 11.5  # 12-15 min is best
    X[:, 0] = X[:, 0] * (285 - 150) + 150  # 350-500 F (175-260 C) is best
    Y = np.zeros(len(X))

    i = 0
    for t, d in X:
        y = -3 / (260 - 175) * t + 21
        if (t > 175 and t < 260 and d > 12 and d < 15 and d <= y):
            Y[i] = 1
        else:
            Y[i] = 0
        i += 1

    return (X, Y.reshape(-1, 1))

X, Y = load_coffee_data()
u = X.mean(axis=0)
sigma = X.std(axis=0)
X = (X - u) / sigma
print(f"Temperature Max, Min post normalization: {np.max(X[:,0]):0.2f}, {np.min(X[:,0]):0.2f}")
print(f"Duration    Max, Min post normalization: {np.max(X[:,1]):0.2f}, {np.min(X[:,1]):0.2f}")
print(X.shape, Y.shape)

neg_pos = np.where(Y == 0)
pos_pos = np.where(Y == 1)
plt.scatter(X[neg_pos, 0], X[neg_pos, 1], marker='x', c='r')
plt.scatter(X[pos_pos, 0], X[pos_pos, 1], marker='o', c='b')

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

def my_dense(a_in, W, b):
    return sigmoid(a_in @ W + b)

def forward_prop(X):
    W1_tmp = np.array([[-8.93, 0.29, 12.9], [-0.1, -7.32, 10.81]])
    b1_tmp = np.array([-9.82, -9.28, 0.96])
    W2_tmp = np.array([[-31.18], [-27.59], [-32.56]])
    b2_tmp = np.array([15.41])
    A1 = my_dense(X, W1_tmp, b1_tmp)
    A2 = my_dense(A1, W2_tmp, b2_tmp)
    return A2

X_tst = np.array([
    [200,13.9],  # postive example
    [200,17]])   # negative example
X_tst = (X_tst - u) / sigma
predictions = forward_prop(X_tst)
print(predictions)
yhat = (predictions > 0.5).astype(int)
print(yhat)
plt.show()

反向传播 Back propagation

前向传播算出预测和损失;反向传播用链式法则从输出层往回,逐层算出每个参数对损失的梯度,然后用梯度下降更新。

前向传播(forward) → 计算损失(loss) → 反向传播(backward) → 参数更新(update)

链式法则: LW[1]=Ly^y^Z[2]Z[2]A[1]A[1]Z[1]Z[1]W[1]

  • 两层网络的标准反向传播公式(交叉熵 + softmax/sigmoid)

定义每层的误差项 δ,从后往前递推:

δ[2]=Y^Y(输出层,标准搭配下的漂亮结果)

LW[2]=1n(A[1])Tδ[2],Lb[2]=1nδ[2]

δ[1]=(δ[2](W[2])T)g(Z[1])(是逐元素乘)

LW[1]=1nXTδ[1]Lb[1]=1nδ[1]

规律:本层误差 = 后层误差"穿过"权重传回来 × 本层激活的导数;参数梯度 = 本层误差 × 本层输入。

均方误差+线性激活反向传播计算图

代码实现

python
import matplotlib.pyplot as plt
import numpy as np


def load_coffee_data():
    pass # 同前向传播

X, Y = load_coffee_data()
u = X.mean(axis=0)
sigma = X.std(axis=0)
X = (X - u) / sigma
print(f"Temperature Max, Min post normalization: {np.max(X[:,0]):0.2f}, {np.min(X[:,0]):0.2f}")
print(f"Duration    Max, Min post normalization: {np.max(X[:,1]):0.2f}, {np.min(X[:,1]):0.2f}")
print(X.shape, Y.shape)

m, n = X.shape
J_history = []
# INPUT(m, 2) --> Hidden layer(3个神经元,ReLU) --> Output layer(1个神经元,Sigmoid+二元交叉熵)

W1 = np.array([[-8.93, 0.29, 12.9], [-0.1, -7.32, 10.81]])
b1 = np.array([-9.82, -9.28, 0.96])
W2 = np.array([[-31.18], [-27.59], [-32.56]])
b2 = np.array([15.41])

# --- 随机初始化,从头训练 ---
# np.random.seed(0)  # 固定随机种子,保证每次运行结果可复现
# W1 = np.random.randn(2, 3) * np.sqrt(2 / 2)  # He 初始化:ReLU 激活推荐 √(2/n_in)
# b1 = np.zeros(3)
# W2 = np.random.randn(3, 1) * np.sqrt(2 / 3)
# b2 = np.zeros(1)

def sigmoid(x):
    x = np.clip(x, -500, 500)
    return 1 / (1 + np.exp(-x))

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


def back_prop(X, Y, W1, b1, W2, b2, lr):
    # forward prop
    # (m,2) (2, 3)
    Z1 = X @ W1 + b1
    A1 = ReLU(Z1)
    # (m, 3) (3, 1)
    Z2 = A1 @ W2 + b2
    y_hat = sigmoid(Z2)

    y_hat = np.clip(y_hat, 1e-9, 1-1e-9)
    cost = -np.sum(Y * np.log(y_hat) + (1 - Y) * np.log(1 - y_hat)) / m
    J_history.append(cost)

    # back prop
    # (m, 1)
    dZ2 = y_hat - Y

    dW2 = (A1.T @ dZ2) / m
    db2 = np.mean(dZ2, axis=0)
    # db2_test = np.mean(dZ2, axis=0, keepdims=True)
    # print(b2, db2, db2_test)

    dZ1 = (dZ2 @ W2.T) * (Z1 > 0)
    dW1 = (X.T @ dZ1) / m
    db1 = np.mean(dZ1, axis=0)
    # print(b1, db1)

    W1 = W1 - lr * dW1
    b1 = b1 - lr * db1
    W2 = W2 - lr * dW2
    b2 = b2 - lr * db2
    return W1, b1, W2, b2

lr = 0.01
epochs = 1000

for _ in range(epochs):
    W1, b1, W2, b2 = back_prop(X, Y, W1, b1, W2, b2, lr)

plt.plot(J_history)
plt.xlabel("Epoch")
plt.ylabel("Cost")
plt.show()

训练常见问题

参数初始化

  • 参数初始化0或相同值时所有神经元会永远学出相同特征(对称性无法打破)
  • 常用:Xavier 初始化(适合 tanh)、He 初始化(适合 ReLU),按层宽缩放方差

梯度消失 / 梯度爆炸

  • 反向传播连乘很多小导数 → 浅层梯度趋近 0,梯度消失**
  • 对策:ReLU 激活、Batch Normalization、残差连接(ResNet)、合理初始化、梯度裁剪

过拟合

  • Dropout:训练时随机"关掉"一部分神经元,强迫网络不依赖单个神经元
  • L2 正则(weight decay)、早停(early stopping)、数据增强

优化器

SGD(Stochastic Gradient Descent,随机梯度下降)→ Momentum(动量加速)→ Adam(自适应学习率)

强化学习(Reinforcement Learning)