深度学习每周学习总结N6:使用Word2vec实现文本分类

0. 总结:

之前有学习过文本预处理的环节,对文本处理的主要方式有以下三种:

1:词袋模型(one-hot编码)

2:TF-IDF

3:Word2Vec(词向量(Word Embedding) 以及Word2vec(Word Embedding 的方法之一))

详细介绍及中英文分词详见pytorch文本分类(一):文本预处理

上上上上期主要介绍Embedding,及EmbeddingBag 使用示例(对词索引向量转化为词嵌入向量) ,上上上期主要介绍:应用三种模型的英文分类

上上期将主要介绍中文基本分类(熟悉流程)、拓展:textCNN分类(通用模型)、拓展:Bert分类(模型进阶)

上期主要介绍Word2Vec,和nn.Embedding(), nn.EmbeddingBag()相比都是嵌入技术,用于将离散的词语或符号映射到连续的向量空间。

nn.Embeddingnn.EmbeddingBag 是深度学习框架(如 PyTorch)中的层,直接用于神经网络模型中,而 Word2Vec 是一种独立的词嵌入算法。

使用来说,如果需要在神经网络中处理变长序列的嵌入,可以选择 nn.EmbeddingBag;如果需要预训练词嵌入用于不同任务,可以选择 Word2Vec

本期主要介绍使用Word2Vec实现文本分类:

与N4文本分类的异同点总结

  • 共同点
    数据加载:都使用了PyTorch的DataLoader来批量加载数据。
    模型训练:训练过程大同小异,都是前向传播、计算损失、反向传播和梯度更新。
  • 不同点
    分词处理
    BERT模型:使用专门的BERT分词器。
    传统嵌入方法:通常使用jieba等工具进行分词。
    Word2Vec模型:假设数据已经分词。
    词向量表示
    BERT模型:使用BERT生成的上下文相关的词向量。
    传统嵌入方法:使用静态预训练词向量。
    Word2Vec模型:训练一个Word2Vec模型生成词向量。
    模型结构
    BERT模型:使用预训练的BERT模型作为编码器。
    传统嵌入方法:一般使用嵌入层+卷积/循环神经网络。
    Word2Vec模型:使用Word2Vec词向量和一个简单的线性分类器。
  • 值得学习的点
    词向量的使用:了解如何使用Word2Vec生成词向量并将其用于下游任务。
    数据预处理:不同方法的数据预处理方式,尤其是分词和词向量化的处理。
    模型训练:标准的模型训练和评估流程,尤其是损失计算、反向传播和梯度更新等步骤。
    超参数选择:注意学习率、批量大小和训练轮数等超参数的选择。
    通过这些比较和分析,可以更好地理解不同文本分类方法的优缺点以及适用场景。

1.加载数据

import torch
import torch.nn as nn
import warnings

from torch.utils.data import DataLoader,Dataset,random_split

import pandas as pd

warnings.filterwarnings('ignore')# 忽略警告信息
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device
device(type='cpu')
train_data = pd.read_csv('./data/N6-train.csv',sep='\t',header=None)
train_data
01
0还有双鸭山到淮阴的汽车票吗13号的Travel-Query
1从这里怎么回家Travel-Query
2随便播放一首专辑阁楼里的佛里的歌Music-Play
3给看一下墓王之王嘛FilmTele-Play
4我想看挑战两把s686打突变团竞的游戏视频Video-Play
.........
12095一千六百五十三加三千一百六十五点六五等于几Calendar-Query
12096稍小点客厅空调风速HomeAppliance-Control
12097黎耀祥陈豪邓萃雯畲诗曼陈法拉敖嘉年杨怡马浚伟等到场出席Radio-Listen
12098百事盖世群星星光演唱会有谁Video-Play
12099下周一视频会议的闹钟帮我开开Alarm-Update

12100 rows × 2 columns

