Pytorch:jieba分词、hanlp分词、词性标注、命名实体识别、one-hot、Word2vec(CBOW、skipgram)、Word Embedding词嵌入、fasttext

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


文本预处理及其作用:

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

文本预处理中包含的主要环节:

  • 文本处理的基本方法
  • 文本张量表示方法
  • 文本语料的数据分析
  • 文本特征处理
  • 数据增强方法

文本处理的基本方法:

  • 分词
  • 词性标注
  • 命名实体识别

文本张量表示方法:

  • one-hot编码
  • Word2vec
  • Word Embedding

文本语料的数据分析:

  • 标签数量分布
  • 句子长度分布
  • 词频统计与关键词词云

文本特征处理:

  • 添加n-gram特征
  • 文本长度规范

数据增强方法:

  • 回译数据增强法

重要说明:

  • 在实际生产应用中, 我们最常使用的两种语言是中文和英文, 因此, 文本预处理部分的内容都将针对这两种语言进行讲解.

1.2 文本处理的基本方法

学习目标:

  • 了解什么是分词, 词性标注, 命名实体识别及其它们的作用.
  • 掌握分词, 词性标注, 命名实体识别流行工具的使用方法.

什么是分词:

  • 分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。我们知道,在英文的行文中,单词之间是以空格作为自然分界符的,而中文只是字、句和段能通过明显的分界符来简单划界,唯独词没有一个形式上的分界符, 分词过程就是找到这样分界符的过程.

举个栗子:

工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作 

==> 

['工信处', '女干事', '每月', '经过', '下属', '科室', '都', '要', '亲口', '交代', '24', '口', '交换机', '等', '技术性', '器件', '的', '安装', '工作']

分词的作用:

  • 词作为语言语义理解的最小单元, 是人类理解文本语言的基础. 因此也是AI解决NLP领域高阶任务, 如自动问答, 机器翻译, 文本生成的重要基础环节.

流行中文分词工具jieba:

  • 愿景: “结巴”中文分词, 做最好的 Python 中文分词组件.

jieba的特性:

  • 支持多种分词模式
    • 精确模式
    • 全模式
    • 搜索引擎模式
  • 支持中文繁体分词
  • 支持用户自定义词典

jieba的安装: pip install jieba

jieba的使用:

  • 精确模式分词:
    • 试图将句子最精确地切开,适合文本分析.
>>> import jieba
>>> content = "工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作"
>>> jieba.cut(content, cut_all=False)  # cut_all默认为False

# 将返回一个生成器对象
<generator object Tokenizer.cut at 0x7f065c19e318>

# 若需直接返回列表内容, 使用jieba.lcut即可
>>> jieba.lcut(content, cut_all=False)
['工信处', '女干事', '每月', '经过', '下属', '科室', '都', '要', '亲口', '交代', '24', '口', '交换机', '等', '技术性', '器件', '的', '安装', '工作']
  • 全模式分词:
    • 把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能消除 歧义.
>>> import jieba
>>> content = "工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作"
>>> jieba.cut(content, cut_all=True)  # cut_all默认为False

# 将返回一个生成器对象
<generator object Tokenizer.cut at 0x7f065c19e318>

# 若需直接返回列表内容, 使用jieba.lcut即可
>>> jieba.lcut(content, cut_all=True)
['工信处', '处女', '女干事', '干事', '每月', '月经', '经过', '下属', '科室', '都', '要', '亲口', '口交', '交代', '24', '口交', '交换', '交换机', '换机', '等', '技术', '技术性', '性器', '器件', '的', '安装', '安装工', '装工', '工作']
  • 搜索引擎模式分词:
    • 在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜 索引擎分词.
>>> import jieba
>>> content = "工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作"
>>> jieba.cut_for_search(content)

# 将返回一个生成器对象
<generator object Tokenizer.cut at 0x7f065c19e318>

# 若需直接返回列表内容, 使用jieba.lcut_for_search即可
>>> jieba.lcut_for_search(content)
['工信处', '干事', '女干事', '每月', '经过', '下属', '科室', '都', '要', '亲口', '交代', '24', '口', '交换', '换机', '交换机', '等', '技术', '技术性', '器件', '的', '安装', '工作']

# 对'女干事', '交换机'等较长词汇都进行了再次分词.
  • 中文繁体分词:
    • 针对中国香港, 台湾地区的繁体文本进行分词.
>>> import jieba
>>> content = "煩惱即是菩提,我暫且不提"
>>> jieba.lcut(content)
['煩惱', '即', '是', '菩提', ',', '我', '暫且', '不', '提']

使用用户自定义词典:

  • 添加自定义词典后, jieba能够准确识别词典中出现的词汇,提升整体的识别准确率.
  • 词典格式: 每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开,顺序不可颠倒.
  • 词典样式如下, 具体词性含义请参照附录: jieba词性对照表, 将该词典存为userdict.txt, 方便之后加载使用.
云计算 5 n
李小福 2 nr
easy_install 3 eng
好用 300
韩玉赏鉴 3 nz
八一双鹿 3 nz
>>> import jieba
>>> jieba.lcut("八一双鹿更名为八一南昌篮球队!")
# 没有使用用户自定义词典前的结果:
>>> ['八', '一双', '鹿', '更名', '为', '八一', '南昌', '篮球队', '!']


>>> jieba.load_userdict("./userdict.txt")
# 使用了用户自定义词典后的结果:
['八一双鹿', '更名', '为', '八一', '南昌', '篮球队', '!']

流行中英文分词工具hanlp:

  • 中英文NLP处理工具包, 基于tensorflow2.0, 使用在学术界和行业中推广最先进的深度学习技术.

hanlp的安装:

# 使用pip进行安装
pip install hanlp

使用hanlp进行中文分词:

>>> import hanlp
# 加载CTB_CONVSEG预训练模型进行分词任务
>>> tokenizer = hanlp.load('CTB6_CONVSEG')
>>> tokenizer("工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作")
['工信处', '女', '干事', '每', '月', '经过', '下', '属', '科室', '都', '要', '亲口', '交代', '24口', '交换机', '等', '技术性', '器件', '的', '安装', '工作']

使用hanlp进行英文分词:

# 进行英文分词, 英文分词只需要使用规则即可
>>> tokenizer = hanlp.utils.rules.tokenize_english 
>>> tokenizer('Mr. Hankcs bought hankcs.com for 1.5 thousand dollars.')
['Mr.', 'Hankcs', 'bought', 'hankcs.com', 'for', '1.5', 'thousand', 'dollars', '.']

什么是命名实体识别:

  • 命名实体: 通常我们将人名, 地名, 机构名等专有名词统称命名实体. 如: 周杰伦, 黑山县, 孔子学院, 24辊方钢矫直机.
  • 顾名思义, 命名实体识别(Named Entity Recognition,简称NER)就是识别出一段文本中可能存在的命名实体.

举个栗子:

鲁迅, 浙江绍兴人, 五四新文化运动的重要参与者, 代表作朝花夕拾.

==>

鲁迅(人名) / 浙江绍兴(地名)人 / 五四新文化运动(专有名词) / 重要参与者 / 代表作 / 朝花夕拾(专有名词)

命名实体识别的作用:

  • 同词汇一样, 命名实体也是人类理解文本的基础单元, 因此也是AI解决NLP领域高阶任务的重要基础环节.

