1. 从零开始:你的第一个机器学习项目环境

很多朋友问我,想学机器学习,第一步该干嘛?我的经验是,别急着啃那些复杂的数学公式,先把“厨房”搭好。就像你要做一顿大餐,得先有个趁手的灶台、锋利的刀具和干净的案板。对于机器学习来说,这个“厨房”就是你的开发环境。我见过太多新手,一上来就被各种库的版本冲突、环境配置搞得焦头烂额,热情瞬间被浇灭一半。所以,咱们今天不聊高深理论,就踏踏实实地,手把手把环境搭起来,让你能立刻跑起代码,看到成果。

1.1 Python版本与发行版:别在起跑线就选错

选Python版本,是不是觉得“最新就是最好”?我刚开始也这么想,结果踩了坑。当时Python 3.10刚发布,我兴冲冲地装上,结果发现好几个我需要的科学计算库还没适配,报错信息看得我一头雾水,折腾了半天又退回3.8。所以,给新手的建议是:求稳不求新。目前业界最广泛支持、文档最全、社区问题解答最多的版本是 Python 3.8 到 3.10 这个区间。对于机器学习来说,这个范围内的版本,所有主流库(像Scikit-learn、TensorFlow、PyTorch)都有非常稳定的支持。

那具体怎么安装Python呢?我不推荐你直接去Python官网下载安装,因为那样你需要自己手动管理各种包,很容易陷入“依赖地狱”。我强烈推荐你使用 Anaconda。你可以把它理解为一个“科学计算全家桶”。它不仅仅是一个Python解释器,更重要的是它自带了一个强大的**环境管理工具(conda)**和一大堆预装好的数据科学库(NumPy, Pandas, Matplotlib, Scikit-learn等)。这意味着你安装完Anaconda,80%的基础工具就已经就位了,省去了大量 pip install 和解决兼容性问题的时间。

安装Anaconda非常简单,去它的官网下载对应你操作系统(Windows/macOS/Linux)的安装包,一路点击“下一步”即可。安装时记得勾选“Add Anaconda to my PATH environment variable”(将Anaconda添加到系统路径),这样以后在命令行里使用会更方便。

1.2 核心武器库:NumPy, Pandas, Matplotlib 初体验

环境装好了,我们来看看三个最核心的“武器”。它们是你处理数据、进行计算的基石。

NumPy 是地基。它提供了Python中缺少的高性能多维数组对象,以及处理这些数组的工具。几乎所有其他科学计算库都建立在NumPy之上。想象一下,你要处理一个班级所有学生的各科成绩,用Python原生的列表来算平均分会很慢,而用NumPy的数组,一行代码,速度飞快。

import numpy as np
# 创建一个数组
scores = np.array([85, 90, 78, 92, 88])
# 计算平均分
average_score = np.mean(scores)
print(f"平均分是:{average_score}")
# 执行向量化运算(这才是NumPy的威力)
bonus_scores = scores + 5  # 给每人加5分
print(f"加分后的成绩:{bonus_scores}")

Pandas 是瑞士军刀。它建立在NumPy之上,提供了两种核心数据结构:Series(一维)和 DataFrame(二维表格)。DataFrame简直就是为处理Excel、CSV这类表格数据而生的,你可以像操作Excel一样筛选、分组、聚合数据,但功能强大无数倍。

import pandas as pd
# 创建一个简单的学生信息DataFrame
data = {
    '姓名': ['张三', '李四', '王五'],
    '数学': [85, 92, 78],
    '英语': [88, 90, 85],
    '班级': ['一班', '二班', '一班']
}
df = pd.DataFrame(data)
print(df)
# 轻松筛选出一班的学生
class_one = df[df['班级'] == '一班']
print("\n一班的学生:")
print(class_one)
# 计算每门课的平均分
average_by_subject = df[['数学', '英语']].mean()
print(f"\n各科平均分:\n{average_by_subject}")

Matplotlib 是画笔。数据光看数字是枯燥的,一图胜千言。Matplotlib能帮你把数据变成各种图表,折线图、散点图、柱状图、直方图等等。它能让你直观地看到数据的分布、趋势和关系,是探索性数据分析的必备工具。