class CustomDataset(Dataset):
    def __init__(self,texts,labels):
        self.texts = texts
        self.labels = labels
        
    def __len__(self):
        return len(self.texts)
    
    def __getitem__(self,idx):
        return self.texts[idx],self.labels[idx]
    
x = train_data[0].values[:]
y = train_data[1].values[:]

2. 构建词典

from gensim.models.word2vec import Word2Vec
import numpy as np

# 训练Word2Vec 浅层神经网络模型
w2v = Word2Vec(
    vector_size = 100, # 指特征向量的维度,默认为100
    min_count = 3      # 可以对字典做截断,词频少于min_count次数的单词会被丢弃掉,默认值为5
)
w2v.build_vocab(x)
w2v.train(
    x,
    total_examples = w2v.corpus_count,
    epochs = 20
)
(2733133, 3663560)

Word2Vec可以直接训练模型,一步到位。这里分了三步


第一步构建一个空模型


第二步使用 build_vocab 方法根据输入的文本数据 x 构建词典。build_vocab 方法会统计输入文本中每个词汇出现的次数,并按照词频从高到低的顺序将词汇加入词典中。


第三步使用 train 方法对模型进行训练,total_examples 参数指定了训练时使用的文本数量,这里使用的是 w2v.corpus_count 属性,表示输入文本的数量

如果一步到位的话代码为:

w2v = Word2Vec(x,vector_size=100,min_count=3,epochs=20)
# 将文本转化为向量
def average_vec(text):
    vec = np.zeros(100).reshape((1,100))
    for word in text:
        try:
            vec+= w2v.wv[word].reshape((1,100))
        except KeyError:
            continue
    return vec
# 将词向量保存为Ndarray
x_vec = np.concatenate([average_vec(z) for z in x])

# 保存 Word2Vec 模型及词向量
w2v.save('data/w2v_model.pkl')

这段代码定义了一个函数 average_vec(text),它接受一个包含多个词的列表 text 作为输入,并返回这些词对应词向量的平均值。该函数


首先初始化一个形状为 (1, 100) 的全零 numpy 数组来表示平均向量


然后遍历 text 中的每个词,并尝试从 Word2Vec 模型 w2v 中使用 wv 属性获取其对应的词向量。如果在模型中找到了该词,函数将其向量加到 vec 中。如果未找到该词,函数会继续迭代下一个词


最后,函数返回平均向量 vec

然后使用列表推导式将 average_vec() 函数应用于列表 x 中的每个元素。得到的平均向量列表使用 np.concatenate() 连接成一个 numpy 数组 x_vec,该数组表示 x 中所有元素的平均向量。x_vec 的形状为 (n, 100),其中 n 是 x 中元素的数量。

train_iter = CustomDataset(x_vec,y)
len(x),len(x_vec)
(12100, 12100)
label_name = list(set(train_data[1].values[:]))
label_name
['Audio-Play',
 'Radio-Listen',
 'Travel-Query',
 'TVProgram-Play',
 'Video-Play',
 'Other',
 'Music-Play',
 'Alarm-Update',
 'Weather-Query',
 'Calendar-Query',
 'FilmTele-Play',
 'HomeAppliance-Control']

3. 生成数据批次和迭代器

text_pipeline = lambda x:average_vec(x)
label_pipeline = lambda x:label_name.index(x)

lambda 表达式的语法为:lambda arguments: expression

其中 arguments 是函数的参数,可以有多个参数,用逗号分隔。expression 是一个表达式,它定义了函数的返回值。


text_pipeline 函数:接受一个包含多个词的列表 x 作为输入,并返回这些词对应词向量的平均值,即调用了之前定义的 average_vec 函数。这个函数用于将原始文本数据转换为词向量平均值表示的形式。


label_pipeline 函数:接受一个标签名 x 作为输入,并返回该标签名label_name 列表中的索引。这个函数可以用于将原始标签数据转换为数字索引表示的形式。

