Document

いいプログラムは
コーディング技術だけではない
宇野 毅明 (国立情報学研究所
&総合研究大学院大学)
2007年3月22日 JOI合宿
簡単に自己紹介
名前: 宇野 毅明
年齢・職種: 36歳、助教授
研究分野: アルゴリズム理論
- コンピュータプログラムの設計手法の理論
- 速いコンピュータを作ったり、プログラミングの腕を競うの
ではなく、「設計方法の違いによる性能の向上」を目指す
最近の研究: ゲノム情報学やデータマイングで出てくる巨大なデー
タベースの基礎的(とはいっても非常に時間のかかる)な解析を超
高速で行うアルゴリズムの開発
趣味(日課?): 子供と遊ぶこと
アルゴリズム技術の粋
世の中での「プログラミング」
・ 情報化社会において、コンピュータプログラムはありとあらゆる
場所で使われている、もはや「言葉」と同じくらい基本的で重要な
ツール
・ それゆえに、プログラミングは、単純労働に近い位置にある
- SEのシステムを組む、という作業は、
プログラムではなく全体の設計
- ゲノムなど、○○情報学の分野でも、あくまでプログラミング
は道具であり、真の目的とは一線を画する
・ 昔はプログラムが組めるだけですごかったんだけど...
プログラムの美学
・ プログラムの普及がプログラムを単純労働にしたが、それはプ
ログラムの価値を低くしたわけではない
 誰でも文章を書けるが、質の高い文学作品は誰にでも書ける
ものではない
・ プログラムにはプログラムの粋がある
情報
システム
ビジネス
モデル
プログラム
自然
科学
豊かな
生活
技術
の粋
技術の粋とは
・ 「日本にはOSを作れる人材がいない」と言われることがある
・ これは言いすぎだと思うが、「高い技術を持ったプログラマーが
少ない」というのはあっていると思う
・ OSのような高度なシステム作りに要求される技術は「大規模な
システムを、論理的に正しくデザインすること」
 いわば、車やジャンボジェット作りに似ている
(1万、10万を超える部品を組立てて、安定した製品を作る)
・ 他の粋として、「速いプログラムを作る」というものがある
 こちらは、F1カーや、ジェット戦闘機作りに相当
(目的に特化して、どこまで高性能にできるか、限界に挑戦)
プログラムを速くするには
・ プログラムを速くする方法の1つは、並列化をすること
 クラスタコンピュータ、デュアルコア、メニーコア
・ もうひとつはプログラムコードの改良
- 使用言語を変える
 インタープリタ系(perl,lisp)からコンパイル系へ(C,PASCAL)
- キャッシュのヒット率を上げる(ループを開く)
- ディスクIO、メモリIOを高速化する(バッファを自分で管理)
・ その他にも、「アルゴリズムの改良」
 アルゴリズムは、いわばプログラムの設計書。設計を変える
ことで、高速化を図る
アルゴリズム理論のアプローチ
・ アルゴリズム理論では、解く問題の大きさに対する計算時間に
注目し、増加の仕方が小さくなる設計法を考える
・ 「増加の仕方」にしか注目しないので、コーディングの技術など、
問題の大きさに依存しない高速化部分は無視できる
・ 大体の場合、「最悪の場合の計算時間」
を算定するので、リスクが小さい
・ 「実際の計算時間」「小規模だと単純なものに負ける」
が弱点
情報爆発時代のアルゴリズム
データ中心の科学
・ 近年、IT技術の発達で、大規模なデータが半自動的に収集で
きるようになった
(POS、web、文書、顧客データ、財務、利用者、人事…)
既存のデータを使って何かを得たい
データの選別
モデル化
データ処理
いわば、データを出発点とした問題解決の科学
(人工知能、データマイニング、自然言語処理、セマンティックweb…
近年の情報学でもメジャーな研究スタイル)
データ中心科学の特徴
・ データが整形されていない
目的がはっきりしない、あるいは異なる目的のために集められたデータを用いるため、
必要なものがすぐ取り出せるとは限らない。また、ノイズや不正確な情報も含まれうる。
・ 目的関数があいまい
データが情報の塊のようなものなので、そこから得られるものはやはり情報であること
が多い(知識、特徴分析といったもの)。それら情報の価値は数理的な尺度では計りにく
い。また、従来の最適化とは異なる尺度を用いることが多い。(グラフクラス、シークエ
ンス、情報量、隣接性、類似度、頻出度・・・)
・ データが巨大で、構造を持つ
半自動で集められたデータであるので、データは通常、巨大である。しかし各
項目が持つ属性は少なく、疎である。
・ データ処理は比較的簡単なものが多い
データ処理の計算は、最適化のような複雑ではなく、
組合せの検索や整形などいくつかの簡単な処理の組合せ
今、巨大データにできること
・ データベースの構築(データ構造)
・ キーワード検索
・ ソート、整列
・ フィルタリング
・ 統計量の計算
・ 圧縮
・ 最短路検索
...
1次的な処理が多い。組合せ的な構造を処理するものは少ない
より高度な解析のため、より複雑な基礎処理アルゴリズムが必要
データ処理の変化
▪ 不ぞろいなデータから有用な情報を得るには複雑で豊かなモデ
ルを解く必要がある
▪ そのためには、解く問題を複雑にする必要がある
▪ 比較・統計量  全対比較・組合せ的な統計量
▪ キーワード検索  パターンマイニング
▪ 完全一致  類似検索
データベース
▪ 最適化  列挙
・ このように処理が変化すると、既存のアルゴリズムを用いて行っ
た場合に、非常に時間がかかることがある
例) 全対比較を通常の検索を用いて行うと、レコード数だけのクエ
リを必要とする
複雑かつ大量の計算を効率良く行う手法の開発が重要
データ処理に求められるもの
[多様性] 個別の案件に対してモデルが変化
 問題設定の変化に対して柔軟であること
- [基礎問題] 解く問題が基礎的であること
- [単純な構造] アルゴリズムのアイディアが単純であり、
汎用性の高いレベルで構築されていること
[速度] 大規模データに対しても高速に動作すること
 コードの改良より、良いアルゴリズムの開発
