数据预处理:
加载中文文本数据并进行分词,以将文本转化为单词序列。可以使用分词库,如jieba。然后,将文本序列转换为词嵌入表示,如Word2Vec或GloVe。
填充序列:
对文本序列进行填充,确保它们具有相同的长度。这是因为卷积神经网络需要输入相同长度的数据。你可以选择一个固定的序列长度或根据最长文本来决定。
构建TextCNN模型:
TextCNN模型通常由以下几个组件组成:
- 卷积层:
使用一维卷积层来捕捉文本中的局部特征。您可以定义多个卷积核,每个卷积核用于检测不同的特征。卷积核的大小通常是一个重要的超参数,您可以尝试不同的大小。
一维卷积操作将创建新的特征表示。在PyTorch或TensorFlow中,您可以使用nn.Conv1d(PyTorch)或Conv1D层(TensorFlow)来定义卷积层。
激活函数:
通常在卷积层后应用激活函数,如ReLU(Rectified Linear Unit),以引入非线性性。这有助于模型学习更复杂的特征。在PyTorch中,您可以使用nn.ReLU,而在TensorFlow中,您可以使用tf.nn.relu。
- 池化层:
池化层用于减小特征映射的尺寸,同时保留最重要的信息。通常使用最大池化层(Max-Pooling)来选择每个特征映射的最大值。您可以在卷积层之后添加一维最大池化层,以减小特征映射的大小。
- 全连接层和输出层:
将卷积和池化层的输出展平,然后连接一个或多个全连接层,以学习文本的全局特征。最后,添加一个输出层,其神经元数等于分类类别的数量,使用softmax激活函数进行多类别分类。
训练模型:
使用已标记的文本数据集来训练TextCNN模型。定义损失函数(如交叉熵损失)和选择优化器(如SGD、Adam等)。训练模型以最小化损失函数。
评估模型:
使用测试数据集评估模型性能。常见的性能指标包括准确性、精确度、召回率和F1分数。这些指标将帮助您了解模型在分类任务中的表现。
预测:
使用经过训练的模型对新文本进行分类预测。将新文本输入模型,获取其分类标签。
模型优化:
可以尝试不同的超参数,如卷积核大小、卷积层数、池化层类型、学习率等,以优化模型性能。您还可以考虑使用预训练的词嵌入来改善性能。
代码实现
import os
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import Dataset,DataLoader
def read_data(train_or_test, num=None):
with open(os.path.join(train_or_test + ".txt"), encoding="utf-8") as f:
all_data = f.read().split("\n")
texts = []
labels = []
for data in all_data:
if data:
t, l = data.split("\t")
texts.append(t)
labels.append(l)
if num == None:
return texts, labels
else:
return texts[:num], labels[:num]
def built_curpus(train_texts,embedding_num): # 传入参数:语料
word_2_index = {"<PAD>":0,"<UNK>":1}# 填充为零,未知为一
for text in train_texts:
for word in text:
word_2_index[word] = word_2_index.get(word,len(word_2_index))
# get方法get(key) 方法在 key(键)不在字典中时,可以返回默认值 None 或者设置的默认值。
return word_2_index,nn.Embedding(len(word_2_index),embedding_num)
# 返回键值以及一个tensor类型的张量,这个张量代表了生成字典元素个数个嵌入向量,每个嵌入向量的维度是embedding_num
class TextDataset(Dataset):
def __init__(self, all_text, all_label, word_2_index, max_len):
self.all_text = all_text
self.all_label = all_label
self.word_2_index = word_2_index
self.max_len = max_len
def __getitem__(self, index):
text = self.all_text[index][:self.max_len]
label = int(self.all_label[index])
text_idx = [self.word_2_index.get(i, 1) for i in text]
text_idx = text_idx + [0] * (self.max_len - len(text_idx))
text_idx = torch.tensor(text_idx).unsqueeze(dim=0)
return text_idx, label
def __len__(self):
return len(self.all_text)
class Block(nn.Module):
def __init__(self, kernel_s, embeddin_num, max_len, hidden_num):
super().__init__() # 标准写法
# 卷积层
self.cnn = nn.Conv2d(in_channels=1, out_channels=hidden_num, kernel_size=(
kernel_s, embeddin_num)) # 1 * 1 * 7 * 5 (batch * in_channel * len * emb_num )
# 激活层(采用Relu)
self.act = nn.ReLU()
# 池化层(取最大值)
self.mxp = nn.MaxPool1d(kernel_size=(max_len - kernel_s + 1))
# 进行三步自动化运算
def forward(self, batch_emb): # 1 * 1 * 20 * 50 (batch * in_channel * len * emb_num )
c = self.cnn.forward(batch_emb)
a = self.act.forward(c)
a = a.squeeze(dim=-1)
m = self.mxp.forward(a)
m = m.squeeze(dim=-1)
return m
class TextCNNModel(nn.Module):
def __init__(self, emb_matrix, max_len, class_num, hidden_num):
super().__init__()
self.emb_num = emb_matrix.weight.shape[1]
# 第二个值为文字维度,一直对应到语料库第二个返回值的第二个元素,承接上文提到的nn.embedding函数的使用与返回结果
self.block1 = Block(2, self.emb_num, max_len, hidden_num)
self.block2 = Block(3, self.emb_num, max_len, hidden_num)
self.block3 = Block(4, self.emb_num, max_len, hidden_num)
self.block4 = Block(5, self.emb_num, max_len, hidden_num) # 每次取词,词的维度
# 构建每一个block
self.emb_matrix = emb_matrix
self.classifier = nn.Linear(hidden_num * 4, class_num) # 2 * 3
self.loss_fun = nn.CrossEntropyLoss()
def forward(self, batch_idx, batch_label=None): # 可以选择不传label值,就是训练的情况
batch_emb = self.emb_matrix(batch_idx) # 根据id号找到相应生成的embedding
b1_result = self.block1.forward(batch_emb)
b2_result = self.block2.forward(batch_emb)
b3_result = self.block3.forward(batch_emb)
b4_result = self.block4.forward(batch_emb)
feature = torch.cat([b1_result, b2_result, b3_result, b4_result], dim=1) # 1* 6 : [ batch * (3 * 2)]
pre = self.classifier(feature)
if batch_label is not None: # 有标签代表是训练
loss = self.loss_fun(pre, batch_label)
return loss
else: # 没有标签代表是预测
return torch.argmax(pre, dim=-1) # 返回概率最大的那个
if __name__ == "__main__":
train_text, train_label = read_data(r"E:\技能学习\AI\TextCNN文本分类实现\train")
dev_text, dev_label = read_data(r"E:\技能学习\AI\TextCNN文本分类实现\dev")
embedding = 50
max_len = 20
batch_size = 200
epoch = 200
lr = 0.001
hidden_num = 2 # 输出通道
class_num = len(set(train_label))
device = "cuda:0" if torch.cuda.is_available() else "cpu"
word_2_index, words_embedding = built_curpus(train_text, embedding) # 返回的字的编号和每个字所对应的维度向量
train_dataset = TextDataset(train_text, train_label, word_2_index, max_len)
train_loader = DataLoader(train_dataset, batch_size, shuffle=False)
dev_dataset = TextDataset(dev_text, dev_label, word_2_index, max_len)
dev_loader = DataLoader(dev_dataset, batch_size, shuffle=False)
model = TextCNNModel(words_embedding, max_len, class_num, hidden_num).to(device)
# 进入模型进行forward
opt = torch.optim.AdamW(model.parameters(), lr=lr) # 进行优化
for e in range(epoch):
for batch_idx, batch_label in train_loader:
batch_idx = batch_idx.to(device)
batch_label = batch_label.to(device)
loss = model.forward(batch_idx, batch_label)
loss.backward() # 反向传播找到合适的参数
opt.step()
opt.zero_grad()
print(f"loss:{loss:.3f}") # 损失值
right_num = 0 # 统计测试集正确的数量
for batch_idx, batch_label in dev_loader:
batch_idx = batch_idx.to(device)
batch_label = batch_label.to(device)
pre = model.forward(batch_idx)
right_num += int(torch.sum(pre == batch_label)) # 统计测试集正确的数量
MODEL_DIR = r'E:/技能学习/AI/TextCNN文本分类实现/output/提问训练/'
torch.save(model, MODEL_DIR + f'model_{e}.pth') # 将模型存储到pth文件中
print(f"acc = {right_num / len(dev_text) * 100:.2f}%") # 输出正确率