AI 项目实践(第 3 节深度讲解)
3. AI 项目的开发与部署:完整教学教程
本教程将带你完成一个完整的 AI 项目开发与部署流程。我们将一步步带你从数据获取、数据清洗、模型训练、评估,到最终部署与集成,帮助你掌握如何从零开始构建 AI 项目。通过这些步骤,你将了解如何获取并清理数据,如何训练并评估模型,如何将模型部署为 Web 服务,以及如何通过容器化和 CI/CD 流程进行管理和自动化部署。
环境搭建
首先,确保你的开发环境中已安装必要的 Python 库。这些库将帮助你进行数据处理、机器学习模型训练、模型部署等任务。你可以使用 pip 安装这些库:
pip install pandas numpy scikit-learn flask pickle-mixin requests3.1 数据获取与清洗
✔ 从 Kaggle 获取数据集
Kaggle 是 Google 旗下面向数据科学与机器学习的社区平台,提供竞赛、公开数据集、在线 Notebook 与讨论区等功能,可快速完成实验与交流。
常用优势:
- 完整的数据描述:绝大多数数据集都附带字段说明、示例 Notebook 与许可证信息,能帮助理解业务背景。
- 可复现的 Notebook:直接 Fork 官方或社区 Notebook,快速对比不同特征工程与模型方案。
- API 自动化:
kaggleCLI 支持脚本化地批量下载、同步数据,便于放入 CI/CD 或日常流水线。
了解这些特性后,就能理解为何 Kaggle 是获取结构化数据与复现基准实验的首选入口。
以 Titanic 数据集为例,进行数据清洗与建模。该数据集源自 1912 年泰坦尼克号沉船事件,共包含 891 名乘客的基本信息(例如 Name、Sex、Age、Pclass、SibSp、Parch、Fare、Embarked 等)以及标签 Survived。目标是根据这些字段预测乘客是否在事故中幸存:1 代表幸存,0 代表遇难。因为字段清晰、标签明确,而且数据量适中,因此常被用于初学者的分类练习。
- 访问 Kaggle Titanic 数据集页面
- 注册并下载数据集(
train.csv和test.csv)
然后在 Python 中加载数据:
import pandas as pd
# 加载 Titanic 数据集
df = pd.read_csv('train.csv')
print(df.head())💡
pd.read_csv()默认会从当前执行脚本的目录查找文件。如果你把train.csv放在其他位置,可传入绝对路径:
- macOS / Linux:路径使用正斜杠,例如
df = pd.read_csv('/Users/yourname/Downloads/titanic/train.csv')- Windows:路径使用反斜杠或原始字符串,例如
df = pd.read_csv(r'C:\Users\yourname\Downloads\titanic\train.csv')建议把数据文件放在项目根目录的
data/或datasets/文件夹,并用相对路径(如pd.read_csv('data/train.csv')),这样无论在 macOS 还是 Windows 上都能保持一致。
ℹ️
df.head()会返回数据框的前 5 行(默认值),通过print(df.head())就能快速预览各列字段、数据类型以及是否有明显的缺失或异常值,是检查数据加载是否成功的第一步。⚠️ 以上清洗操作只会修改内存中的
DataFrame,不会影响磁盘上的原始train.csv。只有显式调用df.to_csv()等写入函数时,才会生成新的文件或覆盖旧文件。
✔ 数据清洗
数据清洗是机器学习项目中的关键步骤,清洗数据的目的是确保数据的质量,避免模型训练时受到噪声和不一致数据的影响。数据清洗常见的步骤包括:
- 去除缺失值:删除或填补缺失的数据行。
- 去除重复数据:删除完全相同的数据行。
- 填补缺失数据:可以使用均值、中位数、众数等填补缺失数据。
数据清洗代码示例:
上面提到的每一步都在解决特定的数据质量问题,下面逐一拆解:
-
缺失值统计
机器学习模型通常要求输入表格是“整齐的”,也就是每条记录每个特征都要有值。df.isnull().sum()会遍历所有列并统计缺失(NaN)数量,输出结果类似Age 177,提醒我们 Age 列缺失 177 个值。只有掌握总体缺失情况,才能决定某列是应该删除还是填补。 -
均值填充 Age
Age 是连续数值特征,若直接删除存在缺失的行,很容易减少大量样本。用df['Age'].mean()的结果填充,可以保持 Age 的整体分布,弥补缺失带来的数据不足。相比填 0 或随机数,均值填补更贴近真实乘客的年龄范围,也更容易被模型学习。 -
众数填充 Embarked
Embarked 表示乘客在何处登船,是一个分类变量,常见取值为 C、Q、S。对于这类离散特征,均值没有意义,所以我们使用最常出现的值(众数)填补:df['Embarked'].mode()[0]。众数就是“出现次数最多的类别”,把缺失的记录替换为当前最常见的港口,能最大程度维持原有分布。如果缺失占比较小,这类填充对整体比例的影响甚微;若缺失率较高,就需要考虑按Pclass等条件分组取众数,或用建模方式预测缺失值,以免某一类别过度膨胀。 -
删除 Cabin 列
Cabin(客舱号)缺失率极高,保留它既无法直接用于建模,还可能引入噪声。在这种情况下,与其强行填补,不如用df.drop(columns=['Cabin'])将整列删除,把注意力放在质量更高的特征上。 -
删除重复数据
数据源难免会重复抓取同一位乘客的记录,df.drop_duplicates()可以把完全相同的行去掉。若不处理,模型在训练时会多次“看到”同一条样本,从而导致偏置或过拟合。
# 查看缺失值
print(df.isnull().sum())
# 填补缺失的 Age 数据,用均值填充
df['Age'] = df['Age'].fillna(df['Age'].mean())
# 填补缺失的 Embarked 数据,用众数填充
df['Embarked'] = df['Embarked'].fillna(df['Embarked'].mode()[0])
# 删除 Cabin 列,因为缺失值过多
df = df.drop(columns=['Cabin'])
# 删除重复数据
df = df.drop_duplicates()ℹ️ 后续的特征工程与模型训练默认在同一个脚本或 Notebook 中继续执行,因此无需“复制”前面的清洗代码,只要保持顺序运行即可。如果你想把清洗和训练分开管理,可以在此处用
df.to_csv('data/train_clean.csv', index=False)保存清洗结果,然后在新的脚本里读取这个干净文件。⚠️ 如果你在 Notebook 中多次反复运行
get_dummies这类会修改列名的操作,可能会出现KeyError: 'Embarked'(即列已经被转换或删除)的错误。遇到这种情况,重新执行“读取数据”那一节以获得原始列,再继续清洗即可;也可以在操作前通过print(df.columns)来确认列名是否还存在。
扩展练习:
- 改用不同的填充方法:尝试使用不同的策略填补缺失值(例如,中位数或众数)。
- 查看数据分布:使用 Seaborn 库对数据进行可视化,观察数据是否存在明显的偏差。
import seaborn as sns
sns.histplot(df['Age'])3.2 模型训练与评估
⚠️ 在进入建模阶段前,需要把
Sex、Embarked等类别型字符串字段转换成模型能理解的数字。最常见的做法是用pandas.get_dummies()进行独热编码(One-Hot Encoding),将每个类别拆成单独的 0/1 列。否则会出现 “could not convert string to float: ‘male’” 这类错误,因为大多数 sklearn 模型(包括随机森林)只能处理数值输入。
# 通过独热编码把分类字段转换为数字列
df = pd.get_dummies(
df,
columns=['Sex', 'Embarked'], # 指定要转换的列
drop_first=True # 丢弃一个基准列,避免完全共线
)经过独热编码后,原来的
Sex列会变成Sex_male(1 表示男,0 表示女),Embarked则会拆成Embarked_Q、Embarked_S两列(是否在对应港口登船)。这些都是后续建模时要用到的数值特征。
✔ 选择合适的模型进行训练
在本 Demo 中,我们将使用 随机森林 作为分类器进行建模,并将其用于预测是否幸存。使用 Scikit-learn 中的 RandomForestClassifier 进行训练。
📘 随机森林(Random Forest)是一种“装袋(Bagging)”思想的集成学习模型:
- 多棵树并行训练:算法先对原始数据做有放回的随机采样(bootstrap),得到多个训练子集,并为每棵树随机挑选部分特征,这样每棵树都“不一样”。“有放回”指的是每次抽样后会把样本放回原集合,再进行下一次抽取,所以同一个样本可能被同一棵树重复抽中,也可能完全没被抽到,这正是让每棵树拥有“不同视角”的关键。
- 结果投票或平均:分类任务中,每棵树给出一个类别预测,随机森林会对所有树的结果做投票(取最多票的类别);回归任务则对所有树的预测取平均。
- 优点:天然支持非线性关系、抗过拟合(因为树之间差异大)、几乎无需特征缩放,还能通过特征重要性衡量哪些字段更关键。常用超参数包括
n_estimators(树的数量)、max_depth(树深度)、max_features(每次挑选的特征数)等。
对于初学者来说,只需设置树的数量和随机种子,就能得到一个效果稳定、易于解释的基线模型。你可以把它想象成“让多位不同专长的专家分别独立判断,再通过投票得到最终结论”:
- 单棵决策树:类似问答流程——先问“乘客是男是女?”,再根据回答继续问“年龄多少?”,直到给出“幸存/遇难”的预测。但单棵树很容易被某些特征噪声左右。
- 多棵树组成森林:随机森林会让每棵树看到不同的数据和特征,降低“所有树都被同一个噪声影响”的概率。某一棵树即使判断偏了,也会被其他树纠正。
- 可解释性:训练完成后,可以查看
model.feature_importances_,了解模型认为“票价”“性别”“客舱等级”等特征的重要程度,方便分析业务洞察。
划分数据集:
from sklearn.model_selection import train_test_split # 切分训练/测试集的工具
# 1. 选择模型输入特征:客舱等级、年龄、亲友数量、票价及编码后的性别/登船港口
feature_cols = [
'Pclass', 'Age', 'SibSp', 'Parch', 'Fare',
'Sex_male', # One-Hot 编码后得到的列
'Embarked_Q', 'Embarked_S'
]
X = df[feature_cols]
# 2. 选择预测目标:Survived(1 为幸存,0 为遇难)
y = df['Survived']
# 3. 数据集划分:70% 训练集,30% 测试集;random_state 固定随机数便于复现
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)训练模型:
from sklearn.ensemble import RandomForestClassifier # 随机森林分类器
# 创建并训练模型:
# - n_estimators=100 表示训练 100 棵树,数量越多越稳定但训练更慢
# - random_state 控制随机性,确保每次运行结果一致
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train) # 用训练集拟合模型ℹ️ 这里虽然只调用了一次
model.fit(),但由于我们把n_estimators设为 100,随机森林内部会自动训练 100 棵不同的决策树。可以把这看成“一个模型里包含了 100 个子模型”:RandomForestClassifier负责统筹管理,后台帮你创建、训练每一棵树,并在预测时收集它们的投票结果。
模型评估:
在模型训练完成后,我们使用 AUC 和 F1 分数 来评估模型性能。
from sklearn.metrics import f1_score, roc_auc_score # 常见分类评估指标
# 1. 得到测试集预测标签(0/1),用于计算 F1
y_pred = model.predict(X_test)
# 2. 计算 F1:同时考虑查准率和查全率,数值越接近 1 表示越平衡
f1 = f1_score(y_test, y_pred)
print(f'F1 Score: {f1}')
# 3. 计算 AUC:基于预测概率衡量分类阈值在 0~1 之间变化时的整体表现
y_pred_proba = model.predict_proba(X_test)[:, 1] # 取“幸存”的概率
auc = roc_auc_score(y_test, y_pred_proba)
print(f'AUC: {auc}')
- F1 分数:是精准率(Precision)和召回率(Recall)的调和平均,适合样本不平衡的二分类场景。F1 越接近 1,说明模型既能尽量找对正样本,又不会误判太多负样本。
- AUC(Area Under the ROC Curve):以不同阈值下的 TPR/FPR 关系为基础,衡量模型整体区分正负样本的能力。AUC=0.5 表示完全随机,接近 1 则说明模型越优秀。
这段代码先获得预测标签y_pred,再计算 F1;同时通过predict_proba获取“幸存”概率,以此计算 AUC,最后把两个指标打印出来便于比较不同模型或超参数。
扩展练习:
- 尝试其他分类模型:除了随机森林,可以尝试 Logistic Regression、SVM 或 XGBoost 等其他模型,并比较它们的表现。
- 交叉验证:使用 KFold 交叉验证来评估模型的稳定性和泛化能力。
from sklearn.model_selection import cross_val_score
cv_score = cross_val_score(model, X, y, cv=5)
print(f'Cross-validation score: {cv_score.mean()}')3.3 模型部署与集成
✔ Python 服务化(使用 Flask)
将训练好的模型转化为 Web 服务是 AI 项目部署的重要步骤。我们将使用 Flask 来创建 Web 服务。
⚠️ 服务端必须复用训练阶段的特征工程。上文中我们对
Sex、Embarked做了 One-Hot 编码,生成Sex_male、Embarked_Q、Embarked_S三列,因此线上推理也要构造同样的 8 个特征,否则会出现 “X has 7 features, but RandomForestClassifier is expecting 8” 的错误。
- 保存训练好的模型:
import pickle
pickle.dump(model, open('titanic_model.pkl', 'wb'))- 创建 Flask Web 服务:
from flask import Flask, request, jsonify
import pickle
# 加载模型
model = pickle.load(open('titanic_model.pkl', 'rb'))
FEATURE_COLS = ['Pclass', 'Age', 'SibSp', 'Parch', 'Fare', 'Sex_male', 'Embarked_Q', 'Embarked_S']
def build_feature_vector(payload):
"""将原始 JSON 输入转换为与训练阶段一致的 8 维特征"""
sex_value = str(payload['Sex']).lower()
embarked_value = str(payload['Embarked']).upper()
row = {
'Pclass': payload['Pclass'],
'Age': payload['Age'],
'SibSp': payload['SibSp'],
'Parch': payload['Parch'],
'Fare': payload['Fare'],
'Sex_male': 1 if sex_value in ('male', 'm', '1') else 0,
# 训练时 drop_first=True,把 C 作为基准列,因此这里只需要 Q/S 两列
'Embarked_Q': 1 if embarked_value in ('Q', '2') else 0,
'Embarked_S': 1 if embarked_value in ('S', '3') else 0,
}
return [[row[col] for col in FEATURE_COLS]]
app = Flask(__name__)
@app.route('/predict', methods=['POST'])
def predict():
data = request.get_json()
features = build_feature_vector(data)
prediction = model.predict(features)
return jsonify({'prediction': int(prediction[0])})
if __name__ == '__main__':
app.run(debug=True)- 通过浏览器或 Postman 测试 API:
发送 POST 请求到http://localhost:5000/predict,并在请求体中传递数据,例如:
{
"Pclass": 3,
"Age": 22,
"SibSp": 1,
"Parch": 0,
"Fare": 7.25,
"Sex": "male",
"Embarked": "S"
}命令行可使用 curl 直接发起请求:
curl -X POST http://localhost:5000/predict \
-H "Content-Type: application/json" \
-d '{"Pclass":3,"Age":22,"SibSp":1,"Parch":0,"Fare":7.25,"Sex":"male","Embarked":"S"}'✔ 使用 Docker 容器化部署
- Dockerfile:
FROM python:3.8
WORKDIR /app
# 安装 Flask 和依赖
COPY requirements.txt .
RUN pip install -r requirements.txt
# 拷贝应用代码
COPY . .
CMD ["python", "app.py"]- 构建 Docker 镜像并启动容器:
docker build -t titanic-model .
docker run -p 5000:5000 titanic-model通过 Docker 部署,我们可以确保在不同环境中一致运行。
✔ 持续集成与持续部署(CI/CD)
使用 GitHub Actions 自动化模型训练、测试、部署流程:
name: CI/CD Pipeline
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Run tests
run: |
pytest tests/
- name: Deploy Model
run: |
ssh deploy@your-server "docker-compose pull && docker-compose up -d"通过 CI/CD 工具,自动化模型的训练、测试和生产环境部署,确保 AI 项目的高效持续迭代。
🎯 本章总结
通过本章,你已经掌握了:
- 数据获取与清洗:如何从 Kaggle 下载数据并进行清洗。
- 模型训练与评估:如何训练、评估和优化模型。
- 模型部署与集成:将模型封装为 Web 服务,进行容器化部署,使用 CI/CD 自动化过程。