PaddleNLP评论观点抽取和属性级情感分析

项目地址:PaddleNLP评论观点抽取和属性级情感分析 - 飞桨AI Studio星河社区 (baidu.com) 

情感分析旨在对带有情感色彩的主观性文本进行分析、处理、归纳和推理,其广泛应用于消费决策、舆情分析、个性化推荐等领域,具有很高的商业价值。

依托百度领先的情感分析技术,食行生鲜自动生成菜品评论标签辅助用户购买,并指导运营采购部门调整选品和促销策略;房天下向购房者和开发商直观展示楼盘的用户口碑情况,并对好评楼盘置顶推荐;国美搭建服务智能化评分系统,客服运营成本减少40%,负面反馈处理率100%。

情感分析相关的任务有语句级情感分析、评论对象抽取、观点抽取等等。一般来讲,被人们所熟知的情感分析任务是语句级别的情感分析,该任务是在宏观上去分析整句话的感情色彩,其粒度可能相对比较粗。

因为在人们进行评论的时候,往往针对某一产品或服务进行多个属性的评论,对每个属性的评论可能也会褒贬不一,因此针对属性级别的情感分析在真实的场景中会更加实用,同时更能给到企业用户或商家更加具体的建议。例如这句关于薯片的评论。

这个薯片味道真的太好了,口感很脆,只是包装很一般。

可以看到,顾客在口感、包装和味道 三个属性上对薯片进行了评价,顾客在味道和口感两个方面给出了好评,但是在包装上给出了负面的评价。只有通过这种比较细粒度的分析,商家才能更有针对性的发现问题,进而改进自己的产品或服务。

基于这样的考虑,本项目提出了一种细粒度的情感分析能力,对于给定的文本,首先会抽取该文本中的评论观点,然后分析不同观点的情感极性。

1. 方案设计

那么应该如何细粒度的分析语句中不同方面的评论呢?本实践的解决方案大致分为两个环节,首先需要进行评论观点抽取,接下来,便可以根据该评论观点去分析相应观点的情感极性。

1.1 评论观点抽取

在本实践中,我们将采用序列标注的方式进行评论观点抽取,具体而言,会抽取评论中的属性以及属性对应的观点,为此我们基于BIO的序列标注体系进行了标签的拓展:B-Aspect, I-Aspect, B-Opinion, I-Opinion, O,其中前两者用于标注评论属性,后两者用于标注相应观点。

如图1所示,首先将文本串传入SKEP模型中,利用SKEP模型对该文本串进行语义编码后,然后基于每个位置的输出去预测相应的标签。

图1 评价观点抽取模型 

1.2 属性级情感分类

在抽取完评论观点之后,便可以有针对性的对各个属性进行评论。具体来讲,本实践将抽取出的评论属性和评论观点进行拼接,然后和原始语句进行拼接作为一条独立的训练语句。

如图2所示,首先将评论属性和观点词进行拼接为"味道好",然后将"味道好"和原文进行拼接,然后传入SKEP模型,并使用"CLS"位置的向量进行细粒度情感倾向。

图2 属性级情感分类模型

2. 评论观点抽取模型

2.1 数据处理

2.1.1 数据集介绍

本实践中包含训练集、评估和测试3项数据集,以及1个标签词典。其中标签词典记录了本实践中用于抽取评论对象和观点词时使用的标签。

另外,本实践将采用序列标注的方式完成此任务,所以本数据集中需要包含两列数据:文本串和相应的序列标签数据,下面给出了一条样本。

还不错价格不高,服务好 O B-Opinion O B-Aspect I-Aspect B-Opinion I-Opinion O B-Aspect I-Aspect B-Opinion

2.2.2 数据加载

本节我们将训练、评估和测试数据集,以及标签词典加载到内存中。相关代码如下:

