☰
LLM开发必备:用Pandas搞定80%数据处理难题
2026/10/14 14:31:18 网站建设 项目流程

做 LLM 相关开发这一年多,我最大的感受是:模型架构本身已经不是最耗时间的环节了,真正让人头秃的是数据。不管是微调一个开源底座模型,还是给 RAG 应用准备知识库,又或者是做 SFT 指令数据,每天打交道最多的库就是 Pandas。你要是跟我一样,从初学者的角度把 Pandas 用熟,会发现它几乎能覆盖 LLM 开发中 80% 的数据处理需求。

这篇文章不是给你念 API 文档,而是把我自己在 LLM 项目里用 Pandas 处理数据的完整思路、常用代码片段、以及踩过的坑都整理出来。从最基础的读写数据开始,一路讲到 Token 统计、Embedding 向量管理、训练集构建这些进阶操作。无论你是刚入门想找方向,还是已经写了几个预处理脚本但总觉得不够系统,这篇内容都值得你花二十分钟看完,很多细节我是真金白银换来的教训。

1. LLM 开发为什么绕不开 Pandas

1.1 数据在 LLM 项目里的真实占比

跟你讲个我自己的经历。去年我做了一个垂直领域的对话机器人项目,前前后后大概花了三个月,其中调模型的 Prompt 用了不到一周,真正让我加班到凌晨的,全是在处理数据:把不同渠道收集来的问答记录去重、清洗、格式化,再按业务场景分类打标,最后还要统计各个类别的样本量是不是均衡。

这个比例其实在行业里很常见。模型就像一台发动机,数据就是燃料。你拿劣质燃料去跑,发动机再好也白搭。放在 LLM 开发里就是:数据质量决定了模型表现的上限,Prompt 和微调只是在逼近这个上限而已。

Pandas 之所以在 LLM 开发里这么重要,是因为它的 DataFrame 结构天生适合做表格型数据的处理。而 LLM 的数据,无论是文本语料、指令对、还是向量化之后的 Embedding,本质上都能表达成表格:行是样本,列是特征。比如指令微调数据,每行就是一条指令样本,包含 instruction、input、output 三列;RAG 知识库的数据,每行就是一段文本和它的元信息;评估集数据,每行就是一个测试用例和预期答案。

1.2 从原始文本到训练语料的典型链路

我总结了一下,LLM 项目里数据处理大概走这么一条链路:数据采集 → 数据清洗 → 格式转换 → 统计分析 → 数据拆分 → 质量抽检。

拿我曾经做的那个对话机器人举例。原始数据是从某企业内部系统导出的对话日志,长什么样?有缺失的字段、有重复的问答、有包含敏感信息的记录,还有大量闲聊内容跟业务无关。这种数据直接拿去微调,模型学到的全是噪音。

这时候 Pandas 就派上用场了。我可以用drop_duplicates()去掉完全重复的样本,用dropna()处理缺失值,用str.contains()配合正则把无关内容筛掉,再用groupby()看各个类别的分布情况。整个过程像流水线一样,每一步都在 DataFrame 上操作,清晰又高效。

这个能力在 LLM 开发里尤其稀缺。因为 LLM 的数据处理不像传统机器学习那样,特征工程做好就完事了。它需要你在文本层面做大量精细操作,比如判断两段话是不是语义重复、需要按 Token 数截断超长文本、需要把原始对话改造成指令格式。没有 Pandas,这些工作就得写一堆纯 Python 循环,代码又长又难维护。有了 Pandas,大部分操作变成了一行方法调用。

2. 初学必先掌握的 Pandas 核心操作

2.1 读取数据:别只会 read_csv

我见过很多初学者,数据文件一进来就pd.read_csv()一把梭。LLM 项目里的数据格式其实五花八门,JSON、JSONL、Parquet、Excel 都常见。尤其是 JSONL,在 LLM 数据里太常见了——每条数据一行,格式灵活,适合流式处理。

import pandas as pd # 读取 JSONL,这个在 LLM 项目里是最常见的 df = pd.read_json("sft_data.jsonl", lines=True) # 读取 Parquet,如果你处理的是大规模向量数据 df = pd.read_parquet("embeddings.parquet") # 从 JSON 字符串列展开成多列 df = pd.read_json("raw_data.json")

这里有个容易踩坑的地方:JSONL 文件如果嵌套层级很深,直接读进来会得到一堆 dict 对象列。这时候需要配合json_normalize()把嵌套结构展开。我处理过一批第三方 API 返回的数据,每条记录里面套了两层options列表,不展开根本没法分析。

