尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

数据预处理全攻略:表格、文本、图像与时序数据清洗实战

数据预处理全攻略:表格、文本、图像与时序数据清洗实战 先说一个我这些年反复撞过墙的结论很多项目最后不是死在模型上而是死在数据上。你高高兴兴从网上下载一份公开数据集或者从业务系统里导出一批 新鲜 数据什么清洗都不做就直接塞进模型第一轮跑完效果差得离谱然后开始调参、换网络结构、加正则化折腾到半夜最后猛然回头看一眼数据——缺失值占了三分之一类别严重不平衡有几张图像甚至根本就是损坏文件。这种事我在不同团队里见得太多了。所以这篇东西我不打算讲高深理论就实打实聊聊数据集预处理这件事。从表格数据、文本数据、图像数据到时序类和专业领域数据集像 KITTI、MNIST、DEAP、WM-811K、FastMRI 这些被问得比较多的公开数据集我把平时最常用的那套处理流程、代码片段和踩过的坑一起整理出来。不管你是刚入门准备跑通 YOLO 训练自己的数据集还是已经在做语言模型相关的文档加载与切片这篇应该都能给你一些可以直接拿去用的东西。1. 数据预处理到底在解决什么问题1.1 模型的上限由数据质量决定很多人以为模型效果不好就是模型不行实际上数据决定了模型效果的上限模型只是去逼近这个上限。一个 200 层的 Transformer喂进去的是充满缺失值、单位不一致、标注互相矛盾的原始数据它依然只会学到一堆垃圾规律。这跟做菜一个道理洗菜、切菜、配菜这些准备工作没人看得见但食材本身不处理好再好的大厨也没法做出像样的菜。预处理的核心目标可以拆成四点统一格式让不同来源的数据能被同一个模型读取清理异常把缺失值、重复样本、明显错误的记录处理掉调整分布做标准化、归一化或类别平衡让模型训练更稳定特征提取从原始数据中整理出更有表达力的信息。这四个目标不是流水线式地执行一遍就完事而是要基于对数据本身的理解反复调整。1.2 不同数据类型的预处理目标差异关键在于不同类型的数据干净 的定义完全不同。表格数据里的缺失值可能是某一行找不到记录文本数据里的噪声可能是 HTML 标签、全角空格、乱码符号图像数据里的问题可能是尺寸不一致、通道数不同、标注坐标出界时间序列里可能会遇到传感器掉线导致的长时间空白。用一个统一的 数据清洗脚本 去处理所有数据是新手最容易犯的错误。所以这篇文章接下来的结构就按数据类型展开每一种我都会直接给可落地的代码和判断依据。2. 表格数据的清洗、补全与标准化从一张 CSV 到模型能用的输入2.1 读文件阶段就要踩住的坑表格数据最常见的载体就是 CSV但很多人第一行pd.read_csv(data.csv)就翻车了。中文环境下最容易遇到的是编码问题有的文件是 UTF-8 带 BOM有的是 GBK还有极端的 GB18030用 pandas 读出来直接乱码。我现在已经习惯在读取时先试编码或者直接统一转成 UTF-8。读文件的几个参数值得注意别偷懒import pandas as pd df pd.read_csv( data.csv, encodingutf-8-sig, # 带 BOM 的 UTF-8 也能读 sep,, # 有些导出文件用分号或制表符 na_values[N/A, null, --, ], nrows200, # 先读前 200 行看看长什么样 )na_values这一步很多人会忽略。原始数据里可能是用-表示缺失如果你不告诉 pandas它就把-当成一个合法字符串。我建议一开始就维护一张缺失占位符清单避免后续统计缺失值的时候漏掉。文件读进来以后先别急着处理。用df.info()看每列类型用df.describe()看数值分布用df.head(10).T直接扫一眼内容。这些操作花不了半分钟但能让你在动手处理前对数据有一个整体概念。2.2 缺失值、重复值与异常值的处理顺序和选择我处理表格数据通常按这个顺序先看缺失再去重最后处理异常。缺失值的处理没有银弹完全取决于业务场景和后续模型。如果这一列 80% 都是缺失我的建议是直接丢弃因为靠 20% 的有效值去脑补缺失部分大概率只会引入噪声。如果是少量缺失数值型列可以用中位数或均值填充类别型列可以用众数填充。如果数据有时序属性比如传感器数据优先用df.interpolate()做线性插值这样可以保留趋势。这里要特别强调一点填充缺失值用的统计量只能在训练集上计算不能让测试集参与进来。这个问题我放到后面数据泄漏部分细说它是整个预处理里最隐蔽的坑。重复值也要分情况。完全重复的行直接drop_duplicates()删掉但如果是主键相同、其他字段不同的 半重复 数据你需要自己定规则比如保留离当前时间最近的一条或者保留信息最完整的一条。盲目丢弃可能把正常业务记录也删了。异常值检测在表格数据里最常用的是 z-score 和 IQR。z-score 是把数据标准化后看哪些点偏离均值超过 3 个标准差适合近似正态分布的数据IQR 则更稳健用四分位数来界定正常范围Q1 df[score].quantile(0.25) Q3 df[score].quantile(0.75) IQR Q3 - Q1 lower Q1 - 1.5 * IQR upper Q3 1.5 * IQR outliers df[(df[score] lower) | (df[score] upper)]但检出异常值后不要急着删。异常值有可能对应的是真实的业务风险事件比如某个极端天气下的发电量异常这反而是模型应该学习的信息。我一般的做法是先看异常样本的原始记录能合理解释的删掉解释不了的就保留让模型自己学。2.3 编码、标准化与数据划分的正确姿势表格里的类别特征不能直接喂给线性模型和神经网络需要做编码。如果类别本身有次序关系比如低/中/高用OrdinalEncoder如果没有顺序用OneHotEncoder。用 pandas 的get_dummies也能做 one-hot但 sklearn 版本的编码器可以保存下来推理时能保证训练和预测的一致性这一段我强烈建议用 sklearn。数值特征的标准化也很关键。StandardScaler减均值除标准差适合多数模型尤其是梯度下降类模型它把特征变成均值为 0、方差为 1 的分布MinMaxScaler缩放到 [0,1]适合已知上下界的数据比如图像像素值。选哪个取决于数据的分布和模型的需求没有绝对的对错但我个人在深度学习任务里更喜欢MinMaxScaler因为输出范围确定配合 sigmoid 系输出时梯度更稳定。到了划分数据集这一步分类任务一定记得用stratify参数保持类别比例from sklearn.model_selection import train_test_split X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, stratifyy, random_state42 )这个stratifyy很重要。不做分层抽样极端情况下测试集里可能连少数类都抽不到后面的指标评估会失真。3. 文本数据加载、清洗与切片的完整规则从 txt/word 到语言模型3.1 txt 与 word 文档的加载方式最近做语言模型相关任务的朋友问得最多的问题有两个txt 文件、word 文件按照什么规则加载预处理以及怎么对文档切片。先说加载。txt 文件最直接但编码依然是个老问题。Windows 上常见 GBKLinux 上常见 UTF-8一个稳妥的做法是用chardet或charset_normalizer先检测编码再读取。对于特别大的文件不要一次性read()到内存用for line in f逐行读处理完了再写回新文件。word 文件分两种。.docx用python-docx读取遍历document.paragraphs就能拿到段落文本.doc这种老格式没有稳定的纯 Python 方案我自己的做法是先用 LibreOffice 批量转成.docx再读libreoffice --headless --convert-to docx *.doc这个命令行在 Linux 和 Windows 上都能跑适合批量处理 PPT、doc 等老 Office 文件。转换这一步会丢失一部分复杂排版但对文本预处理来说通常无所谓。如果文档还包含表格里的内容python-docx默认的paragraphs是拿不到的需要额外遍历document.tables。3.2 文本清洗的常用操作清单文本清洗在 NLP 任务里是个无底洞因为噪声类型实在太多。我用的最多的操作就这几类大家可以直接对照检查自己的文本去掉 HTML 标签re.sub(r[^], , text)去掉控制字符re.sub(r[\x00-\x1f\x7f], , text)比如那些不可见的零宽空格统一空白把多个空格、换行、制表符合并成一个空格全角转半角中文全角符号有时会漏进来统一转半角可以避免分词和匹配时出问题清理 BOM 和零宽字符这类字符不会显示但会真实影响模型训练时的 token 编码这里要提醒一句不要一上来就删停用词。如果任务是语言模型预训练或文本生成删停用词会破坏句子的自然结构模型学不到真实的语言分布如果是文本分类且词表有限去掉的、了、是这类高频停用词可能反而有效。清洗规则必须跟着任务走。3.3 文档切片规则与重叠窗口设计文档切片是准备语言模型训练数据时最受关注的一步。很多人直接text[:512]、text[512:1024]这样硬切结果句子被拦腰截断模型看到的语义非常破碎。我实践下来比较稳的方案是先分句再把句子组装成块同时保留重叠窗口。分句可以用正则按句号、问号、感叹号切分中文要同时兼容英文标点。组装的时候每个块设一个最大长度超过就另开新块并且让新块的开头带上上一块的尾部内容这样前后文信息不会完全切断。代码大致长这样import re from typing import List def clean_text(text: str) - str: text re.sub(r[^], , text) text re.sub(r[\x00-\x1f\x7f], , text) # 控制字符 text re.sub(r\s, , text) return text.strip() def split_sentences(text: str) - List[str]: parts re.split(r(?[。!?])\s*, text) return [p for p in parts if p] def build_chunks(sentences: List[str], max_chars: int 500, overlap: int 50) - List[str]: chunks [] cur for sent in sentences: # 如果句子本身超过上限按字符硬切 while len(sent) max_chars: if cur: chunks.append(cur) cur chunks.append(sent[:max_chars]) sent sent[max_chars:] if cur and len(cur) len(sent) max_chars: chunks.append(cur) cur cur[-overlap:] sent else: cur sent if cur: chunks.append(cur) return chunks在真实语言模型项目里我会用 tokenizer 统计 token 数而不是字符数因为 500 个字符对应的 token 数在中文和英文里差异很大。把len(sent)换成len(tokenizer.encode(sent))即可逻辑不变。切片后的每个 chunk 就相当于一个独立训练样本喂给模型之前还要经过 tokenizer 加上特殊标记和注意力掩码。另外如果文本本身有结构比如 Markdown 标题、章节序号最好先在结构边界处切片再按最大长度做二次合并这样能最大限度保留文档原有的语义段落。纯靠滑动窗口硬写的切法只适合没有明显结构的纯文本。4. 图像数据集的目录组织、标注格式与增强从 MNIST 到 YOLO 自己的数据集4.1 分类任务目录结构、缩放到归一化图像预处理里最简单的就是分类任务。MNIST 这种灰度手写数字数据集处理流程通常是读入 28x28 灰度图把像素值从[0, 255]缩放到[0, 1]或标准化到均值为 0、方差为 1然后转成模型要求的张量格式。如果你在用 PyTorch 做自定义图片分类目录结构可以直接按下面这样组织然后交给torchvision.datasets.ImageFolder读取data/ train/ class_a/ img_001.jpg img_002.jpg class_b/ ... val/ class_a/ class_b/注意ImageFolder按子目录名自动生成类别标签所以目录名不要用中文和特殊字符避免读取时出乱码。训练前一定要看一下所有图片的尺寸和通道数。有的图像是灰度图有的是 RGB还有 PNG 带透明通道如果你不统一处理模型输入就可能遇到形状不一致的报错。稳妥做法是在数据加载器里统一Resize和ToTensor或者预处理时全部转成同一尺寸的 RGB 图。4.2 目标检测与分割标注格式的统一与转换目标检测的数据预处理比分类复杂一个量级因为不仅图像要处理标注框也要跟着处理。现在主流数据集和框架的标注格式五花八门COCO 用 JSONVOC 用 XMLYOLO 用 txtDOTA 用四点坐标KITTI 又有自己的一套。很多项目大部分时间都花在格式转换上。YOLO 训练自己的数据集我最常用的标注格式是 YOLO txt每一行对应一个目标内容是class_id x_center y_center width height其中坐标都是相对图像宽高的比值范围在0~1之间。例如0 0.5 0.5 0.2 0.3表示类别为 0目标中心在图片中心宽度占图片的 20%高度占 30%。用 labelimg 或 x-anylabeling 标注完通常会自动生成 YOLO 格式的 txt。我每次拿到标注文件第一件事是写一个脚本检查所有坐标是否越界比如出现负数或者宽度超过图片宽度。这类问题在手工标注时非常常见在 yaml 配置里修改图片尺寸之前必须先修正。COCO 格式则完全不同它的 bbox 是[x, y, width, height]x 和 y 是左上角像素坐标不是归一化值所以如果要做转换需要除以图片宽高。如果训练的数据集是目标旋转检测比如 DOTA 分类下的遥感飞机、船舶标注是四点坐标得转换为旋转框表示这就要用 MMRotate 提供的 DOTA 转脚本。每次转换后我都建议做一次反向可视化把标注框画在图上确认转换没有出偏差。4.3 数据增强的同步问题与边界条件数据增强是图像任务里不可或缺的一步但它的核心难点不是用什么增强而是怎么保证增强后的标注也跟着同步变化。分类任务比较简单翻转、裁切、颜色抖动随便上因为有torchvision.transforms直接处理张量即可。检测任务就不一样了你随机翻转了一张图图上目标的中心 x 坐标会变成1 - x如果标注没有同步更新模型会在训练时和标签打架。我的建议是检测任务直接使用albumentations库它能帮你同步处理图像和 bbox/掩码。它的Compose里有一个bbox_params参数用来定义标注的格式是pascal_voc还是yolo它会自动完成坐标同步变换import albumentations as A transform A.Compose([ A.RandomResizedCrop(width640, height640, scale(0.7, 1.0)), A.HorizontalFlip(p0.5), A.ColorJitter(brightness0.2, contrast0.2, p0.5), ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels]))还要注意增强的参数不能太过分。我在一个医学图像分割项目里试过加入大量旋转和弹性形变结果模型在真实数据上的效果反而更差因为正常扫描的图像根本不会出现那种形变。数据增强的目的是模拟真实分布的变化不是制造数据集里没有的分布。4.4 用 YOLO 训练自己数据集时我建议的预处理顺序如果你是从零开始准备自己的 YOLO 数据集我建议按下面这个顺序走一遍能少踩很多坑收集图片统一格式和命名避免中文路径和特殊字符。标注工具任选导出 YOLO 格式。按场景划分train/val分类任务可以随机分检测任务尽量保证同一场景的图不分散到两个集合里。写data.yaml指定train、val路径、类别数量和类别名。写一个简单的检查脚本统计每张图的 bbox 数量、类别分布画出几张抽查图确认标注和图像对齐。确认没有任何越界框、空标注图再开始训练。空标注图这个坑尤其隐蔽。一张图里如果没有任何目标YOLO 的 txt 就是一个空文件很多数据加载器读到空文件会直接报错或者静默跳过。我在训练初期排查了很久才发现是这个原因。医学影像和遥感影像里面还有几个额外的问题。CT 图像是 HU 值需要设置窗宽窗位再归一化比如腹部常用的窗中心 40、窗宽 400把[center - width/2, center width/2]范围内的值截断再映射到0~1。遥感影像通常是超大图不能直接喂给模型需要滑窗切块而且注意标注框要跟着窗口一起切。息肉分割、水下管道裂缝这类分割项目背景像素常常占了 95% 以上预处理时要考虑类别权重或者采样策略不然模型的 loss 会被大背景区域彻底主导。5. 时间序列与专业场景数据的特殊处理光伏、风电、生理信号与医学影像5.1 时间序列的切片与防泄漏光伏、风力发电、卫星信号信噪比这类数据本质上都是带时间戳的连续测量值。预处理和表格数据最大的区别在于不能随机打乱样本因为样本之间存在严格的时间顺序模型需要学习的是时间依赖关系。时间序列项目里我一般会做这几件事先去缺失传感器掉线的常见缺失不多就直接线性插值缺失成片的要考虑用前后同期的历史均值填充再重采样到固定频率比如把采集间隔不统一的原始记录重采样成 15 分钟一个点最后做滑动窗口切样本用过去一段时间预测未来一段时间。平滑和去趋势也是常规操作。风力发电数据常常有强烈的日周期和季节周期有时候简单的移动平均就能把噪声压下去但做这些操作时千万注意如果后续用深度学习模型不要在训练前就对全量数据做滤波或归一化否则验证集的信息会被 过滤 出来。所有统计量的计算都必须在训练集上完成验证集和测试集只能被 transform。5.2 专业领域数据集的常见特殊要求不同专业领域的数据集有各自的脾性有些看起来像图像数据但处理方式完全不同。DEAP数据集存放的是脑电和生理信号s01.mat这类 mat 文件里每个 trial 是一段 60 秒、128Hz 采样的多通道信号。预处理时要做通道选择、去除眼电伪迹、基线校正和滤波尤其是 4~45Hz 频段然后降采样或分段提取特征。这里的核心是理解生理信号的意义不是套一个通用滤波器就能完事。WM-811K是晶圆图数据每个样本是一张二值图表示同一片晶圆上哪些芯片合格哪些不合格。它的缺失值很特殊晶圆边缘本来就没有芯片这些位置并不是 数据缺失而是真实的空间结构。如果按一般表格数据的缺失值处理逻辑去填充会直接破坏空间模式模型就没法学到正确的失效模式了。FastMRI这类医学成像数据很多是把原始 k-space 数据直接给出来需要先从 k-space 重建图像。k-space 里存的是复数值常见预处理是裁到中心区域比如320x320然后把复数拆成实部虚部两个通道或者转成幅度和相位图。这里不能像自然图像那样直接除以 255因为数据范围完全不一样。DeepMIMO是无线通信领域常用的信道数据集存的是 MIMO 信道矩阵同样涉及复数处理。读取.mat文件后需要把矩阵 reshape 成特定维度把实部和虚部分开再做一个 min-max 归一化。不同场景下的数据分布差异大建议每个场景单独计算归一化参数而不是合并计算。做这类专业领域任务最重要的建议就一句话先去读数据集的说明文档搞清楚每个维度、每个值、每个缺失符号的实际物理意义然后针对性设计预处理流程。不要拿一个通用脚本就去处理所有 mat 文件。6. 预处理中的常见翻车现场与我的避坑习惯6.1 数据泄漏最隐蔽也最致命数据泄漏在所有预处理坑里排第一。最典型的例子就是先对整个数据集做标准化然后再切训练集和测试集。这时测试集的均值、方差已经参与过训练数据的调整测试集就不再是全新的数据最终评估指标会虚高真实上线时马上现原形。正确的做法是先切分再在训练集上 fit然后在训练集、验证集、测试集上分别 transform。这一点在表格数据、时间序列和专业领域数据里都适用。时间序列里还要注意不能随机拆分训练集和验证集应该按时间顺序切分保证验证集的时间戳永远晚于训练集否则模型可能在验证集上偷看到了 未来。6.2 文件与编码问题读不进来就谈不上预处理文件读不进来的情况比想象中多得多。图片路径里有中文或空格cv2.imread会返回NoneCSV 文件里有隐藏的\ufeffBOM 字符列名的第一个列会多出一个看不见的字符文本文件是 GBK 编码却按 UTF-8 读直接乱码还有那些从 Excel 导出的数据某列混入了一个过长数字被 pandans 当成 float精度丢失。我的对策是每个新数据集开始处理前先写一个环境校验脚本把所有图片路径和标注路径遍历一遍确认文件存在、格式正确、编码正确。这个脚本本身很简单却能省下后面排查问题的一整晚。6.3 可复现性把预处理固化成脚本最后一个习惯建议所有预处理步骤都写成脚本参数放在配置文件里不要依赖 notebook 里手动按顺序执行的 cell。因为一旦换机器、换同事、隔几天再跑结果可能不一样也可能完全跑不出同样效果。我会把每次处理后的数据版本另存一份比如data_processed_v1.parquet并记录每一步用了什么参数。这样模型出了问题还能回溯排查是不是预处理环节造成的。此外随机种子一定要固定random.seed(42)、np.random.seed(42)、torch.manual_seed(42)一个都别落下。用于数据划分、K折交叉验证、数据增强的随机操作都要保证可复现这是所有严谨实验的底线。最后再分享一个小技巧每次做完预处理我习惯随机挑几条样本把处理后的结果打印出来或者可视化展示一下。图像就画 bounding box文本就打印切片后的 chunk表格就打印统计摘要。这一步只需要一分钟但能让你立刻发现处理过程中有没有出错。毕竟预处理做得对不对别等到模型训练完再来后悔。
返回列表