text_pipeline("你在干嘛")
array([[ 0.18396786,  0.24416898,  1.0977701 , -0.01573543, -2.34493342,
         0.14462006,  1.09909924,  0.72848918,  0.43942198, -0.36590184,
        -1.64841774, -4.0461483 ,  0.52117442, -0.93646932,  0.65021983,
         1.8863973 ,  3.21435681, -2.37889412,  4.38834111, -1.31753699,
         3.04073831, -1.56194845, -0.01702204, -0.26231994, -0.57977456,
        -0.59202761, -2.09177154, -0.93535494,  2.01195888, -2.03609261,
         2.05241142,  1.59645403, -0.09730234, -1.39273715,  0.35281967,
         0.07837144, -0.28004935,  3.58092116, -2.69026518,  1.25223976,
         0.26495122,  0.6858208 , -0.26904737,  1.87308259,  0.21092373,
         0.18170499,  2.06353265, -0.55430332, -2.19337641,  1.70870071,
         0.30772619, -2.93958779, -0.97791416,  0.84643018, -0.75232047,
        -0.05284989,  1.25495276, -0.59528226, -0.44547228,  0.5255087 ,
         0.86212044, -1.28084296,  2.38232671, -0.28152593, -0.64530418,
         1.20289534, -0.42659164,  0.95189874,  1.22811846,  0.04619575,
        -0.50489213,  0.27519883,  2.16535747, -0.17590564,  0.42813917,
        -0.09275024, -3.63906508,  0.53455901,  2.07623281, -0.39027843,
        -1.92971458,  0.42125694, -2.16986141,  2.77343564, -1.69743965,
        -1.50195191,  1.76218376, -1.49796952, -0.26446094, -0.30981308,
         0.9657587 , -1.63175048,  1.01593184,  0.9586434 ,  0.70671193,
        -2.49167663,  0.37804852,  0.94789429, -1.52659395,  0.88284026]])
label_pipeline("Travel-Query")
2
from torch.utils.data import DataLoader

def collate_batch(batch):
    label_list,text_list = [],[]
    
    for (_text,_label) in batch:
        # 标签列表
        label_list.append(label_pipeline(_label))
        
        # 文本列表
        processed_text = torch.tensor(text_pipeline(_text),dtype=torch.float32)
        text_list.append(processed_text)
        
    label_list = torch.tensor(label_list,dtype = torch.int64)
    text_list = torch.cat(text_list)
    
    return text_list.to(device),label_list.to(device)

4.模型搭建及初始化

from torch import nn

class TextClassificationModel(nn.Module):
    def __init__(self,num_class):
        super(TextClassificationModel,self).__init__()
        self.fc = nn.Linear(100,num_class)
    def forward(self,text):
        return self.fc(text)
# 模型初始化
num_class = len(label_name)
vocab_size = 100000
em_size = 12
model = TextClassificationModel(num_class).to(device)

5. 定义训练与评估函数

def train(dataloader):
    model.train()  # 切换为训练模式
    total_acc, total_loss, total_count = 0, 0, 0

    for idx, (text,label) in enumerate(dataloader):
        pred = model(text)
        
        optimizer.zero_grad()                    # grad属性归零
        loss = criterion(pred, label) # 计算网络输出和真实值之间的差距,label为真实值
        loss.backward()                          # 反向传播
        torch.nn.utils.clip_grad_norm_(model.parameters(), 0.1) # 梯度裁剪
        optimizer.step()  # 每一步自动更新
        
        # 记录acc与loss
        total_acc   += (pred.argmax(1) == label).sum().item()
        total_loss  += loss.item()
        total_count += label.size(0)
        
    return total_acc / total_count, total_loss / total_count

def evaluate(dataloader):
    model.eval()  # 切换为测试模式
    total_acc, total_loss, total_count = 0, 0, 0

    with torch.no_grad():
        for idx, (text,label) in enumerate(dataloader):
            pred = model(text)
            
            loss = criterion(pred, label)  # 计算loss值
            # 记录测试数据
            total_acc   += (pred.argmax(1) == label).sum().item()
            total_loss  += loss.item()
            total_count += label.size(0)
            
    return total_acc/total_count, total_loss/total_count

