如果您曾安装过nlpia包(https://github.com/totalgood/nlpia),便可以运行本书中的所有示例。我们会在README文件中保持安装说明的最新状态。若您已安装Python 3,并且自信能应对技术挑战(或者恰好拥有Linux环境),可以尝试执行以下命令:
sh
git clone https://github.com/totalgood/nlpia
pip3 install -e nlpia
若上述命令无效,可能需要在操作系统中安装包管理器和一些二进制文件。我们将分别介绍不同操作系统中的具体步骤:
在这些章节中,我们会展示如何安装操作系统包管理器。一旦安装了包管理器(或使用如Ubuntu这样已经内置包管理器的开发友好型操作系统),您可以安装Anaconda3。
Python 3非常适合自然语言处理(NLP),因为它功能强大且表达能力强。在几乎所有系统上安装Python 3最简便的方法是安装Anaconda3。这种方式还能提供一个包和环境管理器,有助于在易出现问题的操作系统(如Windows)上安装复杂的包(如matplotlib)。
可以通过执行以下代码来安装最新版本的Anaconda及其conda包管理器:
sh
OS=MacOSX # 或 Linux 或 Windows
BITS=_64 # 或 '' 表示32位
curl https://repo.anaconda.com/archive/ > tmp.html
FILENAME=$(grep -o -E -e "Anaconda3-[.0-9]+-$OS-x86$BITS.(sh|exe)" tmp.html | head -n 1)
curl "https://repo.anaconda.com/archive/$FILENAME" > install_anaconda
chmod +x install_anaconda
./install_anaconda -b -p ~/Anaconda
export PATH="$HOME/Anaconda/bin:$PATH"
echo 'export PATH="$HOME/Anaconda/bin:$PATH"' >> ~/.bashrc
echo 'export PATH="$HOME/Anaconda/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile
rm install_anaconda
现在,您可以通过创建一个conda环境来安装nlpia的所有依赖项。这是一个完整的Python环境,可以隔离所有Python二进制文件和操作系统Python环境。
我们推荐在用户目录$HOME下的code/子目录安装开发中的软件源代码,但您也可以选择任何喜欢的位置。如果以下代码不起作用,请参考nlpia的README文件以获取最新的安装说明。
sh
mkdir -p ~/code
cd ~/code
git clone https://github.com/totalgood/nlpia
cd ~/code/nlpia
conda install -y pip # 在根conda环境下安装最新的conda二进制文件
pip install --upgrade pip # 将pip升级到最新的pypi.python.org版本
conda env create -n nlpiaenv -f conda/environment.yml # 创建一个conda环境
source activate nlpiaenv # 激活Python环境
pip install --upgrade pip # 在nlpiaenv环境下安装最新的pip
pip install -e . # 创建一个可编辑的源码目录,以便实时更新所有更改
现在,您的机器上已经有了Python 3和nlpia,接下来只需要一个优秀的文本编辑器来构建我们的集成开发环境(IDE)。我们不推荐安装像PyCharm这样的复杂系统,而是选择适合小型团队使用的工具,如Sublime Text,因为这些工具在单人团队中表现出色。
开发者常常会开发出比公司开发者更好的工具,因为他们愿意接受用户反馈和代码贡献。个人开发者倾向于优化工具以适应自己的工作流程,因此这些工具往往更加可靠和高效。像Jupyter这样的大型开源项目也非常棒,只要它们不使用商业许可的代码分支,就能广泛适用且功能齐全。
幸运的是,Python IDE所需的工具都是免费的、可扩展的,并且持续维护。大多数甚至都是开源的,因此您可以自由使用它们。
一些高效的开发者使用Python的REPL工作流。IPython、Jupyter控制台和Jupyter笔记本等REPL控制台功能强大,支持help、?、??和%等魔法命令。这些命令可以快速查找导入的Python包的代码文档和源代码。此外,您可以在不离开键盘的情况下执行shell命令,从而最大限度地减少上下文切换并提高效率。
Linux发行版通常已经安装了功能完备的包管理器。如果使用Anaconda的软件包管理器conda,那么可能很少需要用到系统自带的包管理器。Ubuntu的包管理器称为apt。在之前的章节中,我们已经建议安装了一些软件包。几乎可以肯定,您并不需要所有这些软件包,但这里提供了一个详细的工具清单,以防在使用Anaconda安装软件时提示缺少二进制文件。您可以从第一行开始逐行执行,直到conda可以安装Python包为止。详细请参见代码清单A-3。
sh
sudo apt-get update
sudo apt install -y build-essential libssl-dev g++ cmake swig git
sudo apt install -y python2.7-dev python3.5-dev libopenblas-dev libatlas-base-dev gfortran libgtk-3-dev
sudo apt install -y openjdk-8-jdk python-dev python-numpy python-pip python-virtualenv python-wheel python-nose
sudo apt install -y python3-dev python3-wheel python3-numpy python-scipy python-dev python-pip python3-six python3-pip
sudo apt install -y python3-pyaudio python-pyaudio
sudo apt install -y libcurl3-dev libcupti-dev xauth x11-apps python-qt4
sudo apt install -y python-opencv-dev libxvidcore-dev libx264-dev libjpeg8-dev libtiff5-dev libjasper-dev libpng12-dev
如果apt-get update命令失败并出现关于bazel的错误,则可能需要添加谷歌的apt仓库以及用于TensorFlow的构建工具。
在安装与其他开发者保持一致所需的所有工具之前,您需要一个真正的包管理器(而不是XCode)。
Homebrew是Mac系统中流行的命令行包管理器,易于安装,并包含许多开发人员常用工具的一键安装包。它相当于Ubuntu的apt包管理器。苹果公司本可以确保他们的操作系统与apt兼容,但他们不希望开发人员绕过他们的XCode和App Store的“渠道”,这显然是出于商业利益考虑。因此,一些勇敢的Ruby开发人员开发了自己的包管理器。它几乎和apt或其他操作系统自带的二进制包管理器一样好。详细请参见代码清单A-4。
sh
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
安装完成后,可能还需要安装一些常用的Linux工具,如代码清单A-5所示。
sh
brew install wget htop tree pandoc asciidoctor
如果您对NLP和软件开发非常认真,需要确保操作系统准备妥当以便胜任工作。下面是我们在Mac上创建新用户账户时安装的内容:
如果您想与其他NLP开发者分享屏幕截图,需要一个屏幕截图软件,如Snappy。剪贴板管理器(如CopyClip)可以让您一次复制和粘贴多项内容,并在不重启系统的情况下保留剪贴板历史记录。
我们还应该添加bash shell的历史记录,添加一些更安全的rm -f别名,设置默认编辑器,创建彩色提示符文本,以及为浏览器、文本编辑器和代码合并工具添加open命令,如代码清单A-6所示。
```sh
echo "Running customized ~/.bash_profile script: '$0' ......." export HISTFILESIZE=10000000 export HISTSIZE=10000000
shopt -s histappend
shopt -s histreedit
shopt -s histverify
shopt -s cmdhist
shopt -s checkwinsize
export GREP_OPTIONS='--color=always'
export GREP_COLOR='1;35;40'
export PROMPTCOMMAND='echo "# cd $PWD" >> ~/bashhistoryforever; '$PROMPTCOMMAND
readonly PROMPT_COMMAND
if [ ! -f /usr/local/bin/firefox ]; then ln -s /Applications/Firefox.app/Contents/MacOS/firefox /usr/local/bin/firefox fi alias firefox='open -a Firefox'
if [ ! -f /usr/local/bin/subl ]; then ln -s /Applications/Sublime Text.app/Contents/SharedSupport/bin/subl /usr/local/bin/subl fi
if [ ! -f /usr/local/bin/meld ]; then ln -s /Applications/Meld.app/Contents/MacOS/Meld /usr/local/bin/meld fi export VISUAL='subl -w' export EDITOR="$VISUAL"
alias rm="rm -i" alias mv="mv -i" alias ..="cd .." alias ...="cd ../.." ```
用于包管理的命令行工具(如Windows上的cygwin)并不是那么好用。但如果在Windows机器上安装了GitGUI,可以得到一个bash提示符和一个可以运行Python REPL控制台的可用终端工具。
git安装程序附带了一个版本的bash shell,应该可以在Windows中很好地工作,但它安装的git-gui对用户不是非常友好,特别是对于初学者。除非在命令行(Windows下的bash shell)里使用git,否则在Windows下所有的git push/pull/merge需求都应该通过GitHub Desktop来完成。在本书的整个编辑过程中,我们遇到了一些问题:当出现版本冲突时,git-gui会执行一些不可预测的操作,这些操作会覆盖他人的提交内容,即使在不涉及冲突的文件中也是如此。这就是我们建议在原始git和git-bash之上安装GitHub Desktop的原因。GitHub Desktop提供了对用户更加友好的git体验,让您知道何时需要pull或push或merge更改结果。
一旦在Windows终端上运行了一个shell,就可以像我们在其他部分一样使用GitHub仓库README中的说明,安装Anaconda并使用conda包管理器来安装nlpia包。
如果您对Windows感到不满意,可以安装VirtualBox或Docker,然后用Ubuntu操作系统创建一个虚拟机。这个主题需要一整本书(或至少一章)来详细介绍,在这个领域有比我们做得更好的人:
在Windows世界中使用Linux的另一种方法是使用微软的Ubuntu shell应用程序。我没有用过,所以我无法保证它与你要安装的Python包的兼容性。如果你尝试使用,请在nlpia仓库中与我们分享你的知识,并在文档上发起一个新的功能请求或拉取请求。Manning出版社网站上的本书论坛也是你分享知识和获取帮助的好地方。
幸运的是,nlpia有一些自动环境配置程序,可以下载NLTK、Spacy、Word2vec模型以及本书所需的数据。只需调用的nlpia包装器函数(如segment_sentences())需要上述数据集或模型时,就会触发这些下载程序。但是,该软件还在开发中,并不断由像您这样的读者维护和扩展。因此,当nlpia的自动化失败时,您可能想知道如何手动安装这些软件包并下载所需的数据,从而使它们可以正常工作。您也可能只对那些用于句子解析和词性标注的数据集感到好奇。因此,如果要自定义环境,后续的附录将展示如何安装和配置功能完善的NLP开发环境所需的各个组件。
现在您已经准备好阅读《自然语言处理实战》这本书了。让我们先来看看这本书的学习路径。
自然语言处理实战:应用Python了解、分析和生成文本
向量和矩阵(线性代数基础)
计算机处理的基本“数字”是位,类似于人类思考的语言。字母(字符)是词中最基本、不可分割的部分。所有数学运算都可以简化为位序列上的逻辑运算。当我们阅读时,人脑也是在处理字符序列。因此,如果要教会计算机理解我们的词,首先要解决的挑战是提出一种计算机可以使用的表示方法,用于表示字符、词、句子和中间概念的向量,从而实现看似智能的行为。
向量是一个有序的数字序列,没有任何“跳跃”。在scikit-learn和numpy中,向量是一个密集数组(array),使用方式类似于Python的数字列表(list)。我们使用numpy数组而不是Python列表的主要原因是前者速度快得多(比后者快100倍),并且占用的内存更少(只有后者的1/4)。此外,我们可以使用向量运算,例如,可以将整个数组乘以一个值,而不需要使用for循环遍历每个值。当有大量文本包含基于向量和数字表示的大量信息时,这一点非常重要。
创建向量的过程如下:
python
import numpy as np
np.array(range(4))
np.arange(4)
x = np.arange(0.5, 4, 1)
x
x[1] = 2
x
x.shape
x.T.shape
数组有一些列表没有的属性,例如 .shape 和 .T。.shape 属性包含向量维度的长度或大小(即其包含的对象个数)。当我们命名数组和向量(或只是数字)变量时,我们使用小写字母,就像正式的数学符号一样。在线性代数、物理和工程学课本中,这些字母通常用粗体表示,有时在字母上方用箭头修饰(特别是在使用黑板或白板的教授们)。
如果您听说过矩阵,可以将其视为行向量的数组,如下所示:
python
np.array([range(4), range(4)])
X = np.array([range(4), range(4)])
X.shape
X.T.shape
T 属性返回矩阵的 转置。矩阵的转置是沿左上角到右下角的假想对角线翻转后的矩阵。因此,给定一个矩阵A:
python
A = np.array([[1, 2, 3], [4, 5, 6]])
A
A.T
因此,如果A初始化为行向量的集合,那么A.T将会把这些行向量转换为列向量。
两个向量之间的距离可以通过多种方式度量。两个向量的差仍然是向量。例如:
python
A = np.array([[1, 2, 3], [4, 5, 6]])
A[0]
A[1]
np.diff(A, axis=0)
A[1] - A[0]
向量 [3, 3, 3] 确切地给出了两个向量在每个维度的差异。想象一下,如果上述两个向量分别代表两个人所在的曼哈顿街区和楼层:向量的差就是从一个位置到另一个位置的确切方向。如果一个人住在第一街和第二大道的三楼,那么他的坐标就是 [1, 2, 3],与上面的例子相同。如果他的Python导师住在第四街和第五大道的六楼,那么她的坐标就是 [4, 5, 6]。因此,这两个向量之间的差值 [3, 3, 3] 表示你需要向北走三个街区,向东走三个街区,然后向上爬三个楼层到达她的公寓。实际上,向量和数学并不关心像地心引力这样的烦人细节。因此,代数学假设你可以踏着窗户外面“回到未来”中的悬浮滑板,在车流上方的三层楼高处迅速行驶,然后到达线性代数导师的公寓。
如果你告诉导师她的公寓和你的公寓的距离是 [3, 3, 3],她会嘲笑这个愚蠢的精确度。当你谈论距离时,稍微聪明的人会将上述三个数字简化成一个数字,即一个标量。因此,如果你说她的位置有六个街区远,她就会明白你的意思,你忽略了不重要的楼层维度,因为这对你的悬浮滑板(或电梯)来说不值一提。除了忽略某些维度外,你还使用了一种称为曼哈顿距离的巧妙的距离度量。后面我们将展示如何计算300维词向量之间的曼哈顿距离,就像计算二维公寓位置向量一样容易。
当提到“像鸟儿一样飞翔”时,我们说的就是二维向量的欧几里得距离(即欧式距离)。它是空间中两点之间的直线距离(即向量的“尾部”或“头部”之间的长度)。
欧几里得距离也称为L2范数,因为它是两个向量差的长度。L2中的“L”代表长度。L2中的“2”表示在对这些值求和之前(且在求和的平方根之前)向量差的各个维度对应的指数(平方)。
欧几里得距离也称为RSS距离,其表示距离或差值平方和的平方根,即:
python
euclidean_distance = np.sqrt(((vector1 - vector2) ** 2).sum())
我们来看一个Patrick Winston的AI系列讲座中提到的NLP示例中一些向量之间的欧几里得距离。假设有一个二维词频(词袋)向量,对应“hack”和“computer”在“Wired Magazine”和“Town and Country”两篇文章中的出现次数。我们希望在研究某些内容时能够查询这些文章,从而找到关于特定主题的一些结果。查询字符串中包含“hacking”和“computers”两个词。对于词“hack”和“computer”,我们的查询字符串词向量是 [1, 1],因为这是经过分词和词干提取后的结果(见第2章)。
现在来看哪些文章与我们的查询在欧几里得距离上最接近。欧几里得距离是图C-1中4条线的长度。它们看起来非常接近,不是吗?为了让搜索引擎针对此查询返回一些有用的文章,我们应该如何处理这个问题?

我们可以计算词数相对于文档中词总数的比率,并基于该比率计算欧几里得距离。但在第3章中我们已经学习了一种更好的计算该比率的方法——TF-IDF。TF-IDF向量之间的欧几里得距离倾向于成为文档距离(逆相似性)的良好衡量标准。
如果要使用受限的欧几里得距离,我们可以将所有向量归一化为单位长度(每个向量长度为1)。这将确保所有向量之间的距离都在0到2之间。
另一种调整距离计算的方法可以使我们的距离值更加有用。余弦距离是余弦相似度的反面(cosine_distance = 1 - cosine_similarity)。余弦相似度是两个向量之间夹角的余弦。因此,在上述示例中,查询字符串的TF向量与“Wired Magazine”文章的向量之间的夹角远小于该查询与“Town and Country”文章之间的夹角。这正是我们想要的结果。因为查询“hacking computers”应该为我们返回“Wired Magazine”杂志的文章,而不是关于骑马(“hacking”)、打猎、晚宴和乡村风格的室内设计等娱乐活动的文章。
该距离可以通过计算两个归一化向量的点积来有效计算,归一化向量即将每个向量除以其自身的长度。例如:
python
import numpy as np
vector_query = np.array([1, 1])
vector_tc = np.array([1, 0])
vector_wired = np.array([5, 6])
normalized_query = vector_query / np.linalg.norm(vector_query)
normalized_tc = vector_tc / np.linalg.norm(vector_tc)
normalized_wired = vector_wired / np.linalg.norm(vector_wired)
np.dot(normalized_query, normalized_tc) # 余弦相似度
np.dot(normalized_query, normalized_wired) # 余弦相似度
1 - np.dot(normalized_query, normalized_tc) # 余弦距离
1 - np.dot(normalized_query, normalized_wired) # 余弦距离
下面列出了余弦相似度用于计算NLP中TF向量相似度的原因:
然而,与欧几里得距离相比,余弦距离有一个缺点:它不是一个真正的距离度量,因为此时三角不等式并不成立。这意味着,如果“red”词向量与“car”词向量的余弦距离为0.5,与“apple”词向量的余弦距离为0.3,则“apple”和“car”的距离可能远远超过0.8。当想用余弦距离来证明向量的一些性质时,三角不等式很重要。当然,在实际的NLP任务中很少会出现这种情况。
曼哈顿距离也称为出租车距离或L1范数。之所以称为出租车距离,是因为如果这些向量的坐标与街道网格对齐并且它们都是二维向量的话,该距离表示出租车从一个向量到达另一个向量所需的行驶距离。这个距离也称为L1范数。
曼哈顿距离计算起来非常简单:计算所有维度的相对距离之和。使用我们后面的虚拟杂志向量,曼哈顿距离将是:
python
vector_tc = np.array([1, 0])
vector_wired = np.array([5, 6])
np.abs(vector_tc - vector_wired).sum()
如果在计算曼哈顿距离之前对向量进行了归一化,则计算的数值会有很大差异:
python
normalized_tc = vector_tc / np.linalg.norm(vector_tc)
normalized_wired = vector_wired / np.linalg.norm(vector_wired)
np.abs(normalized_tc - normalized_wired).sum()
我们可能会希望这个距离度量限定在一定的范围内,如0到2,但它并不会如此。与欧几里得距离一样,曼哈顿距离是一个真正的距离度量,因此它服从三角不等式,并且可以用于依赖真实距离度量的数学证明中。但是与归一化向量的欧几里得距离不同,我们不能期望归一化向量之间的曼哈顿距离保持在一个理想的范围内,如0到2。即使已经把向量全部归一化为长度为1的向量,曼哈顿距离的最大长度也会随着维数的增加而增长。对于归一化的二维向量,任意两个向量之间的最大曼哈顿距离约为2.82()。对于三维向量,这个值约为3.46()。大家能猜出或计算出四维向量所对应的值吗?