NLP自然语言处理与神经网络——01.embedding实现(理论+实践)

RNN

1.分词

tokenization:分词,每个词语是一个token

分词方法

  1. 把句子转化为词语
    比如我爱深度学习=》{我, 爱,深度学习}
  2. 把句子转化为单个字
    比如我爱深度学习=》{我,爱,深,度,学,习}
  3. 把连续多个字作为一个词

2.N-garm表示方法

分词的第三种方法, N-garm,一组一组的词语,其中 N表示能够被一起使用的词语数量

NLP自然语言处理与神经网络——01.embedding实现(理论+实践)
在传统的机器学习中,用N-gram往往会取得很好的效果,但是在深度学习比如RNN中往往自带N-gram的效果。

; 3.向量化

因为文本不能直接被模型计算,所以需要将文本转化为向量

文本转化为向量有两种表示方法:

  1. 转化为 one-hot编码
  2. 转化为 word embedding

3.1 one-hot编码

one-hot编码中,每一个token使用一个 长度为n的向量表示,n为词典中单词的数量。
假设我们有一个词典里面十个词,其中第五个词为”爱”,那么”爱”这个词表示为one-hot向量时为 0000100000,也就是 除第五个位置为1,其他位置都为0.

但是one-hot使用稀疏向量表示文本,占的空间较多

3.2 word embedding(词嵌入)

word embedding是深度学习中一种常用的方法。和one-hot编码不同,word-embedding使用了浮点型的稠密矩阵来表示token。根据词典的大小,向量通常使用不同的维度,例如100,256,300等,其中向量的每一个值是一个超参数,其初始值是随机生成的,之后在训练过程中通过学习获得。
如果文本有30000个词,使用one-hot编码,那么会有3000030000的矩阵,但是使用word embedding只需要30000维度,比如30000*300.

tokennumvector词10[w11,w12,w13…w1n] 其中n表示维度词21[w21,w22,w23…w2n]………词mm[wm1,wm2,wm3…wmn] 其中m表示词典的大小

我们会把所有的文本用向量来表示,也就是把句子用向量表示,但是,由于初始向量是随机生成的,我们要先把文本输入,但文本不能输入模型,所以要 先把文本转化为数字,再把数字输入转化为向量。即 token--->num--->vector

embedding的理解:
比如选择五个句子,batch_size=5,每个句子有N个词,那矩阵为[batch_size, N](下图左),我们选择的向量维度为4,词典大小为M表示为向量的矩阵为[M,4](下图上),然后将一个batch_size放在一起就是一个[batch_size, N, 4]的矩阵(下图右)

NLP自然语言处理与神经网络——01.embedding实现(理论+实践)

; 3.3 word embedding API

torch.nn.Embedding(num_embeddings, embdeeing_dim)

参数:

  1. num_embeddings:词典的大小
  2. embdeeing_dim:embedding的维度

3.4形状变化

例:每个 batch中的每个句子有 十个词语,经过形状为 [20, 4]word embedding之后,原来的句子会变成什么形状?
每个词语用形状为4的向量表示,所以最终会变成 [batch_size, 10, 4]增加了一个维度,这个维度是embedding的dim。

文本情感分类

1.案例介绍

为了对前面的word_embedding这种常用的文本向量化进行巩固,这里完成一个文本情感分类的案例

现在有一个经典的数据集IMDB数据集,地址:点我
这是一条包含了五万条流行电影的评论数据,其中训练集25000条,测试集25000条,数据格式如下:
下图分别为名称和评论内容,名称包含两部分,分别是序号和情感评分,(1-4为neg,5-10为pos)

NLP自然语言处理与神经网络——01.embedding实现(理论+实践)
NLP自然语言处理与神经网络——01.embedding实现(理论+实践)

; 2.思路分析

首先,可以将问题定义分类问题,情感评分为1-10,10个类别(也可以当做回归问题,这里当做分类问题考虑)
大致流程如下:

  1. 准备数据集
  2. 构建模型
  3. 训练模型
  4. 模型评估

3.准备数据集

3.1实例化Dataset和准备DataLoader

有几个问题需要考虑一下:

  1. 每个batch中文本的 长度如何解决
  2. 每个batch中的文本如何 转化为数字

tips:

