机器学习之手写数字识别

一、作业环境准备:导入依赖包

1. 核心依赖说明

库 / 模块 作用
numpy as np Python 科学计算基础库,用于数组运算、矩阵操作
tensorflow as tf 主流深度学习框架,本作业用其 Keras 接口搭建神经网络
Sequentialtf.keras.models Keras 中的序贯模型,用于线性堆叠神经网络层
Densetf.keras.layers 全连接层(密集层),用于构建神经网络的基础层
activationstf.keras 激活函数集合,包含 linear/relu/sigmoid
matplotlib.pyplot as plt 数据可视化库,用于绘制图像、模型结果
logging/tf.autograph 用于屏蔽 TensorFlow 的冗余日志,简化输出

2. 完整导入代码

复制代码
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.activations import linear, relu, sigmoid
%matplotlib widget
import matplotlib.pyplot as plt
plt.style.use('./deeplearning.mplstyle')  # 加载课程预设的绘图样式

import logging
logging.getLogger("tensorflow").setLevel(logging.ERROR)  # 屏蔽 TensorFlow 错误以外的日志
tf.autograph.set_verbosity(0)  # 关闭 AutoGraph 的冗余输出

# 课程自定义工具(单元测试、绘图工具等)
from public_tests import *
from autils import *
from lab_utils_softmax import plt_softmax

np.set_printoptions(precision=2)  # 设置 numpy 数组输出保留 2 位小数

二、激活函数:ReLU 详解

1. ReLU 核心概念

  • 公式:\(a = \max(0, z)\)
  • 含义:修正线性单元(Rectified Linear Unit),输入为负时输出 0,输入为正时直接输出本身。
  • 核心特点
    1. 非线性激活:引入 "关闭" 特性(负输入输出为 0),让多层神经网络能拟合复杂非线性关系。
    2. 缓解梯度消失:相比 Sigmoid,ReLU 在正区间梯度恒为 1,反向传播时梯度不易消失,训练深层网络更稳定。
    3. 计算高效:无复杂指数运算,比 Sigmoid/Tanh 计算更快。

2. ReLU vs Sigmoid(作业场景对比)

特性 ReLU Sigmoid
适用场景 连续特征、多分类 / 回归任务 二元分类、概率输出场景
输出范围 \([0, +\infty)\) \((0, 1)\)
核心作用 引入非线性,让多层网络叠加有效 输出 "开 / 关" 类二元概率
关键优势 解决梯度消失、计算快 输出可直接解释为概率

三、Softmax 函数:多分类的概率输出层

1. Softmax 核心概念

  • 公式:对于输入向量 z,第 j 个输出为\(a_j = \frac{e^{z_j}}{\sum_{k=0}^{N-1} e^{z_k}}\)其中 N 是输出类别数量,\(z = W \cdot x + b\) 是线性层的输出。
  • 核心特性
    1. 输出所有值都在 \((0,1)\) 之间,且所有输出之和为 1,可直接解释为 "每个类别的预测概率"。
    2. 输入值越大,对应的输出概率越高,能放大输入间的微小差异,让模型更 "坚定" 地预测类别。

2. 手写 Softmax 实现(两种方式)

方式 1:向量化实现(推荐,高效简洁)
复制代码
def my_softmax(z):
    """ Softmax converts a vector of values to a probability distribution.
    Args:
        z (ndarray (N,)) : input data, N features
    Returns:
        a (ndarray (N,)) : softmax of z
    """
    ez = np.exp(z)          # 对每个元素取指数
    a = ez / np.sum(ez)     # 每个元素除以指数和,得到概率分布
    return a
方式 2:循环实现(原理直观,适合理解)
复制代码
def my_softmax(z):
    N = len(z)
    a = np.zeros(N)         # 初始化输出数组
    ez_sum = 0              # 初始化指数和为 0
    # 第一步:先计算所有元素的指数和(分母)
    for k in range(N):
        ez_sum += np.exp(z[k])
    # 第二步:逐个计算每个元素的 softmax 输出
    for j in range(N):
        a[j] = np.exp(z[j]) / ez_sum
    return a

3. 验证 Softmax 正确性

复制代码
# 测试输入
z = np.array([1., 2., 3., 4.])
a = my_softmax(z)
atf = tf.nn.softmax(z)

