3. 数据加载与基础处理(cuDF DataFrame)
cuDF 说白了就是 GPU 版的 pandas。API 很接近,但底层跑在显存上。我刚开始用的时候,还总觉得它跟 pandas 一模一样,结果某天直接把显存撑爆了…… 嗯,从那以后我就老老实实先看数据类型了。
这一节,我们聊聊怎么把数据塞进 cuDF,并做最基础的处理。后续所有 cuML 算法,都依赖这里清洗干净的 DataFrame。
3.1 加载数据——read_csv / read_parquet
cuDF 支持 CSV、Parquet、ORC、JSON 等格式。我个人习惯,如果原始文件是 Parquet,优先用 read_parquet,因为列式存储加上 GPU 并行,加载速度比 CSV 快 5~10 倍。CSV 的话,记得指定数据类型,否则默认推断会吃很多显存。
import cudf
# 加载 CSV(推荐先指定 dtypes)
df = cudf.read_csv('data.csv',
dtype={'feature1': 'float32',
'feature2': 'int32'})
# 加载 Parquet(更省显存)
df = cudf.read_parquet('data.parquet')
chunksize 参数,分批读取,避免一次性把显存占满。
3.2 看一眼数据——基础元信息
拿到 DataFrame 后,我通常会先做三件事:看头、看信息、看分布。这和 pandas 完全一致,但速度更快。
df.head(10) # 前10行,确认数据样子
df.info() # 每列的 dtype、非空数、显存占用
df.describe() # 数值列的统计摘要
你想想看,info() 里会显示显存占用(Memory usage),这是 pandas 没有的。如果那一行数字比其他列大很多,说明该列 dtype 可能太宽了(比如默认成了 float64),需要降到 float32。
3.3 缺失值处理——别让 NaN 偷偷捣乱
cuML 的很多算法不接受缺失值。所以这一步躲不掉。我习惯先看每列的缺失比例:
df.isna().sum() / len(df)
然后根据业务选择策略:
- 数值列:用
fillna()填充均值/中位数,或者用bfill/ffill - 类别列:填充众数,或者加一个 “missing” 类别
- 缺失极少的行:直接
dropna()扔掉(别超过总样本的 1%)
# 数值列填充均值
df['age'] = df['age'].fillna(df['age'].mean())
# 类别列填充 'unknown'
df['city'] = df['city'].fillna('unknown')
# 某列缺失超过80%,干脆删掉该列
if df['noisy_col'].isna().mean() > 0.8:
df.drop(columns=['noisy_col'], inplace=True)
fillna() 默认返回新副本,会占用双倍显存。可以用 inplace=True 减少临时开销,或者链式操作时小心显存溢出。
3.4 类型转换——省钱又省显存
GPU 对 float32 / int32 最友好。我曾经在项目中,整个 DataFrame 默认全成了 float64,结果显存占用翻倍,训练慢得像蜗牛。原因是 CSV 里有一列带小数,cuDF 推断为 float64,其他列也被自动提升…… 后来我强制指定了 dtype。
# 整型超过2^31才用int64,否则用int32
df['user_id'] = df['user_id'].astype('int32')
# 浮点用float32,除非精度要求极高
df['score'] = df['score'].astype('float32')
# 类别型压缩为category类型
df['gender'] = df['gender'].astype('category')
转换后记得再用 info() 确认一下显存占用。一般能降到原来的 1/3 甚至更低。
3.5 分组与聚合——为特征工程热身
cuDF 的 groupby + agg 性能非常强悍,利用 GPU 并行,几千万行分组只需要几百毫秒。我经常用它来算统计特征。
# 按城市分组,计算平均收入和方差
grp = df.groupby('city')['income'].agg(['mean', 'std'])
# 多个聚合函数
stats = df.groupby('city').agg(
income_mean=('income', 'mean'),
income_std=('income', 'std'),
count=('user_id', 'count')
)
注意:分组后的结果还是 cuDF DataFrame,可以直接喂给 cuML。但 groupby 这个操作本身不会修改原始数据,放心用。
3.6 一句话流程总览
下面这张图总结了从文件到可建模数据的核心步骤。我刚做 GPU 数据 pipeline 时,就是照着这个顺序走的,几乎没出过岔子。
- 加载时提前指定
dtype省显存 - 缺失值必须先处理,否则 cuML 直接报错
- 类型转换是压缩显存的性价比之王
- 分组聚合可以原地返回新 DataFrame,不污染原数据
做完上面这几步,你的数据就基本干净了。接下来可以直接扔进 cuML 做特征学习。记住一个原则:把数据整理得越规整,模型训练就越顺畅——我早期项目里一半的 bug 都出在加载和清洗阶段,后来严格按照这个流程走,几乎没再翻车过。