SentencePieceをCLionでコンパイルする

広告

最近SentencePieceというツールをいじっています。このツールについては「Sentencepiece : ニューラル言語処理向けトークナイザ」という記事で作者の方が解説をしてくださっています。ありがたや。

しかしながら、SentencePieceが出てしばらくの間は色々な記事が出ましたが、多くのケースではこのツールをどのように扱っていいか理解の外にある方が多かった印象です。単にMeCabの代替として用いようとしていたり、語彙数を256,000に設定したり、色々なケースが見られました。もちろん私も理解したとは言えませんので、あれこれいじって検証しているところです。一つ言えるのは、SentencePieceは賢いMeCabではないということです。似ているけど全く別のツールと言えます。

興味深いのは次の論文です。

4.1 Wordpiece Model

Our most successful approach falls into the second category (sub-word units), and we adopt the wordpiece model (WPM) implementation initially developed to solve a Japanese/Korean segmentation problem for the Google speech recognition system [35]. This approach is completely data-driven and guaranteed to generate a deterministic segmentation for any possible sequence of characters. It is similar to the method used in [38] to deal with rare words in Neural Machine Translation.

Google’s Neural Machine Translation System: Bridging the Gap between Human and Machine Translation

個人的に注目するのは

  • Wordpieceモデルはもともと日本語や韓国語の音声認識用に開発された
  • このアプローチは完全なデータドリブンである

というところです。

音声認識はいまスマートスピーカーが次々日本に上陸して熱い時期です。日本語をどのように認識するかは難しいテーマです。一方で韓国語は基本表音文字なので分割が適当でも読み方を迷うことがないと聞いています。例えば「参鶏湯」という料理があります。湯は中国語ではスープという意味で日本語の「お湯」という意味はありません。ですから参鶏湯を分割するとしたら「参鶏湯」という1つの単語か、「参鶏 湯」と分割するのが文法的には正しいと言えます。もちろん韓国語ではこれをハングル表記しますが、3文字のハングルで漢字と1:1対応しています。ですから「参 鶏湯」と分割したところで、表音文字の韓国語では発音がわからなくなることはないはずです。「サム」だけだと「3」と区別がつかないなど、日本語の全部平仮名表記のような問題はありますが、音声認識に限って言うと便利です。つまり日本語で言うと同じ発音である「三 鶏湯」と「参 鶏湯」を区別しないといけませんが、ハングルで書くと「三」も「参」も同じ「삼」なので出力で迷うことがありません。

日本語で「参 鶏湯」と分割してしまった場合には音を拾うことができないと思うのです。基本「参」は「さん」と読み「さむ」とは読みません。「鶏湯」も辞書にない単語なので読み方に困るでしょう。ですから、日本語の場合は単語分割と発音の組み合わせが重要になってくるはずで、これをどのように日本語の音声認識に応用したのかは興味の尽きないところです。

余談ですが中国語でもWordpieceモデルはうまくいきそうだと思います。中国語では大部分の漢字は1つの読みしかありません。たまに「旅行=りゅーしん」とか「銀行=いんはん」のように同じ字なのに別の読み方をする例もありますが、これは「行」は「しん」「はん」という2つの読み方を発音辞書に登録しておけば十分だと思います。これに接続する他の「旅」などの文字は読みが1つしかないため、組合せ爆発することはないはずです。また高頻度で出現する文字の組み合わせは1つの単語として登録されますから、旅行とか銀行に関して言うと1語になるでしょう。

もう1つはデータドリブンであること、つまり教師なし学習であるということです。MeCabで分割するためにはMeCab辞書のトレーニングが必要でした。MeCabは教師あり学習なので当然です。Wordpieceモデルは教師データの作成の必要がありません。Googleのような大企業なら別にどっちでもいいような気はしますが、個人の趣味でやっている場合にはきちんとしたトレーニングデータを用意するのは難しいので、このアプローチは歓迎です。

韓国語や中国語にも形態素解析ツールはありますが、日本語のツールほどはできがよくないのです。いいツールがあるかも知れないけど、個人的には知りませんのでもしご存じの方がいたら教えていただければ幸いです。ですので、一から学習データを作って形態素解析ツールのトレーニングをするよりは、Wordpieceモデルに放り込んだほうが早い上に、音声認識分野に限っては問題なく機能するでしょう。

そのようなわけで、SentencePieceを見ているのですが、人の書いたコードというのは意外とわかりにくいのです。個人的にはそういうときはプログラムを動作させて、printf()などを挟むことで自分の仮説どおりに動いているか、流れているか、変数などが設定されているかを見ます。もう少し賢くやるにはデバッガが便利です。私は軟弱なのでGUIのデバッガしか使えないため、CLionで動かしてみました。

CLionでビルドするにはCMakeLists.txtというファイルを編集します。今回は学習済みモデルから単語分割するところだけ見ていたので次のようなCMakeLists.txtを作成しました。

cmake_minimum_required(VERSION 3.8)
project(sentencepiece_master)


set(CMAKE_CXX_STANDARD 11)


set(SOURCE_FILES
#        python/sentencepiece.i
#        python/sentencepiece_wrap.cxx
        src/bpe_model.cc
        src/bpe_model.h
#        src/bpe_model_test.cc
#        src/bpe_model_trainer.cc
#        src/bpe_model_trainer.h
#        src/bpe_model_trainer_test.cc
#        src/builder.cc
#        src/builder.h
#        src/builder_test.cc
        src/char_model.cc
        src/char_model.h
#        src/char_model_test.cc
#        src/char_model_trainer.cc
#        src/char_model_trainer.h
#        src/char_model_trainer_test.cc
        src/common.h
#        src/compile_charsmap_main.cc
        src/error.cc
        src/flags.cc
        src/flags.h
#        src/flags_test.cc
        src/model_factory.cc
        src/model_factory.h
#        src/model_factory_test.cc
        src/model_interface.cc
        src/model_interface.h
#        src/model_interface_test.cc
#        src/normalization_rule.h
        #        src/normalizer_test.cc
        src/sentencepiece.pb.cc
        src/sentencepiece.pb.h
        src/sentencepiece_model.pb.cc
        src/sentencepiece_model.pb.h
        src/sentencepiece_processor.cc
        src/sentencepiece_processor.h
#        src/sentencepiece_processor_test.cc
#        src/spm_decode_main.cc

        src/spm_encode_main.cc

#        src/spm_export_vocab_main.cc
#        src/spm_normalize_main.cc
#        src/spm_train_main.cc
#        src/stringpiece.h
#        src/test_main.cc
#        src/testharness.cc
#        src/testharness.h
#        src/trainer_factory.cc
#        src/trainer_factory.h
#        src/trainer_factory_test.cc
#        src/trainer_interface.cc
#        src/trainer_interface.h
#        src/trainer_interface_test.cc
#        src/unicode_script.cc
#        src/unicode_script.h
#        src/unicode_script_map.h
#        src/unicode_script_test.cc
        src/unigram_model.cc
        src/unigram_model.h
#        src/unigram_model_test.cc
#        src/unigram_model_trainer.cc
#        src/unigram_model_trainer.h
#        src/unigram_model_trainer_test.cc
        src/util.cc
        src/util.h
#        src/util_test.cc
        src/word_model.cc
        src/word_model.h
#        src/word_model_test.cc
#        src/word_model_trainer.cc
#        src/word_model_trainer.h
#        src/word_model_trainer_test.cc
        src/darts.h
#        third_party/esaxx/esa.hxx
#        third_party/esaxx/sais.hxx
        src/config.h
        src/mathlimits.cpp src/mathlimits.h src/normalizer.h src/normalizer.cpp)


find_package(Protobuf REQUIRED)
include_directories(${PROTOBUF_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
#protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS foo.proto)

add_executable(sentencepiece_master ${SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(sentencepiece_master ${PROTOBUF_LIBRARIES})

ただし事前にProtobufをインストールしておく必要があります。