回忆一下传统的机器学习算法,针对经典的 Adult Income(成年人收入)数据集,写一个极简教程!

        数据源自1994年美国人口普查局(U.S. Census Bureau)的数据库,目标很简单:根据一个人的年龄、学历、职业、工作时长等信息,预测他的年收入是否能超过 5 万美元(>50K)。这是一个典型的二分类任务

        我们将选择三个不同的机器学习模型进行比较,基本过程是:

        加载数据  ➡️ 划分数据  ➡️  特征工程  ➡️  训练模型  ➡️  验证阶段

一、导入依赖

import pandas as pd  
import numpy as np 

# sklearn机器学习相关工具
from sklearn.model_selection import train_test_split  

# 引入三种适合表格分类任务的经典模型
from sklearn.linear_model import LogisticRegression          # 逻辑回归
from sklearn.ensemble import RandomForestClassifier          # 随机森林
from sklearn.ensemble import GradientBoostingClassifier      # 梯度提升树

from sklearn.metrics import accuracy_score

# 数据预处理的流水线工具
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer

import warnings
warnings.filterwarnings('ignore') 

二、加载数据

        直接从官方渠道里加州大学欧文分校(UCI)的公开数据库里下载数据,注意如遇网络问题建议使用终端代理。

# 数据下载地址
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data"

# 告诉电脑每一列数据代表什么意思(列名)
columns = ['age', 'workclass', 'fnlwgt', 'education', 'education-num', 'marital-status',
           'occupation', 'relationship', 'race', 'sex', 'capital-gain', 'capital-loss',
           'hours-per-week', 'native-country', 'income']

# 读取数据!
# sep=',\\s*' 是为了处理逗号后面的空格,na_values="?" 是告诉电脑把问号当做缺失数据(空白)
df = pd.read_csv(url, names=columns, sep=',\\s*', engine='python', na_values="?")

print(f"一共拿到了 {df.shape[0]} 个人的数据,每个人有 {df.shape[1]} 项信息。")
df.head() 

        一共拿到了 32561 个人的数据,每个人有 15 项信息。

        在机器学习中,特征(Feature)是模型用来进行预测的输入变量,目标(Target)是模型要预测的输出变量

        可以看到数据集包含 14 个特征字段和 1 个目标字段 income(收入,二分类:<=50K 与 >50K

  1. 数值型特征有 age(年龄)、education-num(受教育年限)、hours-per-week(每周工作小时数)等;
  2. 类别型特征有 workclass(工作类型,如 State-gov)、education(教育程度,如 Bachelors)、marital-status(婚姻状况,如 Never-married)、occupation(职业,如 Adm-clerical)等。

        例如第一条样本是一位, 39 岁的男性(age=39, sex=Male),教育程度为学士(education=Bachelors),婚姻状况为未婚(marital-status=Never-married),职业是行政文员(occupation=Adm-clerical),每周工作 40 小时(hours-per-week=40),最终年收入不超过 5 万美元(income=<=50K)。

三、划分数据

        把所有的数据分成两份:

  1. 训练集(复习题):占80%,给模型找规律用
  2. 训练集(考试题):占20%,藏起来,等模型学习完了考考它们准不准
# 1. 拆分题目 (X) 和 答案 (y)
X = df.drop('income', axis=1) # 去掉收入列,剩下的都是题目
y = df['income'].apply(lambda x: 1 if x == '>50K' else 0) # 答案变成数字:>50K是1,否则是0

# 2. 切分训练集和测试集
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]} 道")

        训练集有 26048 道,测试集有 6513 道

四、特征工程

4.1 为什么需要特征工程?

        为什么要特征工程,而不直接把数据交给模型呢?因为机器学习模型只认识数字,不认识像“Bachelors(本科)、Husband(丈夫)”这样的英文单词。如果有文字数据,需要转为类似0、1这样的独热编码。

        而且有些数据是缺漏的,所以要进行数据预处理,例如填入最常见的值。这里我们用Pipeline工具。

  from sklearn.pipeline import Pipeline 是从 scikit-learn(一个 Python 机器学习库)中导入 Pipeline(管道) 类。

        它用于将多个数据预处理步骤和模型按顺序串联成一个整体对象,避免重复造轮子。 Pipeline使用时把一系列的 (名称, 变换器) 元组传入 Pipeline