import matplotlib.pyplot as plt
# 使用上面的DataFrame数据
plt.figure(figsize=(8, 5))
# 绘制数学成绩的柱状图
plt.bar(df['姓名'], df['数学'], color='skyblue', label='数学')
# 绘制英语成绩的柱状图,并排显示
plt.bar(df['姓名'], df['英语'], color='lightcoral', label='英语', alpha=0.7)
plt.xlabel('学生姓名')
plt.ylabel('分数')
plt.title('学生成绩对比')
plt.legend() # 显示图例
plt.grid(axis='y', linestyle='--', alpha=0.7)
plt.show()

运行这段代码,你就能立刻看到一个清晰的成绩对比图。这种即时反馈,对保持学习兴趣至关重要。

1.3 开发利器:Jupyter Notebook 与 VS Code 的黄金组合

工具选对了,效率翻倍。在机器学习学习和项目初期,我首推 Jupyter Notebook。它是什么?它是一个基于网页的交互式编程环境。你可以把代码、运行结果、公式、图片和文字说明全部整合在一个文档里。它的最大优点是“分段执行”,你可以写一小段代码,马上看到结果,然后基于结果再写下一段。这种探索式的学习方式,特别适合数据分析和机器学习模型调试。

Anaconda安装好后,你可以在开始菜单找到“Jupyter Notebook”点击启动,或者在Anaconda Prompt命令行里输入 jupyter notebook。浏览器会自动打开一个本地页面,那就是你的工作台了。

但是,当项目逐渐变大,需要更专业的代码管理、版本控制和调试功能时,Jupyter Notebook就显得有些力不从心了。这时,我建议你切换到 Visual Studio Code (VS Code)。它是一款轻量级但功能极其强大的免费代码编辑器。通过安装Python扩展和Jupyter扩展,你可以在VS Code里获得Jupyter Notebook的所有交互功能,同时还能享受智能代码补全、语法高亮、集成终端、Git版本控制等专业IDE的特性。我的工作流通常是:在Jupyter里做快速原型验证和数据分析,在VS Code里进行最终的代码重构、模块化和项目部署。

提示:无论用哪个工具,都强烈建议你尽早学习使用 虚拟环境(conda create -n myenv python=3.9)。它为每个项目创建独立的Python环境,避免项目间的库版本冲突,是专业开发的必备习惯。

2. 数据炼金术:从原始数据到模型“食材”

搞机器学习的人常开玩笑说,80%的时间都在处理数据。这话一点不假。模型再高级,算法再精妙,如果喂给它的是“垃圾”数据,那它也只能输出“垃圾”结果。这一章,咱们就来聊聊怎么把原始的、杂乱的数据,变成干净、规整、适合模型“食用”的“食材”。这个过程,我称之为“数据炼金术”。

2.1 数据加载与初窥:知己知彼,百战不殆

拿到数据的第一步,不是急着清洗,而是先“认识”它。常用的数据格式有CSV、Excel、JSON,或者直接来自数据库。Pandas提供了非常简便的读取方法。

import pandas as pd
# 假设我们有一个‘sales_data.csv’文件
df = pd.read_csv('sales_data.csv') # 读取CSV
# df = pd.read_excel('data.xlsx') # 读取Excel
# 快速查看数据的前5行和基本信息
print("数据预览(前5行):")
print(df.head())
print("\n数据形状(行数,列数):", df.shape)
print("\n数据基本信息:")
print(df.info())
print("\n数值型列的统计摘要:")
print(df.describe())

df.info() 会告诉你每一列的数据类型和非空值数量,这是发现缺失值的第一步。df.describe() 则会展示数值型字段的统计信息(均值、标准差、最小值、四分位数等),帮你快速发现异常值(比如年龄列出现200岁)。

2.2 缺失值处理:填补还是删除?这是个策略问题

数据有缺失太常见了。处理缺失值没有绝对正确的答案,只有更适合当前场景的策略。

  • 直接删除:如果某一行或某一列缺失的数据太多(比如超过50%),或者这个特征本身不重要,直接删除可能是最省事的选择。使用 df.dropna()
  • 填充(Imputation):这是更常用的方法。用某个统计量(均值、中位数、众数)来填充。Scikit-learn提供了专门的工具。
from sklearn.impute import SimpleImputer
import numpy as np
# 创建一个有缺失的示例数据
data = {'年龄': [25, np.nan, 35, 40, np.nan],
        '收入': [50000, 60000, np.nan, 80000, 55000]}
df = pd.DataFrame(data)
print("原始数据:")
print(df)
# 策略1:用中位数填充‘年龄’,用均值填充‘收入’
imputer = SimpleImputer(strategy='median') # 先处理年龄
df['年龄'] = imputer.fit_transform(df[['年龄']])
imputer_mean = SimpleImputer(strategy='mean') # 再处理收入
df['收入'] = imputer_mean.fit_transform(df[['收入']])
print("\n填充后的数据:")
print(df)

