如何系统性地学习NLP 自然语言处理?
安装工具
如果大家已经安装nlpia包(https://github.com/totalgood/nlpia),就可以运行本书中的所有示例。我们会保持README文件中的安装说明为最新版本。但是,如果你已经安装了Python 3,而且觉得自己手气不错(或者幸运地拥有一个Linux环境)的话,那么可以尝试执行:
$ git clone https://github.com/totalgood/nlpia
$ pip3 install -e nlpia
如果上面的命令不起作用的话,那么可能需要在操作系统下安装一个软件包管理器和一些二进制软件包。我们将用3节分别介绍不同操作系统下的一些具体使用说明:
Ubuntu;
Mac;
Windows。
在这几节中会展示操作系统包管理器的安装方法。一旦安装了软件包管理器(或者使用一个像Ubuntu这样已经安装包管理器的对开发人员友好的操作系统),就可以安装Anaconda3。
A.1 Anaconda3
Python 3具有很多高性能和表达能力强的功能,非常适合NLP。在几乎所有系统上安装Python 3的最简单方法是安装Anaconda3。这样做的另一个好处是提供了一个包和环境管理器,可以在各种容易出问题的操作系统(如Windows)上安装很多容易出问题的包(如matplotlib)。
可以运行代码清单A-1的代码,以编程方式安装最新版本的Anaconda及其conda软件包管理器。
代码清单A-1 安装Anaconda3
$ OS=MacOSX # or Linux or Windows$ BITS=_64 # or '' for 32-bit$ 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
现在就可以创建一个虚拟环境:不是Python virtualenv,而是一个更完整的conda环境,它将所有Python的二进制依赖项与操作系统Python环境隔离开来。然后,可以在该conda环境中使用代码清单A-2安装NLPIA的依赖项和源代码。
A.2 安装nlpia
我们喜欢在用户目录$HOME下面的code/子目录下安装正在开发的软件源代码,但是读者也可以把它放在任何自己喜欢的地方。如果代码清单A-2中的代码不起作用,请查看nlpia的README文件,以获取更新的安装说明。
代码清单A-2 使用conda安装nlpia源码
$ mkdir -p ~/code
$ cd ~/code
$ git clone https://github.com/totalgood/nlpia
$ cd ~/code/nlpia
$ conda install -y pip ⇽--- 在根conda环境下为pip安装最新的conda二进制文件
$ pip install --upgrade pip ⇽--- 将pip更新为最新的pypi.python.org版本,这里终于pip安装了pip
$ conda env create -n nlpiaenv -f conda/environment.yml ⇽--- 创建一个conda环境,即一个'$HOME/Anaconda3/envs/nlpia'内的目录及二进制执行文件和源码之间的依赖关系
$ source activate nlpiaenv ⇽--- 激活Python环境
$ pip install --upgrade pip ⇽--- 在nlpiaenv环境下安装最新的pip
$ pip install -e . ⇽--- 为nlpia创建一个可编辑的源码目录,从而无论何时将编辑结果存储到磁盘,所有的源码及数据变化都会实时在线
A.3 集成开发环境
现在你的机器上有了Python 3和NLPIA,下面只需要一个优秀的文本编辑器来搭建我们的集成开发环境(IDE)。我们不安装JetBrains提供的像PyCharm这样的完整系统,而安装适合小规模团队开发使用的个人工具(如为单人团队所用的Sublime Text),后者可以把一件事做得很好。
提示 开发者为开发者开发工具确实存在,特别是当开发团队是一个人的团队时更是如此。个人开发者通常会开发出比公司开发者更好的工具,因为个人更愿意吸纳其用户的代码和建议。由于需要而开发一个工具的个人开发者一般是开发一个针对其工作流进行优化的工具。如果他们开发的工具可靠、功能强大且很受欢迎,那么他们的工作流会非常棒。从另一个角度说,像Jupyter这样的大型开源项目也很棒。只要它们没有使用开源项目的商业许可代码分支,它们一般就会非常通用并且功能齐全。
幸好,Python IDE所需的工具都是免费的、可扩展的,并且是持续维护的,大多数甚至是开源的,所以你可以把它们留作自用。
Sublime Text 3:带Package Control和Anaconda自动纠错检查的文本编辑器。
Meld:适用于Mac或其他操作系统的代码合并工具。
ipython(Jupyter控制台):用于阅读→评估→打印→循环(开发工作流)。
jupyter记事本:用于创建报告、教程和博客文章,或用于与老板分享结果。
提示 一些非常高效的开发人员使用Python的REPL工作流[1]。ipython、jupyter控制台和jupyter记事本等REPL控制台非常强大,同时它们还有help、?、??和%等神奇的命令,另外它们的属性、方法、参数、文件路径甚至是dict键都能借助tab健完成自动补全。在使用Google或Stack Overflow搜索之前,我们可以尝试使用像>>> sklearn.linear_model.BayesianRidge??这样的命令来探索所导入的Python包对应的代码文档和源代码。Python的REPL甚至允许我们在手指不离开键盘的情况下执行shell命令(尝试 >>> !git pull或>>> !find . -name nlpia),这样可以最大限度地减少上下文切换并最大限度地提高工作效率。
A.4 Ubuntu包管理器
Linux发行版已经安装了功能齐全的包管理器。如果使用Anaconda的软件包管理器conda,那么就像NLPIA安装说明中所建议的那样,可能根本用不着系统自带的那个包管理器。Ubuntu的包管理器叫作apt。在A.3节中我们已经建议安装了一些软件包。几乎可以肯定,你并不需要所有的这些软件包,但我们还是提供了一个详尽的工具代码清单,以防你在使用Anaconda安装软件时提示缺少二进制文件。我们可以从第一行开始向下执行,直到conda能够安装Python包为止。具体参见代码清单A-3。
代码清单A-3 使用apt安装开发工具
$ 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 libatlasbase- dev gfortran libgtk-3-dev$ sudo apt install -y openjdk-8-jdk python-dev python-numpy pythonpip python-virtualenv python-wheel python-nose$ sudo apt install -y python3-dev python3-wheel python3-numpy pythonscipy 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的构建工具。
A.5 Mac
在安装与其他开发人员保持一致所需的所有工具之前,需要一个真正的包管理器(不是XCode)。
A.5.1 一个Mac包管理器
Homebrew可能是最受开发人员欢迎的Mac系统命令行包管理器。它易于安装,并且包含开发人员使用的大部分工具的一键安装包。它相当于Ubuntu的apt包管理器。苹果公司本可以确保他们的操作系统能够与apt兼容,但他们不希望开发人员绕过他们的XCode和App Store的“渠道”,这显然是出于商业利益考虑。所以一些勇敢的Ruby开发人员开发了他们自己的包管理器。[2]它几乎和apt或任何其他操作系统自带的二进制包管理器一样好。具体参见代码清单A-4。
代码清单A-4 安装brew
$ /usr/bin/ruby -e '$(curl -fsSL https://raw.githubusercontent.com/Homebrew/
install/master/install)'
系统会要求按回车键确认,并输入root或者sudo密码。因此,在输入密码并且安装脚本开始顺利执行之前,不要离开去煮咖啡。
A.5.2 一些工具包
brew装好之后,可能还需要安装一些好用的Linux工具,如代码清单A-5所示。
代码清单A-5 安装开发工具
$ brew install wget htop tree pandoc asciidoctor
A.5.3 准备工作
如果你对NLP和软件开发非常认真,那么需要确保操作系统准备妥当以便能够胜任工作。下面是我们在Mac上创建新的用户账户时安装的内容。
Snappy:用于屏幕截图。
CopyClip:用于管理剪贴板。
如果想与其他NLP开发者分享屏幕截图,就需要一个屏幕截图软件,如Snappy。而剪贴板管理器(如CopyClip)则允许你一次复制和粘贴多项内容,并在不重启系统的情况下保留剪贴板历史记录。剪贴板管理器提供在图形用户界面执行复制和粘贴操作时搜索控制台历史([ctrl] - [R])的强大功能。
同时我们还应该增加bash shell的历史记录,添加一些更安全的rm -f别名,设置默认编辑器,创建彩色提示符文本,以及为浏览器、文本编辑器和代码合并工具添加open命令,具体如代码清单A-6所示。
代码清单A-6 bash_profile脚本
#!/usr/bin/env bash
echo 'Running customized ~/.bash_profile script: '$0' .......'
export HISTFILESIZE=10000000
export HISTSIZE=10000000
# append the history file after each session
shopt -s histappend
# allow failed commands to be re-edited with Ctrl-R
shopt -s histreedit
# command substitions are first presented to user before execution
shopt -s histverify
# store multiline commands in a single history entry
shopt -s cmdhist
# check the window size after each command and, if necessary, update the valu
es of LINES and COLUMNS
shopt -s checkwinsize
# grep results are colorized
export GREP_OPTIONS='--color=always'
# grep matches are bold purple (magenta)
export GREP_COLOR='1;35;40'
# record everything you ever do at the shell in a file that won't be unintent
ionally cleared or truncated by the OS
export PROMPT_COMMAND='echo '# cd $PWD' >> ~/
.bash_history_forever; '$PROMPT_COMMAND
export PROMPT_COMMAND='history -a; history -c; history -r; history 1 >> ~/
.bash_history_forever; $PROMPT_COMMAND'
# so it doesn't get changed again
readonly PROMPT_COMMAND
# USAGE: subl http://******.com # opens in a new tab
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'
# USAGE: subl file.py
if [ ! -f /usr/local/bin/subl ]; then
ln -s /Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl /
usr/local/bin/subl
fi
# USAGE: meld file1 file2 file3
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'
# you can use -
f to override these interactive nags for destructive disk writes
alias rm='rm -i'
alias mv='mv -i'
alias ..='cd ..'
alias ...='cd ../..'
可以使用GitHubGist搜索功能找到其他bash_profile脚本。
A.6 Windows
用于包管理的命令行工具(如Windows上的cygwin)并不是那么好。但如果在Windows机器上安装了GitGUI,那么会得到一个bash提示符和一个能运行Python REPL控制台的可用终端工具。
(1)下载并安装git安装程序。
(2)下载并安装GitHub Desktop。
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更改结果。[3]
一旦在Windows终端上运行了一个shell,就可以像我们在其他部分一样使用github仓库README中的说明,安装Anaconda并使用conda包管理器来安装nlpia包。
虚拟化
如果对Windows感到不满意,可以安装VirtualBox或者Docker,然后用Ubuntu操作系统创建一个虚拟机。这个主题需要用整本书(或至少一章)来介绍,在这个领域有比我们做得更好的人:
Jason Brownlee;
Jeroen Janssens;
Vik Paruchuri;
Jamie Hall。
在Windows世界中使用Linux的另一种方法是使用微软的Ubuntu shell应用程序。我没有用过,所以我无法保证它与你要安装的Python包的兼容性。如果你尝试使用,请在nlpia仓库中与我们分享你的知识,并在文档上发起一个新功能(feature)或拉取(pull)请求。Manning出版社网站上的本书论坛也是你分享知识和获取帮助的好地方。
A.7 NLPIA自动化
幸运的是,nlpia有一些自动环境配置程序,可以下载NLTK、Spacy、Word2vec模型以及本书所需的数据。只要调用的nlpia包装器函数(如segment_sentences())需要任何上述的数据集或模型,就会触发这些下载程序。但是,该软件还在开发中,并不断由像各位一样的读者进行维护和扩展。因此,当nlpia的自动化失败时,你可能想知道如何手动安装这些软件包并下载所需的数据,从而使它们能够正常工作。你也可能只对那些用于句子解析和词性标注的数据集感到好奇。因此,如果要自定义环境,后续的附录将展示如何安装和配置功能齐全的NLP开发环境所需的各个组件。
如上工作准备好可以来阅读《自然语言处理实战》这本书了,先来看一下这本的学习路线
说的是哪一本书?
自然语言处理实战 利用Python理解、分析和生成文本
你需要准备的数学知识:向量和矩阵(线性代数基础)
量和数字是计算机思考的语言。位是计算机处理的最基本的“数字”,有点儿像人类思考的语言,字母(字符)是词中最基本、不可切分的部分。所有数学运算都可以简化为位序列上的若干逻辑运算。当我们以类似的方式阅读时,人脑也是在处理字符序列。因此,如果想要教会计算机理解我们的词,那么第一个挑战就是提出计算机可用的,用于表示字符、词、句子和中间概念的向量,从而实现看似智能的行为。
向量
向量是一个有序的数字序列,没有任何“跳跃”。在scikit-learn和numpy中,向量是一个稠密的数组(array),它的使用方式很像Python的数字列表(list)。我们使用numpy数组而不是Python列表的主要原因是前者的速度快很多(是后者的100倍),并且内存占用更少(只有后者的1/4)。另外,我们可以使用向量运算,例如,可以将整个数组乘以一个值,而无须使用for循环遍历每个值。当有大量文本包含基于向量和数字表示的大量信息时,这一点非常重要。创建向量过程如代码清单C-1所示。
代码清单C-1 创建向量
>>> import numpy as np>>> np.array(range(4))array([0, 1, 2, 3])>>> np.arange(4)array([0, 1, 2, 3])>>> x = np.arange(0.5, 4, 1)>>> xarray([ 0.5, 1.5, 2.5, 3.5])>>> x[1] = 2>>> xarray([ 0.5, 2, 2.5, 3.5])>>> x.shape(4,)>>> x.T.shape(4,)
数组有一些列表没有的属性,例如.shape和.T。.shape属性包含向量维度的长度或大小(即其包含的对象个数)。当命名数组和向量(或者只是数字)变量时,我们使用小写字母,就像正式的数学符号一样。在线性代数、物理和工程学课本中,这些字母通常用粗体表示,有时在字母上方用箭头修饰(特别是使用黑板或白板的教授们)。
如果听说过矩阵,那么可能知道它可以被看成是一个行向量数组,如下:
>>> np.array([range(4), range(4)])
>>> array([[0, 1, 2, 3],
[0, 1, 2, 3]])
>>> X = np.array([range(4), range(4)])
>>> X.shape
(2, 4)
>>> X.T.shape
(4, 2)
T属性返回矩阵的转置矩阵。矩阵的转置矩阵是沿着左上角到右下角的假想对角线翻转后得到的矩阵。所以,给定下面一个矩阵A:
>>> A = np.array([[1, 2, 3], [4, 5, 6]])>>> Aarray([[1, 2, 3], [4, 5, 6]])
其对应的转置矩阵是:
>>> A.T
array([[1, 4],
[2, 5],
[3, 6]])
因此,如果A初始化为行向量的集合,那么A.T将会把这些行向量转换为列向量。
距离
两个向量之间的距离可以通过很多不同的方式来度量。两个向量的差还是向量,如代码清单C-2所示。
代码清单C-2 向量的差
>>> Aarray([[1, 2, 3], [4, 5, 6]])>>> A[0]array([1, 2, 3])>>> A[1]array([4, 5, 6])>>> np.diff(A, axis=0)array([[3, 3, 3]]>>> A[1] - A[0]array([3, 3, 3])
[3, 3, 3]向量确切地给出了两个向量在每个维度的距离。想象一下,假设上述两个向量分别代表两个人所在的曼哈顿街区和楼层:向量的差就是从其中一个位置到另一个位置需要行走的确切方向。如果你在第一街和第二道拐角公寓的3楼,那么你对应街、道、楼层的坐标就是[1, 2, 3],就和上例中一样。如果你的Python导师在第四街和第五道拐角公寓的6楼,那么她的坐标就是[4, 5, 6]。所以,这两个向量之间的差值([3, 3, 3])表示你需要向北走3个街区,向东走3个街区,然后向上爬3个楼层到达她的公寓。实际上,向量和数学并不关心像地心引力这种烦人的细节。因此,代数学假设你可以踏着窗户外面“回到未来”中的悬浮滑板上,在车流上方的3层楼高处快速行驶,然后到达线性代数导师的公寓。
如果你告诉导师她的公寓和你的公寓的距离是[3, 3, 3],她会嘲笑这个愚蠢的精确率。当谈论距离时,稍微聪明的人会将上述3个数字简化成一个数字,即一个标量。所以,如果你说她的位置有6个街区远,她就会明白你的意思,你忽略了不重要的楼层维度,因为这对你的悬浮滑板(或电梯)而言不值一提。除了忽略某些维度,你还使用了一种有时称为曼哈顿距离的巧妙的距离度量。后面,我们会展示如何计算300维词向量之间的曼哈顿距离,就像计算二维公寓位置向量一样容易。
1.欧几里得距离
当提到“像乌鸦飞行一样”时,我们说的就是二维向量的欧几里得距离(即欧式距离)。它是由向量定义的空间中两个点之间的直线距离(即向量的“尾部”或“头部”之间的长度)。
欧几里得距离也称为L2范数,因为它是两个向量差的长度。L2中的“L”代表长度。L2中的“2”表示在对这些值求和之前(且在求和的平方根之前)向量差的各个维度对应的指数(平方)。
欧几里得距离也称为RSS距离,其表示距离或差值平方和的平方根,即:
euclidean_distance = np.sqrt(((vector1 - vector2) ** 2).sum())
下面我们看一下在Patrick Winston的AI系列讲座中提到的一个NLP示例中的一些向量之间的欧几里得距离。[1]
假设有一个二维词频(词袋)向量,对应“hack”和“computer”在“Wired Magazine”和“Town and Country”两个杂志的文章中出现的次数。我们希望能够在研究某些内容时能够查询这些文章,从而找到有关特定主题的一些结果。查询字符串中包含“hacking”和“computers”两个词。对于词“hack”和“computer”,我们的查询字符串词向量是[1, 1],因为这是我们的查询经过分词和词干还原后的结果(见第2章)。
现在来看哪些文章与我们的查询在欧几里得距离上最接近。欧几里得距离是图C-1中4条线的长度。它们看起来非常接近,是不是?为了让搜索引擎针对此查询返回一些有用的文章,我们该如何解决这个问题?
图C-1 欧几里得距离的计算
我们可以计算词数相对于文档中词总数的比率,并基于该比率计算欧几里得距离。但是在第3章中我们已经学了更好的计算该比率的方法——TF-IDF。TF-IDF向量之间的欧几里得距离倾向于成为文档距离(逆相似性)的良好衡量标准。
如果要使用限定的欧几里得距离,我们可以将所有向量归一化为单位长度(每个向量长度为1)。这将确保所有向量之间的距离都在0到2之间。
2.余弦距离
另一种对距离计算的调整使我们的距离值更加有用。余弦距离是余弦相似度的取反结果(cosine_distance = 1 - cosine_similarity)。余弦相似度是两个向量之间夹角的余弦。因此,在上例中,查询字符串的TF向量与“Wired Magazine”文章的向量之间的夹角远小于该查询与“Town and Country”文章之间的夹角。这正是我们想要的结果。因为查询“hacking computers”应该为我们返回“Wired Magazine”杂志的文章,而不是关于骑马(“hacking”)[2]、打猎、晚宴和乡村风格的室内设计等娱乐活动的文章。
该距离可以通过计算两个归一化向量的点积来进行有效计算,归一化向量即每个向量均除以自己的长度,如代码清单C-3所示。
代码清单C-3 余弦距离
>>> 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)
>>> normalized_queryarray([ 0.70710678, 0.70710678])>>> normalized_tcarray([ 1., 0.])>>> normalized_wiredarray([ 0.6401844 , 0.76822128])
我们的查询TF向量与其他两个TF向量之间的余弦相似度(向量之间夹角的余弦)分别为:
>>> np.dot(normalized_query, normalized_tc) # cosine similarity
0.70710678118654746
>>> np.dot(normalized_query, normalized_wired) # cosine similarity
0.99589320646770374
我们的查询与这两个TF向量之间的余弦距离是1减去余弦相似度,即:
>>> 1 - np.dot(normalized_query, normalized_tc) # cosine distance0.29289321881345254>>> 1 - np.dot(normalized_query, normalized_wired) # cosine distance0.0041067935322962601
下面给出了余弦相似性用于计算NLP中TF向量相似度的原因:
计算简单(只需乘法和加法);
有一个方便的取值范围(−1到+1);
其取反(余弦距离)易于计算(1 − 余弦相似度);
其取反(余弦距离)有界(0到+2)。
然而,与欧几里得距离相比,余弦距离有一个缺点:它不是真正的距离度量,因为此时三角形不等式并不成立。[3]这意味着如果“red”词向量与“car”词向量的余弦距离为0.5,与“apple”词向量的余弦距离为0.3,则“apple”和“car”的距离可能远远超过0.8。当想用余弦距离来证明向量的一些性质时,三角不等式是很重要的。当然,在实际的NLP问题中很少会出现这种情况。
3.曼哈顿距离
曼哈顿距离也称为出租车距离或L1范数。之所以称为出租车距离,因为如果这些向量的坐标与街道网格对齐并且它们都是二维向量的话,那么该距离表示出租车从一个向量到达另一个向量需要行驶的距离。[4]这个距离也称为L1范数。
曼哈顿距离计算起来非常简单:计算所有维度的绝对距离的和。使用我们前面虚构的杂志向量,曼哈顿距离将是:
>>> vector_tc = np.array([1, 0])
>>> vector_wired = np.array([5, 6])
>>> np.abs(vector_tc - vector_wired).sum()
10
如果在计算曼哈顿距离之前对向量进行了归一化,则计算的距离会有很大差异:
>>> 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()1.128...
我们可能希望这个距离度量限定在一定的范围内,如0~2,但它并不会如此。与欧几里得距离一样,曼哈顿距离是一个真实度量,因此它遵从三角不等式,并且可以用于依赖真实距离度量的数学证明中。但是与归一化向量的欧几里得距离不同,我们不能指望归一化向量之间的曼哈顿距离保持在一个理想的范围内,如0~2。即使已经把向量全部归一化为长度为1的向量,曼哈顿距离的最大长度也会随着维数的增加而增长。对于归一化的二维向量,任意两个向量之间的最大曼哈顿距离约为2.82()。对于三维向量,这个值约为3.46()。大家能猜出或计算出四维向量所对应的值吗?