print(f"my_softmax(z): {a}")
print(f"tensorflow softmax(z): {atf}")

# 单元测试(课程提供)
test_my_softmax(my_softmax)
  • 预期输出:两个 Softmax 结果完全一致,且所有值之和为 1。

四、多分类神经网络:手写数字识别任务

4.1 任务背景

  • 目标:识别 0-9 共 10 个手写数字,属于多分类任务(10 选 1)。
  • 应用场景:邮政编码识别、银行支票数字识别等。

4.2 数据集说明

1. 数据加载与结构
复制代码
# 加载数据集(课程自定义函数)
X, y = load_data()
  • 训练集规模:5000 个样本
  • 输入矩阵 X:形状为 (5000, 400)
    • 每个样本是 20×20 像素的灰度图像,展开为 400 维向量(每个像素为浮点数,表示灰度强度)。
    • 每一行对应一个手写数字样本。
  • 标签向量 y:形状为 (5000, 1)
    • 每个元素是 0-9 的整数,对应图像的数字标签(如 y=4 表示图像是数字 4)。
2. 数据维度与内容查看
复制代码
# 查看单个元素
print('The first element of X is: ', X[0])
print('The first element of y is: ', y[0,0])
print('The last element of y is: ', y[-1,0])

# 查看数据形状
print('The shape of X is: ' + str(X.shape))
print('The shape of y is: ' + str(y.shape))
  • 预期输出:
    • X.shape = (5000, 400)y.shape = (5000, 1)
    • y[0,0] = 0y[-1,0] = 9
3. 数据可视化(查看手写数字样本)
复制代码
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)

m, n = X.shape  # m=样本数 5000,n=特征数 400

# 创建 8×8=64 张图像的子图
fig, axes = plt.subplots(8, 8, figsize=(5,5))
fig.tight_layout(pad=0.13, rect=[0, 0.03, 1, 0.91])  # 调整布局,给标题留空间

for i,ax in enumerate(axes.flat):
    # 随机选择一个样本
    random_index = np.random.randint(m)
    # 将 400 维向量还原为 20×20 图像,并转置(课程数据的格式要求)
    X_random_reshaped = X[random_index].reshape((20,20)).T
    # 显示灰度图像
    ax.imshow(X_random_reshaped, cmap='gray')
    # 显示标签
    ax.set_title(y[random_index,0])
    ax.set_axis_off()  # 隐藏坐标轴
fig.suptitle("Label, image", fontsize=14)
  • 效果:随机展示 64 个手写数字样本,每个图像上方标注对应的数字标签。

4.3 模型结构设计

作业中使用的是三层全连接神经网络,结构如下:

单元数 激活函数 输入维度 输出维度 参数维度(W, b)
输入层 400 - - 400 -
第 1 层(L1) 25 ReLU 400 25 W1: (400,25), b1: (25,)
第 2 层(L2) 15 ReLU 25 15 W2: (25,15), b2: (15,)
第 3 层(L3) 10 Linear(无激活) 15 10 W3: (15,10), b3: (10,)
  • 关键说明:
    1. 前两层用 ReLU 激活引入非线性,让模型能拟合复杂模式。
    2. 输出层用 linear 激活,不直接加 Softmax,而是在损失函数中处理 Softmax,提升数值稳定性。

4.4 Keras 模型实现(Sequential 方式)

完整代码
复制代码
tf.random.set_seed(1234)  # 设置随机种子,保证结果可复现

model = Sequential(
    [
        ### START CODE HERE ###
        # 输入层:指定输入维度为 400(可选,Keras 会自动推断)
        tf.keras.layers.InputLayer((400,)),
        # 第1层:25个单元,ReLU 激活
        tf.keras.layers.Dense(25, activation="relu", name="L1"),
        # 第2层:15个单元,ReLU 激活
        tf.keras.layers.Dense(15, activation="relu", name="L2"),
        # 第3层:10个单元,线性激活(输出 logits)
        tf.keras.layers.Dense(10, activation="linear", name="L3")
        ### END CODE HERE ###
    ], name = "my_model"
)

