ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零构建humanizer:用规则引擎去除AI文本的“塑料感”

从零构建humanizer:用规则引擎去除AI文本的“塑料感” 最近一直在折腾一个叫“humanizer”的小项目起因特别简单我拿 ChatGPT 写英文邮件和博客草稿时总是感觉输出有点“过于标准”读起来像客服模板一眼就能识别出不是人写的。网上搜了一圈现成的 humanizer 工具要么要注册要么限字数免费的又改得不够自然。索性自己动手写一个核心目标就一个把 AI 生成文本里那层“塑料感”剥掉让它更像真人随手写的——会有句子长短变化会有点口语甚至有轻微的不完美。这篇博文就完整记录一下这个工具的设计思路、核心实现、踩坑过程和优化经验希望能给同样被 AI 文本困扰的朋友一些参考。1. 项目概述到底在解决什么问题它的用途和适用人群1.1 需求背景为什么需要专门做一个“humanizer”先说一个场景。AI 写作在逻辑性、结构完整度上确实很强但问题也恰恰出在这里大語言模型训练时被投喂了大量规范化文本导致它的输出天然带有“结构化痕迹”。我观察过大量 ChatGPT、Claude 的英文输出最常见的特征有以下几点过度使用“Firstly / Secondly / Furthermore / In conclusion”这类连接词句式过分完整几乎不出现口语化的省略、反问、语气词长句冗长且“顺滑”缺少真人写作时那种长短句交错的节奏感常用“It is important to note that...”、“It should be emphasized that...”这类无主语形式结构显得刻板还有一个很有意思的问题——AI 生成的文本在词汇选择上总是“恰到好处”不会太过口语也不会太过生僻但这种“完美的平均”反而暴露了它。真人写东西不会每个词都精准到那个程度反而会有偏好词、重复词甚至偶尔的冗余表达。所以 humanizer 要解决的就是把这些“AI 痕迹”抹掉让文章在保留原意和准确性的前提下变得更像“某个人”写出来的而不是“某个语言模型”生成的。1.2 使用场景定位这个工具适合谁用我总结了几个最典型的场景内容创作者用 AI 辅助写初稿后批量化“去掉 AI 味”需要大量英文邮件往来的职场人让 AI 起草的邮件读起来像亲手写的学生和教育工作者理解“机械化写作问题”的一个很好的教学案例对自然语言处理感兴趣的开发者可以从这个项目里学到文本分析、模式匹配、字符串转换的实际应用我自己最常用的场景是给博客文章批量除味。先用 AI 生成一个结构完整的草稿再跑一遍 humanizer把“模板块”打散重排然后手动润色一遍。整个过程比我直接从零写快了三倍不止而且保住了个人风格。2. 技术核心设计思路与文本转换的原理拆解2.1 核心设计思路做这个工具之前我先把问题拆解成了两个层面。第一层是“文本变换层”目标是识别并改写那些固定模板化表达。比如把“In conclusion, we can see that...”改成“So, yeah, it turns out that...”会显得更口语化。这个层面本质上就是一个“模式匹配替换”的过程规则可以用正则表达式、短语映射表来实现。第二层是“结构重排层”目标是打散 AI 写作时有规律的句式节奏。这个要靠语法规则和启发式规则当检测到一个长句里连续出现多个并列表述时把它拆成短句当检测到连续出现多个短句时适当用连接词合并当检测到“开头用连接词引导”的句式过于多次时把其中一部分反转成主语开头。这两个层面配合起来效果是最明显的。单做第一层虽然简单但容易出现“替换过度”的问题——每段都像套了同一个模板单做第二层又不够精准容易破坏语法。最终我采用的是“先规则、后重排、最后校验”的三步流水线。2.2 需要优先处理的三类“AI 味”问题所有的处理规则都不是凭空来的而是基于我喂给工具的大量 AI 生成文本样本总结出了三类最常见、也是对“AI 味”贡献最大的问题第一类模板化连接词泛滥代表词汇有“In addition”、“Moreover”、“Additionally”、“Furthermore”、“On the other hand”、“In conclusion”等。真人写作更常用“Also”、“Plus”、“But then again”、“So anyway”、“Or maybe”等。第二类形式主语滥用“It is important to note that...”、“It should be pointed out that...”、“It can be observed that...”这类结构在 AI 输出里频繁出现。真人更倾向直接说“Something important is...”、“Let me point out that...”、“You can see that...”。第三类长句与并列结构堆砌AI 擅长把多个观点用从句串联成一个超长复合句虽然语法正确但真人写作里这种情况其实较少见。更重要的是把长句拆短增加句号或者用好“and”、“but”、“so”这类简单连词而不是“which”、“whereas”。这三类问题是规则引擎设计的核心。在实际测试中覆盖好这三类文本的“AI 味”就能下降 80% 以上。2.3 版工具选型思路技术上没有选用大型 Transformer 模型而是用了最基础的 Python 正则表达式 轻量语法解析核心原因有三条第一prototype 阶段追求快速迭代规则全部写成 JSON 配置文件改规则不用改代码第二离线运行不依赖外部 API隐私安全第三可控性强每一条替换规则都能审查不会出现大模型“自由发挥”造成的误改。当然这种方案也有边界它对语言深层次语义的把握不如大模型有些需要理解上下文才能判断的改写会做不好。但考虑到 80/20 法则规则引擎已经能覆盖绝大多数大规模除味需求。3. 完整实操过程从零构建 humanizer 的代码实现3.1 功能模块划分整个项目按职责划分成四个模块模块职责核心依赖pattern.py定义模式匹配规则包括模板短语、连接词表、句式特征re,jsontransformer.py实现句式重写包括长句拆分、形式主语改写、连接词替换pattern.pyhumanizer.py主流程控制负责调用各个模块并保证最终文本可读性transformer.pycli.py命令行入口支持输入文件、输出文件、批量处理argparse整个流程大概是输入文本→拆句→逐句判断类型是否需要替换/拆分/重排→替换规则→重新组装→输出。中间还加入了“重复模式检测”用来避免替换后出现连续相同的句式。3.2 模式匹配层核心难点是“防误伤”match-and-replace 的难点从来不是“能匹配上”而是“会不会误伤”。比如“So”这个连接词在句首出现时可能是口语化的“所以”也可能是表程度的“如此”如果不加限制地替换就会语意全乱。我在 pattern.py 里设计的匹配规则长这样import re import json class PatternMatcher: def __init__(self, config_pathrules.json): with open(config_path, r, encodingutf-8) as f: self.rules json.load(f) self.compiled_rules { sentence_start: [ (re.compile(pattern, re.IGNORECASE), replacement) for pattern, replacement in self.rules[sentence_start] ], general: [ (re.compile(pattern, re.IGNORECASE), replacement) for pattern, replacement in self.rules[general] ], } def transform_sentence(self, sentence: str) - str: original sentence.strip() # 先处理句首模式 for pattern, replacement in self.compiled_rules[sentence_start]: if pattern.match(original): sentence pattern.sub(replacement, sentence, count1) break # 再处理通用模式 for pattern, replacement in self.compiled_rules[general]: if pattern.search(sentence): sentence pattern.sub(replacement, sentence) return sentence这里有一个关键点句首模式用match只匹配开头位置避免把句子中间表达程度语气的“So”误伤。通用模式用search但要特别注意规则顺序先替换长短语再替换短语避免“In conclusion”被更早的更短规则拆掉。3.3 替换规则配置把“AI 腔”换成“人话”rules.json是工程的核心资产。这是我长期打磨出来的规则库在这里直接分享最常用的几条仅示意完整版很长{ sentence_start: [ [^In conclusion,?, So, in short,], [^Moreover,?, Plus,], [^Furthermore,?, Also,], [^In addition,?, Another thing is,], [^Nevertheless,?, Even so,], [^It is important to note that, One key thing here is that], [^It should be noted that, Worth noting: ], [^It is worth mentioning that, Quick note:] ], general: [ [In order to, to], [due to the fact that, because], [despite the fact that, even though], [at this point in time, right now], [as a matter of fact, actually], [a large number of, a lot of], [with regard to, about] ] }这里要说一个细节In order to这类长短语必须放在“短语优先”的位置。如果先执行了“把所有的 In 开头的词做某种处理”这种模糊规则后续规则就失效了。因此匹配遵循的是“最长优先”原则这也是我踩过的一个很深的坑后面会细讲。3.4 句式重排层把长句拆碎把节奏打散匹配替换只是“点状”修复真正的“整体去 AI 味”动作在 transformer.py 里的长句拆分逻辑。我实现了一个简化版的 clause splitter找到以连接词and, but, because, which, whereas为界的子句切分点如果句子长度超过 25 个词就在第一个合理位置强行断开。import re CLAUSE_SPLITTERS [ re.compile(r, and\s), re.compile(r, but\s), re.compile(r, which\s), re.compile(r, because\s), re.compile(r, whereas\s), ] def split_long_sentence(sentence: str, max_words: int 25) - list[str]: words sentence.split() if len(words) max_words: return [sentence] for splitter in CLAUSE_SPLITTERS: match splitter.search(sentence) if match: idx match.start() left_part sentence[:idx] right_part sentence[idx len(match.group(0)) :] # 对左边/右边递归拆分 left_parts split_long_sentence(left_part, max_words) right_parts split_long_sentence(right_part, max_words) return left_parts [right_part] return [sentence]这个递归拆分有个细节连接词本身要保留在左半句或右半句不能直接丢掉。比如拆完“The algorithm is efficient, but it requires careful tuning”如果拆成“The algorithm is efficient”和“it requires careful tuning”读起来有点断不如保留“But”在第二句开头更口语拆完后第二句加一个“But ”前缀拆完后第二句如果开头还是连接词自动重新组装成完整句在这个基础上还可以做“短句合并”当连续出现两个过短句子少于 4 个词时用“and”或者“so”连接。这套逻辑虽然简单却恰好模拟了人类写作时的节奏控制我是对照一篇文章手动调了很多次才满意的。3.5 主流程与前后处理主流程 humanizer.py 的调用逻辑并不复杂class Humanizer: def __init__(self): self.matcher PatternMatcher(rules.json) self.splitter split_long_sentence def humanize_text(self, text: str) - str: paragraphs text.split(\n) new_paragraphs [] for para in paragraphs: if not para.strip(): new_paragraphs.append(para) continue sentences re.split(r(?[.!?])\s, para.strip()) transformed [] for sent in sentences: sent self.matcher.transform_sentence(sent) sub_parts self.splitter(sent) transformed.extend(sub_parts) new_paragraphs.append( .join(transformed)) return \n.join(new_paragraphs)注意这里用了re.split(r(?[.!?])\s, ...)做分句这个正则有一个天然缺陷遇到“Mr. Smith”或“e.g.”会错误切分。我选择忽略这个问题因为项目主要处理博客、邮件等日常文本缩写干扰很小。如果之后要处理研究报告类文本就需要引入更完善的 sentence boundary detection 了。3.6 命令行接口与批量处理为了实用我做了个简单的 CLI支持单文件、批量文件夹处理python cli.py input.txt -o output.txt python cli.py ./drafts/ -o ./cleaned/ --batchCLI 逻辑比较简单就不全贴了只留核心片段import argparse from pathlib import Path from humanizer import Humanizer def main(): parser argparse.ArgumentParser(descriptionHumanize AI-generated text.) parser.add_argument(input, typestr) parser.add_argument(-o, --output, typestr, defaultNone) args parser.parse_args() humanizer Humanizer() input_path Path(args.input) if input_path.is_file(): text input_path.read_text(encodingutf-8) result humanizer.humanize_text(text) output Path(args.output) if args.output else input_path.with_suffix(.humanized.txt) output.write_text(result, encodingutf-8) print(fSaved to {output}) elif input_path.is_dir(): for file_path in input_path.glob(*.txt): result humanizer.humanize_text(file_path.read_text(encodingutf-8)) out_path file_path.with_suffix(.humanized.txt) out_path.write_text(result, encodingutf-8) print(fSaved to {out_path})4. 踩坑记录调试过程中遇到的典型问题与解决思路4.1 规则覆盖不足第一个最明显的问题是初版规则库覆盖率太低。当时只写了十几个高频模板跑测试文本时发现大量 AI 味句子根本没被触及处理后的文本和原文差别很小。解决方式非常笨从真实使用记录里持续收集“AI 典型表达”然后一条条补充进规则库。这个过程中我总结出一个经验规则库不是一次性写完的它是一个持续生长的资产。现在我的 rules.json 已经接近 100 条规则但上线前仍然会定期用新鲜语料回测防止某条新增规则破坏了已有文本的流畅度。4.2 误伤问题的反复出现替换规则最大的麻烦就是误伤。比如有一条规则叫“把 Furthermore 换成 Also”听起来很安全但实际跑的时候发现有些文本里的 Furthermore 出现在句首但指的是“而且”的意思改成 Also 没问题可也有一些文章用 Furthermore 表示“更进一步”的递进逻辑改成 Also 就很别扭。后来我给规则加了“上下文标记”机制只有当前面句子内容是“并列列举”时才允许替换该连接词如果是递进逻辑则保留原词。虽然增加了复杂度但文本的自然度大幅提升。4.3 长句拆分后语义断裂拆分长句过程中容易把“虽然……但是”这种关联结构拆坏。比如原句“Although the weather was bad, we still went out”如果拆成两句“Although the weather was bad”“We still went out”第一句就变得有点奇怪成为一个不完整从句。后来我增加了“关联词检测”当左半句以 Although, When, Because 等引导时不直接拆分优先把关联词移到第二个句子或改成独立句。这是一个比较值得注意的地方。具体做法是增加一个is_dependent_clause检查函数遇到从句引导词时不做拆分而是尝试改写为“The weather was bad. But we still went out.”这类更口语的句式。4.4 连续替换导致文本变得过于口语化还有一次踩坑是所有规则都生效后文本确实不像 AI 了但也完全不像专业文章了——通篇都是“So...”、“Plus...”、“Another thing is...”读起来像在跟朋友聊天。这个问题提醒我humanizer 的目标不是“口语化到极端”而是“匹配原本的文体”。我的解决方案是给规则库增加“风格权重”轻度模式只替换“formal 模板化表达”比如“due to the fact that”换成“because”中度模式额外替换连接词增加口语跃动感重度模式允许长句全拆、句首全改适合社交媒体/非正式邮件实际使用时我通常保持“中度”因为它在保持专业感和自然感之间最平衡。4.5 复读检测避免替换后句式重复你可能会忽略一个问题如果文本里有 5 处 Furthermore规则会全部替换成 Also结果就是文本里冒出 5 个“Also”这同样很不自然。为了避免这种问题我在主流程里加了一个简单的“频率控制”机制同一个替换词在一段文本中出现的次数超过阈值后续句子就跳过替换保留原词或换用其他变体。这个逻辑在功能上类似“同义词轮换”但实现起来非常简单却带来了肉眼可见的自然度提升。5. 效果验证真实测试与体验优化5.1 量化测试为了验证效果我召集了几个朋友做盲测。准备了三段文本一段纯 AI 生成、一段经过我的 humanizer 处理、一段人类原生写作。读完不计名打分判断“是否认为是人写的”。测试结果显示文本类型被误判为人工写作的比例纯 AI 生成14%humanizer 处理后58%人类原生写作86%这个结果说明两个问题第一humanizer 确实有效大幅提高了文本的自然性第二离完全“以假乱真”还差得远需要搭配人工润色才能达到最终效果。58% 的“人味”已经能免除大部分 AI 痕迹检测的疑虑但也提醒我这个工具定位应该是“辅助”不是“替代”。5.2 场景适配我还在不同场景下测试了规则库的表现邮件场景人工感提升明显。把“Please find attached...”、“I am writing to inform you that...”这类典型邮件 AI 味句子换成“Here’s the file you asked for.”、“Quick update on...”后回收率明显变高。博客场景效果中等。规则库对连接词替换很拿手但对“罗列式开头”的处理仍偏弱有时需要额外手动段落重组。研究报告场景效果一般。长句拆分逻辑对这种文本特别有用但很多专业术语和固定表达不能随意替换轻度模式更适合。这些测试结果反过来帮我调整了规则库的适用边界也让工具从“通用型”逐渐向“按场景选配置”的方向演进。5.3 未来扩展方向目前实现的是规则引擎版本后续还有几个可以扩展的方向接入本地轻量语言模型把句间重写能力从“替换”升级为“改写”增加“个人写作风格画像”让同一个 humanizer 在不同人手里输出不同风格引入更精细的语义一致性校验用 BERT、BERTScore 检查改写后核心语义有没有漂移这些方向虽然复杂但底层思路并不变识别规律、替换规律、找回“人”的表达习惯。6. 经验总结做文本处理工具需要注意的三个认知经过这段时间的开发和实测我发现这类“文本增改工具”和普通的增删改查功能完全不是一个思维模式有几点很值得说第一正则替换不是垃圾方案。很多人一听说“去 AI 味”就觉得必须上大语言模型但实际效果里大量的 AI 痕迹都是高度模板化的表达用规则引擎处理反而比大模型更可控、更稳定、完全可离线运行。技术选型永远是从问题本质出发不是从技术热度出发。第二不要试图一次覆盖所有场景。AI 文本的“人味”问题不是一个数学上可定义的问题不同文体的人味标准完全不同。项目的正确成长方式应该是“具体场景驱动”先覆盖高频场景再逐步扩展规则库。这也是我后来把规则按文体分 files 的原因。第三humanizer 永远是一个“辅助工具”。它能把 AI 的输出变得自然但最终负责任的还是人。发布前一定要通读一遍尤其是替换了语义强烈词汇的地方需要人工确认是否保住了原本的表达意图这一点在专业内容里尤其重要。自这个项目做完以后我日常处理 AI 草稿的效率提升非常明显原来一篇博客从生成到发布要花大半天现在基本压缩到两三个小时。如果你也常被“AI 味”困扰与其找一个通用在线工具不如按这个思路造一个自己的规则引擎顺手还能积累一套完全属于你自己的表达偏好规则用起来比任何现成方案都顺手。
返回列表