import os
import argparse
from functools import partial
import paddle
import paddle.nn.functional as F
from paddlenlp.metrics import ChunkEvaluator
from paddlenlp.datasets import load_dataset
from paddlenlp.data import Pad, Stack, Tuple
from paddlenlp.transformers import SkepTokenizer, SkepModel, LinearDecayWithWarmup
from utils.utils import set_seed
from utils import data_ext, data_clstrain_path = "./data/data121190/train_ext.txt"
dev_path = "./data/data121190/dev_ext.txt"
test_path = "./data/data121190/test_ext.txt"
label_path = "./data/data121190/label_ext.dict"# load and process data
label2id, id2label = data_ext.load_dict(label_path)
train_ds = load_dataset(data_ext.read, data_path=train_path, lazy=False)
dev_ds =  load_dataset(data_ext.read, data_path=dev_path, lazy=False)
test_ds =  load_dataset(data_ext.read, data_path=test_path, lazy=False)# print examples
print(train_ds[0])
print(train_ds[2])
{'text': ['兑', '换', '很', '方', '便', ','], 'label': ['B-Aspect', 'I-Aspect', 'O', 'B-Opinion', 'I-Opinion', 'O']}
{'text': ['骑', '车', '不', '错', '的', '地', '方'], 'label': ['O', 'O', 'B-Opinion', 'I-Opinion', 'O', 'B-Aspect', 'I-Aspect']}

2.2.3 将数据转换成特征形式

在将数据加载完成后,接下来,我们将各项数据集转换成适合输入模型的特征形式,即将文本字符串数据转换成字典id的形式。这里我们要加载paddleNLP中的SkepTokenizer,其将帮助我们完成这个字符串到字典id的转换。

model_name = "skep_ernie_1.0_large_ch"
batch_size = 8
max_seq_len = 512tokenizer = SkepTokenizer.from_pretrained(model_name)
trans_func = partial(data_ext.convert_example_to_feature, tokenizer=tokenizer, label2id=label2id, max_seq_len=max_seq_len)
train_ds = train_ds.map(trans_func, lazy=False)
dev_ds = dev_ds.map(trans_func, lazy=False)
test_ds = test_ds.map(trans_func, lazy=False)# print examples
print(train_ds[0])
print(train_ds[2])
([1, 2697, 806, 321, 58, 518, 4, 2], [0, 0, 0, 0, 0, 0, 0, 0], 8, [0, 1, 2, 0, 3, 4, 0, 0])
([1, 1570, 320, 16, 990, 5, 31, 58, 2], [0, 0, 0, 0, 0, 0, 0, 0, 0], 9, [0, 0, 0, 3, 4, 0, 1, 2, 0])

2.2.4 构造DataLoader

接下来,我们需要根据加载至内存的数据构造DataLoader,该DataLoader将支持以batch的形式将数据进行划分,从而以batch的形式训练相应模型。

batchify_fn = lambda samples, fn=Tuple(Pad(axis=0, pad_val=tokenizer.pad_token_id),Pad(axis=0, pad_val=tokenizer.pad_token_type_id),Stack(dtype="int64"),Pad(axis=0, pad_val= -1)): fn(samples)train_batch_sampler = paddle.io.BatchSampler(train_ds, batch_size=batch_size, shuffle=True)
dev_batch_sampler = paddle.io.BatchSampler(dev_ds, batch_size=batch_size, shuffle=False)
test_batch_sampler = paddle.io.BatchSampler(test_ds, batch_size=batch_size, shuffle=False)train_loader = paddle.io.DataLoader(train_ds, batch_sampler=train_batch_sampler, collate_fn=batchify_fn)
dev_loader = paddle.io.DataLoader(dev_ds, batch_sampler=dev_batch_sampler, collate_fn=batchify_fn)
test_loader = paddle.io.DataLoader(test_ds, batch_sampler=test_batch_sampler, collate_fn=batchify_fn)

2.3 模型构建

本案例中,我们将基于SKEP模型实现图1所展示的评论观点抽取功能。具体来讲,我们将处理好的文本数据输入SKEP模型中,SKEP将会对文本的每个token进行编码,产生对应向量序列。接下来,我们将基于该向量序列进行预测每个位置上的输出标签。相应代码如下。

class SkepForTokenClassification(paddle.nn.Layer):def __init__(self, skep, num_classes=2, dropout=None):super(SkepForTokenClassification, self).__init__()self.num_classes = num_classesself.skep = skepself.dropout = paddle.nn.Dropout(dropout if dropout is not None else self.skep.config["hidden_dropout_prob"])self.classifier = paddle.nn.Linear(self.skep.config["hidden_size"], num_classes)def forward(self, input_ids, token_type_ids=None, position_ids=None, attention_mask=None):sequence_output, _ = self.skep(input_ids, token_type_ids=token_type_ids, position_ids=position_ids, attention_mask=attention_mask)sequence_output = self.dropout(sequence_output)logits = self.classifier(sequence_output)return logits

2.4 训练配置

