1. 文档切片:这里有个大坑,chunk_size和overlap设置不对,检索效果天差地别
海归硕士入职两月,聊聊我是如何死磕自然语言处理的
早上8点,公司还没几个人,我熟练地刷开闸机,接了杯冰美式回到工位。作为刚回国不到半年的海归硕士,能在今年这个地狱级难度的求职市场里卷赢一众大佬拿到现在的offer,我属实是把“勤能补拙”发挥到了极致。入职新公司刚好两个月,目前还在试用期的新手村苟着。为了不被优化,我基本保持着早8晚9的节奏,毕竟身边全是卷王,稍微一摸鱼就有种要被优化的错觉。
最近公司高层不知道看了什么技术公众号,突然决定要搞“AI赋能”,Leader顺势把内部知识库智能化的需求塞给了我。好家伙,我一个平时写业务CRUD的,突然就要搞自然语言处理(NLP)了。被逼无奈,只能硬着头皮啃这块硬骨头。今天刚好周末,趁着早上8点还没人来打扰,跟大家聊聊我这俩月是怎么从NLP小白一路踩坑爬过来的,希望能给同样想转AI方向的新手一点参考。
先说说业务背景。我们公司内部有个庞大的文档知识库,以前大家找资料全靠Ctrl+F,或者在群里吼一声让老员工指路。产品经理老王(对,就是那个天天让我把按钮颜色从“五彩斑斓的黑”改成“低调奢华的灰”的老王)最近非要搞个智能问答系统。他的原话是:“我们要让用户搜‘请假流程’,系统能直接告诉他怎么请假,而不是给他甩出十个包含‘请假’俩字的PDF链接。”
当时我听完就想砸电脑。传统的Elasticsearch基于倒排索引,玩的是字面匹配,你搜“休假”,它根本不知道跟“请假”是一回事。要实现老王说的这种“懂人话”的搜索,必须得上NLP,搞语义理解,也就是现在火得一塌糊涂的 AI搜索 和 RAG(检索增强生成)架构。
刚接手这活儿的时候,我连Transformer的论文都没翻过。一开始我想着从最基础的Word2Vec和BERT开始学,结果被Leader无情打断:“现在都什么年代了,谁还从头训小模型?直接上开源大模型加RAG!”于是我的学习路线直接从“NLP入门”跳跃到了“大模型应用开发”。
第一个大坑就是数据清洗。公司历史文档那叫一个惨不忍睹,各种Word、PDF、甚至扫描件混在一起。特别是那些带表格的PDF,用开源工具解析出来全是一堆乱码和错位的数据。为了解决这个问题,我差点把正则表达式写到吐血。后来实在受不了了,我尝试用了 Replit Agent 来帮我写数据清洗脚本。不得不说,这玩意儿写Python脚本简直神器。我直接用自然语言告诉它:“帮我写个脚本,用pdfplumber解析PDF,把表格里的数据提取成JSON格式,并且处理掉换行符和多余的空格。”它几秒钟就给我生成了代码,我还顺便让它加了异常处理和日志记录。有了 Replit Agent 的加持,我硬是把原本需要三天的数据清洗工作压缩到了半天,省下来的时间终于可以去研究模型调优了。
数据洗干净后,就进入了核心的 AI搜索 环节。这里我选择了基于LangChain框架来搭建RAG流程。很多新手(包括当时的我)以为把文档塞进向量数据库,然后让大模型回答就完事了,结果上线一测,发现大模型经常“胡说八道”,也就是所谓的幻觉问题。
后来我复盘了一下,发现核心问题出在文档切片(Chunking)和检索策略上。下面是我优化后的一段核心检索代码,给大家避避坑:
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.chains import RetrievalQA
from langchain.llms import Ollama
# 我测试下来,对于中文技术文档,chunk_size=500, chunk_overlap=50 效果最好
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
length_function=len,
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " "]
)
chunks = text_splitter.split_documents(documents)
# 2. 向量化:千万别用英文的Embedding模型搞中文,一定要用多语言或者专门的中文模型
# 这里我选了 bge-large-zh,性价比极高
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-zh",
model_kwargs={'device': 'cuda:0'}, # 有显卡一定要用GPU,不然慢到怀疑人生
encode_kwargs={'normalize_embeddings': True} # bge模型必须加这个参数
)
# 3. 构建向量库
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
collection_name="company_knowledge_base"
)
# 4. 本地部署大模型(为了数据安全,公司要求私有化部署)
# 使用Ollama跑Qwen2.5-7B,显存占用刚刚好
llm = Ollama(model="qwen2.5:7b", temperature=0.1) # temperature调低,减少幻觉
# 5. 构建QA链
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 4}), # 召回Top 4
return_source_documents=True
)
代码看着简单,但里面的参数全是血泪教训。比如 temperature 参数,一开始我设了0.7,结果大模型回答得那叫一个放飞自我,连公司没成立的部门都编出来了。后来调到0.1,回答就严谨多了。还有召回数量 k,设成4的时候,既能保证上下文完整,又不会超出大模型的上下文窗口限制。
搞定了基础RAG,老王又跑来挑刺:“有时候搜专业名词,搜出来的东西不对啊。”这就涉及到了 AI搜索 的进阶优化——混合检索。单纯的向量检索对专有名词(比如某个特定的系统代号“天狼星”)很不敏感,因为向量模型倾向于捕捉语义,而不是精确的字面匹配。
为了解决这个问题,我引入了 BM25 算法,搞了一套“向量检索 + 关键字检索”的混合策略。通过 RRF(Reciprocal Rank Fusion)算法把两路召回的结果进行融合。这波操作上去之后,专业名词的召回率直接提升了30%,老王终于闭嘴了。
在模型训练和调优方面,虽然Leader说不用从头训,但我还是对基座模型做了一点轻量级的微调(LoRA)。因为公司内部的很多黑话和缩写,开源模型根本不懂。我从历史工单里抠出了大概2000条高质量的QA对,构建了一个小型的指令微调数据集。用 LLaMA-Factory 跑了几个Epoch,微调后的模型在回答内部业务问题时,准确率肉眼可见地提升了。
至于效果评估,这绝对是个玄学。一开始我想用 BLEU 和 ROUGE 这些自动化指标,但发现这俩指标对生成式大模型根本不适用。最后我搞了个“半自动化”方案:写个脚本让大模型自己去给回答打分(LLM-as-a-Judge),然后再拉上测试组的妹子们进行人工抽检。虽然拉人帮忙点鼠标的时候被吐槽“拿她们当免费劳动力”,但好歹把评估体系给建起来了。
回首这两个月,从连NLP是什么都一知半解,到现在能独立扛起公司内部的 AI搜索 项目,中间熬了多少个大夜、掉了多少头发只有我自己知道。但看着系统上线后,群里抱怨“找不到文档”的声音越来越少,心里还是挺有成就感的。
对于想入门NLP或者转AI应用开发的新手,我的建议是:千万别一上来就死磕数学公式和底层原理,先跑通一个RAG的Demo,先让业务跑起来,再去慢慢优化细节。工具链现在非常成熟了,像 Replit Agent 这种AI辅助编程工具也能帮你扫清很多代码层面的障碍。
不说了,快9点了,团队里的大佬们陆续到齐了,我得赶紧去站会同步进度了。试用期还没过,继续搬砖去了,祝大家代码无Bug,永不宕机!

评论 0