基于依存关系的自然语言可视化仿真系统

2021-09-28 10:17袁雨轩陈科淇
计算机技术与发展 2021年9期
关键词:复句例句向量

袁雨轩,李 放,陈科淇,韩 正

(1.大连东软信息学院 数字艺术与设计学院,辽宁 大连 116023;2.华威大学 华威商学院,考文垂 CV4 7AL)

0 引 言

自然语言处理技术是在程序设计中常用的人工智能算法,也是人工智能的热点研究方向。主要应用场景包括个性化推荐、搜索纠错等。神经网络是自然语言处理以及人工智能领域最重要的方法之一。为了让计算机理解自然世界的知识,可以根据具体研究对象的特征构建不同的神经网络结构,如适用于图像处理的卷积神经网络(convolutional neural networks,CNN)、适用于文本和序列数据的递归神经网络(recurrent neural networks,RNN)和长短期记忆网络(long short-term memory neural networks,LSTM)等[1-2]。尽管这些网络结构在处理不同的任务时表现出不同的性能,对于具体任务仍很难确定使用哪种网络结构[3],所使用模型的训练过程同样需要耗费大量时间。为了覆盖更多的语言场景,本系统依赖百度AI开放平台的自然语言处理技术接口。百度AI开放平台提供了全面的语言处理基础能力和语言处理应用技术,并以标准化的接口封装。且该平台的训练语料以中文为主,对中文输入的接受能力较强。

对自然语言处理技术来说,其最细的划分粒度是词语。而在计算机仿真领域中可以使用三维模型、图片等来模拟名词,使用动画、声音等交互系统模拟动词。由于中文的能指极其复杂,在自然语言处理技术难以解决的词语多义性问题上更是雪上加霜。文献[4]提出了中文句式的三个层级:(1)表层:形式结构,语言中语法项的线性配列式。如主语+谓语构成SV句式等。(2)中介层:表现法,语言中线性配列的特定样式。如抒情、描写手法等。(3)深层思维方式:反映说话者心理的主体意识。并提出深层思维方式的表达注重于主观感受的抒发,即为了满足句子表层结构的语法在表达中的正确性,其深层思维方式可以不严格满足语法规则。由于深层思维方式不执著于逻辑和形式结构规范,其表达与所指的联系也更紧密。

基于以上分析,文中将深入讨论句子中不同实体之间的依存关系,排除中介层和表层结构中的修饰成分,从而得到易于计算机理解句子深层逻辑的输入。并提出了一种可视化的虚拟仿真解决方案,用实体所指代的对象代替中文复杂的能指,并以视频的形式可视化呈现。

1 系统开发中的关键技术

1.1 中文依存关系的研究

聚类(cluster)是一组数据对象的集合,在文中对应着人们使用自然语言输入、不利于计算机理解的句子。文献[5]在引用中总结到,聚类的基本划分方法存在需要事先给出样例的缺陷。而人工定义的语法规则也是难以穷举、无法事先给出的。同时,中文的依存句法分析区别于英文,需要对句子进行分词[6]。对过于复杂而无法进行预处理的数据可以考虑使用基于深度学习算法的模型。百度AI依存句法分析接口提供了使用百度日常搜索内容做训练数据的Query模型,该模型擅长对口语Query句式的处理。以及来源于全网网页数据的Web模型,该模型偏向表达规整的书面语法。以上模型可以解析出共34种依存关系。对此可以根据集合学习的思想,构建多个对不同数据的处理效果有明显偏好的模型,整合出一个更强大的模型做最后决策。

为了研究解析出的不同依存关系对句子深层逻辑的影响程度,实验1.2将使用百度AI的短文本相似度接口,采用CNN模型验证去除不同依存关系后对句子结构的影响,相比于该接口提供的词袋模型(bag-of-words model,BOW)和RNN模型,CNN模型的显著特点是对序列输入敏感,但是语义泛化能力较弱。本实验更关注实体之间的逻辑,在对比实验的过程中仅去掉某一个依存关系对应的实体,因此对词语泛化能力的要求较低,且CNN模型对句子保留的局部特征更敏感,因此选择CNN模型可以在实验中获得更理想的结果。

1.2 实 验