接下来,定义情感分析模型训练时的环境,包括:配置训练参数、配置模型参数,定义模型的实例化对象,指定模型训练迭代的优化算法等,相关代码如下。

# model hyperparameter  setting
num_epoch = 3
learning_rate = 3e-5
weight_decay = 0.01
warmup_proportion = 0.1
max_grad_norm = 1.0
log_step = 20
eval_step = 100
seed = 1000
checkpoint = "./checkpoint/"set_seed(seed)
use_gpu = True if paddle.get_device().startswith("gpu") else False
if use_gpu:paddle.set_device("gpu:0")
if not os.path.exists(checkpoint):os.mkdir(checkpoint)skep = SkepModel.from_pretrained(model_name)
model = SkepForTokenClassification(skep, num_classes=len(label2id))num_training_steps = len(train_loader) * num_epoch
lr_scheduler = LinearDecayWithWarmup(learning_rate=learning_rate, total_steps=num_training_steps, warmup=warmup_proportion)
decay_params = [p.name for n, p in model.named_parameters() if not any(nd in n for nd in ["bias", "norm"])]
grad_clip = paddle.nn.ClipGradByGlobalNorm(max_grad_norm)
optimizer = paddle.optimizer.AdamW(learning_rate=lr_scheduler, parameters=model.parameters(), weight_decay=weight_decay, apply_decay_param_fun=lambda x: x in decay_params, grad_clip=grad_clip)metric = ChunkEvaluator(label2id.keys())

2.5 模型训练与测试

本节我们将定义一个train函数和evaluate函数,其将分别进行训练和评估模型。在训练过程中,每隔log_steps步打印一次日志,每隔eval_steps步进行评估一次模型,并始终保存验证效果最好的模型。相关代码如下:

def evaluate(model, data_loader, metric):model.eval()metric.reset()for idx, batch_data in enumerate(data_loader):input_ids, token_type_ids, seq_lens, labels = batch_datalogits = model(input_ids, token_type_ids=token_type_ids)        # count metricpredictions = logits.argmax(axis=2)num_infer_chunks, num_label_chunks, num_correct_chunks = metric.compute(seq_lens, predictions, labels)metric.update(num_infer_chunks.numpy(), num_label_chunks.numpy(), num_correct_chunks.numpy())precision, recall, f1 = metric.accumulate()return precision, recall, f1def train():# start to train modelglobal_step, best_f1 = 1, 0.model.train()for epoch in range(1, num_epoch+1):for batch_data in train_loader():input_ids, token_type_ids, _, labels = batch_data# logits: batch_size, seql_len, num_tagslogits = model(input_ids, token_type_ids=token_type_ids)            loss = F.cross_entropy(logits.reshape([-1, len(label2id)]), labels.reshape([-1]), ignore_index=-1)loss.backward()lr_scheduler.step()optimizer.step()optimizer.clear_grad()if global_step > 0 and global_step % log_step == 0:print(f"epoch: {epoch} - global_step: {global_step}/{num_training_steps} - loss:{loss.numpy().item():.6f}")if (global_step > 0 and global_step % eval_step == 0) or global_step == num_training_steps:precision, recall, f1  = evaluate(model, dev_loader,  metric)model.train()if f1 > best_f1:print(f"best F1 performence has been updated: {best_f1:.5f} --> {f1:.5f}")best_f1 = f1paddle.save(model.state_dict(), f"{checkpoint}/best_ext.pdparams")print(f'evalution result: precision: {precision:.5f}, recall: {recall:.5f},  F1: {f1:.5f}')global_step += 1paddle.save(model.state_dict(), f"{checkpoint}/final_ext.pdparams")train()
epoch: 1 - global_step: 20/300 - loss:0.775109
epoch: 1 - global_step: 40/300 - loss:0.654723
epoch: 1 - global_step: 60/300 - loss:0.417590
epoch: 1 - global_step: 80/300 - loss:0.326240
epoch: 1 - global_step: 100/300 - loss:0.236355
best F1 performence has been updated: 0.00000 --> 0.71855
evalution result: precision: 0.65176, recall: 0.80058,  F1: 0.71855
epoch: 2 - global_step: 120/300 - loss:0.206213
epoch: 2 - global_step: 140/300 - loss:0.243644
epoch: 2 - global_step: 160/300 - loss:0.250103
epoch: 2 - global_step: 180/300 - loss:0.147552
epoch: 2 - global_step: 200/300 - loss:0.287132
best F1 performence has been updated: 0.71855 --> 0.76334
evalution result: precision: 0.72468, recall: 0.80636,  F1: 0.76334
epoch: 3 - global_step: 220/300 - loss:0.092556
epoch: 3 - global_step: 240/300 - loss:0.190176
epoch: 3 - global_step: 260/300 - loss:0.189123
epoch: 3 - global_step: 280/300 - loss:0.184761
epoch: 3 - global_step: 300/300 - loss:0.150254
best F1 performence has been updated: 0.76334 --> 0.77901
evalution result: precision: 0.74603, recall: 0.81503,  F1: 0.77901

