深度学习自然语言处理教程(一)

原文:Deep Learning for Natural Language Processing

协议:CC BY-NC-SA 4.0

一、自然语言处理和深度学习导论

自然语言处理是计算机科学中一项极其困难的任务。语言带来了各种各样的问题,这些问题因语言而异。如果方法正确,从自由文本中构造或提取有意义的信息是一个很好的解决方案。以前,计算机科学家将语言分解成语法形式,如词类、短语等。,使用复杂的算法。今天,深度学习是执行相同练习的关键。

自然语言处理深度学习的第一章为读者提供了 Python 语言、NLP 和深度学习的基础知识。首先,我们介绍了 Pandas、NumPy 和 SciPy 库中的初级代码。我们假设用户已经设置了初始 Python 环境(2.x 或 3.x ),并安装了这些库。我们还将简要讨论 NLP 中常用的库,并给出一些基本的例子。最后,我们将讨论深度学习背后的概念和一些常见的框架,如 TensorFlow 和 Keras。然后,在后面的章节中,我们将继续提供 NLP 的更高层次的概述。

根据机器和版本首选项,可以使用以下参考资料安装 Python:

前面的链接和基础包安装将为用户提供深度学习所需的环境。

我们将使用下面的包开始。除了软件包名称供您参考之外,请参考以下链接:

Python 机器学习

Python 深度学习

Python 自然语言处理

如果需要,我们可能会安装其他相关的软件包。如果您在安装的任何阶段遇到问题,请参考以下链接: https://packaging.python.org/tutorials/installing-packages/

Note

