张飞比关羽还能打?一位酷爱三国的日本程序员,用NLP分析了武将们的战斗力

大数据文摘出品

来源:Qiita

编译:李欣月、刘俊寰

作为中国四大名著之一,三国的故事自然备受国人喜爱和追捧,但是谁又能想到三国竟然在日本也“出了圈”,举个例子,吴宇森导演的电影《赤壁》在日本的票房收入超过国内,同时该电影也是日本影史上票房最高的华语电影。

不仅在影视行业,三国因为其同时具有历史、策略、动作、人物等众多元素,在日本游戏行业的发展势头也相当迅猛,其中日本光荣株式协会就开发了包括《三国志》在内的一系列游戏,备受好评。

人物是三国故事经久不衰的关键因素之一,出场的众多复杂人物到底孰优孰劣成为三国游戏得以建立的前提,但是从小说本身来看,它在文本意义上果真如此吗?

怀着这样的疑问,一个日本小哥哥(@youwht)就利用AI对三国中的人物进行了一次深扒,通过“自然语言处理”和“机器学习”来分析三国文本,更新了三国游戏中的武将排名,得出了很有趣的结论,比如,张飞比关羽还能打,武力值高居榜首,而在“政治谋略”上,诸葛亮则比两大主公——曹操和刘备都更胜一筹,而姜维作为诸葛亮的传人,也被分析出是最接近诸葛亮的人物。

不仅分析出的结果好玩,这位作者的语言风格也非常有意思,代码外还有各种人物内心戏乱入,简直还原了一个懂AI的三国小剧场。

今天让我们一起看看,这位热爱三国的日本程序员,是如何用AI帮三国武将们重新徘名的。

链接:

https://qiita.com/youwht/items/92056e63498c36de4e3b

三顾茅庐——背景介绍

关羽:“喂,我们的英雄事迹是如何在后世传播的呢?”

孔明:“是像《苍天航路》一样从扇子中传出一道光?还是在《SD三国高达传》里和机动战士高达融合在一起?甚至是在《一骑当千,龙之命运》里变成萌萌的女性角色?”

有人只看图就能分辨出谁是谁吗?

刘禅:“你们这些人要么拥有100的智商,要么拥有99的武力,在后世必定会受到优待,就算有101个我也赢不了你们。”

魏延:“但我们看起来很像原始人好吧……”

孔明:“我有100的智商是理所当然的呀!”

司马懿:“喂,最后赢的人是我吧?”

荀彧:“不对不对,好像我才是王佐之才……”

甘宁:“我才是最强的!”

张辽:“张辽在此,最强的是我!”

张飞:“你们难道是忘了我的存在么?!”

【到底谁的智商和武力更胜一筹呢?豪杰们的争论渐趋白热化……】

曹操:“大家静一静,听我一言,听说最近AI很火,要不去看看AI是怎么解释的。”

三国鼎立——结论

怀着对KOEI各大武将地位的崇高敬意,我们将利用“自然语言解析”和“机器学习”处理小说文本内容,各位武将的排名会发生怎样的变化呢?(内心os:这不过是浪费技术的无双乱舞罢辽)

本次给大家介绍一个谁都能使用的技能:只需要Colaboratory,不用构造环境就能进行“三国分析”。在此我们尽可能地简单描述,以便大家仅用复制粘贴就能上手尝试。

名称

本次实验的结果

(参考)KOEI三国志5的数据

曹操

95, 92, 87, 105

87, 96, 97, 98

刘备

89, 89, 84, 105

79', 77, 80,'99

诸葛亮

78, 98, 90, 104

60, 100, 96, 97

关羽

92, 75, 62, 82

99, 83, 64, 96

张飞

97, 61, 44, 77

99, 45, 17, 44

魏延

91, 65, 50, 68

94, 48, 37, 56

袁绍

70, 71, 66, 77

81, 77, 49, 92

注:从左到右的顺序是(武力、智商、政治、魅力)

本文的文本输入采用的是日本作家吉川英治根据《三国演义》改写的现代版小说《三国》@青空文库。通过“自然语言处理”和“机器学习”,推导出了上述武力、智力等参数。

简单来说,这里的机器学习就是把一个武将变换成50次元向量,再将向量放进完全相同的“公式”中得到一个值,这个值就是表中的参数。

