##内容
一度これまでやったことを整理しながら、自分の考えも整理し、今後の方針を明確化する。現状で最適と判断した方法を紹介する。まずテーマとして、「韻」を身近に感じて欲しいと思っている。そこで、自分が普段使っている言葉で韻が踏めれば楽しいのではないかと考えた。韻の検索機能のようなものも考えたが、それだとみんな同じ韻を踏むことになって個性が失われるのではないか、という発想だ。普段思っていること、話していること、そういったものを箇条書きしたようなものが入力されたとき、「韻」の観点からそのデータを見ていこうという試みである。
##データの前処理
from pykakasi import kakasi
import re
import numpy as np
with open("./gennama.txt","r", encoding="utf-8") as f:
data = f.read()
kakasi = kakasi()
kakasi.setMode('J', 'K')
kakasi.setMode('H', 'K')
conv = kakasi.getConverter()
#カタカナ変換。(主目的は漢字を読みにする。eイの変換もしやすくなる。)
text_data = conv.do(data)
#eイ→ee,oウ→ooのように変換したテキストを得る
def expansion(text_data):
text_data_len = len(text_data)
text_data = text_data.replace("イイ", "イi").replace("ウウ","ウu")
text_data = text_data.split("イ")
new_text_data = []
kakasi.setMode('K', 'a')
conv = kakasi.getConverter()
for i in range(len(text_data)):
if len(text_data[i]) > 0:
if ("e" in conv.do(text_data[i][-1])):
new_text_data.append(text_data[i] + "e")
else:
new_text_data.append(text_data[i] + "i")
text_data = "".join(new_text_data).split("ウ")
new_text_data = []
for i in range(len(text_data)):
if len(text_data[i]) > 0:
if ("o" in conv.do(text_data[i][-1])):
new_text_data.append(text_data[i] + "o")
else:
new_text_data.append(text_data[i] + "u")
return "".join(new_text_data)[:text_data_len]
#スペース等の区切りは意味があるものとし、分割。
def ngram(text_data, N):
#全角スペース、改行は分割
text_data = re.split("\u3000|\n", text_data)
ngram = []
for text in text_data:
if len(text) < N:
ngram.append(text)
else:
for i in range(len(text)-N+1):
row = "".join(text[i:i+N])
ngram.append(row)
return ngram
#テキストの分割、変形
ex_text_data = expansion(text_data)
n = 5
n_text_data = ngram(text_data, n)
n_ex_text_data = ngram(ex_text_data, n)
dic_text_data = {k:v for k,v in enumerate(n_text_data)}
kakasi.setMode('K', 'a')
conv = kakasi.getConverter()
#ローマ字変換→母音のみ残し、キーに連番を与えて辞書にする。
vowel_text_data = [conv.do(t) for t in n_text_data]
vowel_text_data = [re.sub(r"[^aeiou]+","",text) for text in vowel_text_data]
vowel_ex_data = [conv.do(t) for t in n_ex_text_data]
vowel_ex_data = [re.sub(r"[^aeiou]+","",text) for text in vowel_ex_data]
dic_vo_t = {k:v for k,v in enumerate(vowel_text_data)}
dic_vo_ex = {k:v for k,v in enumerate(vowel_ex_data)}
ここではまずテキストを全てカタカナに変換している。漢字を読みにすることで(読みは辞書能力に依存)、分割のバリエーションが増えるという考えと、expansion
という部分で、韻を拡張させて捉えやすくなる。分割方法はN文字分割が最良と考える。例えば「多い(ooi)」から「遠い(ooi)」は思い付きやすいが、「テクノロジー(euooi)」や「その見方(ooi aa)」は思い付きにくいはずだ。品詞縛りや分節分けでは見逃しそうな部分をカバー出来ると考えている。
分割した後にローマ字変換、母音のみ残すようにして連番を与えているので、単語IDのようなものになり、3種のdicの要素にそのIDでアクセス出来る。
##韻の捉え方
#短い方のwordをスライスし、他方にそれが含まれていた場合「韻」と見なし、その長さをスコアとして加算する
def create_weight(word_a, word_b):
weight = 0
if len(word_a) > len(word_b):
for i in range(len(word_b)):
for j in range(len(word_b) + 1):
if word_b[i:j] in word_a and len(word_b[i:j]) > 1 and not word_a==word_b:
weight += len(word_b[i:j])
else:
for i in range(len(word_a)):
for j in range(len(word_a) + 1):
if word_a[i:j] in word_b and len(word_a[i:j]) > 1 and not word_a==word_b:
weight += len(word_a[i:j])
return weight
#各dicを渡し、インデックスがnode,値がedge,重みがweightの(node,node,weight)を作る。
def create_edge_list(dic_text_data, dic_vo_t, dic_vo_ex):
edge_list = []
for i in dic_text_data.keys():
for j in dic_text_data.keys():
text_weight = create_weight(dic_text_data[i],dic_text_data[j])
vo_weight = create_weight(dic_vo_t[i],dic_vo_t[j])
ex_weight = create_weight(dic_vo_ex[i],dic_vo_ex[j])
#比率変えるのも有りかと。
weight = text_weight*1.0 + vo_weight*1.0 + ex_weight*1.0
#ここは閾値作って良いかも。また、i-jもスコア加算しても良いかも。
if weight != 0:
edge_list.append((i,j,weight))
return edge_list
edge_list = create_edge_list(dic_text_data, dic_vo_t, dic_vo_ex)
母音の一致は結局最初に使っていたものが最適と思う。例えば「aiueo」の「iu,ue,iue」を取り出すには、前方や後方から母音の一致を見ただけでは不十分であるからだ。当初target_wordという任意の言葉と入力データを比べることを考えていたが、入力データ内で完結させるのが良いと判断した。そして、ここで行っていることは分割した各wordの関係性や類似度を見ていることになるので、グラフを見ることにする。また、weightには3種のdicを用いることで、子音等の一致、母音の一致、韻を拡張させた場合の母音の一致を足し合わせたものにしている。
import networkx as nx
import matplotlib.pyplot as plt
G = nx.Graph()
G.add_weighted_edges_from(edge_list)
pos = nx.spring_layout(G)
nx.draw_networkx_edges(G, pos)
plt.figure(figsize=(20,20))
plt.show()
見ての通り何のことか分からない。N文字分割なので、隣り合うword=nodeはどうしても繋がりがある。今考えていることは、create_edge_list
と名付けた関数の中でコメントアウトしているようなことだ。ひとまずこれでクラスタリングしてみると
from networkx.algorithms.community import greedy_modularity_communities
com = list(greedy_modularity_communities(G, weight="weight"))
for i in range(len(com)):
if len(com[i]) > 2:
p = com[i]
print("コミュニティ:"+str(i))
for k in p:
print(dic_text_data[k])
良い部分もあるが、分割が少しずれたものが出現するのがもどかしい。モジュール性の高いクラスタリングとなれば、各コミュニティ内の繋がりが強く(韻が踏める)、コミュニティ同士の繋がりは弱い(他コミュニティとは韻が踏めない)ということになり、各コミュニティ毎にワンフレーズづつぐらい踏めるような気がする。それにしてはエッジが出過ぎている感は否めない。
##まとめ
text_data_dicで一致を見るのは必要ない。やるなら母音が一致しているときに、さらに子音が一致していれば加算する方法が良い。クラスタリングとN文字分割が相性悪いと思われる。ただ、クラスタリングすることが最終目標で良いのではないかと感じた。今後の方針としては、分割手法をもう一度考え直すか、ノード間の距離が一定以上離れていなければ繋げないか等の改良方法の模索と、分割でずっと悩まされるので、いっそ分割せずに比べていく手法を考えようと思う。何にせよ1歩進んで1歩下がっている感は否めないが…