# 模型编译:损失函数用 SparseCategoricalCrossentropy,from_logits=True 表示输出是 logits
model.compile(
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
)
关键细节:from_logits=True
  • 含义:告诉损失函数,模型输出的是未经过 Softmax 的 logits,损失函数会自动计算 Softmax 再计算交叉熵。
  • 优势:
    1. 数值更稳定:Softmax + 交叉熵合并计算,避免中间指数运算导致的数值溢出。
    2. 训练更高效:TensorFlow 内部优化了合并计算的梯度。
  • 注意:此时模型的输出不是概率,而是 logits;如果需要概率,需要手动调用 tf.nn.softmax(model.predict(x))

学习笔记补充:关键易错点

  1. Softmax 实现的数值稳定性 :当输入 z 很大时,np.exp(z) 可能溢出,实际工程中会先减去 z 的最大值(z = z - np.max(z)),避免指数溢出。
  2. 数据维度匹配 :模型输入 X 的形状必须和网络输入层维度一致(400),否则会报错。
  3. 多分类标签格式 :这里的标签是 0-9 的整数,所以用 SparseCategoricalCrossentropy;如果标签是 one-hot 编码(如 [1,0,0,...]),则用 CategoricalCrossentropy
  4. ReLU 的 "死亡 ReLU" 问题:如果学习率太高,部分神经元可能永远输出 0,梯度消失;可以通过合理设置学习率缓解。

二.深度学习多类别分类可选实验笔记

一、实验目标与背景

1. 核心目标

探索使用神经网络解决多类别分类问题,理解多分类与二分类的区别,以及 Softmax、ReLU 在多分类场景下的作用。

2. 多分类 vs 二分类

维度 二分类 多分类
任务目标 区分 "是 / 否" 两类(如 "猫 / 非猫") 从多个类别中选一个(如 "猫 / 狗 / 马 / 其他")
输出层 1 个单元,Sigmoid 激活输出概率 多个单元,线性激活 + 损失函数内 Softmax
决策边界 单条直线 / 曲线,划分两类 多条决策边界,划分多个类别区域
典型损失函数 BinaryCrossentropy SparseCategoricalCrossentropy

二、实验工具与依赖导入

库 / 模块 作用
numpy as np 科学计算,处理数组与矩阵
matplotlib.pyplot as plt 数据可视化,绘制分类边界、决策图
sklearn.datasets.make_blobs 生成人工多分类数据集
tensorflow as tf 搭建和训练多分类神经网络
Sequential/Densetf.keras 构建序贯模型和全连接层
lab_utils_multiclass_TF 课程自定义工具,包含多分类绘图函数
复制代码
import numpy as np
import matplotlib.pyplot as plt
%matplotlib widget
from sklearn.datasets import make_blobs
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

np.set_printoptions(precision=2)  # 输出保留2位小数
from lab_utils_multiclass_TF import *  # 导入课程绘图工具

import logging
logging.getLogger("tensorflow").setLevel(logging.ERROR)  # 屏蔽冗余日志
tf.autograph.set_verbosity(0)  # 关闭AutoGraph输出

三、数据集准备与可视化

1. 生成多分类数据集

使用 make_blobs 生成 4 个类别的人工数据集,模拟多分类场景:

复制代码
# 生成4类数据集
classes = 4  # 类别数
m = 100      # 样本数
centers = [[-5, 2], [-2, -2], [1, 2], [5, -2]]  # 每个类别的中心
std = 1.0    # 数据标准差(分散程度)

X_train, y_train = make_blobs(
    n_samples=m, 
    centers=centers, 
    cluster_std=std, 
    random_state=30
)

2. 数据信息查看

复制代码
# 查看类别、标签表示和数据维度
print(f"unique classes {np.unique(y_train)}")
print(f"class representation {y_train[:10]}")
print(f"shape of X_train: {X_train.shape}, shape of y_train: {y_train.shape}")
  • 预期输出:
    • unique classes [0 1 2 3]:4 个类别
    • shape of X_train: (100, 2), shape of y_train: (100,):100 个样本,每个样本 2 个特征
    • y_train 是 0-3 的整数标签,对应 4 个类别

3. 数据可视化

复制代码
plt_mc(X_train, y_train, classes, centers, std=std)
  • 效果:不同颜色代表 4 个类别,每个样本点的坐标为两个输入特征,直观展示数据分布。

