😃

キーフレーズ抽出を用いた類似文書検索の簡易実験

2022/05/21に公開

こんにちは。初めて記事を投稿します。

類似文書検索とは

類似文書検索は、自然言語処理のタスクの一種です。あるテキストに意味的に類似するテキストを、既存のテキストデータセットから選び出すタスクです。

類似するテキストを選び出す際にかつてよくあったやり方は、BoW(Bag-of-Words)やtf-idf、BM25などに沿ってテキストのベクトル表現を作り、ベクトル間の類似度(主にコサイン類似度)を測定する試みです。そして、各テキストの中で類似度の特に高いテキストを選び出します。
それに対して今回は、テキストのベクトル表現を作る際に、キーフレーズ抽出という手法の活用を考えました。今回はそのことについての記事を書いてみます。

キーフレーズ抽出とは

キーフレーズ抽出も自然言語処理のタスクの一種です。文字通りではありますが、ある文章から、その文章のキーフレーズを抽出します。
キーフレーズとは、意味的にその文章をよく表現するフレーズや、その文章を象徴するフレーズのことです。フレーズは、1つまたは複数のワード(単語)の連なりです。
通常、キーフレーズは複数抽出されますが、1フレーズのみが抽出される場合や何も抽出されない場合もあります。

PKE

キーフレーズ抽出のできるライブラリとして、PKE(Python Keyphrase Extraction)というものがあります。他のライブラリも含めていくつか試して検討した結果、今回はこのライブラリを用いることにします。このライブラリの使用にあたり、主にこちらの解説ページを参考にさせていただいています。

pkeを使い日本語のキーフレーズ抽出を行う
はじめての自然言語処理 第5回 pke によるキーフレーズ抽出

キーフレーズ抽出を用いることにした理由

テキストのベクトル表現を作る際にキーフレーズ抽出を用いてみることにした主な理由は、この手法ならあまり本質的でない文言(記号、挨拶文、どのテキストにも出てきそうな文言など)を無視できる可能性が高いのではと想像したことです。

通常、本質的でない文言を無視できる場合ほど、そのテキストをよく表現するベクトルを作りやすくなります。これは言い換えれば、意味の異なる他のテキストのベクトル表現から遠いベクトルを作りやすくなるということです。
仮にそうなれば、意味の異なるテキスト同士に比較的低い類似度を与えることができますし、逆に意味の似ているテキスト同士に比較的高い類似度を与えることができます。

また、一般にベクトルの次元が大きくなりすぎると、ベクトル間の距離がどれもあまり変わらなくなってしまう傾向があります(いわゆる次元の呪いの一種です)。
そうなると、どれが類似するテキストなのかの判定が難しくなってしまいます。BoWやtf-idfに基づくベクトルは語彙数相当分の次元を持つため、次元が1万以上にもなる場合もあります。その際は次元圧縮を施してもいいですが、それも少々手間になってしまいます。

もっとも、近年はBERTや、BERTを基にしたSentenceBERTなどの有力なモデルやアルゴリズムが登場しており、そちらに軍配が上がるかもしれません。SentenceBERTなどには、テキストの文脈を考慮できる強みや、ワード間の意味の近さを加味できる強みがあります。
ただ、逆にそれらの強みを活かしにくい場合なら、今回の手法も効果的ではないかと想像しています。

用いるデータセット

今回は次の7種類のテキストを実験に用います。

text1

NVIDIAがデータセンター向けGPU「NVIDIA H100」を発表 新アーキテクチャ「Hopper」を採用NVIDIAが、新アーキテクチャのデータセンター向けGPUを発表した。現行のAmpereアーキテクチャから演算能力やデータ点速度を引き上げた他、別売の専用ハードウェア(ラック)を用意することで最大256基のGPUをより高速に連結できる仕組みも用意した。(2022/3/23)

text2

NVIDIAが新GPUアーキテクチャ「Hopper」を発表、AI処理性能は4000TFLOPSへNVIDIAは「GTC 2022」の基調講演において、新たなGPUアーキテクチャである「NVIDIA Hopperアーキテクチャ」と、Hopperを搭載するGPU「NVIDIA H100 GPU」を発表。H100は、800億ものトランジスタを集積しており、自然言語認識AIの開発で用いられているトランスフォーマーモデルの精度を落とすことなく処理性能を大幅に向上できるという。(2022/3/23)

text3

NVIDIAが注力する車載AIとゲーム向けの新技術NVIDIAは、米国ラスベガスで開催された「CES 2022」(2022年1月5~7日)において、車載AI(人工知能)やゲーム向けとして近く発表予定のさまざまな新技術を披露した。また、AT&TやSamsung Electronicsとの連携による新たなイニシアチブも発表している。(2022/1/14)

text4

13億パラメーターを持つGPT言語モデルをrinnaが公開、日本語に特化rinnaは13億パラメーターを持つ日本語に特化したGPT言語モデルをMITライセンスで公開した。性能は約14パープレキシティー。一般的な日本語の特徴を持つ文章を自動生成できるという。(2022/1/27)

text5

OpenAI、文章から画像を生成する新モデル「GLIDE」 前モデルよりも高品質な画像を生成OpenAIの研究チームは、自然言語からフォトリアリスティックな画像を生成する機械学習の新しいモデル「GLIDE」を開発した。(2022/1/6)

text6

AIアプリケーション向け、2枚のウエハーを貼り合わせた3D WoW IPUを発表グラフコア(Graphcore)は、3次元半導体技術による3D WoWプロセッサ「Bow IPU」を発表した。次世代AIコンピュータシステム「Bow Pod」の心臓部として、従来のプロセッサより最大40%高い性能と、16%高い電力効率を提供する。(2022/4/5)

text7

初のWoW技術適用で大幅性能向上、Graphcoreの新IPUGraphcoreが、第3世代のIPU(Intelligence Processing Unit)を発表した。業界初となる3D Wafer on Wafer(以下、WoW)技術適用のプロセッサだ。(2022/3/14)

これらのテキストは、ITmediaに掲載されたニュース記事一覧から選び取ったものです。特に意図はないですが、自然言語処理やGPUに関心があったのでこれらを選びました。
また、選んだ各ニュース記事のうち、タイトルと冒頭の文章を取り出し、それらをそのままつなげてテキストにしています。

見ての通り、「text1とtext2」や「text6とtext7」が、特に類似性の高いテキストの組み合わせになります。また、「text4とtext5」も、言語モデルという共通の話題となっており比較的似ています。
他方でtext3は、NVIDIA関係という点でtext1やtext2と共通しますが、内容自体は特に似ているとは言えません。

今回実験したアルゴリズム

今回試した方法は、BoWやtf-idfに似たものです。以下、アルゴリズムの大まかな手順です。

1. キーフレーズの抽出

PKEを用いて、各テキストにキーフレーズ抽出をかけます。なお、今回は品詞を名詞または固有名詞に絞っています。

2.キーフレーズの一覧の作成

各テキストから抽出したフレーズを統合し、重複したフレーズがあれば1つにまとめます。こうして全体でのフレーズの一覧を作成します。

3.各テキストに対するベクトルの作成

今回作りたいベクトルのサイズ(要素の数)は、全体でのフレーズの数(種類の数)です。
全体での各フレーズごとに、そのフレーズがテキストから抽出したフレーズの一覧の中にあるかどうかをチェックします。もしあれば1を、なければ0を、ベクトルの対応する要素に割り当てます。
その後、ベクトルの長さ(L2ノルム)を1にします。これは類似度(コサイン類似度)を測定しやすくするためです。

4.各テキスト間の類似度の測定

コサイン類似度を指標として、各テキスト間の類似度を測定します。測定結果が、目検で判断した類似性の高低にうまく沿っているかを確認します。
以上がアルゴリズムの手順です。

補足

今回の場合、テキスト間の類似度の測定にROUGE1のような指標を用いる手も考えられます。すなわち、テキストAから抽出したフレーズに占める、テキストBから抽出したフレーズと共通するフレーズの割合を算出します。いわゆる適合率です。そして今度は再現率を算出し、F1スコア(適合率と再現率の調和平均)などでもって類似度を測定する、というやり方です。
今回の場合はどちらでもいいかもしれません。

また、今回はフレーズ単位で類似度を測定していますが、各フレーズをワード(あるいは単語や形態素)に分割してワード単位で類似度を測定する手も考えられます。フレーズによって構成するワードの数が異なっており、ワードの数の多いフレーズほど他のテキストから抽出されにくくなるので、ワード単位に分割したほうがいいかもしれません。
ですが、今回はひとまずフレーズ単位で実験します。

コード

今回は次のコードを作成しました。

text_retrieval_by_pke.ipynb
import pke
import numpy as np
import seaborn as sns
import pandas as pd

#ファイル名
file_input = './inputs_news.txt'
#対象の品詞の一覧(NOUNは名詞、PROPNは固有名詞)
position = {'NOUN', 'PROPN'}

#テキストごとのフレーズの組の一覧
list_set_phrase = []
#テキストごとのラベルの一覧
list_label = []

with open(file_input, 'r') as f:
    for i, line in enumerate(f):
        extractor = pke.unsupervised.MultipartiteRank()
        extractor.load_document(input=line, language='ja', normalization=None)
        extractor.candidate_selection(pos=position)
        extractor.candidate_weighting(threshold=0.74, method='average', alpha=1.1)
        result_ext = extractor.get_n_best(10)
        vec_phrases = []
        #result_extの各要素はフレーズとスコア(重要度)の組になっている。
        #今回はフレーズのみを取得する。
        for one_set_phrase_score in result_ext:
            vec_phrases.append(one_set_phrase_score[0])
        list_set_phrase.append(vec_phrases)
        list_label.append('text' + str(i+1))
        
#全体でのフレーズの一覧
list_all_phrase = []
for one_set_phrase in list_set_phrase:
    list_all_phrase.extend(one_set_phrase)
list_all_phrase = list(set(list_all_phrase))

print('★全体でのフレーズの一覧:')
print(str(list_all_phrase))

print('\n' + '★全体でのフレーズの数:')
print(str(len(list_all_phrase)))

#テキストごとのベクトル表現の作成。各ベクトルの次元は、全体でのフレーズの数。
vectors = []
for i, one_set_phrase in enumerate(list_set_phrase):
    vector = []
    for j, phrase in enumerate(list_all_phrase):
        if phrase in one_set_phrase:
            vector.append(1.0)
        else:
            vector.append(0.0)
    l2_norm = np.linalg.norm(np.array(vector))
    vector /= l2_norm
    vectors.append(vector)

#テキスト間のコサイン類似度の行列表現
matrix_cosine_similarity = []
for i, vector_1st in enumerate(vectors):
    vector_temp = []
    for j, vector_2nd in enumerate(vectors):
        vector_temp.append(np.dot(np.array(vector_1st), np.array(vector_2nd)))
    matrix_cosine_similarity.append(vector_temp)

matrix_cosine_similarity = pd.DataFrame(
    matrix_cosine_similarity, index=list_label, columns=list_label)

print('\n' + '★テキスト間のコサイン類似度:')
sns.heatmap(matrix_cosine_similarity, annot=True, cmap="Reds", linewidths=.5)

実験とその結果

実験の操作手順

前述のデータセットをテキストファイルとして保存し、ファイル名を「inputs_news.txt」としたうえで、任意の場所に格納します。
次に、前述の「text_retrieval_by_pke.ipynb」コードファイルを準備し、テキストファイルと同じ階層に格納します。
その次に、前述の解説ページを参考にして、pkeやginzaなどの必要なライブラリをインストールしておきます。
その後、このコードファイルを実行します。

今回は簡易的に実験を済ませたいため、tf-idfやSentenseBERTなど他の手法との比較は省略しています。

実験の結果

結果は次のようになりました。

このヒートマップは、テキスト間の類似性の高さを表します。

この結果を見ると、目検で判断した類似性に結構沿っていますね。「text1とtext2」や「text6とtext7」は比較的高い類似度を出せていますし、「text1とtext3」や「text2とtext3」は比較的低い類似度を出せています。類似度の数値自体はせいぜい0.3と小さく見えますが、他のテキストよりも高い類似度を出せれば問題ありません。
そして類似性を判断していないテキスト同士については、おおむね0の類似度を出せています。

ただし、「text4とtext5」の類似度は0になってしまいました。抽出したフレーズを確認すると、共通のワードである「言語」や「モデル」はあるものの、下のようにフレーズとしては別々になってしまったことが分かります。

text4から抽出したフレーズ

'日本語', 'gpt 言語 モデル', 'rinna', 'パラメーター', '特化 rinna', 'mit ライセンス', 'パープレキシティー'

text5から抽出したフレーズ

'自然 言語', 'glide', '研究 チーム', 'モデル', 'フォト', 'openai', '機械学習'

これはある意味、今回試した方法の欠点が表れたと言えます。

今回のタスク自体はおそらくそれほど難しいものではなく、tf-idfでも割とうまく類似度の判定ができそうな気がします。とは言え、全体としては今回のようなやり方による類似文書検索の可能性を感じられた結果になったと思います。

終わりに

今回は以上です。お読みいただき、ありがとうございました。

Discussion