此次研究的主题是,通过“小说(自然语言)”⇒“数值化”⇒“公式”的流程能否计算出各位武将的武力/智力。

来感受一下各位武将的“推导”过程!(摘录于实际输出结果):

  • 比较接近诸葛亮的人是谁?

⇒ 姜维、司马懿、陆逊、周瑜、魏延、马谡

  • 刘备拥有关羽,那么曹操拥有谁呢?

⇒ 袁绍、张辽

※因为年轻时候就彼此熟悉,所以袁绍更胜一筹?

  • 孙权有鲁肃辅佐,刘备又对应谁呢?

⇒ 司马徽(水镜先生)、徐庶

※介绍贤者的职位是什么?

为了得到高精确度的结果,将《三国》这种特殊的小说进行自然语言处理(自然语言的前处理)是最重要的。从卖草鞋到成为蜀汉皇帝,每次分析的改善,都想让大家享受用代码解读三国的快乐。

机器学习的结果固然有趣,但从自然语言分析的角度来看,仅用一本小说制作得出的精确度还有待商榷。如果想真正决定游戏的参数,需要准备大量的小说和文本作为INPUT。(本次研究的目的就是检验这种操作是否可行,并不是想用这个来决定实际参数)

天下三分之计——整体方针/目录

曹操:“AI!AI!”

杨修:“OK!解决了,全军击退!”

刘备:“等等,这就结束了?”

杨修:“刘备,你知道AI嘛?”

刘备:“孔明!交给你了,接下来让你表演!”

孔明:“……”

刘备:“赶紧的!”

孔明:“那么我就像天下三分的计谋一样,分三个步骤来说明这次的计划。”

刘备:(“好像不分三步就说不出来似的……”)