接下来,我们将加载训练过程中评估效果最好的模型,并使用测试集进行测试。相关代码如下。

# load model
model_path = "./checkpoint/best_ext.pdparams"loaded_state_dict = paddle.load(model_path)
skep = SkepModel.from_pretrained(model_name)
model = SkepForTokenClassification(skep, num_classes=len(label2id))    
model.load_dict(loaded_state_dict)

3. 细粒度情感分类

3.1 数据处理

3.1.1 数据集介绍

本实践中包含训练集、评估和测试3项数据集,以及1个标签词典。其中标签词典记录了两类情感标签:正向和负向。

另外,数据集中需要包含3列数据:文本串和相应的序列标签数据,下面给出了一条样本,其中第1列是情感标签,第2列是评论属性和观点,第3列是原文。

1 口味清淡 口味很清淡,价格也比较公道

3.2.2 数据加载

本节我们将训练、评估和测试数据集,以及标签词典加载到内存中。相关代码如下:

import os
import argparse
from functools import partial
import paddle
import paddle.nn.functional as F
from paddlenlp.metrics import AccuracyAndF1
from paddlenlp.datasets import load_dataset
from paddlenlp.data import Pad, Stack, Tuple
from paddlenlp.transformers import SkepTokenizer, SkepModel, LinearDecayWithWarmup
from utils.utils import set_seed, decoding, is_aspect_first, concate_aspect_and_opinion, format_print
from utils import data_ext, data_clstrain_path = "./data/data121242/train_cls.txt"
dev_path = "./data/data121242/dev_cls.txt"
test_path = "./data/data121242/test_cls.txt"
label_path = "./data/data121242/label_cls.dict"# load and process data
label2id, id2label = data_cls.load_dict(label_path)
train_ds = load_dataset(data_cls.read, data_path=train_path, lazy=False)
dev_ds =  load_dataset(data_cls.read, data_path=dev_path, lazy=False)
test_ds =  load_dataset(data_cls.read, data_path=test_path, lazy=False)# print examples
print(train_ds[0])
print(train_ds[1])
{'label': 1, 'target_text': '环境不错', 'text': '服务和环境都不错……'}
{'label': 1, 'target_text': '不错造型', 'text': '服务态度挺好的,还给吹了一个不错的造型'}

3.2.3 将数据转换成特征形式

在将数据加载完成后,接下来,我们将各项数据集转换成适合输入模型的特征形式,即将文本字符串数据转换成字典id的形式。这里我们要加载paddleNLP中的SkepTokenizer,其将帮助我们完成这个字符串到字典id的转换。

model_name = "skep_ernie_1.0_large_ch"
batch_size = 8
max_seq_len = 512tokenizer = SkepTokenizer.from_pretrained(model_name)
trans_func = partial(data_cls.convert_example_to_feature, tokenizer=tokenizer, label2id=label2id, max_seq_len=max_seq_len)
train_ds = train_ds.map(trans_func, lazy=False)
dev_ds = dev_ds.map(trans_func, lazy=False)
test_ds = test_ds.map(trans_func, lazy=False)# print examples
print(train_ds[0])
print(train_ds[1])

3.2.4 构造DataLoader

接下来,我们需要根据加载至内存的数据构造DataLoader,该DataLoader将支持以batch的形式将数据进行划分,从而以batch的形式训练相应模型。