torch.nn.utils.clip_grad_norm_(model.parameters(), 0.1)是一个PyTorch函数,用于在训练神经网络时限制梯度的大小。这种操作被称为梯度裁剪(gradient clipping),可以防止梯度爆炸问题,从而提高神经网络的稳定性和性能。

在这个函数中:


model.parameters()表示模型的所有参数。对于一个神经网络,参数通常包括权重和偏置项。


0.1是一个指定的阈值,表示梯度的最大范数(L2范数)。如果计算出的梯度范数超过这个阈值,梯度会被缩放,使其范数等于阈值。

梯度裁剪的主要目的是防止梯度爆炸。梯度爆炸通常发生在训练深度神经网络时,尤其是在处理长序列数据的循环神经网络(RNN)中。当梯度爆炸时,参数更新可能会变得非常大,导致模型无法收敛或出现数值不稳定。通过限制梯度的大小,梯度裁剪有助于解决这些问题,使模型训练变得更加稳定。

6. 拆分数据集并运行模型

# 超参数
EPOCHS = 10 # epoch
LR = 5 # 学习率
BATCH_SIZE = 64 # batch size for training

criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=LR)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, 1.0, gamma=0.1)
total_accu = None

# 构建数据集
train_dataset = CustomDataset(train_data[0].values[:], train_data[1].values[:])

split_train_, split_valid_ = random_split(
    train_dataset,
    [int(len(train_dataset)*0.8), len(train_dataset) - int(len(train_dataset)*0.8)]
)
train_dataloader = DataLoader(split_train_, batch_size=BATCH_SIZE,
                              shuffle=True, collate_fn=collate_batch)
valid_dataloader = DataLoader(split_valid_, batch_size=BATCH_SIZE,
                              shuffle=True, collate_fn=collate_batch)

import copy
import time
epochs     = 10

train_loss = []
train_acc  = []
test_loss  = []
test_acc   = []

best_acc = 0    # 设置一个最佳准确率,作为最佳模型的判别指标

for epoch in range(epochs):
    # 更新学习率(使用自定义学习率时使用)
    # adjust_learning_rate(optimizer, epoch, learn_rate)
    epoch_start_time = time.time()
    
    epoch_train_acc, epoch_train_loss = train(train_dataloader)
    scheduler.step() # 更新学习率(调用官方动态学习率接口时使用)
    
    epoch_test_acc, epoch_test_loss = evaluate(valid_dataloader)
    
    # 保存最佳模型到 best_model
    if epoch_test_acc > best_acc:
        best_acc   = epoch_test_acc
        best_model = copy.deepcopy(model)
    
    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)
    
    # 获取当前的学习率
    lr = optimizer.state_dict()['param_groups'][0]['lr']
    
    template = ('Epoch:{:2d}, time: {:4.2f}s,Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')
    print(template.format(epoch+1,time.time() - epoch_start_time,epoch_train_acc*100, epoch_train_loss, 
                          epoch_test_acc*100, epoch_test_loss, lr))
    
# 保存最佳模型到文件中
PATH = './best_model.pth'  # 保存的参数文件名
torch.save(model.state_dict(), PATH)

print('Done')