使用hanlp进行中文命名实体识别:

>>> import hanlp
# 加载中文命名实体识别的预训练模型MSRA_NER_BERT_BASE_ZH
>>> recognizer = hanlp.load(hanlp.pretrained.ner.MSRA_NER_BERT_BASE_ZH)
# 这里注意它的输入是对句子进行字符分割的列表, 因此在句子前加入了list()
# >>> list('上海华安工业(集团)公司董事长谭旭光和秘书张晚霞来到美 国纽约现代艺术博物馆参观。')
# ['上', '海', '华', '安', '工', '业', '(', '集', '团', ')', '公', '司', '董', '事', '长', '谭', '旭', '光', '和', '秘', '书', '张', '晚', '霞', '来', '到', '美', '国', '纽', '约', '现', '代', '艺', '术', '博', '物', '馆', '参', '观', '。'] 
>>> recognizer(list('上海华安工业(集团)公司董事长谭旭光和秘书张晚霞来到美国纽约现代艺术博物馆参观。'))
[('上海华安工业(集团)公司', 'NT', 0, 12), ('谭旭光', 'NR', 15, 18), ('张晚霞', 'NR', 21, 24), ('美国', 'NS', 26, 28), ('纽约现代艺术博物馆', 'NS', 28, 37)]

# 返回结果是一个装有n个元组的列表, 每个元组代表一个命名实体, 元组中的每一项分别代表具体的命名实体, 如: '上海华安工业(集团)公司'; 命名实体的类型, 如: 'NT'-机构名; 命名实体的开始索引和结束索引, 如: 0, 12.

使用hanlp进行英文命名实体识别:

>>> import hanlp
# 加载英文命名实体识别的预训练模型CONLL03_NER_BERT_BASE_UNCASED_EN
>>> recognizer = hanlp.load(hanlp.pretrained.ner.CONLL03_NER_BERT_BASE_UNCASED_EN))
# 这里注意它的输入是对句子进行分词后的结果, 是列表形式.
>>> recognizer(["President", "Obama", "is", "speaking", "at", "the", "White", "House"])
[('Obama', 'PER', 1, 2), ('White House', 'LOC', 6, 8)]
# 返回结果是一个装有n个元组的列表, 每个元组代表一个命名实体, 元组中的每一项分别代>表具体的命名实体, 如: 'Obama', 如: 'PER'-人名; 命名实体的开始索引和结束索引, 如: 1, 2.

什么是词性标注:

  • 词性: 语言中对词的一种分类方法,以语法特征为主要依据、兼顾词汇意义对词进行划分的结果, 常见的词性有14种, 如: 名词, 动词, 形容词等.
  • 顾名思义, 词性标注(Part-Of-Speech tagging, 简称POS)就是标注出一段文本中每个词汇的词性.

举个栗子:

我爱自然语言处理

==>

我/rr, 爱/v, 自然语言/n, 处理/vn

rr: 人称代词
v: 动词
n: 名词
vn: 动名词

词性标注的作用:

  • 词性标注以分词为基础, 是对文本语言的另一个角度的理解, 因此也常常成为AI解决NLP领域高阶任务的重要基础环节.

使用jieba进行中文词性标注:

>>> import jieba.posseg as pseg
>>> pseg.lcut("我爱北京天安门") 
[pair('我', 'r'), pair('爱', 'v'), pair('北京', 'ns'), pair('天安门', 'ns')]

# 结果返回一个装有pair元组的列表, 每个pair元组中分别是词汇及其对应的词性, 具体词性含义请参照[附录: jieba词性对照表]()

使用hanlp进行中文词性标注:

>>> import hanlp
# 加载中文命名实体识别的预训练模型CTB5_POS_RNN_FASTTEXT_ZH
>>> tagger = hanlp.load(hanlp.pretrained.pos.CTB5_POS_RNN_FASTTEXT_ZH)
# 输入是分词结果列表
>>> tagger(['我', '的', '希望', '是', '希望', '和平'])
# 结果返回对应的词性
['PN', 'DEG', 'NN', 'VC', 'VV', 'NN']

使用hanlp进行英文词性标注:

>>> import hanlp
# 加载英文命名实体识别的预训练模型PTB_POS_RNN_FASTTEXT_EN
>>> tagger = hanlp.load(hanlp.pretrained.pos.PTB_POS_RNN_FASTTEXT_EN)
# 输入是分词结果列表
>>> tagger(['I', 'banked', '2', 'dollars', 'in', 'a', 'bank', '.'])
['PRP', 'VBD', 'CD', 'NNS', 'IN', 'DT', 'NN', '.']

小节总结:

  • 学习了什么是分词:
    • 分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。我们知道,在英文的行文中,单词之间是以空格作为自然分界符的,而中文只是字、句和段能通过明显的分界符来简单划界,唯独词没有一个形式上的分界符, 分词过程就是找到这样分界符的过程.

学习了分词的作用:

  • 词作为语言语义理解的最小单元, 是人类理解文本语言的基础. 因此也是AI解决NLP领域高阶任务, 如自动问答, 机器翻译, 文本生成的重要基础环节.

学习了流行中文分词工具jieba:

  • 支持多种分词模式: 精确模式, 全模式, 搜索引擎模式
  • 支持中文繁体分词
  • 支持用户自定义词典

学习了jieba工具的安装和分词使用.

学习了流行中英文分词工具hanlp及其安装和分词使用.

学习了什么是命名实体识别:

  • 命名实体: 通常我们将人名, 地名, 机构名等专有名词统称命名实体. 如: 周杰伦, 黑山县, 孔子学院, 24辊方钢矫直机.
  • 顾名思义, 命名实体识别(Named Entity Recognition,简称NER)就是识别出一段文本中可能存在的命名实体.

命名实体识别的作用:

  • 同词汇一样, 命名实体也是人类理解文本的基础单元, 因此也是AI解决NLP领域高阶任务的重要基础环节.

学习了使用hanlp进行命名实体识别.

学习了什么是词性标注:

  • 词性: 语言中对词的一种分类方法,以语法特征为主要依据、兼顾词汇意义对词进行划分的结果, 常见的词性有14种, 如: 名词, 动词, 形容词等.
  • 顾名思义, 词性标注(Part-Of-Speech tagging, 简称POS)就是标注出一段文本中每个词汇的词性.

学习了词性标注的作用:

  • 词性标注以分词为基础, 是对文本语言的另一个角度的理解, 因此也常常成为AI解决NLP领域高阶任务的重要基础环节.

学习了使用jieba和hanlp进行词性标注.


1.3 文本张量表示方法

  • 学习目标:
    • 了解什么是文本张量表示及其作用.
    • 掌握文本张量表示的几种方法及其实现.

什么是文本张量表示:

  • 将一段文本使用张量进行表示,其中一般将词汇为表示成向量,称作词向量,再由各个词向量按顺序组成矩阵形成文本表示.

举个栗子:

["人生", "该", "如何", "起头"]

==>

# 每个词对应矩阵中的一个向量
[[1.32, 4,32, 0,32, 5.2],
 [3.1, 5.43, 0.34, 3.2],
 [3.21, 5.32, 2, 4.32],
 [2.54, 7.32, 5.12, 9.54]]

