1. 项目概述从零到一的Brat标注实战最近在做一个信息抽取相关的项目需要构建一个高质量的实体识别数据集。数据标注是模型训练前最基础、也最磨人的一步选对工具能省下大把时间。在对比了Prodigy、Label Studio、Doccano等一众工具后我最终还是选择了Brat。原因很简单它开源、免费、对NLP标注尤其是实体、关系、事件的支持非常专业而且社区生态成熟。不过Brat的安装和后续的数据格式转换对于新手来说确实有几个“坑”。这次我就把从零安装Brat到完成标注再到一键将标注结果转换为模型训练所需的BIO格式的完整过程以及踩过的所有坑和解决方案详细记录下来。如果你也正在为构建命名实体识别NER数据集发愁这篇实战指南应该能帮你少走很多弯路。Brat本身是一个基于Web的协作式文本标注工具它生成的标注文件是.ann格式这是一种对人类阅读友好、但对机器学习模型不太友好的格式。而BIOB-I-O格式则是序列标注任务如NER的标准输入格式之一。所谓BIO即BBegin实体开头、IInside实体内部、OOutside非实体。我们最终的目标就是高效地完成标注并自动化地完成从.ann到BIO格式的转换为后续接入BERT、RoBERTa等预训练模型进行微调扫清障碍。2. Brat快速部署与环境配置详解2.1 系统环境选择与准备工作Brat是一个用Python 2写的古老但健壮的工具这意味着它对现代操作系统环境有一定要求。我强烈推荐在Linux系统如Ubuntu 20.04/22.04 LTS或macOS上部署这会避免绝大多数兼容性问题。Windows虽然可以通过WSL2完美运行但纯Windows环境配置会相对复杂。首先确保你的系统有python2.7和pip。虽然Python 2已停止维护但Brat依赖它。同时我们还需要apache2作为Web服务器。# 更新包列表并安装必要组件 sudo apt-get update sudo apt-get install -y python2.7 python2.7-dev apache2 libapache2-mod-wsgi git接下来为Brat创建一个专用的用户和目录这有利于权限管理和安全隔离。# 创建名为‘brat’的系统用户并指定其家目录 sudo useradd -m -d /var/www/brat -s /bin/bash brat # 切换到brat用户的家目录 cd /var/www/brat2.2 获取Brat源码与基础配置我们直接从官方GitHub仓库克隆最新稳定版本的代码。# 以brat用户身份操作注意以下命令可能需要切换用户或使用sudo -u brat sudo -u brat git clone https://github.com/nlplab/brat.git .克隆完成后目录下会出现brat的主代码。现在需要进行初始配置。Brat提供了一个交互式的配置脚本。# 运行安装脚本 sudo -u brat ./install.sh脚本会提示你输入一系列信息管理员邮箱和密码用于登录Brat Web界面进行管理。邮件服务器设置如果不需要邮件通知功能可以直接回车跳过。主机名或IP地址填写你服务器的公网IP或域名。如果仅在本地使用填写localhost或127.0.0.1即可。注意安装脚本默认会尝试安装一些Python依赖。如果遇到网络问题导致pip安装失败你可能需要手动安装依赖sudo -u brat pip2 install -r requirements.txt。安装脚本最后会提示你创建一个初始的工作目录data和一个示例项目example。务必同意创建这是后续所有标注工作的基础。2.3 Apache服务器配置与权限攻坚这是Brat安装中最容易出错的一步。我们需要配置Apache让它能够正确代理Brat的WSGI应用并处理好文件权限。首先创建Brat的Apache配置文件。sudo vim /etc/apache2/sites-available/brat.conf将以下配置内容写入文件。请将/var/www/brat替换为你实际的Brat安装路径。VirtualHost *:80 # 服务器域名或IP本地使用可用localhost ServerName your-server-ip-or-domain # 静态文件如图片、CSS、JS的别名设置 Alias /brat/static /var/www/brat/brat/static/ Directory /var/www/brat/brat/static/ Require all granted Options Indexes /Directory # Brat主应用通过WSGI运行 WSGIScriptAlias /brat /var/www/brat/brat/wsgi.py Directory /var/www/brat/brat Files wsgi.py Require all granted /Files /Directory # 最关键的部分标注数据目录的权限设置 Alias /brat/data /var/www/brat/data Directory /var/www/brat/data # 必须允许Apache进程对该目录有写权限 Require all granted Options Indexes MultiViews AllowOverride None Order allow,deny allow from all /Directory ErrorLog ${APACHE_LOG_DIR}/brat_error.log CustomLog ${APACHE_LOG_DIR}/brat_access.log combined /VirtualHost保存退出后启用该站点配置并重载Apache。sudo a2ensite brat.conf sudo systemctl reload apache2现在打开浏览器访问http://your-server-ip/brat你应该能看到Brat的登录界面。用安装时设置的管理员账号登录。第一个常见错误500 Internal Server Error 或 403 Forbidden这几乎都是权限问题。Apache进程通常是www-data用户需要对Brat的data目录有读写权限。# 将data目录及其所有子目录的所有者改为www-data用户 sudo chown -R www-data:www-data /var/www/brat/data # 赋予该目录可读、可写、可执行的权限 sudo chmod -R 775 /var/www/brat/data第二个常见错误“Installation error: DATA DIRECTORY NOT FOUND”登录后如果看到这个错误说明Apache无法找到或访问data目录。请检查Apache配置中Alias /brat/data的路径是否正确。上述的权限设置是否已执行。确保/var/www/brat/data目录确实存在并且里面有一个index.html文件由安装脚本创建。解决这些问题后刷新页面你应该能看到Brat的管理界面并看到example项目。3. 标注体系设计与实战标注流程3.1 定义标注规范与配置文件在开始标注前必须规划好你的标注体系。Brat通过annotation.conf文件来定义实体类型、关系和事件。我们以构建一个“科技新闻人物与机构”NER数据集为例。进入你的项目目录例如我们在data下新建一个ner_project目录。cd /var/www/brat/data sudo -u www-data mkdir ner_project cd ner_project创建annotation.conf文件sudo -u www-data vim annotation.conf文件内容如下[entities] # 定义实体类型 PERSON # 人物 ORG # 组织机构公司、学校等 PRODUCT # 产品、技术 LOCATION # 地理位置 DATE # 日期 [relations] # 本例暂不定义关系专注于实体 [events] # 本例暂不定义事件 [attributes] # 可以为实体添加属性例如实体链接ID同时你需要准备待标注的纯文本文件.txt。Brat要求文本文件使用UTF-8编码。将你的文本文件如news1.txt放入ner_project目录。3.2 Web界面标注实操与技巧在浏览器中访问你的Brat项目如http://localhost/brat/#/ner_project加载文本后就可以开始标注了。选择实体类型在右侧边栏点击实体类型如PERSON。标注在文本中用鼠标拖拽选中一个词或短语。例如选中“张三”系统会自动创建一个PERSON实体。标注不连续实体有时实体是跨段落的Brat不支持或不连续的词组如“北京大学人民医院”可能被拆开。对于不连续部分Brat支持通过按住Shift键进行多选但体验一般。更常见的做法是将其标注为一个整体或后续在数据处理阶段进行规则合并。重叠实体Brat原生不支持嵌套或重叠实体如“北京大学生”中“北京大学”是ORG“大学生”可能属于其他类别。这是一个局限。变通方法是采用更细粒度的标注规范或者训练能处理嵌套实体的模型如使用Span-based模型而非序列标注。标注心得一致性是关键提前制定详细的标注指南Guideline明确边界情况例如“腾讯公司”标为ORG那“腾讯”单独出现时标不标。善用快捷键Brat支持快捷键如a切换标注模式能极大提升效率。定期备份data目录下的.ann和.txt文件就是你的全部数据务必定期备份。完成一批文本的标注后你的项目目录下会为每个.txt文件生成一个同名的.ann文件。这就是Brat的原生标注文件。4. 核心转换从Brat ANN到BIO格式的一行代码4.1 Brat ANN格式解析一个典型的.ann文件内容如下T1 PERSON 0 3 张三 T2 ORG 8 15 北京大学 T3 DATE 20 30 2023年10月1日每一行代表一个标注T1标注ID。PERSON实体类型。0 3实体在文本中的起止字符偏移量从0开始左闭右开。张三实体对应的文本。4.2 一行代码转换的脚本与原理我们的目标是将上述信息结合原始文本转换成如下BIO格式这里以字为单位也可以用词张 B-PERSON 三 I-PERSON 北 B-ORG 京 I-ORG 大 I-ORG 学 I-ORG 2 B-DATE 0 I-DATE 2 I-DATE 3 I-DATE 年 I-DATE 1 I-DATE 0 I-DATE 月 I-DATE 1 I-DATE 日 I-DATE转换的核心思路是根据.ann文件中的字符偏移量将文本的每个字符或分词后的每个词映射到对应的实体标签B-XXX, I-XXX或O非实体。下面这个Python脚本ann2bio.py就是实现“一行代码”转换的核心。你需要将其放在包含所有.txt和.ann文件的目录下运行。#!/usr/bin/env python2 # -*- coding: utf-8 -*- import os import sys import codecs def ann2bio(txt_file, ann_file, output_file): 将一对 Brat 的 .txt 和 .ann 文件转换为 BIO 格式。 以字符为单位进行标注。 # 读取文本 with codecs.open(txt_file, r, utf-8) as f: text f.read() # 初始化标签列表全部为O tags [O] * len(text) # 读取并解析 .ann 文件 with codecs.open(ann_file, r, utf-8) as f: for line in f: line line.strip() if not line.startswith(T): # 只处理实体标注行 continue parts line.split(\t) if len(parts) 3: continue type_and_span parts[1].split() if len(type_and_span) 3: continue entity_type type_and_span[0] start int(type_and_span[1]) end int(type_and_span[2]) # 将实体范围内的字符标签设置为 B- 和 I- for i in range(start, end): if i start: tags[i] B-{}.format(entity_type) else: tags[i] I-{}.format(entity_type) # 写入BIO格式文件 with codecs.open(output_file, w, utf-8) as f_out: for char, tag in zip(text, tags): # 处理换行符和空格通常将换行符视为特殊分隔符空格标为O if char \n: f_out.write(\n) # 空行表示句子结束 elif char : f_out.write({} O\n.format(_)) # 空格用下划线等占位符表示 else: f_out.write({} {}\n.format(char, tag)) f_out.write(\n) # 文件末尾加一个空行 if __name__ __main__: # 获取当前目录下所有txt文件 for filename in os.listdir(.): if filename.endswith(.txt): base_name filename[:-4] txt_file filename ann_file base_name .ann output_file base_name .bio if os.path.exists(ann_file): print(fProcessing {txt_file}...) ann2bio(txt_file, ann_file, output_file) print(f - Saved to {output_file}) else: print(fWarning: {ann_file} not found, skipping {txt_file}.)所谓“一行代码自动标注”其实就是指在配置好环境后在项目根目录执行一条命令python2 ann2bio.py这条命令会自动遍历当前目录下所有.txt文件找到对应的.ann文件并生成同名的.bio文件。这大大简化了繁琐的格式转换流程。4.3 转换后的处理与模型适配生成的BIO文件可以直接用于像CRF、BiLSTM-CRF等传统序列标注模型。但如果要使用BERT、RoBERTa等Transformer预训练模型还需要进一步处理分词对齐BERT等模型使用WordPiece或BPE子词分词器。我们的字符级BIO标签需要与分词后的子词subword对齐。这是一个关键步骤通常的规则是一个词的首个子词继承原标签B-或I-后续的子词则被标记为X在Hugging Face Transformers库中常用-100忽略其损失或特殊的I-标签。构建数据集将BIO文件按比例分割为训练集、验证集和测试集。创建标签映射将B-PERSON,I-PERSON,B-ORG,O等标签映射为数字ID。你可以使用Hugging Face的datasets库或自定义脚本完成这些步骤。核心是确保分词后的input_ids和labels两个序列能正确对齐。5. 进阶问题排查与性能优化指南5.1 安装与运行中的典型错误错误1ImportError: No module named past这是因为缺少future库。Brat的某些代码兼容Python 2和3需要这个库。sudo -u www-data pip2 install future错误2Apache日志出现“Permission denied: ‘/.brat’”Brat会在用户家目录下创建配置缓存。需要给Apache进程www-data相应的权限。sudo mkdir -p /var/www/.brat sudo chown www-data:www-data /var/www/.brat也可以在Brat的config.py中显式设置CACHE_DIR到一个有权限的路径。错误3标注保存失败页面提示“Server Error”检查Apache错误日志/var/log/apache2/brat_error.log。最常见的原因是data目录或其子目录的权限不对Apache进程无法写入新的.ann文件。确保整个data目录树的所有权都是www-data并且有写权限775。5.2 大规模标注的工程化建议当标注数据量很大时原始的手工操作和脚本转换可能效率低下。自动化预处理在将文本放入Brat前可以用规则或弱监督方法如用已有的NER模型预测进行预标注生成初始的.ann文件标注员只需进行修正和审核这可以大幅提升效率这就是“主动学习”或“人机回环”的思路。版本控制使用Git来管理data目录下的标注文件.txt和.ann便于追踪标注变更、解决冲突特别是在多人协作时。编写质量检查脚本定期运行脚本检查标注一致性例如检查所有I-XXX标签前面是否一定有B-XXX或I-XXX检查实体偏移量是否超出文本范围统计各类实体数量确保数据平衡。转换流水线将ann2bio.py脚本集成到你的数据处理流水线中并加入分词对齐、数据集分割、格式转换如转换为CoNLL、JSONL格式等步骤实现从原始标注到模型训练数据的一键生成。5.3 与深度学习框架的集成生成的BIO文件是起点。以Hugging Face Transformers库为例一个典型的集成流程如下使用TokenClassificationpipeline如果你只是快速验证可以使用pipeline(“token-classification”)但它对自定义数据集的加载不够灵活。自定义Dataset类继承torch.utils.data.Dataset编写自己的数据加载类。在__getitem__方法中完成文本读取、分词、标签对齐、构建attention_mask等所有操作。关键标签对齐函数这是核心中的核心。下面是一个简化的示例函数展示如何将字符级BIO标签与BERT分词器对齐from transformers import AutoTokenizer import torch tokenizer AutoTokenizer.from_pretrained(bert-base-chinese) def align_labels(text, char_level_labels, tokenizer): 将字符级标签与分词后的token对齐。 # 分词获取每个token对应的字符位置 encoded tokenizer(text, return_offsets_mappingTrue, truncationTrue, max_length512) tokens encoded.tokens() offset_mapping encoded[offset_mapping] aligned_labels [] for token, (start, end) in zip(tokens, offset_mapping): # 跳过特殊token如[CLS], [SEP] if start end: aligned_labels.append(-100) # PyTorch CrossEntropyLoss 忽略索引 else: # 取该token覆盖的第一个字符的标签 # 这里简化处理取起始字符的标签。更复杂的策略可能需要考虑整个span。 label char_level_labels[start] # 如果是B-或I-标签需要根据情况调整 # 例如如果一个词被分成多个子词只有第一个子词保留B-/I-标签后面的标为-100 # 这里为了示例我们简单传递第一个字符的标签实际项目需要更精细的逻辑 aligned_labels.append(label_to_id.get(label, label_to_id[O])) return tokens, aligned_labels在实际项目中你需要构建一个从标签字符串到ID的映射字典label_to_id并妥善处理子词标签的分配问题通常使用“B-”标签保留给词的首个子词后续子词标为“I-”或-100。通过以上步骤你就打通了从Brat可视化标注到自动化格式转换再到直接喂给深度学习模型训练的完整链路。这套方法不仅适用于NER经过适当调整后也能用于关系抽取、事件抽取等更复杂的标注任务。数据标注是AI项目的地基花时间搭建好这个高效、可靠的流程后续的模型迭代和效果提升才会事半功倍。