- [疎成] データの疎性、スケールフリー性を利用して
- [計算構造] 計算構造の改良により、無駄な探索を省く
- [スケールメリット] 多数の操作を一度に行うことで高速化
[正確性] なんらかの意味で正確な計算を行うこと
- [列挙] 全ての解をもらさず重複無く発見
- [精度保障] 誤差の範囲を保障する
アルゴリズム理論の利点
・ 大規模な計算には、アルゴリズム理論に基づいた技術が有効
 アルゴリズム理論による高速化は、問題の大きさに対する計算
時間の増加を抑える
 計算の結果は変化しない
100項目
100万項目
2-3倍
10000倍
データが巨大になるほど、アルゴリズム改良の加速率は上がる
頻出パターン発見
データベースを分析したい
・ データベース構築と検索は、もうできるようになった
(絞込みや、あいまい検索はまだ改良の余地があるけど)
・ より詳しくデータを解析するために、データの特徴を捉えたい
各種統計量(データベースの大きさ、密度、分布)よりも、深い解
析がしたい
 組合せ(パターン)的な構造に注目
(どういう組合せ(パターン)が
どれくらい入っているか)
・ 組合せ・パターンの個数は指数的に
増えていくので、全てを尽くすのは無理
 多く現れるものだけに注目
データベース
実験1 実験2 実験3 実験4
●
▲
▲
●
▲
●
●
▲
●
●
●
▲
●
▲
●
●
●
▲
●
●
▲
▲
▲
▲
実験結果
ATGCGCCGTA
TAGCGGGTGG
TTCGCGTTAG
GGATATAAAT
GCGCCAAATA
ATAATGTATTA
TTGAAGGGCG
ACAGTCTCTCA
ATAAGCGGCT
ゲノム情報
頻出パターンの列挙
・ データベースの中に多く現れるパターンを全て見つける問題を
頻出パターン列挙(あるいは発見、マイニング)問題という
データベース: トランザクション、ツリー、グラフ、多次元ベクトル
パターン: 部分集合、木、パス・サイクル、グラフ、図形
データベース
実験1 実験2 実験3 実験4
●
▲
▲
●
▲
●
●
▲
●
●
●
▲
●
▲
●
●
●
▲
●
●
▲
▲
▲
▲
実験結果
頻出する
パターンを抽出
ATGCGCCGTA
TAGCGGGTGG
TTCGCGTTAG
GGATATAAAT
GCGCCAAATA
ATAATGTATTA
TTGAAGGGCG
ACAGTCTCTCA
ATAAGCGGCT
ゲノム情報
・ 実験1● ,実験3 ▲
・ 実験2● ,実験4●
・ 実験2●, 実験3 ▲, 実験4●
・ 実験2▲ ,実験3 ▲
.
.
.
・ ATGCAT
・ CCCGGGTAA
・ GGCGTTA
・ ATAAGGG
.
.
.
多く現れる  頻出する
多く現れるものを見つけるために、多く現れるとは何か、を決める
・ データベースが項目の集まりだとする
・ パターンに対して、そのパターンを含む項目を出現という
・ 出現の数(頻出度)が閾値より大きければ、良く現れるとする
(含む、の定義は、集合で行ったり、文字列の
包含、グラフの埋め込みなどで定義する)
パターン
{A,C,D}
XYZ
項目
{A,B,C,D,E}
AXccYddZf
トランザクションデータベース
・ パターンとして、集合を考える (集合:ものの集まり。ここ
では {1,2,…,n}。部分集合は、この中からいくつかを選んだ
もの。{1,4,7} など。)
トランザクションデータベース:
各トランザクション T がアイテム集合 E={1,…,n} の
部分集合であるデータベース
D=
- POSデータ(各項目が、客1人の購入品目)
- アンケートのデータ(1人がチェックした項目)
- web log (1人が1回のwebサーフィンで見たページ)
- オプション装備 (車購入時に1人が選んだオプション)
実際のデータは、大きくて疎なものが多い
パワー則、スモールワールドが成り立つ
1,2,5,6,7
2,3,4,5
1,2,7,8,9
1,7,9
2,7,9
2
集合の出現と頻出度
集合K に対して:
K の出現: K を含む D のトランザクション
K の出現集合 Occ(K): K を含む D のトランザクション全ての集合
K の頻出度 frq(K): K の出現集合の大きさ
1,2,5,6,7,9
2,3,4,5
T = 1,2,7,8,9
1,7,9
2,7,9
2
{1,2}の出現集合
= { {1,2,5,6,7,9},
{1,2,7,8,9} }
{2,7,9}の出現集合
= { {1,2,5,6,7,9},
{1,2,7,8,9},
{2,7,9} }
頻出集合
・ 頻出集合:T の定数θ個以上のトランザクションに含まれる集合
(頻出度がθ以上の集合)( θを最小サポートとよぶ)
例) データベースT の3つ以上のトランザクションに含まれる集合
1,2,5,6,7,9
2,3,4,5
T = 1,2,7,8,9
1,7,9
2,7,9
2
3つ以上に含まれるもの
{1} {2} {7} {9}
{1,7} {1,9}
{2,7} {2,9} {7,9}
{1,7,9} {2,7,9}
与えられたトランザクションデータベースと最小サポートθ
に対して、頻出集合を全て見つける問題を考える
応用:バスケット分析
・ スーパーなどの小売店舗で、同時に購入される事の多い品物
の組を知りたい
・ 客が購入した品目  トランザクション
・ 品目の組で、多くの客が購入したもの
 多くのトランザクションに含まれるアイテム集合
 (あるθに対する)頻出集合
● 牛乳、弁当
「おむつとビールの組合せが良く売れる」
という発見が有名
● お茶、弁当
● おにぎり、雑誌
● はさみ、のり
● ラーメン、はし
● こっぷ、皿
● 弁当、おにぎり
...
応用:データベースの比較
・ 2つのデータベースが、意味的にどの程度似ているか知りたい
 大きさの違い、ノイズは無視したい