文本张量表示的作用:

  • 将文本表示成张量(矩阵)形式,能够使语言文本可以作为计算机处理程序的输入,进行接下来一系列的解析工作.

文本张量表示的方法:

  • one-hot编码
  • Word2vec
  • Word Embedding

什么是one-hot词向量表示:

  • 又称独热编码,将每个词表示成具有n个元素的向量,这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素为0的位置不同,其中n的大小是整个语料中不同词汇的总数.

举个栗子:

["改变", "要", "如何", "起手"]

==>

[[1, 0, 0, 0],
 [0, 1, 0, 0],
 [0, 0, 1, 0],
 [0, 0, 0, 1]]

onehot编码实现:

  • 进行onehot编码:
# 导入用于对象保存与加载的joblib
from sklearn.externals import joblib
# 导入keras中的词汇映射器Tokenizer
from keras.preprocessing.text import Tokenizer
# 假定vocab为语料集所有不同词汇集合
vocab = {"周杰伦", "陈奕迅", "王力宏", "李宗盛", "吴亦凡", "鹿晗"}
# 实例化一个词汇映射器对象
t = Tokenizer(num_words=None, char_level=False)
# 使用映射器拟合现有文本数据
t.fit_on_texts(vocab)

for token in vocab:
    zero_list = [0]*len(vocab)
    # 使用映射器转化现有文本数据, 每个词汇对应从1开始的自然数
    # 返回样式如: [[2]], 取出其中的数字需要使用[0][0]
    token_index = t.texts_to_sequences([token])[0][0] - 1
    zero_list[token_index] = 1
    print(token, "的one-hot编码为:", zero_list)

# 使用joblib工具保存映射器, 以便之后使用
tokenizer_path = "./Tokenizer"
joblib.dump(t, tokenizer_path)

输出效果:

鹿晗 的one-hot编码为: [1, 0, 0, 0, 0, 0]
王力宏 的one-hot编码为: [0, 1, 0, 0, 0, 0]
李宗盛 的one-hot编码为: [0, 0, 1, 0, 0, 0]
陈奕迅 的one-hot编码为: [0, 0, 0, 1, 0, 0]
周杰伦 的one-hot编码为: [0, 0, 0, 0, 1, 0]
吴亦凡 的one-hot编码为: [0, 0, 0, 0, 0, 1]

# 同时在当前目录生成Tokenizer文件, 以便之后使用

onehot编码器的使用:

# 导入用于对象保存与加载的joblib
# from sklearn.externals import joblib
# 加载之前保存的Tokenizer, 实例化一个t对象
t = joblib.load(tokenizer_path)

# 编码token为"李宗盛"
token = "李宗盛"
# 使用t获得token_index
token_index = t.texts_to_sequences([token])[0][0] - 1
# 初始化一个zero_list
zero_list = [0]*len(vocab)
# 令zero_List的对应索引为1
zero_list[token_index] = 1
print(token, "的one-hot编码为:", zero_list) 

输出效果:

李宗盛 的one-hot编码为: [1, 0, 0, 0, 0, 0]

one-hot编码的优劣势:

  • 优势:操作简单,容易理解.
  • 劣势:完全割裂了词与词之间的联系,而且在大语料集下,每个向量的长度过大,占据大量内存.

说明:

  • 正因为one-hot编码明显的劣势,这种编码方式被应用的地方越来越少,取而代之的是接下来我们要学习的稠密向量的表示方法word2vec和word embedding.

# 导入用于对象保存和加载的包
from sklearn.externals import joblib
# 导入keras中的词汇映射器Tokenizer
from keras.preprocessing.text import Tokenizer

# 初始化一个词汇表
vocab = {"周杰伦", "陈奕迅", "王力宏", "李宗盛", "吴亦凡", "鹿晗"}

# 实例化一个词汇映射器
t = Tokenizer(num_words=None, char_level=False)

# 在映射器上拟合现有的词汇表
t.fit_on_texts(vocab)

# 循环遍历词汇表, 将每一个单词映射为one-hot张量表示
for token in vocab:
    # 初始化一个全零向量
    zero_list = [0] * len(vocab)
    # 使用映射器转化文本数据, 每个词汇对应从1开始
    token_index = t.texts_to_sequences([token])[0][0] - 1
    # 将对应的位置赋值为1
    zero_list[token_index] = 1
    print(token, "的one-hot编码为:", zero_list)

# 将拟合好的词汇映射器保存起来
tokenizer_path = "./Tokenizer"
joblib.dump(t, tokenizer_path)

# 导入用于对象保存与加载的joblib
from sklearn.externals import joblib
# 导入keras中的词汇映射器Tokenizer
from keras.preprocessing.text import Tokenizer
# 假定vocab为语料集所有不同词汇集合
vocab = {"周杰伦", "陈奕迅", "王力宏", "李宗盛", "吴亦凡", "鹿晗"}
# 实例化一个词汇映射器对象
t = Tokenizer(num_words=None, char_level=False)
# 使用映射器拟合现有文本数据
t.fit_on_texts(vocab)

for token in vocab:
    zero_list = [0]*len(vocab)
    # 使用映射器转化现有文本数据, 每个词汇对应从1开始的自然数
    # 返回样式如: [[2]], 取出其中的数字需要使用[0][0]
    token_index = t.texts_to_sequences([token])[0][0] - 1
    zero_list[token_index] = 1
    print(token, "的one-hot编码为:", zero_list)

# 使用joblib工具保存映射器, 以便之后使用
tokenizer_path = "./Tokenizer"
joblib.dump(t, tokenizer_path)


# 导入用于对象保存与加载的joblib
# from sklearn.externals import joblib
# 加载之前保存的Tokenizer, 实例化一个t对象
t = joblib.load(tokenizer_path)

# 编码token为"李宗盛"
token = "李宗盛"
# 使用t获得token_index
token_index = t.texts_to_sequences([token])[0][0] - 1
# 初始化一个zero_list
zero_list = [0]*len(vocab)
# 令zero_List的对应索引为1
zero_list[token_index] = 1
print(token, "的one-hot编码为:", zero_list)
# 导入用于对象保存于加载的包
from sklearn.externals import joblib
# 将之前已经训练好的词汇映射器加载进来
t = joblib.load("./Tokenizer")

token = "李宗盛"
# 从词汇映射器中得到李宗盛的index
token_index = t.texts_to_sequences([token])[0][0] - 1
# 初始化一个全零的向量
zero_list = [0] * 6
zero_list[token_index] = 1
print(token, "的one-hot编码为:", zero_list)


什么是word2vec:

  • 是一种流行的将词汇表示成向量的无监督训练方法, 该过程将构建神经网络模型, 将网络参数作为词汇的向量表示, 它包含CBOW和skipgram两种训练模式.

CBOW(Continuous bag of words)模式:

  • 给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用上下文词汇预测目标词汇.

分析:

  • 图中窗口大小为9, 使用前后4个词汇对目标词汇进行预测.

CBOW模式下的word2vec过程说明:

  • 假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope can set,因为是CBOW模式,所以将使用Hope和set作为输入,can作为输出,在模型训练时, Hope,can,set等词汇都使用它们的one-hot编码. 如图所示: 每个one-hot编码的单词与各自的变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘之后再相加, 得到上下文表示矩阵(3x1).

  • 接着, 将上下文表示矩阵与变换矩阵(参数矩阵5x3, 所有的变换矩阵共享参数)相乘, 得到5x1的结果矩阵, 它将与我们真正的目标矩阵即can的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模型迭代.

  • 最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示.

