首页
/ UCB EE120 信号与系统自学指南:六个 Python 实验打通从傅里叶变换到滤波与控制的完整链路

UCB EE120 信号与系统自学指南:六个 Python 实验打通从傅里叶变换到滤波与控制的完整链路

2026-09-07 11:09:51作者:何将鹤

本篇是 CS 自学指南(cs-self-learning)「电子基础 · 信号与系统」分支中 UCB EE120 课程页 的深度展开。EE120 全称 Signal and Systems,由 UC Berkeley 开设,它以扎实的傅里叶变换讲义与 6 个 Python 编程实验闻名:从手写 FFT、从手指影像测心率,到还原哈勃望远镜拍摄的星空、用反馈系统平衡小车上的细杆。读完本文,你将明确这门课的难度定位、先修链条、100 小时投入如何分配,以及每个核心实验背后的信号处理理论,从而判断它是否值得纳入你自己的自学路线。

课程档案一览

以下元数据直接取自仓库中的课程介绍文档 docs/电子基础/signal.en.md 及其中文版 signal.md

项目 内容
所属大学 UC Berkeley
课程全称 EE120: Signal and Systems
先修要求 CS61A、CS70、微积分、线性代数
编程语言 Python
课程难度 ★★★★★
预计学时 100 小时
作业组成 5 个书面作业 + 6 个编程作业

在仓库中,这门课被归入「电子基础(Fundamental Electronics)」分类,与 EE16A&B(电路)MIT 6.007 Signals and Systems 并列,导航结构见 mkdocs.yml。需要特别注意的是:EE120 在本仓库标记为满级五星难度,而同分类下的 MIT 6.007(Oppenheim 讲授的"远古版本")仅有二星、约 50–70 小时投入——两者定位差异明显,后面会详述如何按需取舍。

为什么把 EE120 放进自学路线

CS 学习规划 的「信号与系统」小节给出了作者的选课动机:

最初学它只是为了满足我对傅里叶变换的好奇,但学完之后我才不禁感叹,傅立叶变换给我提供了一个全新的视角去看待这个世界……让你沉浸在用数学去精确描绘和刻画这个世界的优雅与神奇之中。

同小节还明确指出:UCB EE120 关于傅里叶变换的 notes 写得非常好,并且提供了 6 个非常有趣的 Python 编程作业,让学习者"在实践中运用信号与系统的理论与算法"。这正是它区别于 MIT 6.003 / 6.007 等纯理论派课程的核心卖点:

  • MIT 6.007(Signals_and_Systems_AVO)由 Alan V. Oppenheim 主讲,理论体系经典、难度温和,适合快速建立学科直觉;
  • EE120 则把同样的理论(卷积、傅里叶变换、采样、滤波、反馈)落到"能用代码解决真实问题"的层面,适合已经具备编程能力、希望动手验证数学的人。

先修基础与仓库内对应课程

EE120 属于本科中高阶课程,对数学与编程都有明确要求。若基础未打牢,可以先用仓库中对应的自学资源补齐:

EE120 要求 对应自学资源(仓库内)
CS61A(Python 编程) UCB CS61A: Structure and Interpretation of Computer Programs
CS70(离散数学与概率) UCB CS70: Discrete Math and Probability Theory
微积分 MIT 18.01 / 18.02 数学课程
线性代数 MIT 18.06 线性代数

其中 Python 熟练度尤为重要:课程的 6 个实验都以 Python 为载体,实验里要手写 FFT、处理视频帧序列、批量操作图像矩阵,如果此前只写过脚本级代码,建议先用 CS61A 或等价课程把 numpy 生态(数组运算、向量化、fft/rfft 等)练熟。此外,如果希望先对"传感器采集 → 数据分析 → 预测反馈"整条链路有感性的体感,可以选修同分支下的 EE16A&B(伯克利电子系大一入门课,难度三星)作为热身,但这并非 EE120 的硬性前置。

学习节奏与课程结构

课程材料形态为:课堂讲义(notes)+ 课程录播 + 5 个书面作业 + 6 个编程作业(Lab),录播与作业均通过课程官网分发。EE120 的讲义质量在仓库中受到高度评价(见上节引文),建议的学习循环是:

  1. 先读对应章节 notes,抓住数学主线——信号如何表示、LTI 系统如何用卷积刻画、时域与频域如何互相转换;
  2. 再看录播补充推导细节,书面作业通常围绕变换公式、系统性质、采样定理等推导题展开;
  3. 最后用 Lab 把理论兑现成可运行的 Python 代码,并用可视化结果反哺理解。