对于分类数据(比如“城市”),可以用众数(出现次数最多的值)来填充。更高级的方法可以用模型(如KNN)来预测缺失值,但对于初学者,统计量填充已经能解决大部分问题。

2.3 特征工程(一):缩放与编码——让模型“公平”竞争

这是数据预处理中最关键的一步。想象一下,你的数据里有两个特征:“年龄”(范围0-100)和“年薪”(范围0-1,000,000)。如果不做处理,模型会认为“年薪”的数值更大,因此更重要,这显然是不公平的。我们需要特征缩放

标准化 (Standardization):将数据缩放为均值为0,标准差为1的分布。这是最常用的方法,适用于大多数算法(特别是基于距离的算法如SVM、KNN)。

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
# 假设‘df_features’是只包含数值特征的DataFrame
scaled_features = scaler.fit_transform(df[['年龄', '收入']])
# fit_transform的结果是numpy数组,可以转回DataFrame
df_scaled = pd.DataFrame(scaled_features, columns=['年龄_标准化', '收入_标准化'])
print(df_scaled.head())
print(f"缩放后年龄的均值:{df_scaled['年龄_标准化'].mean():.2f}, 标准差:{df_scaled['年龄_标准化'].std():.2f}")

另一个问题是分类特征编码。模型看不懂“北京”、“上海”、“广州”这些文字,需要把它们变成数字。最简单的是标签编码 (Label Encoding),但可能会引入大小顺序的误解(比如把城市编码成1,2,3,模型会误以为3>2>1)。更安全的方法是独热编码 (One-Hot Encoding),它为每个类别创建一个新的二进制列。

from sklearn.preprocessing import OneHotEncoder
# 原始数据
df_city = pd.DataFrame({'城市': ['北京', '上海', '广州', '北京', '深圳']})
print("原始城市数据:")
print(df_city)
# 使用Pandas的get_dummies进行独热编码(更简单)
df_encoded = pd.get_dummies(df_city, columns=['城市'], prefix='city')
print("\n独热编码后的数据:")
print(df_encoded)

现在,“城市”这个特征被拆成了 city_北京city_上海city_广州city_深圳 四个二进制的列,模型就能正确理解了。

3. 模型实战:手把手构建你的第一个预测器

理论说了不少,是时候动真格了。这一章,我们不谈天花乱坠的算法,就选两个最经典、最实用的问题:分类回归。我会用最少的代码,带你走完从数据到评估的完整流程,让你真切地感受到“我搞定了!”的成就感。

3.1 分类初体验:鸢尾花分类——机器学习的“Hello World”

鸢尾花数据集是机器学习界的经典入门案例。它包含了150朵鸢尾花的测量数据(花萼和花瓣的长度、宽度),以及它们对应的品种(山鸢尾、变色鸢尾、维吉尼亚鸢尾)。我们的任务是建立一个模型,根据花的测量数据来预测它的品种。

# 1. 导入必要的库
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression # 逻辑回归,用于分类
from sklearn.metrics import accuracy_score, classification_report
# 2. 加载数据
iris = load_iris()
X = iris.data  # 特征数据(150行,4列)
y = iris.target # 目标标签(0, 1, 2 代表三个品种)
print(f"特征形状:{X.shape}, 标签形状:{y.shape}")
print(f"特征名:{iris.feature_names}")
print(f"品种名:{iris.target_names}")
# 3. 划分数据集(重中之重!)
# 绝对不能拿训练的数据去测试,那叫“作弊”。
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}, 测试集大小:{X_test.shape}")
# 4. 创建并训练模型
model = LogisticRegression(max_iter=200) # 增加迭代次数确保收敛
model.fit(X_train, y_train) # 这一步就是“学习”的过程
# 5. 用模型进行预测
y_pred = model.predict(X_test)
# 6. 评估模型
accuracy = accuracy_score(y_test, y_pred)
print(f"\n模型在测试集上的准确率:{accuracy * 100:.2f}%")
# 更详细的评估报告
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))

重点解释一下 train_test_splitrandom_statetest_size=0.2 表示拿出20%的数据作为测试集,剩下的80%用于训练。random_state 是一个随机种子,设为固定的数字(比如42)可以保证每次运行代码时,数据集的划分方式是一样的,这样你的实验结果才是可复现的。classification_report 提供了精确率、召回率、F1分数等更细致的指标,尤其在各类别样本不平衡时比单纯看准确率更有价值。

