PyTorch-2.x-Universal-Dev-v1.0实战:手把手教你用Titanic数据集完成数据预处理

1. 从零开始:为什么你需要这个开箱即用的开发环境?

如果你刚开始接触深度学习项目,大概率会遇到这样的场景:好不容易找到一个开源模型,兴致勃勃地准备复现,结果光是安装PyTorch、配置CUDA、搭建Jupyter环境就折腾了大半天,最后还可能因为版本冲突导致代码跑不起来。这种体验,相信很多开发者都深有体会。

PyTorch-2.x-Universal-Dev-v1.0镜像就是为了解决这个问题而生的。它基于官方PyTorch底包构建,预装了数据处理、可视化、开发环境等全套工具,并且配置了国内镜像源,真正做到下载即用。今天,我就带你用这个镜像,从零开始完成一个完整的数据预处理流程。

我们将使用经典的Titanic数据集作为案例。这个数据集虽然不大,但包含了数值、类别、文本、缺失值等多种数据类型,非常适合用来演示数据预处理的完整流程。通过这篇文章,你不仅能学会如何使用这个镜像,还能掌握一套通用的数据预处理方法。

2. 环境准备:快速启动与基础验证

2.1 启动镜像并进入开发环境

假设你已经通过容器平台拉取了PyTorch-2.x-Universal-Dev-v1.0镜像,启动命令很简单。这里以Docker为例:

docker run -it --gpus all \
  -p 8888:8888 \
  -v ./data:/workspace/data \
  pytorch-universal-dev:v1.0

这个命令做了几件事:

  • --gpus all:启用所有GPU(如果你有的话)
  • -p 8888:8888:把容器的8888端口映射到本地,这是JupyterLab的默认端口
  • -v ./data:/workspace/data:把本地的./data目录挂载到容器的/workspace/data,这样你就能在容器里访问本地文件了

容器启动后,终端会显示一个带token的URL,复制到浏览器就能打开JupyterLab界面。

2.2 验证环境是否正常工作

在JupyterLab里新建一个Python笔记本,运行下面这段代码来检查环境:

import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 检查CUDA是否可用
print("CUDA Available:", torch.cuda.is_available())
print("CUDA Device Count:", torch.cuda.device_count())
print("Current Device:", torch.cuda.current_device())
print("Device Name:", torch.cuda.get_device_name(0))

# 检查关键库的版本
print("\nPyTorch Version:", torch.__version__)
print("Pandas Version:", pd.__version__)
print("NumPy Version:", np.__version__)

如果一切正常,你会看到类似这样的输出:

CUDA Available: True
CUDA Device Count: 1
Current Device: 0
Device Name: NVIDIA A800-SXM4-40GB

PyTorch Version: 2.1.0+cu118
Pandas Version: 2.1.4
NumPy Version: 1.26.2

看到CUDA Available: True就说明GPU已经就绪,可以开始干活了。

3. 数据加载与初步探索

3.1 加载Titanic数据集

首先,你需要把Titanic数据集下载到本地。可以从Kaggle下载,或者用Python直接获取。这里假设你已经把train.csv放到了挂载的./data目录下。

在JupyterLab里新建一个单元格,运行:

# 加载数据
df = pd.read_csv('/workspace/data/titanic_train.csv')

# 先看看数据长什么样
print("数据集形状(行数, 列数):", df.shape)
print("\n前5行数据预览:")
print(df.head())
print("\n列名列表:")
print(df.columns.tolist())

输出会显示数据集有891行、12列。列包括:

  • PassengerId:乘客ID
  • Survived:是否幸存(0=否,1=是)
  • Pclass:船舱等级(1=头等舱,2=二等舱,3=三等舱)
  • Name:乘客姓名
  • Sex:性别
  • Age:年龄
  • SibSp:兄弟姐妹/配偶数量
  • Parch:父母/子女数量
  • Ticket:船票编号
  • Fare:票价
  • Cabin:船舱号
  • Embarked:登船港口(C=Cherbourg, Q=Queenstown, S=Southampton)

3.2 了解数据的基本情况

在开始处理之前,我们需要对数据有个整体的了解:

# 查看数据类型和缺失值情况
print("数据类型信息:")
print(df.info())

print("\n数值型特征的统计信息:")
print(df.describe())

print("\n类别型特征的取值分布:")
categorical_cols = ['Survived', 'Pclass', 'Sex', 'Embarked']
for col in categorical_cols:
    print(f"\n{col}的分布:")
    print(df[col].value_counts())

从输出中你会发现几个关键信息:

  • Age有177个缺失值(约20%)
  • Cabin有687个缺失值(约77%)
  • Embarked有2个缺失值
  • Fare的票价差异很大(从0到512)

这些发现会指导我们后续的处理策略。

4. 缺失值处理:不只是简单填充

