ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

03 NumPy 入门:机器学习中的数组和矩阵

03 NumPy 入门:机器学习中的数组和矩阵 前言前两篇分别介绍了机器学习的基本流程和 Python 环境。接下来要认识机器学习代码里最常见的数据结构数组和矩阵。我们看到的原始数据形式差别很大。照片由像素组成文本可以被转换为词频或向量Excel 表格包含一行行记录。进入模型之前它们通常都要变成数字并按照一定形状排列成数组。模型训练时所做的大量工作本质上是在这些数字上进行加法、乘法、求平均值和矩阵运算。Python 自带的列表可以存放数字但机器学习需要处理成千上万甚至更多的数据。NumPy 提供的数组更适合这类任务。本文不会把 NumPy 写成函数手册而是围绕一个问题展开如何把 100 名学生的三项特征组织成模型能够使用的矩阵并完成一次简单计算。为什么机器学习需要 NumPyPython 原生list很灵活同一个列表可以同时保存整数、字符串和其他对象。这种灵活性适合普通程序却不是大规模数值计算的最佳选择。首先列表里的元素通常是独立的 Python 对象。对大量数字逐个计算时解释器需要不断读取对象并执行循环额外开销比较明显。其次列表没有直接定义逐元素乘法、按列平均值或矩阵乘法等数值语义。二维列表也只是“列表中再放列表”行长度甚至可以不一致。NumPyNumerical Python数值 Python通过ndarray提供多维数组。数组中的元素通常使用统一的数据类型并以更紧凑、规则的方式存储。许多数值运算在底层实现避免我们在 Python 层手写大量循环。例如给 10000 个数同时加 1NumPy 可以直接写成arr 1。这种一次描述整组计算的写法叫向量化计算Vectorized Computation。它不仅代码更短也更接近“对整列特征进行处理”的机器学习思路。不过NumPy 不是让任何代码自动变快的魔法。数组类型、形状和内存布局仍会影响结果与性能。入门阶段最重要的是养成两个习惯计算前先看shape计算后检查结果形状。NumPy 是什么NumPy 是 Python 科学计算生态中的基础库核心对象是ndarray名称可以理解为“N 维数组”。这里的“维”不是数据有几个元素而是需要几个索引才能定位一个元素。一维数组像一排数字例如三名学生的分数二维数组像一张表有行和列三维及更高维数组可以表示多层数据例如多张彩色图片。在机器学习中二维特征数组最常见。通常每一行代表一个样本每一列代表一个特征。如果收集 100 名学生的学习时间、练习次数和睡眠时间就可以得到形状为(100, 3)的数组100 是样本数量3 是每个样本的特征数量。许多机器学习库把这个特征矩阵记为大写X把希望预测的目标记为y。看到错误信息里出现(100, 3)或(3, 1)时不必害怕它们只是在说明数据有多少行、多少列。创建第一个 NumPy 数组通常先导入 NumPy并使用约定俗成的别名npimport numpy as np一维数组可以这样创建scores np.array([1, 2, 3]) print(scores)二维数组需要在外层列表中放入多行数据matrix np.array([ [1, 2], [3, 4], ])np.array()会根据输入推断数据类型和形状。上面的scores是一维数组matrix是两行两列的二维数组。可以用四个属性观察它们print(matrix.shape) # (2, 2) print(matrix.ndim) # 2 print(matrix.dtype) # 具体结果取决于平台和输入 print(matrix.size) # 4这些属性不执行复杂计算却是排查机器学习数据问题时最先应该看的信息。数组的基本属性shape数组的形状shape返回一个元组依次记录每个轴的长度。形状(100, 5)表示 100 行、5 列。在常见的特征矩阵中它通常对应“100 个样本、每个样本 5 个特征”。顺序不能交换。(100, 5)与(5, 100)包含相同数量的元素但前者通常表示 100 个样本后者更像把特征放到了行上。模型期待前一种形式时传入后一种就会报错或产生错误含义。ndim数组维度ndim表示数组有几个轴。一维数组的ndim是 1二维矩阵是 2。需要注意np.array([1, 2, 3])的形状是(3,)不是(3, 1)。前者是一维数组后者是三行一列的二维数组。dtype元素的数据类型dtype表示数组中元素的统一类型例如int64或float64。机器学习计算经常使用浮点数。整数数组参与除法时通常会产生浮点结果但把浮点结果写回整数数组可能截断小数或报错因此修改数据前要留意类型。size元素总数size是所有轴长度的乘积。形状为(100, 3)的数组共有 300 个元素。它与len(arr)不同len()只返回第一轴长度也就是这里的 100。NumPy 中的索引和切片索引用来获取某个位置的元素。对一维数组arr[0]表示第一个元素Python 从 0 开始计数。arr np.array([10, 20, 30]) print(arr[0]) # 10二维数组可以同时写行索引和列索引data np.array([ [2.0, 10], [3.5, 18], [5.0, 25], ]) print(data[0, 1]) # 第 1 行、第 2 列切片用于选择一段数据。冒号:表示该轴上的全部元素first_feature data[:, 0] first_two_students data[:2, :]data[:, 0]选择所有行的第 1 列。在机器学习代码中常见的X[:, 0]也是这个意思取出所有样本的第一项特征。X[:, 1:3]则保留第 2 列到第 3 列右边界 3 不包含在结果中。切片通常返回原数组的视图而不是完全独立的副本。修改切片可能同时改变原数组。需要独立数据时可以显式调用.copy()。常用数组操作创建特殊数组np.zeros()可以创建全 0 数组常用于初始化结果np.ones()创建全 1 数组np.arange()按给定步长生成连续数值。zeros np.zeros((2, 3)) ones np.ones((2, 3)) numbers np.arange(0, 10, 2)传给zeros和ones的(2, 3)是目标形状。np.arange(0, 10, 2)从 0 开始、到 10 之前结束步长为 2结果是[0, 2, 4, 6, 8]。数学计算NumPy 的算术运算通常逐元素进行values np.array([2.0, 4.0, 6.0]) print(values 1) # 每个元素加 1 print(values * 2) # 每个元素乘 2 print(values.mean()) # 平均值 print(values.max()) # 最大值 print(values.min()) # 最小值对二维数组使用mean(axis0)可以分别计算每一列的平均值mean(axis1)则计算每一行的平均值。机器学习预处理经常需要按列统计因为一列通常对应同一种特征。这些写法并不是“永远禁止for循环”。当每个样本需要复杂条件逻辑时循环仍然有价值。但能用清晰数组运算表达的批量数值计算优先向量化通常更简洁也能利用 NumPy 的底层实现。怎样理解axisaxis是初学 NumPy 时很容易混淆的参数。可以把二维数组想成一张表axis0表示沿着行的方向把多行压缩掉最后每一列留下一个统计值axis1表示沿着列的方向把多列压缩掉最后每一行留下一个统计值。students np.array([ [2.0, 10, 7.0], [4.0, 20, 8.0], ]) column_means students.mean(axis0) row_means students.mean(axis1)column_means的形状是(3,)分别对应三项特征的平均值row_means的形状是(2,)分别对应两个样本各自三列数字的平均值。后者虽然可以计算但“学习小时、练习次数和睡眠小时”的单位不同直接求行平均值通常没有实际意义。这也提醒我们NumPy 只负责按照指令计算不会判断统计方式是否符合业务含义。如果暂时分不清轴先用一个两行三列的小数组实验再观察输入和输出的shape。不要只背“0 是列、1 是行”这样的口诀因为在三维数组中会更容易混乱。更可靠的理解是指定哪个轴就压缩哪个轴没有被压缩的轴决定结果中还剩多少个数。矩阵运算与机器学习机器学习模型经常要把每个样本的多个特征按不同权重组合起来。假设特征矩阵X有 100 行、3 列权重矩阵W有 3 行、1 列那么result X W运算符表示矩阵乘法。结果形状是(100, 1)每个样本得到一个数。直观上每一行的三个特征分别乘以三个权重再把结果相加。线性模型常写成y XW b其中X是输入特征W是权重b是偏置y是输出。这里不需要展开数学推导先记住形状关系即可X的列数必须等于W的行数否则矩阵乘法无法进行。真实训练会根据数据不断调整权重而不是手工指定。本文实践中的权重只用于演示X W如何把三列特征转换为一列结果不能解释为真实的成绩规律。第一个 NumPy 实践案例我们用固定随机种子构造 100 个模拟学生样本每个样本有三项特征学习时间、练习次数和睡眠时间。代码先用np.column_stack()把三组一维数据按列合并为X再沿axis0计算各列的平均值、最高值和最低值。随后创建形状为(3, 1)的权重矩阵执行XW b得到(100, 1)的模拟预测结果。完整可运行代码将下面代码保存为03_numpy_demo.pyNumPy 入门示例构造学生特征矩阵并完成简单矩阵计算。 from __future__ import annotations import sys def load_numpy(): 导入 NumPy缺少或损坏时给出清晰的错误信息。 try: import numpy as np except ModuleNotFoundError as exc: raise RuntimeError( 当前 Python 环境缺少 NumPy请先确认解释器和 Conda 环境。 ) from exc except Exception as exc: raise RuntimeError(fNumPy 已被找到但导入失败{exc}) from exc return np def main() - None: 生成 100 个模拟样本统计特征并执行 X W。 if sys.version_info (3, 8): raise RuntimeError(本示例需要 Python 3.8 或更高版本。) np load_numpy() rng np.random.default_rng(42) # 三列分别表示学习时间、练习次数和睡眠时间。 study_hours rng.uniform(1.0, 10.0, size100).round(1) practice_counts rng.integers(0, 51, size100) sleep_hours np.clip( rng.normal(loc7.0, scale0.8, size100), 4.5, 9.5 ).round(1) x np.column_stack((study_hours, practice_counts, sleep_hours)) if x.shape ! (100, 3) or not np.isfinite(x).all(): raise ValueError(特征矩阵必须是 100 行 3 列并且不能包含无效数值。) feature_names (学习时间小时, 练习次数次, 睡眠时间小时) means x.mean(axis0) maximums x.max(axis0) minimums x.min(axis0) # 这里的权重只用于演示矩阵乘法不是从真实成绩数据中训练得到的。 weights np.array([[5.0], [0.4], [1.5]], dtypefloat) bias 5.0 predicted_scores x weights bias if predicted_scores.shape ! (100, 1): raise ValueError(矩阵计算结果的形状不是预期的 (100, 1)。) print(fNumPy: {np.__version__}) print(f特征矩阵 shape: {x.shape}) print(f特征矩阵 ndim: {x.ndim}) print(f特征矩阵 dtype: {x.dtype}) print(f特征矩阵 size: {x.size}) print(\n三列特征的统计结果) for name, mean, maximum, minimum in zip( feature_names, means, maximums, minimums ): print( f{name}: 平均值{mean:.2f}, 最高值{maximum:.2f}, f最低值{minimum:.2f} ) print(f\n权重矩阵 shape: {weights.shape}) print(f预测结果 shape: {predicted_scores.shape}) print( 前 5 个预测结果: np.array2string(predicted_scores[:5, 0], precision2, separator, ) ) if __name__ __main__: try: main() except (RuntimeError, ValueError) as exc: print(f程序无法继续{exc}, filesys.stderr) sys.exit(1)在项目根目录运行python 03_numpy_demo.py本示例已在 Condabase环境中实际运行使用 Python 3.11.4 和 NumPy 1.24.3程序正常结束。固定种子 42 得到的输出如下NumPy: 1.24.3 特征矩阵 shape: (100, 3) 特征矩阵 ndim: 2 特征矩阵 dtype: float64 特征矩阵 size: 300 三列特征的统计结果 学习时间小时: 平均值5.38, 最高值9.80, 最低值1.10 练习次数次: 平均值23.97, 最高值50.00, 最低值0.00 睡眠时间小时: 平均值6.96, 最高值9.00, 最低值5.30 权重矩阵 shape: (3, 1) 预测结果 shape: (100, 1) 前 5 个预测结果: [64.05, 58.55, 68.85, 64.8 , 31.75]三列数据合并后都变为float64因为同一个 NumPy 数组通常使用统一类型练习次数虽然由整数生成进入特征矩阵后也被转换成了浮点数。size为 300正好等于 100 个样本乘以 3 个特征。NumPy 常见错误1.ValueError: operands could not be broadcast together广播Broadcasting是 NumPy 让不同形状数组参与逐元素运算的规则。例如形状(100, 3)的矩阵可以与形状(3,)的数组相加因为后者能对应到三列。但(100, 3)与(2,)无法按规则对齐便会出现广播错误。解决时不要盲目reshape先打印双方shape明确希望按行还是按列计算。2. 矩阵乘法的shape不匹配A B要求A的列数等于B的行数。(100, 3) (3, 1)可以得到(100, 1)而(100, 3) (2, 1)无法计算。错误信息里的维度数字通常已经指出冲突位置。3. 整数和浮点类型问题整数数组不能无损保存小数。如果标准化后的结果需要写回原整数数组可能发生截断或类型转换错误。可以在创建时写dtypefloat或使用arr.astype(float)得到浮点副本。转换前还要考虑内存占用和是否真的需要复制。4. 一维数组和二维数组混淆形状(3,)、(1, 3)和(3, 1)含义不同。机器学习模型往往要求二维输入即使只有一个特征也常写成(样本数, 1)。可以用reshape(-1, 1)把一维特征转换为一列-1表示让 NumPy 根据元素总数自动推断行数。NumPy 与后续机器学习库的关系NumPy 负责底层数组和数值计算是许多 Python 数据工具的共同基础。Pandas 在它之上提供带行列标签的表格结构适合读取、清洗和分析数据scikit-learn 提供数据预处理、模型训练和评估接口常接收 NumPy 数组作为输入PyTorch 使用“张量”进行深度学习计算张量的形状、索引和广播思想与 NumPy 很接近并增加了 GPU 与自动求导能力。学习 NumPy 的目的不是记住所有函数而是能读懂数据形状、选择需要的行列并判断一次运算的输入与输出。掌握这些内容后后续遇到 Pandas、scikit-learn 和 PyTorch 时会发现许多概念是相通的。总结本文认识了 NumPy 的核心对象ndarray理解了shape、ndim、dtype和size并练习了索引、切片、按轴统计和矩阵乘法。实践案例把 100 个样本、3 个特征组织成(100, 3)的矩阵再通过X W b得到一列模拟结果。后续处理任何机器学习数据时都可以先问三个问题每一行代表什么每一列代表什么当前数组的shape是否符合计算要求。这比死记函数名称更有用也是继续学习 Pandas、机器学习算法和深度学习的重要基础。下一篇预告下一篇是《Pandas 入门机器学习中的数据读取与清洗》。我们会学习如何读取表格数据、处理缺失值、筛选行列并把清洗后的数据交给后续模型。
返回列表