from pandas import json_normalize records = [...] # 原始 JSON 数组 df = json_normalize(records, sep="_")

sep="_"的作用是让嵌套字段的层级路径用下划线连接,比如options_0_text,这样列名不会带点号,后续用df["options_0_text"]访问也方便。

2.2 筛选与清洗:把脏数据挡在模型之前

数据清洗在我眼里是 Pandas 最核心的战场。LLM 数据的脏,跟传统数据的脏还不太一样。它不只是有空值、有重复,还有格式混乱、混入非目标语言、包含 HTML 标签、有超长文本截断了一半等等。

空值和重复值是最基础的,处理起来也简单:

# 丢到全为空的行 df.dropna(how="all", inplace=True) # 子集去重,留下第一条 df.drop_duplicates(subset=["instruction", "output"], keep="first", inplace=True)

但实际项目里,更麻烦的是那些"看起来不一样、其实是一回事"的重复数据。比如问答对里,有人问“怎么退款”,有人问“退款流程是啥”,这两条从字面上看完全不同,但对模型训练来说就是重复样本。这种语义层面的去重,单纯靠 Pandas 做不到,需要引入 Embedding 相似度计算。我的做法是:先用 Pandas 把文本列批量向量化,再用向量相似度矩阵筛掉高相似度的样本。这个后面在 Embedding 管理那节详细说。

清洗这一步还经常用正则配合str方法:

# 去掉 HTML 标签 df["text"] = df["text"].str.replace(r"<[^>]+>", "", regex=True) # 去掉多余的空白字符 df["text"] = df["text"].str.replace(r"\s+", " ", regex=True).str.strip() # 过滤掉包含敏感词的样本(举例:replace 为你的敏感词库) sensitive_words = ["词A", "词B", "词C"] mask = df["text"].str.contains("|".join(sensitive_words)) df = df[~mask]

这里面有个性能优化点值得说:用str.replace()带regex=True比用 Python 原生的re.sub()配合apply()快很多,因为 Pandas 的字符串方法底层是向量化的。数据量上万条之后,apply()和向量化操作的差距非常明显。我一开始习惯用df["text"].apply(lambda x: re.sub(...)),处理 10 万条数据跑了快一分钟,换成str.replace之后几秒钟就搞定了。

2.3 apply 与向量化:从循环思维里跳出来

很多从纯 Python 转过来的初学者,习惯性写for i in range(len(df))这种循环。在 Pandas 里这是最不推荐的做法,除非万不得已。原因很简单:Python 循环逐行访问 DataFrame,每一步都有类型检查、索引开销,效率极低。

正确的方式是用apply()处理逐行逻辑,用向量化操作处理列级逻辑。

# 不推荐:纯粹 Python 循环 for idx, row in df.iterrows(): df.loc[idx, "text_len"] = len(row["text"]) # 推荐:apply 逐行处理 df["text_len"] = df["text"].apply(lambda x: len(x)) # 更推荐:直接 len 函数配合 str df["text_len"] = df["text"].str.len()

在 LLM 数据预处理里,apply()最常见的场景就是:对每条文本做统一处理,比如加 Prompt 模板、拼接上下文、调用 Tokenizer 计算 Token 数。

df["prompt"] = df["input"].apply(lambda x: f"请回答以下问题:{x}") def count_tokens(text): # 这里调用你的 tokenizer return len(tokenizer.encode(text)) df["token_count"] = df["text"].apply(count_tokens)

要注意的是,apply()传进去的函数里不要做全局状态的修改,否则并行化的时候会出问题。如果你想用多进程加速,Pandas 的apply()支持传入args参数,或者你可以试试swifter这个库,它能在apply()基础上自动选择并行方式。

3. LLM 场景下的高级数据处理技巧

3.1 Token 统计与成本预估

这个是我觉得很多教程都不会讲、但实际项目里特别实用的点。做 LLM 开发,Token 是什么?就是模型处理文本的最小单位,大概可以理解成"半个词"。你调用 API、你微调模型、你做推理,都是按 Token 计费的。所以提前估算 Token 数量,直接关系到你的项目预算和方案选型。

用 Pandas 做 Token 统计很方便。先给每条文本算 Token 数,然后汇总统计:

