第 13 章 NumPy 和向量
文章目录
- [第 13 章 NumPy 和向量](#第 13 章 NumPy 和向量)
-
- [🌈 关于《AI 零基础 36 讲》](#🌈 关于《AI 零基础 36 讲》)
- 先把依赖安装到自己的环境
- 列表与数组怎样做加法
- 一张成绩表有两个轴
- 向量描述一名学生,矩阵组织多名学生
- 同时写行下标和列下标
- 求均分之前先确定汇总方向
- 广播让一组调整值作用于每一行
- 汇总后怎样保留一个轴
- 数组结果保存前要转换格式
- 用布尔数组筛选成绩
- 跑完这一份成绩统计
- 留一道形状练习
- 资料来源
第 11 章用循环累加分钟数,第 12 章把重复动作整理成函数。现在有四名学生、三门课,每名学生需要一个均分,每门课也需要一个均分。你可以再写几组循环,也可以把数字整理成数组,让 NumPy 一次处理。
本章会完成一份成绩统计 Notebook,算出学生均分和课程均分,筛选全部及格的学生,再按课程批量调整分数。需要先读完第 12 章,能使用函数、列表和循环。

🌈 关于《AI 零基础 36 讲》
🧠 零基础也能开始学 AI
《AI 零基础 36 讲》是一套面向初学者的 AI 入门教程。课程从认识和使用 AI 开始,逐步学习
Python、数据处理、机器学习与深度学习,再进入大模型应用开发,最后完成个人知识库助手的综合实践。
📖 本篇是正式讲解文章
通过具体例子、原创图示和操作结果讲清概念。涉及编程的章节提供可运行代码,方便你边读边试。
🎯 读完以后,动手检验
建议按专栏顺序学习,并完成对应的课后训练,检查自己能否独立运用本章知识。
博客专栏:《AI 零基础 36 讲》
先把依赖安装到自己的环境
NumPy 是提供数组和数值计算功能的 Python 库,需要额外安装。在本章目录创建虚拟环境,再使用该环境自己的解释器安装依赖。
powershell
python -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt
.venv\Scripts\python.exe -c "import numpy; print(numpy.__version__)"
最后一条命令应打印 2.3.5。这是本章实际验收用的版本,版本固定用于复现。查看文档网站的 stable 页面时可能遇到更新的版本,当前示例的解释仍以交付代码和对应版本的文档为准。
安装成功却提示 ModuleNotFoundError,通常要先核对运行脚本的解释器。编辑器选中的 Python、终端里的 python 和安装时使用的 Python 可能来自不同环境。本章命令明确写出 .venv\Scripts\python.exe,可以让安装和运行指向同一个解释器。macOS 与 Linux 使用 .venv/bin/python。
列表与数组怎样做加法
先比较两行最小代码。
python
import numpy as np
print([1, 2] + [3, 4])
print(np.array([1, 2]) + np.array([3, 4]))
列表得到 [1, 2, 3, 4],数组得到 [4 6]。列表加法把两组元素接起来,数组加法把对应位置的数字相加。数组打印时常省略元素之间的逗号,这是显示格式的差异。
import numpy as np 把 NumPy 导入并取一个短名字,后面写 np.array 就能调用创建数组的功能。数组 array 把多项数值按形状排列,NumPy 常用的数组类型叫 ndarray。普通数值数组使用统一的数据类型,方便批量执行运算。
python
import numpy as np
values = np.array([1, 2, 3], dtype=np.float64)
print(values)
print(values.dtype)
print(values * 2)
三行结果分别是 [1. 2. 3.]、float64 和 [2. 4. 6.]。dtype 指定元素的数据类型,float64 使用 64 位表示浮点数。后面的乘法作用在每个元素上,不需要自己写三次乘二。
如果需要带小数的成绩调整,开始就用浮点类型会更清楚。整数数组里直接写入 80.5 时,小数部分可能被转换掉。混入"缺考"字符串也会改变构造出来的数组类型,本章成绩统一要求为数值。
一张成绩表有两个轴
我们把四名学生排成行,三门课程排成列。先看原始表格。
| 学生 | Python | 数据处理 | AI 基础 |
|---|---|---|---|
| 小林 | 80 | 70 | 90 |
| 小周 | 60 | 85 | 75 |
| 小陈 | 95 | 90 | 85 |
| 小吴 | 50 | 60 | 70 |
对应数组如下。
python
import numpy as np
scores = np.array([[80, 70, 90], [60, 85, 75], [95, 90, 85], [50, 60, 70]], dtype=np.float64)
print(scores.shape)
print(scores.ndim)
print(scores.size)
结果是 (4, 3)、2 和 12。shape 是形状,表示每个轴分别有多少元素;ndim 是轴的数量;size 是全部元素数量。这里沿行方向有四项,沿列方向有三项,所以形状是四行三列。
轴 axis 是数组中可以沿着取元素或汇总的一个方向。本章二维数组的第 0 轴对应学生,第 1 轴对应课程。这两个方向的编号固定,但"学生"和"课程"的含义来自我们怎样放置数据,NumPy 不会从数字猜到表头。

向量描述一名学生,矩阵组织多名学生
向量可以先理解成按固定顺序排列的一组数字。一名学生的三门成绩就可以表示为 [80, 70, 90]。这个向量有三个分量,每个位置都对应一项特征,也就是用于描述样本的一个数值。换成其他任务,特征可能是学习时长、练习次数或测验得分。
多名学生的向量按行排列,就形成一张矩阵。本章用二维 ndarray 保存这张数字表,不使用 NumPy 里另有运算规则的 matrix 类型。
python
import numpy as np
one_student = np.array([80, 70, 90])
one_row = np.array([[80, 70, 90]])
one_column = one_student.reshape(3, 1)
print(one_student.shape)
print(one_row.shape)
print(one_column.shape)
结果依次是 (3,)、(1, 3) 和 (3, 1)。一个一维数组不带明确的行列朝向,两个二维数组则分别有一行和一列。reshape 在元素总数不变的前提下改变形状。(3,) 里的逗号表示单元素元组,和第 11 章的元组写法一致。
后面做模型输入时,特征顺序要固定。把 Python 与 AI 基础两列互换,数组形状仍然是四行三列,数字的含义已经变了。形状检查能发现一部分问题,列名和顺序也要保留。本章用 SUBJECTS 常量和 CSV 表头记录这个顺序。
同时写行下标和列下标
二维数组取一个元素时,可以在同一对方括号里写两个下标。
python
import numpy as np
scores = np.array([[80, 70, 90], [60, 85, 75], [95, 90, 85], [50, 60, 70]])
print(scores[1, 2])
print(scores[0])
print(scores[:, 0])
print(scores[:2, 1:])
第一行取到第二名学生、第三门课的 75。第二行得到第一名学生的 [80 70 90]。第三行取所有学生的第一门课,得到 [80 60 95 50]。第四行取前两名学生的后两门课,得到两行两列的数组。
下标仍然从 0 开始,切片右端仍不包括在内。冒号表示这一轴全部保留,scores[:, 0] 保留全部行并选第 0 列。先在原表上圈出想取的区域,再翻译成下标,能更快看出行列是否写反。
数组的基本切片通常得到视图。视图与原数组共享底层数据,修改视图可能改到原数组。
python
import numpy as np
scores = np.array([[80, 70, 90], [60, 85, 75]])
view = scores[:, 0]
view[0] = 0
print(scores[0, 0])
copied = scores[:, 1].copy()
copied[0] = 0
print(scores[0, 1])
输出是 0 和 70。想保留原成绩,先使用 copy 再修改。这里说明的是基本切片;布尔索引等其他取数方式可能产生副本,不能把所有索引都当作视图。
求均分之前先确定汇总方向
聚合就是把一组数字汇总成较少的数字,比如总和、均值、最大值。下面算三种均分。
python
import numpy as np
scores = np.array([[80, 70, 90], [60, 85, 75], [95, 90, 85], [50, 60, 70]], dtype=np.float64)
print(scores.mean(axis=1))
print(scores.mean(axis=0))
print(scores.mean())
axis=1 汇总课程这个轴,每名学生保留一个均分,得到约 80、73.33、90、60。axis=0 汇总学生这个轴,每门课程保留一个均分,得到 71.25、76.25、80。没有指定 axis 时,所有十二个数一起平均,得到约 75.83。
可以把 axis 参数读成"要消去哪个方向"。四行三列沿第 1 轴汇总,结果有四个数;沿第 0 轴汇总,结果有三个数。不要机械记成"0 求列,1 求行"然后直接用到三维数组上,本章先用结果长度核对二维数据的含义。
sum、min 和 max 分别求总和、最小值和最大值,也能使用 axis。查看均分时,显示到两位小数方便阅读,计算过程先保留原精度。均分的小数差异不要用字符串比较判断,程序使用 np.allclose 检查浮点结果是否在允许误差内一致。
广播让一组调整值作用于每一行
现在按课程给每名学生分别加 5、0、2 分。三个数字与三列对应,可以直接相加。
python
import numpy as np
scores = np.array([[80, 70, 90], [60, 85, 75]])
bonus = np.array([5, 0, 2])
print(scores + bonus)
结果是两行 [85 70 92] 和 [65 85 77]。这种让较小形状参与较大数组运算的规则叫广播 broadcasting。本例把同一组三列调整值用于每一名学生。
广播从形状最右边的轴开始比较。两边大小相同,或其中一边为 1,就能对应;较短形状在左侧缺少的轴按 1 看待。四行三列的 (4, 3) 和三元素的 (3,) 能对应,最后一个轴都是 3。把调整值写成 (2,),最后一个轴变成 3 与 2,会产生 ValueError。
如果想让每名学生所有课程都加上自己的调整量,四个数字需要变成 (4, 1),这样每行的一项才能扩展到三列。仅仅"总元素数相近"不足以决定能不能广播,要按轴比较形状。
python
import numpy as np
scores = np.array([[80, 70, 90], [60, 85, 75], [95, 90, 85], [50, 60, 70]])
student_bonus = np.array([1, 2, 3, 4]).reshape(4, 1)
print(scores + student_bonus)
四行结果依次是 [81 71 91]、[62 87 77]、[98 93 88]、[54 64 74]。小林每门加一,小周每门加二,其余依此类推。这个调整与前面的按课程加分回答不同的需求,数字摆在哪个轴上会决定作用对象。
汇总后怎样保留一个轴
如果想把每名学生的成绩减去自己的均分,均分也要与原表按行对应。mean 的 keepdims=True 会在汇总后保留长度为 1 的轴。
python
import numpy as np
scores = np.array([[80, 70, 90], [60, 85, 75]], dtype=np.float64)
means = scores.mean(axis=1, keepdims=True)
print(means.shape)
print(np.round(scores - means, 2))
均分形状是 (2, 1),差值两行约为 [0 -10 10] 和 [-13.33 11.67 1.67]。小林每门都减去 80,小周每门都减去约 73.33。负值只表示低于该学生的个人均分,原成绩仍保留在 scores 中。np.round 在这里帮助展示两位小数,没有把原始成绩改成整数。
数组结果保存前要转换格式
第 12 章直接把字典和列表交给 JSON 模块。本章计算出来的 ndarray,需要先用 tolist 转成普通 Python 列表,单个均值也可以用 float 转成 Python 浮点数。
python
import json
import numpy as np
means = np.array([80, 73.33333333333333])
print(json.dumps({"means": means.tolist()}))
结果是一段含两个均分的 JSON 文本。主程序保存结果时采用相同做法,读回后便能用第 12 章学过的列表与字典操作核对。数据在不同工具之间传递时,既要保留数值,也要留住列名、形状与统计口径。
用布尔数组筛选成绩
比较数组与数字,会逐项产生布尔值组成的数组。我们可以用它筛选行。
python
import numpy as np
scores = np.array([[80, 70, 90], [60, 85, 75], [95, 90, 85], [50, 60, 70]])
mask = (scores >= 60).all(axis=1)
print(mask)
print(scores[mask].shape)
mask 得到 [ True True True False],筛选结果形状为 (3, 3)。all 要求每项都为真,any 要求至少一项为真。axis=1 把每名学生的三个及格判断汇总成一个真假值,然后保留为真的学生行。
直接把多个元素的布尔数组放进 if,Python 无法知道你想要求全部为真还是至少一个为真,会提示真假含糊。要先用 all 或 any 说清口径。完整的错误、修复代码及结果见 error-samples.md。
跑完这一份成绩统计
score_stats.py 保存本章数据、统计函数和循环对照。程序要求二维数组、每行三门课程,至少一名学生,所有成绩都在 0 到 100 之间且没有缺失或无穷值。输入不符合条件时会产生带原因的 ValueError,避免把错误数据继续算成正常均分。
powershell
.venv\Scripts\python.exe examples/score_stats.py
你会看到形状 (4, 3)、学生与课程均分、全部及格判断,以及"批量与循环一致 True"。结果同时保存到 score_summary.json。调整后的成绩使用 np.minimum 截到 100 分,给 95 加 5 后得到 100。
Notebook 保存在 score_analysis.ipynb,已经保留实际执行输出。在第 9 章的编辑器中打开它,选择安装了本章依赖的 Python 内核,再从头运行全部单元格。
留一道形状练习
在 Notebook 中取出小周和小陈的 Python 与 AI 基础成绩,结果形状应为 (2, 2);再算这两名学生在这两门课上的个人均分,应得到 67.5 和 90。先用纸面表格确认取了哪些数,再写索引和 axis。其余练习及答案见 【零基础学AI】第 13 章课后练习与答案。
下一章给数字重新带上列名,使用 Pandas 读取、清洗和保存表格。