关于 100 小时的时间预算,可以做一个粗略拆解:若每周投入 8–10 小时、持续约 10–13 周(大致一个学期的节奏),6 个 Lab 平均每个需要投入一周中的 2–3 个小时用于编码与调试,书面作业与 notes 阅读占剩余时间。考虑到它是五星满级难度,建议不要在课业繁忙期硬塞,留出连续、不被打断的编码窗口。

核心实验逐站拆解

课程介绍文档明确指出,这门课"最精华的部分就是 6 个超有趣的编程作业",并逐一给出了从 Lab3 到 Lab6 的剧透。仓库文档并未逐条描述 Lab1、Lab2 的内容,但从 Lab3 起就要求手写 FFT 可以推断,前两个实验重在循序渐进地引入用 Python/numpy 处理离散信号的基本工作流,为后续的"重头戏"铺路。下面把文档明示的四个核心实验逐一拆开,讲清它考察的理论点与背后的工程价值。

Lab 3:手写 FFT 并对标 numpy

文档任务描述:实现 FFT 算法,并与 Numpy 官方实现进行性能对比。

离散傅里叶变换(DFT)的朴素实现是 O(N2)O(N^2) 的矩阵乘,而 FFT 通过分治把复杂度降到 O(NlogN)O(N \log N)。自己实现一遍 FFT,才能真正理解蝶形运算、位反转与复数旋转因子这些概念,而不是把 np.fft.fft 当成黑盒。下面是还原这一核心对比的示意代码(非课程官方代码,仅演示二者的正确性与性能差距):

import numpy as np
import time

def dft(x):
    """按定义逐点实现的 DFT(O(N^2)),N 不宜取太大。"""
    N = len(x)
    n = np.arange(N).reshape(-1, 1)   # 列向量
    k = np.arange(N)                  # 行向量
    W = np.exp(-2j * np.pi * n * k / N)
    return W @ x

N = 2048
x = np.random.randn(N) + 1j * np.random.randn(N)

t0 = time.perf_counter()
y_naive = dft(x)
t1 = time.perf_counter()
y_fft = np.fft.fft(x)
t2 = time.perf_counter()

print(f"naive DFT: {t1 - t0:.3f}s  |  numpy FFT: {t2 - t1:.3f}s")
print("max abs error:", np.max(np.abs(y_naive - y_fft)))

运行后你会直观看到:结果一致(误差在浮点精度级别),但 FFT 的速度优势随着 NN 增大呈数量级差距。这个实验的意义在于让"算法复杂度分析"落地——课堂上的 O(NlogN)O(N \log N) 与真实耗时在这里互相印证。

Lab 4:从手指影像推断心率

文档任务描述:通过分析手指头的影像数据推断心率。

其背后的物理原理是光电容积描记(PPG):手指组织中的血流量随心跳周期性变化,会规律性地改变对光线的吸收,因此视频每一帧中手指区域的平均亮度会携带周期性的微小波动,这个波动的频率就是心率。处理管线通常是:

  1. 逐帧提取手指区域,计算区域平均亮度,得到一条随时间变化的一维信号;
  2. 去除直流分量与缓慢漂移(去趋势/去均值),必要时做带通滤波去掉呼吸等低频干扰;
  3. 对信号做 FFT,在合理的心率频带(约 0.8–3.0 Hz,对应 48–180 bpm)内找到幅度最大的频率分量,乘以 60 即得到每分钟心跳数。

下面是一段能说明核心思路的示意代码:

import numpy as np

def estimate_heart_rate(brightness, fps=30.0):
    """brightness: 每帧手指区域平均亮度构成的一维数组。"""
    x = brightness - brightness.mean()
    X = np.fft.rfft(x * np.hanning(len(x)))
    freqs = np.fft.rfftfreq(len(x), d=1.0 / fps)
    lo, hi = 0.8, 3.0                     # 心跳频带:48 ~ 180 bpm
    band = (freqs >= lo) & (freqs <= hi)
    peak = freqs[band][np.argmax(np.abs(X[band]))]
    return peak * 60.0                    # 单位:bpm