# 假设 df 里有一列 text 是你要发给模型的内容 df["token_count"] = df["text"].apply(lambda x: len(tokenizer.encode(x))) # 总 Token 数、平均 Token 数、最大最小 total_tokens = df["token_count"].sum() mean_tokens = df["token_count"].mean() max_tokens = df["token_count"].max() min_tokens = df["token_count"].min()

当时我做 RAG 知识库的时候,要评估 5 万篇文档的向量化成本。用 Pandas 统计完,发现平均每篇文档 1200 Token,总计 6000 万 Token。按当时的 Embedding 接口价格一算,光向量化一次就要花不少钱,而且后续增量更新也要持续投入。这个数字直接影响了技术选型——后来我们改用更经济的模型,并且做了分段向量化,大大压缩了成本。

Token 均值还能帮你判断 Prompt 设计是否合理。我有一次调试一个分类任务,发现输入 Prompt 平均 1500 Token,但其中 90% 都是背景说明文本,真正需要模型判断的只有最后两句话。这种设计既费钱又容易让模型注意力分散。用 Pandas 按场景分组统计 Token 分布,能很直观地发现问题。

3.2 长文本处理与截断策略

LLM 有上下文窗口限制,比如有的模型是 4K、8K、32K。实际业务里的文档经常超过这个长度。那个经典的问题就来了:直接截断会丢失信息,不截断又放不下。

我用 Pandas 做过一套滑动窗口式的长文本拆分方案。核心思路是:把一条长文本按固定 Token 数量切成多段,每段之间保留一定的重叠区域,避免切在语义边界上。这样既能保证每段都在上下文窗口内,又能保持文本的连贯性。

def split_long_text(text, chunk_size=800, overlap=100): tokens = tokenizer.encode(text) chunks = [] start = 0 while start < len(tokens): end = min(start + chunk_size, len(tokens)) chunk = tokenizer.decode(tokens[start:end]) chunks.append(chunk) if end == len(tokens): break start = end - overlap return chunks # 每条原始文档展开成多行 df_exploded = df.assign(chunks=df["text"].apply(split_long_text)).explode("chunks")

里面用了一个 Pandas 很有用的功能:explode()。它能把一个列表类型的单元格拆成多行,其他列自动复制。这个操作在 LLM 数据处理里非常常用。比如你有 5 万篇文章,切完之后变成 12 万个片段,每行一个片段。后续做 Embedding、建索引,都是在这张展开后的表上进行。

还有一点要注意:不同 Tokenizer 的切分方式不同,tokenizer.encode()返回的是 Token ID 列表,decode()把 Token ID 列表还原成文本。这两个方法配对使用才能保证切出来的片段是"合法"的文本,不会出现半个词的情况。

3.3 Embedding 向量管理

向量检索是 RAG 的基础。每个文本片段经过 Embedding 模型变成一串浮点数,通常是几百到几千维。Pandas 处理向量数据的方式跟普通列不太一样。

我的常见做法是:把向量存在一个 list 类型的列里,然后利用 Pandas 的apply()做运算。

# 假设 df 有文本片段和对应的向量列 df["embedding"] = df["text"].apply(lambda x: embed_model.encode(x)) # 计算每个向量跟某个查询向量的余弦相似度 query_vec = embed_model.encode("怎么申请退款") def cosine_sim(v): dot = sum(a * b for a, b in zip(v, query_vec)) norm_v = sum(a * a for a in v) ** 0.5 norm_q = sum(a * a for a in query_vec) ** 0.5 return dot / (norm_v * norm_q) df["similarity"] = df["embedding"].apply(cosine_sim) df.sort_values("similarity", ascending=False, inplace=True)

这个写法数据量小的时候没问题,比如几千条。但是向量数据一上万,用 Python 循环算相似度就很吃力了。我处理过 20 万条向量,一个查询跑下来要几分钟,完全没法接受。

后来我的做法是:向量列在 Pandas 里只做存储和管理,真算相似度的时候,把整个向量列转成 NumPy 矩阵,一次矩阵乘法搞定所有相似度。

import numpy as np # 把向量列转成二维矩阵 matrix = np.array(df["embedding"].tolist()) # 批量计算余弦相似度(假设 query_vec 已经归一化) query_norm = query_vec / np.linalg.norm(query_vec) matrix_norm = matrix / np.linalg.norm(matrix, axis=1, keepdims=True) sims = matrix_norm @ query_norm # 把相似度放回 DataFrame df["similarity"] = sims

20 万条向量,整个计算过程不到一秒。这就是向量化思维带来的差距。Pandas 和 NumPy 配合使用,各司其职:Pandas 负责数据管理、筛选、分组,NumPy 负责大规模数值计算。

