C ++でテーブルを保存する最良の方法は何ですか


8

C4.5アルゴリズムのわずかに変更されたバージョンを使用して、C ++で決定木をプログラミングしています。各ノードはデータセットの属性または列を表し、属性の可能な値ごとに子があります。

私の問題は、各ノードにサブセットを使用する必要があるため、行と列のサブセットのみを選択する簡単な方法が必要であることを念頭に置いて、トレーニングデータセットを格納する方法です。

主な目標は、可能な限り最もメモリと時間効率の良い方法で(優先度の高い順に)実行することです。

私が考えた最善の方法は、配列の配列(またはstd :: vector)またはそのようなものを持ち、各ノードにリスト(配列、ベクトルなど)または何かcolumn,line(おそらくタプル)を持つことですそのノードに有効なペア。

私はこれを行うためのより良い方法があるはずです、何か提案はありますか?

更新:私が必要なのは次のようなものです:

初めに私はこのデータを持っています:

Paris    4    5.0    True
New York 7    1.3    True
Tokio    2    9.1    False
Paris    9    6.8    True
Tokio    0    8.4    False

しかし、2番目のノードでは、次のデータが必要です。

Paris    4    5.0
New York 7    1.3
Paris    9    6.8

そして、3番目のノードの場合:

Tokio    2    9.1
Tokio    0    8.4

しかし、最大数百の列を持つ数百万のレコードのテーブルがあります。

私が念頭に置いているのは、すべてのデータをマトリックスに保持し、各ノードについて現在の列と行の情報を保持することです。このようなもの:

Paris    4    5.0    True
New York 7    1.3    True
Tokio    2    9.1    False
Paris    9    6.8    True
Tokio    0    8.4    False

ノード2:

columns = [0,1,2]
rows = [0,1,3]

ノード3:

columns = [0,1,2]
rows = [2,4]

このように、最悪のケースのシナリオでは、私は無駄にしなければなりません

size_of(int) * (number_of_columns + number_of_rows) * node

これは、ノードごとに独立したデータマトリックスを持つよりもはるかに少ないです。


Lokiのマルチメソッドを確認することをお勧めします。loki-lib.sourceforge.net/html/main.html
Stu

C ++に移植(変換)したい非C ++実装はありますか?そうでなければ、あなたの質問はより注目されるかもしれません:stats.stackexchange.com
mda

それがあなたの目的に役立つかどうかはわかりませんが、boost :: multi_indexはあなたが探しているものです。
sergiol 2013年

1
これが解決されたかどうか、またはこれがあなたが望んだものかどうかわからないが、構造体を作成してそこに値を格納し、各構造体を配列/ベクトルまたはリストに格納しないのはなぜですか?

回答:


2

前回C4.5を理解しようとして失敗しましたが、ID3のバリアントを実装しました-もともとは好奇心のためでしたが、最終的には過剰キルの複数ディスパッチコードジェネレーターの一部として使用されました。ただし、これは大規模なデータセットを処理することはなく、良い仕事です。あなたは私がしたことのほとんどを真似るのは上手ではありませんが、いくつかの例外はありますが、もちろん間違いから少し学びました。

私はエキスパートシステムのディシジョンツリーを構築するという観点から考える傾向があるため、次の用語を使用する傾向があります。混乱を招く場合は申し訳ありません...

Column = Question ..... A question the expert system might ask
Row    = Conclusion ... A possible conclusion the expert system might reach
Cell   = Answer ....... For the question and conclusion, what answer should
                        the user be expected to give

実際、私の場合、論理ゲートの真理値表のように、別の列に結論を出しました。したがって、行番号は単なる行番号でした。これにより、同じ結論を複数の行に表示できない場合でも表現できないXORスタイルの問題を処理できました。これがあなたに関係があるかどうかはわかりません。いずれにせよ、私はこれを無視します-とにかく次に尋ねる質問の選択の詳細を見るまでは、それほど大きな違いはありません。データマイニングの場合、おそらくいずれにしても、ターゲットの結論として扱う特定の情報はありません。「結論」は、質問をやめることに決めたときに残っているものです。

したがって、これまでに導出された各決定木ノードについて、一連の未解決の質問(列)と、まだ排除されていない結論(行)のセットがあります。それが私がしたことです。追加する価値がある唯一の点は、ビットベクトルを使用したことです。

IIRC、C ++ std::vector<bool>std::array<bool> ビットベクトルとして実装されていますが、それでも一度に一つのアイテムを操作する一連の操作のためのSTLアルゴリズム、に依存しています。私は、一定期間にわたって徐々に構築され、基礎となるビット単位演算子を使用する独自のビットベクトルクラスを使用しましたstd::vector<CHUNK>(ここCHUNKで、unsigned int型は、通常32ビット幅です)。

C ++ 11またはBoostには、より優れたビットベクトルオプションがあり、いくつかの場所には優れたライブラリがいくつかあるはずです-符号なし整数のセットで作業することになるプログラムの種類はたくさんあります。自分の使い方から切り替えるのが面倒なので、私はそれらについてあまり知りません。

ただし、ビットベクトルは、セットがほぼ密集している場合に最適です。この場合、行のセットが明らかな問題です。デシジョンツリーのルートノードのみが完全に密な行セットを持ちます。ルートから遠ざかるにつれて、行セットはまばらになり、各質問に回答することで、行のセットが2つ以上の互いに素な次のノードの行セットに分散されます。