4.2 这里怎么做?

 这里我们具体构建的流水线如下:

  1. 数字型数据(如年龄、受教育年限等),如有缺漏就填入中位数(median策略),然后做标准化(0~1)避免数字太夸张对其他特征不公平,影响模型来判断。
  2. 文字型数据(如工作类型、教育程度等),如有缺漏就填入这一列中出现次数最多的值(most_frequent策略),然后做独热编码(onehot),自动将文字转为计算机可以学习的01编码串。
# 自动挑出哪些是数字列,哪些是文字列
num_cols = X.select_dtypes(include=['int64', 'float64']).columns.tolist()
cat_cols = X.select_dtypes(include=['object']).columns.tolist()

# 针对数字的流水线(填补缺失值 -> 标准化)
num_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

# 针对文字的流水线(填补缺失值 -> 转成0和1)
cat_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
])

# 把两条流水线合并成一个超级加工厂
preprocessor = ColumnTransformer([
    ('num', num_pipeline, num_cols),
    ('cat', cat_pipeline, cat_cols)
])

        在 Jupyter Notebook 中,可以显示Pipeline流水线的结构图

from sklearn import set_config
set_config(display='diagram')

 # 直接运行这一行,就能看到漂亮的图表,用于理解流水线的结构
preprocessor

五、训练模型

     应该是最激动人心的一步了!这里我们用三种sklearn自带的三种不同的模型,来对付这个二分类数据集任务:

  1. 逻辑回归 (Logistic Regression):最基础的回归模型,用作 Baseline。
  2. 随机森林 (Random Forest):由很多棵“决策树”组成的评审团,综合不同意见,不容易偏科。
  3. 梯度提升树 (Gradient Boosting):相对更复杂一些的模型,用一棵树接一棵树地去纠正前一棵树的错误,通常在表格数据上通常准确率最高。
# 准备好我们的 3 个模型
models = {
    "逻辑回归 (Logistic Regression)": LogisticRegression(max_iter=1000, random_state=42),
    "随机森林 (Random Forest)": RandomForestClassifier(n_estimators=100, random_state=42),
    "梯度提升树 (Gradient Boosting)": GradientBoostingClassifier(n_estimators=100, random_state=42)
}

best_model = None
best_acc = 0
best_name = ""

print("正在训练模型...\n")

# 让模型轮流学习和考试
for name, clf in models.items():
    # 把流水线和当前模型绑在一起
    model_pipeline = Pipeline([
        ('preprocessor', preprocessor),
        ('classifier', clf)
    ])
    
    # 学习!(只给它看复习题 X_train 和 y_train)
    model_pipeline.fit(X_train, y_train)
    
    # 考试算分
    y_pred = model_pipeline.predict(X_test)
    acc = accuracy_score(y_test, y_pred)
    
    print(f"- {name} 模型的准确率是:{acc * 100:.2f}%")
    
    # 记录得分最高的模型
    if acc > best_acc:
        best_acc = acc
        best_model = model_pipeline
        best_name = name

print(f"\n🎉 模型训练和测试结束!最终胜出的是:【{best_name}】!")
  • 逻辑回归 (Logistic Regression) 模型的准确率是:85.52%
  • 随机森林 (Random Forest) 模型的准确率是:85.98%
  • 梯度提升树 (Gradient Boosting) 模型的准确率是:87.13%

🎉 模型训练和测试结束!最终胜出的是:【梯度提升树 (Gradient Boosting)】!

六、验证阶段

6.1 从测试集中验证

        眼见为实,我们从测试集中抽三条数据,看看最佳模型猜的准不准。

# 抽取测试集里的第 10, 100, 1000 个人
sample_indices = [10, 100, 1000]
X_samples = X_test.iloc[sample_indices]
y_samples_true = y_test.iloc[sample_indices].values

# 让最强的模型预测这 3 个人
y_samples_pred = best_model.predict(X_samples)

