ML · 章节 1
机器学习 1 — Python 与数据初探
本章为整门课程奠定基础。在正式讨论各类学习算法之前,我们必须先掌握一组实用工具:在 Notebook 环境中编写 Python 代码,加载真实的数据集,提取若干基本统计量,并以多种方式将其可视化。
我们首先介绍工作环境(Kaggle 与 Jupyter Notebook),随后回顾后续章节所必需的 Python 语法。接着进入 NumPy 学习向量化数值计算,进入 pandas 学习表格型数据的操作,再依次介绍 Seaborn 与 Plotly Express 这两套互补的可视化工具。本章最后引入 Python 的面向对象编程,为后续封装学习模型做好准备。
我们的目标并不是要在一节课之内把读者训练成熟练的 Python 程序员,而是为大家建立一个共同的基础,使后面的章节可以专注于机器学习本身,而不必反复回到语法层面的细枝末节。
工作环境:Kaggle 与 Jupyter
整门课程的代码都在 Kaggle 平台上运行。这是一个面向数据科学与机器学习的在线平台,在教学上有若干显著优势:无需任何本地安装,Python 环境统一(NumPy、pandas、scikit-learn、Plotly 等常用库已预装),数据集通过标准路径访问,所有同学都在严格一致的环境中运行代码。这就保证了在你笔记本上能跑通的代码,在邻座同学那里也一定能跑通。
在 Kaggle Notebook 中,Python 代码运行在远端服务器上。挂载到 Notebook 中的数据集只读地暴露在 /kaggle/input/数据集名/ 之下,你自己生成的文件则写入 /kaggle/working/。
单元格、执行与 Markdown
Jupyter Notebook 是一种交互式文档,把解释性文字、数学公式、可执行代码与可视化结果融合在一起。它由若干单元格(cells)组成,主要分两类:代码单元格包含可执行的 Python 代码,最后一行表达式的结果会自动显示;Markdown 单元格则承载文字、标题、公式与图片。
执行单元格时使用以下键盘快捷键:
- Shift + Enter:执行当前单元格并移到下一格
- Ctrl + Enter:执行当前单元格但不移动
- Alt + Enter:执行当前单元格并在下方插入新格
单元格在原则上可以以任意顺序执行,但变量是按实际执行顺序存入解释器内存的。因此实践中最稳妥的做法是从上到下执行整个 Notebook,在做出重要修改之后重新整体执行一次,这是确保所见输出与所写代码一致的唯一方法。
Markdown 单元格支持常见语法(# 表示标题,**粗体**、*斜体*、有序与无序列表),也支持 LaTeX 数学公式。行内公式写作 $ax + b$,独立公式则用 $$ ... $$ 包围,例如:
图片用  插入,路径通常指向一个数据集或外部 URL。
标准导入语句
本章及后续几乎所有 Notebook 都以同一组导入语句开头:
import numpy as np import matplotlib.pyplot as plt import pandas as pd import plotly.express as px
NumPy 负责向量化的数值计算,Matplotlib 是静态绘图的基础设施,pandas 提供表格型数据结构(即 DataFrame),Plotly Express 则是高层的交互式可视化接口。np、pd、px 这些别名是社区约定,请务必遵循,这样写出的代码才便于他人阅读。
Python 必备语法回顾
继续深入之前,我们快速回顾后续会反复用到的 Python 语法。Python 是一种动态类型语言:类型与值绑定,而不是与变量绑定。因此可以这样写:
a = 3 # int b = 2.5 # float c = "Python" # str d = True # bool
最常见的内置类型是 int、float、str 和 bool。列表(list)用于存放有序的元素集合:
values = [1, 2, 3, 4, 5] values[0] # 第一个元素 values[-1] # 最后一个元素 values[1:4] # 子列表(切片)
控制结构方面,for 循环用于遍历集合,range 生成整数序列,while 循环则在条件成立时持续执行。这些都是再熟悉不过的工具:
for v in values: print(v) for i in range(0, 10, 2): print(i) count = 0 while count < 5: print(count) count += 1
while 循环必须在循环体内更新条件变量,否则将陷入无限循环。条件分支用 if / elif / else 表达,布尔运算符 and、or、not 用于组合条件:
if x % 2 == 0 and x > 10: print("x 是大于 10 的偶数")
函数用于封装并复用一段逻辑,通过 def 关键字定义,通过 return 返回结果:
def square(x): return x ** 2
最后两点同样不可忽视:模块导入通常采用约定的别名(如 import numpy as np),缩进在 Python 中具有语法意义,缩进错误会直接导致运行时错误。
Python 的设计哲学是"显式优于隐式":缩进既是排版,也是语法。把代码格式化整齐既是美感,也是正确性的一部分。
用 NumPy 处理向量与矩阵
NumPy 是 Python 数值计算生态的基石。它高效地操作向量(一维数组)与矩阵(二维数组),并对它们提供向量化的数学运算。所谓"向量化",意思是对整个数组做运算时不需要显式写循环,语义直观,执行速度也比纯 Python 循环快几个数量级。
向量与矩阵分别由 np.array 构造:
x = np.array([1, 2, 3, 4]) X = np.array([ [1, 2], [3, 4], [5, 6] ])
每个数组都有一个 shape 属性,描述其形状。一维向量的形状形如 (n,),二维矩阵的形状则是 (n_行, n_列)。在动手做任何运算之前先看一眼 shape,这是避免维度错误的最佳习惯。
x.shape # (4,) X.shape # (3, 2)
reshape 方法可以改变数组的形状而不改变其元素,前提是元素总数保持不变:
x = np.arange(1, 11) # 1..10 X = x.reshape(5, 2) # 5 行 2 列
这个操作在机器学习里非常常见——很多 scikit-learn 接口要求二维输入,而你手头可能只有一维向量,这时 reshape 就是常规的"形状适配器"。
求和、均值与轴的概念
NumPy 提供了一组聚合函数,如 np.sum、np.mean、np.std、np.min、np.max。在二维矩阵上,这些函数可以通过 axis 参数指定聚合方向:
np.sum(X) # 所有元素之和 np.sum(X, axis=0) # 每一列分别求和(沿行折叠) np.sum(X, axis=1) # 每一行分别求和(沿列折叠)
一个易记的口诀是:
axis=0折叠"行"维度,得到逐列结果;axis=1折叠"列"维度,得到逐行结果。
点积与矩阵-向量乘积
两个向量的点积是机器学习中最基础的运算之一:
u = np.array([1, 2, 3]) v = np.array([4, 5, 6]) np.dot(u, v)
把它推广到矩阵-向量乘积,就得到线性模型的核心表达式:
X = np.array([[1, 2], [3, 4], [5, 6]]) w = np.array([0.5, 1.0]) X @ w
它对应的数学表达式是
其中 X 的每一行是一个样本的特征向量,w 是权重向量,X @ w 给出每个样本的预测值。
* 与 @ 的关键区别
初学者最常混淆的就是这两个运算符:
X * w # 逐元素相乘(广播) X @ w # 矩阵-向量乘积
* 是逐元素运算,要求两个数组形状兼容(必要时按广播规则扩展);@ 是矩阵乘积,严格遵守线性代数中的形状约束。课程中只要写到线性模型,就一定是 @,别写错了。
向量化运算同样适用于标量与数组之间:
x + 1 x * 2 x ** 2
这些表达式作用于整个数组,无须显式循环。一旦习惯了这种写法,就能告别绝大多数 for 循环。
用 pandas 处理 DataFrame
如果说 NumPy 把 Python 变成了一台数值计算机,那么 pandas 则把它变成了一张电子表格。pandas 围绕 DataFrame(带行列标签的二维表)与 Series(带索引的一维列)展开,几乎所有的数据探索都从这两种结构入手。
用 read_csv 读入数据
pandas.read_csv 是把 CSV 文件转成 DataFrame 的标准入口:
df = pd.read_csv('/kaggle/input/mini-datasets/co2_mini.csv')
最重要的几个可选参数是 sep(列分隔符,默认为逗号,但有些文件用 ; 或制表符),以及 encoding(文件编码,常用 'utf-8')。Kaggle 上数据集添加进 Notebook 后,会以子目录的形式出现在 /kaggle/input/ 下,完整路径可在右侧 Data 面板中将鼠标移到文件名上、点击复制图标得到。
课程中我们使用的
co2_mini数据集来自实测的车辆 CO₂ 排放数据,只保留了使用高品质汽油(类型 "Z")的车辆。它包含两列:consumption表示百公里油耗(L/100km),co2表示碳排放量(g/km)。
第一眼:head 与 shape
head() 显示 DataFrame 的前若干行(默认 5),是检查列名、值的格式与类型一致性最直接的方法:
df.head() df.head(10)
如果 head() 显示出 NaN 或杂乱的列,通常意味着 read_csv 的 sep、decimal、encoding 或 na_values 参数没设对。
shape 返回 (n_行, n_列) 二元组:
df.shape # 例如 (100, 2) df.shape[0] # 行数,即观测数 df.shape[1] # 列数,即变量数
columns 给出所有列名;info() 则一次性提供行数、每列非空值的数量、数据类型与内存占用,可以快速识别缺失值和需要类型转换的列。
选择列
pandas 中常用的列选择有两种写法:
df['co2'] # Series(一维) df[['co2']] # DataFrame(二维,只含一列) df[['consumption', 'co2']] # DataFrame(多列)
重要区分
df['co2']返回的是一维Series,而df[['co2']]返回的是二维DataFrame。在调用某些 scikit-learn 接口时(它们要求二维输入),这一差异决定了代码能否运行。
也有点号写法 df.co2,但仅当列名不含空格、特殊字符且不与 pandas 自身属性冲突时才有效,因此略显脆弱,推荐用方括号写法。
简单统计:pandas 与 NumPy
最常用的几个统计量在 pandas 与 NumPy 中都可以方便地计算,二者本质上等价但用法略有差别:
df["co2"].sum() # pandas:Series 方法 np.sum(df["co2"]) # NumPy:作用于 Series 同样可行 df["co2"].mean() df["co2"].std() df["co2"].min() df["co2"].max()
pandas 风格的好处是:它直接整合在 DataFrame 中,自动处理缺失值;NumPy 风格则更通用,任何"类数组"对象都能传入。
用 describe 一览统计摘要
describe() 一次性返回所有数值列的统计摘要,是探索性分析的利器:
df.describe()
对每一列,它给出 count(非空值数)、mean(算术平均)、std(标准差,衡量围绕均值的离散度)、min(最小值)、四分位数 25%、50%、75% 与 max(最大值)。
四分位数把有序数据分为四等份:
- (25%):有 25% 的值小于或等于它
- (50%):中位数,把数据切成两半
- (75%):有 75% 的值小于或等于它
四分位距定义为
它衡量数据的中部离散度,对极端值不敏感,因此比标准差更稳健。借助 IQR,可以约定异常值(outliers)为满足
的观测。这是后面箱线图绘制时使用的判据。
用 Seaborn 进行分布可视化
Seaborn 是构建在 Matplotlib 之上的统计可视化库。它提供更具表达力的图形,API 直接面向 DataFrame,非常适合探索性分析。
箱线图
箱线图(boxplot)用五个描述性统计量浓缩呈现一个数值变量的分布:最小值、、中位数、、最大值。盒子从 延伸到 ,中间一条线代表中位数,须线延伸到非异常值的范围,异常值则单独以点标出。
import seaborn as sns plt.figure(figsize=(6, 4)) sns.boxplot(y=df["co2"]) plt.title("Boxplot of the CO2 variable") plt.show()
借助一张箱线图就能快速判断数据的离散度、对称性以及极端值的存在。需要强调的是,异常值不应被自动剔除,它们可能是测量错误,也可能是合法但少见的现象,任何清理决定都应基于具体的领域分析。
直方图与核密度估计
直方图(histogram)把观测分到一系列区间(bin)里,以每个区间的频次作为柱高:
plt.figure(figsize=(6, 4)) sns.histplot(data=df, x="co2", bins=100) plt.title("Distribution of the CO2 variable") plt.show()
bins 参数控制区间数:太少会掩盖分布的细节,太多会放大噪声。
核密度估计(KDE)给出一条平滑的连续密度曲线,曲线下面积为 1:
plt.figure(figsize=(6, 4)) sns.kdeplot(data=df, x="co2", fill=True) plt.title("Density estimation (KDE)") plt.show()
两者也可以叠加显示,把柱状的频次与平滑的趋势放在同一张图上:
plt.figure(figsize=(6, 4)) sns.histplot(data=df, x="co2", bins=100, kde=True) plt.title("Histogram with density estimation") plt.show()
小提琴图
小提琴图(violin plot)同样依赖密度估计,但把它沿轴对称地画出来,宽度反映密度。设置 inner="box",可在小提琴内部嵌入一个迷你箱线图,把密度曲线与统计摘要合并成一张图:
plt.figure(figsize=(6, 4)) sns.violinplot(y=df["co2"], inner="box") plt.title("Violin plot of the CO2 variable") plt.show()
直方图、KDE 与小提琴图三者互补:直方图描述离散区间,KDE 给出连续近似,小提琴图把密度与描述性统计结合起来,共同构成建模前的探索性分析的标准动作。
散点图与变量间关系
到目前为止,我们都在分析单变量分布。散点图(scatter plot)则用于研究两个数值变量之间的关系——这正是引入线性回归的入口:
plt.figure(figsize=(6, 4)) sns.scatterplot(data=df, x="consumption", y="co2") plt.title("Relationship between consumption and CO2") plt.show()
每个点代表一组观测 。从散点图我们可以观察到整体趋势(增长/下降)、关系的形式(线性还是非线性)、点云的离散程度,以及非典型观测的存在。这些直观的判断会直接指导我们后续选择什么样的模型。
Plotly Express 简介:交互式可视化
Plotly Express(惯例别名 px)是一个高层 Plotly 接口,可用一行调用就把 DataFrame 渲染成交互式图形。和 Seaborn / Matplotlib 不同,Plotly 图默认就是交互式的:鼠标悬停可以看精确数值,缩放、平移都是原生支持,生成的图也方便嵌入到仪表盘中。
每次调用 px.* 都返回一个 fig 对象,后续可继续修改:
fig = px.histogram(df, x="co2", nbins=100) fig.show() fig = px.violin(df, y="co2", box=True, points="outliers") fig.show() fig = px.scatter(df, x="consumption", y="co2", trendline="ols") fig.show()
值得注意的是,px.scatter 的 trendline="ols" 参数会在点云上叠加一条最小二乘拟合直线,这正是下一章我们要学的线性回归的图形预览。
px.* 生成的图由若干轨迹(trace)与一个布局(layout)组成。可以通过 update_traces 调整轨迹的视觉属性,通过 update_layout 调整整体布局:
fig = px.histogram(df, x="co2", nbins=50) fig.update_traces( opacity=0.7, marker_line_color="black", marker_line_width=1 ) fig.update_layout(width=600, height=400) fig.show()
这种"先创建、后微调"的工作流是 Plotly 的核心思想:第一步用 px.* 建好默认图,第二步根据需要调整外观。
Python 类与对象
本章最后引入 Python 的面向对象编程(OOP)。从下一章开始,scikit-learn 中的每一个模型都会以"类"的形式出现,因此我们需要先理解几个核心概念:类、属性、方法、实例化与状态。
类是一种自定义的对象类型,把数据(称为属性)与函数(称为方法)打包在一起。看一个最小的例子:
class Calculatrice: def __init__(self): # 实例属性:每个对象都有自己的内存 self.memoire = 10 def add(self, x): # 方法:修改对象的内部状态 self.memoire = self.memoire + x
这里几个要点要逐一理解。class 关键字用于定义新的类型,Calculatrice 是这个类型的名字。__init__ 是构造函数,在新对象创建时自动调用,用来初始化属性。self 是约定俗成的第一个参数,代表当前实例,通过 self 我们才能访问到属于这个对象的属性和方法。self.memoire = 10 给该实例创建了一个属性 memoire,因此每个 Calculatrice 对象都拥有自己独立的"内存"。add 是一个方法,它通过 self.memoire = self.memoire + x 修改对象的内部状态。
实例化与使用
要从类得到一个具体可用的对象,需要做实例化:
c = Calculatrice()
带括号地调用类等价于触发新对象的创建:Python 在内存中开辟空间,接着自动调用 __init__(self),初始化属性,最终把对该对象的引用赋给变量 c。我们可以用同一个类创建任意多个对象,每个对象都有自己独立的状态。
实例化之后,通过点号即可读取属性、调用方法:
c.memoire # 读取属性,返回 10 c.add(20) # 调用方法,把 memoire 改为 30 c.memoire # 再次读取,返回 30
读取属性不会改变对象状态;调用方法则会(本例中)修改属性 memoire。状态的改变是持久的,只要对象还在内存中,它就保留新的值。
总结起来:类描述了对象的结构与行为;__init__ 负责初始化属性;self 指代当前实例;属性存储状态,方法读取或修改这些状态。这套机制后面会反复出现:scikit-learn 的每一个估计器都遵循"先实例化(model = LinearRegression())、再调用方法(model.fit(...),model.predict(...))"的模式。
f-string 提醒
在打印对象状态、生成报告的过程中,我们会大量使用 f-string(格式化字符串字面量)。在字符串前加 f,大括号 {} 中的表达式会被自动求值并插入:
year = 3 capital = 1234.567 print(f"Year {year}: capital = {capital:.2f}")
冒号后面的 .2f 是格式说明符:f 表示浮点数,.2 表示保留两位小数。本章的练习以及后续章节都会频繁用到这种写法,值得提前熟练掌握。
练习
下列练习集中检验本章的核心技能。请独立完成,然后再与同伴比对。
练习 1 — Python 基础回顾(不使用 NumPy)
本练习用于验证后续章节所需的 Python 基本结构是否掌握。
- 创建一个包含数字 1 到 10 的列表。
- 用
for循环遍历该列表,只打印偶数。 - 用
for ... in range(...)循环计算 1 到 10 之间所有整数之和。 - 编写一个函数
mean(values),接收一个数字列表,返回它们的算术平均值。 - 用
while循环按降序打印从 5 到 1 的整数。
练习 2 — NumPy 向量与矩阵运算
本练习用 NumPy 操作向量与矩阵,不使用任何显式循环,为线性回归中将出现的运算做铺垫。
考虑向量
x = np.array([1, 2, 3, 4, 5, 6])
依次完成:
-
用
reshape把x变成形状为(3, 2)的矩阵X。 -
计算:
X所有元素之和;- 每列之和;
- 每行之和。
-
给定系数向量
w = np.array([0.5, 1.0])计算矩阵-向量乘积
X @ w。 -
比较
X * w X @ w两个表达式的结果,并简要解释它们的差异。
练习 3 — 面向对象编程:复利计算器
我们想对按复利方式投资的资本进行建模。资本根据下式逐年演化:
其中 是第 年末的资本, 是年化利率(以小数形式给出,例如 5% 写作 0.05)。
请完成:
- 编写一个类
CompoundInterestCalculator,包含:- 构造函数
__init__(self, capital_initial, taux); - 表示当前资本的属性
capital; - 表示年化利率的属性
taux。
- 构造函数
- 添加方法
next_year(self),通过应用利息更新capital,即修改对象的内部状态。 - 添加方法
simulate(self, n),在n年内模拟资本的演化,并在每年之后打印当前资本(建议使用 f-string,保留两位小数)。 - 实例化一个对象,初始资本为
1000,年利率为5%(即0.05)。 - 模拟该资本在
5年内的演化。
拓展阅读
下列官方文档可作为本章内容的补充和深入学习的入口:
- Python 官方教程:https://docs.python.org/3/tutorial/
- NumPy 用户指南:https://numpy.org/doc/stable/user/index.html
- pandas 入门教程:https://pandas.pydata.org/docs/getting_started/index.html
- Seaborn 教程:https://seaborn.pydata.org/tutorial.html
- Plotly Express 概览:https://plotly.com/python/plotly-express/
- Jupyter Notebook 文档:https://jupyter-notebook.readthedocs.io/en/stable/