🕐 预计用时:3-4 小时 | 🎯 目标:掌握线性回归、逻辑回归的使用方法,理解模型评估指标
Scikit-learn(简称 sklearn)是 Python 最流行的机器学习库,提供了大量现成的算法和工具。如果说昨天我们学了"什么是机器学习",今天就是亲手让机器学起来。
你可以把它想象成一个机器学习超市——货架上摆满了各种算法(线性回归、决策树、SVM...),你只需要把数据"喂"进去,它就能帮你训练出一个模型。
💡 核心思想:Scikit-learn 的所有模型都遵循相同的套路——创建模型 → fit(X, y) 训练 → predict(X) 预测。学会一个,就等于学会了一百个。
# 安装 scikit-learn
pip install scikit-learn
# 通常还需要这些
pip install numpy pandas matplotlib
# 验证安装
python -c "import sklearn; print(sklearn.__version__)"导入常用模块:
# 数据集
from sklearn import datasets
# 模型
from sklearn.linear_model import LinearRegression, LogisticRegression
# 数据拆分
from sklearn.model_selection import train_test_split
# 评估指标
from sklearn.metrics import accuracy_score, mean_squared_error, classification_report
# 预处理
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import numpy as np
import pandas as pd
import matplotlib.pyplot as pltScikit-learn 自带了很多经典数据集,方便我们练习:
# 加载鸢尾花数据集(分类任务经典)
iris = datasets.load_iris()
print(type(iris)) # Bunch(类似字典)
# 查看有哪些内容
print(iris.keys())
# dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names'])
# 特征矩阵(150个样本,4个特征)
print(iris.data.shape) # (150, 4)
# 标签
print(iris.target.shape) # (150,)
print(iris.target_names) # ['setosa' 'versicolor' 'virginica']
# 特征名
print(iris.feature_names)
# ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']用 Pandas 看得更清楚:
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['species'] = iris.target
print(df.head()) sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) species
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0
2 4.7 3.2 1.3 0.2 0
3 4.6 3.1 1.5 0.2 0
4 5.0 3.6 1.4 0.2 0常用数据集一览:
⚠️ 注意:sklearn 1.2+ 版本已移除 datasets.load_boston(),可以用 fetch_openml(name='boston') 替代,或者用我们自己构造的数据。
线性回归是最基础的机器学习算法。它做的事情很简单:找到一条直线(或平面),让数据点尽量靠近它。
生活中的例子:
数学公式:y = w₁x₁ + w₂x₂ + ... + b
其中 w 是权重(斜率),b 是偏置(截距)。
# 生成模拟数据:学习时间 → 考试成绩
np.random.seed(42)
X = np.random.rand(100, 1) * 10 # 学习时间 0~10 小时
y = 2.5 * X.squeeze() + 5 + np.random.randn(100) * 2 # 成绩 = 2.5*时间 + 5 + 噪声
# 拆分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"训练集: {X_train.shape[0]} 个样本")
print(f"测试集: {X_test.shape[0]} 个样本")
# 创建模型并训练
model = LinearRegression()
model.fit(X_train, y_train)
# 查看学到的参数
print(f"权重 w = {model.coef_[0]:.2f}") # 应该接近 2.5
print(f"偏置 b = {model.intercept_:.2f}") # 应该接近 5
# 预测
y_pred = model.predict(X_test)
# 对比前5个
for i in range(5):
print(f"实际: {y_test[i]:.1f} 预测: {y_pred[i]:.1f} 误差: {abs(y_test[i]-y_pred[i]):.1f}")plt.figure(figsize=(10, 6))
plt.scatter(X_train, y_train, alpha=0.5, label='训练数据', color='#07c160')
plt.scatter(X_test, y_test, alpha=0.5, label='测试数据', color='#1890ff')
# 画回归线
X_line = np.linspace(0, 10, 100).reshape(-1, 1)
y_line = model.predict(X_line)
plt.plot(X_line, y_line, 'r-', linewidth=2, label=f'回归线: y={model.coef_[0]:.2f}x+{model.intercept_:.2f}')
plt.xlabel('学习时间 (小时)')
plt.ylabel('考试成绩')
plt.title('线性回归:学习时间 vs 考试成绩')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('linear_regression.png', dpi=150)
plt.show()# 用糖尿病数据集(10个特征)
diabetes = datasets.load_diabetes()
X, y = diabetes.data, diabetes.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = LinearRegression()
model.fit(X_train, y_train)
# 每个特征的权重
for name, coef in zip(diabetes.feature_names, model.coef_):
print(f" {name}: {coef:.2f}")
print(f"\n训练集 R² = {model.score(X_train, y_train):.4f}")
print(f"测试集 R² = {model.score(X_test, y_test):.4f}")📊 R² 分数(决定系数):R² 越接近 1,说明模型解释数据的能力越强。R²=0.6 意味着模型解释了 60% 的数据变化。一般来说:R² > 0.7 算不错,R² > 0.9 算优秀。
虽然名字里有"回归",但逻辑回归其实是一个分类算法。它解决的是"是或否"的问题:
它的工作原理:把线性回归的结果通过 Sigmoid 函数压缩到 0~1 之间,变成概率。
# Sigmoid 函数可视化
def sigmoid(z):
return 1 / (1 + np.exp(-z))
z = np.linspace(-10, 10, 100)
plt.figure(figsize=(8, 4))
plt.plot(z, sigmoid(z), 'b-', linewidth=2)
plt.axhline(y=0.5, color='r', linestyle='--', alpha=0.5, label='决策边界 (0.5)')
plt.xlabel('z')
plt.ylabel('σ(z)')
plt.title('Sigmoid 函数')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()# 用鸢尾花数据集做二分类(只取前两类)
iris = datasets.load_iris()
X = iris.data[:100] # 只取前100个(两类)
y = iris.target[:100] # 0=setosa, 1=versicolor
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 创建逻辑回归模型
model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test) # 概率
# 查看前5个预测结果
for i in range(5):
actual = 'setosa' if y_test[i] == 0 else 'versicolor'
predicted = 'setosa' if y_pred[i] == 0 else 'versicolor'
prob = y_prob[i].max()
print(f"实际: {actual:>10s} 预测: {predicted:>10s} 置信度: {prob:.2%}")# 用全部3类鸢尾花
X = iris.data
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# sklearn 1.5+ 已弃用 multi_class 参数,默认使用 multinomial 策略
model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print(f"准确率: {accuracy_score(y_test, y_pred):.2%}")
print(f"\n分类报告:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))训练完模型,怎么知道它好不好用?需要评估指标来打分。
# 回归评估示例
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
# 假设真实值和预测值
y_true = [100, 200, 300, 400, 500]
y_pred = [110, 190, 310, 380, 520]
mse = mean_squared_error(y_true, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_true, y_pred)
r2 = r2_score(y_true, y_pred)
print(f"MSE = {mse:.2f}")
print(f"RMSE = {rmse:.2f}")
print(f"MAE = {mae:.2f}")
print(f"R² = {r2:.4f}")分类任务的评估更有趣。先理解四个基础概念:
| 实际为正 | ||
| 实际为负 |
由此衍生出三个重要指标:
🎯 精确率 vs 召回率:
- 精确率:宁可漏掉,也不误报(垃圾邮件过滤——误杀正常邮件很糟糕)
- 召回率:宁可误报,也不漏掉(癌症筛查——漏掉一个患者很危险)
- 两者通常是跷跷板关系,需要根据场景取舍。
from sklearn.metrics import confusion_matrix, classification_report
# 混淆矩阵
cm = confusion_matrix(y_test, y_pred)
print("混淆矩阵:")
print(cm)
# 完整报告
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=['类别A', '类别B']))F1 分数是精确率和召回率的调和平均,兼顾两者:
from sklearn.metrics import f1_score
f1 = f1_score(y_test, y_pred, average='weighted')
print(f"F1 分数: {f1:.4f}")F1 的取值范围 0~1,越高越好。一般标准:
真实世界的数据"脏乱差",需要先清洗和处理:
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# 标准化:均值=0,标准差=1(最常用)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_train)
# 归一化:缩放到 [0, 1]
scaler = MinMaxScaler()
X_normalized = scaler.fit_transform(X_train)
print("原始数据前3行:")
print(X_train[:3])
print("\n标准化后:")
print(X_scaled[:3])⚠️ 为什么要做特征缩放?如果一个特征范围是 0~1,另一个是 0~10000,大的那个会主导模型训练。缩放后,所有特征"站在同一起跑线"。
注意:要用 fit_transform() 处理训练集,用 transform() 处理测试集,避免数据泄露。
from sklearn.impute import SimpleImputer
# 用均值填充缺失值
imputer = SimpleImputer(strategy='mean')
X_filled = imputer.fit_transform(X)
# 其他策略: 'median'(中位数), 'most_frequent'(众数), 'constant'(常数)from sklearn.preprocessing import LabelEncoder, OneHotEncoder
# 标签编码:分类 → 数字
le = LabelEncoder()
y_encoded = le.fit_transform(['猫', '狗', '猫', '鸟'])
print(y_encoded) # [1 0 1 2] (按 Unicode 排序:狗=0, 猫=1, 鸟=2)
# 独热编码:分类 → 二进制向量
# sklearn 1.2+ 用 sparse_output=False;旧版本用 sparse=False
ohe = OneHotEncoder(sparse_output=False)
categories = [['男'], ['女'], ['男']]
print(ohe.fit_transform(categories))
# [[0. 1.] ← 男
# [1. 0.] ← 女
# [0. 1.]] ← 男把今天学到的所有知识串起来,完成一个完整的机器学习项目:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, r2_score
import matplotlib.pyplot as plt
# ========== 1. 生成模拟房价数据 ==========
np.random.seed(42)
n_samples = 500
# 特征:面积、卧室数、楼层、年龄
area = np.random.uniform(50, 200, n_samples) # 面积(平方米)
rooms = np.random.randint(1, 6, n_samples) # 卧室数
floor_num = np.random.randint(1, 30, n_samples) # 楼层
age = np.random.uniform(0, 30, n_samples) # 房龄(年)
# 目标:房价(万元),假设公式
price = (area * 1.5 + rooms * 20 - age * 0.5 + floor_num * 0.3
+ np.random.randn(n_samples) * 15)
# 组装 DataFrame
df = pd.DataFrame({
'面积': area,
'卧室数': rooms,
'楼层': floor_num,
'房龄': age,
'房价': price
})
print("数据概况:")
print(df.describe().round(2))
# ========== 2. 数据准备 ==========
X = df[['面积', '卧室数', '楼层', '房龄']]
y = df['房价']
# 拆分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# ========== 3. 训练模型 ==========
model = LinearRegression()
model.fit(X_train_scaled, y_train)
# 查看权重
print("\n特征权重:")
for name, coef in zip(X.columns, model.coef_):
print(f" {name}: {coef:+.2f}")
print(f" 偏置: {model.intercept_:.2f}")
# ========== 4. 评估模型 ==========
y_pred = model.predict(X_test_scaled)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
print(f"\n评估结果:")
print(f" RMSE = {rmse:.2f} 万元")
print(f" R² = {r2:.4f}")
# ========== 5. 可视化 ==========
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 真实 vs 预测
axes[0].scatter(y_test, y_pred, alpha=0.5, color='#07c160')
axes[0].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--')
axes[0].set_xlabel('真实房价')
axes[0].set_ylabel('预测房价')
axes[0].set_title(f'真实 vs 预测 (R²={r2:.4f})')
# 残差分布
residuals = y_test - y_pred
axes[1].hist(residuals, bins=30, color='#1890ff', alpha=0.7)
axes[1].axvline(x=0, color='r', linestyle='--')
axes[1].set_xlabel('残差')
axes[1].set_ylabel('频次')
axes[1].set_title('残差分布')
plt.tight_layout()
plt.savefig('house_price_prediction.png', dpi=150)
plt.show()
# ========== 6. 实际预测 ==========
print("\n新数据预测:")
new_houses = pd.DataFrame({
'面积': [90, 120, 150],
'卧室数': [2, 3, 4],
'楼层': [15, 8, 22],
'房龄': [5, 10, 2]
})
new_scaled = scaler.transform(new_houses)
predictions = model.predict(new_scaled)
for i, pred in enumerate(predictions):
print(f" 房子{i+1}: {new_houses.iloc[i]['面积']}㎡ {int(new_houses.iloc[i]['卧室数'])}室 → 预测房价: {pred:.1f}万元")用逻辑回归对鸢尾花数据集做三分类,打印分类报告。
用线性回归预测糖尿病进展,计算 R² 和 RMSE。
分别用标准化和不标准化训练逻辑回归模型,对比准确率变化。
🎯 一句话总结:Scikit-learn 的核心套路就三步——准备数据 → fit 训练 → predict 预测。掌握这个流程,你就入门机器学习了!
🔮 明天预告:Day87 我们将学习更强大的分类算法(决策树、随机森林)和聚类算法(K-Means),以及交叉验证这个"考试技巧"。敬请期待!