我叫程砚舟,平时在团队里负责数据管道和建模前的特征工程。很多同事来问的不是“怎么用 NumPy”,而是更具体的:为什么我按教程写了,矩阵形状总不对、类型总变成 object、结果一算就溢出。今天我就围绕“numpy创建矩阵”这件事,把常用创建方式按真实工作场景拆开讲清楚:你该用哪一种、会踩哪些坑、以及怎么快速自检。

在进入细节前先说一句判断标准:你想要的到底是“二维数组(2D ndarray)”,还是“线性代数意义的矩阵对象”?在 2026 年的工程实践里,我更推荐直接用 ndarray(也就是 np.array / np.zeros 这些产生的对象),因为生态里绝大多数库(SciPy、scikit-learn、PyTorch 的数据接口等)都围绕 ndarray 设计;np.matrix 这个老对象常常让人因为 * 运算语义不同而误判结果。

你要的“矩阵”,本质是形状、dtype、内存布局三件事

我做代码评审时会让大家在创建矩阵之后立刻打印三样:shapedtypeorder(或至少知道是不是连续内存)。它们决定了后面所有运算是否稳定。

  • shape:是不是 (m, n),有没有不小心变成 (n,) 或 (m, 1, n)
  • dtype:是不是你想要的 float32/float64/int64,有没有混入字符串导致 object
  • 内存布局:大多数情况下无需纠结,但遇到大矩阵乘法、切片后性能异常时,np.ascontiguousarray 能救命

顺手给一个我常用的“创建后自检”小片段:

import numpy as np

numpy创建矩阵从入门到避坑写法-实战场景全覆盖

A = np.zeros((3, 4), dtype=np.float64)print(A.shape, A.dtype, A.flags['C_CONTIGUOUS'])

这一步很朴素,但能把“看起来像矩阵”的东西和“可可靠计算的矩阵”分开。

numpy创建矩阵的四类常用入口:从“明确目的”反推写法

不同来源的数据,写法不同。我按工作中最常见的四类来讲。

1)从现成数据来:np.array与 shape 的控制 当你手里已经有列表、嵌套列表、读取的行列数据,最直接就是 np.array

A = np.array([[1, 2, 3],              [4, 5, 6]], dtype=np.int64)# shape = (2, 3)

常见坑是“锯齿列表”:

bad = np.array([[1, 2], [3, 4, 5]])print(bad.dtype)  # 往往变成 object

一旦变成 object,你后面很多向量化计算都会变慢甚至报错。我的处理方式很硬:要么先补齐、要么改成稀疏结构(SciPy sparse),不要让 object 混进来。

另一类坑是“读 CSV 后全是字符串”。这不是 NumPy 的错,是你输入没清洗。你可以明确转型:

A = np.array([["1.0", "2.5"], ["3.2", "4.1"]], dtype=np.float64)

如果你只想改变形状而不改数据顺序,用 reshape,并且保证元素个数对得上:

x = np.arange(12)A = x.reshape(3, 4)   # 3行4列

我习惯写成 reshape(rows, cols),而不是 reshape(-1, cols),因为后者在数据长度变化时更容易悄悄生成你没预期的行数。

2)从“空白画布”来:zeros/ones/full/empty怎么选 工程里初始化矩阵,我更关注“是否需要确定的初值”。

  • np.zeros((m, n)):默认全 0,安全
  • np.ones((m, n)):全 1
  • np.full((m, n), fill_value):全指定常数,做 mask、平滑项很常用
  • np.empty((m, n)):不初始化内存,速度快但内容不可控,适合你会立刻完全覆盖赋值的场景
W = np.full((128, 64), 0.01, dtype=np.float32)mask = np.zeros((720, 1280), dtype=bool)

empty 的坑我见过太多:有人 np.empty 后只填了一部分,剩下的“脏值”参与计算,结果漂移到你怀疑人生。除非你非常确定会写满,否则别用。

3)从“规律序列”来:arange/linspace+ reshape 才是高频组合 需要规则网格或索引矩阵时,我最常写这两种:

  • np.arange(start, stop, step):离散步长
  • np.linspace(start, stop, num):指定点数,包含端点(默认)
