向量和矩阵是线性代数中组织数值的基本形式,也是 AI 编程中表示样本、特征、模型参数和批量数据的重要工具。一个样本可以表示为向量,一批样本可以组成矩阵,图像、序列和神经网络的中间结果则常使用更高维数组表示。
在线性代数中,需要关注向量的分量、矩阵的行列结构以及相应的尺寸;在 Python 和 NumPy 中,还需要理解数组的维度数、形状、轴、索引、切片和数据类型。
一、标量、向量与矩阵
1、标量
标量(Scalar)是数域中的一个元素,常见的标量包括实数和复数。标量本身没有向量分量或矩阵的行列结构。
在实数范围内,可以设:
例如:
其中,a、b、c 是标量变量,3、−2 和 0.5 是它们所取的标量值。
复数也可以作为标量。例如:
系列文章中若无特别说明,向量和矩阵的元素均取自实数域 ℝ;对于复数向量和复数矩阵,可以将 ℝ 相应替换为 ℂ。
在 AI 编程中,学习率、损失值和正则化系数通常使用标量表示;向量和矩阵中的每个元素也都是标量。
2、向量
向量(Vector)是由若干个有序分量组成的数学对象。一个 n 维向量可以写成:
例如:
向量 v 包含 3 个分量,因此是三维向量。
为了统一符号,以下采用线性代数中的常见约定,将向量写成列向量。对应的行向量使用列向量的转置表示:
列向量 v 的尺寸为 3 × 1,行向量 vᵀ 的尺寸为 1 × 3。转置改变行列形式,不改变向量包含的三个分量。
向量的维数是它包含的分量数量。
3、矩阵
矩阵(Matrix)是按照行和列排列的数值结构。一个 m × n 矩阵可以写成:
其中,aᵢⱼ 表示矩阵 A 中第 i 行、第 j 列的元素。
例如:
矩阵 A 有 2 行、3 列,因此尺寸为 2 × 3。
其中:
4、矩阵的行与列
矩阵的每一行和每一列都可以单独看作一个向量。
矩阵 A 的第 i 行可以写成:
这一行包含 n 个元素,可以看作一个 n 维行向量,尺寸为 1 × n。
矩阵 A 的第 j 列可以写成:
这一列包含 m 个元素,可以看作一个 m 维列向量,尺寸为 m × 1。
对于:
第 1 行是:
第 2 列是:
因此,一个 m × n 矩阵既可以看作由 m 个 n 维行向量按行排列而成,也可以看作由 n 个 m 维列向量按列排列而成。
二、使用 Python 基本语法表示向量和矩阵
1、标量表示
在 Python 中,整数、浮点数和复数都可以表示标量:
count = 3learning_rate = 0.01complex_value = 2 + 3jprint(type(count))print(type(learning_rate))print(type(complex_value))
输出:
<class 'int'><class 'float'><class 'complex'>
2、使用列表表示向量
Python 可以使用列表保存向量的各个分量:
v = [2, -1, 3]print(v)print(len(v))
输出:
len(v) 返回列表中的元素数量,对应向量包含的分量数。
可以通过索引访问具体分量:
print(v[0])print(v[1])print(v[2])
输出:
Python 索引从 0 开始,因此:
v[0] 对应数学中的 v₁;
v[1] 对应数学中的 v₂;
v[2] 对应数学中的 v₃。
Python 列表只保存一组有序元素,本身不区分列向量和行向量。
3、使用嵌套列表表示矩阵
矩阵可以使用嵌套列表表示。外层列表保存各行,内层列表保存每一行的元素:
A = [ [1, 2, 3], [4, 5, 6]]print(A)
输出:
访问第 1 行:
输出:
访问第 2 行、第 3 列的元素:
输出:
4、检查矩阵形状
对于结构规则的嵌套列表:
A = [ [1, 2, 3], [4, 5, 6]]rows = len(A)columns = len(A[0])print(rows)print(columns)
输出:
这种写法默认每一行的长度都相同。如果各行长度不同,就不能表示规则矩阵。
可以通过自定义函数检查嵌套列表的结构:
def get_matrix_shape(matrix): # 检查矩阵是否为空 if not matrix: return 0, 0 columns = len(matrix[0]) # 检查各行长度是否相同 if any(len(row) != columns for row in matrix): raise ValueError("各行长度必须相同") return len(matrix), columnsA = [ [1, 2, 3], [4, 5, 6]]print(get_matrix_shape(A))
输出:
三、创建 NumPy 数组
NumPy 使用 ndarray 对象表示多维数组。通常先导入 NumPy:
1、零维数组
单个数值可以转换为零维 NumPy 数组:
scalar_array = np.array(3.5)print(scalar_array)print(scalar_array.ndim)print(scalar_array.shape)
输出:
零维数组保存一个标量值,但它仍然是 ndarray 对象,不是普通的 Python 标量。
可以使用 item() 方法取得其中的 Python 标量:
value = scalar_array.item()print(value)print(type(value))
输出:
2、一维数组
可以将一层 Python 列表传给 np.array(),创建一维 NumPy 数组:
v = np.array([2, -1, 3])print(v)print(v.ndim)print(v.shape)
输出:
这个数组包含 3 个元素,只有一个轴,因此形状为 (3,)。
(3,) 中的逗号表示它是一个只有一个元素的元组。
3、二维数组
可以将规则嵌套列表传给 np.array(),创建二维 NumPy 数组:
A = np.array([ [1, 2, 3], [4, 5, 6]])print(A)print(A.ndim)print(A.shape)
输出:
这个数组有两个轴:
第 0 轴的长度为 2;
第 1 轴的长度为 3。
数学中的矩阵尺寸(Matrix Size)写成 2 × 3,NumPy 数组的形状写成 (2, 3)。两者描述相同的行列结构,但符号形式和使用场景不同。
4、常用数组属性
NumPy 数组的常用属性包括:
ndim:数组包含的轴数;
shape:每个轴上的元素数量;
size:数组中的元素总数;
dtype:数组元素的数据类型。
例如:
A = np.array([ [1, 2, 3], [4, 5, 6]])print(A.ndim)print(A.shape)print(A.size)print(A.dtype)
输出结果可能为:
在部分系统中,整数类型可能显示为 int32。
四、NumPy 一维数组与数学上的行/列向量
1、一维数组没有行列方向
创建一维数组:
v = np.array([1, 2, 3])print(v.shape)
输出:
此一维数组的形状为 (3,),表示一维数组只有一个轴,因此它既不能表示数学上的行向量,也不能表示列向量。
而对应的数学中的列向量尺寸为 3 × 1:
在 NumPy 中若要保留明确的列结构,应使用形状 (3, 1),而不是 (3,)。
2、对一维数组转置
对一维数组使用转置属性 .T:
v = np.array([1, 2, 3])print(v.T)print(v.T.shape)
输出:
一维数组只有一个轴,没有两个轴可以交换,因此转置不会改变其形状。
3、二维数组与行向量
可以使用 reshape() 方法把一维数组转换为二维数组:
v = np.array([1, 2, 3])row_vector = v.reshape(1, -1)print(row_vector)print(row_vector.shape)
输出:
形状为 (1, 3) 的二维数组对应数学中尺寸为 1 × 3 的行向量:
4、二维列向量
可以把一维数组转换为二维数组:
column_vector = v.reshape(-1, 1)print(column_vector)print(column_vector.shape)
输出:
形状为 (3, 1) 的二维数组对应数学中尺寸为 3 × 1 的列向量:
5、使用 np.newaxis 增加轴
也可以使用 np.newaxis 对一维数组增加轴,从而创建二维行结构或列结构:
v = np.array([1, 2, 3])row_vector = v[np.newaxis, :]column_vector = v[:, np.newaxis]print(row_vector.shape)print(column_vector.shape)
输出:
其中:
v[np.newaxis, :] 在数组前面增加一个轴;
v[:, np.newaxis] 在数组后面增加一个轴。
形状 (3,)、(1, 3) 和 (3, 1) 包含相同的三个数值,但在矩阵乘法、广播和批量计算中可能产生不同结果。
五、维度、形状与轴
1、向量维数与数组维度数
需要区分数学中的向量维数和 NumPy 中数组的维度数。
数学中的向量:
包含 3 个分量,因此是三维向量。
对应的一维 NumPy 数组:
于是:
向量维数为 3;
NumPy 数组的维度数(ndim)为 1;
NumPy 数组的形状(shape)为 (3,)。
这些概念描述的对象不同,不能混用。
2、数组形状
形状(shape)表示每个轴上的元素数量:
A = np.array([ [1, 2, 3], [4, 5, 6]])print(A.shape)
输出:
其中:
A.shape[0] 为 2;
A.shape[1] 为 3。
可以分别获取:
rows = A.shape[0]columns = A.shape[1]print(rows)print(columns)
输出:
3、二维数组的轴
设:
A = np.array([ [1, 2, 3], [4, 5, 6]])
对应数学矩阵:
数组形状为 (2, 3)。
沿 axis=0 求和时,第 0 轴被压缩,分别对每一列求和:
对应代码:
输出:
结果形状为 (3,)。
沿 axis=1 求和时,第 1 轴被压缩,分别对每一行求和:
对应代码:
输出:
结果形状为 (2,)。
指定某个轴执行缩减运算时,该轴通常会消失,其他轴被保留下来。
设置 keepdims=True 可以将被缩减的轴保留为长度 1:
column_sums = A.sum( axis=0, keepdims=True)row_sums = A.sum( axis=1, keepdims=True)print(column_sums)print(column_sums.shape)print(row_sums)print(row_sums.shape)
输出:
[[5 7 9]](1, 3)[[ 6] [15]](2, 1)
4、三维数组的轴
创建三维数组:
data = np.array([ [ [1, 2, 3], [4, 5, 6] ], [ [7, 8, 9], [10, 11, 12] ]])print(data.ndim)print(data.shape)
输出:
可以把这个数组理解为:
第 0 轴包含 2 个二维数组;
第 1 轴表示每个二维数组中的 2 行;
第 2 轴表示每行中的 3 个元素。
三维 NumPy 数组不属于通常所说的二维矩阵表示。矩阵具有两个轴,分别对应行和列;三维数组则具有三个轴。
六、数组的索引与切片
1、一维数组索引
v = np.array([10, 20, 30, 40])print(v[0])print(v[-1])
输出:
其中:
v[0] 取得第 1 个元素;
v[-1] 取得最后一个元素。
使用切片取得部分元素:
输出:
切片范围包含起始位置,不包含结束位置。
2、二维数组索引
A = np.array([ [1, 2, 3], [4, 5, 6], [7, 8, 9]])print(A[1, 2])
输出:
A[1, 2] 表示第 2 行、第 3 列的元素。
也可以写成:
不过,A[1, 2] 更直接,也更符合 NumPy 多维索引的写法。
3、提取一行
提取第 1 行:
first_row = A[0, :]print(first_row)print(first_row.shape)
输出:
使用整数索引后,第 0 轴消失,因此结果是一维数组。
如果需要保留二维行结构,可以使用切片:
first_row_2d = A[0:1, :]print(first_row_2d)print(first_row_2d.shape)
输出:
4、提取一列
提取第 1 列:
first_column = A[:, 0]print(first_column)print(first_column.shape)
输出:
如果需要保留二维列结构,同样可以使用切片:
first_column_2d = A[:, 0:1]print(first_column_2d)print(first_column_2d.shape)
输出:
也可以使用整数列表索引:
first_column_2d = A[:, [0]]
因此:
A[:, 0] 的形状为 (3,);
A[:, 0:1] 的形状为 (3, 1);
A[:, [0]] 的形状也为 (3, 1)。
在二维数组中,单个整数索引会移除对应轴;切片或单元素列表索引可以保留二维结构。
5、提取子矩阵
submatrix = A[0:2, 1:3]print(submatrix)print(submatrix.shape)
输出:
这里提取了第 1~2 行和第 2~3 列。
七、形状变换与数组组合
1、使用 reshape() 改变形状
reshape() 方法可以改变数组的形状和索引组织方式,但不改变元素总数。
data = np.array([ 1, 2, 3, 4, 5, 6])matrix = data.reshape(2, 3)print(matrix)print(matrix.shape)
输出:
原数组形状为 (6,),新数组形状为 (2, 3)。
变形前后的元素总数必须相同:
reshape() 中可以使用 -1,让 NumPy 自动推断对应轴的长度:
matrix = data.reshape(3, -1)print(matrix)print(matrix.shape)
输出:
[[1 2] [3 4] [5 6]](3, 2)
一个 reshape() 中最多只能使用一个 -1。
reshape() 会尽可能返回视图;若当前内存布局不支持目标形状,则可能创建副本。
2、展平数组
使用 flatten() 方法可以将多维数组展平为一维数组:
A = np.array([ [1, 2, 3], [4, 5, 6]])flattened = A.flatten()print(flattened)print(flattened.shape)
输出:
flatten() 返回新的独立数组。
也可以使用 ravel() 方法:
ravel() 会尽可能返回原数组的视图;无法共享原数据时才创建副本。
3、增加和删除长度为 1 的轴
使用 np.expand_dims() 函数增加轴:
v = np.array([1, 2, 3])expanded = np.expand_dims( v, axis=0)print(expanded)print(expanded.shape)
输出:
使用 np.squeeze() 函数删除长度为 1 的轴:
restored = np.squeeze( expanded, axis=0)print(restored)print(restored.shape)
输出:
如果省略 axis 参数,np.squeeze() 会删除所有长度为 1 的轴。
4、堆叠与连接数组
np.stack() 函数会创建一个新轴:
v1 = np.array([1, 2, 3])v2 = np.array([4, 5, 6])rows = np.stack( [v1, v2], axis=0)columns = np.stack( [v1, v2], axis=1)print(rows)print(rows.shape)print(columns)print(columns.shape)
输出:
[[1 2 3] [4 5 6]](2, 3)[[1 4] [2 5] [3 6]](3, 2)
np.concatenate() 不创建新轴,而是沿已有轴连接数组:
A = np.array([ [1, 2], [3, 4]])B = np.array([ [5, 6]])combined = np.concatenate( [A, B], axis=0)print(combined)print(combined.shape)
输出:
[[1 2] [3 4] [5 6]](3, 2)
八、数据类型与内存共享
1、数组元素的数据类型
每个 NumPy 数组都具有统一的 dtype,用于描述数组元素的存储类型:
A = np.array([1, 2, 3])print(A.dtype)
输出结果可能为:
创建浮点数组:
A = np.array( [1, 2, 3], dtype=float)print(A)print(A.dtype)
输出:
在线性代数中的归一化、方程求解和其他连续数值运算中,通常需要使用浮点类型。
2、转换数据类型
可以使用 astype() 方法转换数据类型:
A = np.array([1, 2, 3])B = A.astype(float)print(B)print(B.dtype)
输出:
astype() 默认返回新数组,不修改原数组的数据类型。
3、混合数据会进行统一转换
A = np.array([1, 2.5, 3])print(A)print(A.dtype)
输出:
因为数组中包含浮点数,整数会被统一转换为浮点数。
如果混合数值与字符串:
A = np.array([1, 2, "3"])print(A)print(A.dtype)
NumPy 通常会把所有元素转换为字符串。这样的数组不能直接进行一般线性代数运算。
4、不规则嵌套列表
下面的嵌套列表各行长度不同:
data = [ [1, 2, 3], [4, 5]]
新版 NumPy 通常会拒绝直接将其转换为规则数值数组:
此时会抛出 ValueError。
如果明确指定:
A = np.array( data, dtype=object)
可以创建 object 数组,但其中保存的是普通 Python 对象,不适合常规矩阵计算。
5、基本切片通常返回视图
A = np.array([ 10, 20, 30, 40])part = A[1:3]part[0] = 99print(part)print(A)
输出:
NumPy 的基本切片通常返回原数组的视图,因此修改 part 也会影响 A。
如果需要独立数组,应显式复制:
6、高级索引返回副本
使用整数数组或整数列表进行高级索引时,结果通常是副本:
A = np.array([ 10, 20, 30, 40])part = A[[1, 2]]part[0] = 99print(part)print(A)
输出:
修改 part 不会影响原数组 A。
九、数组在 AI 编程中的典型应用
1、单个样本
假设一个样本包含 D 个特征,在数学中可以表示为:
在 NumPy 中,单个样本通常保存为一维数组。例如:
sample = np.array([ 0.8, 1.2, -0.5, 2.0])print(sample.shape)
输出:
这里的 4 表示特征数量。
2、批量样本
如果约定每个样本占据一行,一共有 N 个样本,每个样本包含 D 个特征,则数据矩阵可以写成:
其中:
N 表示样本数量;
D 表示每个样本的特征数量;
第 i 行表示第 i 个样本;
第 j 列表示所有样本的第 j 个特征。
NumPy 示例:
X = np.array([ [0.8, 1.2, -0.5, 2.0], [1.0, 0.7, 0.3, 1.5], [0.2, 1.8, -0.1, 0.9]])print(X.shape)
输出:
这里:
3 表示样本数量;
4 表示特征数量。
本系列文章涉及批量数据时,通常采用“样本按行、特征按列”的约定;若某个公式采用其他排列方式,将单独说明。
3、标签数组
如果每个样本对应一个标量标签,可以写成:
在 NumPy 中,标签通常保存为一维数组:
y = np.array([0, 1, 0])print(y.shape)
输出:
如果后续运算需要二维列结构,可以转换为:
y_column = y.reshape(-1, 1)print(y_column)print(y_column.shape)
输出:
选择 (N,) 还是 (N, 1),需要根据后续运算和函数接口决定。
4、图像数据
一张灰度图像可以表示为二维数组:
(height, width)
一张通道位于最后一轴的彩色图像可以表示为:
(height, width, channels)
一批彩色图像可以表示为:
(batch, height, width, channels)
例如:
images = np.zeros( (32, 64, 64, 3))print(images.shape)
输出:
表示:
32 张图像;
每张图像高 64;
每张图像宽 64;
包含 3 个颜色通道。
PyTorch 图像张量通常采用:
(batch, channels, height, width)
因此,在 NumPy 与深度学习框架之间传递图像时,需要确认轴的排列顺序。
5、序列数据
一批序列数据常使用形状:
(batch, sequence_length, feature_dim)
例如:
sequences = np.zeros( (16, 20, 128))print(sequences.shape)
输出:
可以理解为:
16 个序列;
每个序列包含 20 个位置;
每个位置由 128 维特征表示。
在实际数据处理中,数组的每个轴通常对应一种明确含义。只有理解各轴表示什么,才能正确执行转置、求和、广播和矩阵乘法。
十、常见问题及使用建议
1、区分数学对象与 NumPy 数组概念
数学中的向量和矩阵,与 NumPy 中的数组表示方式并不是完全对应的概念。
数学中的:
向量维数表示向量包含的分量数量;
行向量和列向量表示不同的排列形式;
矩阵尺寸表示行数与列数。
NumPy 中的:
ndim 表示数组包含的轴数;
shape 表示每个轴上的元素数量;
size 表示数组中的元素总数。
例如,一维数组 (3,) 包含 3 个元素,但它没有明确的行列方向;只有转换为 (1, 3) 或 (3, 1) 的二维数组后,才对应数学中的行向量或列向量。
又比如,数学中的三维向量表示具有 3 个分量的向量,指的是向量维度(Vector Dimension),而 NumPy 的三维数组表示具有 3 个轴的数据结构,指的是数组维度(Array Dimensionality)。
理解这些概念的区别,是正确进行索引、矩阵运算、广播和批量计算的基础。
2、注意整数索引会减少轴
结果是一维数组。
需要保留二维列结构时,应使用:
或:
3、注意切片可能与原数组共享数据
NumPy 基本切片通常返回视图。修改切片结果时,原数组可能同时发生变化。
需要独立数据时,应使用:
4、注意数组的数据类型
整数数组适合保存计数、索引和分类标签,但归一化、方程求解和连续数值运算通常需要浮点类型。
可以在创建数组时指定:
A = np.array( data, dtype=float)
5、同时检查数值和形状
下面三种数组可能包含相同数值:
但它们在矩阵乘法、广播和批量计算中的行为不同。
调试线性代数程序时,应经常检查:
print(array.shape)print(array.ndim)print(array.dtype)
📘 小结
向量和矩阵是线性代数中组织数值的基本结构,NumPy 则使用不同维度和形状的数组表示这些数学对象。理解向量维数、矩阵尺寸与数组的 ndim、shape、size 之间的区别,是进行后续向量运算、矩阵运算和批量数据处理的基础。