们假设情绪正负向分类为二分类问题,正向为1,负向为0。
步骤:
- 准备数据:我们构造一些关于电影《你好,李焕英》的评论,并标注情感倾向(0或1)。
- 构建Transformer模型:这里我们使用PyTorch和transformers库。我们将使用预训练的BERT模型,并在其基础上添加一个全连接层进行分类。
- 数据预处理:将文本转换为BERT模型所需的输入格式(input_ids, attention_mask等)。
- 训练模型:使用准备好的数据对模型进行训练。
- 评估模型:在测试集上评估模型性能。
注意:由于资源限制,我们这里只使用少量数据进行示例,实际应用中需要更多数据。
首先,我们构造一些示例数据。假设我们有以下评论:
我来为你构建一个完整的电影评论情感分类系统,使用《你好,李焕英》的评论数据。
一、准备示例数据
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
import torch
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertForSequenceClassification, AdamW, get_linear_schedule_with_warmup
from sklearn.metrics import accuracy_score, classification_report
import warnings
warnings.filterwarnings('ignore')
# 创建示例数据集 - 《你好,李焕英》的影评
comments = [
# 正向评论 (label=1)
("这部电影真的太感人了,看哭了三次,贾玲导演的处女作真的很棒!", 1),
("笑中带泪,温情满满,值得一看的好电影", 1),
("《你好,李焕英》让我想起了自己的妈妈,非常感动", 1),
("贾玲和张小斐的演技都很好,剧情也很真实", 1),
("今年看过的最好的国产电影,推荐大家去看", 1),
("母女情深,让人泪目,真挚的情感最打动人", 1),
("虽然有些地方不够成熟,但情感真挚,瑕不掩瑜", 1),
("春节档的惊喜之作,值得二刷", 1),
("笑点自然,哭点真实,一部走心的电影", 1),
("看到了贾玲对妈妈的思念,很温暖", 1),
# 负向评论 (label=0)
("剧情有些老套,感觉一般般吧", 0),
("笑点有些尴尬,没有预期的好看", 0),
("煽情过度,感觉有点刻意", 0),
("穿越的设定有些牵强,逻辑不够严谨", 0),
("对不起,我没有被感动到,可能期望太高了", 0),
("节奏有点慢,中间有点想睡觉", 0),
("感觉被过度营销了,实际观影体验一般", 0),
("不太喜欢这种类型的电影,个人感受", 0),
("有些情节太刻意了,不够自然", 0),
("整体来说比较平庸,没有特别出彩的地方", 0),
# 中性或混合评论 (可以标注为0或1,这里标为1表示偏正向)
("特效不错,但剧情有待加强,整体还可以", 1),
("演员演技在线,但剧本可以更好", 1),
("作为喜剧片合格,但深度不够", 0),
("适合带家人一起看,虽然有些瑕疵", 1),
]
# 转换为DataFrame
df = pd.DataFrame(comments, columns=['comment', 'label'])
# 查看数据分布
print("数据分布:")
print(df['label'].value_counts())
print(f"\n总样本数: {len(df)}")
print("\n前5条数据:")
print(df.head())
二、构建Transformer情感分类模型
class WeiboCommentDataset(Dataset):
"""自定义微博评论数据集"""
def __init__(self, comments, labels, tokenizer, max_len=128):
self.comments = comments
self.labels = labels
self.tokenizer = tokenizer
self.max_len = max_len
def __len__(self):
return len(self.comments)
def __getitem__(self, idx):
comment = str(self.comments[idx])
label = self.labels[idx]
# 对评论进行编码
encoding = self.tokenizer.encode_plus(
comment,
add_special_tokens=True,
max_length=self.max_len,
padding='max_length',
truncation=True,
return_attention_mask=True,
return_tensors='pt'
)
return {
'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'label': torch.tensor(label, dtype=torch.long)
}
class SentimentClassifier:
"""情感分类器主类"""
def __init__(self, model_name='bert-base-chinese', num_labels=2):
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {self.device}")
# 加载预训练模型和tokenizer
self.tokenizer = BertTokenizer.from_pretrained(model_name)
self.model = BertForSequenceClassification.from_pretrained(
model_name,
num_labels=num_labels
)
self.model.to(self.device)
def create_data_loader(self, df, batch_size=16, max_len=128):
"""创建数据加载器"""
dataset = WeiboCommentDataset(
comments=df['comment'].to_numpy(),
labels=df['label'].to_numpy(),
tokenizer=self.tokenizer,
max_len=max_len
)
return DataLoader(
dataset,
batch_size=batch_size,
num_workers=2,
shuffle=True
)
def train(self, train_df, val_df, epochs=5, batch_size=16, learning_rate=2e-5):
"""训练模型"""
train_loader = self.create_data_loader(train_df, batch_size)
val_loader = self.create_data_loader(val_df, batch_size)
# 优化器
optimizer = AdamW(self.model.parameters(), lr=learning_rate)
# 学习率调度器
total_steps = len(train_loader) * epochs
scheduler = get_linear_schedule_with_warmup(
optimizer,
num_warmup_steps=0,
num_training_steps=total_steps
)
# 训练循环
for epoch in range(epochs):
print(f'\nEpoch {epoch + 1}/{epochs}')
print('-' * 50)
# 训练阶段
self.model.train()
total_loss = 0
for batch in train_loader:
input_ids = batch['input_ids'].to(self.device)
attention_mask = batch['attention_mask'].to(self.device)
labels = batch['label'].to(self.device)
# 前向传播
outputs = self.model(
input_ids=input_ids,
attention_mask=attention_mask,
labels=labels
)
loss = outputs.loss
total_loss += loss.item()
# 反向传播
loss.backward()
torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
optimizer.step()
scheduler.step()
optimizer.zero_grad()
avg_train_loss = total_loss / len(train_loader)
# 验证阶段
val_acc, val_loss = self.evaluate(val_loader)
print(f'训练损失: {avg_train_loss:.4f}')
print(f'验证损失: {val_loss:.4f}')
print(f'验证准确率: {val_acc:.4f}')
def evaluate(self, data_loader):
"""评估模型"""
self.model.eval()
predictions = []
actual_labels = []
total_loss = 0
with torch.no_grad():
for batch in data_loader:
input_ids = batch['input_ids'].to(self.device)
attention_mask = batch['attention_mask'].to(self.device)
labels = batch['label'].to(self.device)
outputs = self.model(
input_ids=input_ids,
attention_mask=attention_mask,
labels=labels
)
loss = outputs.loss
total_loss += loss.item()
_, preds = torch.max(outputs.logits, dim=1)
predictions.extend(preds.cpu().tolist())
actual_labels.extend(labels.cpu().tolist())
accuracy = accuracy_score(actual_labels, predictions)
avg_loss = total_loss / len(data_loader)
return accuracy, avg_loss
def predict(self, comments):
"""预测单个或多个评论的情感"""
self.model.eval()
if isinstance(comments, str):
comments = [comments]
predictions = []
probabilities = []
for comment in comments:
encoding = self.tokenizer.encode_plus(
comment,
add_special_tokens=True,
max_length=128,
padding='max_length',
truncation=True,
return_attention_mask=True,
return_tensors='pt'
)
input_ids = encoding['input_ids'].to(self.device)
attention_mask = encoding['attention_mask'].to(self.device)
with torch.no_grad():
outputs = self.model(
input_ids=input_ids,
attention_mask=attention_mask
)
probs = torch.softmax(outputs.logits, dim=1)
pred = torch.argmax(probs, dim=1)
predictions.append(pred.item())
probabilities.append(probs.cpu().numpy())
return predictions, probabilities
def save_model(self, path='./weibo_sentiment_model'):
"""保存模型"""
self.model.save_pretrained(path)
self.tokenizer.save_pretrained(path)
print(f"模型已保存到 {path}")
def load_model(self, path='./weibo_sentiment_model'):
"""加载已保存的模型"""
self.model = BertForSequenceClassification.from_pretrained(path)
self.tokenizer = BertTokenizer.from_pretrained(path)
self.model.to(self.device)
print(f"已从 {path} 加载模型")
三、训练和评估模型
def main():
# 1. 准备数据
print("准备数据...")
df = pd.DataFrame(comments, columns=['comment', 'label'])
# 划分训练集和验证集
train_df, val_df = train_test_split(
df,
test_size=0.2,
random_state=42,
stratify=df['label']
)
print(f"训练集大小: {len(train_df)}")
print(f"验证集大小: {len(val_df)}")
# 2. 初始化分类器
print("\n初始化情感分类器...")
classifier = SentimentClassifier()
# 3. 训练模型
print("\n开始训练模型...")
classifier.train(
train_df=train_df,
val_df=val_df,
epochs=10, # 可以调整训练轮数
batch_size=8, # 可以调整批次大小
learning_rate=2e-5
)
# 4. 测试模型
print("\n测试模型...")
test_comments = [
"这部电影真的很好看,强烈推荐!",
"感觉很一般,没有想象中的好",
"贾玲的导演处女作超出了我的预期",
"剧情太拖沓了,看得有点无聊",
"笑点和泪点都处理得很好,很温暖",
"对不起,我觉得这部电影被高估了"
]
print("\n预测结果:")
for comment in test_comments:
pred, probs = classifier.predict(comment)
sentiment = "正向" if pred[0] == 1 else "负向"
prob = probs[0][0][pred[0]]
print(f"评论: {comment}")
print(f"情感: {sentiment} (置信度: {prob:.4f})")
print(f"详细概率: 负向: {probs[0][0][0]:.4f}, 正向: {probs[0][0][1]:.4f}")
print("-" * 50)
# 5. 保存模型
classifier.save_model()
return classifier
if __name__ == "__main__":
classifier = main()
四、使用训练好的模型进行预测
def predict_new_comments(model_path='./weibo_sentiment_model'):
"""使用训练好的模型预测新的评论"""
# 加载模型
classifier = SentimentClassifier()
classifier.load_model(model_path)
# 新评论示例
new_comments = [
"《你好,李焕英》这部电影真的很感人,我哭了",
"感觉剧情很一般,没什么新意",
"贾玲的表现让人惊喜,张小斐也很棒",
"不知道为啥评分这么高,我觉得很一般",
"适合和家人一起看,很有共鸣",
"浪费了我两个多小时的时间"
]
print("新评论情感分析:")
print("=" * 60)
for comment in new_comments:
pred, probs = classifier.predict(comment)
sentiment = "正向" if pred[0] == 1 else "负向"
confidence = max(probs[0][0])
print(f"评论: {comment}")
print(f"预测结果: {sentiment} (置信度: {confidence:.2%})")
# 显示详细概率
neg_prob = probs[0][0][0] * 100
pos_prob = probs[0][0][1] * 100
print(f"负向概率: {neg_prob:.1f}% | 正向概率: {pos_prob:.1f}%")
# 简单可视化
bar_length = 20
neg_bar = "█" * int(neg_prob / 5)
pos_bar = "█" * int(pos_prob / 5)
print(f"[{neg_bar:<20}] {neg_prob:5.1f}% 负向")
print(f"[{pos_bar:<20}] {pos_prob:5.1f}% 正向")
print("-" * 60)
# 运行预测
predict_new_comments()
五、完整的训练脚本(简化版)
# train_simple.py
import pandas as pd
import torch
from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments
from sklearn.model_selection import train_test_split
# 1. 准备数据
data = [
("电影很好看,很感动", 1),
("剧情一般,没什么意思", 0),
("贾玲导演的很好,支持", 1),
("太煽情了,不喜欢", 0),
("笑点很多,推荐观看", 1),
("节奏太慢,看着困", 0),
]
df = pd.DataFrame(data, columns=['text', 'label'])
train_df, val_df = train_test_split(df, test_size=0.2)
# 2. 加载tokenizer和模型
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=2)
# 3. 数据预处理
def preprocess_function(examples):
return tokenizer(examples['text'], truncation=True, padding=True, max_length=128)
train_dataset = train_df.apply(preprocess_function, axis=1)
val_dataset = val_df.apply(preprocess_function, axis=1)
# 4. 训练参数
training_args = TrainingArguments(
output_dir='./results',
num_train_epochs=5,
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
warmup_steps=500,
weight_decay=0.01,
logging_dir='./logs',
)
# 5. 训练
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=val_dataset,
)
trainer.train()
六、部署建议
- 增加数据量:实际应用中需要更多标注数据
- 数据增强:可以使用回译、同义词替换等方法增加数据
- 模型选择:可以尝试其他预训练模型:
hfl/chinese-roberta-wwm-extuer/roberta-base-finetuned-dianping-chinese
- 集成学习:结合多个模型的结果
- 在线学习:定期用新数据更新模型
七、注意事项
- 数据平衡:确保正负样本数量相对平衡
- 过拟合:使用早停、dropout等技术防止过拟合
- 计算资源:Transformer模型需要GPU进行训练
- 实际部署:可以考虑模型量化、剪枝等技术减少模型大小
这个完整系统可以直接运行,你可以根据自己的需求调整参数和模型配置。建议在实际使用中收集更多真实微博评论数据进行训练。