CCF BDCI 剧本角色情感识别:多目标学习开源方案

简介: CCF BDCI 剧本角色情感识别:多目标学习开源方案

1、赛题名称


剧本角色情感识别

比赛链接:https://www.datafountain.cn/competitions/518


2、赛题背景


剧本对影视行业的重要性不言而喻。一部好的剧本,不光是好口碑和大流量的基础,也能带来更高的商业回报。剧本分析是影视内容生产链条的第一环,其中剧本角色的情感识别是一个非常重要的任务,主要是对剧本中每句对白和动作描述中涉及到的每个角色从多个维度进行分析并识别出情感。相对于通常的新闻、评论性文本的情感分析,有其独有的业务特点和挑战。


3、赛题任务


本赛题提供一部分电影剧本作为训练集,训练集数据已由人工进行标注,参赛队伍需要对剧本场景中每句对白和动作描述中涉及到的每个角色的情感从多个维度进行分析和识别。该任务的主要难点和挑战包括:1)剧本的行文风格和通常的新闻类语料差别较大,更加口语化;2)剧本中角色情感不仅仅取决于当前的文本,对前文语义可能有深度依赖。


4 数据简介


比赛的数据来源主要是一部分电影剧本,以及爱奇艺标注团队的情感标注结果,主要用于提供给各参赛团队进行模型训练和结果验证使用。


数据说明


训练数据:训练数据为txt格式,以英文制表符分隔,首行为表头,字段说明如下:

字段名称 类型 描述 说明
id String 数据ID -
content String 文本内容 剧本对白或动作描写
character String 角色名 文本中提到的角色
emotion String 情感识别结果(按顺序) 爱情感值,乐情感值,惊情感值,怒情感值,恐情感值,哀情感值


备注:

  1)本赛题的情感定义共6类(按顺序):爱、乐、惊、怒、恐、哀;

  2)情感识别结果:上述6类情感按固定顺序对应的情感值,情感值范围是[0, 1, 2, 3],0-没有,1-弱,2-中,3-强,以英文半角逗号分隔;

  3)本赛题不需要识别剧本中的角色名;

  文件编码:UTF-8 无BOM编码


5 评估标准


本赛题算法评分采用常用的均方根误差(RMSE)来计算评分,按照“文本内容+角色名”识别出的6类情感对应的情感值来统计。


32.png


score = 1/(1 + RMSE)

其中是yi,j预测的情感值,xi,j是标注的情感值,n是总的测试样本数。

最终按score得分来排名。


6 基于预训练模型的对目标学习


这个题目可操作的地方有很多,一开始见到这个比赛的时候见想到了multi outputs的模型构建,这里给大家分享下这个基线,希望有大佬能够针对这个思路优化上去~


6.1 加载数据


首先读取数据

with open('data/train_dataset_v2.tsv', 'r', encoding='utf-8') as handler:
    lines = handler.read().split('\n')[1:-1]
    data = list()
    for line in tqdm(lines):
        sp = line.split('\t')
        if len(sp) != 4:
            print("ERROR:", sp)
            continue
        data.append(sp)
train = pd.DataFrame(data)
train.columns = ['id', 'content', 'character', 'emotions']
test = pd.read_csv('data/test_dataset.tsv', sep='\t')
submit = pd.read_csv('data/submit_example.tsv', sep='\t')
train = train[train['emotions'] != '']


提取情感目标

train['emotions'] = train['emotions'].apply(lambda x: [int(_i) for _i in x.split(',')])
train[['love', 'joy', 'fright', 'anger', 'fear', 'sorrow']] = train['emotions'].values.tolist()


6.2 构建数据集


数据集的标签一共有六个:

class RoleDataset(Dataset):
    def __init__(self,texts,labels,tokenizer,max_len):
        self.texts=texts
        self.labels=labels
        self.tokenizer=tokenizer
        self.max_len=max_len
    def __len__(self):
        return len(self.texts)
    def __getitem__(self,item):
        """
        item 为数据索引,迭代取第item条数据
        """
        text=str(self.texts[item])
        label=self.labels[item]
        encoding=self.tokenizer.encode_plus(
            text,
            add_special_tokens=True,
            max_length=self.max_len,
            return_token_type_ids=True,
            pad_to_max_length=True,
            return_attention_mask=True,
            return_tensors='pt',
        )
#         print(encoding['input_ids'])
        sample = {
            'texts': text,
            'input_ids': encoding['input_ids'].flatten(),
            'attention_mask': encoding['attention_mask'].flatten()
        }
        for label_col in target_cols:
            sample[label_col] = torch.tensor(label[label_col], dtype=torch.float)
        return sample


6.3 模型构建


33.png

class EmotionClassifier(nn.Module):
    def __init__(self, n_classes):
        super(EmotionClassifier, self).__init__()
        self.bert = BertModel.from_pretrained(PRE_TRAINED_MODEL_NAME)
        self.out_love = nn.Linear(self.bert.config.hidden_size, n_classes)
        self.out_joy = nn.Linear(self.bert.config.hidden_size, n_classes)
        self.out_fright = nn.Linear(self.bert.config.hidden_size, n_classes)
        self.out_anger = nn.Linear(self.bert.config.hidden_size, n_classes)
        self.out_fear = nn.Linear(self.bert.config.hidden_size, n_classes)
        self.out_sorrow = nn.Linear(self.bert.config.hidden_size, n_classes)
    def forward(self, input_ids, attention_mask):
        _, pooled_output = self.bert(
            input_ids=input_ids,
            attention_mask=attention_mask,
            return_dict = False
        )
        love = self.out_love(pooled_output)
        joy = self.out_joy(pooled_output)
        fright = self.out_fright(pooled_output)
        anger = self.out_anger(pooled_output)
        fear = self.out_fear(pooled_output)
        sorrow = self.out_sorrow(pooled_output)
        return {
            'love': love, 'joy': joy, 'fright': fright,
            'anger': anger, 'fear': fear, 'sorrow': sorrow,
        }


