TF-IDF 感動巨編3部作は前回のエントリで完結したわけですが、今回はその番外編、スピンオフとして「MADlib svec編」をお送りします。
MADlib には、sparse(疎)な配列、つまり多くの要素がゼロであるような配列を扱うデータ型として svec というデータ型があります。
MADlib については、ガッツリと割愛します。以前のエントリで詳しくご紹介しましたので、そちらを参照してください。
svec は、ゼロの多い sparse な配列を圧縮して保持するデータ型です。データ分析をしていると、頻繁に遭遇するデータの構造になります。
例えば、float8 の配列で以下のようにゼロが並ぶデータがあったとします。
svec は、この配列を以下のようにランレングス圧縮(RLE圧縮)することでデータサイズを縮小します。
このようなデータ型を用意することで、ディスク容量とメモリの消費を抑え、大量のデータの処理を可能にします。(もちろん、演算処理時には圧縮されたデータを展開しながら行いますので、そのCPUコストは発生します)
さらなる詳細はマニュアルを参照してください。
まず、TF-IDF 感動巨編3部作が完結した状態のテーブルから始めます。
まず、比較のために、docid, filename カラムと、float8[] の tfidf_vec カラム、もしくは svec の tfidf_svec カラムだけを保持するテーブル pgsql_doc_vec と pgsql_doc_svec を作成します。
tfidf_svec カラムは madlib.svec 型になっていることを確認します。
まず、2つのテーブルのデータサイズを比較してみます。
このようにデータサイズを小さくすることによって、ディスクサイズの節約、I/O読み込みの抑制、バッファキャッシュの消費抑制などが実現され、ひいてはパフォーマンスの向上につながります。
それでは、上記で作ったテーブルとカラムを使って、前回用いた「wal.html と類似のドキュメントを検索する」クエリでパフォーマンスを比較してみます。
float8[] 型を使う場合のクエリは以下の通りです。
データが圧縮されたことによって、パフォーマンス改善が実現されているようです。
今回は、MADlib で提供されている svec 型を使ってみました。
今回のケースでは1割程度のパフォーマンス改善になりましたが、当然ながらデータ圧縮の程度によってパフォーマンス向上の度合いは変わってくるでしょうし、データ量によっても変わってくると思われます。
興味のある方は、ぜひトライしてみていただければと思います。
では、また。
MADlib には、sparse(疎)な配列、つまり多くの要素がゼロであるような配列を扱うデータ型として svec というデータ型があります。
- MADlib: Sparse Vectors
https://madlib.incubator.apache.org/docs/latest/group__grp__svec.html
■「MADlib」とは何か
MADlib については、ガッツリと割愛します。以前のエントリで詳しくご紹介しましたので、そちらを参照してください。
- データ分析用ライブラリ MADlib を使って PostgreSQL で機械学習する
http://pgsqldeepdive.blogspot.jp/2016/04/madlib-postgresql.html
■「svec」 とは何か
svec は、ゼロの多い sparse な配列を圧縮して保持するデータ型です。データ分析をしていると、頻繁に遭遇するデータの構造になります。
例えば、float8 の配列で以下のようにゼロが並ぶデータがあったとします。
すると、この配列は 320kB 以上のディスク容量またはメモリを消費することになります。ほとんど意味のないゼロを保持するだけのために、これだけのリソースを食ってしまいます。
'{0, 33,...40,000個のゼロ..., 12, 22 }'::float8[]
svec は、この配列を以下のようにランレングス圧縮(RLE圧縮)することでデータサイズを縮小します。
このように圧縮することによって、5つの整数型と5つの浮動小数点型に集約され、データサイズが劇的に小さくなります。
'{1,1,40000,1,1}:{0,33,0,12,22}'::madlib.svec
このようなデータ型を用意することで、ディスク容量とメモリの消費を抑え、大量のデータの処理を可能にします。(もちろん、演算処理時には圧縮されたデータを展開しながら行いますので、そのCPUコストは発生します)
さらなる詳細はマニュアルを参照してください。
- MADlib: Sparse Vectors
https://madlib.incubator.apache.org/docs/latest/group__grp__svec.html
■float8[] を svec に変換する
まず、TF-IDF 感動巨編3部作が完結した状態のテーブルから始めます。
- TF-IDFでデータベース内の類似テキストを検索する Part 3 (性能改善編)
http://pgsqldeepdive.blogspot.jp/2016/07/tf-idf-part-3.html
この時、tfidf_vec のカラムは、以下のようにゼロの多い float8 の配列になっています。
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=>
この float8[] を svec に変換するには、単に madlib.svec 型へキャストすれば完了です。
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=>
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つのテーブルのデータサイズを比較してみます。
上記を見て分かる通り、MADlib の svec 型を使ったテーブルの方が 3MB ほど小さくなっています。
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=>
このようにデータサイズを小さくすることによって、ディスクサイズの節約、I/O読み込みの抑制、バッファキャッシュの消費抑制などが実現され、ひいてはパフォーマンスの向上につながります。
■float8[] と svec のパフォーマンス比較
それでは、上記で作ったテーブルとカラムを使って、前回用いた「wal.html と類似のドキュメントを検索する」クエリでパフォーマンスを比較してみます。
float8[] 型を使う場合のクエリは以下の通りです。
svec 型を使う場合のクエリは以下の通りです。
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;
上記のクエリを5回ずつ実行した結果、
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;
- 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
データが圧縮されたことによって、パフォーマンス改善が実現されているようです。
■まとめ
今回は、MADlib で提供されている svec 型を使ってみました。
今回のケースでは1割程度のパフォーマンス改善になりましたが、当然ながらデータ圧縮の程度によってパフォーマンス向上の度合いは変わってくるでしょうし、データ量によっても変わってくると思われます。
興味のある方は、ぜひトライしてみていただければと思います。
では、また。