赞
踩
大数据文摘出品
来源:Qiita
编译:李欣月、刘俊寰
作为中国四大名著之一,三国的故事自然备受国人喜爱和追捧,但是谁又能想到三国竟然在日本也“出了圈”,举个例子,吴宇森导演的电影《赤壁》在日本的票房收入超过国内,同时该电影也是日本影史上票房最高的华语电影。
不仅在影视行业,三国因为其同时具有历史、策略、动作、人物等众多元素,在日本游戏行业的发展势头也相当迅猛,其中日本光荣株式协会就开发了包括《三国志》在内的一系列游戏,备受好评。
人物是三国故事经久不衰的关键因素之一,出场的众多复杂人物到底孰优孰劣成为三国游戏得以建立的前提,但是从小说本身来看,它在文本意义上果真如此吗?
怀着这样的疑问,一个日本小哥哥(@youwht)就利用AI对三国中的人物进行了一次深扒,通过“自然语言处理”和“机器学习”来分析三国文本,更新了三国游戏中的武将排名,得出了很有趣的结论,比如,张飞比关羽还能打,武力值高居榜首,而在“政治谋略”上,诸葛亮则比两大主公——曹操和刘备都更胜一筹,而姜维作为诸葛亮的传人,也被分析出是最接近诸葛亮的人物。
不仅分析出的结果好玩,这位作者的语言风格也非常有意思,代码外还有各种人物内心戏乱入,简直还原了一个懂AI的三国小剧场。
大数据文摘试图原文还原这篇有趣的blog,一起看看,这位热爱三国的日本程序员,是如何用AI帮三国武将们重新徘名的。
有人只看图就能分辨出谁是谁吗?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
比较接近诸葛亮的人是谁?
刘备拥有关羽,那么曹操拥有谁呢?
孙权有鲁肃辅佐,刘备又对应谁呢?
将吉川英治《三国》中出现的特有名称进行形态分析后以单词为单位区隔开。
区隔后的结果用Word2Vec进行矢量化。(Word2Vec:能够以N维矢量表现单词,并进行加法运算等运算的技术。提前阅读一下 “陌生人”的反义词是“白色恋人”这篇文章可能会更好理解。附链接:https://qiita.com/youwht/items/f21325ff62603e8664e6)
此时这些武将已经被矢量化了,如果要从这些矢量中得出相关性高的矢量(复数矢量的集合体),需要多少个算式才能计算出接近KOEI三国志的参数呢?其实最大的难关是第一步的形态分析,因此必须尽可能正确地去认识三国。
韩玄,刘度,赵范,金旋(AI知道我们荆州四杰么?)
玄德=刘玄德=刘备玄德⇒“刘备”
Colaboratory:在浏览器上可以免费使用的Python运行环境
Janome:环境构建轻松的形态分析器
Word2Vec:将自然语言数值化/矢量化的模型
GoogleDriveのマウント
# これを実行すると、認証用URLが表示されて、キーを入力すると
# 「drive/My Drive/」の中に、認証したアカウントのgoogle driveが入るfrom google.colab import drivedrive.mount('/content/drive')
Janomeのインストール
!pip install 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+neologdのインストール
#結構時間がかかる(6分くらい)
#Mydrive上の、先程のjanome+neologdのパスを指定する
#最新版とファイル名が一致しているかどうかは各自で確認すること!pip install "drive/My Drive/Janome-0.3.9.neologd20190523.tar.gz" --no-compile
インストール実行結果の末尾
#WARNING: The following packages were previously imported in this runtime:
# [janome]
#You must restart the runtime in order to use newly installed versions.
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))
実行結果
['劉備', '関羽', '張飛', '三', '人', '桃園', '義兄弟', '契り', '結ぶ']['悪来', '典韋', '許褚', 'ため', '愚弄', 'する', 'れる', '烈火', '憤る']['田豊', '沮授', '許', '収', '顔良', '審配', '郭図', '文醜', '錚々たる', '人材', 'ある']['鎮', '後将軍', '南陽', '太守', '袁術', '字', '公路', '筆頭', '二', '鎮', '冀州', '刺史', '韓', '馥', '三', '鎮', '予州', '刺史', '孔', '伷', '四',
人名リストの読み込み
#人物の名前が列挙してあるテキストから、ワードリストを作成する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['張楊', '張虎', '張闓', '張燕', '張遼']
因为马忠同名同姓,所以放弃了区分;
“乔瑁”在wiki上不存在所以之后追加;
“張繍”“張繡”微妙的字体不同;
“祝融夫人”变更为“祝融”。
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))
実行結果
['劉備', '関羽', '張飛', 'の', '三', '人', '桃園', '義兄弟', '契り', '結ぶ']['悪来', '典韋', '許褚', 'ため', '愚弄', 'する', 'れる', '烈火', '憤る']['田豊', '沮授', '許', '収', '顔良', '審配', '郭図', '文醜', '錚々たる', '人材', 'ある']['鎮', '後将軍', '南陽', '太守', '袁術', '字', '公路', '筆頭', '二', '鎮', '冀州', '刺史', '韓馥', '三', '鎮', '予州', '刺史', '孔伷', '四', '鎮', '兗
あざな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)))
実行結果
これは予州の太守劉備が義弟の関羽字は関羽なり趙雲は、白馬を飛ばして、馬上から一気に彼を槍で突き殺した。趙雲も、やがては、戦いつかれ、劉備も進退きわまって、すでに自刃を覚悟した時だった。
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['いま', 'いや', 'いろいろ', 'うち', 'おおまか']
鶏肋ワードの除去機能を実装する
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)
実行結果
['彼', '予州', '太守', '劉備', '義弟', '関羽', '字', '関羽', 'なり', '彼', '劉備', 'の', '義兄弟']['予州', '太守', '劉備', '義弟', '関羽', '関羽', 'なり', '劉備', 'の', '義兄弟']
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')
全文の形態素解析
%%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])
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。