简介源自国际语义评测SemEval的SemEval-2014 Task 4数据集是自然语言处理中细粒度情感分析与方面级情感分析的经典基准面向研究者、算法工程师及高校学生可用于训练和评估识别评论中具体方面及其情感极性的模型。压缩包共11个文件以10份XML数据文件为主涵盖餐厅与笔记本电脑两大领域的训练集、验证集及ABSA测试数据PhaseA/PhaseB其中训练数据对食物、服务、价格等具体方面进行了情感极性标注另含1份PDF版标注指南便于理解标注规范和评测标准。目前已有1217人学习下载。该资源省去了逐条下载的麻烦使用者可直接用于情感词典构建、情感目标抽取、情感极性判断及深度学习模型如RNN、LSTM、BERT的实验对比也可作为统一基准与其他团队公平比较算法性能。 做情感分析的人早晚都会撞上SemEval-2014 Task 4这份数据集。它是一份zip压缩包十多年前发布的评测数据装的是Laptop和Restaurant两个领域的评论文本和方面级情感标注。到今天只要你想跑基于方面的情感分析ABSA的baseline或者想验证一下LLM在细粒度情感识别上到底行不行这份数据依旧是绕不开的试金石。这篇文章不聊综述也不复述论文就从一个实际使用者的角度把这份zip从解压到训练的全过程拆一遍里面到底有什么、标注是什么逻辑、解析时会踩什么坑、怎么把它变成模型能吃的格式。1. 十年前的情感分析benchmark为什么今天还要专门聊它先说清楚一个很多人忽略的事实SemEval-2014 Task 4并不是一个普通的分类数据集它是国际语义评测大会SemEval在2014年组织的“基于方面的情感分析”Aspect Based Sentiment AnalysisABSA评测任务。当年参赛队伍拿到的就是我现在说的这个zip赛方提供的训练集和测试集都是XML格式的评论文本覆盖两个领域笔记本电脑评论和餐厅评论。这份数据后来成了ABSA方向引用率最高的公开基准之一很多经典论文和开源框架都用它做验证。1.1 四个子任务一份数据全包含SemEval-2014 Task 4的核心在于它把情感分析从“整句正面/负面”这种粗粒度判断精细到了“句子里的哪个方面、属于什么类别、表达什么情感”。具体拆成四个子任务Aspect Term ExtractionAT方面词抽取从句子里抽出明确写出来的方面词比如“battery life”、“the food”、“service”。Aspect Term PolarityAP方面词倾向判断判断抽取出的每个方面词对应的情感极性通常有positive、negative、neutral、conflict四类。Aspect Category DetectionAC方面类别识别为句子里的隐含主题分配预定义类别比如Restaurant域里的FOOD#QUALITY、SERVICE#GENERAL。Aspect Category PolarityAC上极性判断判断每个方面类别对应的情感极性。如果不做抽取只做AC和AC极性判断数据集中同样提供了预标注好的category和polarity所以你可以把AT/AP当作一个序列标注加分类任务也可以把AC相关当作多标签分类任务来研究。这个“一份数据四个玩法”的结构是它这么多年还能持续产出论文的重要原因。1.2 两个领域的差异决定了适用场景Laptop和Restaurant虽然都来自真实用户评论但语言风格完全不同。Laptop评论句子偏长、技术词密集比如“the keyboard is comfortable but the screen is too reflective”Restaurant评论更口语化常见“the pizza was amazing, but the waiter was rude”这类转折明显的句子。这意味着同一个模型在两个领域上的表现可能差很多所以评测时通常要分开报告结果而不是混合计算。很多人第一次跑实验时图省事把两个域合并最后指标虚高审稿人一眼就能看出来。2. 数据标注体系拆解从句子文本到类别极性一层层剥开打开zip之后训练目录下通常是Laptop_Train.xml、Laptop_Test_Gold.xml、Restaurants_Train.xml、Restaurants_Test_Gold.xml四个主文件外加一个README。XML名字里的“Gold”表示测试集带标注对应文件就是官方用来评测的黄金标注。理解这份XML结构是整个预处理的核心。?xml version1.0 encodingUTF-8? Reviews Review rid1004293 sentences sentence id1004293:0 textI charged the battery overnight, and the battery still died quickly./text aspectTerms aspectTerm termbattery polaritynegative from7 to14/ aspectTerm termbattery polaritynegative from43 to50/ /aspectTerms aspectCategories aspectCategory categoryLAPTOP#BATTERY polaritynegative/ /aspectCategories /sentence sentence id1004293:1 textAfter a week the keyboard started to rattle./text aspectTerms aspectTerm termkeyboard polaritynegative from12 to20/ /aspectTerms aspectCategories aspectCategory categoryLAPTOP#KEYBOARD polaritynegative/ /aspectCategories /sentence /sentences /Review /Reviews2.1 aspectTerm和aspectCategory到底有什么区别这是新手最容易混淆的地方。aspectTerm是句子中明确出现的词它的from/to属性是字符偏移指向text里的起止位置aspectCategory是抽象主题可能没有对应的具体词出现。比如服务员没时间理人句子里可能完全没出现“service”这个词但模型必须判断出属于SERVICE#GENERAL且情感是negative。所以AP任务依赖抽取结果AC任务更像是文本分类。从训练角度理解两者的关系aspectTerm是一个短语级别的实体识别问题aspectCategory是句子级的分类问题。很多进阶工作会把两者联合建模比如先抽取term再根据term的上下文推断category这就是后来ASTEAspect Sentiment Triplet Extraction的雏形。你如果从零搭模型建议先分别跑通单任务再考虑联合。2.2 类别体系和标签分布Restaurant域的aspectCategory是一个两段式结构前半是实体FOOD、SERVICE、AMBIENCE、PRICE、ANECDOTES后半是属性QUALITY、STYLE_OPTIONS、PRICE、MISCELLANEOUS中间用#连接。Laptop域则直接用LAPTOP作为实体前缀搭配BATTERY、DISPLAY、KEYBOARD、SOFTWARE等属性。两套体系的设计思路略有差异实际处理时不要跨域套用。域常见类别示例是否包含ANECDOTESLaptopLAPTOP#BATTERY, LAPTOP#DISPLAY, LAPTOP#KEYBOARD, LAPTOP#SOFTWARE, LAPTOP#PRICE否RestaurantFOOD#QUALITY, FOOD#STYLE_OPTIONS, SERVICE#GENERAL, AMBIENCE#GENERAL, PRICE#GENERAL是很多复现实验会忽略一个细节Restaurant域的ANECDOTES#GENERAL类别的样本量很小但有些论文统计时把它算进去有些直接去掉导致不同论文之间的类别数对不上。你写实验设置时最好明确说自己用哪个版本、去掉还是保留否则评测结果缺少可比性。2.3 polarity里的conflict处理方式影响结果四分类polarity中有个特殊标签conflict表示一个方面同时包含正面和负面信息。比如“battery life is great, but it still drains fast”里的battery很可能被标注为conflict。不同论文对conflict的处理极不统一有人保留为第四类有人直接丢弃有人映射成negative。你会看到同一个模型在不同论文里F1差一两个点往往就是conflict处理方式不同造成的。建议在你的代码里把conflict单独处理并且明确写进文档。3. 拿到zip之后解压、字符编码、XML解析里的那些坑这份zip下载下来通常几十MB压得非常小解压后也没多大。但真正折磨人的不是体积而是解析过程中的细节。我第一次处理这份数据时光是把XML洗干净就折腾了半天现在把关键问题一次性说清楚。3.1 用Python标准库解析避免引入额外依赖解析这份XML不需要高大上的工具Python自带的xml.etree.ElementTree就够用。需要注意的是原XML里text节点的内容会有HTML实体比如quot;、amp;、apos;。用ElementTree的text属性拿到的字符串已经做过实体解码但文件里的from/to偏移仍然是基于原始文本的。在大多数情况下两者能对上但如果句子包含被实体化的引号或尖括号直接用字符串切片就可能错位。import xml.etree.ElementTree as ET def parse_semeval2014(xml_path): tree ET.parse(xml_path) root tree.getroot() data [] for review in root.findall(Review): for sent in review.findall(.//sentence): text sent.find(text).text aspects [] for at in sent.findall(.//aspectTerm): aspects.append({ term: at.attrib.get(term), polarity: at.attrib.get(polarity), from: int(at.attrib.get(from)), to: int(at.attrib.get(to)), }) cats [] for ac in sent.findall(.//aspectCategory): cats.append({ category: ac.attrib.get(category), polarity: ac.attrib.get(polarity), }) data.append({text: text, aspectTerms: aspects, aspectCategories: cats}) return data如果你需要准确按偏移切词建议先对text做一次实体解码记录映射表再计算真正的字符位置。严谨的做法是写一个自定义的offset mapping函数把XML实体解码前后的位置对应关系存下来这样无论文本里有几个转义符都不会切错。3.2 常见报错和排查办法很多人卡在解析这一步是因为简单的问题把训练集文件和测试集Gold文件的文件名搞混XML根节点不是Reviews或者Windows下打开文件时默认编码不对。我自己遇到最多的是Python 2时代遗留的编码问题但现在用Python 3encodingutf-8基本能解决。如果ElementTree报“parse error”先别急着改代码用记事本或VS Code打开XML文件看前几行确认?xml version1.0 encodingUTF-8?是不是完整。有些从非官方渠道下载的副本会丢失这行声明导致解析失败。遇到这种情况补上声明或者用ET.parse时传入容错方式都能绕过去。3.3 建议先转成中间格式再喂模型我现在的习惯是先把XML解析后统一转成一份中间JSON存起来再写数据加载器。这样后续实验只需要读JSON不用每次启动都解析一遍XML。尤其当你做数据增强或者多组实验时节省的时间非常可观。简单的中间格式可以设计成{ text: The screen is crisp and bright, but the speakers are weak., aspectTerms: [ {term: screen, polarity: positive, from: 4, to: 10}, {term: speakers, polarity: negative, from: 39, to: 47} ], aspectCategories: [ {category: LAPTOP#DISPLAY, polarity: positive}, {category: LAPTOP#SPEAKERS, polarity: negative} ] }4. 从XML到模型输入一条能直接落地的Baseline链路把数据处理成模型输入之前你得先明确自己在做四个子任务中的哪一个。不同子任务的标签构造方式完全不一样下面分开说。4.1 Aspect Term Extraction走BIO序列标注抽取任务通常转换成BIO标注把句子里的每个token标记为B-term、I-term或O。这一步的关键是tokenization要对齐偏移。如果用BERT的WordPiece分词“battery”可能拆成“battery”一个token但“keyboard”也可能拆成“key”、“board”所以你不能简单按空格打BIO而是要用tokenizer返回的offset mapping把aspectTerm的from/to位置映射到token索引上。from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(bert-base-uncased) def encode_for_extraction(sample, max_len128): encoding tokenizer(sample[text], max_lengthmax_len, truncationTrue, return_offsets_mappingTrue, return_tensorspt) offsets encoding[offset_mapping][0] labels [O] * len(offsets) for aspect in sample[aspectTerms]: start, end aspect[from], aspect[to] token_start token_end None for idx, (s, e) in enumerate(offsets): if s start and token_start is None: token_start idx if e end: token_end idx if token_start is not None and token_end is not None: labels[token_start] B-term for idx in range(token_start 1, token_end 1): labels[idx] I-term return encoding[input_ids][0], encoding[attention_mask][0], labels这段代码的思路是先拿到offset mapping也就是每个token对应原文本的起止位置再拿aspectTerm的偏移去匹配token边界。注意匹配的是位置而不是字符串这样做更稳。测试下来BERT-based的序列标注模型在两个域上的AT任务F1能到80以上但前提是你把特殊token的位置[CLS]、[SEP]排除在loss计算之外。4.2 Aspect Term Polarity把aspect信息拼接进句子极性判断的输入构造相对简单常见做法是把方面词和句子拼起来让模型判断这个方面在上下文里的情感。比如“screen is crisp”加上“The screen is crisp and bright, but the speakers are weak.”模型要能捕捉到but后面带来的转折影响。标准输入格式是这样的def encode_for_polarity(sample, aspect_term): encoded tokenizer( aspect_term, sample[text], truncationonly_second, max_length128, paddingmax_length, return_tensorspt ) return encoded[input_ids][0], encoded[attention_mask][0]这里我用truncationonly_second保证句子过长时优先保留第二段也就是完整上下文同时方面词不被截断。别小看这个参数默认的截断策略可能把句尾的转折信息截掉导致极性判断错误。我自己跑Restaurant域的实验时仅仅改了对齐截断方式AP任务的准确率就涨了一个百分点。4.3 评测用官方脚本还是自己写官方评测用的指标是ACC准确率和F1AT任务重点看Aspect Term的F1AC任务看精确率和召回率。你可以去SemEval仓库找官方评测脚本也可以用sklearn的classification_report自己算。需要注意官方对AT的评测是按aspectTerm整体匹配算的也就是说如果模型抽出了“screen”而标准是“led screen”即使部分重叠也算错误。这个严格匹配标准让抽取任务的F1普遍偏低别一看数值没到90就觉得模型废了。5. 这份数据还够用吗怎么和现在的方法对接经常有人问SemEval-2014的数据这么老了现在还用会不会被审稿人质疑。我的看法是它不是万能的但它仍然是验证ABSA方法最稳妥的starting point。5.1 它的局限领域少、样本量小Laptop训练集大概三千多句Restaurant也差不多三千多句和动辄百万条的预训练语料比它确实很小。这导致直接在这份数据上从头训练深度模型几乎必过拟合所以现在主流做法都是加载预训练模型后微调或者用prompt-based方法做few-shot。另一个局限是只有2014年的标注规范类别体系相对粗糙比如Laptop域没有“ANECDOTES”这种杂项类别遇到样本外话题模型容易分错。5.2 和后续数据集的搭配使用后续SemEval-2015、SemEval-2016在Task 4基础上扩展了数据规模和新类别MAMS、ACOS、ASTE等数据集也陆续出现。如果你要做学术论文建议在SemEval-2014上做主要验证再选一两个后续数据集测迁移能力。这样既能说明方法在经典基准上的表现又能证明它对数据分布变化有一定鲁棒性。我见过不少工作只用这份zip跑几个表格就投出去结果rebuttal时被问泛化性问题非常被动。5.3 LLM时代它反而更值钱大模型流行之后这份数据的作用反而被放大了。现在很多LLM评测会直接从这份zip里取几百条样本作为few-shot示例让模型在“给定aspect和文本输出polarity”的任务设定下做推理。因为它的标注粒度细、类别体系清晰非常适合用来评估LLM是否真能理解某个方面在不同上下文中的情感。你可以把前文的代码稍加改造把aspect terms、categories直接拼成prompt送进GPT或开源模型输出的结构化文本再解析回标签一套LLM评测链路几分钟就能搭起来。我自己现在做实验的习惯是先在这份数据上跑一个BERT baseline确定合理指标范围再用同一套预清洗流程去测LLM最后才谈新方法。如果不先把数据链路跑通后面所有的性能对比都是空中楼阁。最后再分享一个排查数据加载的小技巧解析完XML后先打印统计信息比如句子总数、aspectTerm总数、aspectCategory总数、polarity分布然后和README里给的官方统计数字对一下。如果对不上说明你的解析逻辑或者过滤条件写错了。这个习惯帮我避免过无数个“模型结果异常”但其实数据早就处理错了的尴尬局面。SemEval-2014 Task 4这份zip虽然老但它的数据结构设计得相当扎实你把它彻底吃透后面接MAMS、ASTE、ACOS这些数据集都会轻松很多。本文还有配套的精品资源点击获取