中文的语言特点决定了多数的语言场景需要以复句的形式完成。处理复句的难点在于各个分句间语义关系的准确识别。文献[7]提出了一种基于句内注意力机制的多路CNN网络结构,可以识别分句间的关联特征,以及正确识别复句的逻辑关系。文中虚拟仿真工作产生的效果主要来自各分句中的名词模型,其中的逻辑关系由该分句中的谓语提供,并且中文的表达习惯可以保证句子输入的语序与视频播放的时顺相对应,因此各分句之间可独立看待。为了研究使用自然语言输入的单个句子中词语之间的依存关系,以“袁明吃蛋挞”为例,表1将展示该例句在去掉不同依存关系所对应的词语后与原句子的短文本相似度,该数值介于[0,1]之间,反映了被去掉的依存关系对句子逻辑的影响程度。首先排除由复句产生,或无实际意义的虚词产生的依存关系,如标点符号、关联词等共计17种。

表1 语义相似度对比实验

1.2.1 讨 论

相似度是一个十分复杂的概念[8]。根据实验序号2可知,两个完全相同的句子相似度为1,序号20和21是两个与原文无关的句子,其相似度在本实验中的样例均小于0.3。序号19介宾关系是另一个相似度接近0.3的样本,原因是丢失掉宾语后与原句本身差异过大。对以上结果可暂时作如下假设:短文本相似度接近0.3甚至更小的句子与原文的出入较大,故应取大于相似度0.3的结果做讨论。

文献[9]在文本相似度计算中提到了句子的关键成分与修饰成分的概念,而人们往往从关键成分就可以了解一个句子的大概意思。在介于(0.9,1)区间内存在十种依存关系,除核心关系外,其他的依存关系均是对句子逻辑影响较小的结构成分或修饰成分。以上两种成分的存在保证了句子表层语法规则的正确。以“地”字结构为例,其语法解析树如图1所示。该依存关系仅修饰动词“吃”。去掉修饰成分对句子要表达的深层逻辑影响很小,此句子的动画依然可以由“袁明吃蛋挞”来表示。对此结果不妨假设在(0.9,1)区间内的修饰成分在生成动画时可以忽略。

图1 例句“袁明疯狂地吃蛋挞”

文献[10]中的例句验证了,在以中文复句输入所返回的语法生成树中,关系词都是动词。关系词在复句中常作为分句的核心节点出现,因此本实验的单句中核心节点的作用类同于复句中的关系词。

对于图2所示的核心关系需要特别讨论,因为动词“吃”是SVO句式的关键成分而不是修饰成分,显然与上文的假设相矛盾。这是因为本实验使用的CNN模型虽然对局部特征敏感,但是“袁明”和“蛋挞”之间没有任何成分解释两个词之间的逻辑关系,于是该模型默认把“袁明”作为修饰成分,原句的意思被泛化成图3描述的“袁明牌蛋挞”了。CNN模型在卷积层内部状况为词向量的拼接。词向量是计算机理解词汇的媒介,这种表示方式是将词语映射到一个高维且稠密的向量空间中,用空间距离的形式反映两个词之间的系。文献[11]提到,将词语在计算机中向量化表示的常用方法有one-hot、词向量和Word2vec模型等。虽然百度AI平台没有描述短文本相似度接口使用的CNN模型中的词向量在卷积层内部的具体情况,但该平台语言处理基础能力的SDK中也提供了词向量表示接口。词向量模型训练过程的初始状态下,对语料库内包含的所有词向量随机赋初值,按照词语在样本句子中的位置相应地调整词向量在空间中的位置,以“袁明吃蛋挞”为例句训练词向量,该句子由人名+动作+宾语组成。如图4所示,当再次输入“韩明”和“蛋挞”时,“吃”字在空间中的优先级会小于其他的动词如“买”。输入“李明”和“吃”之后“蛋挞”的优先级同样会高于其他名词。类似的,使用训练集中的句子不断调整词向量的位置,多次训练后的词向量可以作为特征值用于在计算机中表示该词语。

图2 例句“袁明吃蛋挞”

图3 例句“袁明蛋挞”

图4 例句“袁明吃蛋挞”的词向量训练

基于以上结论,对例句词语分别调用百度AI的词向量表示接口,返回类型为1024维空间大小的词向量,其中对“袁明”的返回值为[-0.074 207 4,-0.458 546,-0.010 252 2,0.010 083 9,…],对“吃”的返回值为[0.015 029 4,1.488 3,-0.482 325,-0.740 492,…],对“蛋挞”的返回值为[0.015 029 4,1.488 3,-0.482 325,-0.740 492,…]。CNN模型检测文本相似度方法的内部情况是将以上三个词向量拼接成矩阵表示,在池化层完成对数据降维的工作[12]。文献[13]详细描述了目前主流的池化方法:

(1)平均池,选择池窗口中的平均值表示池区信息。

(2)最大池,选择池窗口中的最大值,该方法可以获得池区的最优值,也是最常用的方法。

(3)K-max池,选择池窗口中的最大K值,该方法是最大池方法的扩展,可以更好地保持文本特征。

(4)多池,该方法是针对文本任务的特点,获得文本的一维向量表示,并在一维向量上使用其他池化方法分段。

该方法的优点是能保留一些词序信息,可以视为最大池的一种变体。本项目对于文本相似度接口返回的数值为离散数据,且难以描述实际意义,可以考虑使用聚类方法处理。参考多池的分段方法,研究返回的三个词向量之间的关系可以通过计算欧氏距离表示词向量在二维空间下的位置关系,其中D为绝对距离,X,Y为实体,x,y为特征值,计算方法如下:

最终计算“袁明”到“蛋挞”的距离为17.433 217 41,句子“袁明吃蛋挞”的距离之和为39.262 644 02。另一个例子,“袁明跳蛋挞”是一句显然不符合表达习惯的句子,此句子与“袁明蛋挞”的相似度为0.767 38,且符合上一节中介值于(0.3,0.9)之间的假设,这个句子的距离之和为46.659 304 67。将以上距离关系映射到二维空间下的表示如图5所示,由于词向量的特征值仅代表在向量空间下的绝对位置,因此该坐标系下的单位仅作运算,无实际意义。

图5 词向量在二维空间下的映射

由数据降维后的结果可见,“吃”字对卷积运算的结果影响较小,以上结果是因为CNN模型运算词向量时对特征敏感而本例句过于简单所导致,所以为了保持句子的完整结构,核心关系应作为特例保留。

1.2.2 结 论

基于上文的实验,文本相似度处于(0.3,0.9)区间的依存关系有主谓关系、介宾关系和动宾关系以及做特例的核心四种依存关系作为句子的关键成分,相似度同样处于(0.3,0.9)区间的处所关系、并列关系、数量关系和“被”字结构是对句子逻辑影响比较大的修饰成分或结构成分。利用已经确定好的八种重要的依存关系,可以重新组合出更接近句子深层含义的SVO句式。在不改变句子本身逻辑的前提下,去掉尽可能多无需以动画展示的修饰成分,对第二节中生成动画的工作显然是更友好的。

2 系统的设计与实现

2.1 系统架构设计

基于依存关系的自然语言可视化仿真系统使用C/S分离式架构设计,其时序图如图6所示。使用.net框架中的TCP协议通信,分离式架构的优点是可以方便本系统在应用层面开发时的扩展[14],例如将模型资源保存在服务器上的数据库中以减少客户端资源的使用,以及在不修改客户端的条件下更新数据。本系统的核心问题是对以上依存关系合理性的验证,故暂时不考虑在架构上做性能优化。

图6 UML序列图

2.2 服务器的系统开发

服务器的开发需要调用百度AI开放平台的自然语言处理接口。使用前需要在官方网站申请API_KEY、SECRET_KEY和APPID并下载C#平台的SDK,在Visual Studio中新建项目,添加对SDK中类库的引用,并创建InputHandle类和NetWork类。调用接口时的请求文本应采用GBK编码,而此编码在.NET Core平台上不可使用。在程序启动时注册RegisterProvider能提供平台不可使用的编码格式,具体代码如下:

System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

System.Text.Encoding.GetEncoding("GB2312");

2.2.1 InputHandle类

InputHandle类包含了调用自然语言处理接口和声明Json解析的方法。首先,将输入的句子传入到依存句法分析方法中,使用正则表达式解析返回的Json结果,并放入到如下格式的结构体数组中。

public struct Word{

public int head;//头节点

public string word;//词

public int id;//节点编号

public string deprel;//关系

};

根据此结构体数组,可以获得一棵有序的语法解析树,使用Equals()方法判断结构体中deprel的取值。关键代码如下:

string Subject,Verb,Object;