batchify_fn = lambda samples, fn=Tuple(Pad(axis=0, pad_val=tokenizer.pad_token_id),Pad(axis=0, pad_val=tokenizer.pad_token_type_id),Stack(dtype="int64"),Stack(dtype="int64")): fn(samples)train_batch_sampler = paddle.io.BatchSampler(train_ds, batch_size=batch_size, shuffle=True)
dev_batch_sampler = paddle.io.BatchSampler(dev_ds, batch_size=batch_size, shuffle=False)
test_batch_sampler = paddle.io.BatchSampler(test_ds, batch_size=batch_size, shuffle=False)train_loader = paddle.io.DataLoader(train_ds, batch_sampler=train_batch_sampler, collate_fn=batchify_fn)
dev_loader = paddle.io.DataLoader(dev_ds, batch_sampler=dev_batch_sampler, collate_fn=batchify_fn)
test_loader = paddle.io.DataLoader(test_ds, batch_sampler=test_batch_sampler, collate_fn=batchify_fn)

3.3 模型构建

本案例中,我们将基于SKEP模型实现图1所展示的评论观点抽取功能。具体来讲,我们将处理好的文本数据输入SKEP模型中,SKEP将会对文本的每个token进行编码,产生对应向量序列。我们使用CLS位置对应的输出向量进行情感分类。相应代码如下。

class SkepForSequenceClassification(paddle.nn.Layer):def __init__(self, skep, num_classes=2, dropout=None):super(SkepForSequenceClassification, self).__init__()self.num_classes = num_classesself.skep = skepself.dropout = paddle.nn.Dropout(dropout if dropout is not None else self.skep.config["hidden_dropout_prob"])self.classifier = paddle.nn.Linear(self.skep.config["hidden_size"], num_classes)def forward(self, input_ids, token_type_ids=None, position_ids=None, attention_mask=None):_, pooled_output = self.skep(input_ids, token_type_ids=token_type_ids, position_ids=position_ids, attention_mask=attention_mask)pooled_output = self.dropout(pooled_output)logits = self.classifier(pooled_output)return logits

3.4 训练配置

接下来,定义情感分析模型训练时的环境,包括:配置训练参数、配置模型参数,定义模型的实例化对象,指定模型训练迭代的优化算法等,相关代码如下。

# model hyperparameter  setting
num_epoch = 3
learning_rate = 3e-5
weight_decay = 0.01
warmup_proportion = 0.1
max_grad_norm = 1.0
log_step = 20
eval_step = 100
seed = 1000
checkpoint = "./checkpoint/"set_seed(seed)
use_gpu = True if paddle.get_device().startswith("gpu") else False
if use_gpu:paddle.set_device("gpu:0")
if not os.path.exists(checkpoint):os.mkdir(checkpoint)skep = SkepModel.from_pretrained(model_name)
model = SkepForSequenceClassification(skep, num_classes=len(label2id))num_training_steps = len(train_loader) * num_epoch
lr_scheduler = LinearDecayWithWarmup(learning_rate=learning_rate, total_steps=num_training_steps, warmup=warmup_proportion)
decay_params = [p.name for n, p in model.named_parameters() if not any(nd in n for nd in ["bias", "norm"])]
grad_clip = paddle.nn.ClipGradByGlobalNorm(max_grad_norm)
optimizer = paddle.optimizer.AdamW(learning_rate=lr_scheduler, parameters=model.parameters(), weight_decay=weight_decay, apply_decay_param_fun=lambda x: x in decay_params, grad_clip=grad_clip)metric = AccuracyAndF1()

3.5 模型训练与测试

本节我们将定义一个train函数和evaluate函数,其将分别进行训练和评估模型。在训练过程中,每隔log_steps步打印一次日志,每隔eval_steps步进行评估一次模型,并始终保存验证效果最好的模型。相关代码如下:

def evaluate(model, data_loader, metric):model.eval()metric.reset()for batch_data in data_loader:input_ids, token_type_ids, _, labels = batch_datalogits = model(input_ids, token_type_ids=token_type_ids)        correct = metric.compute(logits, labels)metric.update(correct)accuracy, precision, recall, f1, _ = metric.accumulate()return accuracy, precision, recall, f1def train():# start to train modelglobal_step, best_f1 = 1, 0.model.train()for epoch in range(1, num_epoch+1):for batch_data in train_loader():input_ids, token_type_ids, _, labels = batch_data# logits: batch_size, seql_len, num_tagslogits = model(input_ids, token_type_ids=token_type_ids)            loss = F.cross_entropy(logits, labels)loss.backward()lr_scheduler.step()optimizer.step()optimizer.clear_grad()if global_step > 0 and global_step % log_step == 0:print(f"epoch: {epoch} - global_step: {global_step}/{num_training_steps} - loss:{loss.numpy().item():.6f}")if (global_step > 0 and global_step % eval_step == 0) or global_step == num_training_steps:accuracy, precision, recall, f1  = evaluate(model, dev_loader,  metric)model.train()if f1 > best_f1:print(f"best F1 performence has been updated: {best_f1:.5f} --> {f1:.5f}")best_f1 = f1paddle.save(model.state_dict(), f"{checkpoint}/best_cls.pdparams")print(f'evalution result: accuracy:{accuracy:.5f} precision: {precision:.5f}, recall: {recall:.5f},  F1: {f1:.5f}')global_step += 1paddle.save(model.state_dict(), f"{checkpoint}/final_cls.pdparams")train()
epoch: 1 - global_step: 20/300 - loss:0.901484
epoch: 1 - global_step: 40/300 - loss:0.154271
epoch: 1 - global_step: 60/300 - loss:0.000010
epoch: 1 - global_step: 80/300 - loss:0.413460
epoch: 1 - global_step: 100/300 - loss:0.000254
best F1 performence has been updated: 0.00000 --> 0.98734
evalution result: accuracy:0.98000 precision: 0.98734, recall: 0.98734,  F1: 0.98734
epoch: 2 - global_step: 120/300 - loss:0.000208
epoch: 2 - global_step: 140/300 - loss:0.000025
epoch: 2 - global_step: 160/300 - loss:0.003228
epoch: 2 - global_step: 180/300 - loss:0.000084
epoch: 2 - global_step: 200/300 - loss:0.783448
evalution result: accuracy:0.98000 precision: 1.00000, recall: 0.97468,  F1: 0.98718
epoch: 3 - global_step: 220/300 - loss:0.000134
epoch: 3 - global_step: 240/300 - loss:0.000813
epoch: 3 - global_step: 260/300 - loss:0.000276
epoch: 3 - global_step: 280/300 - loss:0.002265
epoch: 3 - global_step: 300/300 - loss:0.000753
best F1 performence has been updated: 0.98734 --> 0.99363
evalution result: accuracy:0.99000 precision: 1.00000, recall: 0.98734,  F1: 0.99363

接下来,我们将加载训练过程中评估效果最好的模型,并使用测试集进行测试。相关代码如下。

# load model
model_path = "./checkpoint/best_cls.pdparams"loaded_state_dict = paddle.load(model_path)
skep = SkepModel.from_pretrained(model_name)
model = SkepForSequenceClassification(skep, num_classes=len(label2id))    
model.load_dict(loaded_state_dict)
accuracy, precision, recall, f1  = evaluate(model, test_loader,  metric)
print(f'evalution result: accuracy:{accuracy:.5f} precision: {precision:.5f}, recall: {recall:.5f},  F1: {f1:.5f}')
evalution result: accuracy:0.98000 precision: 0.98824, recall: 0.98824,  F1: 0.98824

4. 全流程模型推理

接下来,我们将加载上边训练完成的评论观点抽取模型和分类模型,进行模型推理,相应代码如下。

