内容
前回入力データの分割方法に改良が必要なのでは?と感じたため、様々な分割方法を試してみる。入力データは前回同様某ラッパーの歌詞を使用。一応検証用として自分が温めていた韻をテーマにしたものも用意している。
分かち書きの場合
import MeCab
mecab = MeCab.Tagger("-Owakati")
mecab_text = mecab.parse(data).split()
歌詞通りの「韻」を抽出できている部分もあるが、「数万円」で踏んでいるところが分かち書きによって「数、万、円」と分割されているため認識できていない。余談だが、kakashi
のconv.do
に一度に多くのデータは渡せないようだ。text_data = [conv.do(text) for text in mecab_text]
とした。ちなみに分かち書き後、母音に変換したwordの長さは最大8文字、平均2.16文字であった。細かく分け過ぎているので、分かち書きでの分割は適していないと言える。
このMeCabを使えるようにするまでに何回も挫けた。色んな記事を見過ぎたのかもしれない。こちらに感謝
N-gramの場合
では、単にN文字ごとに分割していくとどうなるだろう。Nは4から試してみる。
def make_ngram(words, N):
ngram = []
for i in range(len(words)-N+1):
#全角スペースと改行を取り除く
row = "".join(words[i:i+N]).replace("\u3000","").replace("\n","")
ngram.append(row)
return ngram
体感だが、Nは5かそれ以上が良さそうである。(4以下だとスコアの差が付かない)そこそこ歌詞通りに韻を検出できている。試しに検証用データを入れてみると、一見気づかない韻が検出できた。wordが様々な切り取られ方をするのだから、スコアの付け方を変えてみる。
def make_score_ngram(word_a, word_b):
score = 0
for i in range(len(word_a)):
if word_a[-i:] == word_b[-i:]:
score += i
return score
母音の一致を語尾から見ることによって、出力が見やすい。Nの値についてはlen(target_word_vo)
(韻を探す元の言葉の母音の長さ)が良いだろう。自分がやりたいことが表現できてしまったような気がしている。
折角苦労してMeCabを使用できるようにして、「韻の数値化」も自分なりに考えたので、使いたい。この2つを組み合わせてみる。
target_wordが絞れるのでは?!
「韻の数値化」では母音が合致する部分を探索し、合致している長さlen(word[i:j)
をスコアにしていた。このword[i:j]
は「eoi」等の形をしており、この出現回数をカウントすれば、入力データ中で一番登場する母音が分かるはずだ。それを含む言葉をtarget_word
に指定すれば、多くのレコメンドが見込めるという考えである。申し訳程度に分かち書きと検証用に用意したテキストを使う。
from pykakasi import kakasi
import re
from collections import defaultdict
import MeCab
with open("./test.txt","r",encoding="utf-8") as f:
data = f.read()
mecab = MeCab.Tagger("-Owakati")
mecab_text = mecab.parse(data).split()
kakasi = kakasi()
kakasi.setMode('H', 'a')
kakasi.setMode('K', 'a')
kakasi.setMode('J', 'a')
conv = kakasi.getConverter()
text_data = [conv.do(text) for text in mecab_text]
vowel_data = [re.sub(r"[^aeiou]+","",text) for text in text_data]
dic_vo = {k:v for k,v in enumerate(vowel_data)}
# voel_dataのインデックスで母音変換前のdataが分かるように辞書作成。
dic = {k:v for k,v in enumerate(mecab_text)}
# 新たなキーを追加する際に初期化を省きたいためdefaultdictを使用{母音:出現回数}
dic_rhyme = defaultdict(int)
for word_a in vowel_data:
for word_b in vowel_data:
if len(word_a) > len(word_b):
word_len = len(word_b)
for i in range(word_len):
for j in range(word_len + 1):
#カウントするのは2文字以上に限定
if word_b[i:j] in word_a and not len(word_b[i:j])<2:
dic_rhyme[word_b[i:j]] += 1
else:
word_len = len(word_a)
for i in range(word_len):
for j in range(word_len + 1):
if word_a[i:j] in word_b and not len(word_a[i:j])<2:
dic_rhyme[word_a[i:j]] += 1
# カウントが多かった順にソート
dic_rhyme = sorted(dic_rhyme.items(), key=lambda x:x[1], reverse=True)
print(dic_rhyme)
# dic_rhymeの上位にきたものが含まれているものを探索。ここでは"ai"を使用
bool_index = ["ai" in text for text in vowel_data]
for i in range(len(vowel_data)):
if bool_index[i]:
print(dic[i])
登場頻度の高い母音の並びを取得し、どこで使われているか出力できた。しかしながら、分かち書きで細分化された言葉は分かりにくくなっていた。もしかしたら、もう少し長い韻があった可能性もある。
改良点
target_wordを絞るという用途では必要性を感じないが(自分が一番言いたいことを指定したいため)、どの母音の並びが頻出なのかは確認できても良いかもしれない。分かち書きで上手くいかなかったが、MeCabを利用して(何度もいうが、使えるようになるまで四苦八苦した)改良したい。
また、N-gramを採用したことによって「韻の数値化」も簡潔にできたので、もう少し複雑に「韻」を定義し直せないか考える。(現状「っ」など考慮していない)
しかし、回り道をした。「韻の数値化」は韻を漏らさないよう、入力データに対応できるよう、自分なりに考え抜いたつもりのものだ。まさか引数を様々にスライスすることが入力データを様々にスライス(表現が違うかもしれません)で解決できるとは…というかそれに気付かないとは。基本が大事ですね。でもN-gramって意味のない日本語が出来ると感じませんか?ただ、「韻」の部分を強調して発音する等やり方はあるんです。やはり、簡単なデータ使ってとにかくやってみることが大事ってことですわ。