for Startups Tech blog

このブログのデザインを刷新しました。(2023/12/26)

【フォースタ テックブログ】タグの自動予測について。STARTUP DBに機械学習を組み込んだ話【後編】

f:id:forStartups:20211025162517p:plain

こんにちは。テックラボの松原です。

以前、投稿しました「タグの自動予測について。STARTUP DBに機械学習を組み込んだ話【前編】」の続きのお話です。

前編では、STARTUP DBに登録する企業のサービスに適したタグはどれなのか予測する仕組みをつくるため、AWSのコグニティブサービスを試して回り、「やりたいことを、それなりの精度でやるためには、自身で実装しないと厳しい」という結論になったというお話でした。

今回は、手前で開発したその仕組みについて、お話をさせてもらおうと思います。

学習モデル作成

前回も簡単に記載しましたが、学習モデルは、以下の図のようなフローで作成しています。

①学習を行うためのデータ取得と解凍・抽出

まず、言語の処理をするためには、自然言語の文章を構造化し大規模に集積したもの「コーパス」が必要になります。品詞などの情報を含んだ百科事典のようなものですかね。
「全集」とも呼ばれるそうです。

当初、STARTUP DBの中にあるデータだけで、コーパス作成を試みたですが、情報量が足りず、計算できなかったり、過学習が起きました。

後でベクトル化の話をしているのですが、ベクトル化できない言葉が多く、ベクトル化できたデータもベクトルの向きがおかしい…という現象が出てきたのです。
ベクトルは「大きさと方向を持つ量」です。[ 1, 3, 2.5 ]のような数のリストとして表現ができます。

もっと大量の、一般的な文章データが必要だということで、利用したのがWikipediaのデータです。

Wikipediaのコンテンツデータは、再配布や再利用のために利用できる一元化されたデータベース・ダンプでの提供が行われています。
日本語のダンプファイル(出力ファイル)は、こちらからダウンロードができるようになっています。

ダウンロードしたファイルは、bz2という圧縮ファイルになっています。
それを解凍すると、下の画像のようなXML形式のデータが確認できます。

このままだと使えないので、このXMLから本文を抽出する必要があります。

そこで抽出に使うのが、オープンソースで提供されているWikiextractorです。
名前の通りですが、Wikipediaのダンプファイルから本文を抽出するためのプログラムです。

ダウンロードした圧縮ファイルを指定して、このようなコマンドでWikiextractorを実行すると

python -m wikiextractor.WikiExtractor jawiki-latest-pages-articles.xml.bz2

本文が抽出された、複数のテキストファイルが保存されます。

テキストの中身はこんな感じです。

見て分かる通り、<doc ….>というような、不要な文が残ってはしまいます。
なので、このあたりの不要な文も除去して、学習の大元となるデータの完成です。

分かち書き

日本語は、英語などのスペースで区切られた言語と異なり、どこからどこまでが1つの単語なのか、判別が容易ではありません。

日本語の自然言語処理をするためには、その区切りを見つけるために、形態素解析を用いて分かち書きするというのが一般的です。

最近では、深層学習により適した前処理として、WordPiece、Byte-pair encoding (BPE)、 SentencePiece など、テキストを「サブワード」と呼ばれる単語よりも短い単位に分割する手法が用いられるようになってきていますが、深層学習を用いたいわけでもない、というのと、可能な限りブラックボックスにせず、見えるロジックで解いておきたいという思いがあり、昔ながらのやり方ではありますが、MeCab分かち書きを行うようにしました。

MeCab (和布蕪)とは、京都大学で開発された形態素分析のエンジンです。
2013年からバージョンは0.996のままですが、今でもよく利用されているエンジンです。

他の形態素解析のツールとしては、Sudachiというワークスアプリケーションズが開発したものや、JANOMEというオープンソースもあります。
検索・分析エンジンのElasticsearchでよく使われている、kuromojiというものもあります。

IPAdicやNEologdなどの辞書を使いたいとか、速度を重視した時にMeCabを使うのがお薦めです。(メモリの消費はよろしくないです)

このタグ予測の仕組みの場合、APIのリクエストを受けた時にも形態素解析を動かすようにするのですが、レスポンスに時間を掛けすぎないという目的もあり、MeCabを選択しています。

全部ひっくるめたモデルに文章をインプットして、タグが返ってくるようにするというよりは、途中途中の処理をホワイトにすることで、チューニングがちゃんと行えるようにしたいという意図もあり、APIのコントローラー上で形態素解析を動かす必要が出てきたという経緯です。

辞書は、新語・固有表現に強いNEologdを使っています。

新語に強いと言っても、スタートアップ界隈の言葉は全然カバーしきれず、独自の辞書が必要になりはするのですが…

MeCabを使い、①のテキストを分かち書きすると、以下のような結果を得ることができます。

ちなみに、品詞付での出力をするように設定して実行してます。

見て分かる通り、ノイズがとても多いですね。

「、」「。」などの記号、「の」「は」などの助詞、「きっと」「もっと」などの副詞、これらは、文章全体の意味を捉えるという目的に対しては、不要な情報と言っていいと思います。

なので、あらかじめ除外する品詞や単語を決めておき、分かち書きしながら、取り除くという処理を行います。

# 除外する品詞1リスト
except_main_features = ['記号', '助詞', '助動詞', '感動詞', '接頭詞', '副詞', '連体詞', '接続詞']
# 除外する品詞2リスト
except_sub_features = ['代名詞', '接尾','副詞可能', '自立', '非自立', '形容動詞語幹']

そうすることで、もう少し意味のある言葉のみを抽出することができます。

お見せしている例では助詞と副詞を除いてますが、結果次第では含めても良いかもしれませんね。

参考までに助詞、助動詞などを除いた場合、ベクトルの精度があがるという論文のリンクを貼っておきます。

分かち書き + ノイズの除去を行ったデータをS3にアップロードして、学習データの準備完了です。

③学習

前編でも紹介したSageMakerで提供されているアルゴリズムであるBlazingText、これを用いて、ベクトルデータを生成するモデルを作っていきます。

必要なプログラムは、sagemakerのパッケージに含まれているため、考慮することはハイパーパラメータ(機械学習を行うためのそのアルゴリズムに設定する値)をどうするかぐらいでしょうか?

自身で何度も試して決めることもできますが、自動でハイパーパラメータを調整する仕組みもAWSにはあるので、それを使うのもいいかと思います。

参考: https://t-redactyl.io/blog/2020/11/automatic-word2vec-model-tuning-using-sagemaker.html

まずは、BlazingTextのimageを取得します。

image = sagemaker.amazon.amazon_estimator.get_image_uri(region_name, "blazingtext", "latest")

そのimageを使って、Estimator(トレーニングとかするオブジェクト)を生成します。

estimator = sagemaker.estimator.Estimator(image,
                                          role,
                                          train_instance_count=2,
                                          train_instance_type='ml.c4.2xlarge',
                                          train_volume_size = 30,
                                          train_max_run = 360000,
                                          input_mode= 'File',
                                          output_path=s3_output_location,
                                          sagemaker_session=sess)

Estimatorに対して、ハイパーパラメータを設定後、

estimator.set_hyperparameters(mode="batch_skipgram",
                              epochs=5,
                              min_count=5,
                              sampling_threshold=0.0001,
                              learning_rate=0.05,
                              window_size=5,
                              vector_dim=100,
                              negative_samples=5,
                              batch_size=11, #  = (2*window_size + 1) (Preferred. Used only if mode is batch_skipgram)
                              evaluation=True,# Perform similarity evaluation on WS-353 dataset at the end of training
                              subwords=False) # Subword embedding learning is not supported by batch_skipgram

レーニングデータを指定し、

train_data = sagemaker.session.s3_input(s3_train_data, distribution='FullyReplicated',
                                        content_type='text/plain', s3_data_type='S3Prefix')
data_channels = {'train': train_data}

学習を開始します。

estimator.fit(inputs=data_channels, logs=True)

これでモデルの完成です。

出来たモデルに対して、単語を投げると、その単語のベクトルを得ることができます。

タグの予測

①サービスの説明文の分かち書きとベクトル化

STARTUP DBの管理画面にある、企業のサービス編集画面の「タグ予測」ボタンが押されると、Flaskで構築したアプリケーションサーバーにサービスの説明文が投げられます。
アプリケーションサーバーには、学習用のデータを作成したサーバーと同様に、MeCabがインストールされており、分かち書きが行われます。

アプリケーションサーバーは、その後、分かち書きされたサービスの説明文をBlazingTextで作成したモデルが乗っているエンドポイント(MLホスティングインスタンス)に投げて、各単語のベクトルを取得します。

②タグの一覧の取得とベクトル化

アプリケーションサーバーは、タグの一覧を、企業情報用のサーバーからAPIを叩いて取得します。

その後、アプリケーションサーバーはサービスの説明文と同様に、タグ一覧をモデルが乗っているエンドポイントに投げて、各タグのベクトルを取得します。

③ベクトルのクラスタリング

分かち書きで得た単語の数は、もちろん一つではなく、ベクトルの向きも様々です。
近似のタグを見つけるためには、それらを集約させる必要があります。
そのために、得られたベクトルをクラスタリングさせる必要があります。

複数のベクトルの集まりをテンソルと言いますが、テンソルはn次元の行列という形で表現ができ、行列の次元を削減するためにクラスタリングを利用しています。

次元を削減するというのは、n次元の行列をk次元に減らすという作業です。

次元削減自体にも、主成分分析 (Principal Component Analysis: PCA) や非負値行列因子分解(Non-negative matrix factorization: NMF)など次元削減の手法がありますが、今回の内容においては、クラスタリングでいい、という判断をしています。

SageMakerの提供しているアルゴリズムの中にも、クラスタリングを行えるアルゴリズムがあります。ただし、問題になるのは、そのアルゴリズムの種類です。

SageMakerで用意されているクラスタリングアルゴリズムは、K-meansです。
K-meansは、中心を仮定してクラスタリングする方法で、今回の目的とは実は合致しません。

と、言葉で言っても分かりにくいですが、Python機械学習ライブラリであるscikit-learnのWebページに非常に分かりやすい図があります。

参照: https://scikit-learn.org/stable/auto_examples/cluster/plot_cluster_comparison.html

K-meansは1番左の列の図になりますが、図にもみられるように、境界を越えてクラスタリングが行われてしまうのが見えますでしょうか?

また、密度連結成分を検出するDBSCANは、非常に綺麗に分けてくれるアルゴリズムではありますが、

境界がなさ過ぎるとすべて同じクラスタとして見なされてしまいます。

今回のケースだと、似たようなまとまりの集まりだったとしてもそれをいくつかの方向にクラスタリングして欲しいところです。

なので、タグ予測でやりたいクラスタリングを考慮し、アルゴリズムはSpectral Clusteringを選択しています。

Spectral Clusteringは、固有値によるスペクトル分解を行うもので、内部でK-meansやDBSCANのロジックが動いていたりします。

このSpectral Clusteringを利用するため、SageMakerは利用せず、アプリケーションサーバー内にscikit-learnを用意し、Spectral Clusteringを用いたクラスリングを行わせるという方針をとっている訳です。

クラスタリングしたのち、クラスタリングしたベクトルの平均合成を行い、クラスタの方向を導き出します。

④コサイン類似度を用いた類似のタグの算出

ベクトルの近似値は、「コサイン類似度」によって求めることができます。

ベクトルの行列を内積やらなんやらすることで計算することができます。

参考: https://www.cse.kyoto-su.ac.jp/~g0846020/keywords/cosinSimilarity.html#:~:text=%E3%82%B3%E3%82%B5%E3%82%A4%E3%83%B3%E9%A1%9E%E4%BC%BC%E5%BA%A6%E3%81%A8%E3%81%AF,%E3%81%A6%E3%81%84%E3%81%AA%E3%81%84%E3%81%93%E3%81%A8%E3%81%AB%E3%81%AA%E3%82%8B%E3%80%82

アプリケーションサーバーは、タグのベクトルと、クラスタリングされた説明文のベクトルを比較し、近い順にランク付けを行い、管理画面に返します。

最後、これを管理画面で表示してあげるだけです。

課題

この方法には、まだいくつか課題が残っています。

Wikipediaのデータが一般的過ぎて、サービス固有の名前や説明がうまくベクトル化できない → コーパスにスタートアップ関連の記事を多く取り込む必要がある

②辞書に無い言葉がサービスに多く出てくるため、うまくベクトル化できない単語がある → 独自辞書に随時言葉を登録していく作業が発生する

③あっちこっちで処理をさせるようになっており、仕組みが複雑 → SageMakerで動くモデルを自作し、そこに投げるだけでいい仕組みにしてもいいかもしれない

などが、大きな課題です。

まとめ

ブラックボックスにし過ぎず、理解できるロジックで組み上げたことで、細かい調整がきき、精度も確かに出るようになったと思います。ただし、課題にも挙げているように、予測したい文章と学習させる文章が合うかどうか、未知の言葉に対する対応など、潰しきれない課題はどうしても出てくるものだと実感させられました。

文中でも記載しましたが、深層学習に適した前処理なども確立されてきていますし、日本語で、BERTを用いた自然言語処理などもでてきており、 そういうものを導入することで、未知語への対応も、より高い精度の学習もできる可能性があります。そのような新しい仕組みに入れ替えることも考慮に入れた上で、引き続き、ブラッシュアップをしていこうと思っています。