自然语言处理课上做了一个有趣的大作业,于是把它记录下来。
模型代码和数据集文件已经上传至github:https://github.com/MSilke/bert_classifier
一 题目介绍
设计一个关于文本分类的课程大作业,旨在让学生掌握文本分类的基本概念、数据预处理、模型训练、评估和部署等关键步骤。学生将通过实际操作,理解文本分类在自然语言处理中的应用,并能够使用不同的算法和技术来解决实际问题。
二 数据集选择
在数据集的选择上,我选择了MR数据集作为我们大作业的任务。MR数据集是一个有关电影评论的数据集,包含积极评论和消极评论两种类别的标签。
三 数据预处理
实验给定的MR数据集只包含两个.txt文件,如下所示:
其中,mr.txt文件中包含了超过1万条的用户评论,而mr_labels.txt则对用户评论给定了标签,划分了训练集和测试集。
在对数据预处理的过程中,我们使用了pandas库,pandas是基于Numpy 的一种工具,该工具是为解决数据分析任务而创建的。Pandas 纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具。
为了更方便地提取数据集中的信息,我们选择将mr_labels文件转换为csv格式,将对应的id、标签等信息截取到表中,利用python代码实现如下:
import csv
# 读取txt文件
txt_file_path = 'mr_dataset/mr_labels.txt'
csv_file_path = 'mr_dataset/mr_labels.csv'
csvFile = open(csv_file_path, 'w', newline='', encoding='utf-8')
writer = csv.writer(csvFile)
csvRow = []
# 打开txt文件
f = open(txt_file_path, 'r', encoding='utf-8')
# 逐行读取txt文件中的内容,并依据间距空格进行csv格式的划分
for line in f:
csvRow = line.split()
writer.writerow(csvRow)
print(f"Successfully converted {txt_file_path} to {csv_file_path}.")
通过以上的代码,我们实现了txt文件到csv文件格式的转换,如图所示:
打开转换成功的mr_labels.csv文件,并在第一行插入表头属性:
可以看到,我们完成了对文本标签的处理。
接下来,需要清洗mr.txt输入的文本,实现清洗文本的函数:
# 清理文本的函数
def clear_character(sentence):
pattern1 = '[^a-zA-Z0-9\u4e00-\u9fa5\s]' # 保留中英文字符、数字和空格
line = re.sub(pattern1, '', sentence) # 移除其他字符
return line.strip() # 去除首尾空白字符
在读取数据的过程中,加载并调用清洗数据的函数:
# 加载和清理数据
with open('mr_dataset/mr.txt', 'r', encoding='utf-8', errors='replace') as file:
lines = file.readlines()
cleaned_lines = [clear_character(line) for line in lines]
data = pd.read_csv('mr_dataset/mr_labels.csv')
assert len(cleaned_lines) == len(data), "Number of reviews and labels must match"
之后对于原本的数据集,我们划分了数据集的训练集和测试集(加载的是Bert预训练模型,对参数没有很严格的要求,故没有划分验证集):
# 划分数据集
train_texts, val_texts, train_labels, val_labels = train_test_split(cleaned_lines, data['label'], test_size=0.2, random_state=42)
特征提取的环节中,我们实现的算法采用了Bert词向量的特征提取方法。
Bert词向量特征提取:
class SentimentDataset(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, idx):
text = self.texts[idx]
label = self.labels[idx]
encoding = self.tokenizer.encode_plus(
text,
add_special_tokens=True,
max_length=self.max_len,
return_token_type_ids=False,
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)
}
该加载器包含了特征提取和参数设置。
由以上步骤,我们完成了对数据集的预处理。
四 模型训练与评估
首先加载Bert预训练模型,num_labels分类标签设置为2:
# 加载BERT模型
model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
由于bert的模型训练涉及到很多的参数,训练速度相对慢,所以我们在代码中将其放入了GPU中进行训练。
优化器采用的是AdamW,损失函数用的是交叉熵损失,两者的实现都借助于了pytorch框架。
# 定义优化器和损失函数
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-5)
criterion = torch.nn.CrossEntropyLoss()
训练Bert模型的过程如下:
# 训练模型
model.train()
num_epochs = 5
for epoch in range(num_epochs):
total_loss = 0
model.train()
for batch in tqdm(train_loader, desc=f"Training Epoch {epoch + 1}/{num_epochs}"):
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['label'].to(device)
optimizer.zero_grad()
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
loss = criterion(outputs.logits, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_train_loss = total_loss / len(train_loader)
print(f'Epoch: {epoch + 1}, Loss: {avg_train_loss}')
在过程中输出Loss函数损失以便于我们观察训练效果:
可以看出,模型经过多批次训练后,loss值下降明显。
训练完之后,进行评估验证:
model.eval()
all_labels = []
all_preds = []
all_probs = []
with torch.no_grad():
for batch in tqdm(val_loader, desc="Evaluating"):
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['label'].to(device)
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
probs = torch.softmax(outputs.logits, dim=1)[:, 1] # 预测为正类的概率
_, predicted = torch.max(outputs.logits, 1)
all_labels.extend(labels.cpu().numpy())
all_preds.extend(predicted.cpu().numpy())
all_probs.extend(probs.cpu().numpy())
# 计算评估指标
accuracy = accuracy_score(all_labels, all_preds)
precision = precision_score(all_labels, all_preds)
recall = recall_score(all_labels, all_preds)
f1 = f1_score(all_labels, all_preds)
auc_roc = roc_auc_score(all_labels, all_probs)
print(f'Test Accuracy: {accuracy * 100:.2f}%')
各项评价指标的结果如下:
输出并可视化经过训练的bert模型的混淆矩阵:
# 生成并可视化混淆矩阵
conf_matrix = confusion_matrix(all_labels, all_preds)
plt.figure(figsize=(8, 6))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues', xticklabels=['Negative', 'Positive'], yticklabels=['Negative', 'Positive'])
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.title('Confusion Matrix')
plt.show()
输出混淆矩阵:
从各项指标来看,Bert在该文本分类任务中表现良好,体现出了较强的适应下游任务的能力,于是后续平台端部署的算法采用了Bert模型。
在该模型的基础上,我们利用该模型做了一个检测文本评论的页面。
如下图所示:
在实际场景中,该模型展示出了很好的检测评论的效果。
评论区