そのため、行番号の単純なソート済み配列がこれらのセットの最適な表現になる可能性があります。ただし、「スパースビットベクトル」が価値がある場合もあります。可能な実装の1つは、ソートされたペアの配列です。各ペアの最初のペアはブロックの最初の行IDで、2番目はそのブロックの固定サイズのビットベクトルです。たとえば、行番号35はブロック32(35 & ~(32 - 1))のビット位置3(35 & (32 - 1))に格納されます。ビットベクトルがゼロ以外であるペアのみを保存する場合、これにより、IDの並べ替えられた配列と単純なビットベクトルの間に何かが与えられます-特に、IDがセットで密にクラスター化する傾向がある場合は、スパース配列を適切に処理します。

また、サイズが十分に小さくなると、ビットベクトルからソートされた配列表現に切り替えることができるクラスを使用する価値があります。ルートの近くのいくつかのノードに利益をもたらすだけの余分な複雑さは、おそらく無意味です。

とにかく、これらのセットは単一の定数「データベース」を参照するため、これらのセットが表現されるため、アルゴリズムの実行時にデータのコピーとスペースの無駄を大幅に節約できます。しかし、その「データベース」を検討する価値はまだあります。

連想データ構造を使用して、質問IDと結論IDのタプルを使用して検索し、回答IDを取得しました。つまり、キー(質問IDと結論ID)のアイテムごとのオーバーヘッドがあり、この場合はB +スタイルのツリーオーバーヘッドもありました。理由-基本的に習慣。私は非常に柔軟なコンテナーを持っていますが、後で実際に必要になる機能を予測する手間を省くため、それらを頻繁に使用する傾向があります。それには代償がありますが、それは古い時期尚早な最適化の問題です。

あなたの場合、あなたはマトリックスを使用しています-私は、質問IDと回答IDでインデックス付けされた2次元配列を想定しています。

私のバージョンがあなたのバージョンよりも効率的であると想像できる唯一の方法は、ほとんどの答えが不明な場合です。マトリックスでは、既知の回答IDと同じスペースを使用して、そのための特別な不明な回答IDが必要です。連想コンテナでは、それらの行を除外します。

それでも、ソートされた配列は、私のB +ツリーベースのソリューションよりも効率的です。効率的な挿入を可能にする必要がないため、必要なオーバーヘッドはキーのみです。

問題となる可能性のある2つのキーフィールド(質問と結論、行と列)を使用している場合(私は覚えていません)-テーブルの1つのコピーを1つの並べ替え順序で保持できない場合があります。しかし、の行に沿って単一の計算されたキーを使用する場合(row * num_columns) + column、基本的に、とにかく2次元のスパース配列を実装しています。

私にとって、特定の質問に対する未知/未定義の回答の存在は、その質問をすることはまだ許可されていないことを意味します-それも、アルゴリズムを最初に実装したときに使用した理論にすぎません。実際に使ったことはありません。私はそれを置くことができる使用法がありますが、私はそれに取り掛かりませんでした。参考までに、その複数ディスパッチコードジェネレーターでは、型のフィールドに基づいてディスパッチするというアイデアが1つありました。タイプ自体はポリモーフィックであるため、これらのフィールドは存在しない場合もあり、存在する必要があることを確認した後でのみ、フィールドを確認することが有効です。

不明/未定義の回答のアプリケーションがない場合は、既存のマトリックスがおそらく最良のソリューションです。

だから基本的にはそれだけです-私は明らかにより良いオプションを提供することはできません。ただし、考慮すべきトレードオフの可能性がいくつかあります。もちろん、これは時期尚早な(そしておそらく誤った)最適化ではないと仮定します。

主なトレードオフの問題は、値の疎なセットと密なセットの表現の効率に関連するため、C4.5または意思決定ツリーの構築に固有のものではありません。また、より「洗練された」アプローチは、慎重に選択された単純なアプローチよりも効率が悪いことがよくあります。


1

あなたの考えは良いと思います。あなたが望むデータにアクセスするためのあなたのライブラリがどれほど一般的であるかはわかりません。しかし、最初のデータ全体を複数std::vectorの行に格納するようなこともできます。列が不変である場合、データ型にはboost :: tupleライブラリを選択します。

次に、「データベース」全体へのハンドルをノードタイプに渡すことができます。このような構造から列と行のサブセットを取得/アクセスするのは非常に簡単です。ノードタイプは、データにアクセスするための一種のプロキシオブジェクトまたはラッパーとして機能し、データベース全体のビューとして機能します。

さらに、データは直接アクセスされるため、時間コストは時間的に一定です。あなたが述べたように、メモリフットプリントは低くなります。主なコストは初期データベースであり、作成されたデータ自体のコピーがないためです。コストのみが行と列のベクトルからのインデックスになります。

ただし、注意点が1つあります。数百万のレコードと数百の列について話す場合、メモリにはスケーラビリティの制限があることを覚えておく必要があります。実際のデータベースシステムに頼らざるを得ない場合は、メモリ量の制限が原因である可能性があります。私はおそらくSQLiteに助言するでしょう。SQLiteは、揮発性のインメモリデータベースを作成することができます。そのため、データセットが大きくなりすぎた場合は、最初から問題なく通常のファイルDBにシームレスに移行できます。


1

モデルでスペースを節約するためにできることの1つは、ノードのインデックスをビット配列として管理することです。だからあなたの例から:

ノード2:

列= [0,1,2] = 5または101

行= [0,1,3] = 11または1011

ノード3:

列= [0,1,2] = 5または101

行= [2,4] = 20または10100

これは、size_of(int)*(number_of_columns + number_of_rows)* nodeからsizeof(int)* 2 * nodeまでの要件になります。

このアプローチは、行列の次元をsizeof(int)列と同じ行数に制限します。この制限(特に行に強い)を克服するには、それらをブロックに格納します。その場合、ブロックを指定する各ノードで新しい整数が必要になり、合計インデックスサイズはsizeof(int)* 3 * nodeになります。これは、size_of(int)*(number_of_columns + number_of_rows)* nodeよりも小さくなります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.