还有一个小技巧:如果你要存 Embedding 到磁盘,不要直接存涵 Python 列表的 CSV,那样文件巨大且读写极慢。建议把向量列单独抽出来存成.npy文件,文本和元信息存 Parquet。用 Pandas 处理的时候再合并到一起。

# 保存向量 np.save("embeddings.npy", np.array(df["embedding"].tolist())) # 保存元信息 df.drop(columns=["embedding"]).to_parquet("meta.parquet")

4. 训练/评估数据集构建的完整流程

4.1 数据划分:别让训练集和测试集互相"串门"

做 LLM 微调,最容易被忽视的问题就是数据泄漏。什么叫数据泄漏?就是训练集和测试集之间存在重叠或高度相似的样本。模型在训练时已经见过测试集的内容,评估出来的指标虚高,上线之后立刻被打回原形。

我当时做一个分类微调任务,按 8:2 划分训练集和测试集,前期评估准确率有 95%,上线之后直接掉到 70%。排查了半天,最后发现是原始数据里同一个问题出现了多次,只是措辞略有不同。训练集和测试集各分到了一部分,模型相当于开卷考试。

解决思路有两个层面。第一层是字面去重,这个 Pandas 直接能做:

df.drop_duplicates(subset=["instruction"], inplace=True)

第二层是语义去重。我后来养成了一个习惯:每次划分数据之前,先做一次 Embedding 相似度检查。用 3.3 节的方法,把每条样本的向量都算出来,然后两两比对相似度,把相似度超过 0.9 的样本合并到同一个集合里,再去做随机划分。这样能最大程度避免"换了个说法但其实是一道题"的泄漏。

from sklearn.model_selection import train_test_split train_df, eval_df = train_test_split( df, test_size=0.2, random_state=42, stratify=df["label"] )

stratify参数也很重要。如果你的数据类别不平衡(比如正样本 90%,负样本 10%),不做分层抽样的话,小类样本可能全部跑到测试集里,或者全留在训练集里。stratify=df["label"]能保证训练集和测试集的类别分布跟原始数据一致。

4.2 指令微调数据格式转换

SFT(监督微调)的数据格式这几年经历了不少变化,但核心结构是一致的:一条训练样本就是一个指令、一个输入(可选)、一个输出。用 Pandas 把原始数据统一成这种格式,是我的日常操作。

def to_sft_format(row): if pd.isna(row["input"]) or row["input"] == "": prompt = f"### 指令:{row['instruction']}\n\n### 回答:" else: prompt = f"### 指令:{row['instruction']}\n\n### 输入:{row['input']}\n\n### 回答:" return { "prompt": prompt, "response": row["output"], "label": row["label"] if "label" in row else None } sft_data = df.apply(to_sft_format, axis=1).tolist()

这里有个细节容易踩坑:apply(axis=1)会把每一行作为一个 Series 传给函数,性能并不算好。数据量超过 10 万条的时候,建议用itertuples()代替:

sft_data = [] for row in df.itertuples(index=False): if row.input is None or row.input == "": prompt = f"### 指令:{row.instruction}\n\n### 回答:" else: prompt = f"### 指令:{row.instruction}\n\n### 输入:{row.input}\n\n### 回答:" sft_data.append({"prompt": prompt, "response": row.output})

itertuples()生成的是命名元组,属性访问比apply(axis=1)里的row["xxx"]快一个数量级。在几万条数据的场景下,体感差别很明显。

生成好之后,把列表转换成 DataFrame,再按需求导出成 JSONL:

sft_df = pd.DataFrame(sft_data) sft_df.to_json("sft_format.jsonl", orient="records", lines=True, force_ascii=False)

force_ascii=False这个参数别漏了。默认情况下 Pandas 导出 JSON 会把中文转成\uXXXX转义序列,文件看着费劲不说,部分微调框架读取的时候还可能出问题。

4.3 数据质量评估:分布、长度、标签平衡

数据整理完之后,正式训练之前,我习惯用 Pandas 做一轮质量体检。这个过程不复杂,但对后续训练效果影响巨大。

先看长度分布:

df["prompt_len"] = df["prompt"].str.len() df["response_len"] = df["response"].str.len() print(df[["prompt_len", "response_len"]].describe())

describe()会输出每列的平均值、标准差、最小值、最大值、四分位数。我一般重点看两个指标:回答长度是不是有异常值(有的回答只有一两个字,大概率是脏数据),Prompt 长度是不是在合理区间内。