参考 Python 包索引 PyPI ( https://pypi.python.org/pypi ,搜索最新可用的包。

按照步骤通过 https://pip.pypa.io/en/stable/installing/ 安装 pip。

Python 包

我们将涉及到 Pandas、NumPy 和 SciPy 包的安装步骤和初始编码的参考。目前,Python 提供了 2.x 和 3.x 版本,具有兼容机器学习的功能。我们将在需要的地方使用 Python2.7 和 Python3.5。3.5 版在本书的各个章节中被广泛使用。

NumPy

NumPy 特别用于 Python 中的科学计算。它被设计用来有效地操作任意记录的大型多维数组,而不会牺牲小型多维数组的太多速度。它还可以用作通用数据的多维容器。NumPy 能够创建任意类型的数组,这也使得 NumPy 适合于与通用数据库应用程序进行接口,这使得它成为您将在本书中或之后使用的最有用的库之一。

下面是使用 NumPy 包的代码。大多数代码行都附加了注释,以便用户更容易理解。

## Numpy

import numpy as np                # Importing the Numpy package
a= np.array([1,4,5,8], float)     # Creating Numpy array with Float variables
print(type(a))                #Type of variable
> <class 'numpy.ndarray'>

# Operations on the array
a[0] = 5                #Replacing the first element of the array
print(a)
> [ 5\. 4\. 5\. 8.]

b = np.array([[1,2,3],[4,5,6]], float)   # Creating a 2-D numpy array
b[0,1]                # Fetching second element of 1st array
> 2.0
print(b.shape)        #Returns tuple with the shape of array
> (2, 3)
b.dtype               #Returns the type of the value stored
> dtype('float64')
print(len(b))         #Returns length of the first axis
> 2

2 in b                #'in' searches for the element in the array
> True

0 in b
> False

# Use of 'reshape' : transforms elements from 1-D to 2-D here
c = np.array(range(12), float)
print(c)
print(c.shape)
print('---')
c = c.reshape((2,6))    # reshape the array in the new form
print(c)
print(c.shape)
> [ 0\. 1\. 2\. 3\. 4\. 5\. 6\. 7\. 8\. 9\. 10\. 11.]
(12,)
---
[[ 0\. 1\. 2\. 3\. 4\. 5.] [ 6\. 7\. 8\. 9\. 10\. 11.]]
(2, 6)

c.fill(0)                #Fills whole array with single value, done inplace
print(c)
> [[ 0\. 0\. 0\. 0\. 0\. 0.] [ 0\. 0\. 0\. 0\. 0\. 0.]]

c.transpose()            #creates transpose of the array, not done inplace
> array([[ 0., 0.], [ 0., 0.], [ 0., 0.], [ 0., 0.], [ 0., 0.], [ 0., 0.]])
c.flatten()              #flattens the whole array, not done inplace
> array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

# Concatenation of 2 or more arrays
m = np.array([1,2], float)
n = np.array([3,4,5,6], float)
p = np.concatenate((m,n))
print(p)
> [ 1\. 2\. 3\. 4\. 5\. 6.]
(6,)
print(p.shape)

# 'newaxis' : to increase the dimensonality of the array
q = np.array([1,2,3], float)
q[:, np.newaxis].shape
> (3, 1)

NumPy 还有其他函数,如zerosoneszeros_likeones_likeidentityeye,用于创建给定维数的填充 0、1 或 0 和 1 的数组。

加法、减法和乘法发生在相同大小的数组上。NumPy 中的乘法是以元素方式提供的,而不是矩阵乘法。如果数组大小不匹配,则重复较小的数组来执行所需的操作。以下是这方面的一个例子:

a1 = np.array([[1,2],[3,4],[5,6]], float)
a2 = np.array([-1,3], float)
print(a1+a2)
> [[ 0\. 5.] [ 2\. 7.] [ 4\. 9.]]

Note

pie作为常量包含在 NumPy 包中。

关于 NumPy 的详细教程可以参考以下资料: www.numpy.org/https://docs.scipy.org/doc/numpy-dev/user/quickstart.html

NumPy 提供了几个可直接应用于数组的函数:sum(元素的总和)、prod(元素的乘积)、mean(元素的平均值)、var(元素的方差)、std(元素的标准差)、argmin(数组中最小元素的索引)、argmax(数组中最大元素的索引)、sort(对元素排序)、unique(数组中唯一的元素)。

a3 = np.array([[0,2],[3,-1],[3,5]], float)
print(a3.mean(axis=0))           # Mean of elements column-wise
> [ 2\. 2.]
print(a3.mean(axis=1))           # Mean of elements row-wise
> [ 1\. 1\. 4.]

Note

要在多维数组上执行上述操作,请在命令中包含可选参数axis

NumPy 提供了测试数组中出现的值的函数,比如nonzero(检查非零元素)、isnan(检查“非数字”元素)和isfinite(检查有限元素)。where函数返回一个数组,其中的元素满足以下条件:

a4 = np.array([1,3,0], float)
np.where(a!=0, 1/a ,a)
> array([ 0.2 , 0.25 , 0.2 , 0.125])

要生成不同长度的随机数,请使用 NumPy 的 random 函数。

np.random.rand(2,3)
> array([[ 0.41453991, 0.46230172, 0.78318915], [0.54716578, 0.84263735, 0.60796399]])

Note

可以通过numpy.random.seed (1234)设置随机数种子。NumPy 使用 Mersenne Twister 算法来生成伪随机数。

熊猫

Pandas 是一个开源软件库。DataFrames 和 Series 是广泛用于数据分析目的的两种主要数据结构。Series 是一维索引数组,DataFrame 是具有列级和行级索引的表格数据结构。Pandas 是预处理数据集的一个很好的工具,并提供了高度优化的性能。

import pandas as pd
series_1 = pd.Series([2,9,0,1])      # Creating a series object
print(series_1.values)               # Print values of the series object
> [2 9 0 1]
series_1.index             # Default index of the series object
> RangeIndex(start=0, stop=4, step=1)
series_1.index = ['a','b','c','d']   #Settnig index of the series object
series_1['d']                # Fetching element using new index
> 1
# Creating dataframe using pandas
class_data = {'Names':['John','Ryan','Emily'],
             'Standard': [7,5,8],
             'Subject': ['English','Mathematics','Science']}
class_df = pd.DataFrame(class_data, index = ['Student1','Student2','Student3'],
                       columns = ['Names','Standard','Subject'])
print(class_df)
>            Names     Standard     Subject
Student1     John      7            English
Student2     Ryan      5            Mathematics
Student3     Emily     8            Science
class_df.Names
>Student1    John
Student2     Ryan
Student3     Emily
Name: Names, dtype: object
# Add new entry to the dataframe
import numpy as np
class_df.ix['Student4'] = ['Robin', np.nan, 'History']
class_df.T                # Take transpose of the dataframe
>           Student1    Student2       Student3    Student4
Names       John        Ryan           Emily       Robin
Standard    7           5              8           NaN
Subject     English     Mathematics    Science     History
class_df.sort_values(by='Standard')   # Sorting of rows by one column
>            Names     Standard     Subject
Student1     John      7.0          English
Student2     Ryan      5.0          Mathematics
Student3     Emily     8.0          Science
Student4     Robin     NaN          History
# Adding one more column to the dataframe as Series object
col_entry = pd.Series(['A','B','A+','C'],
                      index=['Student1','Student2','Student3','Student4'])
class_df['Grade'] = col_entry
print(class_df)
>            Names     Standard     Subject         Grade
Student1     John      7.0          English         A
Student2     Ryan      5.0          Mathematics     B
Student3     Emily     8.0          Science         A+
Student4     Robin     NaN          History         C
# Filling the missing entries in the dataframe, inplace
class_df.fillna(10, inplace=True)
print(class_df)
>            Names     Standard     Subject         Grade
Student1     John      7.0          English         A
Student2     Ryan      5.0          Mathematics     B
Student3     Emily     8.0          Science         A+
Student4     Robin     10.0         History         C
# Concatenation of 2 dataframes
student_age = pd.DataFrame(data = {'Age': [13,10,15,18]} ,
                           index=['Student1','Student2','Student3','Student4'])
print(student_age)
>            Age
Student1     13
Student2     10
Student3     15
Student4     18
class_data = pd.concat([class_df, student_age], axis = 1)
print(class_data)
>            Names     Standard    Subject        Grade     Age
Student1     John      7.0         English        A         13
Student2     Ryan      5.0         Mathematics    B         10
Student3     Emily     8.0         Science        A+        15
Student4     Robin     10.0        History        C         18

Note

使用map功能对列/行中的每个元素单独执行任何功能,使用apply功能对列/行中的所有元素同时执行任何功能。

# MAP Function
class_data['Subject'] = class_data['Subject'].map(lambda x : x + 'Sub')
class_data['Subject']
> Student1         EnglishSub
Student2         MathematicsSub
Student3         ScienceSub
Student4         HistorySub
Name: Subject, dtype: object
# APPLY Function
def age_add(x):                 # Defining a new function which will increment the age by 1
    return(x+1)
print('-----Old values-----')
print(class_data['Age'])
print('-----New values-----')
print(class_data['Age'].apply(age_add))    # Applying the age function on top of the age column
> -----Old values-----
Student1 13
Student2 10
Student3 15
Student4 18
Name: Age, dtype: int64-----New values-----
Student1 14
Student2 11
Student3 16
Student4 19
Name: Age, dtype: int64

以下代码用于将列的数据类型更改为“category”类型:

# Changing datatype of the column
class_data['Grade'] = class_data['Grade'].astype('category')
class_data.Grade.dtypes
> category

下面将结果存储到一个.csv文件中:

# Storing the results
class_data.to_csv('class_dataset.csv', index=False)

在熊猫图书馆提供的函数库中,合并函数(concatmergeappendgroupbypivot_table函数)在数据处理任务中有大量的应用。详细熊猫教程参考以下来源: http://pandas.pydata.org/

我的天啊

SciPy 提供了复杂的算法以及它们在 NumPy 中作为函数的用途。这分配了高级命令和各种类来操作和可视化数据。SciPy 是以多个小软件包的形式策划的,每个软件包针对单独的科学计算领域。一些子包是linalg(线性代数)constants(物理和数学常数),和sparse(稀疏矩阵和相关例程)。

适用于数组的大多数 NumPy 包函数也包含在 SciPy 包中。SciPy 提供了预先测试的例程,从而在科学计算应用程序中节省了大量处理时间。

import scipy
import numpy as np

Note

SciPy 为表示随机变量的对象提供了内置的构造函数。

下面是来自 Linalg 和 SciPy 提供的多个子包的一些例子。由于子包是特定于领域的,这使得 SciPy 成为数据科学的最佳选择。

这里用于线性代数(scipy.linalg)的 SciPy 子包应该以如下方式显式导入:

from scipy import linalg
mat_ = np.array([[2,3,1], [4,9,10], [10,5,6]]) # Matrix Creation 
print(mat_)
> [[ 2 3 1] [ 4 9 10] [10 5 6]]
linalg.det(mat_)                 # Determinant of the matrix
inv_mat = linalg.inv(mat_)       # Inverse of the matrix
print(inv_mat)
> [[ 0.02409639 -0.07831325 0.12650602] [ 0.45783133 0.01204819 -0.09638554] [-0.42168675 0.12048193 0.03614458]]

执行奇异值分解和存储各个分量的代码如下:

# Singular Value Decomposition
comp_1, comp_2, comp_3 = linalg.svd(mat_)
print(comp_1)
print(comp_2)
print(comp_3)
> [[-0.1854159 0.0294175 -0.98221971]
[-0.73602677 -0.66641413 0.11898237]
[-0.65106493 0.74500122 0.14521585]]
[ 18.34661713 5.73710697 1.57709968]
[[-0.53555313 -0.56881403 -0.62420625]
[ 0.84418693 -0.38076134 -0.37731848]
[-0.02304957 -0.72902085 0.6841033 ]]

Scipy.stats是一个巨大的子包,有各种统计分布和函数,可以对不同种类的数据集进行操作。

# Scipy Stats module
from scipy import stats

# Generating a random sample of size 20 from normal distribution with mean 3 and standard deviation 5
rvs_20 = stats.norm.rvs(3,5 , size = 20)
print(rvs_20, '\n --- ')

# Computing the CDF of Beta distribution with a=100 and b=130 as shape parameters at random variable 0.41
cdf_ = scipy.stats.beta.cdf(0.41, a=100, b=130)
print(cdf_)
> [ -0.21654555 7.99621694 -0.89264767 10.89089263 2.63297827
    -1.43167281 5.09490009 -2.0530585 -5.0128728 -0.54128795
     2.76283347 8.30919378 4.67849196 -0.74481568 8.28278981
    -3.57801485 -3.24949898 4.73948566 2.71580005 6.50054556]
---
0.225009574362

关于使用 SciPy 子包的深入示例,请参考 http://docs.scipy.org/doc/

自然语言处理导论

我们已经看到了 Python 中三个最有用和最常用的库。提供的例子和参考资料应该足以开始。现在,我们正在将我们的重点领域转移到自然语言处理上。

什么是自然语言处理?

简单来说,自然语言处理是计算机/系统真正理解人类语言并以与人类相同的方式处理语言的能力。

够好了,但有什么大不了的?

人类很容易理解其他人所说/表达的语言。例如,如果我说“美国遵循资本主义经济形式,这种经济形式很适合它”,很容易推断出这句话中使用的 which 与“资本主义经济形式”相关联,但计算机/系统如何理解这一点是个问题。

是什么让自然语言处理变得困难?

在人类之间的正常对话中,有些事情往往是没有说出来的,无论是以某种信号、表情还是只是沉默的形式。然而,作为人类,我们有能力理解对话的潜在意图,而这是计算机所缺乏的。

第二个困难是由于句子中的歧义。这可能是词的层面,可能是句子的层面,也可能是意思的层面。

词汇层面的歧义

想想“不会”这个词。这个词总是有歧义。系统会将缩写视为一个单词还是两个单词,在什么意义上(它的含义会是什么?).

判决层面的双重有罪

考虑下面的句子:

大多数时间旅行者担心他们的行李。

没有标点符号,很难从给定的句子中推断出“时间旅行者”是担心他们的行李还是仅仅是“旅行者”

时间像箭一样飞逝。

花费时间的速度被比作箭的速度,仅给出这句话而没有足够的关于所提到的两个实体的一般性质的信息,这是很难描绘的。

意义层面的歧义

考虑一下 tie 这个词。有三种方法可以处理(解释)这个词:作为参赛者之间的平等分数,作为一件衣服,作为一个动词。

图 1-1 展示了一个简单的谷歌翻译失败。它假设范的意思是一个崇拜者,而不是一个对象。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-1

Example of Google Translate from English to Hindi

这些只是你在 NLP 工作时会遇到的无穷挑战中的一小部分。随着我们继续深入,我们将探索如何处理它们。

我们想通过自然语言处理达到什么目的?

通过 NLP 可以实现的目标是无限的。然而,NLP 有一些常见的应用,主要如下:

  • 还记得你上学的时候,老师让全班同学总结一篇课文吗?使用 NLP 可以很好地完成这项任务。
  • 文本标注 NLP 可以有效地用于查找一整串文本的上下文(主题标注)。
  • 命名实体识别这可以确定一个单词或词组是否代表一个地方、组织或其他任何东西。
  • 聊天机器人(Chatbot)人们谈论最多的 NLP 应用是聊天机器人(Chatbot)。它可以发现用户所提问题的意图,并发送适当的答复,这是通过训练过程实现的。
  • 语音识别

这个应用程序可以识别口语,并将其转换成文本。

如前所述,NLP 有许多应用。我们的想法是不要被它们吓倒,而是自己学习和开发一个或多个这样的应用程序。

与语言处理相关的常用术语

随着我们越来越深入,有几个术语你会经常遇到。因此,尽快熟悉它们是一个好主意。

  • 语音学/音系学研究语言声音及其与书面文字的关系
  • 形态学对词的内部结构/词的构成的研究
  • 句法研究句子中词与词之间的结构关系
  • 语义学研究单词的意思以及这些意思如何组合形成句子的意思
  • 语言句子的语用情景运用
  • 话语

大于单个句子(上下文)的语言单位

自然语言处理库

以下是 Python 中一些最常用的 NLP 库的基本示例。

我是 NLTK

NLTK (

Note

以下是安装 NLTK 包的推荐方法:pip install nltk

您可以将给定的句子标记为单个单词,如下所示:

  • 获取单词的同义词。使用 NLTK 可以获得一个单词的同义词列表。

    # Make sure to install wordnet, if not done already so
    # import nltk
    # nltk.download('wordnet')
    # Synonyms
    from nltk.corpus import wordnet
    word_ = wordnet.synsets("spectacular")
    print(word_)
    >> [Synset('spectacular.n.01'), Synset('dramatic.s.02'), Synset('spectacular.s.02'), Synset('outstanding.s.02')]
    print(word_[0].definition())      # Printing the meaning along of each of the synonyms
    print(word_[1].definition())
    print(word_[2].definition())
    print(word_[3].definition())
    >> a lavishly produced performance
    >> sensational in appearance or thrilling in effect
    >> characteristic of spectacles or drama
    >> having a quality that thrusts itself into attention
    
    
  • 词干化和词汇化。词干是指从单词中去掉词缀,返回词根(可能不是真正的单词)。词干化和词干化类似,不同的是,词干化的结果是一个真实的词。

    # Stemming
    from nltk.stem import PorterStemmer
    stemmer = PorterStemmer()          # Create the stemmer object
    print(stemmer.stem("decreases"))
    >> decreas
    
    #Lemmatization
    from nltk.stem import WordNetLemmatizer
    lemmatizer = WordNetLemmatizer()    # Create the Lemmatizer object
    print(lemmatizer.lemmatize("decreases"))
    >> decrease
    
    
import nltk
# Tokenization
sent_ = "I am almost dead this time"
tokens_ = nltk.word_tokenize(sent_)
tokens_
>> ['I', 'am', 'almost', 'dead', 'this', 'time']

文本 Blob

TextBlob ( http://textblob.readthedocs.io/en/dev/index.html )是一个处理文本数据的 Python 库。它提供了一个简单的 API 来深入研究常见的 NLP 任务,如词性标注、名词短语提取、情感分析、分类等等。你可以用它来进行情感分析。感悟是指隐藏在句子中的一种感觉。极性定义了句子中的否定性或肯定性,而主观性意味着句子是模糊地还是完全确定地讨论某事。

from textblob import TextBlob
# Taking a statement as input
statement = TextBlob("My home is far away from my school.")
# Calculating the sentiment attached with the statement
statement.sentiment
Sentiment(polarity=0.1, subjectivity=1.0)

您还可以使用 TextBlob 进行标记。标记是将文本(语料库)中的单词表示为对应于特定词性的过程。

# Defining a sample text
text = '''How about you and I go together on a walk far away from this place, discussing the things we have never discussed on Deep Learning and Natural Language Processing.'''
blob_ = TextBlob(text)           # Making it as Textblob object
blob_
>> TextBlob("How about you and I go together on a walk far away from this place, discussing the things we have never discussed on Deep Learning and Natural Language Processing.")
# This part internally makes use of the 'punkt' resource from the NLTK package, make sure to download it before running this
# import nltk
# nltk.download('punkt')
# nltk.download('averaged_perceptron_tagger')
# Running this separately : python3.6 -m textblob.download_corpora
blob_.tags
>>
[('How', 'WRB'),
 ('about', 'IN'),
 ('you', 'PRP'),
 ('and', 'CC'),
 ('I', 'PRP'),
 ('go', 'VBP'),
 ('together', 'RB'),
 ('on', 'IN'),
 ('a', 'DT'),
 ('walk', 'NN'),
 ('far', 'RB'),
 ('away', 'RB'),
 ('from', 'IN'),
 ('this', 'DT'),
 ('place', 'NN'),
 ('discussing', 'VBG'),
 ('the', 'DT'),
 ('things', 'NNS'),
 ('we', 'PRP'),
 ('have', 'VBP'),
 ('never', 'RB'),
 ('discussed', 'VBN'),
 ('on', 'IN'),
 ('Deep', 'NNP'),
 ('Learning', 'NNP'),
 ('and', 'CC'),
 ('Natural', 'NNP'),
 ('Language', 'NNP'),
 ('Processing', 'NNP')]

您可以使用 TextBlob 来处理拼写错误。

sample_ = TextBlob("I thinkk the model needs to be trained more!")
print(sample_.correct())
>> I think the model needs to be trained more!

此外,该软件包还提供了语言翻译模块。

# Language Translation
lang_ = TextBlob(u"Voulez-vous apprendre le français?")
lang_.translate(from_lang='fr', to="en")
>> TextBlob("Do you want to learn French?")

空间

SpaCy ( 它是用 Cython 语言编写的,包含各种各样的语言词汇、语法、单词到矢量转换和实体识别的训练模型。

Note

实体识别是用于将文本中发现的多个实体分类到预定义类别中的过程,例如人、对象、位置、组织、日期、事件等。单词向量指的是从词汇到实数向量的单词或短语的映射。

import spacy
# Run below command, if you are getting error
# python -m spacy download en
nlp = spacy.load("en")

william_wikidef = """William was the son of King William II and Anna Pavlovna of Russia. On the abdication of his grandfather  William I in 1840, he became the Prince of Orange. On the death of his father in 1849, he succeeded as king of the Netherlands. William married his cousin Sophie of Württemberg in 1839 and they had three sons, William, Maurice, and Alexander, all of whom predeceased him. """

nlp_william = nlp(william_wikidef)

print([ (i, i.label_, i.label) for i in nlp_william.ents])

>> [(William, 'PERSON', 378), (William II, 'PERSON', 378), (Anna Pavlovna, 'PERSON', 378), (Russia, 'GPE', 382), (
, 'GPE', 382), (William, 'PERSON', 378), (1840, 'DATE', 388), (the Prince of Orange, 'LOC', 383), (1849, 'DATE', 388), (Netherlands, 'GPE', 382), (
, 'GPE', 382), (William, 'PERSON', 378), (Sophie, 'GPE', 382), (Württemberg, 'PERSON', 378), (1839, 'DATE', 388), (three, 'CARDINAL', 394), (William, 'PERSON', 378), (Maurice, 'PERSON', 378), (Alexander, 'GPE', 382), (
, 'GPE', 382)]

SpaCy 还提供依存解析,可以进一步用于从文本中提取名词短语,如下所示:

# Noun Phrase extraction
senten_ = nlp('The book deals with NLP')
for noun_ in senten_.noun_chunks:
    print(noun_)
    print(noun_.text)
    print('---')
    print(noun_.root.dep_)
    print('---')
    print(noun_.root.head.text)
>> The book
The book
---

nsubj

---
deals
NLP
NLP
---

pobj

---
with

玄诗

Gensim ( https://pypi.python.org/pypi/gensim )是另一个重要的库。它主要用于主题建模和文档相似性。Gensim 对于获取单词的单词向量等任务最为有用。

from gensim.models import Word2Vec
min_count = 0
size = 50
window = 2
sentences= "bitcoin is an innovative payment network and a new kind of money."
sentences=sentences.split()
print(sentences)
>> ['bitcoin', 'is', 'an', 'innovative', 'payment', 'network', 'and', 'a', 'new', 'kind', 'of', 'money.']
model = Word2Vec(sentences, min_count=min_count, size=size, window=window)
model
>> <gensim.models.word2vec.Word2Vec at 0x7fd1d889e710>
model['a']            # Vector for the character 'a'
>> array([  9.70041566e-03,  -4.16209083e-03,   8.05089157e-03,
         4.81479801e-03,   1.93488982e-03,  -4.19071550e-03,
         1.41675305e-03,  -6.54719025e-03,   3.92444432e-03,
        -7.05081783e-03,   7.69438222e-03,   3.89579940e-03,
        -9.02676862e-03,  -8.58401007e-04,  -3.24096601e-03,
         9.24982232e-05,   7.13059027e-03,   8.80233292e-03,
        -2.46750680e-03,  -5.17094415e-03,   2.74592242e-03,
         4.08304436e-03,  -7.59716751e-03,   8.94313212e-03,
        -8.39354657e-03,   5.89343486e-03,   3.76902265e-03,
         8.84669367e-04,   1.63217512e-04,   8.95449053e-03,
        -3.24510527e-03,   3.52341868e-03,   6.98625855e-03,
        -5.50296041e-04,  -5.10712992e-03,  -8.52414686e-03,
        -3.00202984e-03,  -5.32727176e-03,  -8.02035537e-03,
        -9.11156740e-03,  -7.68519414e-04,  -8.95629171e-03,
        -1.65163784e-03,   9.59598401e-04,   9.03090648e-03,
         5.31166652e-03,   5.59739536e-03,  -4.49402537e-03,
        -6.75261812e-03,  -5.75679634e-03], dtype=float32)

人们可以从 Google 下载经过训练的向量集,并计算出所需文本的表示,如下所示:

model = gensim.models.KeyedVectors.load_word2vec_format('GoogleNews-vectors-negative300.bin', binary=True)  
sentence = ["I", "hope", "it", "is", "going", "good", "for", "you"]
vectors = [model[w] for w in sentence]

(您可以使用以下链接下载示例模型: https://github.com/mmihaltz/word2vec-GoogleNews-vectors ,或者使用给定名称的.bin文件进行常规搜索,并将其粘贴到您的工作目录中。)

Gensim 还提供 LDA(潜在狄利克雷分配——一种生成统计模型,允许通过未观察到的组来解释观察值集,从而解释为什么数据的某些部分是相似的)模块。这既允许从训练语料库进行 LDA 模型估计,又允许推断新的、看不见的文档上的主题分布。该模型还可以用在线培训的新文档进行更新。

模式

模式( https://pypi.python.org/pypi/Pattern )对于各种 NLP 任务都很有用,比如词性标注、n-gram 搜索、情感分析,以及 WordNet 和机器学习,比如向量空间建模、k-means 聚类、朴素贝叶斯、K-NN 和 SVM 分类器。

import pattern
from pattern.en import tag
tweet_ = "I hope it is going good for you!"
tweet_l = tweet_.lower()
tweet_tags = tag(tweet_l)
print(tweet_tags)
>> [('i', 'JJ'), ('hope', 'NN'), ('it', 'PRP'), ('is', 'VBZ'), ('going', 'VBG'), ('good', 'JJ'), ('for', 'IN'), ('you', 'PRP'), ('!', '.')]

斯坦福·科伦普

Stanford CoreNLP ( https://stanfordnlp.github.io/CoreNLP/ )提供了单词的基本形式;他们的词类;无论是公司名称、人名等。;将日期、时间和数值正常化。根据短语和句法依赖性标记句子的结构;指示哪些名词短语指代相同的实体;表示情绪;提取实体提及之间的特定或开放类关系;得到人们所说的话;等等。

NLP 入门

在本章的这一部分,我们将获取一个简单的文本数据(比如一个句子)并执行一些基本操作来熟悉 NLP 是如何工作的。这一部分将为你在本书其余部分将要学习的内容提供基础。

使用正则表达式的文本搜索

正则表达式是从给定文本中搜索特定类型的设计或词集的一种非常有用的方法。正则表达式(RE)指定一组与之匹配的字符串。这个模块中的函数允许你检查一个给定的字符串是否匹配一个特定的 RE(或者一个给定的 RE 是否匹配一个特定的字符串,这归结为同样的事情)。

# Text search across the sentence using Regular expression
import re
words = ['very','nice','lecture','day','moon']
expression = '|'.join(words)
re.findall(expression, 'i attended a very nice lecture last year', re.M)
>> ['very', 'nice', 'lecture']

要列出的文本

你可以读取一个文本文件,并根据你的需要把它转换成单词列表或句子列表。

text_file = 'data.txt'
# Method-1 : Individual words as separate elements of the list
with open(text_file) as f:
    words = f.read().split()
print(words)
>> ['Are', 'you', 'sure', 'moving', 'ahead', 'on', 'this', 'route', 'is', 'the', 'right', 'thing?']

# Method-2 : Whole text as single element of the list
f = open(text_file , 'r')
words_ = f.readlines()
print(words_)
>> ['Are you sure moving ahead on this route is the right thing?\n']

预处理文本

您可以做很多事情来预处理文本。例如,用一个单词替换另一个单词,删除或添加某些特定类型的单词等。

sentence = 'John has been selected for the trial phase this time. Congrats!!'
sentence=sentence.lower()
# defining the positive and negative words explicitly
positive_words=['awesome','good', 'nice', 'super', 'fun', 'delightful','congrats']
negative_words=['awful','lame','horrible','bad']
sentence=sentence.replace('!','')
sentence
>> 'john has been selected for the trial phase this time. congrats'
words= sentence.split(' ')
print(words)
>> ['john', 'has', 'been', 'selected', 'for', 'the', 'trial', 'phase', 'this', 'time.', 'congrats']
result= set(words)-set(positive_words)
print(result)
>> {'has', 'phase', 'for', 'time.', 'trial', 'been', 'john', 'the', 'this', 'selected'}

从 Web 上访问文本

可以使用urllib访问来自 URL 的文本文件。

# Make sure both the packages are installed
import urllib3
from bs4 import BeautifulSoup
pool_object = urllib3.PoolManager()
target_url = 'http://www.gutenberg.org/files/2554/2554-h/2554-h.htm#link2HCH0008'
response_ = pool_object.request('GET', target_url)
final_html_txt = BeautifulSoup(response_.data)
print(final_html_txt)

停用词的删除

停用字词是搜索引擎被编程忽略的常用字词(例如)。

import nltk
from nltk import word_tokenize
sentence= "This book is about Deep Learning and Natural Language Processing!"
tokens = word_tokenize(sentence)
print(tokens)
>> ['This', 'book', 'is', 'about', 'Deep', 'Learning', 'and', 'Natural', 'Language', 'Processing', '!']
# nltk.download('stopwords')
from nltk.corpus import stopwords
stop_words = set(stopwords.words('english'))
new_tokens = [w for w in tokens if not w in stop_words]
new_tokens
>> ['This', 'book', 'Deep', 'Learning', 'Natural', 'Language', 'Processing', '!']

计数器矢量化

计数器矢量化是一个 SciKit-Learn 库工具,它可以获取任意数量的文本,并将每个唯一的单词作为一个特征返回,并计算特定单词在文本中出现的次数。

from sklearn.feature_extraction.text import CountVectorizer
texts=["Ramiess sings classic songs","he listens to old pop ","and rock music", ' and also listens to classical songs']
cv = CountVectorizer()
cv_fit=cv.fit_transform(texts)
print(cv.get_feature_names())
print(cv_fit.toarray())
>> ['also', 'and', 'classic', 'classical', 'he', 'listens', 'listens', 'music', 'old', 'pop', 'ramiess', 'rock', 'sings', 'songs', 'to']
>> [[0 0 1 0 0 0 0 0 0 0 1 0 1 1 0]
    [0 0 0 0 1 1 0 0 1 1 0 0 0 0 1]
    [0 1 0 0 0 0 0 1 0 0 0 1 0 0 0]
    [1 1 0 1 0 0 1 0 0 0 0 0 0 1 0]]

TF-IDF 得分

TF-IDF 是两个术语的首字母缩写词:术语频率和逆文档频率。TF 是表示特定单词的计数与文档中单词总数的比率。假设一个文档包含 100 个单词,其中单词 happy 出现了五次。快乐的频率项(即 tf)则为(5/100) = 0.05。另一方面,IDF 是文档总数与包含特定单词的文档的对数比。假设我们有 1000 万个文档,其中 1000 个文档中出现了 happy 这个词。那么,逆文档频率(即 idf)将被计算为 log (10,000,000/1,000) = 4。因此,TF-IDF 重量是这些量的乘积:0.05 × 4 = 0.20。

Note

与 TF-IDF 类似的是 BM25,它用于根据文档与查询的关系对文档进行评分。BM25 使用每个文档的查询项对一组文档进行排序,而不考虑文档内查询的关键字之间的关系。

from sklearn.feature_extraction.text import TfidfVectorizer
texts=["Ramiess sings classic songs","he listens to old pop","and rock music", ' and also listens to classical songs']
vect = TfidfVectorizer()
X = vect.fit_transform(texts)
print(X.todense())
>> [[ 0\.          0\.          0.52547275  0\.          0\.          0\.          0.
   0\.             0\.          0\.          0.52547275  0\.          0.52547275
   0.41428875     0\.        ]
 [ 0\.             0\.          0\.          0\.          0.4472136          0.4472136
   0\.             0\.          0.4472136   0.4472136   0\.          0\.          0.
   0\.             0.4472136 ]
 [ 0\.             0.48693426  0\.          0\.          0\.          0\.          0.
   0.61761437     0\.          0\.          0\.       0.61761437          0\.          0.
   0\.        ]
 [ 0.48546061     0.38274272  0\.          0.48546061  0\.          0.
   0.48546061     0\.          0\.          0\.          0\.          0\.          0.
   0.38274272     0\.        ]]

文本分类器

文本可以分为各种类别,如正面和负面。TextBlob 提供了许多这样的架构。

from textblob import TextBlob
from textblob.classifiers import NaiveBayesClassifier
data = [
 ('I love my country.', 'pos'),
 ('This is an amazing place!', 'pos'),
 ('I do not like the smell of this place.', 'neg'),
 ('I do not like this restaurant', 'neg'),
 ('I am tired of hearing your nonsense.', 'neg'),
 ("I always aspire to be like him", 'pos'),
 ("It's a horrible performance.", "neg")
 ]
model = NaiveBayesClassifier(data)
model.classify("It's an awesome place!")
>> 'pos'

深度学习简介

深度学习是机器学习的扩展领域,已被证明主要在文本、图像和语音领域非常有用。在深度学习下实现的算法集合与人脑中刺激和神经元之间的关系有相似之处。深度学习在计算机视觉、语言翻译、语音识别、图像生成等方面有着广泛的应用。这些算法非常简单,可以在有人监督和无人监督的情况下学习。

大多数深度学习算法都是基于人工神经网络的概念,并且随着丰富数据和足够计算资源的可用性,在当今世界中训练这种算法已经变得更加容易。有了额外的数据,深度学习模型的性能就会不断提高。在图 1-2 中可以更好地看到这一点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-2

Scaling data science techniques to amount of data

深度学习中的深度一词是指人工神经网络架构的深度,学习代表通过人工神经网络本身进行学习。图 1-3 准确描述了深度和浅层网络之间的差异,以及深度学习一词流行的原因。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-3

Representation of deep and shallow networks

深度神经网络能够从未标记和非结构化的数据中发现潜在的结构(或特征学习),例如图像(像素数据)、文档(文本数据)或文件(音频、视频数据)。

尽管人工神经网络和深度学习中的模型从根本上持有相似的结构,但这并不意味着两个人工神经网络的组合在训练使用数据时将与深度神经网络表现相似。

任何深度神经网络与普通人工神经网络的区别在于我们使用反向传播的方式。在普通的人工神经网络中,反向传播训练后面(或结束)的层比训练初始(或前面)的层更有效。因此,当我们回到网络中时,误差变得更小、更分散。

“深”有多深?

我们听到“深度”这个词,会立刻联想到它,但是浅层和深层神经网络之间并没有太大的区别。深度神经网络只是具有多个隐藏层的前馈神经网络。对,就是这么简单!

如果网络有很多层,那么我们说网络很深。你现在应该想到的问题是,一个网络必须有多少层才算深度?

在我们开始 NLP 空间中深度学习的实际旅程之前,回顾神经网络的基础知识及其不同类型将是有用的。

我们将介绍一个基本神经网络的基本结构,以及一些不同类型的神经网络,用于整个行业的应用。为了提供对这种技术的简明而实用的理解,本章的这一部分被细分为六个标题:

  • 什么是神经网络?
  • 神经网络的基本结构
  • 神经网络的类型
  • 多层感知器
  • 随机梯度下降
  • 反向传播

Note

详细的学术了解,可以参考Geoffrey hint on(www.cs.toronto.edu/~hinton/)等人( http://deeplearning.net/ )发表的论文和文章。

什么是神经网络?

神经网络有着悠久的历史,可以追溯到马文·明斯基在人工智能(AI)方面的开创性工作,以及他(在)对解决异或(XOR)函数的挑战的著名引用。随着对越来越大的数据集的访问以及提供巨大计算能力的云计算和 GPU 的出现,神经网络已经变得越来越普遍。这种对数据和计算的便捷访问提高了建模和分析的准确性。

神经网络是一种受生物学启发的范式(模仿哺乳动物大脑的功能),它使计算机能够从观察数据中学习人类的能力。它们目前为许多问题提供解决方案:图像识别、手写识别、语音识别、语音分析和 NLP。

为了帮助我们发展直觉,我们一天中执行的不同任务可以分类如下:

  • 代数或线性推理(例如,A × B = C,或一系列任务,如蛋糕的配方)
  • 识别感知或非线性推理(例如,将名称与动物照片相关联,或减轻压力,或基于声音分析验证陈述)
  • 通过观察学习任务(例如,在谷歌汽车中导航)

第一个任务可以通过算法来解决,即通过编程来描述,以从数字或成分中产生结果,而为后面的任务定义算法方法是困难的,如果不是不可能的话。后面的任务需要一个灵活的模型,它可以根据标记的例子自动调整自己的行为。

现在,统计或优化算法也努力提供与可能的输入相关的正确输出,尽管它们需要指定一个函数来模拟数据,从而产生最佳的一组系数。与优化技术相比,神经网络是一种灵活的功能,它自动调整其行为以尽可能满足输入和预期结果之间的关系,并被称为通用逼近器。

鉴于算法的普遍使用,所有流行的平台上都有可用的库(图 1-4 ),比如 R (knn,nnet 包),Scala(机器学习 ML 扩展),Python (TensorFlow,MXNet,Keras)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-4

Multiple open source platforms and libraries for deep learning

神经网络的基本结构

神经网络背后的基本原理是基本元素的集合,人工神经元或感知器,由 Frank Rosenblatt 在 20 世纪 50 年代首次开发。它们接受几个二进制输入,x 1 ,x 2 ,…,x N 并且如果总和大于激活电位,则产生单个二进制输出。每当激活电位被超过时,神经元就被称为“触发”,并表现为阶跃函数。触发的神经元将信号传递给连接到其树突的其他神经元,如果超过激活电位,这些神经元将依次触发,从而产生级联效应(图 1-5 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-5

Sample neuron

由于并非所有的输入都具有相同的重点,因此权重被附加到每个输入,x i 以允许模型将更多的重要性分配给一些输入。因此,如果加权和大于激活电位或偏置,即,

输出= )

实际上,由于阶跃函数的突变性质,这种简单形式很难实现(图 1-6 )。因此,创建了一个修改的形式来表现更可预测的行为,即权重和偏差的小变化仅引起输出的小变化。主要有两处修改。

  1. 输入可以取 0 到 1 之间的任何值,而不是二进制。
  2. 对于给定的输入,x 1 ,x 2 ,…,x N ,以及权重,为了使输出表现得更加平滑。w 1 ,w 2 ,…,w N ,以及 bias,b,使用以下 sigmoid 函数(图 1-7 ): )

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-6

Step function

指数函数(或σ)的平滑度意味着权重和偏差的微小变化将产生神经元输出的微小变化(该变化可以是权重和偏差变化的线性函数)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-7

Neural network activation function: sigmoid

除了常见的 sigmoid 函数之外,其他更常用的非线性函数如下,每种非线性函数的输出范围可能相似,也可能不同,可以相应地使用。

  • ReLU: Rectified linear unit . This keeps the activation guarded at zero. It is computed using the following function:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    where, x j , the j-th input value, and z j is its corresponding output value after the ReLU function f. Following is the graph (Figure 1-8) of the ReLU function, with ‘0’ value for all x <= 0, and with a linear slope of 1 for all x > 0:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 1-8

    ReLU function graph

ReLUs 经常面临死亡的问题,尤其是当学习速率被设置为更高的值时,因为这触发了不允许激活特定神经元的权重更新,从而使该神经元的梯度永远为零。ReLU 提供的另一个风险是激活函数的爆炸,因为输入值 x j 本身就是这里的输出。虽然 ReLU 也提供了其他好处,例如在 x j 小于 0 的情况下引入稀疏性,从而导致稀疏表示,并且当 ReLU 恒定的情况下梯度返回时,它导致更快的学习,同时降低了梯度消失的可能性。

  • LReLUs(泄漏 relu):通过引入 x 值小于 0 的略微降低的斜率(~0.01),这些减轻了垂死 relu 的问题。LReLUs 确实提供了成功的场景,尽管并不总是如此。
  • ELU(指数线性单位) :这些提供负值,推动平均单位激活接近零,从而通过将附近的梯度移动到单位自然梯度来加速学习过程。为了更好地解释 ELUs,请参考 Djork-Arné Clevert 的原始论文,可在 https://arxiv.org/abs/1511.07289 获得。
  • Softmax:也称为归一化指数函数,它转换(0,1)范围内的一组给定实数值,使得组合和为 1。softmax 函数表示如下:

)对于 j = 1,…,K

所有前面的函数都很容易微分,使得网络可以很容易地用梯度下降法训练(在下一节“神经网络的类型”中讨论)。

正如在哺乳动物的大脑中一样,单个神经元被组织成层,一层内和下一层之间有连接,从而创建了 ANN,即人工神经网络或多层感知器(MLP)。您可能已经猜到了,复杂度是基于元素的数量和连接的邻居的数量。

输入和输出之间的层称为隐藏层,层之间连接的密度和类型就是配置。例如,全连接配置将 L 层的所有神经元连接到 L + 1 层的神经元。对于更明显的定位,我们可以只将一个局部邻域,比如说九个神经元,连接到下一层。图 1-9 展示了两个具有密集连接的隐藏层。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-9

Neural network architecture

神经网络的类型

到目前为止,我们一直在一般性地讨论人工神经网络;然而,基于结构和用途,有不同类型的神经网络。为了使神经网络以更快和更有效的方式学习,各种神经元以这样一种方式放置在网络中,以最大化网络对给定问题的学习。这种神经元的放置遵循了一种合理的方法,并导致了一种架构网络设计,其中不同的神经元消耗其他神经元的输出,或者不同的函数在其输入中从其他函数获取输出。如果神经元之间的连接以循环的形式放置,那么它们就形成了网络,如反馈、递归或循环神经网络。然而,如果神经元之间的连接是非循环的,它们就形成了网络,例如前馈神经网络。以下是对所引用网络的详细解释。

前馈神经网络

前馈神经网络构成了神经网络家族的基本单元。任何前馈神经网络中的数据移动都是通过当前隐藏层从输入层到输出层,从而限制任何类型的循环(图 1-10 )。一层的输出作为下一层的输入,对网络架构中的任何类型的环路都有限制。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-10

A multilayer feedforward neural network

卷积神经网络

卷积神经网络非常适合于图像识别和手写识别。它们的结构基于对图像的窗口或部分进行采样,检测其特征,然后使用这些特征来构建表示。从这个描述中可以明显看出,这导致了几个层的使用,因此这些模型是第一批深度学习模型。

循环神经网络

循环神经网络(RNNs 当数据模式随时间变化时,使用图 1-11 。rnn 可以被假设为随时间展开。RNN 使用输出(即先前时间步长的状态作为输入)在每个时间步长对输入应用相同的图层。

rnn 具有反馈回路,其中来自前一次发射或时间索引 T 的输出作为时间索引 T + 1 处的输入之一被馈送。可能有这样的情况,神经元的输出作为输入反馈给它自己。因为它们非常适合于涉及序列的应用,所以它们广泛用于与视频相关的问题,视频是图像的时间序列,并且用于翻译目的,其中理解下一个单词是基于先前文本的上下文。以下是各种类型的 rnn:

  • Encoding recurrent neural networks : This set of RNNs enables the network to take an input of the sequence form (Figure 1-12).

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 1-12

    Encoding RNNs

  • Generating recurrent neural networks: Such networks basically output a sequence of numbers or values, like words in a sentence (Figure 1-13).

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 1-13

    Generating RNNs

  • General recurrent neural networks: These networks are a combination of the preceding two types of RNNs. General RNNs (Figure 1-14) are used to generate sequences and, thus, are widely used in NLG (natural language generation) tasks.

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 1-14

    General RNNs

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-11

Recurrent neural network

编码器-解码器网络

编码器-解码器网络使用一个网络来创建输入的内部表示,或者对其进行“编码”,并且该表示被用作另一个网络的输入以产生输出。这对于超越输入的分类是有用的。基于概念,最终输出可以是相同的模态,即语言翻译,或者不同的模态,例如图像的文本标记。作为参考,人们可以参考谷歌( https://papers.nips.cc/paper/5346-sequence-to-sequence-learning-with-neural-networks.pdf )团队发表的论文“用神经网络进行序列到序列的学习”。

循环神经网络

在循环神经网络(图 1-15 )中,一组固定的权重递归应用于网络结构,主要用于发现数据的层次或结构。RNN 是一条链,而循环神经网络采取树状结构的形式。这种网络在自然语言处理领域有很大的用途,例如破译句子的情感。整体情感不仅仅取决于单个作品,还取决于单词在句子中的句法组合顺序。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-15

Recursive neural network

可以看出,网络有不同的类型,虽然有些网络可以应用于许多不同的环境,但就速度和质量而言,特定的网络更适合某些应用。

多层感知器

多层感知器(MLPs)属于前馈神经网络的范畴,由三种类型的层组成:输入层、一个或多个隐藏层和最终输出层。正常的 MLP 具有以下特性:

  • 具有任意数量神经元的隐藏层
  • 使用线性函数的输入图层
  • 使用激活函数的隐藏层,如 sigmoid
  • 给出任意数量输出的激活函数
  • 输入层、隐藏层和输出层之间正确建立的连接

MLPs 也称为通用逼近器,因为它们可以通过在隐藏层中使用足够数量的神经元、改变权重或者通过使用额外的训练数据来逼近给定的函数达到任何精度水平,从而找到输入值和目标之间的关系。这甚至不需要大量关于输入和输出值之间映射的先验信息。通常,在给定 MLP 自由度的情况下,它可以通过引入更多的隐藏层、每个隐藏层中更少的神经元和最佳权重来胜过基本的 MLP 网络。这有助于模型的整体泛化过程。

以下是网络体系结构的一些对其性能有直接影响的功能

  • 隐藏层:这些有助于网络的泛化因子。在大多数情况下,在足够数量的神经元的支持下,单个层足以包含任何期望函数的近似。
  • 隐藏神经元:隐藏层中存在的神经元的数量,可以通过使用任何类型的公式进行选择。一个基本的经验法则是在一个和几个输入单元之间选择计数。另一种方法是使用交叉验证,然后检查隐藏层中神经元的数量与每个组合的平均均方误差(MSE)之间的关系,最后选择具有最小 MSE 值的组合。它还取决于非线性的程度或初始问题的维数。因此,增加/删除神经元更像是一个适应性过程。
  • 输出节点:输出节点的计数通常等于我们想要分类目标值的类的数量。
  • 激活函数:这些函数应用于各个节点的输入。一组非线性函数(在本章神经网络的基本结构一节中有详细描述)用于使输出落在所需的范围内,从而防止网络瘫痪。除了非线性之外,这些函数的连续可微性有助于防止对神经网络训练的抑制。

由于 MLP 给出的输出仅依赖于当前输入,而不依赖于过去或未来的输入,因此 MLP 被认为适于解决分类问题。

图 1-16 显示 MLP 共有(L + 2)层,输入层在第一个位置,接着是 L 个隐藏层,最后是第(L + 2)个位置的输出层。以下等式定义了 MLP 的不同单位,激活函数应用于网络的不同阶段。

W(k)表示第 k 个隐藏层和它之前的层、输入层或另一个隐藏层之间的权重连接。每个 W(k)由两个连接层的单元 I 和 j 之间的权重 W ij (k) 组成。b(k)是第 k 层的偏差。

下面的等式代表对于 k > 0 的隐藏层预激活:

)

对于第 k 个隐藏层中存在的任何第 I 个神经元,以下等式成立:

)

输出层(k = L + 1)的激活函数如下:

)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-16

Multilayer neural network

随机梯度下降

几乎所有最优化问题的解决方案的主力是梯度下降算法。这是一种迭代算法,通过随后更新函数的参数来最小化损失函数。

正如我们从图 1-17 中看到的,我们首先把我们的功能想象成一种山谷。我们想象一个球滚下山谷的斜坡。我们的日常经验告诉我们,球最终会滚到谷底。也许我们可以用这个想法来寻找成本函数的最小值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-17

Ball rolling down the slope

这里我们使用的函数依赖于两个变量:v1 和 v2。这可能是显而易见的,因为我们的损失函数看起来和前面的一样。为了实现这样的平滑损失函数,我们取二次损失,如下:

)

同样,读者应该注意,二次成本函数只是一种方法,还有许多其他方法来定义损失。最终,选择不同损失函数的目的是得到

  1. 关于重量的平滑偏导数
  2. 一条好的凸曲线,实现全局最小。然而,在寻找全局最小值时,许多其他因素也起作用(学习速率、函数形状等。).

我们随机选择一个(假想的)球的起点,然后模拟球滚到谷底时的运动。打个类似的比方,想象我们在曲线上的某个任意点初始化网络的权重,或者一般来说,初始化函数的参数(就像在斜率的任意点上丢一个球一样),然后我们在附近检查斜率(导数)。

我们知道,由于重力的作用,球将向最大坡度的方向下落。类似地,我们在该点的导数方向上移动权重,并根据以下规则更新权重:

设 J(w) =成本是权重的函数

w =网络参数(v1 和 v2)

w i =初始权重集(随机初始化)

)

这里,dJ(w)/dw =重量 w 相对于 J(w)的偏导数

η =学习率。

学习率更多的是一个超参数,没有固定的方法找到最合适的学习率。然而,人们总是可以通过研究批次损失来找到它。

一种方法是看到损失,分析损失的模式。一般来说,不良的学习率会导致小批量的不稳定损失。它(亏损)可以递归上下,不稳定。

图 1-18 通过图表说明了更直观的解释。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-18

Impact of small and large learning rates

在上图中,有两种情况:

  1. 小学习率
  2. 大学习率

目的是达到上图的最小值,我们必须到达谷底(就像球的类比一样)。现在,学习率与球滚下山时的跳跃有关。

首先考虑情况 1(图的左边部分),其中我们进行小跳跃,逐渐保持向下滚动,慢慢地,并最终达到最小值,有可能球会卡在沿途的一些小裂缝中,并且无法逃脱,因为它无法进行大跳跃。

在情况 2(图的右边部分)中,与曲率的斜率相比,有更大的学习速率。这是一个次优的策略,实际上可能会把我们从山谷中驱逐出去,在某些情况下,这可能是一个走出局部最小值的良好开端,但在跳过全局最小值的情况下,这一点都不令人满意。

在图中,我们正在实现局部最小值,但这只是一种情况。这意味着权重卡在了局部最小值,而我们错过了全局最小值。梯度下降或随机梯度下降不能保证神经网络收敛到全局最小值(假设隐藏单元不是线性的),因为成本函数是非凸的。

理想情况是步长不断变化,本质上更具适应性,开始时略高,然后在一段时间内逐渐减小,直到收敛。

反向传播

理解反向传播算法可能需要一些时间,如果您正在寻找神经网络的快速实现,那么您可以跳过这一部分,因为现代库具有自动区分和执行整个训练过程的能力。然而,理解这种算法肯定会让你深入了解与深度学习相关的问题(学习问题,学习缓慢,爆炸梯度,递减梯度)。

梯度下降是一种强大的算法,但当权重数量增加时,它是一种缓慢的方法。在神经网络具有数千个范围内的参数的情况下,相对于损失函数训练每个权重,或者更确切地说,将损失公式化为所有权重的函数,对于实际应用来说变得极其缓慢和复杂。

由于 Geoffrey Hinton 和他的同事在 1986 年发表了开创性的论文,我们有了一个非常快速和漂亮的算法,可以帮助我们找到损失相对于每个重量的偏导数。该算法是每个深度学习算法的训练过程的主力。更多详细信息可以在这里找到: www.cs.toronto.edu/~hinton/backprop.html

这是计算精确梯度的最有效的可能过程,并且其计算成本总是与计算损失本身具有相同的 O()复杂度。反向传播的证明超出了本书的范围;然而,对算法的直观解释可以让你很好地理解它的复杂工作。

为了使反向传播有效,关于Error函数有两个基本假设。

  1. 总误差可以写成训练样本/小批量个体误差的总和,)
  2. 误差可以写成网络输出的函数

反向传播由两部分组成:

  1. 正向传递,其中我们初始化权重,并建立一个前馈网络来存储所有值
  2. 向后传递,执行该传递以使存储的值更新权重

偏导数、链式法则、线性代数是处理反向传播所需的主要工具(图 1-19 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1-19

Backpropagation mechanism in an ANN

最初,所有的边权重都是随机分配的。对于训练数据集中的每个输入,激活人工神经网络,并观察其输出。将该输出与我们已知的期望输出进行比较,误差被“传播”回前一层。会记录此错误,并相应地“调整”权重。重复该过程,直到输出误差低于预定阈值。

一旦前面的算法终止,我们就有了一个“学习过的”人工神经网络,我们认为它可以处理“新的”输入。据说这个 ANN 已经从几个例子(标记的数据)和它的错误(错误传播)中学习了。

好奇的读者应该研究一下关于反向传播的原始论文。我们提供了一个资源和博客列表,以便更深入地理解该算法。然而,当涉及到实现时,您几乎不会编写自己的反向传播代码,因为大多数库都支持自动微分,并且您不会真的想要调整反向传播算法。

通俗地说,在反向传播中,我们尝试顺序更新权重,首先通过在网络上进行正向传递,之后我们首先使用标签和最后一层输出来更新最后一层的权重,然后随后在之前的层上递归地使用该信息并继续。

深度学习图书馆

本节包括对一些广泛使用的深度学习库的介绍,包括 Theano、TensorFlow 和 Keras,此外还有对其中每一个库的基本教程。

提亚诺

Theano 是一个开源项目,主要由蒙特利尔大学在 Yoshua Bengio 的监督下开发。它是 Python 的一个数值计算库,语法类似于 NumPy。它在用多维数组执行复杂的数学表达式时很有效。这使得它成为神经网络的完美选择。

链接 http://deeplearning.net/software/theano 将使用户对所涉及的各种操作有更好的了解。我们将演示在不同平台上安装 Theano 的步骤,然后是相关的基础教程。

Theano 是一个数学库,它提供了创建机器学习模型的方法,这些模型可以在以后用于多个数据集。在 ano 之上已经实现了许多工具。主要包括

Note

应该注意的是,在撰写本书时,由于其他深度学习包的使用量大幅增加,社区成员已经停止了对 Theano 包的贡献。

Theano 安装

下面的命令对于在 Ubuntu 上安装 Theano 非常有用:

> sudo apt-get install python-numpy python-scipy python-dev python-pip python-nose g++ libopenblas-dev git

> sudo pip install Theano

关于在不同平台上安装 Theano 的详细说明,请参考以下链接: http://deeplearning.net/software/theano/install.html 。甚至连 CPU 和 GPU 兼容的 docker 镜像都有。

Note

建议在单独的虚拟环境中继续安装。

最新版本的 Theano 可从以下网站的开发版本安装

> git clone git://github.com/Theano/Theano.git
> cd Theano
> python setup.py install

对于 Windows 上的安装,采取以下步骤(来源于对堆栈溢出的回答):

  1. 安装 TDM GCC x64 ( http://tdm-gcc.tdragon.net/ )。
  2. 安装 Anaconda x64 ( www.continuum.io/downloads ,在C:/Anaconda中说)。
  3. 安装 Anaconda 之后,运行以下命令:
    1. conda update conda
    2. conda update -all
    3. conda install mingw libpython
  4. 在环境变量PATH中包含目的地'C:\Anaconda\Scripts'
  5. 安装旧版本或可用的最新版本。
    1. 旧版本:

      > pip install Theano
      
      
    2. 最新版本:

         >  pip install --upgrade --no-deps  git+git://github.com/Theano/Theano.git
      
      

没有例子

下一节介绍了 Theano 库中的基本代码。Theano 库的张量子包包含了大部分需要的符号。

下面的示例使用了张量子包,并对两个数执行运算(输出已包括在内以供参考):

> import theano
> import theano.tensor as T
> import numpy
> from theano import function
# Variables 'x' and 'y' are defined
> x = T.dscalar('x')               # dscalar : Theano datatype
> y = T.dscalar('y')

# 'x' and 'y' are instances of TensorVariable, and are of dscalar theano type
> type(x)
<class 'theano.tensor.var.TensorVariable'>
> x.type
TensorType(float64, scalar)
> T.dscalar
TensorType(float64, scalar)

# 'z' represents the sum of 'x' and 'y' variables. Theano's pp function, pretty-print out, is used to display the computation of the variable 'z'
> z = x + y
> from theano import pp
> print(pp(z))
(x+y)

# 'f' is a numpy.ndarray of zero dimensions, which takes input as the first argument, and output as the second argument
# 'f' is being compiled in C code
> f = function([x, y], z)        

可以按以下方式使用前面的函数来执行加法运算:

> f(6, 10)
array(16.0)
> numpy.allclose(f(10.3, 5.4), 15.7)
True

TensorFlow

TensorFlow 是 Google 为大规模机器学习实现提供的开源库。TensorFlow 在真正意义上是 dist faith 的继任者,dist faith 是谷歌发布的一个早期软件框架,能够利用具有数千台机器的计算集群来训练大型模型。

TensorFlow 是谷歌大脑团队的软件工程师和研究人员的创意,谷歌大脑团队是谷歌集团(现为 Alphabet)的一部分,主要专注于深度学习及其应用。它利用数据流图进行数值计算,下面将详细介绍。它的设计方式是通过单一的 API 来满足单一桌面或服务器或移动设备上的 CPU 或 GPU 系统的计算。

TensorFlow 将高度密集的计算任务从 CPU 转移到面向异构 GPU 的平台,只需对代码进行非常微小的更改。此外,在一台机器上训练的模型可以在另一台轻量级设备上使用,例如支持 Android 的移动设备,以达到最终实现的目的。

TensorFlow 是 DeepDream 和 RankBrain 等应用程序的实现基础,deep dream 是一种自动图像字幕软件,rank brain 帮助谷歌处理搜索结果,并向用户提供更相关的搜索结果。

为了更好地了解 TensorFlow 的工作和实现,可以在 http://download.tensorflow.org/paper/whitepaper2015.pdf 阅读相关白皮书。

数据流图表

TensorFlow 使用数据流图来表示以图形形式执行的数学计算。它利用了带有节点和边的有向图。这些节点表示数学运算,并充当数据输入、结果输出或持久变量读/写的终端。边处理节点之间的输入和输出关系。数据边在节点之间携带张量或动态大小的多维数据数组。这些张量单位在整个图形中的运动本身就导致了 TensorFlow 这个名称。图中的节点在接收到来自传入边的所有各自的张量后,异步且并行地执行。

数据流图中涵盖的整体设计和计算流程发生在一个会话中,然后在所需的机器上执行。TensorFlow 提供了 Python、C 和 C+API,依靠 C++进行优化计算。

凭借 TensorFlow 的以下特性,它是机器学习领域所需的大规模并行性和高可扩展性的最佳选择

  • 深度灵活性:用户获得了在 TensorFlow 上编写自己的库的全部权限。人们只需要以图形的形式创建整个计算,其余的由 TensorFlow 处理。
  • 真正的可移植性:TensorFlow 提供的可扩展性使得在笔记本电脑上编写的机器学习代码可以在 GPU 上进行训练,以实现更快的模型训练,而无需更改代码,并且可以作为云服务部署在移动设备、最终产品或 docker 上。
  • 自动微分:TensorFlow 通过其自动微分功能来处理基于梯度的机器学习算法的导数计算。数值导数的计算有助于理解数值相对于彼此的扩展图。
  • 语言选项:TensorFlow 提供 Python 和 C++接口来构建和执行计算图形。
  • 性能最大化:TensorFlow 图中的计算元素可以分配给多个设备,TensorFlow 通过广泛支持线程、队列和异步计算来实现最大性能。

tensorflow 安装

TensorFlow 安装非常容易,就像任何其他 Python 包一样,可以通过使用一个 pip install 命令来实现。此外,如果需要,用户可以按照 TensorFlow 主站点上的详细说明进行安装(r0.10 版本为 www.tensorflow.org/versions/r0.10/get_started/os_setup.html )。

通过 pip 安装之前,必须安装与平台相关的二进制包。有关 TensorFlow 软件包及其资源库 https://github.com/tensorflow/tensorflow 的更多详细信息,请参考以下链接。

要查看 TensorFlow 在 Windows 上的安装,请查看以下博客链接: www.hanselman.com/blog/PlayingWithTensorFlowOnWindows.aspx

TensorFlow 示例

运行和试验 TensorFlow 就像安装一样简单。官方网站上的教程 www.tensorflow.org/ ,非常清晰,涵盖了基础到专家级的例子。

下面是一个这样的例子,带有 TensorFlow 的基础知识(输出已包括在内以供参考):

> import tensorflow as tf
> hello = tf.constant('Hello, Tensors!')
> sess = tf.Session()
> sess.run(hello)
Hello, Tensors!

# Mathematical computation
> a = tf.constant(10)
> b = tf.constant(32)
> sess.run(a+b)
42

run()方法将计算的结果变量作为参数,并为此进行反向调用。

TensorFlow 图由不需要任何输入的节点形成,即源。然后,这些节点将它们的输出传递给其他节点,这些节点对产生的张量执行计算,整个过程以这种模式进行。

以下示例显示使用 Numpy 创建两个矩阵,然后使用 TensorFlow 将这些矩阵指定为 TensorFlow 中的对象,然后将两个矩阵相乘。第二个例子包括两个常数的加法和减法。TensorFlow 会话也被激活以执行操作,并在操作完成后被停用。

> import tensorflow as tf
> import numpy as np

> mat_1 = 10*np.random.random_sample((3, 4))   # Creating NumPy arrays
> mat_2 = 10*np.random.random_sample((4, 6))

# Creating a pair of constant ops, and including the above made matrices
> tf_mat_1 = tf.constant(mat_1)
> tf_mat_2 = tf.constant(mat_2)

# Multiplying TensorFlow matrices with matrix multiplication operation
> tf_mat_prod = tf.matmul(tf_mat_1 , tf_mat_2)

> sess = tf.Session()            # Launching a session

# run() executes required ops and performs the request to store output in 'mult_matrix' variable
> mult_matrix = sess.run(tf_mat_prod)
> print(mult_matrix)

# Performing constant operations with the addition and subtraction of two constants
> a = tf.constant(10)
> a = tf.constant(20)

> print("Addition of constants 10 and 20 is %i " % sess.run(a+b))
Addition of constants 10 and 20 is 30
> print("Subtraction of constants 10 and 20 is %i " % sess.run(a-b))
Subtraction of constants 10 and 20 is -10

> sess.close()                          # Closing the session

Note

由于在前面的 TensorFlow 示例中没有指定图形,因此会话仅使用默认实例。

Keras 是一个高度模块化的神经网络库,它运行在 ano 或 TensorFlow 之上。Keras 是同时支持 CNN 和 RNNs(我们将在后面的章节中详细讨论这两种神经网络)的库之一,可以毫不费力地在 GPU 和 CPU 上运行。

模型被理解为独立的、完全可配置的模块的序列或图形,这些模块可以以尽可能少的限制被插在一起。特别地,神经层、成本函数、优化器、初始化方案、激活函数、正则化方案都是独立的模块,可以组合起来创建新的模型。

硬安装

除了作为后端的 Theano 或 TensorFlow 之外,Keras 还使用了一些库作为依赖项。在安装 no 或 TensorFlow 之前安装这些组件可以简化安装过程。

> pip install numpy scipy
> pip install scikit-learn
> pip install pillow
> pip install h5py

Note

Keras 总是要求安装最新版本的 Theano(如前一节所述)。在整本书中,我们使用 TensorFlow 作为 Keras 的后端。

> pip install keras

克拉斯原则

Keras 提供了一个模型作为它的主要数据结构之一。每个模型都是可定制的实体,可以由不同的层、成本函数、激活函数和正则化方案组成。Keras 提供了广泛的预构建层来插入神经网络,其中一些包括卷积层、下降层、池层、局部连接层、递归层、噪声层和归一化层。网络的单个层被认为是下一层的输入对象。

Keras 中的代码片段主要是为实现神经网络和深度学习而构建的,除了它们相关的神经网络之外,它们也将包含在后面的章节中。

硬例子

Keras 的基本数据结构是模型类型,由网络的不同层组成。顺序模型是 Keras 中的主要模型类型,其中一层一层地添加,直到最终的输出层。

以下 Keras 示例使用了 UCI ML 知识库中的输血数据集。可以在这里找到关于数据的详细信息: https://archive.ics.uci.edu/ml/datasets/Blood+Transfusion+Service+Center )。数据取自台湾的一个输血服务中心,除了目标变量之外,还有四个属性。这是一个二元分类的问题,'1'代表献血者,'0'代表拒绝献血者。关于属性的更多细节可以从提到的链接中获得。

将网站上共享的数据集保存在当前工作目录中(如果可能,删除标头)。我们首先加载数据集,在 Keras 中构建基本的 MLP 模型,然后在数据集上拟合模型。

Keras 中模型的基本类型是顺序的,这为模型提供了逐层增加的复杂性。多个层可以用它们各自的结构制造,并堆叠在初始基础模型上。

# Importing the required libraries and layers and model from Keras
> import keras
> from keras.layers import Dense
> from keras.models import Sequential
> import numpy as np

# Dataset Link : # https://archive.ics.uci.edu/ml/datasets/Blood+Transfusion+Service+Center
# Save the dataset as a .csv file :

tran_ = np.genfromtxt('transfusion.csv', delimiter=',')
X=tran_[:,0:4]           # The dataset offers 4 input variables
Y=tran_[:,4]             # Target variable with '1' and '0'
print(x)

由于输入数据有四个相应的变量,因此input_dim(指不同输入变量的数量)被设置为四个。我们利用 Keras 中由密集层定义的完全连接的层来构建附加层。网络结构的选择是基于问题的复杂性。这里,第一个隐藏层由八个神经元组成,它们负责进一步捕捉非线性。该层已经用均匀分布的随机数和激活函数 ReLU 初始化,如本章前面所述。第二层有六个神经元,其结构与前一层相似。

# Creating our first MLP model with Keras
> mlp_keras = Sequential()
> mlp_keras.add(Dense(8, input_dim=4, init="uniform", activation="relu"))
> mlp_keras.add(Dense(6, init="uniform", activation="relu"))

在最后一层输出中,我们将激活设置为 sigmoid,如前所述,它负责生成一个介于01之间的值,并有助于二进制分类。

> mlp_keras.add(Dense(1, init="uniform", activation="sigmoid"))

为了编译网络,我们使用了具有对数损失的二进制分类,并选择 Adam 作为优化器的默认选择,选择 accuracy 作为要跟踪的期望指标。使用反向传播算法以及给定的优化算法和损失函数来训练网络。

> mlp_keras.compile(loss = 'binary_crossentropy', optimizer="adam",metrics=['accuracy'])

该模型已经在给定的数据集上用少量迭代(nb_epoch)进行了训练,并且以可行的批量大小的实例(batch_size)开始。可以根据以前处理这种数据集的经验来选择参数,或者甚至可以利用网格搜索来优化这种参数的选择。必要时,我们将在后面的章节中讨论相同的概念。

> mlp_keras.fit(X,Y, nb_epoch=200, batch_size=8, verbose=0)

下一步是最终评估已经构建的模型,并检查初始训练数据集的性能指标、损失和准确性。相同的操作可以在模型不熟悉的新测试数据集上执行,并且可以是模型性能的更好的度量。

> accuracy = mlp_keras.evaluate(X,Y)
> print("Accuracy : %.2f%% " %  (accuracy[1]*100 ))

如果想要通过使用不同的参数组合和其他调整来进一步优化模型,可以通过在进行模型创建和验证时使用不同的参数和步骤来实现,尽管这不需要在所有情况下都产生更好的性能。

# Using a different set of optimizer
> from keras.optimizers import SGD
> opt = SGD(lr=0.01)

下面的示例创建了一个模型,其配置类似于早期模型中的配置,但具有不同的优化器,并且包括来自初始定型数据的验证数据集:

> mlp_optim = Sequential()
> mlp_optim.add(Dense(8, input_dim=4, init="uniform", activation="relu"))
> mlp_optim.add(Dense(6, init="uniform", activation="relu"))
> mlp_optim.add(Dense(1, init="uniform", activation="sigmoid"))

# Compiling the model with SGD
> mlp_optim.compile(loss = 'binary_crossentropy', optimizer=opt, metrics=['accuracy'])

# Fitting the model and checking accuracy
> mlp_optim.fit(X,Y, validation_split=0.3, nb_epoch=150, batch_size=10, verbose=0)
> results_optim = mlp_optim.evaluate(X,Y)
> print("Accuracy : %.2f%%" % (results_optim[1]*100 ) )

在继续下一步之前,请确保前面几节中提到的自然语言处理和深度学习的所有包都已安装。一旦你建立了这个系统,你就可以很好地使用本书提供的例子了。

后续步骤

第一章介绍了自然语言处理和深度学习领域,以及来自公共 Python 库的相关介绍性示例。我们将在接下来的章节中对此进行更深入的研究,介绍当前自然语言处理中的行业问题,以及深度学习的存在如何影响以有效方式解决这些问题的范式。

二、词向量表示法

当处理语言和单词时,我们可能会在数千个类别中对文本进行分类,以用于多种自然语言处理(NLP)任务。近年来,在这一领域进行了大量的研究,这导致了语言中的单词向可以在多组算法和过程中使用的向量格式的转换。本章深入解释了单词嵌入及其有效性。我们介绍了它们的起源,并比较了用于完成各种 NLP 任务的不同模型。

单词嵌入简介

语言项目之间语义相似性的分类和量化属于分布语义学的范畴,并且基于它们在语言使用中的分布。向量空间模型以向量的形式表示文本文档和查询,长期以来被用于分布式语义目的。由向量空间模型在 N 维向量空间中表示单词对于不同的 NLP 算法实现更好的结果是有用的,因为它导致在新的向量空间中相似文本的分组。

单词嵌入这个术语是 Yoshua Bengio 在他的论文《一种神经概率语言模型》( www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf )中提出的。随后罗南·科洛伯特(Ronan Collobert)和杰森·韦斯顿(Jason Weston)在他们的论文《自然语言处理的统一架构》( https://ronan.collobert.com/pub/matos/2008_nlp_icml.pdf )中,作者展示了多任务学习和半监督学习的使用如何提高共享任务的泛化能力。最后,Tomas Mikolov 等人创建了 word2vec,并将单词嵌入置于镜头之下,阐明了单词嵌入的训练以及预训练单词嵌入的使用。后来,Jeffrey Pennington 引入了 GloVe,这是另一组预训练的单词嵌入。

单词嵌入模型已被证明比最初使用的单词袋模型或一热编码方案更有效,该模型由大小与词汇表大小相等的稀疏向量组成。向量表示中存在的稀疏性是词汇表的巨大规模以及在索引位置标注其中的单词或文档的结果。单词嵌入通过使用所有单个单词的周围单词,使用来自给定文本的信息并将其传递给模型,取代了这个概念。这使得嵌入可以采用密集向量的形式,在连续的向量空间中,这种形式表示单个单词的投影。因此,嵌入指的是单词在新学习的向量空间中的坐标。

下面的例子给出了一个单词向量的创建过程,它对样本词汇表中的单词进行了一键编码,然后对单词向量进行了重组。它使用了一种分布式表示方法,并展示了如何使用最终的矢量组合来推断单词之间的关系。

假设我们的词汇表包含罗马、意大利、巴黎、法国和国家这几个词。我们可以利用这些单词中的每一个来创建一个表示,对所有的单词使用一键方案,如图 2-1 中的罗马所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-1

A representation of Rome

使用前面的以向量形式呈现单词的方法,我们可以或多或少地通过比较单词的向量来测试单词之间的相等性。这种方法不会达到其他更高的目的。在一种更好的表示形式中,我们可以创建多个层次或分段,其中每个单词所显示的信息可以被分配不同的权重。我们可以选择这些片段或维度,并且每个单词将由这些片段上的权重分布来表示。因此,现在我们有了一种新的单词表示格式,对每个单词使用不同的标度(图 2-2 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-2

Our representation

用于每个单词的前面的向量确实表示了该单词的实际意思,并且提供了更好的尺度来进行单词之间的比较。新形成的向量足以回答单词之间的这种关系。图 2-3 表示使用这种新方法形成的矢量。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-3

Our vectors

不同单词的输出向量确实保留了语言规则和模式,并且这些模式的线性翻译证明了这一点。比如 vectors 和后面的单词 vector(法国)- vector(巴黎)+ vector(意大利)的差的结果,会接近 vector(罗马),如图 2-4 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-4

Comparing vectors

随着时间的推移,单词嵌入已经成为无监督学习领域最重要的应用之一。词向量提供的语义关系在神经机器翻译、信息检索和问答应用的 NLP 方法中有所帮助。

神经语言模型

Bengio 提出的前馈神经网络语言模型(FNNLM)引入了一个前馈神经网络,它由一个单独的隐藏层组成,预测序列的未来单词,在我们的例子中,只有一个单词。

训练神经网络语言模型以找到θ,这最大化了训练语料惩罚对数似然:

)

这里,f 是由与词汇表中存在的每个单词的分布式特征向量相关的参数和前馈或循环神经网络的参数组成的复合函数。R(θ)指的是正则化项,其将权重衰减惩罚应用于神经网络和特征向量矩阵的权重。函数 f 返回 softmax 函数使用前面的 n 个单词为第 t 个位置的单词计算的概率得分。

Bengio 引入的模型是同类模型中的第一个,为未来的单词嵌入模型奠定了基础。这些原始模型的组件仍然在当前的单词嵌入模型中使用。其中一些组件包括以下内容:

  1. 嵌入层:它记录了训练数据集中所有单词的表示。它用一组随机权重初始化。嵌入层由三部分组成,包括词汇表的大小、将嵌入所有单词的向量的输出大小以及模型的输入序列的长度。嵌入层的最终输出是一个二维向量,它对给定单词序列中存在的所有单词进行最终嵌入。
  2. 中间层:隐藏层,从初始层到最终层,计数为 1 或更多,通过将神经网络中的非线性函数应用于先前n单词的单词嵌入来产生输入文本数据的表示。
  3. Softmax 层:这是神经网络架构的最后一层,返回输入词汇表中所有单词的概率分布。

Bengio 的论文提到了 softmax 标准化中涉及的计算成本,并且它与词汇量成比例。这给在全词汇量上对神经语言模型和单词嵌入模型的新算法的试验带来了挑战。

神经网络语言模型有助于获得当前词汇表中不存在的单词的泛化,因为如果单词的组合与已经包含在句子中的单词相似,则从未见过的单词序列被给予更高的概率。

Word2vec

Word2vec 或单词到向量模型是由托马斯·米科洛夫等人( https://arxiv.org/pdf/1301.3781.pdf )提出的,并且是最被采用的模型之一。它用于学习单词嵌入,或单词的矢量表示。通过检查单词组之间的相似性,将所提出的模型的性能与先前的模型进行比较。该论文中提出的技术产生了对于相似单词具有跨多个相似度的单词的向量表示。单词表示的相似性超出了简单的句法规则,简单的代数运算也在单词向量上执行。

Word2vec 模型在内部使用单一层的简单神经网络,并捕获隐藏层的权重。训练模型的目的是学习隐藏层的权重,它代表“单词嵌入”虽然 word2vec 使用神经网络架构,但该架构本身不够复杂,并且没有利用任何类型的非线性。暂时可以卸下深度学习的标签。

Word2vec 提供了一系列用于在 n 维空间中表示单词的模型,通过这种方式,相似的单词和表示更接近含义的单词被放置得彼此靠近。这证明了将单词放置在新的向量空间中的整个练习是正确的。我们将介绍两个最常用的模型,skip-gram 和 continuous bag-of-words (CBOW ),以及它们在 TensorFlow 中的实现。这两个模型在算法上是相似的,区别仅在于它们执行预测的方式。CBOW 模型利用上下文或周围的词来预测中心词,而 skip-gram 模型利用中心词来预测上下文词。

与 one-hot 编码相比,word2vec 有助于减少编码空间的大小,并将单词的表示压缩到向量所需的长度(图 2-5 )。Word2vec 根据单词出现的上下文来处理单词表示。例如,同义词、反义词、语义相似的概念和相似的词出现在整个文本的相似上下文中,因此以相似的方式嵌入,并且它们的最终嵌入彼此更接近。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-5

Using the window size of 2 to pick the words from the sentence “Machines can now recognize objects and translate speech in real time” and training the model

跳格模型

跳格模型通过使用序列中的当前单词来预测周围的单词。周围单词的分类分数基于与中心单词的句法关系和出现次数。序列中出现的任何单词都被作为对数线性分类器的输入,对数线性分类器进而对出现在中心单词之前和之后的某个预先指定的单词范围内的单词进行预测。在单词范围的选择和结果单词向量的计算复杂度和质量之间有一个折衷。随着与相关单词的距离增加,与较近的单词相比,较远的单词与当前单词的相关程度较低。这是通过将权重分配为与中心单词的距离的函数,并从较高范围的单词中给予较小的权重或采样较少的单词来解决的(见图 2-6 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-6

Skip-gram model architecture

跳格模型的训练不涉及密集矩阵乘法。再加上一点优化,它可以产生一个高效的模型训练过程。

模型构件:建筑

在本例中,网络用于训练模型,输入单词作为一个热码编码的向量,输出作为一个热码编码的向量,表示输出单词(图 2-7 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-7

The model

模型构件:隐藏层

使用隐藏层来完成神经网络的训练,其中神经元的数量等于我们想要用来表示单词嵌入的特征或维度的数量。在下图中,我们用权重矩阵来表示隐藏层,该矩阵的列数为 300,等于神经元的数量(这将是单词嵌入的最终输出向量中的特征数),行数为 100,000,等于用于训练模型的词汇的大小。

神经元的数量被认为是模型的超参数,可以根据需要改变。谷歌训练的模型利用了 300 维特征向量,并且已经公开。对于那些不想训练单词嵌入模型的人来说,这可能是一个好的开始。你可以使用以下链接下载训练好的向量集: https://code.google.com/archive/p/word2vec/

由于作为词汇表中每个单词的输入而给出的输入向量是一次性编码的,所以在隐藏层阶段发生的计算将确保仅从权重矩阵中选择对应于相应单词的向量,并将其传递到输出层。如图 2-8 所示,在词汇量为 v 的情况下,对于任何一个单词,在输入向量中的期望索引处都会有“1”出现,将其乘以权重矩阵后,对于每一个单词,我们都会得到该单词对应的一行作为输出向量。因此,真正重要的不是输出,而是权重矩阵。图 2-8 清楚地表示了如何使用隐藏层的权重矩阵来计算单词向量查找表。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-8

Weight matrix of the hidden layer and vector lookup table

即使独热编码向量完全由零组成,将 1 × 100,000 维向量乘以 100,000 × 300 权重矩阵仍将导致选择存在“1”的对应行。图 2-9 给出了这种计算的图示,隐含层的输出就是关注词的矢量表示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-9

The calculation

模型组件:输出层

我们计算单词的单词嵌入背后的主要意图是确保具有相似含义的单词在我们定义的向量空间中更接近。这个问题由模型自动处理,因为在大多数情况下,具有相似含义的单词被相似的上下文(即,围绕输入单词的单词)包围,这在训练过程中固有地以相似的方式进行权重调整(图 2-10 )。除了同义词和具有相似含义的单词之外,该模型还处理词干提取的情况,因为复数或单数单词(比如,car 和 cars)将具有相似的上下文。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-10

The training process

CBOW 模型

连续词袋模型在架构上与 FNNLM 相似,如图 2-11 所示。单词的顺序不会影响投影层,重要的是哪些单词当前落入袋中以进行输出单词预测。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-11

Continuous bag-of-words model architecture

输入层和投影层以类似于 FNNLM 中共享的方式共享所有字位置的权重矩阵。CBOW 模型利用了上下文的连续分布表示,因此是一个连续的单词包。

Note

在较小的数据集上使用 CBOW 导致分布信息的平滑,因为模型将整个上下文视为单个观察。

二次抽样常用词

在大多数处理文本数据的情况下,词汇表的大小可以增加到大量的唯一单词,并且可以由所有单词的不同频率大小组成。为了选择出于建模目的而保留的单词,单词在语料库中出现的频率被用于决定单词的移除,也通过检查总单词的计数。子采样方法是由 Mikolov 等人在他们的论文“单词和短语的分布式表示及其组合性”中引入的。通过包括子采样,在训练过程中获得了显著的速度,并且以更有规律的方式学习单词表示。

生存函数用于计算单词级别的概率得分,该得分可在以后用于决定从词汇表中保留或删除该单词。该函数考虑了相关单词的频率和子采样率,可以进行调整:

)

其中,w i 是相关作品,z(w i 是该单词在训练数据集或语料库中的频率,s 是子采样率。Note

Mikolov 等人在他们的论文中提到的原始函数不同于 word2vec 代码的实际实现中使用的函数,并且已经在前面的文本中提到过。论文中为二次抽样选择的公式是启发式选择的,它包括一个阈值 t,通常表示为 10 -5 ,作为语料库中单词的最小频率。论文中提到的用于子采样的公式为

)

其中,w i 为关注的词,f(w i 为该词在训练数据集或语料库中的出现频率,s 为使用的阈值。

子采样率对是否保留频繁词做出关键决定。较小的值意味着单词不太可能保留在语料库中用于模型训练。在大多数情况下,在生存函数的输出上设置一个首选阈值,以删除出现频率较低的单词。参数 s 的优选值是 0.001。所提到的二次采样方法有助于克服语料库中稀有词和频繁词之间的不平衡。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-12

Distribution of the Survival function, P(x) = {(sqrt(x/0.001) + 1) * (0.001/x)} for a constant value of 0.001 for sampling rate (Credits : http://www.mccormickml.com )

该图显示了单词的频率与通过子采样方法生成的最终概率得分之间的图表。由于语料库中存在的单词都不能占据更高的百分比,所以我们将考虑图中单词百分比范围较低的部分,即沿 x 轴的部分。从上面的图表中,我们可以得出一些关于单词的百分比及其与生成的分数的关系的观察结果,从而得出二次抽样对单词的影响:

  • 当 z(w i ) < = 0.0026 时,P(w i ) =1。这意味着频率百分比小于 0.26%的单词将不会被考虑进行二次采样。
  • 当 z(w i ) = 0.00746 时,P(w i ) = 0.5。因此,一个词有 50%的机会被保留或删除所需的百分比是当它有 0.746%的频率。
  • P(w i ) = 0.033 出现在 z(w i ) =1 的情况下,即,即使整个语料库仅由单个单词组成,也有 96.7%的概率将其从语料库中移除,这在实践中没有任何意义。

负采样

负采样是噪声对比估计(NCE)方法的简化形式,因为它在选择噪声或负样本的计数及其分布时做出某些假设。它用作分级 softmax 函数的替代函数。虽然在训练模型时使用了负采样,但是在推断时,要计算完整的 softmax 值,以获得归一化的概率得分。

神经网络模型的隐藏层中的权重矩阵的大小取决于词汇的整体大小,词汇的整体大小是高阶的。这导致了大量的权重参数。所有权重参数在数百万和数十亿训练样本的多次迭代中被更新。对于每个训练样本,负采样会导致模型只更新很小百分比的权重。

给予模型的单词的输入表示是通过一个热编码向量。负采样随机选择给定数量的“负”词(比如 10 个),用“正”词(或中心词)的权重来更新这些词的权重。总的来说,对于 11 个单词(10 + 1),权重将被更新。参考前面给出的图,任何迭代都将导致更新权重矩阵中的 11 × 300 = 3,300 个值。然而,不管负采样的使用,在隐藏层中只更新“正”字的权重。

选择“阴性”样本的概率取决于该词在语料库中的频率。频率越高,“负面”单词被选中的概率就越高。正如在论文“单词和短语的分布式表示及其组成性”中提到的,对于小的训练数据集,负样本的计数在 5 到 20 之间,对于大的训练数据集,建议在 2 到 5 之间。

实际上,负样本是不应该确定输出的输入,只应该产生一个全为 0 的向量。

Note

子采样和负采样的组合在很大程度上减少了训练过程的负荷。

word2vec 模型通过在一系列句法和语义语言任务上使用模型的组合,帮助实现了更高质量的单词矢量表示。随着计算资源、更快的算法和文本数据可用性的进步,与早期提出的神经网络模型相比,有可能训练高质量的单词向量。

在下一节中,我们将研究如何使用 TensorFlow 实现 skip-gram 和 CBOW 模型。这些课程的结构归功于在线课程和写作时可用材料的结合。

Word2vec Code

TensorFlow 库通过引入在 word2vec 算法实现中使用的多个预定义函数,使我们的生活变得更加轻松。本节包括 word2vec 算法、skip-gram 和 CBOW 模型的实现。

本节开头的第一部分代码对于 skip-gram 和 CBOW 模型都是通用的,后面是 skip-gram 和 CBOW 代码小节中的相应实现。

Note

我们的练习使用的数据是 2006 年 3 月 3 日制作的英语维基百科转储的压缩格式。可从以下链接获得: http://mattmahoney.net/dc/textdata.html

导入 word2vec 实现所需的包,如下所示:

"""Importing the required packages"""
import random
import collections
import math
import os
import zipfile
import time
import re
import numpy as np
import tensorflow as tf

from matplotlib import pylab
%matplotlib inline

from six.moves import range
from six.moves.urllib.request import urlretrieve

"""Make sure the dataset link is copied correctly"""
dataset_link = 'http://mattmahoney.net/dc/'
zip_file = 'text8.zip'

函数data_download()下载 Matt Mahoney 收集的维基百科文章的清理数据集,并将其作为一个单独的文件存储在当前工作目录下。

def data_download(zip_file):
    """Downloading the required file"""
    if not os.path.exists(zip_file):
        zip_file, _ = urlretrieve(dataset_link + zip_file, zip_file)
        print('File downloaded successfully!')
    return None
data_download(zip_file)

> File downloaded successfully!

压缩文本数据集在内部文件夹数据集中提取,稍后用于训练模型。

"""Extracting the dataset in separate folder"""

extracted_folder = 'dataset'

if not os.path.isdir(extracted_folder):
    with zipfile.ZipFile(zip_file) as zf:
        zf.extractall(extracted_folder)
with open('dataset/text8') as ft_ :
    full_text = ft_.read()

由于输入数据在整个文本中具有多个标点和其他符号,因此相同的符号被替换为它们各自的标记,标记中具有标点和符号名称的类型。这有助于模型单独识别每个标点符号和其他符号,并生成一个向量。函数text_processing()执行该操作。它将维基百科的文本数据作为输入。

def text_processing(ft8_text):
    """Replacing punctuation marks with tokens"""
    ft8_text = ft8_text.lower()
    ft8_text = ft8_text.replace('.', ' <period> ')
    ft8_text = ft8_text.replace(',', ' <comma> ')
    ft8_text = ft8_text.replace('"', ' <quotation> ')
    ft8_text = ft8_text.replace(';', ' <semicolon> ')
    ft8_text = ft8_text.replace('!', ' <exclamation> ')
    ft8_text = ft8_text.replace('?', ' <question> ')
    ft8_text = ft8_text.replace('(', ' <paren_l> ')
    ft8_text = ft8_text.replace(')', ' <paren_r> ')
    ft8_text = ft8_text.replace('  ', ' <hyphen> ')
    ft8_text = ft8_text.replace(':', ' <colon> ')
    ft8_text_tokens = ft8_text.split()
    return ft8_text_tokens

ft_tokens = text_processing(full_text)

为了提高所产生的矢量表示的质量,建议去除与单词相关的噪声,即在输入数据集中频率小于 7 的单词,因为这些单词将不具有足够的信息来提供它们所存在的上下文。

可以通过检查数据集中单词计数和的分布来改变这个阈值。为了方便起见,我们在这里把它当作 7。

"""Shortlisting words with frequency more than 7"""
word_cnt = collections.Counter(ft_tokens)
shortlisted_words = [w for w in ft_tokens if word_cnt[w] > 7 ]

根据出现频率列出数据集中出现的前几个词,如下所示:

print(shortlisted_words[:15])

> ['anarchism', 'originated', 'as', 'a', 'term', 'of', 'abuse', 'first', 'used', 'against', 'early', 'working', 'class', 'radicals', 'including']

检查数据集中出现的所有单词的统计信息。

print("Total number of shortlisted words : ",len(shortlisted_words))
print("Unique number of shortlisted words : ",len(set(shortlisted_words)))
> Total number of shortlisted words :  16616688
> Unique number of shortlisted words :  53721

为了处理语料库中出现的独特单词,我们制作了一组单词,后跟它们在训练数据集中的频率。下面的函数创建一个字典,将单词转换成整数,反之,将整数转换成单词。最频繁出现的单词被赋予最小的值0,并且以类似的方式,数字被赋予其他单词。单词到整数的转换存储在一个单独的列表中。

def dict_creation(shortlisted_words):
    """The function creates a dictionary of the words present in dataset along with their frequency order"""
    counts = collections.Counter(shortlisted_words)
    vocabulary = sorted(counts, key=counts.get, reverse=True)
    rev_dictionary_ = {ii: word for ii, word in enumerate(vocabulary)}
    dictionary_ = {word: ii for ii, word in rev_dictionary_.items()}
    return dictionary_, rev_dictionary_

dictionary_, rev_dictionary_ = dict_creation(shortlisted_words)
words_cnt = [dictionary_[word] for word in shortlisted_words]

到目前为止创建的变量都是常见的,可以在 word2vec 模型的实现中使用。接下来的小节包括这两种架构的实现。

跳格码

在 skip-gram 模型中加入了二次抽样方法来处理文本中的停用词。通过对它们的频率设置阈值,去除所有具有较高频率并且在中心单词周围没有任何重要上下文的单词。这导致更快的训练和更好的单词向量表示。

Note

我们在这里的实现中使用了关于 skip-gram 的论文中给出的概率得分函数。对于训练集中的每个单词 w i ,我们将按照

)

给出的概率将其丢弃,其中 t 是阈值参数,f(w i )是单词 w i 在总数据集中的出现频率。

"""Creating the threshold and performing the subsampling"""
thresh = 0.00005
word_counts = collections.Counter(words_cnt)
total_count = len(words_cnt)
freqs = {word: count / total_count for word, count in word_counts.items()}
p_drop = {word: 1 - np.sqrt(thresh/freqs[word]) for word in word_counts}
train_words = [word for word in words_cnt if p_drop[word] < random.random()]

由于跳格模型采用中心单词并预测其周围的单词,skipG_target_set_generation()函数以期望的格式为跳格模型创建输入:

def skipG_target_set_generation(batch_, batch_index, word_window):
    """The function combines the words of given word_window size next to the index, for the SkipGram model"""
    random_num = np.random.randint(1, word_window+1)
    words_start = batch_index - random_num if (batch_index - random_num) > 0 else 0
    words_stop = batch_index + random_num
    window_target = set(batch_[words_start:batch_index] + batch_[batch_index+1:words_stop+1])
    return list(window_target)

skipG_batch_creation()函数利用skipG_target_set_generation()函数,创建一个中心单词及其周围单词的组合格式作为目标文本,并返回批处理输出,如下所示:

def skipG_batch_creation(short_words, batch_length, word_window):
    """The function internally makes use of the skipG_target_set_generation() function and combines each of the label
    words in the shortlisted_words with the words of word_window size around"""
    batch_cnt = len(short_words)//batch_length
    short_words = short_words[:batch_cnt*batch_length]  

    for word_index in range(0, len(short_words), batch_length):
        input_words, label_words = [], []
        word_batch = short_words[word_index:word_index+batch_length]
        for index_ in range(len(word_batch)):
            batch_input = word_batch[index_]
            batch_label = skipG_target_set_generation(word_batch, index_, word_window)
            # Appending the label and inputs to the initial list. Replicating input to the size of labels in the window
            label_words.extend(batch_label)
            input_words.extend([batch_input]*len(batch_label))
            yield input_words, label_words

以下代码注册了一个 TensorFlow 图以供 skip-gram 实现使用,声明了变量的inputslabels占位符,这些占位符将用于根据中心词和周围词的组合,为输入词和不同大小的批次分配一个热编码向量:

tf_graph = tf.Graph()
with tf_graph.as_default():
    input_ = tf.placeholder(tf.int32, [None], name="input_")
    label_ = tf.placeholder(tf.int32, [None, None], name="label_")

下面的代码声明了嵌入矩阵的变量,嵌入矩阵的维数等于词汇表的大小和单词嵌入向量的维数:

with tf_graph.as_default():
    word_embed = tf.Variable(tf.random_uniform((len(rev_dictionary_), 300), -1, 1))
    embedding = tf.nn.embedding_lookup(word_embed, input_)

tf.train.AdamOptimizer使用 Diederik P. Kingma 和 Jimmy Ba 的 Adam 算法( http://arxiv.org/pdf/1412.6980v8.pdf )来控制学习速率。关于进一步的信息,另外参考本吉奥的以下论文: http://arxiv.org/pdf/1206.5533.pdf

"""The code includes the following :
 # Initializing weights and bias to be used in the softmax layer
 # Loss function calculation using the Negative Sampling
 # Usage of Adam Optimizer
 # Negative sampling on 100 words, to be included in the loss function
 # 300 is the word embedding vector size
"""
vocabulary_size = len(rev_dictionary_)

with tf_graph.as_default():
    sf_weights = tf.Variable(tf.truncated_normal((vocabulary_size, 300), stddev=0.1) )
    sf_bias = tf.Variable(tf.zeros(vocabulary_size) )

    loss_fn = tf.nn.sampled_softmax_loss(weights=sf_weights, biases=sf_bias,
                                         labels=label_, inputs=embedding,
                                         num_sampled=100, num_classes=vocabulary_size)
    cost_fn = tf.reduce_mean(loss_fn)
    optim = tf.train.AdamOptimizer().minimize(cost_fn)

为了确保单词向量表示保持单词之间的语义相似性,在下面的代码部分中生成了一个验证集。这将在语料库中选择常见和不常见单词的组合,并基于单词向量之间的余弦相似性返回与它们最接近的单词。

"""The below code performs the following operations :
 # Performing validation here by making use of a random selection of 16 words from the dictionary of desired size
 # Selecting 8 words randomly from range of 1000    
 # Using the cosine distance to calculate the similarity between the words
"""
with tf_graph.as_default():
    validation_cnt = 16
    validation_dict = 100

    validation_words = np.array(random.sample(range(validation_dict), validation_cnt//2))
    validation_words = np.append(validation_words, random.sample(range(1000,1000+validation_dict), validation_cnt//2))
    validation_data = tf.constant(validation_words, dtype=tf.int32)

    normalization_embed = word_embed / (tf.sqrt(tf.reduce_sum(tf.square(word_embed), 1, keep_dims=True)))
    validation_embed = tf.nn.embedding_lookup(normalization_embed, validation_data)
    word_similarity = tf.matmul(validation_embed, tf.transpose(normalization_embed))

在当前工作目录下创建一个文件夹model_checkpoint来存储模型检查点。

"""Creating the model checkpoint directory"""
!mkdir model_checkpoint

epochs = 2            # Increase it as per computation resources. It has been kept low here for users to replicate the process, increase to 100 or more
batch_length = 1000
word_window = 10

with tf_graph.as_default():
    saver = tf.train.Saver()

with tf.Session(graph=tf_graph) as sess:
    iteration = 1
    loss = 0
    sess.run(tf.global_variables_initializer())

    for e in range(1, epochs+1):
        batches = skipG_batch_creation(train_words, batch_length, word_window)
        start = time.time()
        for x, y in batches:
            train_loss, _ = sess.run([cost_fn, optim],
                                     feed_dict={input_: x, label_: np.array(y)[:, None]})
            loss += train_loss

            if iteration % 100 == 0:
                end = time.time()
                print("Epoch {}/{}".format(e, epochs), ", Iteration: {}".format(iteration),
                      ", Avg. Training loss: {:.4f}".format(loss/100),", Processing : {:.4f} sec/batch".format((end-start)/100))
                loss = 0
                start = time.time()

            if iteration % 2000 == 0:
                similarity_ = word_similarity.eval()
                for i in range(validation_cnt):
                    validated_words = rev_dictionary_[validation_words[i]]
                    top_k = 8 # number of nearest neighbors
                    nearest = (-similarity_[i, :]).argsort()[1:top_k+1]
                    log = 'Nearest to %s:' % validated_words
                    for k in range(top_k):
                        close_word = rev_dictionary_[nearest[k]]
                        log = '%s %s,' % (log, close_word)
                    print(log)

            iteration += 1
    save_path = saver.save(sess, "model_checkpoint/skipGram_text8.ckpt")
    embed_mat = sess.run(normalization_embed)

> Epoch 1/2 , Iteration: 100 , Avg. Training loss: 6.1494 , Processing : 0.3485 sec/batch
> Epoch 1/2 , Iteration: 200 , Avg. Training loss: 6.1851 , Processing : 0.3507 sec/batch
> Epoch 1/2 , Iteration: 300 , Avg. Training loss: 6.0753 , Processing : 0.3502 sec/batch
> Epoch 1/2 , Iteration: 400 , Avg. Training loss: 6.0025 , Processing : 0.3535 sec/batch
> Epoch 1/2 , Iteration: 500 , Avg. Training loss: 5.9307 , Processing : 0.3547 sec/batch
> Epoch 1/2 , Iteration: 600 , Avg. Training loss: 5.9997 , Processing : 0.3509 sec/batch
> Epoch 1/2 , Iteration: 700 , Avg. Training loss: 5.8420 , Processing : 0.3537 sec/batch
> Epoch 1/2 , Iteration: 800 , Avg. Training loss: 5.7162 , Processing : 0.3542 sec/batch
> Epoch 1/2 , Iteration: 900 , Avg. Training loss: 5.6495 , Processing : 0.3511 sec/batch
> Epoch 1/2 , Iteration: 1000 , Avg. Training loss: 5.5558 , Processing : 0.3560 sec/batch
> ..................
> Nearest to during: stress, shipping, bishoprics, accept, produce, color, buckley, victor,
> Nearest to six: article, incorporated, raced, interval, layouts, confused, spitz, masculinity,
> Nearest to all: cm, unprotected, fit, tom, opold, render, perth, temptation,
> Nearest to th: ponder, orchids, shor, polluted, firefighting, hammering, bonn, suited,
> Nearest to many: trenches, parentheses, essential, error, chalmers, philo, win, mba,
> ..................

对于所有其他迭代,将打印类似的输出,并且已训练的网络将被恢复以供进一步使用。

"""The Saver class adds ops to save and restore variables to and from checkpoints."""
with tf_graph.as_default():
    saver = tf.train.Saver()

with tf.Session(graph=tf_graph) as sess:
    """Restoring the trained network"""
    saver.restore(sess, tf.train.latest_checkpoint('model_checkpoint'))
    embed_mat = sess.run(word_embed)
> INFO:tensorflow:Restoring parameters from model_checkpoint/skipGram_text8.ckpt

为了可视化的目的,我们使用了 t 分布随机邻居嵌入(t-SNE)(https://lvdmaaten.github.io/tsne/)。250 个随机单词的高维 300 向量表示已经在二维向量空间中使用。t-SNE 确保向量的初始结构保留在新的维度中,即使在转换之后。

word_graph = 250
tsne = TSNE()
word_embedding_tsne = tsne.fit_transform(embed_mat[:word_graph, :])

正如我们在图 2-13 中所观察到的,在二维空间中,具有语义相似性的单词在它们的表示中被放置得彼此更接近,从而即使在维度被进一步降低之后,它们的相似性仍然保持。例如,像年、年和年龄这样的词被放置在彼此附近,而远离像国际和宗教这样的词。可以针对更高的迭代次数来训练该模型,以实现单词嵌入的更好表示,并且可以进一步改变阈值,以微调结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-13

Two-dimensional representation of the word vectors obtained after training the Wikipedia corpus using a skip-gram model

CBOW 代码

CBOW 模型考虑周围的单词并预测中心单词。因此,使用cbow_batch_creation()函数已经实现了批处理和标签生成,当期望的word_window大小被传递给该函数时,该函数在label_变量中分配目标单词,在batch变量中分配上下文中的周围单词。

data_index = 0

def cbow_batch_creation(batch_length, word_window):
    """The function creates a batch with the list of the label words and list of their corresponding words in the context of
    the label word."""
    global data_index
    """Pulling out the centered label word, and its next word_window count of surrounding words
    word_window : window of words on either side of the center word
    relevant_words : length of the total words to be picked in a single batch, including the center word and the word_window words on both sides
    Format :  [ word_window ... target ... word_window ] """
    relevant_words = 2 * word_window + 1

    batch = np.ndarray(shape=(batch_length,relevant_words-1), dtype=np.int32)
    label_ = np.ndarray(shape=(batch_length, 1), dtype=np.int32)

    buffer = collections.deque(maxlen=relevant_words)   # Queue to add/pop

    #Selecting the words of length 'relevant_words' from the starting index
    for _ in range(relevant_words):
        buffer.append(words_cnt[data_index])
        data_index = (data_index + 1) % len(words_cnt)

    for i in range(batch_length):
        target = word_window  # Center word as the label
        target_to_avoid = [ word_window ] # Excluding the label, and selecting only the surrounding words

        # add selected target to avoid_list for next time
        col_idx = 0
        for j in range(relevant_words):
            if j==relevant_words//2:
                continue
            batch[i,col_idx] = buffer[j] # Iterating till the middle element for window_size length
            col_idx += 1
        label_[i, 0] = buffer[target]

        buffer.append(words_cnt[data_index])
        data_index = (data_index + 1) % len(words_cnt)

    assert batch.shape[0]==batch_length and batch.shape[1]== relevant_words-1
    return batch, label_

确保cbow_batch_creation()功能按照 CBOW 模型输入运行,已经对第一批标签及其周围窗口长度为 1 和 2 的单词进行了测试,并打印了结果。

for num_skips, word_window in [(2, 1), (4, 2)]:
    data_index = 0
    batch, label_ = cbow_batch_creation(batch_length=8, word_window=word_window)
    print('\nwith num_skips = %d and word_window = %d:' % (num_skips, word_window))

    print('batch:', [[rev_dictionary_[bii] for bii in bi] for bi in batch])
    print('label_:', [rev_dictionary_[li] for li in label_.reshape(8)])
>>
> with num_skips = 2 and word_window = 1:
    batch: [['anarchism', 'as'], ['originated', 'a'], ['as', 'term'], ['a', 'of'], ['term', 'abuse'], ['of', 'first'], ['abuse', 'used'], ['first', 'against']]
    label_: ['originated', 'as', 'a', 'term', 'of', 'abuse', 'first', 'used']

> with num_skips = 4 and word_window = 2:
    batch: [['anarchism', 'originated', 'a', 'term'], ['originated', 'as', 'term', 'of'], ['as', 'a', 'of', 'abuse'], ['a', 'term', 'abuse', 'first'], ['term', 'of', 'first', 'used'], ['of', 'abuse', 'used', 'against'], ['abuse', 'first', 'against', 'early'], ['first', 'used', 'early', 'working']]
    label_: ['as', 'a', 'term', 'of', 'abuse', 'first', 'used', 'against']

以下代码声明了 CBOW 模型配置中使用的变量。单词嵌入向量的大小被指定为 128,并且在目标单词的任一侧,1 个单词被考虑用于预测,如下所示:

num_steps = 100001
"""Initializing :
   # 128 is the length of the batch considered for CBOW
   # 128 is the word embedding vector size
   # Considering 1 word on both sides of the center label words
   # Consider the center label word 2 times to create the batches
"""
batch_length = 128
embedding_size = 128
skip_window = 1
num_skips = 2

要注册一个用于 CBOW 实现的 TensorFlow 图,并计算生成的向量之间的余弦相似性,请使用以下代码:

Note

这是一个与 skip-gram 代码中使用的图不同的图,所以这两个代码可以在一个脚本中使用。

"""The below code performs the following operations :
 # Performing validation here by making use of a random selection of 16 words from the dictionary of desired size
 # Selecting 8 words randomly from range of 1000    
 # Using the cosine distance to calculate the similarity between the words
"""

tf_cbow_graph = tf.Graph()

with tf_cbow_graph.as_default():
    validation_cnt = 16
    validation_dict = 100

    validation_words = np.array(random.sample(range(validation_dict), validation_cnt//2))
    validation_words = np.append(validation_words,random.sample(range(1000,1000+validation_dict), validation_cnt//2))

    train_dataset = tf.placeholder(tf.int32, shape=[batch_length,2*skip_window])
    train_labels = tf.placeholder(tf.int32, shape=[batch_length, 1])
    validation_data = tf.constant(validation_words, dtype=tf.int32)

"""
Embeddings for all the words present in the vocabulary
"""
with tf_cbow_graph.as_default() :
    vocabulary_size = len(rev_dictionary_)

    word_embed = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))

    # Averaging embeddings accross the full context into a single embedding layer
    context_embeddings = []
    for i in range(2*skip_window):
        context_embeddings.append(tf.nn.embedding_lookup(word_embed, train_dataset[:,i]))

    embedding =  tf.reduce_mean(tf.stack(axis=0,values=context_embeddings),0,keep_dims=False)

以下代码部分使用 64 个单词的负采样计算 softmax loss,并进一步优化模型训练中产生的权重、偏差和单词嵌入。阿达格拉德优化器( www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf )已用于此目的。

"""The code includes the following :
 # Initializing weights and bias to be used in the softmax layer
 # Loss function calculation using the Negative Sampling
 # Usage of AdaGrad Optimizer
 # Negative sampling on 64 words, to be included in the loss function
"""
with tf_cbow_graph.as_default() :
    sf_weights = tf.Variable(tf.truncated_normal([vocabulary_size, embedding_size],
                     stddev=1.0 / math.sqrt(embedding_size)))
    sf_bias = tf.Variable(tf.zeros([vocabulary_size]))

    loss_fn = tf.nn.sampled_softmax_loss(weights=sf_weights, biases=sf_bias, inputs=embedding,
                           labels=train_labels, num_sampled=64, num_classes=vocabulary_size)
    cost_fn = tf.reduce_mean(loss_fn)
    """Using AdaGrad as optimizer"""
    optim = tf.train.AdagradOptimizer(1.0).minimize(cost_fn)

此外,计算余弦相似度以确保语义相似单词的接近度。

"""
Using the cosine distance to calculate the similarity between the batches and embeddings of other words
"""
with tf_cbow_graph.as_default() :
    normalization_embed = word_embed / tf.sqrt(tf.reduce_sum(tf.square(word_embed), 1, keep_dims=True))
    validation_embed = tf.nn.embedding_lookup(normalization_embed, validation_data)
    word_similarity = tf.matmul(validation_embed, tf.transpose(normalization_embed))

with tf.Session(graph=tf_cbow_graph) as sess:
    sess.run(tf.global_variables_initializer())

    avg_loss = 0
    for step in range(num_steps):
        batch_words, batch_label_ = cbow_batch_creation(batch_length, skip_window)
        _, l = sess.run([optim, loss_fn], feed_dict={train_dataset : batch_words, train_labels : batch_label_ })
        avg_loss += l
        if step % 2000 == 0 :
            if step > 0 :
                avg_loss = avg_loss / 2000
            print('Average loss at step %d: %f' % (step, np.mean(avg_loss) ))
            avg_loss = 0

        if step % 10000 == 0:
            sim = word_similarity.eval()
            for i in range(validation_cnt):
                valid_word = rev_dictionary_[validation_words[i]]
                top_k = 8 # number of nearest neighbors
                nearest = (-sim[i, :]).argsort()[1:top_k+1]
                log = 'Nearest to %s:' % valid_word
                for k in range(top_k):
                    close_word = rev_dictionary_[nearest[k]]
                    log = '%s %s,' % (log, close_word)
                print(log)
    final_embeddings = normalization_embed.eval()

> Average loss at step 0: 7.807584
> Nearest to can: ambients, darpa, herculaneum, chocolate, alloted, bards, coyote, analogy,
> Nearest to or: state, stopping, falls, markus, bellarmine, bitrates, snub, headless,
> Nearest to will: cosmologies, valdemar, feeding, synergies, fence, helps, zadok, neoplatonist,
> Nearest to known: rationale, fibres, nino, logging, motherboards, richelieu, invaded, fulfill,
> Nearest to no: rook, logitech, landscaping, melee, eisenman, ecuadorian, warrior, napoli,
> Nearest to these: swinging, zwicker, crusader, acuff, ivb, karakoram, mtu, egg,
> Nearest to not: battled, grieg, denominators, kyi, paragliding, loxodonta, ceases, expose,
> Nearest to one: inconsistencies, dada, ih, gallup, ayya, float, subsumed, aires,
> Nearest to woman: philibert, lug, breakthroughs, ric, raman, uzziah, cops, chalk,
> Nearest to alternative: kendo, tux, girls, filmmakers, cortes, akio, length, grayson,
> Nearest to versions: helvetii, moody, denning, latvijas, subscripts, unamended, anodes, unaccustomed,
> Nearest to road: bataan, widget, commune, culpa, pear, petrov, accrued, kennel,
> Nearest to behind: coahuila, writeup, exarchate, trinidad, temptation, fatimid, jurisdictional, dismissed,
> Nearest to universe: geocentric, achieving, amhr, hierarchy, beings, diabetics, providers, persistent,
> Nearest to institute: cafe, explainable, approached, punishable, optimisation, audacity, equinoxes, excelling,
> Nearest to san: viscount, neum, sociobiology, axes, barrington, tartarus, contraband, breslau,
> Average loss at step 2000: 3.899086
> Average loss at step 4000: 3.560563
> Average loss at step 6000: 3.362137
> Average loss at step 8000: 3.333601
> .. .. .. ..

为了可视化的目的,使用 t-SNE,250 个随机单词的高维、128 个矢量表示已经被用于显示整个二维空间的结果。

num_points = 250
tsne = TSNE(perplexity=30, n_components=2, init="pca", n_iter=5000)
embeddings_2d = tsne.fit_transform(final_embeddings[1:num_points+1, :])

cbow_plot()函数绘制维度缩减的向量。

def cbow_plot(embeddings, labels):
    assert embeddings.shape[0] >= len(labels), 'More labels than embeddings'
    pylab.figure(figsize=(12,12))
    for i, label in enumerate(labels):
        x, y = embeddings[i,:]
        pylab.scatter(x, y)
        pylab.annotate(label, xy=(x, y), xytext=(5, 2), textcoords='offset points', ha="right", va="bottom")
    pylab.show()

words = [rev_dictionary_[i] for i in range(1, num_points+1)]
cbow_plot(embeddings_2d, words)

图 2-14 还示出了具有语义相似性的单词在它们的二维空间表示中彼此放置得更近。例如,单词 right、left 和 end 被放在一起,远离单词 one、two、three 等。

在这里呈现的所有单词中,我们可以观察到,在图的左下方,那些与单个字母表相关的单词被放置得彼此更靠近。这有助于我们理解模型是如何工作的,以及如何将没有重要意义的单个字符分配给相似的单词嵌入。在该群集中不存在诸如 a 和 I 这样的单词表明与这两个单词相关的两个字母的单词嵌入与其他单个字母不相似,因为它们在英语中具有实际意义,并且比其他字母使用得更频繁,在其他字母中,它们仅仅是训练数据集中打字错误的标志。具有更高迭代的模型的进一步训练可以试图使这些字母表的向量更接近或更远离语言的实际有意义的单词。

Note

CBOW 和 skip-gram 方法都使用局部统计来学习单词向量嵌入。有时,通过探索单词对的全局统计可以学习更好的表示,GloVe 和 FastText 方法利用了这一点。关于有关算法的进一步细节,可以分别参考以下论文:GloVe ( https://nlp.stanford.edu/pubs/glove.pdf )和 FastText ( https://arxiv.org/pdf/1607.04606.pdf )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2-14

Two-dimensional representation of the word vectors obtained after training the Wikipedia corpus using the CBOW model

后续步骤

本章介绍了在研究和工业领域中使用的单词表示模型。除了 word2vec,还可以探索 GloVe 和 FastText 作为单词嵌入的其他选项。我们尝试使用 CBOW 和 skip-gram 给出一个单词嵌入的可用方法的例子。在下一章中,我们将强调不同类型的可用神经网络,如 RNNs、LSTMs、Seq2Seq,以及它们对文本数据的用例。来自所有章节的知识将帮助读者执行任何结合深度学习和自然语言处理的项目的整个流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值