・ 各アイテム、属性などの総数だけでは、組合せがわからない
・ 組合せを細かく見ると、ノイズに振り回される
頻出集合を列挙することで、
組合せ的な特徴を比較できる
データ
ベース
データ
ベース
・ いろいろな言語の辞書データ
・ 異なる種のゲノムデータ
・ 文書集合の単語データ(新聞のデータ、雑誌のデータなど)
・ 顧客のデータ
応用:分類ルール、特性の発見
・ データの特徴を現す規則、あるいは正例・負例を分類するような
規則が知りたい (A,B,C が含まれている、A,B が含まれれば、C
が含まれる、など)
・ 多く現れる組合せを用いないと、仮定部分を満たすものが少なく、
ルールとして意味がない
・ 組合せを細かく見ると、ノイズに振り回される
頻出集合を仮定に用いることで、
信頼度の高いルールを
効率良く見つけられる
データ
ベース
正例
・ 実験データ
・ 利用者履歴データ、マーケッティング
データ
ベース
負例
頻出集合の列挙
頻出集合発見用のプログラム
・ 頻出集合発見は、データマイニングと呼ばれる最近興ったデータ
解析の中でも基礎的な問題なので、プログラムが多く作られている
・ 入力データ、出力する解、どちらも大きいことが多いので、計算
速度は非常に重要
・ しかも、アルゴリズムの設計しだい
で、パフォーマンスが大きく変わる
・ 国際プログラミングコンテスト
でも、こんな感じ。ばらつき大きい
(時間軸は対数)
どういうアルゴリズムがあ
るのか、見てみよう
プログラムを作ろう
・ 問題は
入力: トランザクションデータベースDと閾値 θ
出力: 全ての頻出集合出現
・ さて、どんな方針でプログラムを作りましょうか
(これがアルゴリズムを考える、という作業)
作戦1:部分集合1つ1つについて頻出度を計算する
 計算時間は O(2n|D|)
(n=アイテムの数、|D|=データベースの大きさ)
 n=30、|D| =1000 くらいでも大変なことになる
もう少し工夫しないと
計算時間は、どうなるべきだろう?
・頻出集合の数は最高で 2n個になるから、計算時間 O(2n|D|) は、
|D| の部分を除けばある意味で仕方ない?
 そんなことはない。そんなにたくさん答えが出てくるような計算
は、そもそもしたくない
 つまり、解(頻出集合)の数はそんなに多くない、と思ってよい
 逆に考えると、解を出力する部分の計算は避けられない
つまり、「これだけは最低かかる」
・そこで、「解1つあたりの計算時間がどうなるか」に注目しよう
頻出集合の単調性
・ 工夫をするためには、何か問題の特徴を
つかまなくてはいけない
111…1
・ 使えそうなのが、「頻出集合の部分集合は
必ず頻出」、というもの(単調性という)
 つまり、ハッセ図(包含関係を
図示したもの)の上では、
頻出集合が存在する
エリアはつながっている
頻出
000…0
1,2,3,4
1,2,3 1,2,4
1,2
これなら、うまいことたどれば、
頻出集合をすばやく全部見つけられそう
1,3
1
1,3,4
1,4
2,3,4
2,3
2,4
3,4
2
3
4
φ
重複に気をつける
・ また、よくよく見ると、「どの頻出集合も、空集合(アイテムが
何も入っていない集合)にアイテムを1つずつ追加して作れる
・ また、頻出集合にアイテムを追加して、頻出でなくなったら、そ
の後いくら追加しても2度と頻出にはならない
全ての追加の仕方を尽くせば、
全ての頻出集合が見つかる
・しかし、単純に全てを
尽くすと、大量に重複が出る
1,2,3,4
1,2,3 1,2,4
1,2
1,3
1
どうやって重複を回避しようか
1,3,4
1,4
2,3,4
2,3
2,4
3,4
2
3
4
φ
重複の回避法
・ グラフ探索問題(幅優先探索、深さ優先探索)をするのだ、と考
えれば、「一度訪れた頂点には、マークをつければいい」となる
 マークをどうやってつける? そもそも、探索するグラフを得る
こと自体が、解を求める作業と同じ
・ 他の手として、出力した解を全部メモリにとっておいて、新たな
頻出集合が見つかるたびに、「今までにこれを出力したか」チェッ
クをする
 メモリが大量に必要。おまけに、探索の手法、というレベルで
は、重複は避けられていないため、1つあたりの計算時間は長く
なるはず
メモリを使わず、本質的に重複を回避する方法がほしい
バックトラック法による探索
・ そもそも重複が起こるのは、各頻出集合がいくつもの部分集
合から「アイテムを1つ追加」として得られるのが原因
({1,2,3} には、{2,3}+1, {1,3}+2, {1,2}+3 の3通りある)
・ そこで、各頻出集合に対して、「作られ方」と1通りに制限する
・ 具体的には、「一番大きなアイテムを加えた場合のみ」とする
({1,2,3} は、{1,2}+3 という
1,2,3,4
作り方でしか作らない、
ということ)
1,2,3 1,2,4
1,3,4 2,3,4
探索ルートが木構造に
なるので、重複がなくなる
1,2
1,3
1
こういう探索方法をバックトラック法という
1,4
2,3
2,4
3,4
2
3
4
φ
バックトラック法の計算時間
・計算時間を算定してみよう。擬似コードは
Backtrack (K)
1 Output K
2 For each e > K の末尾( K の最大のアイテム)
If K +e が頻出集合 call Backtrack (K+e)
-再帰呼び出しの回数は、
頻出集合の数と同じ
-1呼び出し(反復と言う)の
O(|D|)
計算時間は
(n-K の末尾)×(頻出度計算時間)
1,2,3,4
1,2,3 1,2,4 1,3,4 2,3,4
1,2 1,3 1,4 2,3 2,4 3,4
1
解1つあたりの計算時間が算定できた
3
2
φ
4
解1つ当たり、を速くする
・解1つあたりの計算時間はそれなりに(多項式時間で)抑えら
れたが、まだまだ大きい
・ 各 K+e について、その頻出度を計算
-単純にするなら、全ての項目(トランザクション)について、
K+e を含むかどうか調べる
 最悪、データベースの大きさに比例、
平均ではだいたい、項目数、
1,2,5,6,7,9
頻出度×Kの大きさ、の大きいほう
- 2分木のようなデータ構造を作って、含むものだけ 2,3,4,5
1,2,7,8,9
抜き出す、あるいは勘定する、というのは、難しい
1,7,9
2,7,9
ここにもアルゴリズムが必要
2
幅優先探索の利用
D0={φ}, k := 1
while Dk-1 が空でない
for each Dk-1 のメンバー X
for each e
if X+e が頻出集合 then Dk に X+e を挿入
・ X+e の頻出度を計算する前に
X+e に含まれる部分集合が
全てDkにあるか調べる
1,2,3,4
1,2,3 1,2,4
1,2
1,3
1,3,4
1,4
2,3,4
2,3
2,4
3,4
2
3
4
・ ないものがあるなら、頻出でない
1
φ
メモリを使う点、検索に時間がかかる点がネック
含むものしか含まない
・ アイテム集合 X の出現集合を T とする
・ X+e の出現は X を含む(= X の出現)
 X+e を含むトランザクションを見つけるとき
