
この記事は every Tech Blog Advent Calendar 2025 の15日目の記事です。
はじめに
こんにちは!
開発1部デリッシュキッチンの蜜澤です。
今回はクラスタリングとcos類似度を用いて表記揺れ辞書を作成してみたので、どのように作成したかを紹介させていただきます。
本記事では具体的なコードは記載せず、実際に行った手順の紹介のみになります。
やりたいこと
デリッシュキッチンでユーザーが検索したワードの中で意味が同じものをまとめて、同じワードに変換するための辞書を作成します。
ユーザーが実際に検索したワード(sub)と統一した表記(main)が格納された以下のような辞書が今回作成したいものになります。
| sub | main |
|---|---|
| 竜田揚げ | 竜田揚げ |
| 竜田あげ | 竜田揚げ |
| たつたあげ | 竜田揚げ |
課題
一定の検索回数以上だったワード約17000ワードを対象に、表記揺れ辞書の作成を行うため、人力で行うと膨大な時間がかかってしまいます。
生成AIを使うにしても一気に約17000ワードを渡す必要があるため、処理にかなり時間がかかることが予想されます。
今回試した方法
前述の課題を踏まえて、今回は以下のような手順で、クラスタリングとcos類似度を使用して表記揺れ辞書の作成を試みました。
1.対象ワードを全てひらがなに変換
2.ひらがな変換したワードをベクトル化
3.ベクトルに対してクラスタリングを実施
4.クラスタごとに、cos類似度でグループ分け
1.対象ワードを全てひらがなに変換
「竜田揚げ」と「たつたあげ」のように漢字とひらがなの表記は最終的には同じグループにしたいのですが、そのままベクトル化してしまうと、離れてしまい、同じクラスタにすらならない可能性があるため、まずは全ワードにひらがなを振ります。
OpenAI APIを利用して、以下のように元のワードをひらがなに変換したカラムを作成しました。
| word | word_hira |
|---|---|
| 竜田揚げ | たつたあげ |
| 竜田あげ | たつたあげ |
| たつたあげ | たつたあげ |
2.ひらがな変換したワードをベクトル化
1.で変換したひらがなのカラムを、OpenAI APIを利用してベクトル化します。
ベクトル化には既存のパッケージなどを利用しても良いですが、今回は手軽にできるAPI利用にしました。
3.ベクトルに対してクラスタリングを実施
2.で作成したベクトルに対して、総当たりでcos類似度を求めてしまうと計算量が膨大になってしまうので、総当たりの組み合わせ数を減らすために、まずはK-means法でクラスタリングを行います。
K-means法は最初にクラスタ数を決める必要があります。
今回はワードが約17000語あり、表記揺れのパターンは3~4つくらいになることが多いという経験則から、k=5000にしました。
かなり適当な決め方であり、最適なクラスタ数を求めたらもっと少なくなると思います。
しかし、今回はとにかく手軽に行いたい、かつ、クラスタリングはあくまでも大雑把に分けることが目的なので、クラスタ数の最適化は行いませんでした。
「竜田揚げ」が含まれるクラスターは以下のようになりました。
| word | word_hira | cluster_id |
|---|---|---|
| 竜田揚げ | たつたあげ | 0 |
| 竜田あげ | たつたあげ | 0 |
| たつたあげ | たつたあげ | 0 |
| 竜田 | たつた | 0 |
| たつた | たつた | 0 |
| たつくり | たつくり | 0 |
4.クラスタごとに、cos類似度でグループ分け
3.で作成されたクラスタごとにword_hiraのcos類似度行列を作成し、類似度の閾値を超えたものに対して同じgroup_idを振ります。
今回は処理時間のことも考えて、一度グループに割り当てられたワードは処理順が後のワードとの類似度の方が高くても、別のグループに再度割り当てられない設計にしたので、閾値を高く設定して、ミスマッチが減るようにしました。
cos類似度の閾値を0.8、0.9、0.95の場合で試した結果、0.9がちょうど良い結果になりました。
「たつたあげ」「たつた」「たつくり」がそれぞれ別のグループになっているので、理想的と言えます。
| word | word_hira | cluster_id | group_id |
|---|---|---|---|
| 竜田揚げ | たつたあげ | 0 | 1 |
| 竜田あげ | たつたあげ | 0 | 1 |
| たつたあげ | たつたあげ | 0 | 1 |
| 竜田 | たつた | 0 | 2 |
| たつた | たつた | 0 | 2 |
| たつくり | たつくり | 0 | 3 |
閾値を0.8にした場合は「たつた」と「たつたあげ」が同じグループになったり、「ごまだれ」と「ごまだれそうめん」が同じグループになったりしたので、条件が緩すぎました。
閾値を0.95にした場合は「なすとあつあげ」「なすあつあげ」が違うグループになってしまい、同じグループにしたい組み合わせが違うグループになってしまったので条件が厳しすぎました。
実行結果まとめ
今回1.~4.の手順を実行した結果、17354ワードが11886グループに分かれました。
グループごとにwordの中から統一後の表記にするものを決めれば、今回作成したかった表記揺れ辞書が作成できる状態にできました。
1つしかワードがないグループがかなり多い結果となりましたが、検索回数が少ないワードに関しては他に表記揺れパターンがない場合も多々あるので、ある程度納得できました。
最も肝心な精度に関しては、全グループを目視で確認したわけではないので体感にはなってしまいますが、8割程度はあっているものが作成できたと思います。
現状の表記揺れ辞書の運用では、最終的には人が目視で内容を確認する工程を入れているため、叩き台を作ろうくらいの気持ちでの試みだったので、十分な精度かなと思います。
今後の課題
今回のクラスタリングとcos類似度を用いたやり方で多くのワードの表記揺れ辞書の作成はできますが、以下のような対応しきれないパターンもいくつかありました。
- 意味的に(ほぼ)同じもの
- 「パスタ」「スパゲッティ」
- 組み合わせワードの順不同対応
- 「大根と豚肉」「豚肉と大根」
- 小文字と大文字
- 「 きゃべつ」「きやべつ」
- 濁点
- 「たつくり」「たづくり」
これらのパターンはクラスタリングの時点で違うグループになってしまうので、別のアプローチを試す必要があります。
特に「パスタ」と「スパゲッティ」を同じグループにするのはかなり大変なのではないかと思っています。
まとめ
本記事ではクラスタリングとcos類似度を用いて、多数のワードの中から表記揺れ辞書を作成する方法を紹介させていただきました。
体感で8割くらいあっている辞書を作成できたものの、目視確認なしで運用できるほどの精度ではなかったので、今後も良いやり方がないかを考えていきたいと思います。
最後まで読んでいただきありがとうございました。
表記揺れの対応に苦しんでいる方の一助になれたら幸いです!