深度学习:从零开始手搓一个深层神经网络

作者:xier_ran日期:2025/11/17

本文将带你不依赖任何深度学习框架(如 TensorFlow 或 PyTorch),仅用 NumPy 从头实现一个完整的深层神经网络(Deep Neural Network, DNN)。我们将一步步构建前向传播、反向传播、参数更新等核心模块,并在真实的猫图识别数据集上训练模型——真正“手搓”AI!


🧱 第一步:初始化网络参数

神经网络的“大脑”就是它的参数:权重矩阵 W 和偏置向量 b。我们需要为每一层随机初始化这些参数。

1def initialize_parameters_deep(layer_dims):
2    np.random.seed(1)
3    parameters = {}
4    L = len(layer_dims)
5
6    for l in range(1, L):
7        # 使用 Xavier 初始化(除以 sqrt(前一层神经元数))
8        parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1]) / np.sqrt(layer_dims[l-1])
9        parameters['b' + str(l)] = np.zeros((layer_dims[l], 1))
10    return parameters
11
  • 为什么除以 sqrt(layer_dims[l-1])
    这是为了防止梯度爆炸或消失,让激活值的方差保持稳定(Xavier 初始化)。
  • 示例:layer_dims = [12288, 20, 7, 5, 1] 表示输入是 64×64×3=12288 维的图像,网络有 4 层隐藏/输出层。

🔁 第二步:前向传播(Forward Propagation)

前向传播分为两部分:线性变换 + 非线性激活

2.1 线性部分:Z = W·A + b

1def linear_forward(A, W, b):
2    Z = np.dot(W, A) + b
3    cache = (A, W, b)  # 缓存用于反向传播
4    return Z, cache
5

2.2 激活函数:ReLU 或 Sigmoid

1def linear_activation_forward(A_prev, W, b, activation):
2    Z, linear_cache = linear_forward(A_prev, W, b)
3    if activation == "sigmoid":
4        A = sigmoid(Z)
5    elif activation == "relu":
6        A = relu(Z)
7    cache = (linear_cache, Z)
8    return A, cache
9
  • 隐藏层用 ReLU:加速训练,缓解梯度消失。
  • 输出层用 Sigmoid:将结果压缩到 (0,1),适合二分类。

2.3 整个网络的前向传播

1def L_model_forward(X, parameters):
2    caches = []
3    A = X
4    L = len(parameters) // 2  # 总层数
5
6    # 前 L-1 层:ReLU
7    for l in range(1, L):
8        A, cache = linear_activation_forward(A, parameters['W'+str(l)], parameters['b'+str(l)], 'relu')
9        caches.append(cache)
10
11    # 最后一层:Sigmoid
12    AL, cache = linear_activation_forward(A, parameters['W'+str(L)], parameters['b'+str(L)], 'sigmoid')
13    caches.append(cache)
14    return AL, caches
15

💰 第三步:计算损失(Cost Function)

我们使用二分类交叉熵损失

1def compute_cost(AL, Y):
2    m = Y.shape[1]
3    cost = (-1/m) * np.sum(Y * np.log(AL) + (1 - Y) * np.log(1 - AL))
4    return np.squeeze(cost)
5

⚠️ 实际应用中建议加上 np.clip(AL, 1e-8, 1-1e-8) 防止 log(0)。


🔙 第四步:反向传播(Backpropagation)

反向传播的核心思想:链式法则 + 缓存复用

4.1 线性部分的梯度

1def linear_backward(dZ, cache):
2    A_prev, W, b = cache
3    m = A_prev.shape[1]
4
5    dW = np.dot(dZ, A_prev.T) / m
6    db = np.sum(dZ, axis=1, keepdims=True) / m
7    dA_prev = np.dot(W.T, dZ)
8
9    return dA_prev, dW, db
10

4.2 激活函数的梯度

1def linear_activation_backward(dA, cache, activation):
2    linear_cache, Z = cache
3    if activation == "relu":
4        dZ = relu_backward(dA, Z)
5    elif activation == "sigmoid":
6        dZ = sigmoid_backward(dA, Z)
7    return linear_backward(dZ, linear_cache)
8

4.3 全网络反向传播