A = np.arange(1, 13).reshape(3, 4)t = np.linspace(0.0, 1.0, 5)  # 0.0, 0.25, 0.5, 0.75, 1.0

这里的“坑”主要是 dtype:arange 在参数都是整数时会生成整数数组;你后面做归一化除法,如果写成原地除法 A /= 10,整数会截断。我的习惯是:只要后续会做比例计算,创建时就把 dtype 定成 float:

A = np.arange(12, dtype=np.float64).reshape(3, 4)

4)从“单位矩阵/对角结构”来:eye/identity/diag的差别 线代相关的初始化,我常用:

  • np.eye(N, M=None, k=0):可以非方阵,可偏移对角线
  • np.identity(N):只能方阵,语义更“纯”
  • np.diag(v):由向量生成对角矩阵;或从矩阵抽对角线
I = np.eye(4, dtype=np.float64)shift = np.eye(4, k=1)  # 上一条对角线为1D = np.diag([1, 2, 3])

如果你要的是稀疏对角矩阵(维度很大),建议直接用 SciPy 的稀疏对角构造(例如 scipy.sparse.diags),避免 dense 矩阵占内存。但这属于“矩阵存储策略”层面了,先把 NumPy 的 dense 用顺最重要。

我最常见的三种“写对了但算错了”:乘法、广播、复制

创建矩阵只是开始,接下来的运算语义更容易让人误会。

乘法:*是逐元素,矩阵乘用 @dot 对 ndarray 来说:

A = np.arange(6).reshape(2, 3)B = np.arange(6).reshape(3, 2)C = A @ B        # (2,2) 矩阵乘法D = A * A        # (2,3) 逐元素乘法

很多“结果不对”的根源是把 * 当成矩阵乘。只要你心里把 @ 记成“线代入口”,这类问题会少很多。

广播:看起来能跑,不代表形状是你想的举一个真实的坑:你想给每一列减去列均值,写成:

X = np.random.randn(5, 3)mu = X.mean(axis=0)     # shape = (3,)Xc = X - mu             # 广播成功:按列减

这没问题。但如果你把 axis 写错:

mu = X.mean(axis=1)     # shape = (5,)Xc = X - mu             # 报错或产生非预期广播

我的做法是:凡是要按行/列广播,均值这类统计量尽量用 keepdims=True,让形状更“显性”:

mu = X.mean(axis=0, keepdims=True)  # shape = (1,3)Xc = X - mu

复制:B</b>= A 不是复制,是引用 矩阵创建后做“备份”,很多人写:

B = AB[0, 0] = 999

然后发现 A 也变了。这不是 bug。正确复制是:

B = A.copy()
numpy创建矩阵时,我会额外提醒的边界:随机数、版本、文档来源

你可能希望我给出“2026 年 NumPy 某某数据或结论”,但这类版本/性能数字在不同硬件、不同 BLAS(MKL/OpenBLAS/Accelerate)下差异很大,我不建议在没有明确基准测试链接的情况下写死结论。更稳的做法是:把权威文档入口给你,遇到行为差异时能追溯。

  • NumPy 官方文档(数组创建例程、广播、dtype 等):https://numpy.org/doc/
  • NumPy Random 官方文档(随机矩阵、可复现种子 Generator):https://numpy.org/doc/stable/reference/random/

如果你需要随机矩阵,2026 年我仍建议用新接口 default_rng,避免旧的全局状态让实验不可复现:

rng = np.random.default_rng(2026)A = rng.normal(0, 1, size=(3, 4))

到这里,你已经能覆盖 90% 的“numpy创建矩阵”需求:从数据、从初始化、从规律序列、从线代结构,并且知道最容易踩的 dtype、广播、乘法语义和复制问题。你如果愿意再给我一个具体场景(比如“要创建稀疏大矩阵”“要做图像卷积的滑窗矩阵”“要把 Pandas DataFrame 变成训练矩阵”),我可以把写法进一步收敛到你那类业务的最短可维护代码。