skipgram模式:

  • 给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用目标词汇预测上下文词汇.

分析:

  • 图中窗口大小为9, 使用目标词汇对前后四个词汇进行预测.

skipgram模式下的word2vec过程说明:

  • 假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope can set,因为是skipgram模式,所以将使用can作为输入 ,Hope和set作为输出,在模型训练时, Hope,can,set等词汇都使用它们的one-hot编码. 如图所示: 将can的one-hot编码与变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘, 得到目标词汇表示矩阵(3x1).

  • 接着, 将目标词汇表示矩阵与多个变换矩阵(参数矩阵5x3)相乘, 得到多个5x1的结果矩阵, 它将与我们Hope和set对应的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模 型迭代.

  • 最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵即参数矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示.

使用fasttext工具实现word2vec的训练和使用:

  • 第一步: 获取训练数据
  • 第二步: 训练词向量
  • 第三步: 模型超参数设定
  • 第四步: 模型效果检验
  • 第五步: 模型的保存与重加载
  • 第一步: 获取训练数据
# 在这里, 我们将研究英语维基百科的部分网页信息, 它的大小在300M左右
# 这些语料已经被准备好, 我们可以通过Matt Mahoney的网站下载.
# 首先创建一个存储数据的文件夹data
$ mkdir data
# 使用wget下载数据的zip压缩包, 它将存储在data目录中
$ wget -c http://mattmahoney.net/dc/enwik9.zip -P data
# 使用unzip解压, 如果你的服务器中还没有unzip命令, 请使用: yum install unzip -y
# 解压后在data目录下会出现enwik9的文件夹
$ unzip data/enwik9.zip -d data

查看原始数据:

$ head -10 data/enwik9

# 原始数据将输出很多包含XML/HTML格式的内容, 这些内容并不是我们需要的
<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.3/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.3/ http://www.mediawiki.org/xml/export-0.3.xsd" version="0.3" xml:lang="en">
  <siteinfo>
    <sitename>Wikipedia</sitename>
    <base>http://en.wikipedia.org/wiki/Main_Page</base>
    <generator>MediaWiki 1.6alpha</generator>
    <case>first-letter</case>
      <namespaces>
      <namespace key="-2">Media</namespace>
      <namespace key="-1">Special</namespace>
      <namespace key="0" />

原始数据处理:

# 使用wikifil.pl文件处理脚本来清除XML/HTML格式的内容
# 注: wikifil.pl文件已为大家提供
$ perl wikifil.pl data/enwik9 > data/fil9

查看预处理后的数据:

# 查看前80个字符
head -c 80 data/fil9

# 输出结果为由空格分割的单词
 anarchism originated as a term of abuse first used against early working class
  • 第二步: 训练词向量
# 代码运行在python解释器中
# 导入fasttext
>>> import fasttext
# 使用fasttext的train_unsupervised(无监督训练方法)进行词向量的训练
# 它的参数是数据集的持久化文件路径'data/fil9'
>>> model = fasttext.train_unsupervised('data/fil9')


# 有效训练词汇量为124M, 共218316个单词
Read 124M words
Number of words:  218316
Number of labels: 0
Progress: 100.0% words/sec/thread:   53996 lr:  0.000000 loss:  0.734999 ETA:   0h 0m

查看单词对应的词向量:

# 通过get_word_vector方法来获得指定词汇的词向量
>>> model.get_word_vector("the")

array([-0.03087516,  0.09221972,  0.17660329,  0.17308897,  0.12863874,
        0.13912526, -0.09851588,  0.00739991,  0.37038437, -0.00845221,
        ...
       -0.21184735, -0.05048715, -0.34571868,  0.23765688,  0.23726143],
      dtype=float32)
  • 第三步: 模型超参数设定
# 在训练词向量过程中, 我们可以设定很多常用超参数来调节我们的模型效果, 如:
# 无监督训练模式: 'skipgram' 或者 'cbow', 默认为'skipgram', 在实践中,skipgram模
式在利用子词方面比cbow更好.
# 词嵌入维度dim: 默认为100, 但随着语料库的增大, 词嵌入的维度往往也要更大.
# 数据循环次数epoch: 默认为5, 但当你的数据集足够大, 可能不需要那么多次.
# 学习率lr: 默认为0.05, 根据经验, 建议选择[0.01,1]范围内.
# 使用的线程数thread: 默认为12个线程, 一般建议和你的cpu核数相同.

>>> model = fasttext.train_unsupervised('data/fil9', "cbow", dim=300, epoch=1, lr=0.1, thread=8)

Read 124M words
Number of words:  218316
Number of labels: 0
Progress: 100.0% words/sec/thread:   49523 lr:  0.000000 avg.loss:  1.777205 ETA:   0h 0m 0s
  • 第四步: 模型效果检验
# 检查单词向量质量的一种简单方法就是查看其邻近单词, 通过我们主观来判断这些邻近单词是否与目标单词相关来粗略评定模型效果好坏.

# 查找"运动"的邻近单词, 我们可以发现"体育网", "运动汽车", "运动服"等. 
>>> model.get_nearest_neighbors('sports')

[(0.8414610624313354, 'sportsnet'), (0.8134572505950928, 'sport'), (0.8100415468215942, 'sportscars'), (0.8021156787872314, 'sportsground'), (0.7889881134033203, 'sportswomen'), (0.7863013744354248, 'sportsplex'), (0.7786710262298584, 'sporty'), (0.7696356177330017, 'sportscar'), (0.7619683146476746, 'sportswear'), (0.7600985765457153, 'sportin')]


# 查找"音乐"的邻近单词, 我们可以发现与音乐有关的词汇.
>>> model.get_nearest_neighbors('music')

[(0.8908010125160217, 'emusic'), (0.8464668393135071, 'musicmoz'), (0.8444250822067261, 'musics'), (0.8113634586334229, 'allmusic'), (0.8106718063354492, 'musices'), (0.8049437999725342, 'musicam'), (0.8004694581031799, 'musicom'), (0.7952923774719238, 'muchmusic'), (0.7852965593338013, 'musicweb'), (0.7767147421836853, 'musico')]

# 查找"小狗"的邻近单词, 我们可以发现与小狗有关的词汇.
>>> model.get_nearest_neighbors('dog')

[(0.8456876873970032, 'catdog'), (0.7480780482292175, 'dogcow'), (0.7289096117019653, 'sleddog'), (0.7269964218139648, 'hotdog'), (0.7114801406860352, 'sheepdog'), (0.6947550773620605, 'dogo'), (0.6897546648979187, 'bodog'), (0.6621081829071045, 'maddog'), (0.6605004072189331, 'dogs'), (0.6398137211799622, 'dogpile')]
  • 第五步: 模型的保存与重加载
# 使用save_model保存模型
>>> model.save_model("fil9.bin")

# 使用fasttext.load_model加载模型
>>> model = fasttext.load_model("fil9.bin")
>>> model.get_word_vector("the")

