欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

NLP 自然语言处理

最编程 2024-10-05 18:12:44
...

计算机视觉和图像处理

  1. Tensorflow入门
  2. 深度神经网络
  3. 图像分类
  4. 目标检测
  5. 图像分割
  6. OpenCV
  7. Pytorch
  8. NLP自然语言处理

NLP自然语言处理

  • 一、NLP简介
  • 二、文本预处理
    • 2.1 文本预处理简介
    • 2.2 文本处理的基本方法
    • 2.3 文本张量表示方法
      • 2.3.1 onehot编码
      • 2.3.2 word2vec编码
    • 2.4 文本数据分析
    • 2.5 文本特征处理
      • 2.5.1 添加n_grams特征
      • 2.5.2 文本长度规范
    • 2.6 文本数据增强
  • 三、HMM和CRF
  • 四、RNN架构解析
    • 4.1 RNN模型简介
    • 4.2 传统RNN的使用
    • 4.3 LSTM模型的使用
    • 4.4 GRU模型的使用
    • 4.5 注意力机制
  • 五、RNN经典案例——人名分类器
  • 五、Transformer

一、NLP简介

自然语言处理(Natural Language Processing,简称 NLP)是计算机科学、人工智能和语言学的一个分支,旨在使计算机能够理解、解释和生成人类的自然语言。NLP 的目标是弥合人类通信和计算机理解之间的差距,使得计算机能够以更智能、更有效的方式处理文本和语音数据。

NLP 的主要任务

  1. 文本分类:
  • 情感分析:判断文本的情感倾向,如正面、负面或中性。
  • 主题分类:将文档归类到预定义的主题类别中。
  • 垃圾邮件检测:识别电子邮件或消息是否为垃圾信息。
  1. 命名实体识别(NER):
  • 识别文本中的特定实体,如人名、地名、组织名、日期等。
  1. 关系抽取:
  • 从文本中提取实体之间的关系,如“奥巴马是美国的总统”。
  1. 句法分析:
  • 词性标注:为每个词标注其词性(名词、动词、形容词等)。
  • 依存关系分析:分析句子中词与词之间的依存关系。
  1. 语义分析:
  • 语义角色标注:识别句子中的谓词及其论元。
  • 共指消解:确定文本中哪些词语指的是同一个实体。
  1. 机器翻译:
  • 将一种自然语言翻译成另一种自然语言,如将英文翻译成中文。
  1. 对话系统:
  • 聊天机器人:构建能够与用户进行自然对话的系统。
  • 语音识别:将语音信号转换为文本。
  • 语音合成:将文本转换为语音信号。
  1. 文本生成:
  • 自动摘要:生成文本的简洁摘要。
  • 创意写作:生成诗歌、故事等创意文本。

NLP 的技术方法

  1. 统计方法:
  • 使用统计模型(如朴素贝叶斯、支持向量机、隐马尔可夫模型等)来处理文本数据。
  1. 深度学习方法:
  • 循环神经网络(RNN):处理序列数据,适用于文本生成和序列标注任务。
  • 长短期记忆网络(LSTM):改进的 RNN,能够更好地处理长依赖问题。
  • Transformer 模型:基于自注意力机制的模型,广泛应用于各种 NLP 任务,如 BERT、GPT 等。
  • 卷积神经网络(CNN):用于提取局部特征,适用于文本分类任务。

二、文本预处理

2.1 文本预处理简介

文本语料在输送给模型前一般需要一系列的预处理工作, 才能符合模型输入的要求, 如: 将文本转化成模型需要的张量, 规范张量的尺寸等, 而且科学的文本预处理环节还将有效指导模型超参数的选择, 提升模型的评估指标.

2.2 文本处理的基本方法

分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。
分词的作用:
词作为语⾔语义理解的最小单元, 是⼈类理解文本语言的基础. 因此也是AI解决NLP领域高阶任务, 如自动问答, 机器翻译, 文本生成的重要基础环节.
jieba分词器
使用jieba前需要先导包,在命令行输入:pip install jieba
在这里插入图片描述

  1. 精确模式分词,试图将句子最精确地切开,适合文本分析.
import jieba

# 精确模式分词,cut_all=False
content = "它的主要功能是作为Jupyter的内核,允许 Jupyter Notebook与不同的Python环境进行交互。"
jieba.cut(content,cut_all=False)

返回一个生成器对象
在这里插入图片描述

# lcut直接显示分词内容
words = jieba.lcut(content)
result = ' '.join(words)
result

在这里插入图片描述

  1. 全模式分词,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能消除歧义
# 全模式分词,cut_all=True
content = "它的主要功能是作为Jupyter的内核,允许 Jupyter Notebook与不同的Python环境进行交互"
jieba.cut(content,cut_all=True)