3.2 回归问题实战:预测波士顿房价

如果说分类是预测类别(是什么花),那回归就是预测数值(房价是多少)。我们用一个简化版的波士顿房价数据集(注意:原数据集因伦理问题已不推荐使用,这里用其做方法演示)来练习。

# 1. 导入库
from sklearn.datasets import fetch_california_housing # 使用加州房价数据集作为替代
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.preprocessing import StandardScaler
# 2. 加载数据
housing = fetch_california_housing()
X = housing.data
y = housing.target
print(f"数据集描述:{housing.DESCR[:500]}...") # 打印部分描述
# 3. 数据划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 4. 特征缩放(对线性回归很重要)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # 只在训练集上fit
X_test_scaled = scaler.transform(X_test)       # 用训练集的参数转换测试集
# 5. 创建、训练、预测
lin_reg = LinearRegression()
lin_reg.fit(X_train_scaled, y_train)
y_pred = lin_reg.predict(X_test_scaled)
# 6. 评估回归模型
mse = mean_squared_error(y_test, y_pred)
rmse = mse ** 0.5  # 均方根误差,与目标值单位一致,更易解释
r2 = r2_score(y_test, y_pred)
print(f"\n模型评估结果:")
print(f"均方误差 (MSE): {mse:.2f}")
print(f"均方根误差 (RMSE): {rmse:.2f}") # 可以理解为平均预测误差
print(f"决定系数 (R^2 Score): {r2:.2f}")
# 解释一下系数
print(f"\n特征重要性(系数绝对值越大,影响越大):")
for feature_name, coef in zip(housing.feature_names, lin_reg.coef_):
    print(f"{feature_name:>10}: {coef:>7.3f}")

这里引入了两个重要的回归评估指标:RMSER^2。RMSE告诉你模型预测的平均误差有多大,单位与房价相同,非常直观。R^2 则反映了模型对数据波动的解释能力,越接近1越好。我们还查看了模型的系数,正系数表示该特征与房价正相关(如房间数越多,房价越高),负系数则表示负相关。

3.3 模型评估与调优:不止是看准确率

模型训练完,看一眼准确率或RMSE就结束了吗?不,那只是开始。我们需要更深入地评估,并尝试让它变得更好。

交叉验证:我们之前只用了一次划分(80%训练,20%测试)。但这次划分可能恰好让测试集“简单”或“困难”。为了更稳健地评估,可以用 K折交叉验证,把数据分成K份,轮流用其中一份做测试,其余做训练,最后取K次结果的平均值。

from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier
# 继续用鸢尾花数据
cv_model = RandomForestClassifier(random_state=42)
# 进行5折交叉验证,评估指标是准确率
cv_scores = cross_val_score(cv_model, X, y, cv=5, scoring='accuracy')
print(f"5折交叉验证准确率:{cv_scores}")
print(f"平均准确率:{cv_scores.mean():.2f} (+/- {cv_scores.std() * 2:.2f})") # 平均准确率及95%置信区间

超参数调优:模型有些参数不是从数据中学来的,而是我们事先设定的,比如随机森林里树的棵数(n_estimators)、逻辑回归的正则化强度(C)。这些叫超参数。手动调参像碰运气,我们可以用 网格搜索 (GridSearchCV) 自动寻找最优组合。

from sklearn.model_selection import GridSearchCV
# 定义要搜索的参数网格
param_grid = {
    'n_estimators': [50, 100, 200],      # 树的数量
    'max_depth': [None, 10, 20, 30],     # 树的最大深度
    'min_samples_split': [2, 5, 10]      # 分裂内部节点所需的最小样本数
}
# 创建网格搜索对象
grid_search = GridSearchCV(RandomForestClassifier(random_state=42),
                           param_grid,
                           cv=5,          # 使用5折交叉验证
                           scoring='accuracy',
                           n_jobs=-1)     # 使用所有CPU核心加速
# 在训练集上进行搜索(注意:这里用完整的X_train, y_train,GridSearchCV内部会再划分)
grid_search.fit(X_train, y_train)
# 输出最佳结果
print(f"最佳参数组合:{grid_search.best_params_}")
print(f"最佳交叉验证分数:{grid_search.best_score_:.2f}")
# 用最佳模型在测试集上做最终评估
best_model = grid_search.best_estimator_
test_accuracy = best_model.score(X_test, y_test)
print(f"最佳模型在测试集上的准确率:{test_accuracy:.2f}")

