はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】UnityでBag of Wordsベクトルに変換できるライブラリ「UniBagOfWords」を公開してみた(文章の類似度の実装も)

はじめに

タイトルの通り,UnityでBag of Wordsベクトルに変換できるライブラリUniBagOfWordsを公開しました。
github.com

これの使い方や応用として文章の類似度の計算をしてみたいと思います。

できること

f:id:hanaaaaaachiru:20210121212332p:plain
できること

こちらの画像の通り,入力文に予め設定した語彙の中で登場した単語の出現回数をベクトル化して出力してくれます。

Bag of Wordsに関しては以下の記事が参考になると思います。
qiita.com

自然言語の世界でよく用いられ,文章の類似度や機械学習の入力にする等の使い道もあるそうです。

導入方法

まずは以下のリンクを開き,UnityBagOfWords.unitypackageをダウンロードします。(スターも良ければ)
github.com

f:id:hanaaaaaachiru:20210121210635p:plain
導入方法

後はUnityにインポートすればOKです。

使い方

// 使用例1
async void Start()
{
    // テキストデータセット
    string[] sentences = new[]
    {
        "私はラーメンが好きです。",
        "私は餃子が好きです。",
        "私はラーメンが嫌いです。"
    };

    // 1. まずは単語辞書を作る必要があります
    Vocabulary vocabulary = await Vocabulary.Create(sentences);

    // 2. 先程生成した単語辞書を使ったBoWベクトル変換器を作ります
    BagOfWordsConverter converter = new BagOfWordsConverter(vocabulary);

    // 3. 好きな文章をBoW変換します
    int[] bowVec = await converter.ConvertAsync("私はラーメンが嫌いです。");

    // BoWベクトル(テキスト中に単語が出現した回数を並べたもの)
    // 1,1,1,1,0,1,1,0,1
    Debug.Log(string.Join(",", bowVec));

    // 単語辞書の単語と順番
    // 私,は,ラーメン,が,好き,です,。,餃子,嫌い
    Debug.Log(string.Join(",", vocabulary.Words));
}
// 使用例2
private async void Start()
{
    // キャンセル用のトークン生成して利用することもできます
    CancellationTokenSource cts = new CancellationTokenSource();

    string sentene = "私はラーメンが嫌いです。";

    // 形態素解析も行えます
    Morpheme[] morphemes = await MorphAnalyzerClient.AnalyzeAsync(sentene, cts.Token);

    // 私,は,ラーメン,が,嫌い,です,。
    Debug.Log(string.Join(",", morphemes.Select(morpheme => morpheme.Surface).ToArray()));

    // 自身で作成した形態素のコレクションを用いて,Vocabularyを作成することもできます(特定の品詞に限定したりはmorphemesに対して処理をすれば良い)
    Vocabulary vocabulary = new Vocabulary(morphemes)

    // 作成したVocabularyでBoWベクトルに変換します
    BagOfWordsConverter converter = new BagOfWordsConverter(vocabulary);
    int[] bowVec = await converter.ConvertAsync("私は嫌い。", cts.Token);

    // 1,1,0,0,1,0,1
    Debug.Log(string.Join(",", bowVec));
}

細かい仕様はGitHubを参照してみてください。

応用(文章類似度の計算)

今回はこのライブラリを使った応用として、文章の類似度を調べてみたいと思います。

手順としては以下のように行います。

  1. 2つの文章をUniBagOfWordsを用いてベクトル変換
  2. cos類似度を求める
  3. 出力の値が1に近いほど同じ文章である

cos類似度の求め方は以下のサイトを参照してみてください。
コサイン類似度 | 高校数学の美しい物語

using UniBagOfWords;
using UnityEngine;

public class SampleUseCase : MonoBehaviour
{
    private async void Start()
    {
        // テキストデータセット
        string[] sentences = new[]
        {
            "私は犬が欲しい",
            "僕は犬を飼いたい"
        };

        // 1. まずは単語辞書を作る必要があります
        Vocabulary vocabulary = await Vocabulary.Create(sentences);

        // 2. 先程生成した単語辞書を使ったBoWベクトル変換器を作ります
        BagOfWordsConverter converter = new BagOfWordsConverter(vocabulary);

        // 3. 好きな文章をBoW変換します
        int[] vec1 = await converter.ConvertAsync("私は犬が欲しい");
        int[] vec2 = await converter.ConvertAsync("僕は犬を飼いたい");

        // 4. cos類似度を求める(数値計算ライブラリを使ったりGPUで計算をさせたりと工夫するとなお良いと思います)
        float vec1Length = 0, vec2Length = 0;
        float cosSimilarity = 0;

        for (int i = 0; i < vocabulary.Count; i++)
        {
            cosSimilarity += vec1[i] * vec2[i];
            vec1Length += vec1[i] * vec1[i];
            vec2Length += vec2[i] * vec2[i];
        }

        vec1Length = Mathf.Sqrt(vec1Length);
        vec2Length = Mathf.Sqrt(vec2Length);
        cosSimilarity = cosSimilarity / (vec1Length * vec2Length);

        // 5. 出力
        Debug.Log(cosSimilarity);       // 0.3651484
    }
}

さいごに

Unityの多次元ベクトルの内積を計算するにはどういうコードを書けばいいのかは結構疑問ではあります。

数値計算ライブラリを導入するのも手ではありますが、Unityに最適化したいならCompute Shaderが一番いいのですかね。

そこらへんを比較してみるのも割と面白そうなので暇があったらやってみようかなと思ったり。

ではまた。