四、多分类神经网络模型实现

1. 模型结构设计

本实验使用两层全连接网络,结构如下:

单元数 激活函数 作用
输入层 2 - 输入两个特征 x0, x1
第 1 层(L1) 2 ReLU 引入非线性,学习特征变换
第 2 层(L2) 4 Linear 输出 4 个类别的 logits(未归一化分数)

2. Keras 模型构建与编译

复制代码
tf.random.set_seed(1234)  # 设置随机种子,保证结果可复现

# 构建序贯模型
model = Sequential(
    [
        Dense(2, activation='relu', name="L1"),
        Dense(4, activation='linear', name="L2")
    ]
)

# 编译模型
model.compile(
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=tf.keras.optimizers.Adam(0.01)
)

# 训练模型
model.fit(
    X_train, y_train,
    epochs=200
)

3. 关键细节解读

(1)from_logits=True 的作用
  • 含义:告诉损失函数,模型输出的是未经过 Softmax 的 logits,损失函数会自动计算 Softmax + 交叉熵。
  • 优势:避免手动 Softmax 带来的数值溢出,提升训练稳定性;同时 TensorFlow 会优化合并计算的梯度,训练更高效。
  • 注意:模型直接输出的不是概率,而是每个类别的分数;如果需要概率,需调用 tf.nn.softmax(model.predict(X_train))
(2)SparseCategoricalCrossentropy 损失函数
  • 适用场景:标签是 ** 整数形式(0-3)** 的多分类任务。
  • 对比:如果标签是 one-hot 编码(如 [1,0,0,0]),则使用 CategoricalCrossentropy

五、模型训练结果与底层原理解析

1. 模型分类边界可视化

复制代码
plt_cat_mc(X_train, y_train, model, classes)
  • 效果:展示模型学习到的决策边界,将输入空间划分为 4 个区域,每个区域对应一个类别。

2. 第 1 层(ReLU 层)的工作原理

(1)提取层权重
复制代码
# 获取第一层的权重和偏置
l1 = model.get_layer("L1")
W1, b1 = l1.get_weights()

# 绘制第一层单元的功能图
plt_layer_relu(X_train, y_train.reshape(-1,), W1, b1, classes)
(2)单元功能解读
  • 单元 0:将类别 0/1 与 2/3 分开。线左侧的点(类别 0、1)输出为 0,线右侧的点输出大于 0。
  • 单元 1:将类别 0/2 与 1/3 分开。线上方的点(类别 0、2)输出为 0,线下方的点输出大于 0。
  • 本质:ReLU 层的每个单元都在学习一条 "分界线",将输入空间分为两部分,为后续层构建新的特征表示。

3. 第 2 层(输出层)的工作原理

(1)提取层权重并生成新特征
复制代码
# 获取第二层的权重和偏置
l2 = model.get_layer("L2")
W2, b2 = l2.get_weights()

# 第一层的输出(新特征)
X12 = np.maximum(0, np.dot(X_train, W1) + b1)

# 绘制输出层的决策功能
plt_output_layer_linear(
    X12, y_train.reshape(-1,), W2, b2, classes,
    x0_rng=(-0.25, np.amax(X12[:, 0])), 
    x1_rng=(-0.25, np.amax(X12[:, 1]))
)
(2)输出层单元功能解读

第一层的输出 X12 是新的特征(2 维),第二层基于这些特征学习分类规则:

  • 单元 0 :在 (0,0) 附近输出最大值,对应类别 0(蓝色样本)。
  • 单元 1:在左上角区域输出最大值,对应类别 1(绿色样本)。
  • 单元 2:在右下角区域输出最大值,对应类别 2(橙色样本)。
  • 单元 3:在右上角区域输出最大值,对应类别 3(紫色样本)。
(3)Softmax 的协调作用
  • 每个单元不仅要为自己的类别输出最大值,还要保证该值是所有单元中最高的。
  • 这一过程由损失函数中的 Softmax 实现:Softmax 会将所有输出转换为概率分布,让正确类别的概率最大化,同时抑制其他类别的概率。

六、核心知识点总结