array([-0.03087516,  0.09221972,  0.17660329,  0.17308897,  0.12863874,
        0.13912526, -0.09851588,  0.00739991,  0.37038437, -0.00845221,
        ...
       -0.21184735, -0.05048715, -0.34571868,  0.23765688,  0.23726143],
      dtype=float32)

"""
pip install fasttext
"""

"""
第一步: 获取训练数据
    # 在这里, 我们将研究英语维基百科的部分网页信息, 它的大小在300M左右
    # 这些语料已经被准备好, 我们可以通过Matt Mahoney的网站下载.
    # 首先创建一个存储数据的文件夹data
    $ mkdir data
    # 使用wget下载数据的zip压缩包, 它将存储在data目录中
    $ wget -c http://mattmahoney.net/dc/enwik9.zip -P data
    # 使用unzip解压, 如果你的服务器中还没有unzip命令, 请使用: yum install unzip -y
    # 解压后在data目录下会出现enwik9的文件夹
    $ unzip data/enwik9.zip -d data
    
    原始数据处理:
        # 使用wikifil.pl文件处理脚本来清除XML/HTML格式的内容
        # 注: wikifil.pl文件已为大家提供
        window上:perl wikifil.pl data/enwik9 > data/fil9 把 enwik9(953M) 转换为 fil9(1.32GB)
        (建议)ubuntu上:perl wikifil.pl data/enwik9 > data/fil9 把 enwik9(953M) 转换为 fil9(680M)
        
    window 安装 perl
        http://strawberryperl.com/ 下载 strawberry-perl-5.30.2.1-64bit.msi
        where perl 查看安装路径
        perl -version 查看安装版本
        
    ubuntu 安装 perl
        sudo apt-get install perl
 
    查看预处理后的数据
        # 查看前80个字符
        head -c 80 data/fil9
        
        # 输出结果为由空格分割的单词
        anarchism originated as a term of abuse first used against early working class
"""

"""
第二步: 训练词向量
    # 代码运行在python解释器中
    # 导入fasttext
    >>> import fasttext
    # 使用fasttext的train_unsupervised(无监督训练方法)进行词向量的训练
    # 它的参数是数据集的持久化文件路径'data/fil9'
    >>> model = fasttext.train_unsupervised('data/fil9')
 
    # 有效训练词汇量为124M, 共218316个单词
    Read 124M words
    Number of words:  218316
    Number of labels: 0
    Progress: 100.0% words/sec/thread:   53996 lr:  0.000000 loss:  0.734999 ETA:   0h 0m

    查看单词对应的词向量
        # 通过get_word_vector方法来获得指定词汇的词向量
        model.get_word_vector("the")
"""
# 代码运行在python解释器中
# 导入fasttext
import fasttext
# 使用fasttext的train_unsupervised(无监督训练方法)进行词向量的训练
# 它的参数是数据集的持久化文件路径'data/fil9'
# model = fasttext.train_unsupervised('fil9')

"""
第五步: 模型的保存与重加载
    # 使用save_model保存模型
    >>> model.save_model("fil9.bin")
    
    # 使用fasttext.load_model加载模型
    >>> model = fasttext.load_model("fil9.bin")
    >>> model.get_word_vector("the")
"""

# model.save_model("fil9.bin")
model = fasttext.load_model("fil9.bin")
print(model.get_word_vector("the"))

"""
第三步: 模型超参数设定
    # 在训练词向量过程中, 我们可以设定很多常用超参数来调节我们的模型效果, 如:
    # 无监督训练模式: 'skipgram' 或者 'cbow', 默认为'skipgram', 在实践中,skipgram模
    式在利用子词方面比cbow更好.
    # 词嵌入维度dim: 默认为100, 但随着语料库的增大, 词嵌入的维度往往也要更大.
    # 数据循环次数epoch: 默认为5, 但当你的数据集足够大, 可能不需要那么多次.
    # 学习率lr: 默认为0.05, 根据经验, 建议选择[0.01,1]范围内.
    # 使用的线程数thread: 默认为12个线程, 一般建议和你的cpu核数相同.
    
    >>> model = fasttext.train_unsupervised('data/fil9', "cbow", dim=300, epoch=1, lr=0.1, thread=8)
    
    Read 124M words
    Number of words:  218316
    Number of labels: 0
    Progress: 100.0% words/sec/thread:   49523 lr:  0.000000 avg.loss:  1.777205 ETA:   0h 0m 0s
"""

"""
第四步: 模型效果检验 
    # 检查单词向量质量的一种简单方法就是查看其邻近单词, 通过我们主观来判断这些邻近单词是否与目标单词相关来粗略评定模型效果好坏.
    # 查找"运动"的邻近单词, 我们可以发现"体育网", "运动汽车", "运动服"等. 
    >>> model.get_nearest_neighbors('sports')
    # 查找"音乐"的邻近单词, 我们可以发现与音乐有关的词汇.
    >>> model.get_nearest_neighbors('music')
    # 查找"小狗"的邻近单词, 我们可以发现与小狗有关的词汇.
    >>> model.get_nearest_neighbors('dog')
"""

# print(model.get_nearest_neighbors('sports'))
# print(model.get_nearest_neighbors('music'))
# print(model.get_nearest_neighbors('dog'))

什么是word embedding(词嵌入):

  • 通过一定的方式将词汇映射到指定维度(一般是更高维度)的空间.
  • 广义的word embedding包括所有密集词汇向量的表示方法,如之前学习的word2vec, 即可认为是word embedding的一种.
  • 狭义的word embedding是指在神经网络中加入的embedding层, 对整个网络进行训练的同时产生的embedding矩阵(embedding层的参数), 这个embedding矩阵就是训练过程中所有输入词汇的向量表示组成的矩阵.

word embedding的可视化分析:

  • 通过使用tensorboard可视化嵌入的词向量.
# 导入torch和tensorboard的摘要写入方法
import torch
import json
from torch.utils.tensorboard import SummaryWriter
# 实例化一个摘要写入对象
writer = SummaryWriter()

# 随机初始化一个100x5的矩阵, 认为它是我们已经得到的词嵌入矩阵
# 代表100个词汇, 每个词汇被表示成50维的向量
embedded = torch.randn(100, 50)

# 导入事先准备好的100个中文词汇文件, 形成meta列表原始词汇
meta = list(map(lambda x: x.strip(), fileinput.FileInput("./vocab100.csv")))
writer.add_embedding(embedded, metadata=meta)
writer.close()
  • 在终端启动tensorboard服务:
$ tensorboard --logdir runs --host 0.0.0.0

# 通过http://0.0.0.0:6006访问浏览器可视化页面
  • 浏览器展示并可以使用右侧近邻词汇功能检验效果:


 以一个案例演示狭义的word_embedding的生成过程:

使用tensorboard可视化嵌入的词向量.py 

# 导入torch和tensorboard导入进来
import torch
import json
import fileinput
from torch.utils.tensorboard import SummaryWriter
import csv

# 实例化一个写入对象
writer = SummaryWriter()

# 随机初始化一个100*5的矩阵, 将其视作已经得到的词嵌入矩阵
embedded = torch.randn(100, 50)

def get_a_list(text):
    r = []
    for x in text:
        r.append(x.strip())
    return r