label_ext_path = "./data/data121190/label_ext.dict"
label_cls_path = "./data/data121242/label_cls.dict"
ext_model_path = "./checkpoint/best_ext.pdparams"
cls_model_path = "./checkpoint/best_cls.pdparams"# load dict
model_name = "skep_ernie_1.0_large_ch"
ext_label2id, ext_id2label = data_ext.load_dict(label_ext_path)
cls_label2id, cls_id2label = data_cls.load_dict(label_cls_path)
tokenizer = SkepTokenizer.from_pretrained(model_name)
print("label dict loaded.")# load ext model
ext_state_dict = paddle.load(ext_model_path)
ext_skep = SkepModel.from_pretrained(model_name)
ext_model = SkepForTokenClassification(ext_skep, num_classes=len(ext_label2id))    
ext_model.load_dict(ext_state_dict)
print("extraction model loaded.")# load cls model
cls_state_dict = paddle.load(cls_model_path)
cls_skep = SkepModel.from_pretrained(model_name)
cls_model = SkepForSequenceClassification(cls_skep, num_classes=len(cls_label2id))    
cls_model.load_dict(cls_state_dict)
print("classification model loaded.")
def predict(input_text, ext_model, cls_model, tokenizer, ext_id2label, cls_id2label, max_seq_len=512):ext_model.eval()cls_model.eval()# processing input textencoded_inputs = tokenizer(list(input_text), is_split_into_words=True, max_seq_len=max_seq_len,)input_ids = paddle.to_tensor([encoded_inputs["input_ids"]])token_type_ids = paddle.to_tensor([encoded_inputs["token_type_ids"]])# extract aspect and opinion wordslogits = ext_model(input_ids, token_type_ids=token_type_ids)predictions = logits.argmax(axis=2).numpy()[0]tag_seq = [ext_id2label[idx] for idx in predictions][1:-1]aps = decoding(input_text, tag_seq)# predict sentiment for aspect with cls_modelresults = []for ap in aps:aspect = ap[0]opinion_words = list(set(ap[1:]))aspect_text = concate_aspect_and_opinion(input_text, aspect, opinion_words)encoded_inputs = tokenizer(aspect_text, text_pair=input_text, max_seq_len=max_seq_len, return_length=True)input_ids = paddle.to_tensor([encoded_inputs["input_ids"]])token_type_ids = paddle.to_tensor([encoded_inputs["token_type_ids"]])logits = cls_model(input_ids, token_type_ids=token_type_ids)prediction = logits.argmax(axis=1).numpy()[0]result = {"aspect": aspect, "opinions": opinion_words, "sentiment": cls_id2label[prediction]}results.append(result)# print resultsformat_print(results)max_seq_len = 512
input_text = "环境装修不错,也很干净,前台服务非常好"
predict(input_text, ext_model, cls_model, tokenizer, ext_id2label, cls_id2label,  max_seq_len=max_seq_len)
aspect: 环境, opinions: [], sentiment: 正向
aspect: 装修, opinions: ['不错', '干净'], sentiment: 正向
aspect: 服务, opinions: ['好'], sentiment: 正向

参考文献: 

[1] H. Tian et al., “SKEP: Sentiment Knowledge Enhanced Pre-training for Sentiment Analysis,” arXiv:2005.05635 [cs], May 2020, Accessed: Nov. 11, 2021.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/635703.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#,因数分解(质因子分解)Pollard‘s Rho算法的源代码

因数分解(也称为质因子分解):将一个大整数分解它的质因子之乘积的算法。 Pollard Rho算法的基本思路:先判断当前数是否是素数(质数),如果是,则直接返回。如果不是,继续找…

11、Kafka ------ Kafka 核心API 及 生产者API 讲解

目录 Kafka核心API 及 生产者API讲解★ Kafka的核心APIKafka包含如下5类核心API: ★ 生产者APIKafka 的API 文档 ★ 使用生产者API发送消息 Kafka核心API 及 生产者API讲解 官方文档 ★ Kafka的核心API Kafka包含如下5类核心API: Producer API&#x…

spring data mongo 在事务中,无法自动创建collection

spring data mongo 在事务中,无法自动创建collection org.springframework.dao.DataIntegrityViolationException: Write operation error on server xxx:30001. Write error: WriteError{code=263, message=Cannot create namespace xxx.xxxin multi-document transaction.…

Redis(四)

1、Redis的单/多线程 1.1、单线程 其实直接说Redis什么单线程或者是多线程,不太准确,在redis的4.0版主之前是单线程,然后在之后的版本中redis的渐渐改为多线程。 Redis是单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的&#…

小白水平理解面试经典题目LeetCode 125 Valid Palindrome(验证回文串)

125 验证回文串 说到公司面试,那就是得考出高度,考出水平,什么兼顾这两者呢,那就得看这道 原题描述: 给定一个字符串,判断它是否是回文串。回文串是指正读和反读都一样的字符串。 输入: “A man, a pla…

超级弱口令检查工具

工具介绍 超级弱口令检查工具是一款Windows平台的弱口令审计工具,支持批量多线程检查,可快速发现弱密码、弱口令账号,密码支持和用户名结合进行检查,大大提高成功率,支持自定义服务端口和字典。 工具采用C#开发&#…

1.19(232.用栈实现队列)

1.19(232.用栈实现队列) 在push数据的时候,只要数据放进输入栈就好,但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入),再从出栈弹出数据&a…

unity-声音与声效OLD

声音与声效 基本概念audio clipaudio listeneraudio source 基本操作如何创建音频源(背景音乐)如何在测试的时候关闭声音 常用代码一般流程如何在一个物体上播放多个音效如何在代码中延时播放多个声音如何在代码中停止音频的播放如何判断当前是否在播放音…

福昕软件的使用