6.4 模型训练


回归损失函数直接选取 nn.MSELoss()

EPOCHS = 1 # 训练轮数
optimizer = AdamW(model.parameters(), lr=3e-5, correct_bias=False)
total_steps = len(train_data_loader) * EPOCHS
scheduler = get_linear_schedule_with_warmup(
  optimizer,
  num_warmup_steps=0,
  num_training_steps=total_steps
)
loss_fn = nn.MSELoss().to(device)


模型总的loss为六个目标值的loss之和

def train_epoch(
  model, 
  data_loader, 
  criterion, 
  optimizer, 
  device, 
  scheduler, 
  n_examples
):
    model = model.train()
    losses = []
    correct_predictions = 0
    for sample in tqdm(data_loader):
        input_ids = sample["input_ids"].to(device)
        attention_mask = sample["attention_mask"].to(device)
        outputs = model(
            input_ids=input_ids,
            attention_mask=attention_mask
        )
        loss_love = criterion(outputs['love'], sample['love'].to(device))
        loss_joy = criterion(outputs['joy'], sample['joy'].to(device))
        loss_fright = criterion(outputs['fright'], sample['fright'].to(device))
        loss_anger = criterion(outputs['anger'], sample['anger'].to(device))
        loss_fear = criterion(outputs['fear'], sample['fear'].to(device))
        loss_sorrow = criterion(outputs['sorrow'], sample['sorrow'].to(device))
        loss = loss_love + loss_joy + loss_fright + loss_anger + loss_fear + loss_sorrow
        losses.append(loss.item())
        loss.backward()
        nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
        optimizer.step()
        scheduler.step()
        optimizer.zero_grad()
#     return correct_predictions.double() / (n_examples*6), np.mean(losses)
    return np.mean(losses)


线上提交0.67+

相关文章
|
存储 缓存 算法
[转]分布式唯一ID生成方案
分布式唯一ID生成方案
566 0
[转]分布式唯一ID生成方案
|
开发框架 网络协议 Ubuntu
【Linux】配置网络和firewall防火墙(超详细介绍+实战)
【Linux】配置网络和firewall防火墙(超详细介绍+实战)
5050 0
|
5月前
|
Linux 虚拟化 iOS开发
VMware Fusion 13.6.4 OEM BIOS 2.7 - 在 macOS 中运行 Windows 虚拟机的最佳方式
VMware Fusion 13.6.4 OEM BIOS 2.7 - 在 macOS 中运行 Windows 虚拟机的最佳方式
608 3
|
Java Nacos 开发工具
【Nacos】心跳断了怎么办?!8步排查法+实战代码,手把手教你解决Nacos客户端不发送心跳检测问题,让服务瞬间恢复活力!
【8月更文挑战第15天】Nacos是一款广受好评的微服务注册与配置中心。然而,“客户端不发送心跳检测”的问题时有发生,可能导致服务实例被视为离线。本文介绍如何排查此类问题:确认Nacos服务器地址配置正确;检查网络连通性;查看客户端日志;确保Nacos SDK版本兼容;调整心跳检测策略;验证服务实例注册状态;必要时重启应用;检查影响行为的环境变量。通过这些步骤,通常可定位并解决问题,保障服务稳定运行。
916 0
|
监控 安全 数据可视化
开源的网络监控工具:Sniffnet,简单而有趣!
开源的网络监控工具:Sniffnet,简单而有趣!
1764 0
|
消息中间件 存储 设计模式
JavaScript发布订阅模式:实现事件驱动编程!
JavaScript发布订阅模式:实现事件驱动编程!
|
Java 关系型数据库 MySQL
Seata常见问题之Seata1.7.1不支持jdk1.8如何解决
Seata 是一个开源的分布式事务解决方案,旨在提供高效且简单的事务协调机制,以解决微服务架构下跨服务调用(分布式场景)的一致性问题。以下是Seata常见问题的一个合集
|
网络协议 测试技术 网络架构
双网卡双线路DNS解析分析
在企业网络维护过程中我们经常会遇到这样或那样的奇怪问题,而很多问题需要有深厚的理论知识才能解决。而随着网络的飞速发展越来越多的中小企业开始尝试通过多条线路来保证网络的畅通,一方面双网卡下的双线接入可以保证我们的速度有所提升,彻底解决网通与电信双网交叉访问问题;另一方面双线接入保证企业网络即使有一条线路出现问题另外一条也可以马上接管工作,从而保证企业网络畅通无阻,为企业外网接入提供冗余功能。
2762 0
|
Java Maven 数据安全/隐私保护
Could not transfer artifact from/to Authentication failed for 401 Unauthorized
Could not transfer artifact from/to Authentication failed for 401 Unauthorized
816 0