には、 T のトランザクションしか見なくてよい
・ T のトランザクションで e を含むものを集めると
X+e の出現集合が得られる
・ 出現集合を更新すれば、
データ全体を見なくて良い
 計算時間はだいぶ短くなる
共通部分をとる
・ T のトランザクションで e を含むものを集めると X+e の出現
集合が得られる
 X+e の出現集合は、 Xの出現集合と e の出現集合の共
通部分 (両方に含まれるものを集めたもの)
・ 共通部分をとるには、両者をソートしておき、同時に先頭から
スキャンする
{1,3,7,8,9}
{1,2,4,7,9}
= {1,7,9}
計算時間は、スキャンしたアイテムの数  両者の大きさの和
ビット演算を使った共通部分の高速計算
・ 各アイテムの出現をビットの形で保持する
(現在の部分集合も同じように)
{1,3,7,8,9}
{1,2,4,7,9}


[101000111]
[110100101]
[100000101]
 共通部分の計算が、AND 演算でできる
(いっぺんに32個(最近は64個) 計算できる)
メモリの節約にもなる
しかし、後述するデータベース縮約と相性が悪い
振り分けによる高速化
・ 各アイテムに空のバケツを用意する
・ X の各出現 T に対して、以下を行う
- T に含まれるアイテム e に対して、 e のバケツにT を入れる
 この操作が終わった後は、各アイテムe
のバケツの中身は X+e の出現集合になる
A: 1,2,5,6,7,9
for each X の各出現 T
B: 2,3,4,5
for each T に含まれる e, e>Xの末尾
C: 1,2,7,8,9
eのバケツに T を挿入
D: 1,7,9
E: 2,7,9
F: 2
1: A,C,D
2: A,B,C,E,F
3: B
4: B
5: A,B
6: A
7: A,C,D,E
8: C
9: A,C,D,E
振り分けの計算時間
for each X の各出現 T
for each T に含まれる e, e>Xの末尾
eのバケツに T を挿入
・ 計算時間は,
X の各出現の (Xの末尾) より大きなアイテムの数の総和
A: 1,2,5,6,7
B: 2,3,4,5
C: 1,2,7,8,9
D: 1,7,9
E: 2,7,9
F: 2
Occurrence Deliver
・ Compute the denotations of P ∪{i} for all i’s at once,
A 1 A2
1,2,5,6,7,9
2,3,4,5
D= 1,2,7,8,9
1,7,9
2,7,9
P = {1,7}
2
Check the frequency for all
items to be added in linear
time of the database size
A5 A6 7
A9
B
2 3 4 5
C 1 C2
7 C8 C9
D 1
7
D9
7
9
E
2
F
2
Generating the recursive calls in reverse
direction, we can re-use the memory
1再帰呼び出しの計算時間のイメージ
・ 普通に頻出度の計算をすると
各 X+e に対してデータを
一回スキャンする
(n-t)個
・ 共通部分による計算は
効果はこれだけではない
D(X) と D(e) のをスキャンする
 D(X) を n-t 回スキャンし、
+
データベースの t より大きな
t
アイテムをスキャンする
・ 振り分けは D(X) に含まれるトランザ
クションの t のをスキャンする t より
大きなアイテムをスキャンする
t
(n-t)個
末広がり性
・ 再帰呼び出しを繰り返すと、 Xの頻出度は小さくなる
 振り分けの計算時間も短くなる
・ バックトラックは、各反復で複数の再帰呼び出しをする
 計算木は、下に行くほど大きくなる
 計算時間を支配するのは一番下の数レベル
計算時間長
・・・
計算時間短
ほぼ全ての反復が短時間で終了  全体も速くなる
最小サポートが大きい場合も
・ θが大きいと、下のレベルでも多くの出現を見ることになる
 末広がり性による高速化はいまひとつ
・ データベースの縮約により、下のレベルの高速化をはかる
(1) 前回追加したアイテムより小さいアイテムは消す
(2) 現在の出現集合からできるデータベースの中で、頻出になって
いないアイテムは消去する
(再帰呼び出しの中で加えられることが無いから)
(3) まったく同一のトランザクションは、1つにまとめる
1
・ 実データだと、下のほうのレベルでは
だいたい大きさが定数になる
θが小さいときと速度の大きな差はない
1
3
2
2
6
4
7
4
3
2
5
4
3
1
4
4
4
5
6
7
6
7
6
7
キャッシュとの相性
・ 速いプログラムを作るとき、キャッシュのヒット率が良く問題になる
- ループを開く
- メモリの配置を変える
for i=1 to n { x[i]=0; }
 for i=1 to n step 3 { x[i]=0; x[i+1]=0; x[i+2]=0; }
●
●
●
●
●
●
▲
▲
▲
●●●
●▲
●
▲
●
▲
再帰的に問題が小さくなり、ある反復より先ではキャッシュに入る
 末広がり性より、ほぼ全ての部分でキャッシュに入っている
木構造を用いた圧縮 (trie, prefix tree)
・ 各トランザクションを文字列とみなすと、2分木の形で格納でき、メ
モリを節約できる
 振り分けと併用できる。スキャンの時間も、それだけ短くなる
