1.12k likes | 1.26k Views
いいプログラムは コーディング技術だけではない. 宇野 毅明 (国立情報学研究所 &総合研究大学院大学). 200 7 年 3 月 22 日 JOI 合宿. 簡単に自己紹介. 名前: 宇野 毅明 年齢・職種: 36歳、助教授 研究分野: アルゴリズム理論 - コンピュータプログラムの設計手法の理論 - 速いコンピュータを作ったり、プログラミングの腕を競うの ではなく、「設計方法の違いによる性能の向上」を目指す
E N D
いいプログラムはコーディング技術だけではないいいプログラムはコーディング技術だけではない 宇野 毅明 (国立情報学研究所 &総合研究大学院大学) 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倍 アルゴリズム理論の利点 ・ 大規模な計算には、アルゴリズム理論に基づいた技術が有効 アルゴリズム理論による高速化は、問題の大きさに対する計算時間の増加を抑える 計算の結果は変化しない データが巨大になるほど、アルゴリズム改良の加速率は上がる
データベースを分析したい データベース ・ データベース構築と検索は、もうできるようになった (絞込みや、あいまい検索はまだ改良の余地があるけど) ・より詳しくデータを解析するために、データの特徴を捉えたい 各種統計量(データベースの大きさ、密度、分布)よりも、深い解析がしたい 組合せ(パターン)的な構造に注目 (どういう組合せ(パターン)が どれくらい入っているか) ・ 組合せ・パターンの個数は指数的に 増えていくので、全てを尽くすのは無理 多く現れるものだけに注目 ATGCGCCGTA TAGCGGGTGG TTCGCGTTAG GGATATAAAT GCGCCAAATA ATAATGTATTA TTGAAGGGCG ACAGTCTCTCA ATAAGCGGCT 実験結果 ゲノム情報
頻出パターンの列挙 ・ データベースの中に多く現れるパターンを全て見つける問題を 頻出パターン列挙(あるいは発見、マイニング)問題という データベース: トランザクション、ツリー、グラフ、多次元ベクトル パターン: 部分集合、木、パス・サイクル、グラフ、図形 データベース 頻出する パターンを抽出 ・ 実験1● ,実験3 ▲ ・ 実験2● ,実験4● ・ 実験2●, 実験3 ▲, 実験4● ・ 実験2▲,実験3 ▲ . . . ATGCGCCGTA TAGCGGGTGG TTCGCGTTAG GGATATAAAT GCGCCAAATA ATAATGTATTA TTGAAGGGCG ACAGTCTCTCA ATAAGCGGCT ・ATGCAT ・CCCGGGTAA ・GGCGTTA ・ATAAGGG . . . 実験結果 ゲノム情報
多く現れる 頻出する 多く現れるものを見つけるために、多く現れるとは何か、を決める ・ データベースが項目の集まりだとする ・ パターンに対して、そのパターンを含む項目を出現という ・ 出現の数(頻出度)が閾値より大きければ、良く現れるとする (含む、の定義は、集合で行ったり、文字列の 包含、グラフの埋め込みなどで定義する) パターン XYZ {A,C,D} 項目 AXccYddZf {A,B,C,D,E}
トランザクションデータベース 1,2,5,6,7 2,3,4,5 1,2,7,8,9 1,7,9 2,7,9 2 D= ・ パターンとして、集合を考える (集合:ものの集まり。ここでは {1,2,…,n}。部分集合は、この中からいくつかを選んだもの。{1,4,7} など。) トランザクションデータベース: 各トランザクション Tがアイテム集合 E={1,…,n}の 部分集合であるデータベース -POSデータ(各項目が、客1人の購入品目) - アンケートのデータ(1人がチェックした項目) -web log (1人が1回のwebサーフィンで見たページ) - オプション装備 (車購入時に1人が選んだオプション) 実際のデータは、大きくて疎なものが多い パワー則、スモールワールドが成り立つ
集合の出現と頻出度 集合Kに対して: Kの出現:Kを含む D のトランザクション Kの出現集合 Occ(K): Kを含む Dのトランザクション全ての集合 Kの頻出度 frq(K): Kの出現集合の大きさ {1,2}の出現集合 ={ {1,2,5,6,7,9}, {1,2,7,8,9} } 1,2,5,6,7,9 2,3,4,5 1,2,7,8,9 1,7,9 2,7,9 2 T= {2,7,9}の出現集合 ={ {1,2,5,6,7,9}, {1,2,7,8,9}, {2,7,9} }
頻出集合 3つ以上に含まれるもの {1} {2} {7} {9} {1,7} {1,9} {2,7} {2,9} {7,9} {1,7,9} {2,7,9} 1,2,5,6,7,9 2,3,4,5 1,2,7,8,9 1,7,9 2,7,9 2 ・頻出集合:Tの定数θ個以上のトランザクションに含まれる集合 (頻出度がθ以上の集合)( θを最小サポートとよぶ) 例) データベースT の3つ以上のトランザクションに含まれる集合 T= 与えられたトランザクションデータベースと最小サポートθに対して、頻出集合を全て見つける問題を考える
応用:バスケット分析 ・ スーパーなどの小売店舗で、同時に購入される事の多い品物の組を知りたい ・ 客が購入した品目 トランザクション ・ 品目の組で、多くの客が購入したもの 多くのトランザクションに含まれるアイテム集合 (あるθに対する)頻出集合 ● 牛乳、弁当 ● お茶、弁当 ● おにぎり、雑誌 ● はさみ、のり ● ラーメン、はし ● こっぷ、皿 ● 弁当、おにぎり ... 「おむつとビールの組合せが良く売れる」 という発見が有名
データ ベース データ ベース 応用:データベースの比較 ・ 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,3,4 2,3,4 1,3 1,4 2,3 2,4 3,4 1,2 1 3 4 2 φ 頻出集合の単調性 ・ 工夫をするためには、何か問題の特徴をつかまなくてはいけない ・ 使えそうなのが、「頻出集合の部分集合は必ず頻出」、というもの(単調性という) つまり、ハッセ図(包含関係を 図示したもの)の上では、 頻出集合が存在する エリアはつながっている これなら、うまいことたどれば、 頻出集合をすばやく全部見つけられそう
1,2,3 1,3 1,2 1 1 φ 1,2,3 φ 1,2 2 φ 重複に気をつける 1,2,3,4 ・また、よくよく見ると、「どの頻出集合も、空集合(アイテムが 何も入っていない集合)にアイテムを1つずつ追加して作れる ・また、頻出集合にアイテムを追加して、頻出でなくなったら、その後いくら追加しても2度と頻出にはならない 全ての追加の仕方を尽くせば、 全ての頻出集合が見つかる ・しかし、単純に全てを 尽くすと、大量に重複が出る 1,2,3 1,2,4 1,3,4 2,3,4 1,3 1,4 2,3 2,4 3,4 1,2 1 3 4 2 どうやって重複を回避しようか φ
重複の回避法 ・グラフ探索問題(幅優先探索、深さ優先探索)をするのだ、と考えれば、「一度訪れた頂点には、マークをつければいい」となる マークをどうやってつける? そもそも、探索するグラフを得ること自体が、解を求める作業と同じ ・他の手として、出力した解を全部メモリにとっておいて、新たな頻出集合が見つかるたびに、「今までにこれを出力したか」チェックをする メモリが大量に必要。おまけに、探索の手法、というレベルでは、重複は避けられていないため、1つあたりの計算時間は長くなるはず メモリを使わず、本質的に重複を回避する方法がほしい
1,2,3,4 1,2,3 1,2,4 1,3,4 2,3,4 1,3 1,4 2,3 2,4 3,4 1,2 1 3 4 2 φ バックトラック法による探索 ・ そもそも重複が起こるのは、各頻出集合がいくつもの部分集合から「アイテムを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,3 1,4 2,3 2,4 3,4 1,2 1 3 4 2 φ バックトラック法の計算時間 ・計算時間を算定してみよう。擬似コードは Backtrack (K) 1 Output K 2 For each e > Kの末尾( Kの最大のアイテム) If K+e が頻出集合 callBacktrack (K+e) -再帰呼び出しの回数は、 頻出集合の数と同じ -1呼び出し(反復と言う)の 計算時間は (n-Kの末尾)×(頻出度計算時間) O(|D|) 解1つあたりの計算時間が算定できた
解1つ当たり、を速くする ・解1つあたりの計算時間はそれなりに(多項式時間で)抑えられたが、まだまだ大きい ・ 各 K+eについて、その頻出度を計算 -単純にするなら、全ての項目(トランザクション)について、 K+e を含むかどうか調べる 最悪、データベースの大きさに比例、 平均ではだいたい、項目数、 頻出度×Kの大きさ、の大きいほう - 2分木のようなデータ構造を作って、含むものだけ 抜き出す、あるいは勘定する、というのは、難しい 1,2,5,6,7,9 2,3,4,5 1,2,7,8,9 1,7,9 2,7,9 2 ここにもアルゴリズムが必要
幅優先探索の利用 1,2,3,4 D0={φ}, k := 1 while Dk-1が空でない for each Dk-1のメンバー X for each e if X+e が頻出集合 thenDkに X+e を挿入 ・X+e の頻出度を計算する前に X+e に含まれる部分集合が 全てDkにあるか調べる ・ ないものがあるなら、頻出でない 1,2,3 1,2,4 1,3,4 2,3,4 1,3 1,4 2,3 2,4 3,4 1,2 1 3 4 2 φ メモリを使う点、検索に時間がかかる点がネック
含むものしか含まない ・ アイテム集合 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} [101000111] {1,2,4,7,9} [110100101] [100000101] 共通部分の計算が、AND 演算でできる (いっぺんに32個(最近は64個) 計算できる) メモリの節約にもなる しかし、後述するデータベース縮約と相性が悪い
振り分けによる高速化 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 ・ 各アイテムに空のバケツを用意する ・ Xの各出現 T に対して、以下を行う -Tに含まれるアイテム e に対して、 e のバケツにT を入れる この操作が終わった後は、各アイテムe のバケツの中身は X+e の出現集合になる for each X の各出現 T for each Tに含まれる e, e>Xの末尾 eのバケツに Tを挿入 A: 1,2,5,6,7,9 B: 2,3,4,5 C: 1,2,7,8,9 D: 1,7,9 E: 2,7,9 F: 2
振り分けの計算時間 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 A A A A 1,2,5,6,7,9 2,3,4,5 1,2,7,8,9 1,7,9 2,7,9 2 C C C D= D ・ Compute the denotations of P ∪{i} for alli’s at once, P = {1,7} Check the frequency for all items to be added in linear time of the database size Generating the recursive calls in reverse direction, we can re-use the memory
1再帰呼び出しの計算時間のイメージ (n-t)個 効果はこれだけではない ・ 普通に頻出度の計算をすると 各X+e に対してデータを 一回スキャンする ・ 共通部分による計算は D(X)と D(e) のをスキャンする D(X)を n-t 回スキャンし、 データベースの t より大きな アイテムをスキャンする ・ 振り分けは D(X)に含まれるトランザ クションの t のをスキャンする t より 大きなアイテムをスキャンする + (n-t)個 t t
・・・ 末広がり性 ・ 再帰呼び出しを繰り返すと、 Xの頻出度は小さくなる 振り分けの計算時間も短くなる ・ バックトラックは、各反復で複数の再帰呼び出しをする 計算木は、下に行くほど大きくなる 計算時間を支配するのは一番下の数レベル 計算時間長 計算時間短 ほぼ全ての反復が短時間で終了 全体も速くなる
最小サポートが大きい場合も ・θが大きいと、下のレベルでも多くの出現を見ることになる 末広がり性による高速化はいまひとつ ・ データベースの縮約により、下のレベルの高速化をはかる (1)前回追加したアイテムより小さいアイテムは消す (2) 現在の出現集合からできるデータベースの中で、頻出になっていないアイテムは消去する (再帰呼び出しの中で加えられることが無いから) (3) まったく同一のトランザクションは、1つにまとめる ・ 実データだと、下のほうのレベルでは だいたい大きさが定数になる θが小さいときと速度の大きな差はない
キャッシュとの相性 ・ 速いプログラムを作るとき、キャッシュのヒット率が良く問題になる - ループを開く - メモリの配置を変える 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; } 再帰的に問題が小さくなり、ある反復より先ではキャッシュに入る 末広がり性より、ほぼ全ての部分でキャッシュに入っている
* 1 2 5 6 7 9 A 7 8 9 C 9 7 D 3 4 5 21 B 7 9 E 7 9 F 木構造を用いた圧縮 (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
計算機実験: 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(宇野有村清見)、見事優勝