用 Kev 做自训练时,如果训完效果差,先别怀疑学习率、batch size 或网络结构。常见的前置原因在数据侧:样本量不够、标签脏、特征里混进了未来信息。这三类问题在训练前都能通过统计和切分脚本查出来,代价远低于反复调参。
判断方向:先用脚本量化数据,再决定是否开训。数据量按任务类型粗估,标签做缺失、错位、不平衡三项统计,特征按时间切分检查是否含未来信息。适用场景是自己整理数据喂给 Kev 自训练;操作动作是跑统计脚本和切分脚本;验证方式是看各集合标签分布是否接近、重复运行初始 loss 是否稳定;风险边界是样本量估算只是下限参考,不保证训出可用模型,仍需结合业务确认。
按任务类型估算最小数据量,而不是直接开训
样本太少时,模型往往连决策边界都学不到,调参只能放大噪声。可以先按任务类型做粗略下限估算,再决定是否补数据。下面的数量级是经验区间,不是 Kev 的硬性要求,需要结合你的特征维度和类别数调整。
- 分类任务:每个类别至少几十到几百条,且尽量均衡。粗略下限可以按“类别数 × 每类最少样本数”估算,比如 5 类、每类至少 200 条,就是约 1000 条起步。类别越多、边界越细,下限越高。
- 回归任务:按特征数量估算,经验上样本数至少是特征数的若干倍,避免过拟合。若特征是几十维,千级别样本是常见起点,但目标值波动大时需要更多。
- 序列决策任务:不能只看单条样本数,要看完整轨迹或 episode 数,以及每条轨迹的长度。通常需要足够多条独立轨迹来覆盖不同状态,轨迹太少时模型只会记住少数路径。
估算时可以先统计每个类别的样本数、每条轨迹的长度分布,再对照上面的下限。如果明显偏低,优先补数据或收窄任务范围,而不是开训后靠调参补救。
检查标签是否有缺失、错位和类别极不平衡
标签问题在训练前暴露最省事。可以用 pandas 做三项统计:缺失值、标签分布、重复样本。把下面的骨架存成 check_labels.py,替换列名后直接跑。
import pandas as pd
# 替换:文件路径与标签列名
df = pd.read_csv("train.csv")
label_col = "label"
# 1. 缺失值统计
print("缺失值统计:")
print(df.isna().sum())
print("标签缺失条数:", df[label_col].isna().sum())
# 2. 标签分布
print("标签分布:")
print(df[label_col].value_counts(dropna=False))
print("最小类占比:", df[label_col].value_counts(normalize=True).min())
# 3. 重复样本
feature_cols = [c for c in df.columns if c != label_col]
dup = df.duplicated(subset=feature_cols, keep=False)
print("特征重复条数:", dup.sum())
print(df[dup].sort_values(feature_cols).head(10))
# 4. 检查同一特征对应多个标签(潜在错位)
conflict = df.groupby(feature_cols)[label_col].nunique()
print("同特征多标签组数:", (conflict > 1).sum())
跑完后按结果决定动作:标签缺失条数多时,先确认是采集漏标还是导出错位;最小类占比明显偏低时,考虑补样或合并类别;同特征多标签的组需要人工抽查,往往是标注规范不一致。验证方式就是对照统计数字和抽查样本,确认清洗前后分布变化合理,再进入训练。
确认输入特征和决策输出在时间上无泄漏
时间泄漏会让训练指标虚高,上线后效果崩掉。核心检查两点:特征是否用到了预测时刻之后的信息,以及切分是否按时间而不是随机打乱。
先列出每个特征的采集时间点,凡是晚于决策时刻的特征都要标记出来。常见问题包括用了未来统计的聚合值、用了事后才产生的状态字段。接着按时间切分数据,脚本骨架如下。
import pandas as pd
# 替换:时间列名与切分比例
df = pd.read_csv("train.csv")
time_col = "event_time"
df[time_col] = pd.to_datetime(df[time_col])
df = df.sort_values(time_col).reset_index(drop=True)
train_ratio, valid_ratio = 0.7, 0.15
n = len(df)
train_end = int(n * train_ratio)
valid_end = int(n * (train_ratio + valid_ratio))
train = df.iloc[:train_end]
valid = df.iloc[train_end:valid_end]
test = df.iloc[valid_end:]
print("训练集时间范围:", train[time_col].min(), train[time_col].max())
print("验证集时间范围:", valid[time_col].min(), valid[time_col].max())
print("测试集时间范围:", test[time_col].min(), test[time_col].max())
验证方式:确认训练集的最大时间早于验证集的最小时间,验证集早于测试集,且各集合的标签分布不要偏离太远。如果时间跨度大、分布漂移明显,可以先用最近一段数据做验证,而不是简单按比例切。
把训练集、验证集、测试集按任务目标划分
切分方式要匹配真实决策场景。如果数据有时间顺序、上线后也是按时间到来预测,就用时间切分;如果样本彼此独立、没有时序关系,才考虑随机切分。配置示例如下。
# 随机切分配置示例(适用于独立同分布样本)
from sklearn.model_selection import train_test_split
train, temp = train_test_split(df, test_size=0.3, random_state=42, stratify=df["label"])
valid, test = train_test_split(temp, test_size=0.5, random_state=42, stratify=temp["label"])
# 时间切分配置示例(适用于时序数据)
# 见上一节:先按时间列排序,再按比例或按日期边界切分
验证方式是检查各集合标签分布是否接近。对分类任务,可以打印每个集合的 value_counts(normalize=True);对回归任务,对比目标值的均值和分位数。如果验证集和训练集分布差异大,说明切分方式或数据本身有问题,先别开训。
用固定随机种子跑一次基线训练
在调参之前,先拿到一个可复现的基线。设置随机种子、记录配置和初始 loss,这样后续每次改动都有参照。骨架如下,替换成你的训练入口和数据集即可。
import random
import numpy as np
import json
# 替换:训练框架对应的种子接口
SEED = 42
random.seed(SEED)
np.random.seed(SEED)
config = {
"seed": SEED,
"data_path": "train.csv",
"label_col": "label",
"train_ratio": 0.7,
"valid_ratio": 0.15,
"batch_size": 32,
"lr": 1e-3,
"epochs": 10,
}
with open("config.json", "w") as f:
json.dump(config, f, ensure_ascii=False, indent=2)
# 训练循环内记录首个 epoch 的初始 loss
# 伪代码:for epoch in range(config["epochs"]):
# loss = train_one_epoch(...)
# if epoch == 0:
# print("初始 loss:", loss)
验证方式:用相同配置重复运行两次,初始 loss 和首轮指标应该相同或非常接近;如果差异明显,检查是否还有未固定的随机源,比如数据加载顺序、框架层的随机增强。基线稳定后,再开始调学习率、batch size 这类参数,才有比较意义。