这个过程可能会花点时间,但它是提升模型性能最有效的方法之一。记住,最终评估一定要在从未参与训练和参数搜索的测试集上进行,这才是模型真实能力的体现。

4. 项目落地:从Jupyter Notebook到可复用的系统

在Notebook里跑通一个模型,和真正完成一个项目,中间还隔着一条鸿沟。这一章,我们来聊聊怎么跨过去,把你的实验代码变成健壮、可维护、甚至能提供服务的“产品”。这是我踩过很多坑才总结出的经验。

4.1 代码重构:告别“一次性脚本”

Notebook里的代码通常是线性的、探索式的。要把它变成项目,第一步是模块化重构。把数据加载、预处理、模型训练、评估这些功能拆分成独立的函数或类,放在不同的 .py 文件里。这样做的好处太多了:代码可读性高、易于调试、方便复用。

假设我们有一个房价预测项目,可以这样组织文件结构:

my_house_price_project/
├── data/
│   ├── raw/          # 存放原始数据
│   └── processed/    # 存放处理后的数据
├── src/              # 源代码目录
│   ├── __init__.py
│   ├── data_preprocessing.py  # 数据清洗和特征工程函数
│   ├── model.py               # 模型定义和训练函数
│   └── evaluate.py            # 评估指标和可视化函数
├── notebooks/         # 存放探索性的Jupyter Notebook
│   └── exploration.ipynb
├── config.yaml        # 配置文件,存放超参数、文件路径等
├── train.py           # 主训练脚本
└── requirements.txt   # 项目依赖库列表

data_preprocessing.py 里,你可能会有一个这样的函数:

# src/data_preprocessing.py
import pandas as pd
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline

def build_preprocessing_pipeline(numerical_features, categorical_features):
    """
    构建一个完整的数据预处理管道。
    参数:
        numerical_features: 数值型特征列名列表
        categorical_features: 分类型特征列名列表
    返回:
        一个拟合好的预处理Pipeline
    """
    # 数值型特征的处理:填充中位数,然后标准化
    numerical_transformer = Pipeline(steps=[
        ('imputer', SimpleImputer(strategy='median')),
        ('scaler', StandardScaler())
    ])
    # 分类型特征的处理:填充众数,然后独热编码
    categorical_transformer = Pipeline(steps=[
        ('imputer', SimpleImputer(strategy='most_frequent')),
        ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False)) # sparse_output=False 返回数组而非稀疏矩阵
    ])
    # 将两个处理流程合并
    preprocessor = ColumnTransformer(
        transformers=[
            ('num', numerical_transformer, numerical_features),
            ('cat', categorical_transformer, categorical_features)
        ])
    return preprocessor

def load_and_process_data(filepath, target_column, preprocessor):
    """
    加载数据并应用预处理管道。
    """
    df = pd.read_csv(filepath)
    X = df.drop(columns=[target_column])
    y = df[target_column]
    X_processed = preprocessor.fit_transform(X)
    # 获取独热编码后的特征名(稍微复杂一点,但值得做)
    # ... (此处省略具体代码,可用 preprocessor.get_feature_names_out())
    return X_processed, y

train.py 主脚本里,你就可以清晰、简洁地调用这些模块:

# train.py
from src.data_preprocessing import build_preprocessing_pipeline, load_and_process_data
from src.model import train_random_forest
from src.evaluate import evaluate_model, plot_feature_importance
import yaml

def main():
    # 1. 加载配置
    with open('config.yaml', 'r') as f:
        config = yaml.safe_load(f)
    # 2. 构建预处理管道
    preprocessor = build_preprocessing_pipeline(
        config['numerical_features'],
        config['categorical_features']
    )
    # 3. 加载并处理数据
    X_train, y_train = load_and_process_data(config['train_data_path'], config['target'], preprocessor)
    X_test, y_test = load_and_process_data(config['test_data_path'], config['target'], preprocessor)
    # 4. 训练模型
    model = train_random_forest(X_train, y_train, config['model_params'])
    # 5. 评估模型
    evaluate_model(model, X_test, y_test)
    plot_feature_importance(model, preprocessor)

if __name__ == "__main__":
    main()

看,这样结构是不是清晰多了?任何新加入项目的人,都能很快理解代码的脉络。

4.2 模型持久化:训练一次,到处使用