4.1 分析缺失值的分布

先看看哪些特征有缺失值,缺失了多少:

# 统计每个特征的缺失值数量
missing = df.isnull().sum()
missing_percent = (missing / len(df)) * 100

# 只显示有缺失值的特征
missing_df = pd.DataFrame({
    'Missing Count': missing,
    'Percent (%)': missing_percent
}).sort_values(by='Missing Count', ascending=False)

print("缺失值统计:")
print(missing_df[missing_df['Missing Count'] > 0])

输出结果:

特征缺失数量缺失比例
Cabin68777.1%
Age17719.9%
Embarked20.2%

4.2 针对不同缺失情况的处理策略

对于缺失值,不能一刀切地都用均值或中位数填充。我们需要根据具体情况制定策略:

策略1:直接删除(适用于Cabin) Cabin缺失了77%,如果直接填充会引入大量噪声。但我们可以从已有的信息中提取有用特征:

# 从Cabin中提取船舱甲板信息
df['Deck'] = df['Cabin'].str.slice(0, 1)  # 取第一个字母
print("Deck的取值分布:")
print(df['Deck'].value_counts(dropna=False))

# 将字母映射为数字,缺失值设为0
deck_mapping = {"A":1, "B":2, "C":3, "D":4, "E":5, "F":6, "G":7, "U":8}
df['Deck'] = df['Deck'].map(deck_mapping).fillna(0).astype(int)

# 删除原始的Cabin列
df.drop(columns=['Cabin'], inplace=True)

策略2:智能填充(适用于Age) 年龄的缺失比例适中,而且与其他特征(如船舱等级、性别、票价等)可能有关系。我们可以用机器学习模型来预测缺失的年龄:

from sklearn.ensemble import RandomForestRegressor

# 准备数据:分离有年龄和无年龄的记录
age_notnull = df[df['Age'].notnull()].copy()
age_null = df[df['Age'].isnull()].copy()

# 定义一个特征处理函数
def prepare_features_for_age(df):
    # 从姓名中提取称呼(Mr, Miss, Mrs等)
    df['Title'] = df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
    
    # 合并一些罕见的称呼
    rare_titles = ['Lady', 'Countess', 'Capt', 'Col', 'Don', 'Dr', 
                   'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona']
    df['Title'] = df['Title'].replace(rare_titles, 'Rare')
    df['Title'] = df['Title'].replace('Mlle', 'Miss')
    df['Title'] = df['Title'].replace('Ms', 'Miss')
    df['Title'] = df['Title'].replace('Mme', 'Mrs')
    
    # 将称呼映射为数字
    title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5}
    df['Title'] = df['Title'].map(title_mapping)
    
    # 处理其他特征
    df['Sex'] = df['Sex'].map({'female': 0, 'male': 1})
    df['Embarked'] = df['Embarked'].map({'S': 0, 'C': 1, 'Q': 2})
    
    # 选择用于预测的特征
    features = ['Pclass', 'Sex', 'SibSp', 'Parch', 'Fare', 'Embarked', 'Title']
    return df[features]

# 准备训练数据
X_train = prepare_features_for_age(age_notnull)
y_train = age_notnull['Age']

# 训练随机森林回归模型
rfr = RandomForestRegressor(n_estimators=100, random_state=42, n_jobs=-1)
rfr.fit(X_train, y_train)

# 预测缺失的年龄
X_test = prepare_features_for_age(age_null)
predicted_ages = rfr.predict(X_test)

# 将预测结果填充回原数据
df.loc[df['Age'].isnull(), 'Age'] = predicted_ages

print(f"已填充 {len(predicted_ages)} 个缺失的年龄值")
print(f"填充后Age的统计信息:\n{df['Age'].describe()}")

策略3:众数填充(适用于Embarked) 只有2个缺失值,直接用最常见的值填充:

# 找出Embarked的众数(出现次数最多的值)
embarked_mode = df['Embarked'].mode()[0]
print(f"Embarked的众数是: {embarked_mode}")

# 填充缺失值
df['Embarked'] = df['Embarked'].fillna(embarked_mode)

# 验证是否还有缺失值
print(f"填充后Embarked的缺失值数量: {df['Embarked'].isnull().sum()}")

5. 特征工程:从原始数据到有用特征

5.1 从姓名中提取有用信息

乘客的姓名看起来是文本,但里面藏着有用的信息——称呼(Title)。不同称呼可能反映了社会地位、年龄等信息:

# 我们已经在上面的函数中提取了Title,现在只需要映射一下
# 但为了完整性,这里再处理一次
df['Title'] = df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)

# 合并罕见的称呼
rare_titles = ['Lady', 'Countess', 'Capt', 'Col', 'Don', 'Dr', 
               'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona']