"""
此处的vocab100.csv因为编码为utf8,window执行会报错gbk的编码问题,
因此运行代码生成数据必须在linux下运行,
该数据可以在linux或者window下的tensorboard都能可视化显示embedding数据
"""
# 导入事先准备好的100个中文词汇文件, 形成meta列表原始词汇
meta = list(map(lambda x: x.strip(), fileinput.FileInput("./vocab100.csv")))
# meta = list(map(lambda x: get_a_list(x), csv.reader(open("./vocab100.csv",encoding="utf-8"))))
writer.add_embedding(embedded, metadata=meta)
writer.close()

"""
在终端启动tensorboard服务
    tensorboard --logdir runs 
    http://localhost:6006 不能显示的话 可以跳到访问 http://localhost:6006/#projector
"""
# 导入torch和tensorboard导入进来
import torch
import json
import fileinput
from torch.utils.tensorboard import SummaryWriter

# 实例化一个写入对象
writer = SummaryWriter()

# 随机初始化一个100*5的矩阵, 将其视作已经得到的词嵌入矩阵
embedded = torch.randn(100, 50)

# 导入事先准备好的100个中文词汇文件, 形成meta列表原始词汇
meta = list(map(lambda x: x.strip(), fileinput.FileInput("./vocab100.csv")))
writer.add_embedding(embedded, metadata=meta)
writer.close()


2.1 新闻主题分类任务

  • 学习目标:
    • 了解有关新闻主题分类和有关数据.
    • 掌握使用浅层网络构建新闻主题分类器的实现过程.
  • 关于新闻主题分类任务:
    • 以一段新闻报道中的文本描述内容为输入, 使用模型帮助我们判断它最有可能属于哪一种类型的新闻, 这是典型的文本分类问题, 我们这里假定每种类型是互斥的, 即文本描述有且只有一种类型.
  • 新闻主题分类数据:
  • 通过torchtext获取数据:
# 导入相关的torch工具包
import torch
import torchtext
# 导入torchtext.datasets中的文本分类任务
from torchtext.datasets import text_classification
import os

# 定义数据下载路径, 当前路径的data文件夹
load_data_path = "./data"
# 如果不存在该路径, 则创建这个路径
if not os.path.isdir(load_data_path):
    os.mkdir(load_data_path)

# 选取torchtext中的文本分类数据集'AG_NEWS'即新闻主题分类数据, 保存在指定目录下
# 并将数值映射后的训练和验证数据加载到内存中
train_dataset, test_dataset = text_classification.DATASETS['AG_NEWS'](root=load_data_path)
  • 数据文件预览:
- data/
    - ag_news_csv.tar.gz
    - ag_news_csv/
        classes.txt
        readme.txt
        test.csv
        train.csv
  • 文件说明:

    • train.csv表示训练数据, 共12万条数据; test.csv表示验证数据, 共7600条数据; classes.txt是标签(新闻主题)含义文件, 里面有四个单词'World', 'Sports', 'Business', 'Sci/Tech'代表新闻的四个主题, readme.txt是该数据集的英文说明.
  • train.csv预览:

"3","Wall St. Bears Claw Back Into the Black (Reuters)","Reuters - Short-sellers, Wall Street's dwindling\band of ultra-cynics, are seeing green again."
"3","Carlyle Looks Toward Commercial Aerospace (Reuters)","Reuters - Private investment firm Carlyle Group,\which has a reputation for making well-timed and occasionally\controversial plays in the defense industry, has quietly placed\its bets on another part of the market."
"3","Oil and Economy Cloud Stocks' Outlook (Reuters)","Reuters - Soaring crude prices plus worries\about the economy and the outlook for earnings are expected to\hang over the stock market next week during the depth of the\summer doldrums."
"3","Iraq Halts Oil Exports from Main Southern Pipeline (Reuters)","Reuters - Authorities have halted oil export\flows from the main pipeline in southern Iraq after\intelligence showed a rebel militia could strike\infrastructure, an oil official said on Saturday."
"3","Oil prices soar to all-time record, posing new menace to US economy (AFP)","AFP - Tearaway world oil prices, toppling records and straining wallets, present a new economic menace barely three months before the US presidential elections."
"3","Stocks End Up, But Near Year Lows (Reuters)","Reuters - Stocks ended slightly higher on Friday\but stayed near lows for the year as oil prices surged past  #36;46\a barrel, offsetting a positive outlook from computer maker\Dell Inc. (DELL.O)"
"3","Money Funds Fell in Latest Week (AP)","AP - Assets of the nation's retail money market mutual funds fell by  #36;1.17 billion in the latest week to  #36;849.98 trillion, the Investment Company Institute said Thursday."
"3","Fed minutes show dissent over inflation (USATODAY.com)","USATODAY.com - Retail sales bounced back a bit in July, and new claims for jobless benefits fell last week, the government said Thursday, indicating the economy is improving from a midsummer slump."
"3","Safety Net (Forbes.com)","Forbes.com - After earning a PH.D. in Sociology, Danny Bazil Riley started to work as the general manager at a commercial real estate firm at an annual base salary of  #36;70,000. Soon after, a financial planner stopped by his desk to drop off brochures about insurance benefits available through his employer. But, at 32, ""buying insurance was the furthest thing from my mind,"" says Riley."
"3","Wall St. Bears Claw Back Into the Black"," NEW YORK (Reuters) - Short-sellers, Wall Street's dwindling  band of ultra-cynics, are seeing green again."
  • 文件内容说明:
    • train.csv共由3列组成, 使用','进行分隔, 分别代表: 标签, 新闻标题, 新闻简述; 其中标签用"1", "2", "3", "4"表示, 依次对应classes中的内容.
    • test.csv与train.csv内容格式与含义相同.

整个案例的实现可分为以下五个步骤:

  • 第一步: 构建带有Embedding层的文本分类模型.
  • 第二步: 对数据进行batch处理.
  • 第三步: 构建训练与验证函数.
  • 第四步: 进行模型训练和验证.
  • 第五步: 查看embedding层嵌入的词向量.
  • 第一步: 构建带有Embedding层的文本分类模型
# 导入必备的torch模型构建工具
import torch.nn as nn
import torch.nn.functional as F

# 指定BATCH_SIZE的大小
BATCH_SIZE = 16

# 进行可用设备检测, 有GPU的话将优先使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