DataLoader里面有一个默认的参数 collate_fn,它的默认值是 torch自定义的, collate_fn的作用是对每一个 batch进行处理,而默认的 default_collate处理会出错(会将每一个batch的第一个拿出来作为一个元组,第二个拿出来作为一个元组,以此类推,但两个batch长度不一致还会报错)。所以有一个办法就是 自定义一个collate_fn

def collate_fn(batch):
"""
    :param batch: ([token, label], [token, label]...)
    :return:
"""
    content, label = list(zip(*batch))
    return content, label

Dataset.py文件实现 分词操作( tokenlize方法),定义一个 ImdbDataset的数据集类,该类可以 获取数据集的分词后的内容和label

def tokenlize(content):

    content = re.sub("", " ", content)

    filters = ['\:','\.', '\t', '\n', '\x97', '\x96', '#', '$', '%', '&']
    content = re.sub("|".join(filters), " ", content)
    tokens = [i.strip().lower() for i in content.split()]
    return tokens

class ImdbDataset(Dataset):
    def __init__(self, train=True):
        self.train_data_path = r"D:\python\NLP\data\aclImdb\train"
        self.test_data_path = r"D:\python\NLP\data\aclImdb\test"
        data_path = self.train_data_path if train else self.test_data_path

        temp_data_path = [os.path.join(data_path, "pos"), os.path.join(data_path,"neg")]
        self.total_file_path = []
        for path in temp_data_path:

            file_name_list = os.listdir(path)

            file_path_list = [os.path.join(path, i) for i in file_name_list if i.endswith(".txt")]

            self.total_file_path.extend(file_path_list)

    def __getitem__(self, index):
        file_path = self.total_file_path[index]

        label_str = file_path.split("\\")[-2]
        label = 0 if label_str == "neg" else 1

        content = open(file_path).read()

        tokens = tokenlize(content)
        return tokens, label

    def __len__(self):
        return  len(self.total_file_path)

但是get_dataloader直接使用的是本文,要实现向量化,要先将文本转化为数字。

3.2文本序列化

前面说word embedding的时候,说过要先将文本转化为数字,再把数字转化为向量,在这个过程该怎么实现?
这里可以考虑把文本中的 每个词和其对应的数字使用字典保存,同时实现方法 把句子通过字典映射为包含数字的列表

问题分析:

  • 如何使用字典把句子和词语对应
  • 不同的词语出现次数不尽相同,是否需要对高频或低频词语进行过滤,以及总的词语数量是否需要进行限制。
  • 得到词语字典后,如何转化为数字序列,如何把数字序列转化为句子。
  • 不同的句子长度不同,每个batch的句子如何构造相同的长度(可以对短句子进行填充,填充特殊字符)
  • 对于新出现的词语在词典中没有怎么办(可以使用特殊字符代理)

思路分析

  • 对所有句子进行分词
  • 词语存入字典,对所有句子进行过滤,并统计次数
  • 实现文本转数字序列的方法
  • 实现数字序列转文本的方法

用一个 Word2Sequence类实现以上要求
该类将传入的单个句子保存到 dict中,句子应该是已经用 tokenlize分词后形成的列表,然后将数据集所有句子输入后得到的一个词典( fit方法),再通过词典将每一个词对应一个数字(编号)( build_vocab方法),这样,当给一个文本之后,将文本分词,查字典,就能将每一个句子转换数字序列了( transform方法)。同时,给定一个数字序列,也能将序列转化为文本( inverse_transform方法)。
word_sequence.py


"""
构建词典,实现方法把句子转化为数字序列和其翻转
"""

class Word2Sequence:
    UNK_TAG = "UNK"
    PAD_TAG = "PAD"

    UNK = 0
    PAD = 1
    def __init__(self):
        self.dict={
            self.UNK_TAG : self.UNK,
            self.PAD_TAG : self.PAD
        }
        self.count = {}

    def fit(self, sentence):
"""
        把单个句子保存到dict中
        :param sentence:[word1, word2, word3...]
"""
        for word in sentence:
            self.count[word] = self.count.get(word, 0) + 1

    def build_vocab(self, min=5, max=None, max_features=None):
