web-dev-qa-db-ja.com

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

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

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

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

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

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

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

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

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

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

8
Topo

前回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>_ mayはビットベクトルとして実装されますが、1つのアイテムを1つのアイテムで操作するセット操作のSTLアルゴリズムに依拠しています。時間。私は、一定期間にわたって徐々に構築され、基礎となる_std::vector<CHUNK>_(ここでCHUNKは通常32ビットの符号なしint型です)でビットごとの演算子を使用する独自のビットベクトルクラスを使用しましたワイド)。

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

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

そのため、行番号の単純なソート済み配列がこれらのセットの最適な表現になる場合があります。ただし、「スパースビットベクトル」の方が価値がある場合もあります。可能な実装の1つは、ソートされたペアの配列です。各ペアの最初はブロックの最初の行IDで、2番目はそのブロックの固定サイズのビットベクトルです。たとえば、行番号35は、ビット位置3(35 & ~(32 - 1))のブロック32(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や意思決定ツリーの構築に固有のものではありません。また、より「洗練された」アプローチは、慎重に選択された単純なアプローチよりも効率が悪いことがよくあります。

2
Steve314

モデルでスペースを節約するためにできることの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よりも小さくなります。

1
Pedrom

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

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

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

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

1
luk32