模型训练很耗时,我们不可能每次预测都重新训练。我们需要把训练好的模型“存”下来,下次直接“读”出来用。这个过程叫模型持久化序列化。Python里最常用的工具是 pickle 或者 Scikit-learn 自带的 joblib(对于包含大量numpy数组的模型,joblib效率更高)。

import joblib
# 假设 `best_model` 是你训练好的最优模型,`preprocessor` 是预处理管道
# 保存模型和预处理管道
joblib.dump(best_model, 'models/best_house_price_model.pkl')
joblib.dump(preprocessor, 'models/preprocessor.pkl')
print("模型和预处理管道已保存。")
# --- 在另一个脚本或应用中加载并使用 ---
# 加载模型和管道
loaded_model = joblib.load('models/best_house_price_model.pkl')
loaded_preprocessor = joblib.load('models/preprocessor.pkl')
# 假设有新数据 `new_data` (一个DataFrame)
new_data_processed = loaded_preprocessor.transform(new_data) # 注意是transform,不是fit_transform!
prediction = loaded_model.predict(new_data_processed)
print(f"预测的房价为:{prediction[0]:.2f}")

记住,预处理管道(preprocessor)也必须保存和加载,因为新来的数据必须经过和训练数据完全一样的预处理(使用相同的填充值、缩放参数、编码字典),模型才能正确工作。

4.3 简易部署:让你的模型提供API服务

模型存好了,怎么让别人用呢?一个简单又实用的方法是将它包装成一个 Web API。这样,任何能发送HTTP请求的程序(比如一个手机App、一个网页前端、另一个后端服务)都可以调用你的模型进行预测。我们用轻量级的 Flask 框架来实现。

首先,安装Flask:pip install flask 然后,创建一个 app.py 文件:

# app.py
from flask import Flask, request, jsonify
import joblib
import pandas as pd
import numpy as np

app = Flask(__name__)

# 在服务启动时加载模型和预处理管道
MODEL_PATH = 'models/best_house_price_model.pkl'
PREPROCESSOR_PATH = 'models/preprocessor.pkl'
model = joblib.load(MODEL_PATH)
preprocessor = joblib.load(PREPROCESSOR_PATH)

# 定义API端点
@app.route('/predict', methods=['POST'])
def predict():
    """
    预测端点。
    期望接收一个JSON,包含与训练数据相同的特征。
    """
    try:
        # 1. 从请求中获取JSON数据
        data = request.get_json()
        # 2. 将JSON转换为DataFrame(确保列的顺序)
        # 假设前端传来的数据是 {‘feature1‘: value1, ‘feature2‘: value2, ...}
        input_df = pd.DataFrame([data]) # 注意是包含一个字典的列表
        # 3. 应用预处理
        processed_data = preprocessor.transform(input_df)
        # 4. 进行预测
        prediction = model.predict(processed_data)
        # 5. 返回预测结果
        result = {'predicted_price': float(prediction[0])}
        return jsonify(result), 200
    except Exception as e:
        # 如果出错,返回错误信息
        return jsonify({'error': str(e)}), 400

if __name__ == '__main__':
    # 启动服务,host='0.0.0.0' 让服务在外部可访问
    app.run(host='0.0.0.0', port=5000, debug=True) # debug=True仅用于开发

运行这个脚本(python app.py),你的模型就变成了一个运行在本地5000端口的Web服务。你可以用 curl 命令或者写一段Python代码来测试它:

# test_client.py
import requests
import json

url = 'http://127.0.0.1:5000/predict'
# 构造一条符合模型特征格式的测试数据
test_data = {
    'MedInc': 3.5,
    'HouseAge': 15,
    'AveRooms': 6.0,
    'AveBedrms': 1.0,
    'Population': 1000,
    'AveOccup': 3.0,
    'Latitude': 37.8,
    'Longitude': -122.4
}
headers = {'Content-Type': 'application/json'}
response = requests.post(url, data=json.dumps(test_data), headers=headers)
print(response.json())

如果返回了 {‘predicted_price‘: 2.5} 这样的结果,恭喜你,你的第一个机器学习微服务就成功跑起来了!当然,生产环境还需要考虑很多,比如使用 gunicornuWSGI 这样的WSGI服务器来替代Flask自带的开发服务器,添加身份验证、日志、监控等,但这个小例子已经为你打开了模型服务化的大门。走到这一步,你已经从一个机器学习的学习者,变成了一个能解决实际问题的实践者。

更多推荐