这个实验是"采样定理 + 频谱分析"的绝佳实战:视频帧率就是采样率,若帧率过低导致心跳频率混叠,测出的心率就会出错——把抽象的采样/混叠概念变成肉眼可见的现象。

Lab 5:给哈勃望远镜的照片降噪

文档任务描述:对哈勃望远镜拍到的照片进行降噪处理,恢复绚烂清晰的星空。

深空天文图像的特点是有用的星点信号微弱、散布稀疏,而探测器噪声(光子计数涨落带来的泊松噪声、读出噪声等)相对显著。要在这种低信噪比下"找回星空",课程要求综合运用:

  • 噪声统计建模:理解噪声的分布特性,判断哪些起伏是信号、哪些是噪声;
  • 频域/空间滤波:星点具有近似已知的点扩散形态,与随机噪声在频域上可分,因此可以设计合适的滤波器在抑制噪声的同时保留星点能量;
  • 基于先验的复原:结合"星空中大部分区域是暗背景,亮点是稀疏星点"这一先验,把复原问题转化为可求解的优化或检测问题。

这个 Lab 之所以被课程介绍文档评为"最牛的一个",在于它把一个看起来很"图像处理/天文学"的问题,拆解成了纯粹用信号与系统语言(噪声、滤波、频谱)就能解决的任务,做完之后对"降噪"二字会有本质层面的理解。

Lab 6:构造反馈系统平衡细杆

文档任务描述:构造一个反馈系统,平衡小车上的细杆。

这是经典的 cart-pole(倒立摆)问题,也是控制理论入门的标志性题目。系统由小车与铰接在其上的细杆构成:杆子天生不稳定,任何扰动都会让它倾倒,唯一的手段是测量当前状态(杆的倾角与角速度、小车的位置等),据此计算施加在小车上的力,构成闭环。核心概念包括:

  • 系统的动态建模与传递函数/状态空间描述;
  • 开环不稳定系统为何必须引入反馈;
  • 反馈增益如何决定系统的稳定性与响应速度——增益过低压不住扰动,过高又会引发振荡。

前五个实验解决的多是"分析/复原"类问题(把信号拆开、把噪声去掉),到 Lab 6 则转向"设计/控制"类问题(让系统达到并维持期望状态)。这恰好呼应了 CS 学习规划 中对 EE16 系的描述——从"从环境中收集信息"到"做出预测反馈",EE120 用信号与系统的语言补上了理论拼图。

资源与配套材料

  • 课程资料(notes / 录播 / 书面作业):均通过 UCB EE120 课程官网分发,仓库文档引用的学期为 Fall 2019(inst.eecs.berkeley.edu/~ee120/fa19/),录播与作业具体入口参见课程官网对应栏目。
  • 学习者的作业实现与资源汇总:课程介绍文档注明,@PKUFlyingPig 在学习这门课中用到的所有资源和作业实现都汇总维护在个人仓库 PKUFlyingPig/UCB-EE120 中。作为自学参考时请注意:参考他人的实现思路可以帮助卡点排障,但务必先独立完成实验,再对照差异,这样才能收获完整的调试与推导经验。

如何把它放进你自己的自学计划

结合 CS 学习规划使用指南 对读者的分层,给出三条落地建议:

  1. 面向 CS 系统学习路线的读者:将 EE120 安排在完成编程入门(如 CS61A)与数学基础(微积分、线性代数,对应 MITmathsMITLA)之后。它在仓库中的难度评级为五星、预算 100 小时,属于"需要专门腾出一段连续时间"的重课,建议放在假期或课业较轻的学期。
  2. 只想补齐核心知识、时间有限的读者:可以直接选择同分类下更轻量的 MIT 6.007(Oppenheim)(二星、50–70 小时)建立理论框架,EE120 的 6 个 Lab 则可以挑选其中最感兴趣的 1–2 个(如 Lab4 测心率)单独完成,作为对理论的自测。
  3. 对电路采集链路感兴趣的读者:可先学 EE16A&B 体验从传感器到算法的完整流水线,再进入 EE120,感受同一套"从信号到系统"的思维在不同硬件层级上的复用。

判断这门课是否适合自己,最简单的方式是翻一翻它的傅里叶变换讲义:如果你能被"用另一种视角重新描述世界"这件事打动,那么 EE120 的 100 小时几乎不会让你失望;如果你读完只觉枯燥,那同目录下的 MIT 系列或许是更匹配的起点。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388