Epoch: 1, time: 0.94s,Train_acc:79.9%, Train_loss:0.021, Test_acc:85.5%, Test_loss:0.015, Lr:5.00E-01
Epoch: 2, time: 0.91s,Train_acc:88.8%, Train_loss:0.009, Test_acc:87.8%, Test_loss:0.009, Lr:5.00E-02
Epoch: 3, time: 0.91s,Train_acc:90.1%, Train_loss:0.007, Test_acc:88.1%, Test_loss:0.008, Lr:5.00E-03
Epoch: 4, time: 0.95s,Train_acc:90.1%, Train_loss:0.007, Test_acc:88.1%, Test_loss:0.008, Lr:5.00E-04
Epoch: 5, time: 0.91s,Train_acc:90.2%, Train_loss:0.007, Test_acc:88.1%, Test_loss:0.009, Lr:5.00E-05
Epoch: 6, time: 0.88s,Train_acc:90.2%, Train_loss:0.007, Test_acc:88.1%, Test_loss:0.008, Lr:5.00E-06
Epoch: 7, time: 0.87s,Train_acc:90.2%, Train_loss:0.007, Test_acc:88.1%, Test_loss:0.008, Lr:5.00E-07
Epoch: 8, time: 0.87s,Train_acc:90.2%, Train_loss:0.007, Test_acc:88.1%, Test_loss:0.008, Lr:5.00E-08
Epoch: 9, time: 0.91s,Train_acc:90.2%, Train_loss:0.007, Test_acc:88.1%, Test_loss:0.008, Lr:5.00E-09
Epoch:10, time: 0.92s,Train_acc:90.2%, Train_loss:0.007, Test_acc:88.1%, Test_loss:0.008, Lr:5.00E-10
Done

7. 结果可视化

import matplotlib.pyplot as plt
#隐藏警告
import warnings
warnings.filterwarnings("ignore")               #忽略警告信息
plt.rcParams['font.sans-serif']    = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号
plt.rcParams['figure.dpi']         = 100        #分辨率

epochs_range = range(epochs)

plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)

plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()


在这里插入图片描述

8. 测试指定数据

def predict(text, text_pipeline):
    with torch.no_grad():
        text = torch.tensor(text_pipeline(text), dtype=torch.float32)
        print(text.shape)
        output = model(text)
        return output.argmax(1).item()

# ex_text_str = "随便播放一首专辑阁楼里的佛里的歌"
ex_text_str = "还有双鸭山到淮阴的汽车票吗13号的"

model = model.to("cpu")

print("该文本的类别是:%s" %label_name[predict(ex_text_str, text_pipeline)])
torch.Size([1, 100])
该文本的类别是:Travel-Query

9. 提问:与N4文本分类的异同

分析代码

代码使用了Word2Vec技术将文本转化为词向量,并通过一个简单的线性分类器进行文本分类。以下是详细的分析和与之前两个模型(BERT和基于传统嵌入的方法)处理数据的方式的比较:

数据处理方式
1. 分词处理
  • BERT模型:使用了BERT的分词器(BertTokenizer),能够处理中文字符,并将其转换为BERT所需的输入格式,包括input_idsattention_mask
  • 传统嵌入方法:一般使用jieba进行分词,并通过词汇表将分词结果转换为索引。
  • Word2Vec模型:在代码中直接将句子传递给Word2Vec进行训练,假设句子已经被分好词。
2. 构建词典
  • BERT模型:直接使用预训练模型提供的词典(如bert-base-chinese)。
  • 传统嵌入方法:手动构建词汇表,并将词汇映射为索引。
  • Word2Vec模型:通过训练Word2Vec模型来构建词向量的词典。
3. 向量表示
  • BERT模型:使用BERT生成的上下文相关的词向量。
  • 传统嵌入方法:通常使用静态的预训练词向量(如Word2Vec或GloVe)。
  • Word2Vec模型:代码中训练了一个浅层的Word2Vec模型,并将每个词转换为一个100维的向量,句子表示为这些词向量的平均值。
值得关注的点
1. Word2Vec模型训练
from gensim.models.word2vec import Word2Vec
import numpy as np

# 训练Word2Vec 浅层神经网络模型
w2v = Word2Vec(
    vector_size=100,  # 指特征向量的维度
    min_count=3       # 词频少于min_count次数的单词会被丢弃
)
w2v.build_vocab(x)
w2v.train(x, total_examples=w2v.corpus_count, epochs=20)
  • 训练Word2Vec模型,这里使用的是Gensim库。vector_size参数设置了词向量的维度,min_count参数设置了词频少于3次的单词会被丢弃。