class TextSentiment(nn.Module):
    """文本分类模型"""
    def __init__(self, vocab_size, embed_dim, num_class):
        """
        description: 类的初始化函数
        :param vocab_size: 整个语料包含的不同词汇总数
        :param embed_dim: 指定词嵌入的维度
        :param num_class: 文本分类的类别总数
        """ 
        super().__init__()
        # 实例化embedding层, sparse=True代表每次对该层求解梯度时, 只更新部分权重.
        self.embedding = nn.Embedding(vocab_size, embed_dim, sparse=True)
        # 实例化线性层, 参数分别是embed_dim和num_class.
        self.fc = nn.Linear(embed_dim, num_class)
        # 为各层初始化权重
        self.init_weights()

    def init_weights(self):
        """初始化权重函数"""
        # 指定初始权重的取值范围数
        initrange = 0.5
        # 各层的权重参数都是初始化为均匀分布
        self.embedding.weight.data.uniform_(-initrange, initrange)
        self.fc.weight.data.uniform_(-initrange, initrange)
        # 偏置初始化为0
        self.fc.bias.data.zero_()

    def forward(self, text):
        """
        :param text: 文本数值映射后的结果
        :return: 与类别数尺寸相同的张量, 用以判断文本类别
        """
        # 获得embedding的结果embedded
        # >>> embedded.shape
        # (m, 32) 其中m是BATCH_SIZE大小的数据中词汇总数
        embedded = self.embedding(text)
        # 接下来我们需要将(m, 32)转化成(BATCH_SIZE, 32)
        # 以便通过fc层后能计算相应的损失
        # 首先, 我们已知m的值远大于BATCH_SIZE=16,
        # 用m整除BATCH_SIZE, 获得m中共包含c个BATCH_SIZE
        c = embedded.size(0) // BATCH_SIZE
        # 之后再从embedded中取c*BATCH_SIZE个向量得到新的embedded
        # 这个新的embedded中的向量个数可以整除BATCH_SIZE
        embedded = embedded[:BATCH_SIZE*c]
        # 因为我们想利用平均池化的方法求embedded中指定行数的列的平均数,
        # 但平均池化方法是作用在行上的, 并且需要3维输入
        # 因此我们对新的embedded进行转置并拓展维度
        embedded = embedded.transpose(1, 0).unsqueeze(0)
        # 然后就是调用平均池化的方法, 并且核的大小为c
        # 即取每c的元素计算一次均值作为结果
        embedded = F.avg_pool1d(embedded, kernel_size=c)
        # 最后,还需要减去新增的维度, 然后转置回去输送给fc层
        return self.fc(embedded[0].transpose(1, 0))

实例化模型:

# 获得整个语料包含的不同词汇总数
VOCAB_SIZE = len(train_dataset.get_vocab())
# 指定词嵌入维度
EMBED_DIM = 32
# 获得类别总数
NUN_CLASS = len(train_dataset.get_labels())
# 实例化模型
model = TextSentiment(VOCAB_SIZE, EMBED_DIM, NUN_CLASS).to(device)
  • 第二步: 对数据进行batch处理
def generate_batch(batch):
    """
    description: 生成batch数据函数
    :param batch: 由样本张量和对应标签的元组组成的batch_size大小的列表
                  形如:
                  [(sample1, label1), (sample2, lable2), ..., (sampleN, labelN)]
    :return: 样本张量和标签各自的列表形式(张量)
             形如:
             text = tensor([sample1, sample2, ..., sampleN])
             label = tensor([label1, label2, ..., labelN])
    """
    # 从batch中获得标签张量
    label = torch.tensor([entry[1] for entry in batch])
    # 从batch中获得样本张量
    text = [entry[0] for entry in batch]
    text = torch.cat(text)
    # 返回结果
    return text, label

调用:

# 假设一个输入:
batch = [(torch.tensor([3, 23, 2, 8]), 1), (torch.tensor([3, 45, 21, 6]), 0)]
res = generate_batch(batch)
print(res)

输出效果:

# 对应输入的两条数据进行了相应的拼接
(tensor([ 3, 23,  2,  8,  3, 45, 21,  6]), tensor([1, 0]))
  • 第三步: 构建训练与验证函数
# 导入torch中的数据加载器方法
from torch.utils.data import DataLoader

def train(train_data):
    """模型训练函数"""
    # 初始化训练损失和准确率为0
    train_loss = 0
    train_acc = 0

    # 使用数据加载器生成BATCH_SIZE大小的数据进行批次训练
    # data就是N多个generate_batch函数处理后的BATCH_SIZE大小的数据生成器
    data = DataLoader(train_data, batch_size=BATCH_SIZE, shuffle=True,
                      collate_fn=generate_batch)

    # 对data进行循环遍历, 使用每个batch的数据进行参数更新
    for i, (text, cls) in enumerate(data):
        # 设置优化器初始梯度为0
        optimizer.zero_grad()
        # 模型输入一个批次数据, 获得输出
        output = model(text)
        # 根据真实标签与模型输出计算损失
        loss = criterion(output, cls)
        # 将该批次的损失加到总损失中
        train_loss += loss.item()
        # 误差反向传播
        loss.backward()
        # 参数进行更新
        optimizer.step()
        # 将该批次的准确率加到总准确率中
        train_acc += (output.argmax(1) == cls).sum().item()

    # 调整优化器学习率  
    scheduler.step()

    # 返回本轮训练的平均损失和平均准确率
    return train_loss / len(train_data), train_acc / len(train_data)

def valid(valid_data):
    """模型验证函数"""
    # 初始化验证损失和准确率为0
    loss = 0
    acc = 0

    # 和训练相同, 使用DataLoader获得训练数据生成器
    data = DataLoader(valid_data, batch_size=BATCH_SIZE, collate_fn=generate_batch)
    # 按批次取出数据验证
    for text, cls in data:
        # 验证阶段, 不再求解梯度
        with torch.no_grad():
            # 使用模型获得输出
            output = model(text)
            # 计算损失
            loss = criterion(output, cls)
            # 将损失和准确率加到总损失和准确率中
            loss += loss.item()
            acc += (output.argmax(1) == cls).sum().item()

    # 返回本轮验证的平均损失和平均准确率
    return loss / len(valid_data), acc / len(valid_data)
  • 第四步: 进行模型训练和验证.
# 导入时间工具包
import time

# 导入数据随机划分方法工具
from torch.utils.data.dataset import random_split

# 指定训练轮数
N_EPOCHS = 10

# 定义初始的验证损失
min_valid_loss = float('inf')

# 选择损失函数, 这里选择预定义的交叉熵损失函数
criterion = torch.nn.CrossEntropyLoss().to(device)
# 选择随机梯度下降优化器
optimizer = torch.optim.SGD(model.parameters(), lr=4.0)
# 选择优化器步长调节方法StepLR, 用来衰减学习率
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, 1, gamma=0.9)

# 从train_dataset取出0.95作为训练集, 先取其长度
train_len = int(len(train_dataset) * 0.95)

# 然后使用random_split进行乱序划分, 得到对应的训练集和验证集
sub_train_, sub_valid_ = \
    random_split(train_dataset, [train_len, len(train_dataset) - train_len])

# 开始每一轮训练
for epoch in range(N_EPOCHS):
    # 记录概论训练的开始时间
    start_time = time.time()
    # 调用train和valid函数得到训练和验证的平均损失, 平均准确率
    train_loss, train_acc = train(sub_train_)
    valid_loss, valid_acc = valid(sub_valid_)

    # 计算训练和验证的总耗时(秒)
    secs = int(time.time() - start_time)
    # 用分钟和秒表示
    mins = secs / 60
    secs = secs % 60

    # 打印训练和验证耗时,平均损失,平均准确率
    print('Epoch: %d' %(epoch + 1), " | time in %d minutes, %d seconds" %(mins, secs))
    print(f'\tLoss: {train_loss:.4f}(train)\t|\tAcc: {train_acc * 100:.1f}%(train)')
    print(f'\tLoss: {valid_loss:.4f}(valid)\t|\tAcc: {valid_acc * 100:.1f}%(valid)')

输出效果:

120000lines [00:06, 17834.17lines/s]
120000lines [00:11, 10071.77lines/s]
7600lines [00:00, 10432.95lines/s]

Epoch: 1  | time in 0 minutes, 36 seconds
    Loss: 0.0592(train) |   Acc: 63.9%(train)
    Loss: 0.0005(valid) |   Acc: 69.2%(valid)
