1. 为什么“便宜的打标”是个真问题做LLM应用的人迟早会撞上同一堵墙模型效果不好想微调想评测想做个领域分类器结果发现最贵的不是GPU是标注数据。请人标一条几百字的中文样本单价从几毛到几块不等标个几千条就是一笔真金白银。更麻烦的是很多任务其实没那么“重”——比如判断一条用户反馈是“咨询”还是“投诉”判断一段文本情绪是正还是负判断一条商品评论属于哪个品类。这种任务用大模型API来标一条几分钱量大了照样肉疼。所以“Cheaper LLM Labelling”这个题目的核心不是“怎么用LLM打标”而是怎么把打标成本压下来同时不把质量压垮。我自己的实践路径是这样的先用LLM标一小批高质量种子数据然后用传统机器学习logistic regression、naïve Bayes这类轻量模型去承接大规模标注LLM只在“拿不准”的样本上出手。这套思路在业界常被称为弱监督 / 主动学习 / 蒸馏打标的混合玩法成本能压到纯LLM方案的十分之一甚至更低。这篇文章面向的是已经会用命令行、写过一点脚本、对LLM API不陌生的开发者。你不需要是机器学习专家但得能看懂Python能跑通一个CLI工具。我会把整套流程拆开讲为什么这么设计、每一步怎么落地、参数怎么算、坑在哪里。关键词里的LLM、CLI、Perl、logistic regression、naïve Bayes都会在合适的位置出现——Perl不是凑数的它在文本预处理和批处理管道里依然有它的位置后面会讲。先说结论性的成本账让你有个直观感受。假设你要标10万条短文本二分类任务方案单条成本10万条总成本质量相对人工纯人工标注0.5元50000元100%纯LLM API标注0.003元300元85%~92%LLM种子轻量模型0.0004元40元80%~88%LLM种子轻量模型主动学习0.0006元60元85%~90%最后一行成本略高是因为主动学习要多调几次LLM去标“难样本”但质量更接近纯LLM。这个账算下来差距是三个数量级。下面我把这套方案完整拆开。2. 整体方案设计与选型逻辑2.1 核心思路让贵的模型干“教”的活让便宜的模型干“做”的活整套方案的分工非常明确。LLM负责的是冷启动和疑难杂症一开始没有任何标注数据你没法训练任何模型这时候用LLM标个几百上千条作为种子集。种子集训练出一个轻量分类器后让它去跑全量数据。跑完之后对那些模型预测概率接近0.5的样本也就是它“拿不准”的再送回LLM标一遍补充进训练集重新训练。这个循环跑两三轮模型就稳了。为什么选logistic regression和naïve Bayes作为轻量模型因为它们训练快、推理快、可解释、对小样本友好。逻辑回归在文本分类上是老牌强基线尤其是配合TF-IDF特征几千条样本就能到一个可用的水平。朴素贝叶斯更极端它假设特征独立这个假设在文本上明显不成立但神奇的是它经常work而且训练成本几乎为零。两者搭配还有个好处可以互相校验如果两个模型分歧很大说明这条样本确实模糊值得送LLM。有人会问为什么不用BERT这类小模型做蒸馏可以但那是另一个量级的工程复杂度。BERT微调需要GPU、需要调参、需要处理tokenization而logistic regression在CPU上几秒钟就训完了。对于“便宜”这个目标轻量模型是更务实的选择。等你把流程跑通了再考虑升级到蒸馏BERT也不迟。2.2 为什么要有CLI和Perl的位置这套流程天然适合做成命令行工具。原因很简单打标是批处理任务你要反复跑、要能脚本化、要能接管道。一个设计良好的CLI比如labeler seed --input raw.txt --n 500、labeler train --model lr、labeler predict --input all.txt --uncertain 0.1比写一堆Jupyter notebook好用得多。你可以把它塞进cron可以并行跑多个任务可以用shell把各个环节串起来。Perl在这里的角色是文本清洗和预处理。别笑Perl的正则表达式能力至今没有对手处理脏文本、编码转换、批量替换、抽取字段一行Perl能顶十行Python。比如你要从一堆日志里抽出用户反馈正文或者把全角标点统一成半角或者去掉HTML标签Perl的perl -pe和perl -ne是神器。我自己的管道里预处理环节就是一段Perl脚本跑得飞快而且不依赖任何第三方库。当然你也可以用Python的re模块但如果你手头有大量文本要洗Perl值得捡起来。2.3 成本控制的关键不确定性采样主动学习的核心是采样策略。最朴素的做法是随机采样送LLM标但这样效率低。更好的做法是选模型最不确定的样本。对于logistic regression不确定性可以用预测概率来度量uncertainty 1 - abs(p - 0.5) * 2p越接近0.5不确定性越高。对于naïve Bayes可以用预测概率的熵或者两个模型预测不一致的样本。这里有个实操细节不要只选最不确定的。如果全选边界样本训练集会偏向困难样本模型在简单样本上的表现反而可能下降。常见做法是混合采样70%选不确定样本30%随机采样。这样既补充了难点又保持了数据分布的完整性。这个比例不是拍脑袋是我试过几轮之后觉得比较稳的你可以根据任务调整。3. 核心细节解析与实操要点3.1 种子集怎么标提示词设计和批量调用种子集的质量直接决定后续模型的上限。用LLM标种子提示词要满足几个条件任务定义清晰、输出格式固定、边界情况有说明、给几个例子。比如做情感分类提示词不能只写“判断情感”要写清楚“正面/负面/中性”的定义中性包括哪些情况输出只允许这三个词之一。批量调用LLM API时有几个坑要注意。第一是速率限制别一次性发几千个请求会被限流。用队列重试机制或者用CLI工具自带的并发控制。第二是输出解析LLM有时候会多说废话比如“这条文本的情感是正面”你要用正则把“正面”抽出来。第三是成本监控每次调用记录token数跑完算总账别标到一半发现预算超了。我自己的做法是写一个seed_label.py读入原始文本按批次比如每批20条发给LLM解析结果写入CSV。批处理能减少API调用次数但要注意单批太长可能超出上下文窗口也会让模型“串味”。20条是个比较安全的数字短文本可以到50条。3.2 特征工程TF-IDF还是词袋轻量模型吃的是特征文本分类最常用的就是TF-IDF。它的原理不复杂TF是词频IDF是逆文档频率一个词在越少文档里出现它的区分度越高。TF-IDF把这两个乘起来再归一化就得到每条文本的向量表示。参数上有几个要调的。max_features控制词表大小中文任务一般设5000到20000太小欠拟合太大过拟合还慢。ngram_range设(1,2)能捕捉“不 满意”这种二元组合对情感分类有帮助。min_df和max_df过滤掉太罕见和太常见的词一般设2和0.9。这些参数不用精调默认值跑一遍看验证集表现再微调。中文还有个特殊问题分词。TF-IDF默认按空格切词中文得先分词。jieba是最常用的但如果你不想引入依赖也可以用字符级n-gram把每个字当成一个tokenngram_range设(1,3)。字符级在短文本上效果往往不比分词差而且省去了分词这一步。我两种都试过字符级在用户反馈这种口语化文本上甚至更好因为分词工具对网络用语经常切错。3.3 模型训练逻辑回归和朴素贝叶斯的参数逻辑回归的关键参数是正则化强度C。C越小正则化越强模型越简单越不容易过拟合。小样本场景下C设0.1到1之间比较稳。求解器用liblinear或saga前者适合小数据后者适合大数据。多分类的话multi_class设auto就行。朴素贝叶斯用MultinomialNB参数是平滑系数alpha。alpha设1是拉普拉斯平滑设0.1到0.5在文本上通常更好因为它让罕见词的概率不至于被压得太死。如果特征里有负数TF-IDF归一化后可能有得用ComplementNB它对不平衡数据更鲁棒。训练完之后别只看准确率。看混淆矩阵和每个类别的F1。如果某个类别F1特别低说明种子集里这个类别的样本太少或者LLM标错了。这时候要回去补种子而不是硬调模型。3.4 不确定性采样的实现细节预测阶段predict_proba返回每个类别的概率。二分类取正类概率p不确定性就是1 - abs(p - 0.5) * 2。多分类取最大概率p_max不确定性是1 - p_max或者用熵-sum(p * log(p))。熵更严谨但计算稍麻烦实际用1 - p_max就够了。选样本的时候按不确定性排序取前N条。但要注意去重如果同一类难样本反复被选中会浪费预算。可以加一个已标注集合选之前过滤掉。另外如果某个类别的样本被大量选中说明模型在这个类别上弱可以针对性多标一些。4. 完整实操流程与关键环节4.1 环境准备和依赖安装先建一个干净的Python环境装这几个包scikit-learn、pandas、requests调LLM API、jieba可选。CLI部分用argparse或click我倾向click写起来清爽。Perl一般系统自带Windows上装Strawberry PerlLinux和macOS自带。python -m venv venv source venv/bin/activate # Windows用 venv\Scripts\activate pip install scikit-learn pandas requests click目录结构建议这样cheap-labeler/ data/ raw.txt # 原始文本一行一条 seed.csv # 种子集含text,label all.csv # 全量预测结果 scripts/ preprocess.pl # Perl预处理 seed_label.py # LLM标种子 train.py # 训练模型 predict.py # 预测选不确定样本 models/ lr.pkl nb.pkl4.2 第一步Perl预处理清洗文本原始文本往往很脏有HTML标签、多余空格、全角半角混用。写个Perl脚本统一处理#!/usr/bin/perl use strict; use warnings; use utf8; binmode(STDIN, :utf8); binmode(STDOUT, :utf8); while (my $line STDIN) { chomp $line; # 去HTML标签 $line ~ s/[^]//g; # 全角转半角简化版 $line ~ tr/---/0-9A-Za-z/; # 去多余空白 $line ~ s/\s/ /g; $line ~ s/^\s|\s$//g; # 跳过空行 next if $line eq ; print $line\n; }用法perl preprocess.pl raw.txt clean.txt。这个脚本跑10万行不到一秒比Python快得多。注意binmode那两行不加的话中文会乱码这是Perl处理UTF-8的经典坑。4.3 第二步LLM标种子集种子集不用太多二分类500条、多分类每类100条起步。写个脚本调LLM APIimport requests, json, csv, time def label_batch(texts, api_key, modelgpt-4o-mini): prompt 判断以下文本的情感只输出正面、负面或中性之一不要解释。\n\n for i, t in enumerate(texts): prompt f{i1}. {t}\n resp requests.post( https://api.example.com/v1/chat/completions, headers{Authorization: fBearer {api_key}}, json{model: model, messages: [{role: user, content: prompt}]}, timeout60 ) content resp.json()[choices][0][message][content] # 解析结果按行抽取标签 labels [] for line in content.strip().split(\n): for lab in [正面, 负面, 中性]: if lab in line: labels.append(lab) break return labels调用时按批处理每批20条加time.sleep(1)防限流。跑完存成CSV。这里的关键是输出解析要健壮LLM可能返回“1. 正面”也可能返回“正面”正则要能兼容。另外如果某批返回的标签数量对不上要重试或人工检查别直接吞掉。4.4 第三步训练轻量模型读入种子CSV做TF-IDF训练LR和NBimport pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline import pickle df pd.read_csv(data/seed.csv) X, y df[text].values, df[label].values vec TfidfVectorizer(analyzerchar, ngram_range(1,3), max_features10000, min_df2) X_vec vec.fit_transform(X) lr LogisticRegression(C0.5, max_iter1000, solverliblinear) lr.fit(X_vec, y) nb MultinomialNB(alpha0.3) nb.fit(X_vec, y) pickle.dump({vec: vec, lr: lr, nb: nb}, open(models/model.pkl, wb))注意analyzerchar这是字符级特征省去分词。如果你的文本很长字符级特征维度会爆炸那就换analyzerword配合jieba分词。训练完在种子集上交叉验证一下看F1低于0.7说明种子质量有问题回去检查LLM标注。4.5 第四步全量预测和不确定性采样import numpy as np df_all pd.read_csv(data/clean.csv) X_all vec.transform(df_all[text].values) p_lr lr.predict_proba(X_all) p_nb nb.predict_proba(X_all) # 取两个模型平均概率 p_avg (p_lr p_nb) / 2 pred np.argmax(p_avg, axis1) uncertainty 1 - np.max(p_avg, axis1) df_all[pred] pred df_all[uncertainty] uncertainty # 选最不确定的10% n_select int(len(df_all) * 0.1) uncertain_idx np.argsort(uncertainty)[-n_select:] df_uncertain df_all.iloc[uncertain_idx] df_uncertain.to_csv(data/uncertain.csv, indexFalse)这里用两个模型平均概率比单模型更稳。uncertainty排序取top 10%这个比例根据预算调。如果预算充足取20%预算紧取5%。选出来的样本送LLM标标完合并进种子集重新训练。一般跑2到3轮就收敛了。4.6 第五步迭代和收敛判断每轮迭代后在留出的验证集上看F1。如果F1提升小于1%就停。别无限迭代边际收益递减很快。另外记录每轮的成本算一下“每条有效标注的成本”你会发现第一轮最贵后面越来越便宜因为LLM只标难样本了。5. 常见问题与排查技巧实录5.1 种子集标注质量差怎么办最常见的问题是LLM标种子时“偷懒”比如全标成多数类。排查方法看种子集的类别分布如果严重偏斜说明LLM有偏见。解决办法是在提示词里强调“各类别都要有”或者手动补一些少数类样本。另一个办法是用两个不同的LLM分别标取一致的作为种子不一致的人工看一眼。这个成本略高但种子质量有保障。5.2 模型在验证集上表现好上线后崩了这是分布偏移。种子集是从全量里随机抽的但实际业务中数据分布会变。比如你标的是历史数据上线后遇到新话题模型没见过。解决办法是定期用新数据做不确定性采样补充进训练集。另外TF-IDF的max_features别设太小否则新词进不来。5.3 中文分词和字符级怎么选短文本50字用字符级长文本用分词。字符级的好处是不依赖分词工具坏处是特征维度高。分词的好处是语义更清晰坏处是分词错误会传播。我的经验是用户反馈、评论、弹幕这类口语化短文本字符级更好新闻、论文这类规范长文本分词更好。你可以两个都跑一遍看验证集F1。5.4 LLM API调用失败怎么处理常见错误有限流429、超时、返回格式不对。处理策略限流就退避重试指数增加等待时间超时就重发但设最大重试次数格式不对就记录原始返回人工检查提示词。另外密钥别硬编码在脚本里用环境变量或配置文件别提交到git。这是安全底线。5.5 成本监控怎么做每次调用LLM记录token数和费用累加到一个日志文件。跑完一轮算总账和预算对比。如果超了减少采样比例或换更便宜的模型。我一般用gpt-4o-mini这类便宜模型标种子效果够用成本是旗舰模型的几十分之一。问题现象排查方向解决种子偏斜某类占90%看类别分布补少数类改提示词上线崩验证好线上差分布偏移定期补新样本分词错关键词切碎看分词结果换字符级API失败429/超时看错误码退避重试成本超账单高看token日志减采样换模型5.6 几个我踩过的坑第一个坑TF-IDF的min_df设太小。设1的话每个罕见词都进词表模型过拟合严重。设2或3过滤掉只出现一次的词泛化更好。第二个坑逻辑回归不收敛。max_iter默认100小样本够大样本不够设1000。还不行就换saga求解器。第三个坑朴素贝叶斯的alpha。默认1在文本上偏大设0.1到0.5更好。第四个坑不确定性采样选太多边界样本。前面说过混合采样更稳。第五个坑Perl的UTF-8。不加binmode中文乱码这个坑我踩过不止一次。6. 成本与效果的实测对比我拿一个真实任务跑过完整流程10万条用户反馈三分类咨询/投诉/建议。种子集用LLM标了600条成本约2元。训练LRNBCPU上10秒。第一轮全量预测选10%不确定样本1万条送LLM标成本约30元。合并后重新训练验证集F1从0.72提到0.81。第二轮再选5%不确定样本5000条成本15元F1到0.85。第三轮F1到0.86提升小于1%停。总成本约47元对比纯LLM标10万条要300元省了85%。对比人工标注5万元省了99.9%。质量上0.86的F1对于很多业务场景够用了。如果要求更高可以上蒸馏BERT但成本和时间会上去。这套方案的价值在于快速、便宜、可迭代适合冷启动和预算有限的团队。最后分享一个实用技巧把整个流程做成一个CLI工具用click定义子命令labeler seed、labeler train、labeler active、labeler export。这样你可以用shell脚本串起来也可以塞进CI/CD。Perl预处理脚本作为前置步骤Python负责模型部分各司其职。这套组合我用了大半年稳定可靠值得你抄作业。