口癖

この記事は はてなエンジニア Advent Calendar 2022 の5日目の記事です。
4日目は id:happy_siro さんでレスポンスタイムの分析 - happy_siro's blog でした。


口癖は自分では気付けないので難しい。

blog.sushi.money

この記事で紹介されていた新卒氏は自分のことで、いつのまにか「よかったでしょうか」というのが口癖になっていたらしい。

調べてみるとバイト敬語と言われるもので接客業のバイトをやっていたときに身についてしまったのかもしれない。

口癖を指摘されるまで「よかったでしょうか」の時制について考えることはもちろん、日本語としての違和感に気付いていなかった。

もしかしたら「しばくぞ」が口癖になっているかもしれない。


www.youtube.com


口癖を人から指摘されずに自分で気付く方法はないものか。

Google Scholar で調べると 音声対話におけるユーザ発話からの口癖検出 という面白い論文を見つけた。

この論文を参考に口癖を自動で検出してみる。
自身の文章を入力として、口癖と思われるフレーズを出力とする。

口癖の特徴

口癖の特徴を考えると次の4つが挙げられる。

1. その口癖を発する筆者はそれを頻繁に発する
2. その口癖を発する筆者は偏る
3. トピックに関係なく発せられる
4. 単位性(ひとかたまりのフレーズとして見なせる度合い) が強い

特徴1,2 は口癖の定義とも言える。

特徴3 はトピックとなる単語を含む会話を集めたときに下図のように、口癖はトピックに関わらず出現するということを表す。

特徴4 は例えば「よかっ」と「た」のように別れているよりは「よかった」のようにひとかたまりになっているほうが口癖の可能性が高いことを表す。

以上の特徴を数値化することで口癖らしさをスコアとして評価することができるようになり、口癖を検出することができる。

ストップワード、トピックワード、口癖フレーズ の分布(論文 から引用)

用語の確認

形態素解析n-gram

口癖はある文章を意味ごとに細かく分けて、それらを組み合わせることで表現できる。
例えば「温めてもよかったでしょうか」なら 温め/て/も/よかっ/た/でしょ/う/か のように分けられ、よかっ/た/でしょ/う/か を組み合わせることで「よかったでしょうか」という口癖を表現できる。

このように意味ごとに細かく分けることを形態素解析と呼び、「温め」のように分けられたものを形態素と呼ぶ。

また、形態素を組み合わせたものを n-gram と呼ぶ。nは何個の連続した形態素かを示し、上の例で n=3 なら 「温めても」、「てもよかっ」、「もよかった」、… と続く。

情報量

ある情報が得られたときにその情報の重要性を評価したい。

情報理論では情報の重要性を、ある事象が起こったときにそれがどれだけ起こりにくいかどうかで評価する。
つまり、ある事象 xが起こる確率 P(x)が低いほど情報の量としては大きいとする。
さらに情報を足し合わしていけると何かと都合がいい。

これらの性質を満たすために、事象 xが起こったことを知らされたときに受け取る選択情報量 I(x)を次のように定義する。

$$
I(x) = -\log_2 P(x)
$$

また、情報の重要性は自身が持つ情報によって変化する。
自身が持つ情報によって選択肢が絞られているときに更に選択肢を絞る情報よりも、選択肢が一切絞られていないときに選択肢を絞る情報が得られたほうが嬉しい。
選択肢を絞る情報は、絞られた選択肢の持つ情報量が平均どれぐらい得られるか、つまり選択情報量の期待値によって評価できる。

事象 xが起こったときの選択情報量 I(x)の期待値をエントロピー H(X)と呼び、次のように定義する。

$$
H(X) = -\sum_{i=1}^{N} P(x_i) \times \log_2 P(x_i)
$$

以下のサイトは具体例を踏まえて説明されているのでわかりやすい。
logics-of-blue.com

口癖の特徴を定式化する

口癖は形態素を組み合わせた形態素列で表現できるので形態素列に対して口癖のスコアを算出する。

特徴1

その口癖を発する筆者 sはそれを頻繁に発するとは、形態素 wが筆者 s自身の文章中に出現する頻度 tf(w,s)が高く、かつ、他の筆者が形態素 wを出現させる頻度よりも筆者 sが出現させる頻度が高いと言い換えることができる。

これを特徴1のスコアとして FP_1 とすると筆者 s_{\beta}' 形態素 w_{\alpha}'に対するスコアは次のようになる。

$$
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

その口癖を発する筆者は偏るとは、形態素 w_{\alpha}が出現したという条件のもとでその筆者が誰であるかの確率 P(s_{\beta}|w_{\alpha})が高まると言い換えられる。
身の回りに「しばくぞ」なんて言ってる人はいないので「しばくぞ」と聞くとジャルジャルの確率が高まるということ。
つまり、口癖を発する筆者が偏っている場合には、形態素 w_{\alpha} が出現したとき、その筆者が誰であるかのエントロピー H(S|w_{\alpha})が小さくなる。

これを特徴2のスコアとして FP_2 とすると形態素 w_{\alpha}'に対するスコアは次のようになる。

\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文があるときに「て」は左に「温め」、「買っ」右に「も」、「よかっ」が接続するので単位性が高いが、「よかっ」は左に「も」、「て」右に「た」、「た」が接続するので「て」よりは単位性が低い。
つまり、形態素 w_{\alpha}の左右に接続する形態素エントロピーが大きいほど単位性が高い。

これを特徴4のスコアとして FP_4とし、形態素 w_{\alpha}'の左に接続する形態素 L、右に接続する形態素 Rとすると次のようになる。

\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       の

なんと「しばくぞ」が口癖な奴の本当の口癖は「お前」のようだった。

ちなみに  FP_1 だけでも似たような結果になった。流石にこれだけ「しばくぞ」って言ってるので頻度だけでも検出できた模様。

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 上に公開して遊べるようにできたらよかったけど時間がなかった…

雑なコードですがよければ試してみてください。

github.com

明日は id:utgwkk さんです。