for i in range(len(sample_indices)):
    print(f"\n--- 抽查对象 {i+1} ---")
    print(f"年龄: {X_samples.iloc[i]['age']}, 职业: {X_samples.iloc[i]['occupation']}, 学历: {X_samples.iloc[i]['education']}, 每周工作: {X_samples.iloc[i]['hours-per-week']}小时")
    
    true_label = ">50K (高收入)" if y_samples_true[i] == 1 else "<=50K (低收入)"
    pred_label = ">50K (高收入)" if y_samples_pred[i] == 1 else "<=50K (低收入)"
    
    print(f"【实际情况】: {true_label}")
    print(f"【AI 预测】: {pred_label}")
    if true_label == pred_label:
        print("✅ AI 猜对了!")
    else:
        print("❌ AI 猜错了!")

--- 抽查对象 1 ---
年龄: 43, 职业: Sales, 学历: Bachelors, 每周工作: 42小时
【实际情况】: <=50K (低收入)
【AI 预测】: <=50K (低收入)
✅ AI 猜对了!


--- 抽查对象 2 ---
年龄: 40, 职业: Prof-specialty, 学历: Prof-school, 每周工作: 55小时
【实际情况】: >50K (高收入)
【AI 预测】: >50K (高收入)
✅ AI 猜对了!


--- 抽查对象 3 ---
年龄: 34, 职业: Transport-moving, 学历: HS-grad, 每周工作: 40小时
【实际情况】: <=50K (低收入)
【AI 预测】: <=50K (低收入)
✅ AI 猜对了!

6.2 造全新数据来考考模型

        为了测试这个最佳模型是不在真的学会了,我们造一条互联网上没有的全新的模拟数据,填上极端点的信息,让模型进行预测:

# 按照数据集的列,伪造这两个人的信息
fake_data = pd.DataFrame([
    {
        'age': 22, 'workclass': 'Private', 'fnlwgt': 200000, 'education': 'HS-grad', 
        'education-num': 9, 'marital-status': 'Never-married', 'occupation': 'Handlers-cleaners', 
        'relationship': 'Own-child', 'race': 'White', 'sex': 'Male', 
        'capital-gain': 0, 'capital-loss': 0, 'hours-per-week': 40, 'native-country': 'United-States'
    },
    {
        'age': 50, 'workclass': 'Private', 'fnlwgt': 200000, 'education': 'Doctorate', 
        'education-num': 16, 'marital-status': 'Married-civ-spouse', 'occupation': 'Exec-managerial', 
        'relationship': 'Husband', 'race': 'White', 'sex': 'Male', 
        'capital-gain': 20000, 'capital-loss': 0, 'hours-per-week': 60, 'native-country': 'United-States'
    }
])

👤 人物A(打工小白)

小明,22岁,高中毕业,没结婚,在私人企业做流水线工人(Handlers-cleaners),每周工作40小时,没有资本收益。

💼 人物B(成功人士):

老王,50岁,博士毕业,已婚,在公司当高管(Exec-managerial),每周疯狂工作60小时,投资赚了 20000 美元。

# 预测结果(使用刚刚胜出的 best_model)
fake_preds = best_model.predict(fake_data)

print("=== AI 对伪造人物的收入预测 ===")
names = ["打工小白 (小明)", "成功人士 (老王)"]
for i, name in enumerate(names):
    pred_label = ">50K (高收入,有钱人!)" if fake_preds[i] == 1 else "<=50K (低收入,还需努力!)"
    print(f"{name} 的预测结果: {pred_label}")

=== AI 对伪造人物的收入预测 ===

  • 打工小白 (小明) 的预测结果: <=50K (低收入,还需努力!)
  • 成功人士 (老王) 的预测结果: >50K (高收入,有钱人!)

七、总结

        机器学习其实一点也不神秘,说到底就是:

 拿数据 ➡️ 处理成电脑认识的数字 ➡️ 选几个合适的模型比较 ➡️ 拿新数据让最强模型去猜

        通过这短短的几步,我们已经成功训练了一个能识别收入的AI模型了,大家也自己动手跑一下代码吧。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