*
A: 1,2,5,6,7,9
B: 2,3,4,5
C: 1,2,7,8,9
D: 1,7,9
E: 2,3,7,9
F: 2,7,9
1
2
1
2
5
6
7
7
8
9
7
9
3
4
5
7
9
7
9
D
F
B
E
9
C
A
コンテストの結果
計算機実験: FIMI04
・ FIMI: Frequent Itemset Mining Implementations
- ICDM (International Conference on Data Mining) サテライト
ワークショップで、頻出/頻出飽和/極大頻出集合列挙のプ
ログラムコンテストを行った。2回目。3回目はなし
・去年は15、今年は8個の投稿があった
ルール:
- ファイルを読み、列挙してファイルに書くこと
- time コマンドで時間を計測(メモリも他のコマンドで計測)
- CPUを制御する命令(パイプラインなど)は使用禁止
計算機実験: FIMI04
・計算機環境:CPU、メモリ: Pentium4 3.2GHz、1GB RAM
OS、言語、コンパイラ: Linux 、C言語、gcc
・ データセット:
- 実データ: 疎、アイテム数大
- 機械学習用データ: 密、アイテム数小、規則的
- 人工データ: 疎、アイテム数大、ランダム
- 密な実データ: 超密、アイテム数小
LCM(宇野有村清見)、見事優勝
賞状と賞品
賞品は {ビール, 紙おむつ}
“Most Frequent Itemset” だそうです
実データ
(すかすか)
平均の大きさ5-10
BMS-POS
BMSWebView2
retail
実データ
(すかすか)
メモリ使用量
BMS-POS
BMSWebView2
retail
密(50%程度)で
構造があるデータ
pumsb
connect
chess
密で構造がある
データ、メモリ量
connect
pumsb
chess
密な実データ&
巨大データ
accidents
accidents メモリ
web-doc
飽和集合の列挙
頻出集合の問題点
・ 面白い頻出集合を見つけようとすると、θを小さくする必要がある
 大量の頻出集合が出てくる
・ 情報を失わずに、頻出集合的な、数の少ないものを
見つけるようにモデルを変えたい
111…1
1. 極大頻出集合:
他の頻出集合に含まれない頻出集合
2. 飽和集合:
出現集合が等しいものの中で極大なもの
000…0
極大頻出集合と飽和集合の例
・ 頻出集合を出現集合で分類
1,2,5,6,7,9
2,3,4,5
T = 1,2,7,8,9
1,7,9
2,7,9
2
3つ以上に含まれるもの
{1}
{2}
{7}
{1,7}
{1,9}
{2,7}
{2,9}
{1,7,9}
{9}
{7,9}
{2,7,9}
頻出飽和集合
極大頻出集合
出現集合の共通部分が
飽和集合になる
極大頻出集合と飽和集合
極大頻出集合
・ 多項式時間で列挙できるかどうか、未解決
・ クリークと同じように枝刈りをすると、高速に列挙できる
・ 数が少ないがθによる解のぶれが大きい
飽和集合
・ 逆探索という探索手法で多項式時間列挙可能
・ 離散アルゴリズムと末広がり性を用いて、高速列挙可能
・ 出現の意味で情報の損失がない
・ ノイズが多いと出現集合が等しいものが少なくなり、
解の減少効率が悪くなる
両者とも、1つあたりほぼ定数時間、1秒間に1~10万個
飽和集合の列挙手法
・ 頻出集合列挙ベース
- 頻出集合列挙アルゴリズムをベースに、多少無駄な計算を
省く
- 飽和集合のよさが出ない。計算時間の改善も少ない
・ 保存 + 枝狩り
- 見つけた解を保存し、それを用いて無駄な分枝を刈る
- 計算速度はまあまあ
- 解保存のためにメモリを使用し、それがひとつのネック
・ 逆探索 + データベース縮約
- 計算効率が良い
- 解保存用のメモリが不要
(LCM)
飽和集合の隣接関係
・ 飽和集合から、添え字の大きい順に要素を抜いていく
・ どこかで出現集合が大きくなる
・ その出現集合の飽和集合を求める
・ こうして求めた飽和集合を、親とする (一意的に定まる)
・ 親の頻出度は必ず真に大きいので、親子関係は非巡回的
 親子関係は有向根付き木を導出する
逆探索
親子関係は有向根付き木を導出する
この木を深さ優先探索すれば全ての解を見つけられる
・ 探索には、子供を見つけるアルゴリズムがあれば十分
・ 子供が1つあたり多項式時間で見つかれば、全体も多項式時間
(親に要素を1つ加えて極大をとった飽和集合が子供になる)
非巡回的な親子関係と、子供を見つける多項式時間アル
ゴリズムがあれば、なんでも多項式時間列挙ができる
親子関係の例
・ データベースの全ての
飽和集合とその親子関係
φ
2
1,2,5,6,7,9
2,3,4,5
T = 1,2,7,8,9
1,7,9
2,7,9
2
出現集合が隣接
親子関係
7,9
1,7,9
2,5
1,2,7,9
1,2,7,8,9
2,3,4,5
1,2,5,6,7,9
2,7,9
子どもを求める
・ 子どもから親を作る際に抜いた、最後のアイテムを親に追加
すると、出現集合は子どもと等しくなる
 子どもは、アイテムを1つ追加して、出現集合の共通部分
をとると得られる
 とはいえ、そのようにして作ったもの全てが子どもになると
は限らない
 子どもである条件は、抜いたアイテムより前の部分に、新
しくアイテムが追加されないこと
比較の手間
・K+e の出現の共通部分、素直に計算してもよいが、「共通部分
がKと等しいか」を調べるだけなので、必ずしも全て計算する必
要はない
 異なることがわかった時点で、計算をやめてよい
・ K+e の出現それぞれを小さい順にたどり、
K
全てに共通するものがあるか調べる
3 4 6 9 11
・ 全てに共通するものがあったら
K に入っているか調べる
・ 前回たどったところで止まって
おき、次回はそこからたどる
4 11
1 3 4 5 9 11
K+e の
出現集合
23 4 5 9 11
1 2 4 6 9 11
1 4 9 11
ビットマトリクス
・ スウィープポインタは、行列の形でデータを持ってないがゆえの工
夫。隣接行列を持って入れば、もっと単純に速くできる
・が、大きすぎて構築することすら不可能
・ 出現集合がある程度以下に小さくなったところで、
行列を構築しよう
 ビットで持てば、各列が1つの変数に入る!