在这里插入图片描述

words = jieba.lcut(content)
result = ' '.join(words)
result

在这里插入图片描述

  1. 搜索引擎模式分词,在精确模式的基础上,对长词再次切分,提高召回率。
# 搜索引擎模式分词
content = "它的主要功能是作为Jupyter的内核,允许 Jupyter Notebook与不同的Python环境进行交互"
jieba.cut_for_search(content)

words = jieba.lcut_for_search(content)
result = ' '.join(words)
result

在这里插入图片描述

  1. 中文繁体分词
content = "煩惱即是菩提,我暫且不提"
jieba.cut(content,cut_all=False)
jieba.lcut(content)

在这里插入图片描述

  1. 自定义词典分词
# 使用不了自定义词典,加载不到userdict.txt! 但方法就是这方法
jieba.load_userdict("./userdict.txt")
jieba.lcut("⼋⼀双⿅更名为⼋⼀南昌篮球队!")
  1. 使用jieba进行中文词性标注
# 使用jieba进行中文词性标注
import jieba.posseg as pseg
pseg.lcut("我爱北京*")

在这里插入图片描述

2.3 文本张量表示方法

2.3.1 onehot编码

# 用于对象的保存和加载
import joblib
from tensorflow.keras.preprocessing.text import Tokenizer
# onehot编码
words = {"周杰伦", "陈奕迅", "王力宏", "李宗盛", "吴亦凡", "鹿晗"}

# 实例化词汇映射器对象
# char_level词汇单位是单词而不是字符
t = Tokenizer(num_words=None,char_level=False)
# 拟合现有的文本数据
t.fit_on_texts(words)
print(f"词汇表及索引:{t.word_index}")

for word in words:
    one_hot = [0]*len(words)
    # texts_to_sequences将当前词汇转换为其在词汇表中的索引位置
    # 返回样式[[1]]、[[2]]等
    word_index = t.texts_to_sequences([word])[0][0] - 1
    one_hot[word_index] = 1
    print(f"{word} 的one-hot编码为:{one_hot}")

#保存映射
tokenizer_path = "./Tokenizer"
joblib.dump(t,tokenizer_path)

在这里插入图片描述

# onehot编码器的使用

# 加载之间保存的Tokenizer
t = joblib.load(tokenizer_path)
# 编码王力宏
word = "王力宏"
word_index = t.texts_to_sequences([word])[0][0] - 1
one_hot[word_index] = 1
print(f"{word}的one-hot编码为:{one_hot}")

在这里插入图片描述

2.3.2 word2vec编码

fasttext包我导不进来,调了两三个小时!

import fasttext

# 第一步:获取训练数据
data = [
    "周杰伦是一位著名的歌手",
    "陈奕迅的歌曲非常受欢迎",
    "王力宏的音乐风格多样",
    "李宗盛的歌词很有深度",
    "吴亦凡出演过多部电影",
    "鹿晗在社交媒体上非常活跃"
]

# 将数据写入文件
with open('train.txt', 'w', encoding='utf-8') as f:
    for line in data:
        f.write(line + '\n')

# 第二步:训练词向量
# 模型超参数设定
model = fasttext.train_unsupervised(
    'train.txt',
    model='cbow',  # 模型类型:cbow 或 skipgram
    dim=100,       # 词向量的维度
    ws=5,          # 窗口大小
    epoch=5,       # 训练轮数
    minCount=1     # 最小词频
)

# 第四步:模型效果检验
vector = model.get_word_vector("周杰伦")
print(f"周杰伦的词向量: {vector}")

similar_words = model.get_nearest_neighbors("周杰伦", k=5)
print(f"与周杰伦最相似的词: {similar_words}")

# 第五步:模型的保存与重加载
model.save_model("model.bin")

# 重加载模型
loaded_model = fasttext.load_model("model.bin")

# 检查重加载的模型
vector = loaded_model.get_word_vector("周杰伦")
print(f"重加载后周杰伦的词向量: {vector}")

similar_words = loaded_model.get_nearest_neighbors("周杰伦", k=5)
print(f"重加载后与周杰伦最相似的词: {similar_words}")

2.4 文本数据分析

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
  1. 绘制数据标签数量分布
# 读取数据
# TSV文件使用制表符(\t)作为分隔符
train_data = pd.read_csv("./train.tsv", sep='\t')
test_data = pd.read_csv("./dev.tsv", sep='\t')

# 绘制数据标签数量分布
sns.countplot(x="label",data=train_data)
plt.title("train_data")
plt.show()

sns.countplot(x="label",data=test_data)
plt.title("test_data")
plt.show()