df['Title'] = df['Title'].replace(rare_titles, 'Rare')
df['Title'] = df['Title'].replace(['Mlle', 'Ms'], 'Miss')
df['Title'] = df['Title'].replace('Mme', 'Mrs')

# 查看称呼的分布
print("称呼分布:")
print(df['Title'].value_counts())

# 映射为数字
title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5}
df['Title'] = df['Title'].map(title_mapping)

5.2 创建新的组合特征

有时候,单个特征的信息有限,但组合起来就能发现新的模式:

# 创建家庭规模特征
df['FamilySize'] = df['SibSp'] + df['Parch'] + 1
print("家庭规模分布:")
print(df['FamilySize'].value_counts().sort_index())

# 创建是否独自旅行特征
df['IsAlone'] = (df['FamilySize'] == 1).astype(int)
print(f"独自旅行的乘客比例: {df['IsAlone'].mean():.2%}")

# 创建票价人均特征(考虑家庭规模)
df['FarePerPerson'] = df['Fare'] / df['FamilySize']

5.3 处理类别特征

机器学习模型通常需要数值输入,所以我们需要把文字转换为数字:

# 性别映射
df['Sex'] = df['Sex'].map({'female': 0, 'male': 1})

# 登船港口映射
df['Embarked'] = df['Embarked'].map({'S': 0, 'C': 1, 'Q': 2})

# 检查转换结果
print("性别转换后:")
print(df['Sex'].value_counts())
print("\n登船港口转换后:")
print(df['Embarked'].value_counts())

5.4 删除无用特征

有些特征对预测没有帮助,或者信息已经被其他特征包含,可以删除:

# 删除原始的特征
columns_to_drop = ['Name', 'Ticket', 'PassengerId']
df.drop(columns=columns_to_drop, inplace=True, errors='ignore')

print("处理后的特征列表:")
print(df.columns.tolist())
print(f"\n最终数据形状:{df.shape}")

6. 数据可视化:用图形理解数据

6.1 年龄与生存率的关系

让我们用图形来看看不同年龄段的生存情况:

plt.figure(figsize=(12, 5))

# 子图1:年龄分布直方图
plt.subplot(1, 2, 1)
plt.hist(df['Age'], bins=30, edgecolor='black', alpha=0.7)
plt.xlabel('Age')
plt.ylabel('Count')
plt.title('Age Distribution')
plt.grid(True, alpha=0.3)

# 子图2:按生存状态分组的年龄分布
plt.subplot(1, 2, 2)
survived = df[df['Survived'] == 1]['Age']
not_survived = df[df['Survived'] == 0]['Age']

plt.hist([survived, not_survived], 
         bins=20, alpha=0.7, label=['Survived', 'Not Survived'], 
         color=['green', 'red'], edgecolor='black')
plt.xlabel('Age')
plt.ylabel('Count')
plt.title('Age Distribution by Survival Status')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

从图中可以看到,儿童(特别是10岁以下)的生存率相对较高,这符合"妇女儿童优先"的救援原则。

6.2 船舱等级与生存率的关系

plt.figure(figsize=(10, 6))

# 计算每个船舱等级的生存率
survival_by_class = df.groupby('Pclass')['Survived'].mean()

# 创建条形图
bars = plt.bar(survival_by_class.index, survival_by_class.values, 
               color=['gold', 'silver', 'brown'], edgecolor='black', linewidth=2)

# 添加数值标签
for bar, rate in zip(bars, survival_by_class.values):
    height = bar.get_height()
    plt.text(bar.get_x() + bar.get_width()/2., height + 0.01,
             f'{rate:.1%}', ha='center', va='bottom', fontsize=12)

plt.xlabel('Passenger Class (1=Upper, 2=Middle, 3=Lower)', fontsize=12)
plt.ylabel('Survival Rate', fontsize=12)
plt.title('Survival Rate by Passenger Class', fontsize=14, fontweight='bold')
plt.xticks([1, 2, 3], ['First Class', 'Second Class', 'Third Class'])
plt.ylim(0, 0.7)
plt.grid(True, alpha=0.3, axis='y')

plt.show()

这个图清楚地显示了一等舱乘客的生存率(约63%)远高于三等舱(约24%),社会阶层的影响非常明显。

6.3 特征相关性分析

import seaborn as sns

# 计算相关系数矩阵
correlation_matrix = df.corr()

plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', 
            center=0, square=True, linewidths=1, cbar_kws={"shrink": 0.8})
