本文首发于 Nebula Graph Community 公众号
春节期间如果有小伙伴玩过 Wordle 这个火爆社交媒体的猜词游戏,可能对成语版本的汉兜有所耳闻。在玩汉兜过程中,我发现用 Nebula Graph 的图查问来解 Antfu 的汉兜(中文成语版 Wordle 👉🏻 handle.antfu.me)会是件特地有意思的事件,很适宜当作图数据库语句的实操。在本文中,你将理解我是如何用常识图谱“舞弊”解汉兜。😁
什么是汉兜?
汉兜(https://handle.antfu.me)是由 Vue/Vite 外围团队成员的 Antfu 的又一个十分酷的作品,一个十分粗劣的汉字版的 Wordle,它是一个每日挑战的填字游戏的中文成语版。
每天,汉兜会发动一个猜成语挑战,人们要在十次内猜对对应成语能力获胜,每一步之后都会收到相应的文字、声母、韵母、腔调的匹配状况的提醒,其中:绿色示意这个因素存在并且地位匹配、橘色示意这个元素存在然而地位不对,具体的规定可见如下的网页截图:
汉兜的乐趣在于在无限的尝试次数中,在大脑中搜查可能的答案,一直地去迫近真谛,任何试图舞弊、讨巧去透露后果的行为都是很无趣、倒胃口的(比方从开源的汉兜代码里窃取信息),这个过程就像大脑做了个体操。
说到大脑的成语词汇量体操,我忽然想到,为什么咱们不能在大脑之外造一个汉语成语常识图谱,而后基于这个图谱实操一把图数据库,做个图查问体操呢?
结构解决汉兜的成语常识图谱
什么是常识图谱?
简略来说,常识图谱是一个连贯实体之间关联关系的网络,它最后由 Google 提出并用来满足搜索引擎中基于常识推理才可取得(而不是网页倒排索引)的搜寻问题,比方:”姚明妻子的年龄?“、”火箭队得过几次总冠军?“
这些问题里边,咱们关注问题中的条件。到 2022 年的当初,常识图谱曾经被广泛应用在举荐零碎、问答零碎、平安风控等等更多搜寻之外的畛域。
为什么须要用常识图谱解决汉兜?
起因就是:because I can
实际上,咱们在大脑中解决字谜游戏的过程像极了图谱网络中的信息搜查的过程,汉兜的解谜反馈提醒条件人造适宜被用图谱的语义来进行表白。在本文后边,你们会发现解谜条件翻译成图语义是十分天然的,这个问题就像是一个人造的为图谱而存在的练习一样,我置信这和常识图谱的构造和人脑中的知识结构靠近有很大的关系。
如何构建面向汉兜解谜的常识图谱?
常识图谱是由实体(顶点)和关系(边)组成的,用图数据库管理系统(Graph Database MS)能够很不便地进行常识的入库、更改、查问、甚至可视化摸索。
在本文里,我将利用开源的分布式图数据库 Nebula Graph 开实际这个过程,具体图谱零碎的搭建我都会放在文末。
在本章,咱们只探讨图谱的建模:如何面向汉兜的解谜去设计“实体”与“关系”。
图建模
最后的想法
首先,肯定存在的实体是:
- 成语
- 汉字
- 成语 -[蕴含]-> 汉字,每个汉字 -[读作]-> 读音。
其次,因为解谜过程中波及到了声母、韵母以及腔调的条件,思考到图谱自身的量级十分小(千级别),而且字的读音是一对多的关系,我把读音和声母(包涵声母 -initial 和韵母 -final)也作为实体,他们之间的关系则是牵强附会了:
最终的版本
然而,我在后边基于图谱进行查问的时候发现最后的建模会使得 (成语)–>(字)–>(读音)
查问过程中失落了这个字特定的读法的条件,所以我最终的建模是:
这样,纯文字的条件只波及了 (成语)-->(字)
这一跳,而读音、声母、腔调的条件则是另一条关系门路,既没有最后版本条件的冗余,又能够在一个门路模式匹配里带上两种条件(后边的例子里会波及这样的表白)。
构建成语常识图谱
有了建模、这么简略的图谱的构建就剩下了数据的收集、荡涤和入库。
对于所有成语数据和他们的读音,我一方面间接抽取了汉兜代码外部的数据,另一方面利用 PyPinyin 这个开源的 Python 库将汉兜数据中没有读音的数据取得读音,同时,我也用到了 PyPinyin 里的很多不便的函数,比方:获取一个拼音的声母、韵母。
构建工具的代码在这里:https://github.com/wey-gu/chinese-graph
更多信息我也放在文末的附录之中。
开始常识图谱查问体操
至此,我假如咱们都曾经有了我帮大家搭建的成语 舞弊 常识图谱了,开始咱们的图谱查问体操吧!
首先,关上汉兜 👉🏻 https://handle.antfu.me/
假如咱们想从一个成语开始,如果你没有想法的话能够试试这个:
# 匹配成语中的一个后果
MATCH (x:idiom) "爱憎分明" RETURN x LIMIT 1
# 返回后果
("爱憎分明" :idiom{pinyin: "['ai4','zeng1','fen1','ming2']"})
而后咱们把它填到汉兜之中,取得第一次尝试的提醒条件:
咱们运气不错,失去了三个地位上的条件!
- 有一个非第一个地位的字,拼音是 4 声,韵母是 ai,但不是爱(爱)
- 有一个一声的字,不在第二个地位(憎)
- 有一个字韵母是 ing,不在第四个地位(明)
- 第四个字是二声(明)
上面,咱们开始图数据库语句体操!
# 有一个非第一个地位的字,拼音是 4 声,韵母是 ai,但不是爱
MATCH (char0:character)<-[with_char_0:with_character]-(x:idiom)-[with_pinyin_0:with_pinyin]->(pinyin_0:character_pinyin)-[:with_pinyin_part]->(final_part_0:pinyin_part{part_type: "final"})
WHERE id(final_part_0) == "ai" AND pinyin_0.character_pinyin.tone == 4 AND with_pinyin_0.position != 0 AND with_char_0.position != 0 AND id(char0) != "爱"
# 有一个一声的字,不在第二个地位
MATCH (x:idiom) -[with_pinyin_1:with_pinyin]->(pinyin_1:character_pinyin)
WHERE pinyin_1.character_pinyin.tone == 1 AND with_pinyin_1.position != 1
# 有一个字韵母是 ing,不在第四个地位
MATCH (x:idiom) -[with_pinyin_2:with_pinyin]->(:character_pinyin)-[:with_pinyin_part]->(final_part_2:pinyin_part{part_type: "final"})
WHERE id(final_part_2) == "ing" AND with_pinyin_2.position != 3
# 第四个字是二声
MATCH (x:idiom) -[with_pinyin_3:with_pinyin]->(pinyin_3:character_pinyin)
WHERE pinyin_3.character_pinyin.tone == 2 AND with_pinyin_3.position == 3
RETURN x, count(x) as c ORDER BY c DESC
在图数据库之中运行,失去了 7 个答案:
("惊愚骇俗" :idiom{pinyin: "['jing1','yu2','hai4','su2']"})
("惊世骇俗" :idiom{pinyin: "['jing1','shi4','hai4','su2']"})
("惊见骇闻" :idiom{pinyin: "['jing1','jian4','hai4','wen2']"})
("沽名卖直" :idiom{pinyin: "['gu1','ming2','mai4','zhi2']"})
("惊心骇神" :idiom{pinyin: "['jing1','xin1','hai4','shen2']"})
("荆棘载途" :idiom{pinyin: "['jing1','ji2','zai4','tu2']"})
("出卖灵魂" :idiom{pinyin: "['chu1','mai4','ling2','hun2']"})
看起来“惊世骇俗“比拟支流,试试!
咱们很侥幸,借助于成语 舞弊 常识图谱,竟然一次就找到了答案,当然这实际上得益于第一次随机选取的词带来的限度条件的个数,不过在大部分状况下,两次尝试取得最终答案的可能性还是十分大的!
注,这两头很长的 253 分钟是因为我在查问中发现之前代码里结构的图谱有点 bug,是“披枷带锁”这个词引起的读音图谱的谬误数据,还好起初被修复了。
大家晓得“披枷带锁”的正确读音么?😭
回题,我给大家具体解释一下这个成语破解的过程。
语句的含意
咱们从第一个字的条件开始,这是一个既有声音、又有字形信息的条件。
- 声音信息:存在一个韵母为 ai4 的发音,地位不在第一个字
- 文字信息:这个韵母为 ai4 的字,不是爱字
对于声音信息条件,转换为图模式匹配为:(成语)- 一个字发音 -(拼音)- 蕴含声母 -(韵母) WHERE 拼音韵母为 ai4 AND 地位不是第一个
。
因为建模的时候,属性名称我用的是英文(其实中文也是反对的),实际上的语句为:
# 有一个非第一个地位的字,拼音是 4 声,韵母是 ai
MATCH (x:idiom)-[with_pinyin_0:with_pinyin]->(pinyin_0:character_pinyin)-[:with_pinyin_part]->(final_part_0:pinyin_part{part_type: "final"})
WHERE id(final_part_0) == "ai" AND pinyin_0.character_pinyin.tone == 4 AND with_pinyin_0.position != 0
# ...
RETURN x
相似的,示意非第一个地位的字,不是 爱
的表白是:
# 有一个非第一个地位的字,拼音是 4 声,韵母是 ai,但不是爱
MATCH (char0:character)<-[with_char_0:with_character]-(x:idiom)
WHERE with_char_0.position != 0 AND id(char0) != "爱"
# ...
RETURN x, count(x) as c ORDER BY c DESC
而因为这两个条件最终形容的是同一个字,所以它们是能够被写在一个门路下的:
# 有一个非第一个地位的字,拼音是 4 声,韵母是 ai,但不是爱
MATCH (char0:character)<-[with_char_0:with_character]-(x:idiom)-[with_pinyin_0:with_pinyin]->(pinyin_0:character_pinyin)-[:with_pinyin_part]->(final_part_0:pinyin_part{part_type: "final"})
WHERE id(final_part_0) == "ai" AND pinyin_0.character_pinyin.tone == 4 AND with_pinyin_0.position != 0 AND with_char_0.position != 0 AND id(char0) != "爱"
# ...
RETURN x
更多的 MATCH 语法和例子细节,请大家参考文档:
- MATCH:https://docs.nebula-graph.com.cn/3.0.1/3.ngql-guide/7.general-query-statements/2.match/
- 图模式:https://docs.nebula-graph.com.cn/3.0.1/3.ngql-guide/1.nGQL-overview/3.graph-patterns/
- nGQL 命令:cheatsheet
可视化展现线索
咱们把每一个条件的匹配门路作为输入,利用 Nebula Graph 的可视化能力,能够失去:
# 有一个非第一个地位的字,拼音是 4 声,韵母是 ai,但不是爱 # 有一个非第一个地位的字,拼音是 4 声,韵母是 ai,但不是爱
MATCH p0=(char0:character)<-[with_char_0:with_character]-(x:idiom)-[with_pinyin_0:with_pinyin]->(pinyin_0:character_pinyin)-[:with_pinyin_part]->(final_part_0:pinyin_part{part_type: "final"})
WHERE id(final_part_0) == "ai" AND pinyin_0.character_pinyin.tone == 4 AND with_pinyin_0.position != 0 AND with_char_0.position != 0 AND id(char0) != "爱"
# 有一个一声的字,不在第二个地位
MATCH p1=(x:idiom) -[with_pinyin_1:with_pinyin]->(pinyin_1:character_pinyin)
WHERE pinyin_1.character_pinyin.tone == 1 AND with_pinyin_1.position != 1
# 有一个字韵母是 ing,不在第四个地位
MATCH p2=(x:idiom) -[with_pinyin_2:with_pinyin]->(:character_pinyin)-[:with_pinyin_part]->(final_part_2:pinyin_part{part_type: "final"})
WHERE id(final_part_2) == "ing" AND with_pinyin_2.position != 3
# 第四个字是二声
MATCH p3=(x:idiom) -[with_pinyin_3:with_pinyin]->(pinyin_3:character_pinyin)
WHERE pinyin_3.character_pinyin.tone == 2 AND with_pinyin_3.position == 3
RETURN p0,p1,p2,p3
在可视化工具的 Console 控制台里执行上边的语句之后,抉择导入图摸索,就能够看到:
下一步
如果大家是从本文第一次理解到 Nebula Graph 图数据库,那么大家能够下一步从 Nebula Graph 我的项目和 Nebula Graph 社区的官网 Bilibili 站点 👉🏻 https://space.bilibili.com/47… 理解更多有意思的入门常识。
另外,这里是 Nebula Graph 的官网线上试玩环境,大家能够照着文档,利用试玩环境尝鲜。
后边,Nebula Graph 会发展每天的汉兜 nGQL 体操流动,敬请关注哈!
Happy Graphing!
附录:搭建成语常识图谱
收集、生成图谱数据
$ python3 graph_data_generator.py
导入数据到 Nebula Graph 图数据库
部署图数据库
借助于 Nebula-Up:https://github.com/wey-gu/nebula-up/,一行就能够了。
$ curl -fsSL nebula-up.siwei.io/install.sh | bash -s -- v3.0.0
部署胜利的话,会看到这样的后果:
┌────────────────────────────────────────┐
│ 🌌 Nebula-Graph Playground is Up now! │
├────────────────────────────────────────┤
│ │
│ 🎉 Congrats! Your Nebula is Up now! │
│ $ cd ~/.nebula-up │
│ │
│ 🌏 You can access it from browser: │
│ http://127.0.0.1:7001 │
│ http://<other_interface>:7001 │
│ │
│ 🔥 Or access via Nebula Console: │
│ $ ~/.nebula-up/console.sh │
│ │
│ To remove the playground: │
│ $ ~/.nebula-up/uninstall.sh │
│ │
│ 🚀 Have Fun! │
│ │
└────────────────────────────────────────┘
图谱入库
借助于 Nebula-Importer https://github.com/vesoft-inc…,一行就能够了。
$ docker run --rm -ti \
--network=nebula-docker-compose_nebula-net \
-v ${PWD}/importer_conf.yaml:/root/importer_conf.yaml \
-v ${PWD}/output:/root \
vesoft/nebula-importer:v3.0.0 \
--config /root/importer_conf.yaml
大略一两分钟数据就导入胜利了,命令也会失常退出。
连到图数据库的 Console
取得本机第一个网卡的地址,这里是 10.1.1.168
$ ip address
2: enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 2a:32:4c:06:04:c4 brd ff:ff:ff:ff:ff:ff
inet 10.1.1.168/24 brd 10.1.1.255 scope global dynamic enp4s0
进入 Console 的容器执行下边的命令:
$ ~/.nebula-up/console.sh
# nebula-console -addr 10.1.1.168 -port 9669 -user root -p nebula
检查一下导入的数据:
(root@nebula) [(none)]> show spaces
+--------------------+
| Name |
+--------------------+
| "chinese_idiom" |
+--------------------+
(root@nebula) [(none)]> use chinese_idiom
Execution succeeded (time spent 1510/2329 us)
Fri, 25 Feb 2022 08:53:11 UTC
(root@nebula) [chinese_idiom]> match p=(成语:idiom) return p limit 2
+------------------------------------------------------------------+
| p |
+------------------------------------------------------------------+
| <("一丁不识" :idiom{pinyin: "['yi1','ding1','bu4','shi2']"})> |
| <("赤身露体" :idiom{pinyin: "['yi1','si1','bu4','gua4']"})> |
+------------------------------------------------------------------+
(root@nebula) [chinese_idiom]> SUBMIT JOB STATS
+------------+
| New Job Id |
+------------+
| 11 |
+------------+
(root@nebula) [chinese_idiom]> SHOW STATS
+---------+--------------------+--------+
| Type | Name | Count |
+---------+--------------------+--------+
| "Tag" | "character" | 4847 |
| "Tag" | "character_pinyin" | 1336 |
| "Tag" | "idiom" | 29503 |
| "Tag" | "pinyin_part" | 57 |
| "Edge" | "with_character" | 116090 |
| "Edge" | "with_pinyin" | 5943 |
| "Edge" | "with_pinyin_part" | 3290 |
| "Space" | "vertices" | 35739 |
| "Space" | "edges" | 125323 |
+---------+--------------------+--------+
附录:图建模的 Schema nGQL
CREATE SPACE IF NOT EXISTS chinese_idiom(partition_num=5, replica_factor=1, vid_type=FIXED_STRING(24));
USE chinese_idiom;
# 创立点的类型
CREATE TAG idiom(pinyin string); #成语
CREATE TAG character(); #汉字
CREATE TAG character_pinyin(tone int); #单字的拼音
CREATE TAG pinyin_part(part_type string); #拼音的声部
# 创立边的类型
CREATE EDGE with_character(position int); #蕴含汉字
CREATE EDGE with_pinyin(position int); #读作
CREATE EDGE with_pinyin_part(part_type string); #蕴含声部
参考文献
- 海内爆红的字谜游戏 Wordle,当初有了中文版
交换图数据库技术?退出 Nebula 交换群请先填写下你的 Nebula 名片,Nebula 小助手会拉你进群~~
关注公众号