ビットマトリクスの定数時間計算
・ 各アイテムに対応する列を1変数で持っていると、K+e の出現全
てにそのアイテムが含まれるかどうか、1ステップでチェックできる
・ K+e の出現のビットパターンと、アイテム i の列のビットパターン
の AND をとる
・アイテム i が K+e の出現全てに含まれるなら、共通部分はK+e
の出現ビットパターンと等しくなる
K の頂点
・・・
K<i ∩N(vi)
実データ
(すかすか)
平均の大きさ5-10
BMS-POS
BMSWebView2
retail
実データ
(すかすか)
メモリ使用量
BMS-POS
BMSWebView2
retail
密(50%程度)で
構造があるデータ
connect
pumsb
chess
密で構造がある
データ、メモリ量
connect
pumsb
chess
密な実データ&
巨大データ
accidents
accidents メモリ
web-doc
参考文献など
・ 頻出集合およびその応用 (’90~) 星の数ほど
“frequent pattern”、”frequent itemset” で検索すると出てくる
・ 極大頻出集合およびその応用 (’90~) やはり多い
“maximal frequent itemset” などで検索すると出てくる
・ pasquerらのアルゴリズム (‘99) 飽和集合の導入
・ 宇野らのアルゴリズムLCM (‘04) 現在最速のアルゴリズム
・ 実装 LCM: (Linear time Closed itemset Miner) 宇野のHP
http:research.nii.ac.jp/~uno/
・ レポジトリ (実装、論文、比較実験の数々)
http://fimi.cs.helsinki.fi/
・ 中野・宇野・有村 (’03~ ) 順序木・無順序木の多項式時間頻出列挙
閑話休題: 初期化のいらない配列
閑話休題:初期化のいらない配列
・ 配列は、普通、確保したら初期化(0などを代入)してから使う
 初期化しないで使えるかな???
・ 実はうまい方法がある
・ 配列を準備。初期化せず。各セルには値を入れるところと、添え
字を入れるところ、2つを割当てる
・ あと、もうひとつ、添え字の配列と、書き込まれた配列の数を覚え
るカウンタを準備。カウンタは0に設定
配列
添え字配列
閑話休題:初期化のいらない配列
値
配列
添え字配列
カウンタ 0
1
0
2
0
3
・ 配列の i 番目に値を代入するときは、添え字配列のカウンタの場
所に、i を書き込み、配列の添え字側に、カウンタを書き込む。カウ
ンタを 1 進める。
・ 配列 i 番目の値を参照するときは、添え字側の数字 p を見て、p
がカウンターより大きい、あるいは添え字配列の p 番目が i でない
なら、代入されてない、と答える
類似項目の発見
データベースから類似する項目を見つける
・ データベースの項目の中で、似た項目のペアを全て見つけたい
(項目のペア全てについて、
2項関係が成り立つかを調べる)
・ 類似している項目の検出は、
データベース解析の基礎的な手法
 基礎的なツールがあれば、使い方しだいで
いろいろなことができる
(大域的な類似性、データの局所的な関連の構造・・・)
類似項目発見の計算時間
・ 似たもののペアを全て見つけるさい、項目のペア全てについて、
単純に全対比較を行うと項目数の2乗の時間がかかる
 項目数が100万を越えるころか現実的には解けなくなる
100万×100万 ÷100万(演算per秒) = 100万秒
・ 類似検索を使う手もあるが、100万回のクエリを実行しなければ
ならない。類似検索は完全一致より大幅に時間がかかる
 1秒間にクエリが1000回できるとして、1000秒
問題が大きいので、平均的な意味でよいので、
計算オーダーの小さいアルゴリズムがほしい
応用1:似ているwebページの発見
・ web 検索を行うと、よく似た内容、あるいは引用しているものを
多量に見つけることがある
 ニュース記事、レビューの記事の一部など
・ あらかじめ、類似しているページをグループのように検出でき
れば、こういった似たものをひとくくりにでき、検索エンジンの効率
化にもつながる
(検索結果を出してから似たものを見つける、という方法もあり)
・ さらに、例えば、最近のホットな話題は何ですか、
というような検索もできるかもしれない
応用2:リンクが似ているwebページ
・ リンク先、あるいはリンク元が、集合として似ているページは、
よく似ていると考えられる
 サッカーのページ、料理のページ、地域のページ
・ グループ化すれば、コミュニティー発見、著名なトピック、web
の構造の解析など、いろいろなことがやりやすくなる
・ さらに、例えば、「スパムサイト」の検出にも使えるかも
(レポート課題のコピーの検出、とか)
応用3:長い文章の比較
・ (文庫本のような)長い文章がいくつかあるときに、類似する部
分がどことどこにあるかを検出したい
 長い文章の比較はそれ自体が大変(時間がかかる)ので、
複数あると手が出ない
・ 長い文章を、短い文字列にスライスし、全体を比較する
 大域的に似た部分は、局所的に似ている
ところを多く含むと思われる
つまり、似ている短い文字列のペアを多く含む
・ 短い文字列の全対比較からアプローチできる
応用4: ゲノムの比較
・ 異なる種のゲノムを比較して、類似するところを見つけ出したい
- 2つの染色体の比較(1億文字以上)
- 複数の、短い染色体の比較(バクテリアなど:400万程度)
- 両方とも、ゲノムをスライスして全対比較する
ATGCCGCG
GCGTGTAC
GCCTCTAT
TGCGTTTC
TGTAATGA
...
・ ATGCCGCG と AAGCCGCC
・ GCCTCTAT と GCTTCTAA
・ TGTAATGA と GGTAATGG
...
応用5: 特異的な部分を見つける
・ 似ているものがない項目は、データの中で特異的な部分と考え
られる
- 携帯電話・クレジットカードの不正使用
- 制御システムの故障・異常の発見
- 実験データから新しいものを発見
- マーカーの設計 (「宇野毅明のホームページは、”助教授,
宇野毅明の研究”で検索するとユニークに定まる)
・ 比較的大きなデータが複数あるような場合でも、特異な項目を
多く含むもの、他のどのデータとも、似ている部分が少ないもの
は、特異なデータだと考えられる
・ 「似ている項目の数」は、データがエラーを含む際の統計量とし
て使えるかも知れない
応用5: マイクロアレイのデザイン
・ マイクロアレイは調べたいDNAに、ある特定の短い文字列(20
文字程度)が含まれているかどうかを検出する実験装置(いっぺ
んにたくさん実験できる)
・ 特定の遺伝子(あるいは変化)が含まれているか、たくさんの微
生物が含まれているコロニーの中に、特定の生物種がいるか、と
いったことを調べる際に使われる
・ 短い文字列が、他の場所にも含まれていると、検出が効率良く
できない
 固有の短い文字列があらかじめわかっているとうれしい