plt.title('Feature Correlation Matrix', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()

# 查看与生存率最相关的特征
print("与生存率最相关的特征(绝对值):")
survival_corr = correlation_matrix['Survived'].abs().sort_values(ascending=False)
print(survival_corr[1:6])  # 跳过Survived自身

从相关性矩阵可以看到,性别(Sex)与生存率的相关性最高(女性生存率更高),其次是船舱等级(Pclass)和票价(Fare)。

7. 数据标准化与PyTorch张量转换

7.1 分离特征和标签

在开始训练模型之前,我们需要把数据分成特征(X)和标签(y):

# 分离特征和标签
X = df.drop('Survived', axis=1)
y = df['Survived'].values

print(f"特征矩阵形状: {X.shape}")
print(f"标签向量形状: {y.shape}")
print(f"幸存比例: {y.mean():.2%}")

7.2 标准化数值特征

不同特征的数值范围差异很大(比如年龄0-80,票价0-512),这会影响模型的训练效果。我们需要进行标准化:

from sklearn.preprocessing import StandardScaler

# 选择数值型特征进行标准化
numeric_features = ['Age', 'Fare', 'SibSp', 'Parch', 'FamilySize', 'FarePerPerson']
scaler = StandardScaler()

# 标准化数值特征
X_scaled = X.copy()
X_scaled[numeric_features] = scaler.fit_transform(X[numeric_features])

print("标准化后的特征统计:")
print(X_scaled[numeric_features].describe().loc[['mean', 'std']])

标准化后,这些特征的均值接近0,标准差为1,这样模型训练会更稳定。

7.3 转换为PyTorch张量

PyTorch使用张量(Tensor)作为基本数据结构,我们需要把数据转换过去:

# 转换为PyTorch张量
X_tensor = torch.tensor(X_scaled.values, dtype=torch.float32)
y_tensor = torch.tensor(y, dtype=torch.long)  # 分类问题用long类型

print(f"特征张量形状: {X_tensor.shape}")
print(f"标签张量形状: {y_tensor.shape}")
print(f"特征张量数据类型: {X_tensor.dtype}")
print(f"标签张量数据类型: {y_tensor.dtype}")

# 查看前5个样本
print("\n前5个样本的特征:")
print(X_tensor[:5])
print("\n前5个样本的标签:")
print(y_tensor[:5])

7.4 划分训练集和验证集

我们不能用所有数据来训练,需要留出一部分作为验证集:

from torch.utils.data import DataLoader, TensorDataset, random_split

# 创建数据集
dataset = TensorDataset(X_tensor, y_tensor)

# 划分训练集和验证集(80%训练,20%验证)
train_size = int(0.8 * len(dataset))
val_size = len(dataset) - train_size
train_dataset, val_dataset = random_split(dataset, [train_size, val_size])

print(f"训练集大小: {len(train_dataset)}")
print(f"验证集大小: {len(val_dataset)}")

# 创建数据加载器
batch_size = 32
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)

print(f"训练批次数量: {len(train_loader)}")
print(f"验证批次数量: {len(val_loader)}")

# 查看一个批次的数据
for batch_X, batch_y in train_loader:
    print(f"一个批次的特征形状: {batch_X.shape}")
    print(f"一个批次的标签形状: {batch_y.shape}")
    break

8. 总结:从数据到模型的完整流程

通过这个完整的Titanic数据预处理流程,我们不仅学会了如何使用PyTorch-2.x-Universal-Dev-v1.0镜像,更重要的是掌握了一套通用的数据处理方法。让我们回顾一下关键步骤:

数据处理的核心流程

  1. 数据加载与探索:了解数据的基本情况,发现潜在问题
  2. 缺失值处理:根据缺失比例和特征重要性,选择删除、填充或提取策略
  3. 特征工程:从原始数据中提取有用信息,创建新特征
  4. 数据可视化:用图形理解数据分布和关系
  5. 数据转换:标准化数值特征,编码类别特征
  6. 张量转换:转换为PyTorch可处理的格式
  7. 数据划分:分为训练集和验证集,创建数据加载器

PyTorch-2.x-Universal-Dev-v1.0镜像的优势

  • 开箱即用:无需手动安装PyTorch、CUDA、Jupyter等工具
  • 依赖齐全:预装了数据处理、可视化、开发环境全套工具
  • 国内源优化:配置了阿里云和清华源,下载速度快
  • 轻量化设计:去除了冗余缓存,镜像体积小
  • 适合快速开发:特别适合原型验证、教学演示、小型项目

实际应用建议

  1. 对于大型项目:可以基于这个镜像构建自己的专属镜像,添加项目特定的依赖
  2. 对于团队协作:使用统一镜像可以确保环境一致性,避免"在我机器上能跑"的问题
  3. 对于教学演示:学生可以直接使用,跳过繁琐的环境配置,专注于学习算法
  4. 对于快速验证:有了这个环境,你可以快速测试各种数据处理方法和模型架构

数据预处理是机器学习项目中耗时最长但最重要的环节。好的数据预处理不仅能提升模型性能,还能帮助你更好地理解业务问题。希望这个实战教程能为你提供一个清晰的参考框架。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