foreach(Word w in Words) {

if (Equals(w.deprel,"SBV") Subject =w .word;

if (Equals(w.deprel,"HED") Verb = w.word;

if (Equals(w.deprel,"QUN") Verb = w.word;

if (Equals(w.deprel,"BEI") Verb = w.word;

if (Equals(w.deprel,"COO") Object = w.word;

if (Equals(w.deprel,"VOB") Object = w.word;

if (Equals(w.deprel,"POB") Object = w.word;

if (Equals(w.deprel,"LOC") Object = w.word;

}

将以上代码赋值后的Subject、Verb、Object变量有序地存入字符串数组,此方法封装到DepPraseHandle类中,并将字符串数组作为参数传递给NetWork类。

2.2.2 NetWork类

对于服务器和客户端之间的通信问题,本系统采用.net基于套接字为TCP协议扩展的TcpClient类和TcpListener类共同实现。TcpListener类可以侦听来自TCP网络客户端的连接。由于TCP协议是以稳定字节流的形式将数据在网络间传输[15],TcpClient类中的GetStream()方法可以返回用于发送和接收的网络流。对于字节类型可以使用BinaryWriter类和BinaryReader类共同实现将基元类型读取和写入的二进制值的操作,以上提及的两个类位于命名空间System.IO下。创建ReceiveMessage()方法,包含使用BinaryReader读取到网络流中客户端发送的句子,并作为参数按上文所述的顺序调用InputHandle类中的方法,最终根据返回的消息找到模型在服务器上保存的位置,并使用FileStream类的方法写入流数据,关键代码如下:

public byte[] FileReader(string path)

{

FileStream fileStream = File.Open(path, FileMode.Open);

Console.WriteLine(path);

byte[] array = new byte[fileStream.Length];

fileStream.Read(array, 0, array.Length);

fileStream.Close();

return array;

}

创建SendMessage()方法,包含将返回的字节数组使用BinaryWriter.Write()方法写入到网络流NetWorkStream中。其中NetWorkStream位于命名空间System.Net.Sockets下。将上述方法封装于NetWork类,最终的服务器UML类图如图7所示。

图7 服务器UML类图

2.3 客户端的系统开发

2.3.1 命名实体处理

在Unity3D中将每个实体模型制作为GameObject类型的预制体,并以实体的含义命名后保存在数据库中。

(1)名词类。

由于需要表示的动画不仅是由模型组成,在Unity3D中实现名词还可以意味着动画、天空盒子、粒子系统、声音、视频等功能,故需要根据词语创建模型,并对非模型类的命名实体挂载一个继承自MonoBehaviour类的脚本,并在Start()方法中调用不同组件的Play()方法,以便在生命周期开始时执行。

(2)动词类。

动词类在中文的单句中作为语法树的根节点或度大于一的节点控制前后名词类的逻辑,不仅需要访问名词类,还要根据动词本身的意思让名词类执行不同的操作如的开始、结束、改变位置、复制多个等,在动词类中创建FunSubject()、FunVerb()、FunObject()三个方法,分别对应着与名词的主动关系、动作和被动关系。通常情况下,数量关系、处所关系等修饰成分充当谓语成分时对应着FunObject()方法的实体,表示宾语被动产生的动作。而核心HED往往由动词产生,对应着FunSubject()方法,表示主动发起的动作。以FunSubject()方法为例,获取名词所对应命名实体的代码如下:

public Inventory Bag;

public GameObject Mod;

public string Subject;

foreach (GameObject b in Bag. itemList){

if (Equals(b.name,Subject) == true)Mod = b;

}

获取到模型后,首先要分析该动词的含义,在FunVerb()方法中以低代码化的方式添加简单的代码片段。例如下面的代码为动词“害怕”添加了音频并向后移动了主语模型的位置。

public void FunVerb(){

Instantiate(audioSource);

Mod.transform.position += new Vector3(0, 0, -1);

}

2.3.2 背包系统实现

根据系统架构设计,客户端需要接收服务器发送的字节数组。并按照字节数组中的顺序在客户端写入文件并序列化地生成模型,可以考虑使用GameObject类型的泛型列表保存模型,这种方法的优点是易于根据名称随机读取模型。

创建背包的方法如下:

public class Inventory : ScriptableObject{

ListitemBag = new List();

}

2.3.3 控制器设计

首先创建Receive()方法接收到服务器以字节流格式发送的命名实体,其关键代码如下:

publicstring Receive()

{

byte[] recvBuf = new byte[102400];

networkStream.ReadTimeout = 2000;

int bytesRead = networkStream.Read(recvBuf, 0, 102400);

if (bytesRead > 0)

string message = Encoding.UTF8.GetString(recvBuf);

return message;

}

创建FileWrite()方法,用于将字节数组写入文件。在写入时需要注意判断服务器写入文件的顺序,可以使用标签或分批发送的方式区分好不同实体,下面以动词为例,写入方法如下:

public void FileWrite(string message)

{

int a = message.IndexOf("%YAML 1.1");

//在a下标的100位之后继续寻找下一个文件的报头

int b = message.IndexOf("%YAML 1.1", a + 100);

string verb = message.Substring(a, b - a);

FileStream vFile = new FileStream(@"AssetsResourcesVerb.prefab", FileMode.Create ,FileAccess.ReadWrite);

StreamWriter Vwriter = new StreamWriter(vFile);

Vwriter.WriteLine(verb);

Vwriter.Close();

}

若制作的实体仅由Unity3D自带的模型组成,可以直接使用UTF-8格式编码后使用StreamWriter以字符串的形式写入文件,这种方法相比直接操作字节而言更易于计算。否则需要把制作的命名实体及相关资源以unitypackage包的形式导出,使用BinaryWriter将二进制值写入文件后导入项目。最终将生成的实体放入背包中,方法如下:

Bag.itemList.Add(Resources.Load("Verb") as GameObject);

在执行了以上部分的代码后,此时已获取到句子中涉及到的所有模型和对应关系,名词类模型包含了具体的属性,动词类模型可以获取到名词的属性并执行相应的事件。要实现动画的播放,则只需要依次从背包中取出对应的实体并实例化在场景中即可,关键代码如下:

public Inventory Bag;

void FindVerbsInBag () {

foreach(GameObject go in verbBag.itemList){

if (go.name == Subject)Instantiate(go);

if (go.name == Verb)Instantiate(go);

if (go.name ==Object)Instantiate(go);

}}

在控制器脚本的生命周期结束时,还应添加资源销毁机制并清空背包,避免该场景在下一次激活时执行写入或查找操作出现异常,可以参考如下代码:

private void OnDestroy(){

File.Delete("Assets/Resources/Verb.prefab");

File.Delete("Assets/Resources/Subject.prefab");

File.Delete("Assets/Resources/Object.prefab");

bag.itemList.Clear();

}

该方法在添加时需要慎重考虑,因为在复句或较复杂句式的处理中,该模型与下一个句子可能仍然存有联系。应考虑具体需求灵活变动,例如在销毁前增加条件判断或创建缓存机制等。

3 系统测试

以“袁明害怕小狗”,“袁明坐飞机去上海”为例,为这句话中出现的命名实体制作相应的模型,这两句话在本系统中的运行效果如下:图8(a)按顺序出现了模型,并播放了声音表示害怕,同时人物的模型向后移动了一段距离。图8(b)按顺序出现了模型,并在视频播放完成后更换了天空。

(a)例句“袁明害怕小狗”

(b)例句“袁明坐飞机去上海”

在不销毁以上模型的条件下,输入一个重新组合的句子“袁明到上海,小狗挨着香蕉”,再次制作命名实体“香蕉”对应的模型,添加动词“到”和“挨着”,并识别逗号用做分隔符以两个分句的形式连续调用上述方法。本系统将以两个单句的效果播放动画,最终效果如图9所示。

4 结束语

在现实生活中,人们通常更加熟悉该词语本身所指代的对象,只是不熟悉这一对象在某种语言下的表示中所使用的语言符号。文中为能指与所指之间提供了一种可视化的解决方案,在处理SVO结构单句时可以表现出理想的效果,适合应用于汉英字典例句的可视化翻译、同一语料库内的内容的可视化。对于复句或结构更复杂的句式可以在输入时根据需求切分并逐条处理,以满足大多数应用场景。但是对于不符合语法规则的口语句式或输入错误时表现出的处理结果并不理想。除此之外,标注大量模型的工作是另一个难题。笔者将在日后继续研究此系统的实用性扩展。

猜你喜欢
复句例句向量
连动结构“VP1来VP2”的复句化及新兴小句连接词“来”的形成
向量的分解
中日目的关系复句在句法层面的对比研究
哈汉复合句对比
好词好句
好词好句
好词好句
向量垂直在解析几何中的应用
向量五种“变身” 玩转圆锥曲线
好词好句