口癖
この記事は はてなエンジニア Advent Calendar 2022 の5日目の記事です。
4日目は id:happy_siro さんでレスポンスタイムの分析 - happy_siro's blog でした。
口癖は自分では気付けないので難しい。
この記事で紹介されていた新卒氏は自分のことで、いつのまにか「よかったでしょうか」というのが口癖になっていたらしい。
調べてみるとバイト敬語と言われるもので接客業のバイトをやっていたときに身についてしまったのかもしれない。
口癖を指摘されるまで「よかったでしょうか」の時制について考えることはもちろん、日本語としての違和感に気付いていなかった。
もしかしたら「しばくぞ」が口癖になっているかもしれない。
口癖を人から指摘されずに自分で気付く方法はないものか。
Google Scholar で調べると 音声対話におけるユーザ発話からの口癖検出 という面白い論文を見つけた。
この論文を参考に口癖を自動で検出してみる。
自身の文章を入力として、口癖と思われるフレーズを出力とする。
口癖の特徴
口癖の特徴を考えると次の4つが挙げられる。
1. その口癖を発する筆者はそれを頻繁に発する
2. その口癖を発する筆者は偏る
3. トピックに関係なく発せられる
4. 単位性(ひとかたまりのフレーズとして見なせる度合い) が強い
特徴1,2 は口癖の定義とも言える。
特徴3 はトピックとなる単語を含む会話を集めたときに下図のように、口癖はトピックに関わらず出現するということを表す。
特徴4 は例えば「よかっ」と「た」のように別れているよりは「よかった」のようにひとかたまりになっているほうが口癖の可能性が高いことを表す。
以上の特徴を数値化することで口癖らしさをスコアとして評価することができるようになり、口癖を検出することができる。
用語の確認
形態素解析とn-gram
口癖はある文章を意味ごとに細かく分けて、それらを組み合わせることで表現できる。
例えば「温めてもよかったでしょうか」なら 温め/て/も/よかっ/た/でしょ/う/か
のように分けられ、よかっ/た/でしょ/う/か
を組み合わせることで「よかったでしょうか」という口癖を表現できる。
このように意味ごとに細かく分けることを形態素解析と呼び、「温め」のように分けられたものを形態素と呼ぶ。
また、形態素を組み合わせたものを n-gram と呼ぶ。nは何個の連続した形態素かを示し、上の例で n=3 なら 「温めても」、「てもよかっ」、「もよかった」、… と続く。
情報量
ある情報が得られたときにその情報の重要性を評価したい。
情報理論では情報の重要性を、ある事象が起こったときにそれがどれだけ起こりにくいかどうかで評価する。
つまり、ある事象が起こる確率が低いほど情報の量としては大きいとする。
さらに情報を足し合わしていけると何かと都合がいい。
これらの性質を満たすために、事象が起こったことを知らされたときに受け取る選択情報量を次のように定義する。
$$
I(x) = -\log_2 P(x)
$$
また、情報の重要性は自身が持つ情報によって変化する。
自身が持つ情報によって選択肢が絞られているときに更に選択肢を絞る情報よりも、選択肢が一切絞られていないときに選択肢を絞る情報が得られたほうが嬉しい。
選択肢を絞る情報は、絞られた選択肢の持つ情報量が平均どれぐらい得られるか、つまり選択情報量の期待値によって評価できる。
事象が起こったときの選択情報量の期待値をエントロピーと呼び、次のように定義する。
$$
H(X) = -\sum_{i=1}^{N} P(x_i) \times \log_2 P(x_i)
$$
以下のサイトは具体例を踏まえて説明されているのでわかりやすい。
logics-of-blue.com
口癖の特徴を定式化する
口癖は形態素を組み合わせた形態素列で表現できるので形態素列に対して口癖のスコアを算出する。
特徴1
その口癖を発する筆者はそれを頻繁に発するとは、形態素が筆者自身の文章中に出現する頻度が高く、かつ、他の筆者が形態素を出現させる頻度よりも筆者が出現させる頻度が高いと言い換えることができる。
これを特徴1のスコアとして とすると筆者の形態素列に対するスコアは次のようになる。
$$
FP_1(w_{\alpha}') = tf(w_{\alpha}', s_{\beta}') \times \frac{tf(w_{\alpha}', s_{\beta}')}{\sum_{\beta=1}^{N_s} tf(w_{\alpha}', s_{\beta})}
$$
Python で書くと次のようになる。
# 特徴1のスコア # s_id_to_w_to_count: 筆者idとその筆者の形態素列と回数 # w: 形態素 # s_id: 筆者id def calc_fp1(s_id_to_w_to_count: dict[str, dict[str, int]], w: str, s_id: str) -> float: # 筆者が形態素を出現させた頻度 def calc_tf(s_id_to_w_to_count: dict[str, dict[str, int]], w: str, s_id: str) -> float: total_w_count = sum(s_id_to_w_to_count[s_id].values()) return s_id_to_w_to_count[s_id][w] / total_w_count all_s_tf = 0 for _s_id in s_id_to_w_to_count.keys(): all_s_tf += calc_tf(s_id_to_w_to_count, w, _s_id) return pow(calc_tf(s_id_to_w_to_count, w, s_id), 2) / all_s_tf if all_s_tf > 0 else 1
特徴2
その口癖を発する筆者は偏るとは、形態素列が出現したという条件のもとでその筆者が誰であるかの確率が高まると言い換えられる。
身の回りに「しばくぞ」なんて言ってる人はいないので「しばくぞ」と聞くとジャルジャルの確率が高まるということ。
つまり、口癖を発する筆者が偏っている場合には、形態素列 が出現したとき、その筆者が誰であるかのエントロピーが小さくなる。
これを特徴2のスコアとして とすると形態素列に対するスコアは次のようになる。
\begin{eqnarray}
\begin{split}
FP_2(w_{\alpha}') &= \frac{1}{H(S|w_{\alpha}')+1} \\\
&= \frac{1}{-\sum_{\beta=1}^{N_s} P(s_{\beta} | w_{\alpha}') \log_2 P(s_{\beta} | w_{\alpha}') + 1}
\end{split}
\end{eqnarray}
エントロピーは小さくなるがスコアとしては大きくしたいのでエントロピーの逆数を取っている。
Python で書くと次のようになる。
# 特徴2のスコア def calc_fp2(s_id_to_w_to_count: dict[str, dict[str, int]], w: str) -> float: s_count = 0 for s_id in s_id_to_w_to_count.keys(): for _w in s_id_to_w_to_count[s_id].keys(): if _w == w: s_count += 1 break return 1 / (-s_count * ((1 / s_count) * (math.log2(1 / s_count))) + 1) if s_count > 0 else 1
特徴3
口癖の候補として任意の形態素列を入力としたときに、トピックワードが何であるかはわからない。
さらに、トピックワードを含んだ文章を用意するために大量の文章を用意する必要があると考えられる。
そのため今回は特徴3は利用しないことにした。
特徴4
単位性(ひとかたまりのフレーズとして見なせる度合い) が強いとは、ある形態素の左右に接続する形態素がコロコロ変わる場合はひとかたまりとしてみなすことができると言い換えられる。
例えば温め/て/も/よかっ/た/でしょ/う/か
や買っ/て/よかっ/た
の2文があるときに「て」は左に「温め」、「買っ」右に「も」、「よかっ」が接続するので単位性が高いが、「よかっ」は左に「も」、「て」右に「た」、「た」が接続するので「て」よりは単位性が低い。
つまり、形態素列の左右に接続する形態素のエントロピーが大きいほど単位性が高い。
これを特徴4のスコアとしてとし、形態素列の左に接続する形態素を、右に接続する形態素をとすると次のようになる。
\begin{eqnarray}
\begin{split}
FP_4(w_{\alpha'}) &= \sqrt{H(L|w_{\alpha}') \times H(R|w_{\alpha}')} \\\
&= \sqrt{-\sum_x P(l_x|w_{\alpha}') \log_2 P(l_x|w_{\alpha}') \times -\sum_y P(r_y|w_{\alpha}') \log_2 P(r_y|w_{\alpha}')}
\end{split}
\end{eqnarray}
Python で書くと次のようになる。
# 特徴4のスコア # s_id_to_w_to_left_right: 筆者idとその筆者の形態素列と左右の形態素 def calc_fp4(s_id_to_w_to_left_right: dict[str, dict[str, list[str, str]]], w: str) -> float: left_to_count = defaultdict(lambda: 0) right_to_count = defaultdict(lambda: 0) for w_to_left_right in s_id_to_w_to_left_right.values(): try: left_to_count[w_to_left_right[w][0]] += 1 right_to_count[w_to_left_right[w][1]] += 1 except KeyError: continue # 左と右でエントロピーを求める処理は同じなので関数にしている def calc_entropy(x_to_count: dict[str, int]) -> float: total = sum(x_to_count.values()) entropy = 0 for count in x_to_count.values(): entropy -= (count / total) * math.log2(count / total) return entropy left_entropy = calc_entropy(left_to_count) right_entropy = calc_entropy(right_to_count) return math.sqrt(left_entropy * right_entropy)
論文中では単位性が強い方が口癖のスコアとして高くなるとされているが、実際に実験してみたところ助詞、助動詞といった一文字の形態素のスコアが高くなり、形態素列はほとんどの場合でスコアが0になった。
し, fp4: 2.1382972078056883 ばく, fp4: 0.0 ぞ, fp4: 0.23172506026859618 お前, fp4: 0.0
なので今回はスコアが大きすぎる場合は逆数を取り、小さすぎる場合は大きくするという処理を独自に挟んだ。
(なにか間違えている可能性がありそうなので分かる人がいたら教えてください🙏)
処理の流れ
定式化の際に他の筆者の存在を仮定していたので他の筆者による文章が必要となる。
今回は様々な口調の人が存在するであろう Twitter をデータの収集元にした。
Twitter の検索 API を利用して他の筆者の文章を集めた。
筆者の入力文章の前処理として、n-gram を抽出して1回しか出現しない形態素列は口癖になりえないとして除外する。
そして前処理が完了した形態素列を入力として口癖スコアを算出し、スコアが高いものを順番に出力する。
以上を図にすると下図のようになる。
実験
せっかくなので「しばくぞ」が口癖な奴が本当に「しばくぞ」が口癖なのかを判定してみたい。
「しばくぞ」が口癖な奴が口癖を突っ込まれる2分26秒までを文字に起こすと次のようになる。
しばくぞお前何してんねん 何してんねん しばくぞお前何やねん挨拶って どっか行こか どこ行く しばくぞお前。何考えてんねん しばくぞお前。なんで俺がお前に高級寿司奢らなあかんねん しばくぞお前。一人で行って来い なんで俺がお前とハワイ行かなあかんねん。それやったらええ女連れて行くわ。しばくぞお前 おお、行ったん しばいたろかお前、なんでお前みたいなんがハワイ行けんねん。それやったら俺連れて行け。しばくぞお前 どう良かってん。しばくぞお前 しばくぞお前。彼女おるんかい。俺はおらんっちゅうねん。しばくぞお前 ワイキキ?やっばどんなんやったん しばくぞお前。包み隠さず言えよ 何泊で行ってん。しばくぞお前ちょっと考えたやろお前。短めで言うたやろ しばくぞお前。なんやねんテレビで見たままって。ほな俺テレビで見とけって言うんかい 俺はハワイ行かんでええ言うてるんやろ しばくぞお前。隠すなよ 旅費は? やっぱ1億くらい? しばくぞお前。ちゃんと突っ込めよ お前ボケたときちゃんと突っ込んだってんねんから 俺どこの国が合うかな しばくぞお前。なんで急にアフリカくんねん しばくぞお前。どこがやねん 実際どうなん しばくぞお前。どんだけ厚着して行かなあかんねん。オーロラ見に行くのかよ しばくぞお前。北極行くやつおるかい。あれ陸ないらしいで 日本やったらどこやねん しばくぞお前。絶対考えてるやん しばくぞお前。結局アフリカかい
さて気になる結果は…
1位: fp:0.024883435755113537 お前 2位: fp:0.023698510242965275 しばくぞお前 2位: fp:0.023698510242965275 ばくぞお前 2位: fp:0.023698510242965275 しばくぞ 2位: fp:0.023698510242965275 しばく 2位: fp:0.023698510242965275 ばくぞ 2位: fp:0.023698510242965275 ぞお前 2位: fp:0.023698510242965275 ばく 3位: fp:0.017773882682223956 お前。 4位: fp:0.016588957170075695 ばくぞお前。 4位: fp:0.016588957170075695 ぞお前。 4位: fp:0.016588957170075695 ねん 5位: fp:0.005924627560741319 。しばくぞ 5位: fp:0.005924627560741319 。しばく 5位: fp:0.005924627560741319 んねん 5位: fp:0.005924627560741319 。し 6位: fp:0.004739702048593054 やねん 6位: fp:0.004739702048593054 なんで 6位: fp:0.004739702048593054 ねん。 7位: fp:0.003554776536444791 なあかんねん 7位: fp:0.003554776536444791 あかんねん 7位: fp:0.003554776536444791 てんねん 7位: fp:0.003554776536444791 なあかん 7位: fp:0.003554776536444791 やったら 7位: fp:0.003554776536444791 んねん。 7位: fp:0.003554776536444791 なあか 7位: fp:0.003554776536444791 あかん 7位: fp:0.003554776536444791 ハワイ 7位: fp:0.003554776536444791 考え 7位: fp:0.003554776536444791 あか 7位: fp:0.003554776536444791 行か 8位: fp:0.002369851024296527 ぞお前。なんで 8位: fp:0.002369851024296527 なんで俺がお前 8位: fp:0.002369851024296527 ねん。それやっ 8位: fp:0.002369851024296527 。それやったら 8位: fp:0.002369851024296527 ばくぞお前何 8位: fp:0.002369851024296527 何してんねん 8位: fp:0.002369851024296527 お前。なんで 8位: fp:0.002369851024296527 それやったら 8位: fp:0.002369851024296527 行かなあかん 8位: fp:0.002369851024296527 あかんねん。 8位: fp:0.002369851024296527 んねん。それ 8位: fp:0.002369851024296527 てん。しばく 8位: fp:0.002369851024296527 してんねん 8位: fp:0.002369851024296527 なんで俺が 8位: fp:0.002369851024296527 ハワイ行か 8位: fp:0.002369851024296527 行かなあか 8位: fp:0.002369851024296527 ねん。それ 8位: fp:0.002369851024296527 。それやっ 8位: fp:0.002369851024296527 ぞお前何 8位: fp:0.002369851024296527 何してん 8位: fp:0.002369851024296527 。なんで 8位: fp:0.002369851024296527 なんで俺 8位: fp:0.002369851024296527 俺がお前 8位: fp:0.002369851024296527 それやっ 8位: fp:0.002369851024296527 てん。し 8位: fp:0.002369851024296527 アフリカ 8位: fp:0.002369851024296527 お前何 8位: fp:0.002369851024296527 してん 8位: fp:0.002369851024296527 がお前 8位: fp:0.002369851024296527 で行っ 8位: fp:0.002369851024296527 行かな 8位: fp:0.002369851024296527 連れて 8位: fp:0.002369851024296527 てん。 8位: fp:0.002369851024296527 んかい 8位: fp:0.002369851024296527 かい。 8位: fp:0.002369851024296527 俺が 8位: fp:0.002369851024296527 ええ 8位: fp:0.002369851024296527 連れ 8位: fp:0.002369851024296527 俺は 8位: fp:0.002369851024296527 やろ 9位: fp:0.0006447204319057814 ぞ 10位: fp:0.0002675563233333684 し 11位: fp:0.00025790888992516535 ん 12位: fp:0.00010561854804113384 何 13位: fp:9.72316271148717e-05 俺 14位: fp:6.422786517985336e-05 てん 15位: fp:6.330420830702513e-05 や 16位: fp:5.291939457057859e-05 かい 17位: fp:5.2846477176474456e-05 なん 18位: fp:4.591353240649171e-05 たら 19位: fp:4.0115179978702424e-05 やっ 20位: fp:3.503396634547579e-05 って 21位: fp:3.316092107408038e-05 行っ 22位: fp:3.2481089343882305e-05 行く 23位: fp:2.7095524361262053e-05 言う 24位: fp:1.984034359375621e-05 それ 25位: fp:1.9170077753354137e-05 よ 26位: fp:1.8541025843847835e-05 どこ 27位: fp:1.7091880486366047e-05 ちゃんと 28位: fp:1.667658053179636e-05 てる 29位: fp:1.6473745910520436e-05 何し 30位: fp:1.4794081644190184e-05 どう 31位: fp:1.4718410586707139e-05 おる 32位: fp:1.4352119903107544e-05 見 33位: fp:1.400667890475401e-05 。 34位: fp:1.3144862075663278e-05 。それ 35位: fp:1.2973821360465423e-05 な 36位: fp:1.2824476325764326e-05 か 37位: fp:1.1367411071292689e-05 たん 38位: fp:5.783272171341346e-06 た 39位: fp:5.627368741873755e-06 が 40位: fp:4.238800393230312e-06 で 41位: fp:3.129135324179867e-06 ? 42位: fp:3.07268739651848e-06 は 43位: fp:2.7071553320225555e-06 て 44位: fp:1.4853714017997028e-06 に 45位: fp:1.0476403351087891e-06 、 46位: fp:3.4206778211104284e-07 の
なんと「しばくぞ」が口癖な奴の本当の口癖は「お前」のようだった。
ちなみに だけでも似たような結果になった。流石にこれだけ「しばくぞ」って言ってるので頻度だけでも検出できた模様。
1位: fp:0.038112522686025406 お前 2位: fp:0.036297640653357534 しばくぞお前 2位: fp:0.036297640653357534 ばくぞお前 2位: fp:0.036297640653357534 しばくぞ 2位: fp:0.036297640653357534 しばく 2位: fp:0.036297640653357534 ばくぞ 2位: fp:0.036297640653357534 ぞお前 2位: fp:0.036297640653357534 ばく 3位: fp:0.02722323049001815 お前。 4位: fp:0.025408348457350276 ばくぞお前。 4位: fp:0.025408348457350276 ぞお前。 4位: fp:0.025408348457350276 ねん 5位: fp:0.021307170700754274 ぞ 6位: fp:0.00921638150321425 俺 7位: fp:0.009074410163339383 。しばくぞ 7位: fp:0.009074410163339383 。しばく 7位: fp:0.009074410163339383 んねん 7位: fp:0.009074410163339383 。し 8位: fp:0.007259528130671506 やねん 8位: fp:0.007259528130671506 なんで 8位: fp:0.007259528130671506 ねん。 9位: fp:0.0054446460980036296 なあかんねん 9位: fp:0.0054446460980036296 あかんねん 9位: fp:0.0054446460980036296 てんねん 9位: fp:0.0054446460980036296 なあかん 9位: fp:0.0054446460980036296 やったら 9位: fp:0.0054446460980036296 んねん。 9位: fp:0.0054446460980036296 なあか 9位: fp:0.0054446460980036296 あかん 9位: fp:0.0054446460980036296 ハワイ 9位: fp:0.0054446460980036296 考え 9位: fp:0.0054446460980036296 あか 9位: fp:0.0054446460980036296 行か 10位: fp:0.0050161181474970805 かい 11位: fp:0.004381377196180488 し 12位: fp:0.003629764065335753 ぞお前。なんで 12位: fp:0.003629764065335753 なんで俺がお前 12位: fp:0.003629764065335753 ねん。それやっ 12位: fp:0.003629764065335753 。それやったら 12位: fp:0.003629764065335753 ばくぞお前何 12位: fp:0.003629764065335753 何してんねん 12位: fp:0.003629764065335753 お前。なんで 12位: fp:0.003629764065335753 それやったら 12位: fp:0.003629764065335753 行かなあかん 12位: fp:0.003629764065335753 あかんねん。 12位: fp:0.003629764065335753 んねん。それ 12位: fp:0.003629764065335753 てん。しばく 12位: fp:0.003629764065335753 してんねん 12位: fp:0.003629764065335753 なんで俺が 12位: fp:0.003629764065335753 ハワイ行か 12位: fp:0.003629764065335753 行かなあか 12位: fp:0.003629764065335753 ねん。それ 12位: fp:0.003629764065335753 。それやっ 12位: fp:0.003629764065335753 ぞお前何 12位: fp:0.003629764065335753 何してん 12位: fp:0.003629764065335753 。なんで 12位: fp:0.003629764065335753 なんで俺 12位: fp:0.003629764065335753 俺がお前 12位: fp:0.003629764065335753 それやっ 12位: fp:0.003629764065335753 てん。し 12位: fp:0.003629764065335753 アフリカ 12位: fp:0.003629764065335753 お前何 12位: fp:0.003629764065335753 してん 12位: fp:0.003629764065335753 がお前 12位: fp:0.003629764065335753 で行っ 12位: fp:0.003629764065335753 行かな 12位: fp:0.003629764065335753 連れて 12位: fp:0.003629764065335753 てん。 12位: fp:0.003629764065335753 んかい 12位: fp:0.003629764065335753 かい。 12位: fp:0.003629764065335753 俺が 12位: fp:0.003629764065335753 ええ 12位: fp:0.003629764065335753 連れ 12位: fp:0.003629764065335753 俺は 12位: fp:0.003629764065335753 やろ 13位: fp:0.0030467599209508946 てん 14位: fp:0.0025683277854437066 言う 15位: fp:0.002513175909445225 ん 16位: fp:0.0017574648569438584 どこ 17位: fp:0.0016727629678686667 なん 18位: fp:0.0015615117385518855 何し 19位: fp:0.0014911352792868033 何 20位: fp:0.0013951271938273731 おる 21位: fp:0.0013257544591399014 やっ 22位: fp:0.0012459738388756816 。それ 23位: fp:0.001222633798260035 行っ 24位: fp:0.0011207814274909371 行く 25位: fp:0.0009411610289409479 それ 26位: fp:0.0008107829256619495 ちゃんと 27位: fp:0.0007669624158471428 や 28位: fp:0.0005392328136705962 たん 29位: fp:0.0005104795522110745 どう 30位: fp:0.0004714415730202617 。 31位: fp:0.0004582561837175598 たら 32位: fp:0.0002943944211996535 って 33位: fp:0.00026042727860818815 見 34位: fp:0.00024549611329815726 な 35位: fp:0.00017908574108095078 よ 36位: fp:0.0001616185257232793 か 37位: fp:0.00015388724937783776 てる 38位: fp:0.000150494132900177 が 39位: fp:0.00013042258242588594 た 40位: fp:0.00011586319057384735 で 41位: fp:7.479815198737928e-05 て 42位: fp:7.066903711236725e-05 は 43位: fp:4.4219479539916814e-05 に 44位: fp:3.72031809128523e-05 ? 45位: fp:2.4314173913104137e-05 、 46位: fp:1.2532293159445404e-05 の
終わりに
「しばくぞ」が口癖な奴の本当の口癖を発見できておもしろかった。
口癖によって人を混乱させていることもあると知ったので頑張って矯正していきましょう。
本当は Web 上に公開して遊べるようにできたらよかったけど時間がなかった…
雑なコードですがよければ試してみてください。
明日は id:utgwkk さんです。