1. 多分类神经网络的关键要点

  1. 输出层设计:使用线性激活 + 损失函数内 Softmax,比直接在输出层加 Softmax 更稳定。
  2. ReLU 层的作用:通过多条 "分界线" 学习非线性特征变换,将输入空间映射到更易分类的新特征空间。
  3. 损失函数选择 :标签为整数时用 SparseCategoricalCrossentropy,标签为 one-hot 编码时用 CategoricalCrossentropy
  4. 模型预测 :模型直接输出 logits,取 argmax 即可得到预测类别;如需概率,需额外调用 Softmax。

2. 底层原理:层与层的配合

  • 第一层(ReLU):学习 "分而治之" 的线性分界线,将复杂多分类问题拆解为多个二分类子问题。
  • 第二层(线性):基于第一层生成的新特征,学习每个类别的专属区域,最终通过 Softmax 统一为概率分布。

三.ReLU 激活

一、实验工具与依赖导入

库 / 模块 作用
numpy as np 科学计算,处理数组与矩阵运算
matplotlib.pyplot as plt 数据可视化,绘制激活函数图像、分段线性函数
GridSpecmatplotlib.gridspec 自定义绘图布局,用于复杂可视化
tensorflow as tf 搭建和训练包含 ReLU 层的神经网络
Sequential/Dense/LeakyReLUtf.keras 构建序贯模型、全连接层和 ReLU 变体
activationstf.keras 包含 linear/relu/sigmoid 等激活函数
Slidermatplotlib.widgets 交互式滑块,用于调整模型权重 / 偏置并实时观察效果
课程自定义工具 lab_utils_relu.py/autils.py,提供实验专用绘图函数
复制代码
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
plt.style.use('./deeplearning.mplstyle')  # 课程预设绘图样式
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LeakyReLU
from tensorflow.keras.activations import linear, relu, sigmoid
%matplotlib widget  # 开启交互式绘图

from matplotlib.widgets import Slider
from lab_utils_common import dlc
from autils import plt_act_trio
from lab_utils_relu import *
import warnings
warnings.simplefilter(action='ignore', category=UserWarning)  # 屏蔽用户警告

二、ReLU 激活函数基础

1. 核心定义与公式

  • 全称:修正线性单元(Rectified Linear Unit)
  • 公式:\(a = \max(0, z)\)
  • 含义
    • 当输入 \(z \ge 0\) 时,输出等于输入本身(线性关系);
    • 当输入 \(z < 0\) 时,输出为 0("关闭" 状态)。

2. ReLU vs Sigmoid 对比

特性 ReLU Sigmoid
输出范围 \([0, +\infty)\) \((0, 1)\)
适用场景 连续特征、回归 / 多分类任务 二元分类、概率输出场景
核心优势 缓解梯度消失、计算高效、引入非线性 输出可直接解释为概率
关键特点 存在 "关闭" 状态,能实现分段线性拟合 全程非线性,梯度易消失

3. 实验中的应用场景:需求预测

在课程示例中,ReLU 用于处理具有连续值范围的 "感知特征"(如价格、运费、营销等),而 Sigmoid 仅适合开 / 关类二元场景。ReLU 的线性部分可以建模连续特征的线性关系,"关闭" 状态则能引入非线性,让模型拟合复杂的需求变化模式。


三、为什么需要非线性激活函数?

1. 核心问题:纯线性网络的局限性

如果神经网络中所有层都使用线性激活,那么无论叠加多少层,整个网络都等价于一个单一的线性模型,无法拟合复杂的非线性关系(如分段线性、曲线等)。

2. ReLU 如何实现非线性?

ReLU 的 "关闭" 特性让它能将多个线性函数拼接成分段线性函数,从而近似任意非线性模式:

  • 每个 ReLU 单元可以看作一个 "开关",在特定输入区间内开启(输出线性),其他区间关闭(输出 0);
  • 多个 ReLU 单元的输出叠加,就能组合出斜率变化的分段线性曲线,近似复杂的非线性目标函数。

四、实验:用 ReLU 拟合分段线性函数

1. 实验模型结构

本实验使用一个简单的两层网络,拟合一个三段式分段线性目标函数:

  • 输入层:1 维输入 x
  • 隐藏层:3 个 ReLU 单元(单元 0 固定,单元 1、2 可调整权重 / 偏置)
  • 输出层:线性激活,将隐藏层 3 个单元的输出直接相加