在这里插入图片描述
在深度学习模型评估中, 我们一般使用ACC作为评估指标, 若想将ACC的基线定义在50%左右, 则需要我们的正负样本比例维持在1:1左右, 否则就要进行必要的数据增强或数据删减. 上图中训练和验证集正负样本都稍有不均衡, 可以进行一些数据增强.

  1. 绘制句子长度分布图
# 获取训练集和验证集的句子长度分布
train_data["sentence_length"] = list(map(lambda x:len(x),train_data["sentence"]))
test_data["sentence_length"] = list(map(lambda x:len(x),test_data["sentence"]))

sns.countplot(x="sentence_length",data=train_data)
plt.xticks([])
plt.show()

# 绘制dist长度分布图
sns.distplot(train_data["sentence_length"])
plt.yticks([])
plt.show()


sns.countplot(x="sentence_length",data=test_data)
plt.xticks([])
plt.show()

# 绘制dist长度分布图
sns.distplot(test_data["sentence_length"])
plt.yticks([])
plt.show()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过绘制句子长度分布图, 可以得知我们的语料中大部分句子长度的分布范围, 因为模型的输入要求为固定尺寸的张量,合理的长度范围对之后进行句子截断补齐(规范长度)起到关键的指导作用. 上图中大部分句子长度的范围大致为20-250之间.

  1. 绘制样本长度散点图
# 获取训练集和验证集的正负样本长度散点分布
sns.stripplot(x="label",y="sentence_length",data=train_data)
plt.show()

sns.stripplot(x="label",y="sentence_length",data=test_data)
plt.show()

在这里插入图片描述

在这里插入图片描述
通过查看正负样本长度散点图, 可以有效定位异常点的出现位置, 帮助我们更准确进行人工语料审查. 上图中在训练集正样本中出现了异常点, 它的句子长度近3500左右, 需要我们人工审查。

  1. 统计不同词汇总数
# 获取训练集和验证集不同词汇总数统计
import jieba
# 使用chain方法扁平化列表
from itertools import chain

# 对训练集的句子进行分词,并统计出不同词汇的总数
# 解包操作符 * 将 map 返回的迭代器中的每个列表展开,使 chain 能够将它们连接在一起。
train_words = set(chain(*map(lambda x:jieba.lcut(x),train_data["sentence"])))
print("训练集共包含不同词汇总数为:",len(train_words))

test_words = set(chain(*map(lambda x:jieba.lcut(x),test_data["sentence"])))
print("训练集共包含不同词汇总数为:",len(test_words))

在这里插入图片描述

  1. 绘制高频形容词词云
# 获得训练集上正负样本的高频形容词词云

# 使⽤jieba中的词性标注功能
import jieba.posseg as pseg
from wordcloud import WordCloud
# 获取形容词
def get_list(text):
    r =[]
    for g in pseg.lcut(text):
        if g.flag == 'a':
            r.append(g.word)
    return r
    
# 绘制词云
def get_word_cloud(keywords_list):
    # 实例化绘制词云类
    wordcloud = WordCloud(font_path="./SimHei.ttf",max_words=100,background_color="white")
    # 将传入的列表转化为字符串
    keywords_string = ' '.join(keywords_list)
    # 生成词云
    wordcloud.generate(keywords_string)

    # 绘制图像
    plt.figure(figsize=(10,8))
    plt.imshow(wordcloud)
    plt.axis('off')
    plt.show()

# 获得训练集上正样本
p_train_data = train_data[train_data["label"]==1]['sentence']
# 获取形容词
p_train_adj = chain(*map(lambda x:get_list(x),p_train_data))

# 获得训练集上负样本
n_train_data = train_data[train_data["label"]==0]['sentence']
# 获取形容词
n_train_adj = chain(*map(lambda x:get_list(x),n_train_data))

get_word_cloud(p_train_adj)
get_word_cloud(n_train_adj)

在这里插入图片描述
在这里插入图片描述
根据高频形容词词云显示, 我们可以对当前语料质量进行简单评估, 同时对违反语料标签含义的词汇进行人工审查和修正, 来保证绝大多数语料符合训练标准. 上图中的正样本大多数是褒义词, 而负样本⼤多数是贬义词, 基本符合要求, 但是负样本词云中也存在"便利"这样的褒义词, 因此可以人工进行审查.

2.5 文本特征处理

2.5.1 添加n_grams特征

n-gram 是自然语言处理(NLP)中的一种常用技术,通过将文本分割成连续的子序列(称为“n-grams”)来捕捉文本中的局部结构。

import jieba
# CountVectorizer用于将文本数据转换为数值型特征向量
from sklearn.feature_extraction.text import CountVectorizer