本次流程分为以下三步:

  1. 将吉川英治《三国》中出现的特有名称进行形态分析后以单词为单位区隔开。
  2. 区隔后的结果用Word2Vec进行矢量化。(Word2Vec:能够以N维矢量表现单词,并进行加法运算等运算的技术。提前阅读一下 “陌生人”的反义词是“白色恋人”这篇文章可能会更好理解。附链接:https://qiita.com/youwht/items/f21325ff62603e8664e6)
  3. 此时这些武将已经被矢量化了,如果要从这些矢量中得出相关性高的矢量(复数矢量的集合体),需要多少个算式才能计算出接近KOEI三国志的参数呢?其实最大的难关是第一步的形态分析,因此必须尽可能正确地去认识三国。

阻挡我们正确认识三国世界的壁垒如下:

  • 韩玄,刘度,赵范,金旋(AI知道我们荆州四杰么?)
  • 玄德=刘玄德=刘备玄德⇒“刘备”

那么就让我们开始第一步的形态分析吧!

桃园结义——环境准备

“不求同年同月同日生,但求同年同月同日死!”

此次立下结拜兄弟誓言的最佳道具是以下3点:

  • Colaboratory:在浏览器上可以免费使用的Python运行环境
  • Janome:环境构建轻松的形态分析器
  • Word2Vec:将自然语言数值化/矢量化的模型

首先借助Colaboratory和Janome制造出最简单的自然语言处理模式。(可以借助电脑的浏览器亲手尝试一下)

Colaboratory的准备:访问Colaboratory(需要Google账户),基本的使用是检索器,不需要环境构筑,只用浏览器就能编程。

先尝试制作:“文件夹”⇒“Python3新的笔记本”。因为想要保存此次在GoogleDrive中使用的各种数据,所以请通过以下指令安装GoogleDrive。

GoogleDriveのマウント
# これを実行すると、認証用URLが表示されて、キーを入力すると
# 「drive/My Drive/」の中に、認証したアカウントのgoogle driveが入るfrom google.colab import drivedrive.mount('/content/drive')

将日语区分开来进行品词判定等,安装Janome。

在Colaboratory中,凡是指令开头有“!”的语句,都能执行命令解释程序。

Janomeのインストール
!pip install janome

然后立刻尝试着用Janome着抽出名词·动词。

Janomeで形態素解析(名詞・動詞の抽出)
#素状態のJanomeの性能を確認する
# Janomeのロードfrom janome.tokenizer import Tokenizer
# Tokenneizerインスタンスの生成 tokenizer = Tokenizer()
# テキストを引数として、形態素解析の結果、名詞・動詞原型のみを配列で抽出する関数def extract_words(text):
    tokens = tokenizer.tokenize(text)
    return [token.base_form for token in tokens
        if token.part_of_speech.split(',')[0] in['名詞', '動詞']]
sampletext = u"文章の中から、名詞、動詞原型などを抽出して、リストにするよ"print(extract_words(sampletext))sampletext = u"劉備と関羽と張飛の三人は桃園で義兄弟の契りを結んだ"print(extract_words(sampletext))sampletext = u"悪来典韋はかえって、許褚のために愚弄されたので烈火の如く憤った"print(extract_words(sampletext))
 
実行結果
['文章', '中', '名詞', '動詞', '原型', '抽出', 'する', 'リスト', 'する']['劉', '備', '関', '羽', '張', '飛', '三', '人', '桃園', '義兄弟', '契り', '結ぶ']['典', '韋', '許', '褚', 'ため', '愚弄', 'する', 'れる', '烈火', '憤る']

到此为止,已经形成了最简单的处理自然语言的环境!

武将识别①——关羽

但是如果仔细看结果的话——诶!竟然没有识别到“关羽”!

原来关羽被拆分成了“关”和“羽”。和“结拜兄弟”等普通名词不同,“刘备”“关羽”“张飞”等三国中的武将名字,按平常的方法是不会被识别到的,但这也不是什么大问题。

在Janome中可以利用mecabo-ipadic-neologd的词典数据——由Janome的作者(@moco_beta)公开的和mecaba-ipadic-neologd配套的商品。

访问以下链接,将它复制到其自己的GoogleDrive中。

https://drive.google.com/drive/folders/0BynvpNc_r0kSd2NOLU01TG5MWnc

janome+neologdのインストール
#結構時間がかかる(6分くらい)
#Mydrive上の、先程のjanome+neologdのパスを指定する
#最新版とファイル名が一致しているかどうかは各自で確認すること!pip install "drive/My Drive/Janome-0.3.9.neologd20190523.tar.gz" --no-compile

安装成功后,会出现以下描述,找到“RESTART RUNTIME”按钮。

インストール実行結果の末尾
#WARNING: The following packages were previously imported in this runtime:
#  [janome]
#You must restart the runtime in order to use newly installed versions.

如果要把Colaboratory的RUNTIME复位一次,按这个按钮就OK。

使用NEologd同捆版时,需要稍微改变Tokenneizer最初的生成代码。通过下面的代码,让我们来看看NEologd的效果吧!

NEologd入れた状態で形態素解析する
# Janomeのロードfrom janome.tokenizer import Tokenizer
# Tokenneizerインスタンスの生成 ★ここが異なる★tokenizer = Tokenizer(mmap=True)
# テキストを引数として、形態素解析の結果、名詞・動詞原型のみを配列で抽出する関数def extract_words(text):
    tokens = tokenizer.tokenize(text)
    return [token.base_form for token in tokens
        if token.part_of_speech.split(',')[0] in['名詞', '動詞']]
 
sampletext = u"劉備と関羽と張飛の三人は桃園で義兄弟の契りを結んだ"print(extract_words(sampletext))sampletext = u"悪来典韋はかえって、許褚のために愚弄されたので烈火の如く憤った"print(extract_words(sampletext))sampletext = u"田豊。沮授。許収。顔良。また――審配。郭図。文醜。などという錚々たる人材もあった。"print(extract_words(sampletext))sampletext = u"第一鎮として後将軍南陽の太守袁術、字は公路を筆頭に、第二鎮、冀州の刺史韓馥、第三鎮、予州の刺史孔伷、第四鎮、兗州の刺史劉岱、第五鎮、河内郡の太守王匡、第六鎮、陳留の太守張邈、第七鎮、東郡の太守喬瑁"print(extract_words(sampletext))
実行結果
['劉備', '関羽', '張飛', '三', '人', '桃園', '義兄弟', '契り', '結ぶ']['悪来', '典韋', '許褚', 'ため', '愚弄', 'する', 'れる', '烈火', '憤る']['田豊', '沮授', '許', '収', '顔良', '審配', '郭図', '文醜', '錚々たる', '人材', 'ある']['鎮', '後将軍', '南陽', '太守', '袁術', '字', '公路', '筆頭', '二', '鎮', '冀州', '刺史', '韓', '馥', '三', '鎮', '予州', '刺史', '孔', '伷', '四',

刘备、关羽、张飞等自不用说,典韦、许褚、田丰、沮授等也能够识别出来。另外,除了识别这些有名武将之外,动词和一般名词的识别精确度也提高了,所以总体上来说得出了理想的结果。

但是如果仔细看这个结果的话……

武将识别②——反董卓联盟全军覆没?

通过导入NEologd,“刘备”“关羽”等地位90以上的人,以及SSR里的人虽然能够认识到,但是在三国世界里还有很多不出名的人没能被识别。

在此之前的结果中,叛徒的代名词——“许收”就没有被识别到。同时,虽然喜爱投币式甘蔗蜜水的伪皇帝“袁术”被识别到了,但韩馥、孔伷、刘岱、张邈、乔瑁等都没被识别到。

这样就不能写出反董卓联盟的精彩篇章了,连NEologd也没能覆盖到这里!于是,我们制作了“三国登场人物名单”,将其作为“用户辞典”注册到Janome中:

https://ja.wikipedia.org/wiki/三国演義の人物の一覧

以该网页的人物一览表为基础,简单地制作出每行一人的文本。将这个上传之后,尝试着读出下列的代码。

人名リストの読み込み
#人物の名前が列挙してあるテキストから、ワードリストを作成するimport codecsdef getKeyWordList():
    input_file = codecs.open('drive/My Drive/Sangokusi/三国志_人名リスト.txt' , 'r', 'utf-8')
    lines = input_file.readlines() #読み込み    result_list = []
    for line in lines:
        tmp_line = line
        tmp_line = tmp_line.replace("\r","")
        tmp_line = tmp_line.replace("\n","")
        #ゴミデータ削除のため、2文字以上のデータを人名とみなす        if len(tmp_line)>1:
 
            result_list.append(tmp_line)
    return result_list
jinbutu_word_list = getKeyWordList()print(len(jinbutu_word_list))print(jinbutu_word_list[10:15])
実行結果
1178['張楊', '張虎', '張闓', '張燕', '張遼']

就这样,在录入1178位人物的名称后,得到了一个很简洁的清单。另外,为了精益求精,做了如下调整:

  • 因为马忠同名同姓,所以放弃了区分;
  • “乔瑁”在wiki上不存在所以之后追加;
  • “張繍”“張繡”微妙的字体不同;
  • “祝融夫人”变更为“祝融”。

根据该列表,创建了在Janome中利用的“用户辞典形式”的CSV文件,虽然能够设定的地方还有很多,但由于此次只是单纯的人名列表,因此都以相同的注册内容进行。

Janomeのユーザ辞書csvの作成
#作成したキーワードリストから、janomeのユーザ辞書形式となるCSVファイルを作成するkeyword_list = jinbutu_word_listuserdict_list = []
#janomeのユーザ辞書形式に変換をかける。コストや品詞の設定等for keyword in keyword_list:
  #「表層形,左文脈ID,右文脈ID,コスト,品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音」  #参考:http://taku910.github.io/mecab/dic.html  #コストは,その単語がどれだけ出現しやすいかを示しています.   #小さいほど, 出現しやすいという意味になります. 似たような単語と 同じスコアを割り振り, その単位で切り出せない場合は, 徐々に小さくしていけばいい
  userdict_one_str = keyword + ",-1,-1,-5000,名詞,一般,*,*,*,*," + keyword + ",*,*"
  #固有名詞なので、かなりコストは低く(その単語で切れやすく)設定  userdict_one_list = userdict_one_str.split(',')
  userdict_list.append(userdict_one_list)
print(userdict_list[0:5])
#作成したユーザ辞書形式をcsvでセーブしておくimport csvwith open("drive/My Drive/Sangokusi/三国志人名ユーザ辞書.csv", "w", encoding="utf8") as f:
  csvwriter = csv.writer(f, lineterminator="\n") #改行記号で行を区切る  csvwriter.writerows(userdict_list)
実行結果
[['張譲', '-1', '-1', '-5000', '名詞', '一般', '*', '*', '*', '*', '張譲', '*', '*'], ['張角', '-1', '-1', '-5000', '名詞', '一般', '*', '*', '*',
 
这样,记载了1000位以上有名武将的用户词典就制作出来了!
现在让我们来检验一下这本词典的适用范围。
 
ユーザ辞書を使った場合
# Janomeのロードfrom janome.tokenizer import Tokenizer
#ユーザ辞書、NEologd 両方使う。★ここが変更点★tokenizer_with_userdict = Tokenizer("drive/My Drive/Sangokusi/三国志人名ユーザ辞書.csv", udic_enc='utf8', mmap=True)
# テキストを引数として、形態素解析の結果、名詞・動詞原型のみを配列で抽出する関数def extract_words_with_userdict(text):
    tokens = tokenizer_with_userdict.tokenize(text)
    return [token.base_form for token in tokens
        #どの品詞を採用するかも重要な調整要素        if token.part_of_speech.split(',')[0] in['名詞', '動詞']]
sampletext = u"劉備と関羽と張飛の三人は桃園で義兄弟の契りを結んだ"print(extract_words_with_userdict(sampletext))sampletext = u"悪来典韋はかえって、許褚のために愚弄されたので烈火の如く憤った"print(extract_words_with_userdict(sampletext))sampletext = u"田豊。沮授。許収。顔良。また――審配。郭図。文醜。などという錚々たる人材もあった。"print(extract_words_with_userdict(sampletext))sampletext = u"第一鎮として後将軍南陽の太守袁術、字は公路を筆頭に、第二鎮、冀州の刺史韓馥、第三鎮、予州の刺史孔伷、第四鎮、兗州の刺史劉岱、第五鎮、河内郡の太守王匡、第六鎮、陳留の太守張邈、第七鎮、東郡の太守喬瑁"print(extract_words_with_userdict(sampletext))
実行結果
['劉備', '関羽', '張飛', 'の', '三', '人', '桃園', '義兄弟', '契り', '結ぶ']['悪来', '典韋', '許褚', 'ため', '愚弄', 'する', 'れる', '烈火', '憤る']['田豊', '沮授', '許', '収', '顔良', '審配', '郭図', '文醜', '錚々たる', '人材', 'ある']['鎮', '後将軍', '南陽', '太守', '袁術', '字', '公路', '筆頭', '二', '鎮', '冀州', '刺史', '韓馥', '三', '鎮', '予州', '刺史', '孔伷', '四', '鎮', '兗

“成功了!”

Tips:

在进行形态分析时,首先考虑到的候选工具是mecab,但在mecab上进行环境构建非常麻烦。虽然在Colaboratory上有便捷的方法,但是它能和neologd一起使用么?可以自己添加用户词典么?考虑到这些,在Web上是无法构建一个良好环境的。

因此,就这一点来说Janome便成为首推,因为它能够降低构建环境的障碍。这种对三国等独特世界定制的自然语言的处理方法也很容易上手。

乍一看这已经足够了,但是隐约能感觉到还存在障碍?

下一个是陷入“孔明的圈套”的故事。在进入这个故事之前,提倡AI分析的曹操说到:“让我加入CM!”

CM乱入~

“赤壁之战”活动简介

日期:大约为公元208年11月20日

场所:赤壁(在东风吹起之前请各位好汉畅所欲言)

参加者:曹操、周瑜、诸葛亮等众嘉宾

孙权:“统筹部下的高招。”

黄盖:“三代功臣被年轻一代无情践踏的故事。”

诸葛亮:“让大家看看我是如何筹集10万只箭的。”

蔡瑁:“跳槽后获得上司信赖的方法。”

庞统:“教你学会不晕船的小窍门。”

曹操:“让部下尽忠职守的管理方法。”

其他:联欢会可以参加,是扩大和各大名将的人脉关系的好时机!

(友情提示:请不要出于叛变参加此次活动)

曹操:“赤壁之战再会!”

孔明等待的时机快要到来了……

孔明的圈套——字的识别

终于做好了,输入人名数据资料后就可以分析出来相应的参数!不用再等待了,这就是孔明的圈套。

就这样解析的话得不到一个令人满意的结果,请看下面的例子:

“车上穿着白衣戴着簪冠的人一定是诸葛亮孔明。”

“这是予州太守刘备的义弟关羽字云长。”

“赵子龙骑着白马,在马背上就把他刺死了。”

“赵云子龙在战斗中疏于职守,玄德在两难中。”

孔明是字,诸葛亮才是本名。我们通常都称他孔明,但是也有称他为诸葛亮或者诸葛孔明的时候。刘备字玄德,关羽字云长,赵云字子龙,文中也会出现“玄德、关羽......”一类的称呼。

与此类似,三国中同一个人物有不同的称呼方法。但是一个人有四种称呼方法让我很困扰:赵云=子龙=赵子龙=赵云子龙、刘备=玄德=刘玄德=刘备玄德。

这需要进行进一步替换:赵子龙⇒赵赵云、赵云子龙⇒赵云赵云,采取这样的措施是为了防止重复现象发生。另外,用字称呼的大多是有名的武将,所以这次我们准备了130位武将的名单。利用这份名单,适度参考下三国的粉丝网站,再将制作好字和全名的CSV录入,就可以先告一段落啦!

 あざなCSVの読み込み
import csv
csv_file = open("drive/My Drive/Sangokusi/三国志_あざな変換リスト.csv", "r", encoding="utf8", errors="", newline="" )#リスト形式azana_reader = csv.reader(csv_file, delimiter=",", doublequote=True, lineterminator="\r\n", quotechar='"', skipinitialspace=True)azana_list = [ e for e in azana_reader ]csv_file.close()
print(len(azana_list))print(azana_list[2])
#全員の字リストを作るのは難しかったが、
#['雲長', '関羽']のような132人の代表的な字とその対比表が入っている
実行結果
132['雲長', '関羽']

通过这种方式使用生成的对照表,然后对文本里面的“字”做转换处理。

字(あざな)の変換処理の実装
#これは、字(あざな)を置き換えるだけの単純な置換処理def azana_henkan(input_text):
    result_text = input_text
    for azana_pair in azana_list:
        result_text = result_text.replace(azana_pair[0],azana_pair[1])
    return result_text
#単純に、字からの変換をかけるだけだと、
#趙雲子龍→趙雲趙雲などのようになる場合が多いため、
#同一の人物名で重複している場合は、一方を削除する。
#また、劉玄徳、趙子龍、などのような表現に対応するため、
#フルネームで2文字の場合はAAB→AB(劉玄徳→劉劉備→劉備)
#フルネームで3文字の場合はAAB→AB(諸葛孔明→諸葛諸葛亮→諸葛亮)
# となる名寄せを行う。
#(※名字1文字+名前二文字はあまり居ない気がするので無視)def jinmei_tyouhuku_sakujyo(input_text):
    jinbutu_word_list = getKeyWordList()
    result_text = input_text
    for jinbutumei in jinbutu_word_list:
        result_text = result_text.replace(jinbutumei+jinbutumei, jinbutumei)
        if len(jinbutumei) == 2:
            result_text = result_text.replace(jinbutumei[0]+jinbutumei, jinbutumei)
        if len(jinbutumei) == 3:
            result_text = result_text.replace(jinbutumei[0]+jinbutumei[1]+jinbutumei, jinbutumei)
    return result_text
sampletext = u"これは予州の太守劉玄徳が義弟の関羽字は雲長なり"print(jinmei_tyouhuku_sakujyo(azana_henkan(sampletext)))sampletext = u"趙子龍は、白馬を飛ばして、馬上から一気に彼を槍で突き殺した。"print(jinmei_tyouhuku_sakujyo(azana_henkan(sampletext)))sampletext = u"趙雲子龍も、やがては、戦いつかれ、玄徳も進退きわまって、すでに自刃を覚悟した時だった。"print(jinmei_tyouhuku_sakujyo(azana_henkan(sampletext)))
実行結果
これは予州の太守劉備が義弟の関羽字は関羽なり趙雲は、白馬を飛ばして、馬上から一気に彼を槍で突き殺した。趙雲も、やがては、戦いつかれ、劉備も進退きわまって、すでに自刃を覚悟した時だった。

终于,三国里面的固有名词和对应“孔明的圈套”的词都处理好了。

到了三国统一的最后时刻,在攻克汉中之前,让我们再做最后一步处理!

判处“鸡肋”死刑——消去停止词

曹操:“鸡肋、鸡肋......”

杨修:“ok!解决了!”

“云长可怜他,就给他上了一壶他喜欢的酒。”这里的“他”自然就是张飞。除去“他”进行分析,就会出来“曹操≒他”这样的分析结果(单纯的就是因为曹操的出场次数比较多)。一个看似有意义的“他”,在实际的分析中只会是干扰,因此,就把“他”像杨修操作的一样直接判处死刑。

曹操:“怎么这么任性的就把它除去还判处死刑呢?”

如果你想一览像“鸡肋”一样的停止词的话,SlothLib是我们经常使用的网站。

但是在其他场景里面。这个“他”可能是曹操或者刘备。这里记录的停止词都被判处死刑(消去)并处理成了代码。

首先,登录SlothLib将这些数据制作成一个列表。

SlothLibからのデータの取得&リスト化
#雑音になりやすい単語(「彼」など)はストップワードとして除外する
#SlothLibのテキストを使う。
#どんな言葉が除外されるのかは、直接URLを見れば良い
#参考: http://testpy.hatenablog.com/entry/2016/10/05/004949import urllibslothlib_path = 'http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt'#slothlib_file = urllib2.urlopen(slothlib_path) #←これはPython2のコードslothlib_file = urllib.request.urlopen(slothlib_path)slothlib_stopwords = [line.decode("utf-8").strip() for line in slothlib_file]slothlib_stopwords = [ss for ss in slothlib_stopwords if not ss==u'']
#['彼','彼女',・・・]のようなリストになるprint(len(slothlib_stopwords))print(slothlib_stopwords[10:15])
実行結果
310['いま', 'いや', 'いろいろ', 'うち', 'おおまか']

得到310个“鸡肋”停用词,采用如上制作的清单提取出名词·动词后,再除去“鸡肋清单”中的停用词。

鶏肋ワードの除去機能を実装する
sampletext = u"彼は予州の太守劉玄徳が義弟の関羽字は雲長。彼は劉備玄徳の義兄弟だ"
tmp_word_list = extract_words_with_userdict(jinmei_tyouhuku_sakujyo(azana_henkan(sampletext)))
print(tmp_word_list)
#このようにして、単語リストからストップワードを除外するtmp_word_list = [word for word in tmp_word_list if word not in slothlib_stopwords]
print(tmp_word_list)
実行結果
['彼', '予州', '太守', '劉備', '義弟', '関羽', '字', '関羽', 'なり', '彼', '劉備', 'の', '義兄弟']['予州', '太守', '劉備', '義弟', '関羽', '関羽', 'なり', '劉備', 'の', '義兄弟']

比较两次处理的结果,我们就可以看到“他”这个停用词已经消失了。

那么,所有的准备都做好了!

最后,让我们把成果应用到吉川英治的全文中。

闪亮登场——全文的形态分析

首先在青空文库网站上下载翻译版吉川英治的《三国》全文,制作结合了全部章节的文本。这里需要特别注意的是像下列一样青空文库特有的标记:

需要把公孫※[#「王+贊」、第3水準1-88-37]《こうそんさん》⇒「公孫瓚」。

虽然我之前就使用独立代码,但是感觉还有更好的方法,所以就编写了一万行如下的转换代码。

self.resulttext=re.sub(r'※[#.*?1-88-37.*?]',"瓚",self.resulttext)

首先,制作“字”的人名册。

全文テキストに対して、字(あざな)変換処理をかける
import codecsdef azana_henkan_from_file(input_file_path):
    input_file  = codecs.open(input_file_path, 'r', 'utf-8')
    lines = input_file.readlines() #読み込み    result_txt = ""
    for line in lines:
        result_txt += line
    result_txt = azana_henkan(result_txt)
    return result_txt
#ファイル生成用関数定義
#mesのテキストを、filepathに、utf-8で書き込むdef printFile(mes,filepath):
    file_utf = codecs.open(filepath, 'w', 'utf-8')
    file_utf.write(mes)
    file_utf.close()
    return "OK"
azana_henkango_zenbun = azana_henkan_from_file('drive/My Drive/Sangokusi/三国志全文.txt')azana_henkango_zenbun = jinmei_tyouhuku_sakujyo(azana_henkango_zenbun)
printFile(azana_henkango_zenbun,'drive/My Drive/Sangokusi/三国志全文_あざな変換済み.txt')

使用搭载了NEologd和用户词典的Janome对完成了“字”转换的文本进行形态分析,使用pickle把处理好的数据保存到GoogleDrive上,方便日后使用。

全文の形態素解析
%%time#全文分解するのに10分ほどかかるimport codecs# ['趙雲', '白馬', '飛ばす', '馬上', '彼', '槍', '突き', '殺す'] このようなリストのリスト(二次元リスト)になるdef textfile2wordlist(input_file_path):
    input_file  = codecs.open(input_file_path, 'r', 'utf-8')
    lines = input_file.readlines() #読み込み    result_word_list_list = []
    for line in lines:
        # 1行ずつ形態素解析によってリスト化し、結果格納用のリストに格納していく        # Word2Vecでは、分かち書きされたリスト=1文ずつ、のリストを引数にしている        tmp_word_list = extract_words_with_userdict(line)
 
        #別途準備しておいたstopワードリストを使って除外処理を行う        tmp_word_list = [word for word in tmp_word_list if word not in slothlib_stopwords]
 
        result_word_list_list.append(tmp_word_list)
    return result_word_list_list
Word_list_Sangokusi_AzanaOK_with_userdict_neologd = textfile2wordlist('drive/My Drive/Sangokusi/三国志全文_あざな変換済み.txt')
#作成したワードリストは、pickleを使って、GoogleDriveに保存しておく(一回10分くらいかかるからね)import picklewith open('drive/My Drive/Sangokusi/Word_list_Sangokusi_AzanaOK_with_userdict_neologd_V4.pickle', 'wb') as f:
    pickle.dump(Word_list_Sangokusi_AzanaOK_with_userdict_neologd, f)
#保存したpickleファイルは、以下のように復元するwith open('drive/My Drive/Sangokusi/Word_list_Sangokusi_AzanaOK_with_userdict_neologd_V4.pickle', 'rb') as f:
    Word_list_Sangokusi_AzanaOK_with_userdict_neologd = pickle.load(f)
print(len(Word_list_Sangokusi_AzanaOK_with_userdict_neologd))print(Word_list_Sangokusi_AzanaOK_with_userdict_neologd[10:20])

到此为止,终于完成了对吉川英治《三国》全文的分析,在能够正确识别武将名称并制作了名册的基础上还完成了名词和动词的转换!

自然语言处理篇——完结

朋友们:“写这么长的报道,哈哈哈哈,诸葛亮都没有这么长久!”

确实有点冗长了,为了做一个好的区分,也为了读者们的健康着想,这次就介绍到这里。

下一次的计划就是把制作好的清单和抽选出来的武将名称进行机器学习处理,这篇文章也仅仅只是想写写“孔明的圈套”。

为了把三国的世界进行机器学习处理,这次进行的处理是提高最后结果的精确度的关键。(有没有一种关羽千里走单骑,逐关击破的感觉?)

另外,Colaboratory + Janome + NEologd + 用户词典的配套使用方法如果能给进行自然语言处理的人提供新视角的话就再好不过了!毕竟在Web上面进行NEologd+用户词典的操作虽然简单,但是不论做多全面的调查也不能得到上述一样完整的说明。

欢迎大家踊跃尝试!

彩蛋~

最后,我们还去扒了一下作者的背景,发现这位日本小哥哥一直热衷于用机器学习解决一些有意思的问题,比如,此前他就因为利用AI对日本年号进行分析预测接受了富士电视台的专访。

这里也简单介绍一下小哥哥(@youwht)的日本年号项目。

在Windows10 + Python3 +JupyterNotebook的前提之下,先利用条件(维基百科的文本信息、已使用的年号作为学习对象、小学水平的汉字易懂程度等)筛选到1006×1006的汉字组合,得到了包括“仁九”在内的备选年号,随后对被AI淘汰的候补年号进行再评判,从523个中筛选出了17个,再将这17个中的评判值TOP4加入最终候选。遗憾的是,小哥哥并没有公布完整预测。

不过后来在对已公布的新年号“令和”分析中发现,不管是单个字的评判值还是两个字的“距离感”,从构成法的角度上看,“令和”与“平成”十分相似(后者甚至达到0.3±的标准偏差)。是不是很厉害

原文发布于微信公众号 - CDA数据分析师(cdacdacda)

原文发表时间:2019-08-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券