2. 单元分工与拟合原理

单元 作用 拟合逻辑
单元 0 拟合第一个线性片段 固定权重 / 偏置,在 \(x \in [0,1]\) 区间内输出线性值,\(x>1\) 时 ReLU 截断为 0,不影响后续片段
单元 1 拟合第二个线性片段 调整权重(斜率)和偏置,让 \(x<1\) 时输出为负(ReLU 关闭,无贡献),\(x \ge 1\) 时开启,输出对应斜率的线性值
单元 2 拟合第三个线性片段 调整权重(斜率)和偏置,让 \(x<2\) 时输出为负(ReLU 关闭,无贡献),\(x \ge 2\) 时开启,输出对应斜率的线性值

3. 关键公式与逻辑

隐藏层单元的输出:

\(\begin{align*} a_0^{[1]} &= \text{relu}(x \cdot w_0^{[1]} + b_0^{[1]}) \\ a_1^{[1]} &= \text{relu}(x \cdot w_1^{[1]} + b_1^{[1]}) \\ a_2^{[1]} &= \text{relu}(x \cdot w_2^{[1]} + b_2^{[1]}) \end{align*}\)

输出层的最终结果:

\(a_0^{[2]} = a_0^{[1]} + a_1^{[1]} + a_2^{[1]}\)

4. 交互式实验代码与步骤

复制代码
# 启动交互式实验,调整权重/偏置拟合目标函数
_ = plt_relu_ex()
  • 操作提示:
    1. 先将单元 1、2 的权重和偏置设为 0,观察单元 0 单独拟合的第一段;
    2. 调整单元 1 的权重和偏置,让其在 \(x \ge 1\) 时开启,拟合第二段;
    3. 调整单元 2 的权重和偏置,让其在 \(x \ge 2\) 时开启,拟合第三段;
    4. 观察三个单元输出叠加后,是否与目标分段线性函数完全重合。

五、核心知识点总结

1. ReLU 激活函数的关键特性

  1. 分段线性本质:通过 "开启 / 关闭" 状态,将多个线性函数拼接成复杂的分段线性曲线,近似任意非线性模式;
  2. 计算高效:无复杂指数运算,比 Sigmoid/Tanh 计算更快;
  3. 缓解梯度消失:正区间梯度恒为 1,反向传播时梯度不易消失,训练深层网络更稳定;
  4. "死亡 ReLU" 问题:如果学习率过高,部分神经元可能永远处于关闭状态(输出 0),梯度消失;可通过合理设置学习率、使用 LeakyReLU 缓解。

2. 为什么 ReLU 是深度学习的主流激活函数?

  • 解决了 Sigmoid 等传统激活函数的梯度消失问题;
  • 计算成本低,训练速度快;
  • 能有效引入非线性,让多层神经网络拟合复杂模式;
  • 适配深度学习框架的优化实现,工业界应用广泛。

四.Softmax 功能

一、实验工具与依赖导入

库 / 模块 作用
numpy as np 科学计算,实现 Softmax 函数、处理数组运算
matplotlib.pyplot as plt 数据可视化,绘制 Softmax 输出、概率分布
tensorflow as tf 搭建多分类神经网络,对比两种 Softmax 实现方式
Sequential/Densetf.keras 构建序贯模型和全连接层
make_blobssklearn.datasets 生成多分类人工数据集
课程自定义工具 lab_utils_softmax.py/lab_utils_common.py,提供 Softmax 专用绘图函数
复制代码
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('./deeplearning.mplstyle')  # 课程预设绘图样式
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from IPython.display import display, Markdown, Latex
from sklearn.datasets import make_blobs
%matplotlib widget
from matplotlib.widgets import Slider
from lab_utils_common import dlc
from lab_utils_softmax import plt_softmax
import logging
logging.getLogger("tensorflow").setLevel(logging.ERROR)  # 屏蔽冗余日志
tf.autograph.set_verbosity(0)  # 关闭AutoGraph输出

二、Softmax 函数基础