問題設定:短い文字列の比較
・ 具体的に見るため、扱うデータベースと問題を具体化する
問題: 各項目が同じ長さ l の短い文字列(50文字程度)である
データベースを入力したときに、文字列のペアで異なり数が d 文
字以下である組を全て見つけよ
(ハミング距離がd 以下)
・ 長くて、ある程度似ている文字列は、このような似ている部分
列をある一定数以上含む
ATGCCGCG
GCGTGTAC
GCCTCTAT
TGCGTTTC
TGTAATGA
...
・ ATGCCGCG と AAGCCGCC
・ GCCTCTAT と GCTTCTAA
・ TGTAATGA と GGTAATGG
...
問題の難しさ
・ 全ての項目が同じだと、およそ項目数2) 個の出力がある
 l を定数だと思えば、単純な全対比較のアルゴリズムが
計算量の意味では最適になる
 計算量理論的には面白くない問題
・ 現実には、やたらと似てるものがあるものを比較しても意味が無い
 出力は少ないと仮定する
ATGCCGCG
GCGTGTAC
GCCTCTAT
TGCGTTTC
TGTAATGA
...
・ ATGCCGCG と AAGCCGCC
・ GCCTCTAT と GCTTCTAA
・ TGTAATGA と GGTAATGG
...
基本のアイディア:文字列の分割
・ 各文字列を、k(>d) 個のブロックに分割する
・ k-d 個のブロックの場所を指定したときに、そこがまったく等しく
て、かつハミング距離がd 以下となるようなペアを全て見つけよ、
という部分問題を考える
 各文字列の k-d 個のブロックをつなげてキーにし、ソートをす
る。同じものはグループになる。それを総当りで比較すればよい
・ k-d 個のグループ数が大きければ、平均的にグループのメン
バー数は小さくなるので、総当りで比較してもたいしたことない
全ての場合を尽くす
・ 先の部分問題を、全ての場所の組合せについて解く
 2つの文字列が似てれば、必ずどこか k-d 個のブロックが同じ
 必ずどれかの組合せで見つかる
・ 部分問題は、バケツソートやRadixソートで速く解ける
・ 組合せの数は kCd 。のk=5 で d=2 なら10通り
 ソート10回 +α で解ける。全対比較よりもかなり高速
・各文字の数から、1文字比較した場合に等しくなる確率を求め、
適切な分割数 k を使用する
例題
・ ABC、ABD、ACC、EFG、FFG、AFG、GAB のペアでハミ
ング距離が1以下のものを求めよ
A
A
A
E
F
A
G
B
B
C
F
F
F
A
C
D
C
G
G
G
B
G
A
A
A
E
F
A
A
B
B
C
F
F
F
B
C
D
C
G
G
G
A
A
A
A
E
F
G
B
C
B
F
F
F
A
C
C
D
G
G
G
B
A
A
A
A
E
F
G
B
B
C
F
F
F
A
C
D
C
G
G
G
B
重複の回避
・ まったく同じ文字列があると、全てのブロックの組合せで見
つかるので、 kCd 。回出力される
 重複を回避する必要がある
・ 各見つかったペアについて、選択されていないブロックが選
択したブロックの間にあったら出力しないようにする
 等しいブロックが一番左端によっている場合にのみ出力
メモリに解を保持せずとも、重複が回避できる
イメージ的には
・ 似ているもののペアを探す問題は、マトリクスのセルの中で必
要なものを全て見つける問題
・ 全対比較は、マトリクスのセルをすべて見ていることに対応
・ 分類によるアルゴリズムは、
分類を順々にしていると思えば、
木構造の探索を行っていることに対応
実験:長さ20文字で異なり数 d を変化
10000
1000
d=0
d=1
d=2
d=3
10
20
00
70
00
22
95
3
0.1
70
0
1
20
0
計算時間(秒)
100
長さ(1000文字)
ゲノムの比較
染色体と染色体を比較する
- 1億以上の文字列の比較になるので、非常に時間がかかる
- アラインメントでは部分が入れ替わった構造が見つけられない
・ 比較するゲノムを、オーバーラップするようにスライスし、全対比較
・ 縦横に比較するゲノムをおき
マトリクスを作って類似するペアが
あるセルの色を白くする
(実際は細長い四角でいい)
類似する構造が見える
ゲノムの比較 (1)
ヒト21番染色体とチンパンジー22番染色体の比較
・長さ3000万の配列×2 から、30文字の切片を3000万個取る
・ 似ている部分配列のペアの数に応じて、各ドットの明るさを変える
ヒト 21番染色体
・ 白い部分が
「似ている可能性のある部分」 チ
ン
・ 黒い部分が
パ
「(絶対に)似ていない部分」 ン
ジ
・ 格子模様は、繰り返し
配列を拾っている
PCで 1時間で可能
ー
22
番
染
色
体
ゲノムの比較 (2)
ヒトX染色体とマウスX染色体の比較
・ 30文字で間違い2文
字以下のペアを列挙
・ 長さ3000、幅300
の領域に3つペア
があれば点を打つ
PCで 1時間で可能
X
・ ノイズをかなり
除去できている
マ
ウ
ス
染
色
体
ヒトX番染色体
ゲノムの比較 (3)
バクテリアを30種
ABC順の取得し
つなげて比較
PCで 1時間で可能
(マイクロアレイ用の)固有な配列のデザイン
・ マイクロアレイの設計には、ゲノム配列中でなるべく他の部分と
似ていない配列が使えるとありがたい
・ 配列の長さは20文字、のように決まっているので、
対象となるゲノムの全て20文字の部分配列を比較し、
似ているものがないもの、を見つければよい
・ 似ている文字列の数、はある種の統計量として利用できるかも
しれない
100Mベース、25文字、間違い2文字まで、くらいなら
PCで 1時間で可能
ゲノムの読み取り
・ ゲノム配列は、そのままひも状のものを一度に読むことはできない。
通常、一度に500文字程度しか読めない。
・ そこで、染色体を10万文字程度の長さにぶつ切りにし、大腸菌に移
植して増殖させる
・ 増殖したものをさらにぶつ切りにし、500文字程度ずつ読み、つなげ
る(できたものをBAC配列と言う)
・ BAC配列をさらにつなげて、もとの染色体を作る
重なり部分の検出
・ 短い配列を読んだとき、あるいはBAC配列を作ったとき、それがもとの
染色体、BAC配列のどの位置にあったかはわからない
 配列を構成する際に使える情報は「どことどこが重なるか」