再看标签分布:

df["label"].value_counts(normalize=True)

normalize=True会输出占比而不是单纯的数量。比如你看到某个类别占比 0.2%,而其他类别都在 10% 以上,说明这个类别的样本量严重不足。对这种小类,要么补充采集,要么在训练时提高它的采样权重,否则模型基本学不会这类样本。

有一个我常用的可视化辅助手段,是配合matplotlib画长度分布直方图:

import matplotlib.pyplot as plt plt.figure(figsize=(10, 4)) plt.hist(df["response_len"], bins=50) plt.xlabel("Response Length") plt.ylabel("Count") plt.show()

直方图能非常直观地暴露数据长尾问题。我曾经处理过一份数据,大部分回答在 100~300 字,但有一小撮回答超过了 1000 字,点开一看,是把参考文档整段复制进去了。这种异常样本如果不筛掉,模型很容易学会"回答越权"的坏习惯。

5. 实战:处理一份真实的 LLM 训练语料

5.1 场景设定与数据说明

拿我最近做的一个项目举例。某公司想做一个内部客服助手,需要基于他们的历史工单记录微调一个模型。原始工单数据是从客服系统导出的,包括工单号、用户问题、客服回复、工单分类、处理时间等字段。数据总量大约 8 万条,格式是.csv。

这个项目的数据痛点很典型:大量无效短句比如"你好""谢谢";工单分类是文本描述,不统一,比如"退款问题"有人写成"退款";部分客服回复是从模板复制的,重复率很高;还有涉及到隐私的部分需要脱敏处理。

整个处理流程我拆成了四步,全程用 Pandas 完成。

5.2 完整处理流程代码

import pandas as pd import re # 1. 读取原始数据 raw_df = pd.read_csv("tickets_2024.csv", encoding="utf-8") # 2. 基础清洗 # 去掉完全重复的工单 raw_df.drop_duplicates(subset=["ticket_id"], inplace=True) # 去掉问题或回复为空的记录 raw_df.dropna(subset=["question", "answer"], inplace=True) # 去掉无效短句(问题或回复少于5个字符) raw_df = raw_df[raw_df["question"].str.len() >= 5] raw_df = raw_df[raw_df["answer"].str.len() >= 5] # 3. 文本规范化 raw_df["question"] = raw_df["question"].str.replace(r"\s+", " ", regex=True).str.strip() raw_df["answer"] = raw_df["answer"].str.replace(r"\s+", " ", regex=True).str.strip() # 4. 分类字段统一 def normalize_category(cat): if pd.isna(cat): return "其他" cat = str(cat).strip() if "退款" in cat or "退货" in cat: return "售后" if "物流" in cat or "快递" in cat: return "物流" if "账号" in cat or "登录" in cat: return "账号" return cat raw_df["category"] = raw_df["category"].apply(normalize_category) # 5. 按类别查看分布 print(raw_df["category"].value_counts()) # 6. 按用户问题做语义去重前的字面去重 raw_df.drop_duplicates(subset=["question"], keep="first", inplace=True) # 7. 添加 Token 估算列,方便后续控制训练长度 def estimate_tokens(text): # 中文场景下,一个字符约对应 1~1.5 个 token return int(len(text) * 1.2) raw_df["question_tokens"] = raw_df["question"].apply(estimate_tokens) raw_df["answer_tokens"] = raw_df["answer"].apply(estimate_tokens) # 8. 过滤超长样本(比如超过 2000 token 的回复可能是复制粘贴) raw_df = raw_df[raw_df["answer_tokens"] <= 2000] # 9. 只保留后续训练需要的列 final_df = raw_df[["question", "answer", "category"]].reset_index(drop=True) # 10. 导出为 JSONL final_df.to_json("clean_tickets.jsonl", orient="records", lines=True, force_ascii=False)

5.3 过程中的关键观察与结果

这个流程跑完之后,8 万条原始数据剩下了 5.6 万条,过滤率大约 30%。其中去掉的主要是无效短句、重复工单和超长回复。这个过滤率在真实项目里非常正常,甚至有些更脏的数据能过滤掉一半以上。

清洗过程中有个意外发现:分类字段不统一的严重程度远超预期。原数据里"退款"和"退货"被分成两个类,但业务上其实是一个流程;"物流"问题里混了大量"快递在哪"的相似提问。用normalize_category合并之后,类别数从 27 个降到了 9 个,每个类别都积累了足够的样本量。这对后续分类微调或者指令微调都特别有利。