"""
        生成词典
        :param min: 最小出现次数
        :param max: 最大的次数
        :param max_features: 一共保留多少个词语
"""

        if min is not None:
            self.count = {word:value for word,value in self.count.items() if value>min}

        if max is not None:
            self.count = {word:value for word,value in self.count.items() if value<max}

        if max_features is not None:

            temp = sorted(self.count.items(), key=lambda x:x[-1], reverse=True)[:max_features]
            self.count = dict(temp)

        for word in self.count:
            self.dict[word] = len(self.dict)

        self.reverse_dict = dict(zip(self.dict.values(), self.dict.keys()))

    def transform(self, sentence, max_len=None):
"""
        把句子转为序列 并且把句子转化为长度相同的序列
        :param sentence: [word1, word2,..]
        :param max_len: 对句子进行填充或裁剪
"""
        s_len = len(sentence)
        if max_len is not None:
            if max_len > s_len:
                sentence = sentence + [self.PAD_TAG] * (max_len - s_len)
            if max_len < s_len:
                sentence = sentence[:max_len]

        return [self.dict.get(word, self.UNK) for word in sentence]

    def inverse_transform(self, indices):
"""
        把序列转化为句子
        :param indices: [1, 2, 3, 4...]
"""
        return [self.reverse_dict.get(idx) for idx in indices]

    def __len__(self):
        return len(self.dict)

有了以上两个文件之后,我们就能 根据训练集得到对应的词典,并在 dataset里将文本转为序列后再返回。
先生成词典并保存在 ws.pkl文件,直接使用ws.pkl文件生成 ws,ws就可以直接拿来使用,是我们根据训练集得到的词典


import pickle
ws = pickle.load(open("./model/ws.pkl", "rb"))

import os
import pickle
from Dataset import tokenlize
from tqdm import tqdm
from word_sequence import Word2Sequence

if __name__ == '__main__':
    ws = Word2Sequence()
    path = r"D:\python\NLP\data\aclImdb\train"
    temp_data_path = [os.path.join(path, "pos"), os.path.join(path, "neg")]
    for data_path in temp_data_path:
        file_paths = [os.path.join(data_path, file_name) for file_name in os.listdir(data_path) if file_name.endswith(".txt")]
        for file_path in tqdm(file_paths):
            sentence = tokenlize(open(file_path, encoding='UTF-8').read())
            ws.fit(sentence)
    ws.build_vocab(min=10, max_features=10000)
    pickle.dump(ws, open("./model/ws.pkl", "wb"))
    print(len(ws))

然后修改 collate_fn方法即可

def collate_fn(batch):
"""
    :param batch: ([token, label], [token, label]...)
    :return:
"""
    content, label = list(zip(*batch))

    content = [ws.transform(i, max_len=20) for i in content]
    return content, label

4.定义模型和训练


import torch.nn as nn
from lib import ws, max_len
import torch.nn.functional as F
from torch.optim import Adam
from Dataset import get_dataLoader

class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.embedding = nn.Embedding(len(ws), 100)
        self.fc = nn.Linear(max_len*100, 2)
    def forward(self, input):
"""
        :param input: [natch_size, max_len]
        :return:
"""
        x = self.embedding(input)

        x = x.view([-1, max_len*100])
        out = self.fc(x)
        return F.log_softmax(out, dim=-1)

model = MyModel()
optimizer = Adam(model.parameters())
def train(epoch):
    for idx, (input, target) in enumerate(get_dataLoader(train=True)):

        optimizer.zero_grad()
        output = model(input)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        print(loss.item())

if __name__ == '__main__':
    for i in range(1):
        train(i)
&#x56E0;&#x4E3A;&#x4E3B;&#x8981;&#x4E3A;&#x4E86;&#x505A;embedding&#xFF0C;&#x6240;&#x4EE5;&#x6A21;&#x578B;&#x6BD4;&#x8F83;&#x7B80;&#x5355;&#xFF0C;&#x53EA;&#x8BAD;&#x7EC3;&#x4E00;&#x8F6E;&#xFF0C;&#x6CA1;&#x4EC0;&#x4E48;&#x6548;&#x679C;&#xFF0C;&#x4E0D;&#x505A;&#x6D4B;&#x8BD5;&#x4E86;

Original: https://blog.csdn.net/m0_51474171/article/details/127145813
Author: 头发没了还会再长
Title: NLP自然语言处理与神经网络——01.embedding实现(理论+实践)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/690311/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球