・ 機械の読み取りエラーや大腸菌が増殖する際のコピーミスも起こりうる
ので、「完全に重なる」ではなく「だいたい重なる」でなければいけない
・ BAC1つ作るのに、100-1000万文字の比較が必要
BAC配列の比較
・ ゲノム全体の配列を決める際には、BAC同士がどのようにつ
ながるかを調べる必要がある
・ しかし、どの配列とどの配列がオーバーラップしているか調べ
るのは、大変。(前述のアセンブリをミスしていると、微妙に異な
るところが出て、重ならなくなる)
・ 既存の手法は、直接的でない
手段を使って比較をしていて、
ときどきオーバーラップしそうな
ところを落としてしまう
・ この手法なら全対比較可能
PCで 1ペア1秒
課題点
・ マウス13番染色体の未解読領域に対して、この相同検索アルゴリズ
ムの適用を行っている
 既にいくつかの空白部分が埋まった
・ 比較は高速にできるようになった。しかし、比較した結果をどう使うか、
どのような点に留意する必要があるか、といった点は、まだまだ明らか
でない
- 実験の指針を出すためには、
何を出力する必要があるか
- どの程度の精度が必要か
- どこまで処理を自動化すべきか
- エラーをどのように扱うべきか
 既存のアセンブリングソフトでは
見つからない、特殊な重なり方を
している相同領域が見つかる。
どう解釈すべきか?
類似部分(アイテム)集合の列挙
問題の定義
入力: 部分集合族(トランザクションデータベース) D = {T1,…,Tn}
( ただし、各 Ti はアイテム集合 E = {1,…,n} の部分集合)
+ 閾値 θ
出力: |Ti∩Tj| がθより大きいような、
全てのTi 、Tjのペア
例: 閾値 θ=2 のとき、
(A,B), (A,C), (A,D), (A,E)
(C,D), (C,E), (D,E)
D =
A: 1,2,5,6,7
B: 2,3,4,5
C: 1,2,7,8,9
D: 1,7,9
E: 2,7,9
F: 2
D が巨大かつ疎で(各Ti が平均的に小さい)、出力の数がそれ
ほど多くない( ||D|| の数倍)状況での高速化を考える
単純に全対比較すると
・ 単純に全対比較するアルゴリズムを考える
D =
for i=1 to |D|-1
for j=i to |D|
if |Ti∩Tj|≧ θ then output (Ti, Tj )
A: 1,2,5,6,7
B: 2,3,4,5
C: 1,2,7,8,9
D: 1,7,9
E: 2,7,9
F: 2
・ 共通部分の計算は、各 Ti をアイテム順でソートしておき、Ti 、Tj
をマージソートのように並行してスキャンすればよい。
時間はO(|Ti |+| Tj|)
・ 全体の計算時間は ∑i,j(|Ti |+| Tj|) = 2n ||D||
かなり時間がかかると見てよい
振り分けによる高速化
・各Ti に対し、|Ti∩Tj|がθ以上になるものを見つける問題を考える
・ 各アイテム e に対して、e を含む部分集合の集合を D(e) とする
・ Ti が含む各 e に対して、D(e) の各 T に対して、カウントを1つ増
やす、という作業をする
 全ての e∈Ti についてこの作業をすると、各 Tj のカウントは
|Ti∩Tj| になる
for each e∈Ti
for each Tj∈ D(e), j>i, do c(T)++
・ D(e) を添え字順にソートしておくと、j>i である
Tj∈ D(e) を見つけるのも簡単
D =
A: 1,2,5,6,7
B: 2,3,4,5
C: 1,2,7,8,9
D: 1,7,9
E: 2,7,9
F: 2
振り分けの計算時間
for each e∈Ti
for each Tj∈ D(e), j>i, do c(T)++
・ 計算時間は
∑j ∑e∈Ti |{ Tj∈D(e), i<j}| = ∑e |D(e)|2
 |D(e)| が平均的に小さければ、かなり速い
D =
A: 1,2,5,6,7
B: 2,3,4,5
C: 1,2,7,8,9
D: 1,7,9
E: 2,7,9
F: 2
1再帰呼び出しの計算時間のイメージ
・ 普通に頻出度の計算をすると
各 X+e に対してデータを
一回スキャンする
・ 共通部分による計算は
D(e) と D(e) のをスキャンする
 D(X) を n-t 回スキャンし、
データベースの t より大きな
アイテムをスキャンする
・ 振り分けは D(X) に含まれるトランザ
クションの t のをスキャンする t より
大きなアイテムをスキャンする
(n-t)個
+
t
t
(n-t)個
計算実験
・ webリンクのデータの一部を使用
- ノード数 550万、枝数1300万
- Pentium4 3.2GHz、メモリ2GB
・ リンク先 20個以上 288684個、20個以上共有する
ペア数が143683844個、計算時間、約8分
・ リンク元 20個以上が 138914個、20個以上共有する
ペア数が18846527個、計算時間、約3分
・ 方向を無視して、リンクが 100個以上あるものが 152131個、
100個以上共有するペア数が32451468個、計算時間、約7分
・方向を無視して、リンクが20個以上あるものが370377 個、
20個以上共有するペア数が152919813個、計算時間、約14分
簡単に追加できる条件
・ |A∩B| が、|A| のα倍以上 、(and/or) |B| のα倍以上
・ |A∩B| / |A∪B| ≧ α
・ |A∪B| -|A∩B| ≦ θ
など。
この手の、共通部分や和集合の大きさから計算できる評価値
であれば、簡単に評価できる
・ 計算時間は、ほぼ線形で増えていくと思われるので、多少
大きなデータでも大丈夫
まとめ
・ 速いプログラムを作るための理論、アルゴリズム理論
・ 計算の構造・デザインを工夫することで、コーディング技術では
届かないくらいの高速化を行う
- 頻出集合を列挙するアルゴリズム
計算構造に着目して、解1つあたりの計算時間を短縮
- 類似する項目のペアを列挙する出力数依存型のアルゴリズム
異なりの場所に注目し、分類による絞込みを行う
これからも、より質の高いプログラム作りを目指して
がんばってください