Epoch: 2  | time in 0 minutes, 37 seconds
    Loss: 0.0507(train) |   Acc: 71.3%(train)
    Loss: 0.0005(valid) |   Acc: 70.7%(valid)
Epoch: 3  | time in 0 minutes, 36 seconds
    Loss: 0.0484(train) |   Acc: 72.8%(train)
    Loss: 0.0005(valid) |   Acc: 71.4%(valid)
Epoch: 4  | time in 0 minutes, 36 seconds
    Loss: 0.0474(train) |   Acc: 73.4%(train)
    Loss: 0.0004(valid) |   Acc: 72.0%(valid)
Epoch: 5  | time in 0 minutes, 36 seconds
    Loss: 0.0455(train) |   Acc: 74.8%(train)
    Loss: 0.0004(valid) |   Acc: 72.5%(valid)
Epoch: 6  | time in 0 minutes, 36 seconds
    Loss: 0.0451(train) |   Acc: 74.9%(train)
    Loss: 0.0004(valid) |   Acc: 72.3%(valid)
Epoch: 7  | time in 0 minutes, 36 seconds
    Loss: 0.0446(train) |   Acc: 75.3%(train)
    Loss: 0.0004(valid) |   Acc: 72.0%(valid)
Epoch: 8  | time in 0 minutes, 36 seconds
    Loss: 0.0437(train) |   Acc: 75.9%(train)
    Loss: 0.0004(valid) |   Acc: 71.4%(valid)
Epoch: 9  | time in 0 minutes, 36 seconds
    Loss: 0.0431(train) |   Acc: 76.2%(train)
    Loss: 0.0004(valid) |   Acc: 72.7%(valid)
Epoch: 10  | time in 0 minutes, 36 seconds
    Loss: 0.0426(train) |   Acc: 76.6%(train)
    Loss: 0.0004(valid) |   Acc: 72.6%(valid)
  • 第五步: 查看embedding层嵌入的词向量.
# 打印从模型的状态字典中获得的Embedding矩阵
print(model.state_dict()['embedding.weight'])

输出效果:

tensor([[ 0.4401, -0.4177, -0.4161,  ...,  0.2497, -0.4657, -0.1861],
        [-0.2574, -0.1952,  0.1443,  ..., -0.4687, -0.0742,  0.2606],
        [-0.1926, -0.1153, -0.0167,  ..., -0.0954,  0.0134, -0.0632],
        ...,
        [-0.0780, -0.2331, -0.3656,  ..., -0.1899,  0.4083,  0.3002],
        [-0.0696,  0.4396, -0.1350,  ...,  0.1019,  0.2792, -0.4749],
        [-0.2978,  0.1872, -0.1994,  ...,  0.3435,  0.4729, -0.2608]])
  • 小节总结:

    • 学习了关于新闻主题分类任务:
      • 以一段新闻报道中的文本描述内容为输入, 使用模型帮助我们判断它最有可能属于哪一种类型的新闻, 这是典型的文本分类问题, 我们这里假定每种类型是互斥的, 即文本描述有且只有一种类型.

    • 学习了新闻主题分类数据的获取和样式.

    • 学习了整个案例的实现的五个步骤:
      • 第一步: 构建带有Embedding层的文本分类模型.
      • 第二步: 对数据进行batch处理.
      • 第三步: 构建训练与验证函数.
      • 第四步: 进行模型训练和验证.
      • 第五步: 查看embedding层嵌入的词向量.

  •  小节总结:

    • 学习了什么是文本张量表示:
      • 将一段文本使用张量进行表示,其中一般将词汇为表示成向量,称作词向量,再由各个词向量按顺序组成矩阵形成文本表示.

    • 学习了文本张量表示的作用:
      • 将文本表示成张量(矩阵)形式,能够使语言文本可以作为计算机处理程序的输入,进行接下来一系列的解析工作.

    • 学习了文本张量表示的方法:
      • one-hot编码
      • Word2vec
      • Word Embedding

    • 什么是one-hot词向量表示:
      • 又称独热编码,将每个词表示成具有n个元素的向量,这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素为0的位置不同,其中n的大小是整个语料中不同词汇的总数.

    • 学习了onehot编码实现.

    • 学习了one-hot编码的优劣势:
      • 优势:操作简单,容易理解.
      • 劣势:完全割裂了词与词之间的联系,而且在大语料集下,每个向量的长度过大,占据大量内存.

    • 学习了什么是word2vec:
      • 是一种流行的将词汇表示成向量的无监督训练方法, 该过程将构建神经网络模型, 将网络参数作为词汇的向量表示, 它包含CBOW和skipgram两种训练模式.

    • 学习了CBOW(Continuous bag of words)模式:
      • 给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用上下文词汇预测目标词汇.

    • 学习了CBOW模式下的word2vec过程说明:

      • 假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope you set,因为是CBOW模式,所以将使用Hope和set作为输入,you作为输出,在模型训练时, Hope,set,you等词汇都使用它们的one-hot编码. 如图所示: 每个one-hot编码的单词与各自的变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘之后再相加, 得到上下文表示矩阵(3x1).

      • 接着, 将上下文表示矩阵与变换矩阵(参数矩阵5x3, 所有的变换矩阵共享参数)相乘, 得到5x1的结果矩阵, 它将与我们真正的目标矩阵即you的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模型迭代.

      • 最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示.


    • 学习了skipgram模式:
      • 给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用目标词汇预测上下文词汇.

    • 学习了skipgram模式下的word2vec过程说明:

      • 假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope you set,因为是skipgram模式,所以将使用you作为输入 ,hope和set作为输出,在模型训练时, Hope,set,you等词汇都使用它们的one-hot编码. 如图所示: 将you的one-hot编码与变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘, 得到目标词汇表示矩阵(3x1).

      • 接着, 将目标词汇表示矩阵与多个变换矩阵(参数矩阵5x3)相乘, 得到多个5x1的结果矩阵, 它将与我们hope和set对应的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模 型迭代.

      • 最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵即参数矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示.


    • 学习了使用fasttext工具实现word2vec的训练和使用:
      • 第一步: 获取训练数据
      • 第二步: 训练词向量
      • 第三步: 模型超参数设定
      • 第四步: 模型效果检验
      • 第五步: 模型的保存与重加载

    • 学习了什么是word embedding(词嵌入):
      • 通过一定的方式将词汇映射到指定维度(一般是更高维度)的空间.
      • 广义的word embedding包括所有密集词汇向量的表示方法,如之前学习的word2vec, 即可认为是word embedding的一种.
      • 狭义的word embedding是指在神经网络中加入的embedding层, 对整个网络进行训练的同时产生的embedding矩阵(embedding层的参数), 这个embedding矩阵就是训练过程中所有输入词汇的向量表示组成的矩阵.

    • 通过一个案例: 新闻主题分类任务, 演示狭义的word_embedding的生成过程.

    • 学习了word embedding的可视化分析:
      • 通过使用tensorboard可视化嵌入的词向量.
      • 在终端启动tensorboard服务.
      • 浏览器展示并可以使用右侧近邻词汇功能检验效果.
发布了450 篇原创文章 · 获赞 151 · 访问量 23万+

猜你喜欢

转载自blog.csdn.net/zimiao552147572/article/details/105465131