1def L_model_backward(AL, Y, caches):
2    grads = {}
3    L = len(caches)
4    Y = Y.reshape(AL.shape)
5
6    # 最后一层(Sigmoid)
7    dAL = -(np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
8    current_cache = caches[-1]
9    grads["dA"+str(L-1)], grads["dW"+str(L)], grads["db"+str(L)] = \
10        linear_activation_backward(dAL, current_cache, "sigmoid")
11
12    # 前面各层(ReLU)
13    for l in reversed(range(1, L)):
14        current_cache = caches[l-1]
15        dA_prev, dW, db = linear_activation_backward(grads["dA"+str(l)], current_cache, "relu")
16        grads["dA"+str(l-1)] = dA_prev
17        grads["dW"+str(l)] = dW
18        grads["db"+str(l)] = db
19
20    return grads
21

🔄 第五步:参数更新(Gradient Descent)

有了梯度,就可以用梯度下降法更新参数:

1def update_parameters(parameters, grads, learning_rate):
2    L = len(parameters) // 2
3    for l in range(1, L + 1):
4        parameters["W" + str(l)] -= learning_rate * grads["dW" + str(l)]
5        parameters["b" + str(l)] -= learning_rate * grads["db" + str(l)]
6    return parameters
7

🏗️ 第六步:整合训练流程

把所有模块组装成一个完整的训练函数:

1def dnn_model(X, Y, layers_dims, learning_rate=0.0075, num_iterations=2000, print_cost=True):
2    parameters = initialize_parameters_deep(layers_dims)
3    costs = []
4
5    for i in range(num_iterations):
6        AL, caches = L_model_forward(X, parameters)
7        cost = compute_cost(AL, Y)
8        grads = L_model_backward(AL, Y, caches)
9        parameters = update_parameters(parameters, grads, learning_rate)
10
11        if print_cost and i % 100 == 0:
12            print(f"训练 {i} 次后成本是: {cost:.6f}")
13            costs.append(cost)
14
15    plt.plot(costs)
16    plt.title(f"Learning rate = {learning_rate}")
17    plt.xlabel("Iterations (per hundreds)")
18    plt.ylabel("Cost")
19    plt.show()
20
21    return parameters
22

🐱 第七步:在真实数据上训练

我们使用经典的 猫图识别数据集(来自 Coursera Deep Learning 课程):

1# 加载并预处理数据
2train_x_orig, train_y, test_x_orig, test_y, classes = load_data()
3train_x = train_x_orig.reshape(train_x_orig.shape[0], -1).T / 255.
4test_x = test_x_orig.reshape(test_x_orig.shape[0], -1).T / 255.
5
6# 构建 4 层网络:12288 → 20 → 7 → 5 → 1
7layers_dims = [12288, 20, 7, 5, 1]
8parameters = dnn_model(train_x, train_y, layers_dims, num_iterations=2000, print_cost=True)
9

🎯 总结

通过这篇文章,我们亲手实现了:

  • 参数初始化
  • 前向传播(含 ReLU/Sigmoid)
  • 成本计算
  • 反向传播(链式求导 + 缓存机制)
  • 梯度下降更新
  • 完整训练循环

没有调用任何高级 API,一切尽在掌握之中!

这不仅加深了对神经网络内部机制的理解,也为后续学习更复杂的模型(如 CNN、RNN)打下坚实基础。

💡 真正的深度学习,始于手搓一行代码。


深度学习:从零开始手搓一个深层神经网络》 是转载文章,点击查看原文


相关推荐


Python 的内置函数 property
IMPYLH2025/11/16

Python 内建函数列表 > Python 的内置函数 property Python 的内置函数 property() 是一个非常重要的工具,用于管理类属性的访问。它提供了一种优雅的方式来定义属性访问器(getter)、设置器(setter)和删除器(deleter)方法,同时保持简洁的接口。 基本用法 class Person: def __init__(self, name): self._name = name @property


🌐 实时协同 AIGC:多人在线 Web 创作的技术架构设计
LeonGao2025/11/15

🧠 一、前言:从单机AI到群体创作的演化 一个人对着AI画图、生成文案,像独自谈恋爱。 而当你和5个伙伴一起实时改提示词、AI同步绘画时,那就是多线程的爱情故事。 实时协同 AIGC(AI Generated Content)正处在科学与艺术的交汇点: 它要保证同步性、一致性、低延迟感,同时让AI像“艺术助理”,在多用户同时操作下保持逻辑优雅,而非精神分裂。 🧩 二、传统AIGC协作的问题:AI 总慢半拍 在经典 AIGC 应用中,我们常见的交互模式是: 用户提交提示词; 服务端执行推理


Python 的内置函数 list
IMPYLH2025/11/14

Python 内建函数列表 > Python 的内置函数 list Python 的内置函数 list() 是用于创建列表对象的构造函数,它是 Python 中最常用的数据结构之一。 def list(x=None): ''' 类型转换为 list :param x: 一个变量 :return: 转换为 list 后的值 ''' 详细功能说明 创建空列表: 最简单用法是不带任何参数调用 list()示例:my_list = list()


T型槽平台:工业制造中的多功能基础工装
泊头北重机械2322025/11/13

T型槽平台的结构特点 T型槽平台通常由高强度铸铁或钢材制成,表面加工有均匀分布的T型槽。这些槽口贯穿平台表面,形成网格状结构,便于通过螺栓快速固定工件、夹具或设备。平台经过精密研磨或刮研,确保平面度与平行度符合工业标准,部分高精度平台可达到0.01mm/m的精度等级。 槽口设计遵循标准化尺寸,常见宽度包括12mm、14mm、18mm、22mm等,适配不同规格的T型螺栓。平台边缘通常带有刻度尺或螺纹孔,辅助定位测量。部分重型平台会增设加强筋或地脚螺栓孔,提升稳定性和抗变形能力。 核心功能与应


个人笔记|单臂路由,子接口,VLAN标签
学渣676562025/11/11

你这个问题太棒了! 你问的,就是“VLAN 的生命周期”! 我们(在这次会话里)已经把 Access 口、Trunk 口、子接口都聊过了,现在我们就用你这道“单臂路由”题,把它们“串”成一个完整的故事! “旅程”开始: 目标: PC1 (VLAN 10) 要 ping PC2 (VLAN 20)。网关: PC1 的网关是 192.168.1.254 (R1 的子接口)。“铁律” 1 (Access): Access 口收包“贴签”,发包“撕签”。“铁律” 2 (Trunk): Trunk 口**


TDengine 产品组件 taosX
TDengine (老段)2025/11/9

taosX 工具是企业版中重要的数据备份/恢复及数据迁移/导入工具,是 TDengine TSDB Enterprise 中的一个核心组件,提供零代码数据接入的能力,taosX 支持两种运行模式:服务模式和命令行模式。本节讲述如何以这两种方式使用 taosX。要想使用 taosX 需要先安装 TDengine TSDB Enterprise 安装包。 命令行模式 命令行格式 taosX 的命令行参数格式如下 taosx -f <from-DSN> -t <to-DSN> <其它参数>


Android动态更换应用图标
xiangzhihong82025/11/7

一、概述 在Android应用中实现类似微博的动态更换Launcher图标功能,用户可以在应用内选择不同的图标样式,更换后应用在桌面上的图标会立即改变。 背后的技术原理是:使用Android的activity-alias机制为同一个Activity创建多个别名,每个别名可以设置不同的图标。通过PackageManager动态启用/禁用这些别名来实现图标切换。 核心概念: activity-alias: Activity的别名,可以拥有独立的图标、标签等属性; Compon


CSS 的弹性布局
hubenchang05152025/11/2

#CSS 的弹性布局 在此之前,我们写的页面上元素是按照先后顺序排列的,块元素总是占据一行,不受我们控制。 例如之前 语义化 章节中的示例代码中, aside 块作为侧边栏,却并没有显示在侧边,而是单独占据一行。 本节将学习 CSS 中最常用的布局方式——弹性布局,它可以方便地控制容器内项目的排列、对齐和分布方式。 通过将一个元素样式的 display 属性设为 flex,可以将该元素设为弹性布局的 容器, 容器的直接子元素将不再占据一行。 示例: <div style="display:fle


介绍一个小工具-pake
字节逆旅2025/10/31

今天我安利的这个神器,非常有意思!它叫 Pake(发音类似 "pack")。 Pake 是个啥? 简单说,Pake 就一个命令行小工具,网页的“一键打包机”。你给它一个网址,它“嗖”地一下,就能帮你生成一个跨平台的桌面 App。 它最牛的地方有三点: 轻到离谱! 它用 Rust 语言写的,底层是 Tauri 框架。打包出来的 App 只有几 MB,对比那些动辄上百 MB 的“electron”,简直是羽毛对大象。 快如闪电! 启动速度、运行流畅度都堪比原生应用,内存占用也极低。 小白友好!


Python爬虫抓取豆瓣TOP250数据
蒋星熠Jaxonic2025/10/28

目录 一、开门见山,探究网页结构 二、确定思路 1.拿到页面源代码/响应 2.编写正则,提取页面数据 3.保存数据 三、步骤详解 1.初步爬取 2.绕过反爬 3.编写正则表达式与正则匹配 4.翻页爬取 5.注意点 🌟 Hello,我是蒋星熠Jaxonic! 🌈 在浩瀚无垠的技术宇宙中,我是一名执着的星际旅人,用代码绘制探索的轨迹。 🚀 每一个算法都是我点燃的推进器,每一行代码都是我航行的星图。 🔭 每一次性能优化都是我的天文望远镜,每一次架构

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0