不过我得说句实话:这个流程虽然跑通很顺利,但第一次做的时候,每一步都踩过坑。比如最开始我用dropna(subset=["question", "answer"])没加subset,直接把含任何空值的行全删了,结果把很多有分类但没有回答的模板数据也删掉了,白白损失了一批样本。

6. 避坑指南与性能优化

6.1 新手常见的五个 Pandas 坑

我从实际项目里踩过的坑里,挑了五个最容易反复出现的。整理成一张表,有类似问题的直接对着查就行。

问题表现根本原因解决方案
dropna 删掉了不该删的行没有指定 subset,把所有含空值的行都删了显式添加subset=[列名]
读完 JSON 后列名带点号嵌套字段没展开,点号路径留在列名里用json_normalize展开
export 到 JSONL 中文变成转义字符没有加force_ascii=False导出时加上force_ascii=False
处理 10 万条数据卡到怀疑人生用 iterrows 或 apply 循环逐行操作改成向量化操作或itertuples
修改 DataFrame 后原表也变了浅拷贝导致的 SettingWithCopyWarning用.copy()显式深拷贝

copy()这个问题很隐蔽。我有一次做数据清洗,写了df2 = df[df["label"] == "A"],然后对df2做修改,结果发现df也跟着变了。原因是df2只是df的一个视图(View),修改它会影响原表。解决办法是加.copy():

df2 = df[df["label"] == "A"].copy()

养成这个习惯之后,我再也没有因为副本问题半夜爬起来回滚数据。

6.2 大数据量下的性能优化技巧

LLM 数据处理经常要面对几十万甚至上百万条记录,性能问题躲不开。我在实际项目里常用的优化思路有三条。

第一条,能用内置方法就不用 apply。str.replace、str.contains、str.extract这些内置字符串方法都是向量化实现,底层走的是 C 循环,比 Python 级别的 apply 快一个量级。

第二条,处理超大文件时不要一次读入内存。用read_csv的chunksize参数分块读取,逐块清洗后追加写入。

chunk_size = 10000 output_file = "cleaned_all.csv" for chunk in pd.read_csv("huge_file.csv", chunksize=chunk_size): # 清洗流程 chunk = chunk.dropna(subset=["text"]) chunk["text"] = chunk["text"].str.strip() # 追加写入 if os.path.exists(output_file): chunk.to_csv(output_file, mode="a", header=False, index=False) else: chunk.to_csv(output_file, index=False)

第三条,对列进行筛选,只保留需要的列。有时候原始数据有一堆无关列,比如工单号、处理时间、操作员 ID,这些对模型训练没有用。越早 drop 掉,内存压力越小。

还有一条我看很多人忽略:Pandas 的字符串列默认是 object 类型,会占用大量内存。如果你确定某个列只包含有限几种取值,可以转成 category 类型:

df["category"] = df["category"].astype("category")

这一行操作在某些场景下能省掉 70% 的内存占用。处理几百万行数据的时候,这个优化的收益非常直观。

6.3 性能之外:保证可复现性

还有一点跟性能无关,但我觉得值得单独说一下:Pandas 的操作最好写成脚本而不是在 Notebook 里随手执行。我在项目里习惯把所有数据清洗流程写成一个.py文件,输入输出路径用参数控制。这样每次拿到新数据,跑一遍脚本就能得到同样的清洗结果,不用从头点一遍。

这个习惯帮了我大忙。有一次负责的模型要复现实验结果,团队里有人直接用了一份被重复清洗过的数据做训练,指标完全对不上。后来我拿出清洗脚本重新跑了一遍原始数据,才发现是数据预处理不一致导致的。从那以后,数据清洗流程纳入版本管理就成了我项目的硬性要求。


做了这么多 LLM 项目,我的体会是:Pandas 的价值不在于它有多酷,而在于它让数据处理变得可检查、可复现、可迭代。你写的每一个清洗步骤,都可以随时回看中间结果;你改动的每一行代码,都可以立刻看到它对数据分布的影响。这种掌控感,在 LLM 开发这种"数据主导"的项目里特别重要。

最后再分享一个小技巧。每次处理完数据,我都会顺手生成一份简单的数据报告:总量、类别分布、平均长度、异常样本数量。这份报告既方便自己复盘,也方便跟团队同步数据情况。用 Pandas 写这个报告基本就是几行代码的事,但它的价值,远比你想象的更大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询