1. 核心定义与公式

  • 作用 :将线性层输出的向量 z 转换为概率分布,每个输出值在 0-1 之间,且所有输出之和为 1,可直接解释为 "输入属于每个类别的概率"。
  • 公式:\(a_j = \frac{e^{z_j}}{\sum_{k=1}^{N} e^{z_k}}\)其中 N 是类别数量,\(z_j\) 是第 j 个类别的线性输出(logit)。

2. 两种应用场景

场景 结构 输入 输出
Softmax 回归 单层线性层 + Softmax 原始输入特征 x 每个类别的概率
带 Softmax 输出的神经网络 多层隐藏层 + 线性输出层 + Softmax 隐藏层输出的特征向量 每个类别的概率

3. 关键特性

  1. 放大差异:指数运算会放大输入向量中数值的微小差异,让模型更 "坚定" 地预测类别;
  2. 全局影响:Softmax 作用于所有输出单元,修改任意一个输入值都会改变所有输出概率;
  3. 归一化特性:所有输出之和恒为 1,符合概率分布的定义。

三、Softmax 函数的 NumPy 实现

1. 基础实现代码

复制代码
def my_softmax(z):
    # 对每个元素取指数
    ez = np.exp(z)
    # 每个元素除以指数和,得到概率分布
    sm = ez / np.sum(ez)
    return sm

2. 交互式验证

复制代码
# 关闭之前的绘图窗口
plt.close("all")
# 启动交互式实验,调整输入向量 z 观察 Softmax 输出变化
plt_softmax(my_softmax)
  • 操作提示:通过滑块修改输入向量的数值,观察输出概率的变化,验证 "输入越大,对应输出概率越高" 的特性,以及所有输出之和为 1 的归一化特性。

四、Softmax 损失函数:交叉熵损失

1. 损失函数定义

  • 作用:衡量 Softmax 输出的概率分布与真实标签的差异,作为模型训练的优化目标。

  • 公式:单个样本的损失:\(L(a, y) = -\log(a_y)\)其中 y 是真实类别,\(a_y\) 是 Softmax 输出中对应类别的概率。

    批量样本的成本函数:\(J(\mathbf{w}, \mathbf{b}) = -\frac{1}{m} \left[ \sum_{i=1}^{m} \sum_{j=1}^{N} \mathbf{1}\{y^{(i)} == j\} \log \frac{e^{z_j^{(i)}}}{\sum_{k=1}^{N} e^{z_k^{(i)}}} \right]\)其中 \(\mathbf{1}\{y^{(i)} == j\}\) 是指示函数,当样本 i 的标签为 j 时取 1,否则取 0。

2. 与二分类交叉熵的对比

特性 二分类交叉熵 多分类交叉熵(Softmax)
适用场景 二元分类任务 多分类任务
输出层激活 Sigmoid Softmax(或线性 + 损失内 Softmax)
标签格式 0/1 或 one-hot 编码 整数索引或 one-hot 编码
损失计算 同时计算正负类别的损失 仅计算真实类别对应的损失

五、TensorFlow 中 Softmax 的两种实现方式

1. 数据集准备

复制代码
# 生成4类人工数据集
centers = [[-5, 2], [-2, -2], [1, 2], [5, -2]]
X_train, y_train = make_blobs(
    n_samples=2000, 
    centers=centers, 
    cluster_std=1.0, 
    random_state=30
)

2. 方式一:输出层直接加 Softmax(不推荐)

模型代码
复制代码
model = Sequential([
    Dense(25, activation='relu'),
    Dense(15, activation='relu'),
    Dense(4, activation='softmax')  # Softmax 直接放在输出层
])

model.compile(
    loss=tf.keras.losses.SparseCategoricalCrossentropy(),
    optimizer=tf.keras.optimizers.Adam(0.001)
)

model.fit(X_train, y_train, epochs=10)
特点与问题
  • 优点:模型直接输出概率向量,使用直观;
  • 缺点:数值稳定性差,指数运算容易出现溢出 / 下溢,导致训练不稳定。
输出验证
复制代码
p_nonpreferred = model.predict(X_train)
print(p_nonpreferred[:2])
print("largest value", np.max(p_nonpreferred), "smallest value", np.min(p_nonpreferred))
  • 预期输出:所有值在 0-1 之间,且每行之和为 1。