2. 文本向量化
# 将文本转化为向量
def average_vec(text):
    vec = np.zeros(100).reshape((1, 100))
    for word in text:
        try:
            vec += w2v.wv[word].reshape((1, 100))
        except KeyError:
            continue
    return vec
# 将词向量保存为Ndarray
x_vec = np.concatenate([average_vec(z) for z in x])
  • 使用Word2Vec模型将每个词转换为向量,并计算句子的平均向量。这里的句子表示为其包含词的词向量的平均值。
3. 数据加载和批处理
from torch.utils.data import DataLoader

def collate_batch(batch):
    label_list, text_list = [], []

    for (_text, _label) in batch:
        # 标签列表
        label_list.append(label_pipeline(_label))

        # 文本列表
        processed_text = torch.tensor(text_pipeline(_text), dtype=torch.float32)
        text_list.append(processed_text)

    label_list = torch.tensor(label_list, dtype=torch.int64)
    text_list = torch.cat(text_list)

    return text_list.to(device), label_list.to(device)
  • 这里的collate_batch函数用于将数据批处理成训练时所需的格式。text_pipelinelabel_pipeline分别用于处理文本和标签。
模型结构
1. 模型定义
class TextClassificationModel(nn.Module):
    def __init__(self, num_class):
        super(TextClassificationModel, self).__init__()
        self.fc = nn.Linear(100, num_class)

    def forward(self, text):
        return self.fc(text)
  • 这是一个简单的线性分类器,输入是100维的词向量,输出是分类的结果。
训练和评估
1. 训练过程
def train(dataloader):
    model.train()  # 切换为训练模式
    total_acc, total_loss, total_count = 0, 0, 0

    for idx, (text, label) in enumerate(dataloader):
        pred = model(text)

        optimizer.zero_grad()                    # grad属性归零
        loss = criterion(pred, label) # 计算网络输出和真实值之间的差距,label为真实值
        loss.backward()                          # 反向传播
        torch.nn.utils.clip_grad_norm_(model.parameters(), 0.1) # 梯度裁剪
        optimizer.step()  # 每一步自动更新

        # 记录acc与loss
        total_acc += (pred.argmax(1) == label).sum().item()
        total_loss += loss.item()
        total_count += label.size(0)

    return total_acc / total_count, total_loss / total_count
  • 标准的训练过程,包括前向传播、计算损失、反向传播和梯度更新。
异同点总结
共同点
  1. 数据加载:都使用了PyTorch的DataLoader来批量加载数据。
  2. 模型训练:训练过程大同小异,都是前向传播、计算损失、反向传播和梯度更新。
不同点
  1. 分词处理

    • BERT模型:使用专门的BERT分词器。
    • 传统嵌入方法:通常使用jieba等工具进行分词。
    • Word2Vec模型:假设数据已经分词。
  2. 词向量表示

    • BERT模型:使用BERT生成的上下文相关的词向量。
    • 传统嵌入方法:使用静态预训练词向量。
    • Word2Vec模型:训练一个Word2Vec模型生成词向量。
  3. 模型结构

    • BERT模型:使用预训练的BERT模型作为编码器。
    • 传统嵌入方法:一般使用嵌入层+卷积/循环神经网络。
    • Word2Vec模型:使用Word2Vec词向量和一个简单的线性分类器。
值得学习的点
  1. 词向量的使用:了解如何使用Word2Vec生成词向量并将其用于下游任务。
  2. 数据预处理:不同方法的数据预处理方式,尤其是分词和词向量化的处理。
  3. 模型训练:标准的模型训练和评估流程,尤其是损失计算、反向传播和梯度更新等步骤。
  4. 超参数选择:注意学习率、批量大小和训练轮数等超参数的选择。

通过这些比较和分析,可以更好地理解不同文本分类方法的优缺点以及适用场景。

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值