深度学习
第二十章 NLP概述
一、自然语言处理基本概念
1. 什么是NLP?
- NLP是Natural Language Processing(自然语言处理)简写,NLP常见定义有:
- 定义一:自然语言处理是计算机科学与语言中关于计算机与人类语言转换的领域。 – 中文维基百科
- 定义二:自然语言处理是人工智能领域中一个重要的方向。它研究实现人与计算机之间用自然语言进行有效沟通的各种理论和方法。 – 百度百科
- 定义三:研究在人与人交际中及人与计算机交际中的语言问题的一门学科。NLP要研制表示语言能力和语言应用的模型,建立计算机框架来实现这些语言模型,提出相应的方法来不断完善这种模型,并根据语言模型设计各种实用系统,以及对这些系统的评测技术。-- Bill Manaris,《从人机交互的角度看自然语言处理》
- NLP还有其它一些名称:
- 自然语言理解(Natural Language Understanding)
- 计算机语言学(Computational Linguistics)
- 人类语言技术(Human Language Technology)
2. NLP的主要任务
- 分词:该任务将文本语料库分隔成原子单元(例如:单词)。虽然看似微不足道,但是分词是一项重要任务。例如,在日语中,词语不以空格或标点符号分隔
- 词义消歧:词义消歧是识别单词正确含义的任务。例如,在句子“The dog barked at the mailman”(狗对邮递员吠叫)和“Tree bark is sometimes used as a medicine”(树皮有时用作药物)中,单词bark有两种不同的含义。词义消歧对于诸如问答之类的任务至关重要
- 命名实体识别(NER):NER尝试从给定的文本主体或文本语料库中提取实体(例如,人物、位置或组织)。例如,句子:
- NER在诸如信息检索和知识表示等领域不可或缺
- 词性(PoS)标记:PoS标记是将单词分配到各自对应词性的任务。它既可以是名词、动词、形容词、副词、介词等基本词,也可以是专有名词、普通名词、短语动词、动词等
- 句子/概要分类:句子或概要(例如,电影评论)分类有许多应用场景,例如垃圾邮件检测、新闻文章分类(例如,政治、科技和运动)和产品评论评级(即正向或负向)。我们可以用标记数据(即人工对评论标上正面或负面的标签)训练一个分类模型来实现这项任务
- 语言生成:在语言生成中,我们使用文本语料库(包含大量文本文档)来训练学习模型(例如,神经网络),以预测后面的新文本
- 问答(QA):QA技术具有很高的商业价值,这些技术是聊天机器人和VA(例如,Google Assistant和Apple Siri)的基础。许多公司已经采用聊天机器人来提供客户支持
- 机器翻译(MT):MT是将句子/短语从源语言(例如,德语)转换为目标语言(例如,英语)的任务
二、传统NLP方法
1. 传统NLP流程
2. 传统NLP特征工程
-
词袋模型。将所有词语装进一个袋子里,不考虑其词法和语序的问题,即每个词语都是独立的,统计并产生每个词出现的频率。词袋方法的一个关键缺陷是,由于不再保留单词的顺序,它会丢失上下文信息
-
N-Gram模型。N-Gram是一种基于统计语言模型,语言模型是一个基于概率的判别模型,它的输入是个句子(由词构成的顺序序列),输出是这句话的概率,即这些单词的联合概率
- 常用的有Bi-gram(N=2)和Tri-gram(N=3)。例如:
- 句子:I love deep learning
- Bi-gram:{I, love}, {love, deep}, {deep, learning}
- Tri-gram:{I, love, deep}, {love, deep, learning}
- N-Gram基本思想是将文本里面的内容按照字节进行大小为n的滑动窗口操作,形成了长度是n的字节片段序列。每一个字节片段称为一个gram,对所有gram的出现频度进行统计,并按照事先设置好的频度阈值进行过滤,形成关键gram列表,也就是这个文本向量的特征空间,列表中的每一种gram就是一个特征向量维度
- 常用的有Bi-gram(N=2)和Tri-gram(N=3)。例如:
-
共现矩阵。共现(co-occurrence)矩阵指通过统计一个事先指定大小的窗口内的word共现次数,以word周边的共现词的次数构成一个矩阵。例如:
- 优缺点
- 优点:矩阵定义的词向量在一定程度上缓解了one-hot向量相似度为0的问题;
- 缺点:但没有解决数据稀疏性和维度灾难的问题。
- 优缺点
-
词频-逆文档频率(TF-IDF)。词频-逆文档频率表示词语的语义贡献度,表达式为:
T F − I D F = T F ∗ I D F TF - IDF = TF * IDF TF−IDF=TF∗IDF
其中:I D F = l o g ( n d o c s ( w , D ) + 1 ) IDF = log(\frac{n}{docs(w,D)}+1) IDF=log(docs(w,D)n+1)
n表示文档总数,与doc(w,D)表示词w所出现文件数
3. 传统NLP缺陷
- 需要人工手动设计特征工程
- 传统NLP中使用的预处理步骤迫使我们对文本中嵌入的潜在有用信息(例如,标点符号和时态信息)进行取舍权衡,以便通过减少词汇量来使学习成为可能
- 传统方法需要各种外部资源才能表现良好,并且没有多少免费提供的资源
- 精度、准确度低
三、深度学习NLP方法
1. 深度学习文本处理方式
- 文本表示:将文本表达成类似图像、语音的连续稠密数据,利用卷积神经网络提取文本的局部相关性
- 特征提取:利用CNN/RNN强大的表征能力,自动提取特征,去掉繁杂的人工特征工程
2. 深度学习文本表示方式
- 深度学习使用分布式单词表示技术(也称词嵌入表示),通过查看所使用的单词的周围单词(即上下文)来学习单词表示。这种表示方式将词表示为一个粘稠的序列,在保留词上下文信息的同时,避免维度过大导致的计算困难。
3. TextCNN模型
4. 标准CNN模型的不足
- 假设数据之间是独立的。标准CNN假设数据之间是独立的,所以在处理前后依赖、序列问题(如语音、文本、视频)时就显得力不从心。这一类数据(如文本)和图像数据差别非常大,最明显的差别莫过于,文本数据对文字的前后次序非常敏感。所以,需要发展新的理论模型。
- 标准CNN网络还存在一个短板,输入都是标准的等长向量,而序列数据长度是可变的。
5. RNN模型
- 循环神经网络(Recurrent Neural Network,RNN)是一类具有短期记忆能力的神经网络,适合用于处理视频、语音、文本等与时序相关的问题
- 连接不仅存在于相邻的层与层之间(比如输入层-隐藏层),还存在于时间维度上的隐藏层与隐藏层之间(反馈连接, h 1 h_1 h1到 h t h_t ht)。某个时刻t,网络的输入不仅和当前时刻的输入相关,也和上一个时刻的隐状态相关
- 循环神经网络内部结构
- RNN模型输入输出关系对应模式
6. LSTM模型
第二十一章 利用TextCNN实现文本分类
一、什么是文本分类?
- 图像分类就是将文本划分到不同类别,例如新闻系统中,每篇新闻报道会划归到不同的类别。本质是找到一个有效的映射函数,实现从文本到类别的映射
- 文本分类主要包括:
二、文本分类的应用
- 内容分类(新闻分类)
- 邮件过滤(例如垃圾邮件过滤)
- 评论、文章、对话的情感分类(正面、负面、中性)
三、案例目标
- 目标:利用训练数据集,对模型训练,从而实现对中文新闻摘要类别正确划分
四、数据集介绍
- 来源:从网站上爬取56821条数据中文新闻摘要
- 数据内容:包含10种类别,国际、文化、娱乐、体育、财经、汽车、教育、科技、房产、证券
五、原始数据格式
六、网络模型介绍
七、总体步骤
- 数据预处理:解析数据文件,编码,建立训练集、测试集
- 训练与模型评估
- 输入测试数据,进行预测
八、数据预处理
- 字典编码
- 文本编码
九、代码
# 中文资讯分类
# 任务:根据样本,训练模型,实现新闻摘要正确分类
# 1.数据预处理
import os
from multiprocessing import cpu_count
import numpy as np
import paddle
import paddle.fluid as fluid
# 定义公共变量
data_root = "data/news_classify/" # 数据集所在目录
data_file = "news_classify_data.txt" # 原始样本文件名称
test_file = "test_list.txt" # 测试集文件名称
train_file = "train_list.txt" # 训练集文件名称
dict_file = "dict_txt.txt" # 编码字典文件
data_file_path = data_root + data_file # 原始样本文件完整路径
dict_file_path = data_root + dict_file # 编码字典完整路径
test_file_path = data_root + test_file # 测试集文件完整路径
train_file_path = data_root + train_file # 训练集文件完整路径
# 生成字典文件:把每个字编码成一个唯一的数字,并存入文件
def create_dict():
dict_set = set() # 集合,去重
with open(data_file_path, "r", encoding="utf-8") as f:
lines = f.readlines() # 读取所有样本
# 遍历每行
for line in lines:
# 拆分样本行,取出标题部分,并去掉换行符
title = line.split("_!_")[-1].replace("\n", "")
for w in title: # 取出每个字
dict_set.add(w) # 将字推入集合去重
# 遍历集合,为每个字分配一个编码
dict_list = []
i = 1 # 计数器
for s in dict_set:
dict_list.append([s, i]) # 将"文字-编码"子列表添加到列表中
i += 1
dict_txt = dict(dict_list) # 将列表直接转换为字典
end_dict = {"<unk>": i} # 未知字符
dict_txt.update(end_dict) # 将未知字符-编码键值对添加到字典中
# 将字典对象保存到文件中
with open(dict_file_path, "w", encoding="utf-8") as f:
f.write(str(dict_txt)) # 将字典对象转换为字符串,并存入文件
print("生成字典完成.")
# 对一行标题进行编码
def line_encoding(title, dict_txt, label):
new_line = "" # 编码后的结果
for w in title:
if w in dict_txt: # 字已经在字典中,直接取出编码
code = str(dict_txt[w])
else: # 未在字典中,取出未知字符编码
code = str(dict_txt["<unk>"])
new_line = new_line + code + "," # 将编码值追加到新字符后面
new_line = new_line[:-1] # 去掉最后一个多余的逗号
new_line = new_line + "\t" + label + "\n" # 拼接编码后的字符串及类别
return new_line
# 对原始样本进行编码,将编码后的字符串存入测试集、训练集
def create_data_list():
# 清空测试集、训练集
with open(test_file_path, "w") as f:
pass
with open(train_file_path, "w") as f:
pass
# 打开原始样本,取出每个样本的标题部分进行编码
with open(data_file_path, "r", encoding="utf-8") as f:
lines = f.readlines()
with open(dict_file_path, "r", encoding="utf-8") as f:
# 读取字典文件的第一行(只有一行),并当作表达式执行,返回一个字典对象
dict_txt = eval(f.readlines()[0])
# 遍历样本每行,取出标题部分编码
i = 0
for line in lines:
words = line.replace("\n", "").split("_!_")
label = words[1] # 类别
title = words[3] # 标题
new_line = line_encoding(title, dict_txt, label) # 对标题编码
if i % 10 == 0: # 写测试集
with open(test_file_path, "a", encoding="utf-8") as f:
f.write(new_line)
else: # 写训练集
with open(train_file_path, "a", encoding="utf-8") as f:
f.write(new_line)
i += 1
print("生成测试集、训练集结束.")
create_dict() # 生成字典
create_data_list() # 编码、生成测试集、训练集
# 2.模型搭建、训练、评估
def get_dict_len(dict_path): # 读取字典文件内容,并返回字典长度
with open(dict_path, "r", encoding="utf-8") as f:
line = eval(f.readlines()[0])
return len(line.keys())
# data_mapper:将传入的一行样本转换为整型列表并返回
def data_mapper(sample):
data, label = sample # 将sample元组拆分到两个变量中
# 拆分每个编码后的值,并转换为整数,生成一个列表
val = [int(w) for w in data.split(",")]
return val, int(label) # 返回整型列表和标签(转换为整数标签)
# reader
def train_reader(train_file_path):
def reader():
with open(train_file_path, "r") as f:
lines = f.readlines()
np.random.shuffle(lines) # 打乱样本顺序,做随机化处理
for line in lines:
data, label = line.split("\t")
yield data, label
return paddle.reader.xmap_readers(data_mapper, # reader读取的数据进行下一步处理函数
reader, # 读取样本函数
cpu_count(), # 线程数量
1024) # 缓冲区大小
# 测试集reader
def test_reader(test_file_path):
def reader():
with open(test_file_path, "r") as f:
lines = f.readlines()
for line in lines:
data, label = line.split("\t")
yield data, label
return paddle.reader.xmap_readers(data_mapper, # reader读取的数据进行下一步处理函数
reader, # 读取样本函数
cpu_count(), # 线程数量
1024) # 缓冲区大小
# 定义网络结构
def CNN_net(data, dict_dim, class_dim=10, emb_dim=128, hid_dim=128, hid_dim2=98):
"""
搭建TextCNN模型
:param data: 原始数据
:param dict_dim: 词典大小
:param class_dim: 分类数量
:param emb_dim: 词嵌入计算参数
:param hid_dim: 第一组卷积运算卷积核数量
:param hid_dim2: 第二组卷积运算卷积核数量
:return: 运算结果
"""
# embedding(词嵌入层): 生成词向量,得到一个粘稠的实向量表示
# 能以最少的维度,表达丰富的文本信息
emb = fluid.layers.embedding(input=data, size=[dict_dim, emb_dim])
# 并列两组卷积、池化
conv1 = fluid.nets.sequence_conv_pool(input=emb, # 输入,词嵌入层的输出
num_filters=hid_dim, # 卷积核数量
filter_size=3, # 卷积核大小
act="tanh", # 激活函数
pool_type="sqrt") # 池化类型
conv2 = fluid.nets.sequence_conv_pool(input=emb, # 输入,词嵌入层的输出
num_filters=hid_dim2, # 卷积核数量
filter_size=4, # 卷积核大小
act="tanh", # 激活函数
pool_type="sqrt") # 池化类型
# 输出层
output = fluid.layers.fc(input=[conv1, conv2], # 前面两组卷积层输出作为输入
size=class_dim, # 分类数量
act="softmax")
return output
# 定义变量
model_save_dir = "model/news_classify/" # 模型保存路径
words = fluid.layers.data(name="words", shape=[1], dtype="int64",
lod_level=1) # 张量层级
label = fluid.layers.data(name="label", shape=[1], dtype="int64") # 标签
# 获取字典长度
dict_dim = get_dict_len(dict_file_path)
# 调用函数创建网络
model = CNN_net(words, dict_dim)
# 损失函数
cost = fluid.layers.cross_entropy(input=model, # 预测值
label=label) # 真实值
avg_cost = fluid.layers.mean(cost)
# 准确率
acc = fluid.layers.accuracy(input=model,
label=label)
# 克隆program用于模型评估
test_program = fluid.default_main_program().clone(for_test=True)
# 优化器
optimizer = fluid.optimizer.AdagradOptimizer(learning_rate=0.001)
optimizer.minimize(avg_cost)
# 执行器
place = fluid.CUDAPlace(0)
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())
# reader
tr_reader = train_reader(train_file_path) # 训练集原始读取器
batch_train_reader = paddle.batch(tr_reader, batch_size=128) # 批量读取器
ts_reader = test_reader(test_file_path) # 测试集原始读取器
batch_test_reader = paddle.batch(ts_reader, batch_size=128) # 批量读取器
# feeder
feeder = fluid.DataFeeder(place=place,
feed_list=[words, label]) # 需要喂入的参数
# 开始训练
for pass_id in range(5): # 迭代训练
for batch_id, data in enumerate(batch_train_reader()):
train_cost, train_acc = exe.run(program=fluid.default_main_program(),
feed=feeder.feed(data),
fetch_list=[avg_cost, acc])
# 打印
if batch_id % 100 == 0:
print("pass_id:%d, batch_id:%d, cost:%f, acc:%f" %
(pass_id, batch_id, train_cost[0], train_acc[0]))
# 模型评估
test_costs_list = []
test_accs_list = []
for batch_id, data in enumerate(batch_test_reader()):
test_cost, test_acc = exe.run(program=test_program, # 执行用于测试的program
feed=feeder.feed(data),
fetch_list=[avg_cost, acc])
test_costs_list.append(test_cost[0]) # 记录损失值
test_accs_list.append(test_acc[0]) # 记录准确率
# 计算测试集下平均损失值、准确率
avg_test_cost = sum(test_costs_list) / len(test_costs_list)
avg_test_acc = sum(test_accs_list) / len(test_accs_list)
print("pass_id:%d, test_cost:%f, test_acc:%f" % (pass_id, avg_test_cost, avg_test_acc))
# 训练结束,保存模型
if not os.path.exists(model_save_dir):
os.makedirs(model_save_dir)
fluid.io.save_inference_model(model_save_dir,
feeded_var_names=[words.name], # 预测时需喂入的参数
target_vars=[model], # 预测结果
executor=exe)
print("保存模型完成.")
# 3.模型加载、预测
model_save_dir = "model/news_classify/"
def get_data(sentence): # 对待预测文本编码
# 读取字典中的内容
with open(dict_file_path, "r", encoding="utf-8") as f:
dict_txt = eval(f.readlines()[0])
keys = dict_txt.keys()
ret = [] # 编码后的结果
for s in sentence:
if not s in keys:
s = "<unk>"
ret.append(int(dict_txt[s]))
return ret # 返回经过编码后的列表
# 执行器
place = fluid.CPUPlace()
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())
# 加载模型
print("加载模型")
infer_program, feeded_var_names, target_var = \
fluid.io.load_inference_model(dirname=model_save_dir,
executor=exe)
# 生成一批测试数据
texts = []
data1 = get_data("在获得诺贝尔文学奖7年之后,莫言15日晚在山西汾阳贾家庄如是说") # 文化类
data2 = get_data("综合'每日美国'.'世界日报'等当地媒体,芝加哥滨河警察表示") # 国际类
data3 = get_data("中国队无缘2020年世界杯") # 体育类
data4 = get_data("中国人民银行今日发布通知,降低存款准备金率,预计释放4000亿流动性") # 财经类
data5 = get_data("10月20日,第六届互联网大会正式开幕") # 科技类
data6 = get_data("同一户型,为什么高层比低层要贵那么多?") # 房产类
data7 = get_data("揭秘A股周涨5%资金动向,追捧2类股,抛售600亿香饽饽") # 证券类
data8 = get_data("宋慧乔陷入情感危机,前夫宋仲基不戴口罩露面,身处国外神态轻松") # 娱乐类
data9 = get_data("此盆栽很好养,花美似牡丹,三季开花,南北都能养") # 不属于任何一个类别
texts.append(data1)
texts.append(data2)
texts.append(data3)
texts.append(data4)
texts.append(data5)
texts.append(data6)
texts.append(data7)
texts.append(data8)
texts.append(data9)
# 获取每个句子词数量
base_shape = [[len(c) for c in texts]]
# 生成LodTensor
tensor_words = fluid.create_lod_tensor(texts, base_shape, place)
# 执行预测
result = exe.run(program=infer_program,
feed={feeded_var_names[0]: tensor_words},
fetch_list=target_var)
# print(result)
names = ["文化", "娱乐", "体育", "财经", "房产",
"汽车", "教育", "科技", "国际", "证券"]
for i in range(len(texts)):
lab = np.argsort(result)[0][i][-1] # 对第i个预测结果排序,取出最大值的下标
print("预测结果:%d, 名称:%s,概率:%f" % (lab, names[lab], result[0][i][lab]))