3. 方式二:输出层线性激活 + 损失函数内 Softmax(推荐)

模型代码
复制代码
preferred_model = Sequential([
    Dense(25, activation='relu'),
    Dense(15, activation='relu'),
    Dense(4, activation='linear')  # 输出层用线性激活,不直接加 Softmax
])

preferred_model.compile(
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),  # from_logits=True
    optimizer=tf.keras.optimizers.Adam(0.001)
)

preferred_model.fit(X_train, y_train, epochs=10)
关键参数 from_logits=True
  • 含义:告诉损失函数,模型输出的是未经过 Softmax 的 logits,损失函数会自动计算 Softmax + 交叉熵;
  • 优势:
    1. 数值更稳定:合并 Softmax 和交叉熵计算,避免中间指数运算的溢出;
    2. 训练更高效:TensorFlow 内部优化了梯度计算;
    3. 输出灵活:模型直接输出 logits,如需概率可手动调用 tf.nn.softmax(),直接取 argmax 也能得到预测类别。
输出验证
复制代码
# 模型直接输出 logits(非概率)
p_preferred = preferred_model.predict(X_train)
print(f"two example output vectors:\n {p_preferred[:2]}")
print("largest value", np.max(p_preferred), "smallest value", np.min(p_preferred))

# 手动转换为概率
sm_preferred = tf.nn.softmax(p_preferred).numpy()
print(f"two example output vectors:\n {sm_preferred[:2]}")
print("largest value", np.max(sm_preferred), "smallest value", np.min(sm_preferred))

# 直接取 argmax 得到预测类别(无需转换为概率)
for i in range(5):
    print(f"{p_preferred[i]}, category: {np.argmax(p_preferred[i])}")

六、两种交叉熵损失的区别

损失函数 标签格式 适用场景
SparseCategoricalCrossentropy 整数索引(如 0/1/2/3) 标签为单个整数的多分类任务,无需 one-hot 编码
CategoricalCrossentropy one-hot 编码(如 [1,0,0,0] 标签为独热向量的多分类任务

七、核心知识点总结

1. Softmax 函数的关键要点

  • 作用:将 logits 转换为概率分布,用于多分类任务;
  • 实现:NumPy 可通过 np.exp(z)/np.sum(np.exp(z)) 实现;
  • 最佳实践:在 TensorFlow 中优先使用 from_logits=True 的方式,数值更稳定。

2. 多分类任务的 TensorFlow 实现流程

  1. 数据准备:生成 / 加载多分类数据集,标签为整数索引;
  2. 模型构建:隐藏层用 ReLU 激活,输出层用线性激活;
  3. 模型编译:使用 SparseCategoricalCrossentropy(from_logits=True) 作为损失函数;
  4. 模型训练:调用 model.fit() 训练;
  5. 模型预测:直接用 np.argmax(model.predict(x)) 得到预测类别,如需概率可手动调用 tf.nn.softmax()
相关推荐
流年如夢1 小时前
单链表Ⅲ(LeetCode)
数据结构·算法·leetcode·职场和发展
鉴生Eric1 小时前
FOR算法中的AI智能体具体如何实现频谱感知和动态信道选择?请用技术术语详细说明其决策流程
人工智能·算法
量子炒饭大师1 小时前
【优化算法】双指针算法的「义体化」重构 ——【双指针】双指针算法中的指针是如何定义的?如何使用它进行一些简单的算法?
c++·算法·重构·优化算法·双指针
通信小呆呆2 小时前
ZC序列符号同步:多径信道下的四种经典算法
算法
机器学习之心HML2 小时前
粒子群算法求解速冻食品冷链配送路径优化问题,MATLAB代码
算法·matlab·冷链配送路径优化
fie88892 小时前
基于粒子群优化(PSO)算法的带STATCOM的IEEE 30节点系统最优潮流MATLAB实现
开发语言·算法·matlab
hele_two2 小时前
SDL2高效画实心圆的算法(一)
c++·算法·图形渲染
cheems95272 小时前
[算法手记] 动态规划,二叉树计数问题
算法·动态规划
贫民窟的勇敢爷们2 小时前
Scikit-learn算法:从入门到精通的机器学习工具箱
算法·机器学习·scikit-learn