1. 项目概述当大语言模型学会“照章办事”在信息抽取这个领域里我们常常面临一个困境模型要么是“死记硬背”型的专家只能在训练过的、固定模式的任务上表现良好一旦遇到新的、从未见过的实体类型或关系定义就立刻“傻眼”要么是依赖“常识”的通用模型虽然能理解新概念但抽取结果往往随心所欲难以严格遵循我们精心设计的、包含各种约束和细节的标注规范。这就像你请了一位经验丰富的助手但他要么只会做你教过的那几件事要么就完全按自己的理解自由发挥总是不太“听话”。GoLLIE的出现正是为了解决这个核心痛点。它的全称是Guideline-following Large Language Model for Information Extraction直译过来就是“遵循指南的大语言模型用于信息抽取”。这个名字精准地概括了它的核心能力它被专门训练来理解和遵循人类编写的、详细的标注指南Guidelines。这意味着你可以像给实习生下发一份操作手册一样为GoLLIE定义一套全新的、复杂的抽取规则比如定义“航天发射任务”必须包含“发射日期”、“运载火箭”、“宇航员名单”等字段并且每个字段都有具体的格式和取值说明而GoLLIE能够像一位严谨的资深员工一样严格地按照这份手册去分析文本并输出结构化的结果。这个项目基于Meta开源的Code Llama模型进行微调目前提供了7B、13B和34B三种参数规模的版本。它的最大亮点在于零样本Zero-Shot信息抽取能力。你不需要用成千上万条标注数据去重新训练它只需要在推理时通过一种巧妙的结构化提示Prompt方式将你的标注规范“教”给它它就能立刻上岗工作。这对于快速构建领域特定的信息抽取系统、处理标注数据稀缺的长尾任务具有革命性的意义。2. 核心设计思路为什么“照章办事”如此困难又如此重要要理解GoLLIE的价值我们需要先拆解一下传统信息抽取方法特别是基于大语言模型LLM的方法在遵循复杂指南时面临的挑战。2.1 传统方法的局限从“死记硬背”到“自由发挥”在GoLLIE之前主流的信息抽取路径大致有两条监督学习模型如经典的BERT-CRF、Span-based模型等。这类模型是典型的“专家型”选手。它们在一个固定的、预定义的标签集如人名、地名、组织机构名上进行训练性能可以达到很高的水平。但它们的“知识”完全固化在模型参数中。如果你想让它识别一种新的实体类型比如“电影中的超能力”就必须重新收集数据、重新标注、重新训练模型。这个过程成本高昂、周期长缺乏灵活性。基于提示Prompting的通用LLM随着ChatGPT等模型的兴起人们开始尝试直接用自然语言指令让LLM进行抽取例如“请从下面这段话中找出所有公司的名字”。这种方法灵活度极高理论上可以处理任何新定义的任务。但问题也随之而来不一致性同样的指令模型在不同时间、不同上下文下可能给出格式完全不同的答案有时是列表有时是JSON有时是一段话。忽视细节复杂的指南往往包含许多约束条件如“日期必须格式化为YYYY-MM-DD”、“如果未提及则留空”。通用LLM很容易忽略这些细节或者根据自己的“常识”进行脑补而不是严格依据文本。幻觉Hallucination模型可能会输出文本中根本不存在的“事实”。问题的根源在于通用LLM的训练目标是“续写”出最符合统计规律的下一个词而不是“精确执行”一个结构化的、带有约束的指令。它更像一个富有创造力的作家而不是一个严谨的数据录入员。2.2 GoLLIE的破局之道将指南“编译”进模型的理解中GoLLIE的设计者洞察到了一个关键点标注指南本身是一种高度结构化的“代码”或“规范”。那么为什么不利用擅长理解结构化语言的模型来学习它呢这就是他们选择Code Llama作为基座模型的原因。Code Llama在大量代码数据上进行了预训练对类、函数、数据结构、文档字符串docstrings等编程概念有着深刻的理解。GoLLIE的微调过程本质上就是教会这个“程序员”模型如何将一段用自然语言和类定义Class Definition编写的“业务需求文档”即标注指南转化为从文本中“运行”并“输出”结构化数据的“程序”。GoLLIE的核心创新在于其“提示模板”。它没有使用简单的自然语言指令而是设计了一套基于Pythondataclass的模板。这套模板做了以下几件关键事结构化定义每个需要抽取的实体或事件都被定义为一个Python类。类的属性mention,date,crew等对应了需要抽取的字段。指南即文档详细的标注规则和示例被直接写入类的文档字符串 ... 和每个属性的注释中。这利用了代码模型对文档字符串的天然关注。上下文示例在提示中除了目标文本和定义还会包含少量通常是2-4个其他任务的输入输出示例。这些示例展示了模型应该如何“思考”和“格式化”输出起到了“少样本学习”的作用。强制结构化输出提示的结尾固定为result [模型的任务就是续写一个符合之前定义的、合法的Python对象列表。这种格式强制模型输出结构化的、可解析的结果极大减少了不一致性。通过在海量、多样的信息抽取任务数据上以上述格式进行指令微调GoLLIE内化了一种能力将“类定义文档字符串”这种形式的指南映射到“从文本中实例化对象”这个动作上。它学会了严格遵循属性类型字符串、列表等、尊重文档中的约束条件并只基于给定文本进行推理。注意这种方法的有效性强烈依赖于基座模型的结构化理解能力。这也是为什么选择Code Llama而非纯文本LLM的原因。Code Llama对语法、数据结构有更强的意识能更好地保证输出格式的正确性。3. 实操详解手把手玩转GoLLIE理解了原理我们来看看如何实际使用GoLLIE。项目提供了Hugging Face模型和详细的Jupyter Notebook这里我将结合官方示例和我的实操经验为你拆解每一步。3.1 环境搭建与模型加载首先你需要一个支持CUDA的GPU环境。GoLLIE的模型不小7B版本至少需要16GB GPU内存使用量化技术可降低34B版本则需要更大的显存。安装依赖 官方依赖列表比较全但根据我的经验可以按以下顺序安装避免版本冲突# 1. 安装PyTorch (请根据你的CUDA版本去官网获取对应命令) # 例如对于CUDA 11.8 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 2. 安装Transformers, PEFT, Bitsandbytes (用于量化加载) pip install transformers4.33.1 peft0.4.0 bitsandbytes0.40.0 # 3. 安装Flash Attention 2 (大幅加速注意力计算强烈建议安装) # 这一步可能因系统环境而异如果安装失败可以暂时跳过但推理速度会慢很多。 pip install flash-attn --no-build-isolation # 4. 安装其他工具库 pip install numpy jinja2 tqdm rich psutil datasets加载模型 GoLLIE模型已经集成到Hugging Face的transformers库中加载非常方便。这里以7B模型为例展示如何使用4-bit量化来在消费级显卡如24GB的RTX 4090上运行34B模型。from transformers import AutoTokenizer, AutoModelForCausalLM import torch model_id HiTZ/GoLLIE-7B # 或 HiTZ/GoLLIE-34B # 加载tokenizer tokenizer AutoTokenizer.from_pretrained(model_id) tokenizer.padding_side left # 对于生成任务通常左侧填充 tokenizer.pad_token tokenizer.eos_token # 使用EOS token作为填充token # 使用4-bit量化加载模型极大减少显存占用 model AutoModelForCausalLM.from_pretrained( model_id, torch_dtypetorch.float16, # 使用半精度浮点数 device_mapauto, # 自动将模型层分布到可用的GPU/CPU上 load_in_4bitTrue, # 启用4-bit量化 bnb_4bit_compute_dtypetorch.float16, bnb_4bit_quant_typenf4, # 使用NF4量化类型效果更好 ) model.eval() # 设置为评估模式实操心得load_in_4bitTrue是让大模型在有限显存上运行的关键。实测中34B模型量化后大约需要20GB显存。如果加载失败检查bitsandbytes库是否正确安装并确保你的PyTorch是CUDA版本。另外首次加载模型会下载约20GB7B或70GB34B的数据请确保网络通畅和磁盘空间充足。3.2 定义你的第一个抽取任务让我们复现论文中的经典例子从航天文本中抽取“发射器”和“任务”。关键在于构建那个结构化的提示。from dataclasses import dataclass from typing import List # 1. 定义你的数据类即标注规范 dataclass class Launcher: Refers to a vehicle designed primarily to transport payloads from the Earths surface to space. Launchers can carry various payloads, including satellites, crewed spacecraft, and cargo, into various orbits or even beyond Earths orbit. They are usually multi-stage vehicles that use rocket engines for propulsion. mention: str The name of the launcher vehicle. Such as: Saturn V, Atlas V, Soyuz, Ariane 5 space_company: str # The company that operates the launcher. crew: List[str] # Names of the crew members boarding the Launcher. dataclass class Mission: Any planned or accomplished journey beyond Earths atmosphere with specific objectives, either crewed or uncrewed. mention: str The name of the mission. Such as: Apollo 11, Artemis, Mercury date: str # The start date of the mission departure: str # The place from which the vehicle will be launched. destination: str # The place or planet to which the launcher will be sent. # 2. 准备提示模板 def build_prompt(task_definition: str, text: str) - str: prompt_template # Task: Extract information based on the following definitions. {task_definition} # This is the text to analyze: text \\\{input_text}\\\ # The annotation instances that take place in the text above are listed here: result [ return prompt_template.format(task_definitiontask_definition, input_texttext) # 3. 构建任务定义字符串这里简化了实际应包括更多示例 task_def_code \n.join([Launcher.__doc__, str(Launcher), Mission.__doc__, str(Mission)]) # 注意实际应用中需要更优雅地生成类定义的字符串表示包括属性注释。 # 4. 待分析的文本 input_text ( The Ares 3 mission to Mars is scheduled for 2032. The Starship rocket built by SpaceX will take off from Boca Chica, carrying the astronauts Max Rutherford, Elena Soto, and Jake Martinez. ) # 5. 构建完整提示 prompt build_prompt(task_def_code, input_text) print( 完整提示 ) print(prompt) print(*50)运行上面的代码你会得到一个结构化的提示它清晰地定义了任务并给出了待分析的文本。模型的任务就是补全result [之后的内容。3.3 执行推理与解析结果有了提示接下来就是让模型生成。# 编码提示词 inputs tokenizer(prompt, return_tensorspt, paddingTrue, truncationTrue).to(model.device) # 生成参数设置 generation_args { max_new_tokens: 256, # 生成的最大新token数 do_sample: False, # 为了确定性结果使用贪婪解码 temperature: 0.0, top_p: 1.0, eos_token_id: tokenizer.eos_token_id, pad_token_id: tokenizer.pad_token_id, } # 执行生成 with torch.no_grad(): outputs model.generate(**inputs, **generation_args) # 解码生成结果 # 只取生成的部分跳过输入提示 generated_sequence outputs[0][inputs[input_ids].shape[1]:] generated_text tokenizer.decode(generated_sequence, skip_special_tokensTrue) print( 模型生成结果 ) print(generated_text)理想情况下模型会输出类似这样的内容Mission(mentionAres 3, date2032, departureBoca Chica, destinationMars), Launcher(mentionStarship, space_companySpaceX, crew[Max Rutherford, Elena Soto, Jake Martinez]) ]解析结果 模型输出的是Python代码片段。为了安全地将字符串转换为对象我们可以使用ast.literal_eval或更安全地编写一个简单的解析器。import ast import re def parse_gollie_output(output_text: str): 解析GoLLIE的输出字符串尝试将其转换为对象列表。 这是一个简化的解析器实际应用可能需要更健壮的处理。 # 找到 result [ 之后的部分并补全为一个完整的列表表达式 # 假设输出是干净的以 ] 结尾 list_str [{ output_text.replace(),, },).replace((, {).replace(), }) ] # 将类名作为键例如 Mission(...) - {Mission: {...}} # 这里需要更精细的解析来匹配属性名以下仅为示意 pattern r(\w)\(([^)])\) matches re.findall(pattern, output_text) results [] for cls_name, args_str in matches: arg_dict {} for part in args_str.split(,): if in part: key, val part.split(, 1) key key.strip() val val.strip().strip(\) # 去除引号 # 处理列表类型的值 if val.startswith([): val ast.literal_eval(val) arg_dict[key] val results.append({type: cls_name, **arg_dict}) return results parsed_results parse_gollie_output(generated_text.strip()) print( 解析后的结构化结果 ) import pprint pprint.pprint(parsed_results)重要提示生产环境中解析模型输出需要极其谨慎。建议使用ast.literal_eval配合安全的环境。或者利用Python的dataclasses和eval极度危险不推荐或type动态创建类来实例化。最安全的方式是不执行模型生成的代码而是将其视为一种严格的序列化格式用自定义解析器提取键值对。官方Notebook中可能提供了更完善的解析工具建议优先参考。3.4 进阶技巧使用少样本示例提升效果零样本Zero-Shot虽然强大但在处理极其复杂或模糊的指南时效果可能不稳定。GoLLIE支持在提示中加入少量示例Few-Shot能显著提升表现。关键点在于如何构建示例示例必须与主任务遵循完全相同的格式。通常你需要为模型提供2-4个其他任务或同一任务不同实例的完整输入输出对。# 假设我们有一个“电影信息抽取”的少样本示例 few_shot_examples # Example 1: # Entity definitions for Movie dataclass class Movie: A cinematic film. title: str director: str release_year: int text \\\In 1994, Quentin Tarantino directed the iconic film Pulp Fiction.\\\ result [Movie(titlePulp Fiction, directorQuentin Tarantino, release_year1994)] # Example 2: # (另一个不同领域的示例比如书籍) dataclass class Book: A published work of literature. title: str author: str genre: str text \\\J.K. Rowlings fantasy novel Harry Potter and the Philosophers Stone was published in 1997.\\\ result [Book(titleHarry Potter and the Philosopher\s Stone, authorJ.K. Rowling, genrefantasy)] # 然后将这些示例插入到提示模板中放在任务定义和待分析文本之间。 def build_few_shot_prompt(task_def, examples, input_text): prompt f# Task: Extract information based on the following definitions. {task_def} # Below are some examples of how to perform similar tasks: {examples} # Now, analyze the following text: text \\\{input_text}\\\ result [ return prompt通过提供示例你实际上是在教模型“看当你看到这种格式的定义和文本时应该这样格式化和输出结果。”这对于对齐模型的输出格式和理解复杂约束至关重要。4. 训练你自己的GoLLIE从数据到模型如果你有特定领域的大量标注数据并且希望获得比零样本/少样本更好的性能可以尝试微调你自己的GoLLIE模型。这个过程需要较强的计算资源和数据准备能力。4.1 数据准备构建GoLLIE格式数据集GoLLIE的训练数据需要被转换成它特有的“代码提示”格式。项目提供了数据生成脚本但你需要准备好原始数据集。核心步骤获取数据集项目支持多种公开数据集如CoNLL-2003, OntoNotes, TACRED等。你需要根据configs/data_configs/下的配置文件下载或指定这些数据集的路径。注意像ACE05这样的数据集需要LICENSE。理解数据配置每个JSON配置文件定义了如何将原始标注转换为GoLLIE格式。例如conll03_config.json定义了如何将“PER”, “ORG”等实体类型包装成带有说明的dataclass。运行生成脚本python -m src.generate_data \ --configs configs/data_configs/conll03_config.json \ --output ./my_processed_data \ --include_examples--include_examples参数会在每个训练样本中包含少样本示例这对学习遵循指南至关重要。数据格式解析 生成的数据集每条样本可能长这样简化{ input: # Task: Extract Person, Organization, Location entities.\ndataclass\nclass Person:\n \\\A human being.\\\\n mention: str\n\n... (其他类定义) ...\n\n# Examples:\ntext \...\\nresult [...]\n\n# Text to analyze:\ntext \Apple is looking at buying U.K. startup for $1 billion.\\nresult [, output: Organization(mentionApple), Location(mentionU.K.)] }模型的任务就是学习从input到output的映射。4.2 模型训练配置与执行GoLLIE使用基于PEFTParameter-Efficient Fine-Tuning的QLoRA技术进行微调这可以在单张消费级显卡上微调大型模型。配置文件复制一份configs/model_configs/GoLLIE-7B_CodeLLaMA.yaml作为起点。关键配置项model_name_or_path: codellama/CodeLlama-7b-hf # 基座模型 data_path: ./data/processed_w_examples # 处理后的数据路径 output_dir: ./gollie-finetuned-7b # 输出目录 # QLoRA 配置 load_in_4bit: true bnb_4bit_compute_dtype: float16 bnb_4bit_quant_type: nf4 # 训练参数 num_train_epochs: 3 per_device_train_batch_size: 4 # 根据GPU内存调整 gradient_accumulation_steps: 4 learning_rate: 2e-4 lr_scheduler_type: cosine # 序列长度需要足够长以容纳复杂的提示 max_source_length: 2048 max_target_length: 512启动训练python -m src.run configs/my_gollie_config.yaml训练心得与避坑指南显存不足如果遇到CUDA out of memory首先降低per_device_train_batch_size增加gradient_accumulation_steps以保持总batch size。其次可以尝试gradient_checkpointing: true。序列长度max_source_length必须设置得足够大以容纳最长的提示包括所有类定义和示例。建议先分析数据集中提示的长度分布。数据质量确保生成的数据集中input和output的格式完全正确。一个常见的错误是输出格式与提示中的类定义不匹配如属性名拼写错误。评估项目也提供了评估脚本。在训练过程中或训练后在保留的验证集上运行评估查看F1分数等指标以判断模型是否学到了遵循指南的能力而不是简单地记忆训练数据中的实体。5. 常见问题、排查技巧与性能优化在实际使用和复现GoLLIE的过程中你可能会遇到以下问题。这里我整理了一份排查清单和优化建议。5.1 模型推理常见问题问题1模型输出格式错误无法解析。现象生成的文本不是有效的Pythondataclass实例化代码或者缺少括号、逗号。排查检查提示格式确保你的提示模板与训练数据格式完全一致特别是result [这一行空格和换行符都不要错。检查任务定义确保你的dataclass定义语法正确属性名是有效的Python标识符。使用少样本示例在提示中加入2-3个格式完美的示例可以极大地引导模型输出正确的格式。调整生成参数尝试将do_sample设为False贪婪解码temperature设为0以获得更确定性的输出。如果输出被截断增加max_new_tokens。根本原因模型在零样本情况下对输出格式的把握不够精确。微调过的GoLLIE在这方面已经很强但极端复杂的指南仍可能出错。问题2模型“幻觉”Hallucination抽取了文本中没有的信息。现象输出结果中的某个属性值在原文中找不到对应描述。排查审视指南的模糊性检查你的标注指南docstring是否足够清晰。例如“日期”属性是否明确要求必须是文本中明确提及的模型可能会根据常识推断日期。提供反例在少样本示例中可以包含一个属性值为None或空字符串的例子并说明“当文本未提及时应留空”。后处理校验实现一个简单的后处理步骤将模型输出的每个属性值回标到原文中检查是否存在原文片段与之匹配。如果没有则将该属性置为None或发出警告。根本原因大语言模型固有的补全倾向。即使被训练为遵循指南在信息模糊时仍可能依赖内部知识进行“合理”猜测。问题3抽取不全漏掉了明显的实体或关系。现象文本中明显存在符合定义的信息但模型没有输出。排查检查定义覆盖确认你定义的类如Launcher,Mission是否涵盖了文本中所有应被抽取的信息类型。检查属性约束属性注释中的示例Such as: ...是否具有代表性如果示例太窄模型可能无法泛化。文本长度输入文本是否过长超过了模型的上下文窗口Code Llama的上下文长度是4096或16384取决于版本提示本身会占用大量token留给文本的空间可能不足。考虑对长文档进行分块处理。尝试少样本提供包含类似实体的示例。5.2 性能与效率优化1. 推理速度慢启用Flash Attention 2如安装指南所述正确安装Flash Attention 2可以带来数倍的推理速度提升。确保你的PyTorch版本和CUDA环境与之兼容。使用量化如前所述使用load_in_4bitTrue加载模型不仅能降低显存占用在某些情况下也能轻微加速。批处理Batching如果需要处理大量文本尽量将多个提示组成一个批次输入模型。注意需要统一填充padding。# 假设prompts是一个提示字符串列表 inputs tokenizer(prompts, return_tensors“pt”, paddingTrue, truncationTrue, max_length2048).to(model.device) outputs model.generate(**inputs, **generation_args) # 然后分别解码每个结果2. 显存不足OOM量化是首选4-bit量化是运行大模型的利器。启用CPU Offload对于非常大的模型如34B即使量化后显存仍不足可以尝试使用accelerate库的device_map“sequential”或更精细的device_map设置将部分模型层卸载到CPU内存但会显著降低速度。减少批次大小和序列长度在训练和推理时这都是最直接的显存控制手段。5.3 领域适配建议GoLLIE的零样本能力很强但要让它在你的特定领域达到最佳效果可能需要一些“调教”。编写高质量的指南Docstring这是最重要的环节。指南应清晰无歧义用精确的语言描述实体或事件的边界。提供典型示例在属性注释中使用Such as: “Example1”, “Example2”。这比单纯的文字描述有效得多。说明排除情况如果可能说明什么“不属于”这个类别。构建有效的少样本示例多样性示例应覆盖不同类型的文本和不同的属性组合包括空值。相关性示例任务最好与你的目标任务在结构或领域上相似。格式完美确保示例的输出格式与你期望的完全一致。后处理管道不要完全信任模型的原始输出。建立一个后处理管道用于格式校验与修复自动修正常见的格式小错误如缺失的引号、尾随逗号。值标准化将模型抽取的日期、金额等字符串转换为标准格式。去重与融合对于可能被重复抽取的同一实体进行去重和证据融合。GoLLIE代表了一种将大语言模型与结构化约束相结合的新范式。它没有试图让模型“理解一切”而是聪明地利用模型的结构化语言能力让它成为一个可靠的“规则执行者”。对于需要快速构建可定制、高精度信息抽取系统的开发者来说它提供了一个极具吸引力的新工具。从简单的命名实体识别到复杂的事件图谱构建你只需要用Python类定义和自然语言描述出你的“规则手册”剩下的就可以交给这个学会了“照章办事”的AI助手了。