# 示例文本
documents = [
    "我喜欢编程,Python 很强大",
    "今天天气真好,适合出去玩",
    "Python 是一门强大的编程语言"
]

# 分词
tokenized_documents = [" ".join(jieba.cut(doc)) for doc in documents]


# 创建 CountVectorizer 实例,指定 n-gram 范围
# ngram_range=(2, 2) 生成两个词和双组合的n_grams
vectorizer = CountVectorizer(ngram_range=(2, 2)) 

# 拟合并转换文本数据
# 首先学习词汇表,然后将文档转换为词频矩阵。
x = vectorizer.fit_transform(tokenized_documents)

# 获取特征名称
# 返回词汇表中的所有特征名称,包含所有生成的 n-gram 特征名称
feature_names = vectorizer.get_feature_names_out()

# 打印特征名称和对应的计数值
print("Feature names:", feature_names)
# n-gram出现的次数
print("Counts:\n", x.toarray())

在这里插入图片描述

2.5.2 文本长度规范

# 截断文本到指定的最大长度
def truncate_text(text, max_length, truncation_type='end'):
    if len(text) > max_length:
        if truncation_type == 'start':
            return text[:max_length]
        elif truncation_type == 'middle':
            start = (len(text) - max_length) // 2
            return text[start:start + max_length]
        else:  # 'end'
            return text[-max_length:]
    return text

# 填充文本到指定的最大长度
def pad_text(text, max_length, padding_token='<PAD>', padding_type='post'):
    if len(text) < max_length:
        num_padding = max_length - len(text)
        if padding_type == 'pre':
            return (padding_token * num_padding) + text
        else: # 'post'
            return text + (padding_token * num_padding)
    return text

# 规范化文本长度,先截断再填充
def normalize_text_length(text, max_length, truncation_type='end', padding_token='<PAD>', padding_type='post'):
    # 截断
    truncated_text = truncate_text(text, max_length, truncation_type)
    # 填充
    normalized_text = pad_text(truncated_text, max_length, padding_token, padding_type)
    return normalized_text

# 示例文本
texts = [
    "This is a short sentence.",
    "This is a much longer sentence that needs to be truncated or padded to fit the specified length.",
    "Another example sentence."
]

# 设置最大长度
max_length = 27

# 规范化文本长度
normalized_texts = [normalize_text_length(text, max_length) for text in texts]

# 打印结果
for original, normalized in zip(texts, normalized_texts):
    print(f"Original: {original}")
    print(f"Normalized: {normalized}\n")

在这里插入图片描述

2.6 文本数据增强

回译数据增强法
是将源语言的句子翻译成另一种语言,然后再将翻译后的句子翻译回源语言,从而生成新的训练样本。这种方法可以增加训练数据的多样性,提高模型的泛化能力。

from transformers import MarianMTModel,MarianTokenizer
import torch

# 加载中文到英文的翻译模型和分词器
# huggingFace官网进不去,无法下载Helsinki-NLP/opus-mt-zh-en
model_name_zh_to_en = "Helsinki-NLP/opus-mt-zh-en"
tokenizer_zh_to_en = MarianTokenizer.from_pretrained(model_name_zh_to_en)
model_zh_to_en = MarianMTModel.from_pretrained(model_name_zh_to_en)

# 加载英文到中文你的翻译模型和分词器
model_name_en_to_zh = 'Helsinki-NLP/opus-mt-en-zh'
tokenizer_en_to_zh = MarianTokenizer.from_pretrained(model_name_en_to_zh)
model_en_to_zh = MarianMTModel.from_pretrained(model_name_en_to_zh)

# 翻译文本
def translate(text,tokenizer,model):
    # 将文本转换为模型可以处理的数值形式
    inputs = tokenizer(text,return_tensors="pt")
    print(inputs)
    with torch.no_grad:
        # **操作符用于解包字典,将字典中的键值对作为关键字参数传递给函数。
        outputs = model.generate(**inputs)
    # 将生成的张量解码为文本
    translated_text = tokenizer.batch_decode(outputs,skip_special_tokens=True)
    return translated_text

# 回译文本
def back_translate(text,tokenizer1,model1,tokenizer2,model2):
    # zh_to_en
    translated_text = translate(text,tokenizer1,model1)
    # en_to_zh
    back_translated_text = translate(translated_text,tokenizer2,model2)
    return back_translated_text

texts = [
    "这是一个简短的句子。",
    "这是一个需要回译的较长句子。",
    "另一个示例句子。"
]

back_translated_texts = [back_translate(text,tokenizer_zh_to_en, model_zh_to_en, tokenizer_en_to_zh, model_en_to_zh) for text in texts]

for original,back_translated in zip(texts,back_translated_texts):
    print(f"Original:{original}")
    print(f"Back_Translated: