Quantcast
Channel: PostgreSQL Deep Dive
Viewing all articles
Browse latest Browse all 94

TF-IDFでデータベース内の類似テキストを検索する Part 4 (MADlib svec編)

$
0
0
TF-IDF 感動巨編3部作は前回のエントリで完結したわけですが、今回はその番外編、スピンオフとして「MADlib svec編」をお送りします。

MADlib には、sparse(疎)な配列、つまり多くの要素がゼロであるような配列を扱うデータ型として svec というデータ型があります。
本エントリでは、TF-IDF のベクトルに MADlib の svec を使って、通常の float8[] などとどのように違うのかを見てみます。

■「MADlib」とは何か


MADlib については、ガッツリと割愛します。以前のエントリで詳しくご紹介しましたので、そちらを参照してください。

■「svec」 とは何か


svec は、ゼロの多い sparse な配列を圧縮して保持するデータ型です。データ分析をしていると、頻繁に遭遇するデータの構造になります。

例えば、float8 の配列で以下のようにゼロが並ぶデータがあったとします。

'{0, 33,...40,000個のゼロ..., 12, 22 }'::float8[]
すると、この配列は 320kB 以上のディスク容量またはメモリを消費することになります。ほとんど意味のないゼロを保持するだけのために、これだけのリソースを食ってしまいます。

svec は、この配列を以下のようにランレングス圧縮(RLE圧縮)することでデータサイズを縮小します。

'{1,1,40000,1,1}:{0,33,0,12,22}'::madlib.svec
このように圧縮することによって、5つの整数型と5つの浮動小数点型に集約され、データサイズが劇的に小さくなります。

このようなデータ型を用意することで、ディスク容量とメモリの消費を抑え、大量のデータの処理を可能にします。(もちろん、演算処理時には圧縮されたデータを展開しながら行いますので、そのCPUコストは発生します)

さらなる詳細はマニュアルを参照してください。

■float8[] を svec に変換する


まず、TF-IDF 感動巨編3部作が完結した状態のテーブルから始めます。
この時、以下のようなテーブル定義になっているはずです。

snaga=> \d pgsql_doc
Table "public.pgsql_doc"
Column | Type | Modifiers
-----------+--------------------+-----------------------------------------------------------
docid | integer | not null default nextval('pgsql_doc_docid_seq'::regclass)
filename | text | not null
html | text | not null
plain | text |
tf | jsonb |
tfidf | jsonb |
tfidf_vec | double precision[] |
Indexes:
"pgsql_doc_pkey" PRIMARY KEY, btree (docid)

snaga=>
この時、tfidf_vec のカラムは、以下のようにゼロの多い float8 の配列になっています。

snaga=> SELECT substring(tfidf_vec::text from 0 for 100) FROM pgsql_doc LIMIT 1;
substring
-----------------------------------------------------------------------------------------------------
{0,0,0,0,0,0,0,0.0328645679655572,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0
(1 row)

snaga=>
この float8[] を svec に変換するには、単に madlib.svec 型へキャストすれば完了です。

snaga=> SELECT substring(tfidf_vec::madlib.svec::text from 0 for 100) FROM pgsql_doc LIMIT 1;
substring
-----------------------------------------------------------------------------------------------------
{7,1,31,1,4,1,18,1,70,1,88,1,4,1,51,1,11,1,45,1,1,1,52,1,2,1,4,1,1,7,1,1,1,36,1,1,1,36,1,106,1,14,1
(1 row)

snaga=>

■float8[] と svec のテーブルを用意する


まず、比較のために、docid, filename カラムと、float8[] の tfidf_vec カラム、もしくは svec の tfidf_svec カラムだけを保持するテーブル pgsql_doc_vec と pgsql_doc_svec を作成します。

tfidf_svec カラムは madlib.svec 型になっていることを確認します。

snaga=> CREATE TABLE pgsql_doc_vec AS SELECT docid,filename,tfidf_vec FROM pgsql_doc;
SELECT 1304
snaga=> \d pgsql_doc_vec
Table "public.pgsql_doc_vec"
Column | Type | Modifiers
-----------+--------------------+-----------
docid | integer |
filename | text |
tfidf_vec | double precision[] |

snaga=> CREATE TABLE pgsql_doc_svec AS SELECT docid,filename,tfidf_vec::madlib.svec as tfidf_svec FROM pgsql_doc;
SELECT 1304
snaga=> \d pgsql_doc_svec
Table "public.pgsql_doc_svec"
Column | Type | Modifiers
------------+-------------+-----------
docid | integer |
filename | text |
tfidf_svec | madlib.svec |

snaga=>

■float8[] と svec のデータサイズを比較する


まず、2つのテーブルのデータサイズを比較してみます。

snaga=> \d+
List of relations
Schema | Name | Type | Owner | Size | Description
--------+---------------------+----------+-------+------------+-------------
public | pgsql_doc | table | snaga | 407 MB |
public | pgsql_doc_docid_seq | sequence | snaga | 8192 bytes |
public | pgsql_doc_svec | table | snaga | 3872 kB |
public | pgsql_doc_vec | table | snaga | 6752 kB |
(4 rows)

snaga=>
上記を見て分かる通り、MADlib の svec 型を使ったテーブルの方が 3MB ほど小さくなっています。

このようにデータサイズを小さくすることによって、ディスクサイズの節約、I/O読み込みの抑制、バッファキャッシュの消費抑制などが実現され、ひいてはパフォーマンスの向上につながります。

■float8[] と svec のパフォーマンス比較


それでは、上記で作ったテーブルとカラムを使って、前回用いた「wal.html と類似のドキュメントを検索する」クエリでパフォーマンスを比較してみます。

float8[] 型を使う場合のクエリは以下の通りです。

EXPLAIN ANALYZE SELECT
filename,
euclidean_distance(tfidf_vec, (SELECT tfidf_vec FROM pgsql_doc_vec WHERE filename = 'wal.html') )
FROM
pgsql_doc_vec
ORDER BY
2;
svec 型を使う場合のクエリは以下の通りです。

EXPLAIN ANALYZE SELECT
filename,
euclidean_distance(tfidf_svec::float8[], (SELECT tfidf_svec::float8[] FROM pgsql_doc_svec WHERE filename = 'wal.html') )
FROM
pgsql_doc_svec
ORDER BY
2;
上記のクエリを5回ずつ実行した結果、
  • float8[] 使用時:平均 4757.6 ミリ秒
    • 4738.021, 4739.953, 4734.725, 4793.513, 4781.721
  • svec 使用時:平均 4379.5 ミリ秒
    • 4395.491, 4315.103, 4408.664, 4342.631, 4435.416
となり、svec を使用した方が1割ほど速い、という結果になりました。

データが圧縮されたことによって、パフォーマンス改善が実現されているようです。

■まとめ


今回は、MADlib で提供されている svec 型を使ってみました。

今回のケースでは1割程度のパフォーマンス改善になりましたが、当然ながらデータ圧縮の程度によってパフォーマンス向上の度合いは変わってくるでしょうし、データ量によっても変わってくると思われます。

興味のある方は、ぜひトライしてみていただければと思います。

では、また。


Viewing all articles
Browse latest Browse all 94

Trending Articles