快捷操作 快捷键 快捷键功能备注Ctrl P打印 Ctrl W关闭 Ctrl B书签 鼠标放菜单栏,单击右键即可导入/导出 自定义菜单栏文件-->偏好设置-->文档 1、多实例:单击PDF后均重新打开一个新界面。

MySQL中SELECT字句的顺序以及具体使用

目录 1.SELECT字句及其顺序 2.使用方法举例 3.HAVING和WHERE 1.SELECT字句及其顺序 *下表来自于图灵程序设计丛书,数据库系列——《SQL必知必会》 2.使用方法举例 *题目来源于牛客网 题目描述 现在运营想要查看不同大学的用户平均发帖情况,并期望结…

[AI]文心一言出圈的同时,国外的ChatGPT-4.5最新资讯

前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家:https://www.captainbed.cn/z ChatGPT体验地址 文章目录 前言4.5key价格泄漏ChatGPT4.0使用地址ChatGPT正确打开方式最新功能语音助手存档…

为了Atcoder系列复习C++语法

很久之前学过忘了 为了打比赛重新复习 每打一次就更一次 含日语内容 B - 1.01.出力とコメント 1.cout << 2525 << endl; 可以没有endl 结尾. endl作用是换行 2.整除问题 int情况下1/2无法变成0.5 所以1/2应该放在后面 100 * (100 1) / 2 3.f…

五、模 板

1 泛型编程 以往我们想实现一个通用的交换函数&#xff0c;可能是通过下面的方式来实现的&#xff1a; void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(double& left, double& right) {double temp left;left ri…

【机器学习】四大类监督学习_模型选择与模型原理和场景应用_第03课

监督学习中模型选择原理及场景应用 监督学习应用场景 文本分类场景&#xff1a; o 邮件过滤&#xff1a;训练模型识别垃圾邮件和非垃圾邮件。 o 情感分析&#xff1a;根据评论或社交媒体内容的情感倾向将其分类为正面、负面或中性评价。 o 新闻分类&#xff1a;将新闻文章自动…

适用于 Windows 11 的 12 个最佳免费 PDF 编辑器

除了绘图等基本功能外&#xff0c;一些适用于 Windows 11 的免费 PDF 编辑器还具有 AI、OCR 识别和书签等高级功能。 我们的列表包含易于立即下载的 PDF 编辑软件工具。 这些工具不仅可以帮助转换 PDF、编辑、上传、删除、裁剪、分割、提取等。 PDF 是指便携式文档格式&…

【爬虫实战】2024知乎热榜可视化爬取

项目功能简介&#xff1a; 1.支持配置&#xff1b; 2.单次爬取&#xff1b; 3.循环爬取&#xff1b; 4.爬虫暂停&#xff1b; 5.数据清除&#xff1b; 6.数据保存到csv文件&#xff1b; 7.程序支持打包成exe文件&#xff1b; 8.项目操作说明文档&#xff1b; 9.模块封装到类&a…

Vue中的组件

在应用程序的开发中&#xff0c;组件是不可缺少的。在Vue的使用中&#xff0c;同样也会用到组件。   1、组件的名字唯一&#xff1b;   2、组件以Html形式书写&#xff1b;   3、组件可以复用&#xff1b;   4、组件可以嵌套&#xff1b;   5、组件可以相互调用&…

Docker(七)使用网络

作者主页&#xff1a; 正函数的个人主页 文章收录专栏&#xff1a; Docker 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01; Docker 中的网络功能介绍 Docker 允许通过外部访问容器或容器互联的方式来提供网络服务。 一、外部访问容器 容器中可以运行一些网络应用&…

【计算机网络】2、传输介质、通信方向、通信方式、交换方式、IP地址表示、子网划分

文章目录 传输介质双绞线无屏蔽双绞线UTP屏蔽双绞线STP 网线光纤多模光纤MMF单模光纤SMF 无线信道无线电波红外光波 通信方向单工半双工全双工 通信方式异步传输同步传输串行传输并行传输 交换方式电路交换报文交换分组交换 IP地址表示IP地址的定义IP地址的分类无分类编址特殊I…

NVIDIA 大模型 RAG 分享笔记

文章目录 大语言模型在垂直领域落地的三个挑战&#xff1a;什么是 RAG以及为什么能解决大预言模型所带来的的这三个问题RAG 不是一项技术而是整体的 Pipeline非参数化 &#xff1a;数据库部分加载到数据库中检索阶段 提升检索效率的技术检索前&#xff1a;对query做处理use que…