中宏安科技 大模型训练工程师 一面
1.自我介绍
2. 文章分类大概有多少文章?数据规模怎么影响方案?
答案:
如果文章量只有几千篇,我一般不会一上来就训练很复杂的模型,而是先用预训练模型微调,或者用 embedding 加传统分类器做一个强 baseline。因为数据量太小的时候,复杂模型很容易过拟合,线上效果不稳定。
如果有几十万到几百万篇文章,就可以考虑训练一个更稳定的分类模型,包括 BERT 微调、领域继续预训练、层级分类模型,甚至做多任务学习。数据量越大,越要关注标签噪声,因为文章分类里的标注经常不是绝对干净的,尤其是多维度分类,一个文档可能同时属于多个业务类别。
实际项目里我会先看三个东西:类别数量、每个类别样本分布、单篇文档长度。因为分类方案不是只由文章数量决定的,文档长度和标签体系对模型设计影响更大。
3. 文章自动分类具体是怎么做的?整体链路怎么实现?
答案:
文章分类一般不会直接从文件进模型,而是先做文档解析,把 Word、PDF、HTML、TXT 等格式统一转成结构化文本。然后做清洗、去噪、分段,提取标题、摘要、正文、目录、章节名等字段,再进入分类模型。
如果是短文档,可以直接用 BERT 类模型做分类。如果是长文档,就会先切 chunk,每个 chunk 单独编码,然后把 chunk 级别的表示再聚合成文档级表示,最后做分类。线上一般还会加规则兜底,比如标题里出现非常强的业务关键词时,可以提高某个类别的权重。
代码上,一个简单的文本分类流程大概是这样:
from transformers import AutoTokenizer, AutoModel
import torch
import torch.nn as nn
class DocClassifier(nn.Module):
def __init__(self, model_name, num_labels):
super().__init__()
self.encoder = AutoModel.from_pretrained(model_name)
self.dropout = nn.Dropout(0.1)
self.classifier = nn.Linear(self.encoder.config.hidden_size, num_labels)
def forward(self, input_ids, attention_mask):
outputs = self.encoder(input_ids=input_ids, attention_mask=attention_mask)
cls = outputs.last_hidden_state[:, 0, :]
logits = self.classifier(self.dropout(cls))
return logits
这个只适合短文本或者截断后的文本。如果文档很长,就不能只靠这个结构。
4. 用 BERT 做文章分类时,是把整篇文章都放进去吗?
答案:
一般不会。BERT 的输入长度通常是 512 token,中文文章稍微长一点就会超过这个长度。如果直接截断,只保留前 512 个 token,可能会丢掉正文后面的关键信息。比如合同、标书、审计报告、技术文档,重要结论不一定在开头。
比较常见的做法是把文章切成多个 chunk,每个 chunk 送进 BERT 得到一个向量,然后再把这些 chunk 向量聚合成文档向量。聚合方式可以是 mean pooling、max pooling、attention pooling,也可以用一个上层 Transformer 建模 chunk 之间的关系。
简单实现如下:
def split_text(text, max_len=400, stride=100):
chunks = []
start = 0
while start < len(text):
chunks.append(text[start:start + max_len])
start += max_len - stride
return chunks
这里用 stride 是为了避免切分边界把一句话或者一个关键信息截断。实际业务里还会优先按标题、段落、章节来切,而不是纯按长度切。
5. 如果 Word 文档有多个分类维度,自动分类应该怎么做?
答案:
多维度文档分类一般不是单标签分类,而是多任务或者多标签问题。比如一个文档既要判断所属业务线,又要判断文档类型,还要判断风险等级、保密等级、适用部门,这些维度之间可能有关联,但不能简单合成一个超大类别。
我会倾向于共享一个文档编码器,然后每个维度接一个独立分类头。这样底层语义表示可以共享,上层每个任务单独学习自己的标签空间。比如业务类型是 20 类,风险等级是 4 类,文档来源是 6 类,那就接三个 head。
class MultiTaskDocClassifier(nn.Module):
def __init__(self, encoder, hidden_size, task_labels):
super().__init__()
self.encoder = encoder
self.heads = nn.ModuleDict({
task: nn.Linear(hidden_size, num_labels)
for task, num_labels in task_labels.items()
})
def forward(self, doc_vector):
return {
task: head(doc_vector)
for task, head in self.heads.items()
}
如果某些维度允许一个文档属于多个标签,就不能用 softmax,而要用 sigmoid + BCEWithLogitsLoss。多维度分类的核心不是把标签硬拼起来,而是把标签体系设计清楚。
6. 如果一个文档有 5000 页,怎么做分类?
答案:
5000 页的文档不能直接丢给任何常规分类模型。这个时候要把分类问题变成一个层级证据聚合问题,先从文档中找出对分类最有用的部分,再基于这些证据做判断。
比较合理的流程是先解析目录、标题、页眉页脚、章节结构,然后按章节或页面分块。每个块先做粗筛,判断它和目标分类维度有没有关系。只保留高相关 chunk,再进行精排、摘要和分类。最后模型不是基于全文判断,而是基于“关键证据片段 + 结构信息 + 摘要”判断。
这种任务里,最重要的是不要平均处理所有页面。5000 页里可能只有几十页真正决定分类,比如摘要、目录、结论、风险提示、技术方案、财务表格说明等。工程上可以设计两阶段模型:第一阶段召回相关片段,第二阶段做文档级分类。
7. 你会怎么给上千页甚至 2GB 的大文档打分?
答案:
大文档打分不能直接让模型读全文后给分,而是要把评分标准拆成若干可验证的评分项。比如合规性、完整性、风险程度、技术可行性、证据充分性,每一项都要对应可检索的证据。
实际做法是先把文档切块并建立索引,然后针对每个评分项生成查询,从文档中召回相关片段。模型只基于这些片段给单项分,并要求输出引用依据。最后再把各项分数按权重汇总成总分。
def final_score(item_scores, weights):
score = 0
for item, value in item_scores.items():
score += value * weights.get(item, 0)
return round(score, 2)
item_scores = {
"completeness": 82,
"risk": 65,
"feasibility": 78
}
weights = {
"completeness": 0.4,
"risk": 0.3,
"feasibility": 0.3
}
print(final_score(item_scores, weights))
对于 2GB 文件,重点不是模型有多大,而是文档解析、分块索引、证据召回和评分一致性。否则模型给出来的分数很容易变成主观判断,难以复核。
8. 你处理过的文档一般有多大?不同大小的文档方案有什么区别?
答案:
如果是几页到几十页的文档,可以直接按段落切分,召回关键段落后分类。几百页的文档就需要明显的章节结构处理,因为全文 chunk 数量已经比较多,不能全部送入
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏聚焦 AI-Agent 面试高频考点,内容来自真实面试与项目实践。系统覆盖大模型基础、Prompt工程、RAG、Agent架构、工具调用、多Agent协作、记忆机制、评测、安全与部署优化等核心模块。以“原理+场景+实战”为主线,提供高频题解析、标准答题思路与工程落地方法,帮助你高效查漏补缺.