34

これが、私がこの質問をしている理由です。 昨年、特定のタイプのモデル (ベイジアン ネットワークで記述) の事後確率を計算する C++ コードをいくつか作成しました。このモデルはうまく機能し、他の人が私のソフトウェアを使い始めました。今、私は自分のモデルを改善したいと考えています。私はすでに新しいモデル用にわずかに異なる推論アルゴリズムをコーディングしているので、ランタイムはそれほど重要ではなく、Python を使用するとよりエレガントで扱いやすいコードを作成できる可能性があるため、Python を使用することにしました。

通常、このような状況では、Python で既存のベイジアン ネットワーク パッケージを検索しますが、使用している推論アルゴリズムは独自のものであり、Python の優れた設計についてさらに学ぶ絶好の機会になると考えました。

ネットワーク グラフ用の優れた Python モジュール (networkx) を既に見つけました。これにより、各ノードと各エッジに辞書をアタッチできます。基本的に、これにより、ノードとエッジのプロパティを指定できます。

特定のネットワークとその観測データについて、モデル内の割り当てられていない変数の可能性を計算する関数を作成する必要があります。

たとえば、従来の「アジア」ネットワーク ( http://www.bayesserver.com/Resources/Images/AsiaNetwork.png ) では、「XRay Result」と「Dyspnea」の状態がわかっているため、関数を記述する必要があります。他の変数が特定の値を持つ可能性を計算します (モデルに従って)。

これが私のプログラミングに関する質問です。 いくつかのモデルを試してみるつもりですが、将来的には別のモデルを試してみたいと思うかもしれません。たとえば、1 つのモデルがアジア ネットワークとまったく同じように見える場合があります。別のモデルでは、「Visit to Asia」から「Has Lung Cancer」に有向エッジが追加される場合があります。別のモデルは元の有向グラフを使用する場合がありますが、「結核または癌」ノードと「気管支炎あり」ノードが与えられた場合、「呼吸困難」ノードの確率モデルは異なる場合があります。これらのモデルはすべて、異なる方法で尤度を計算します。

すべてのモデルにはかなりの重複があります。たとえば、「Or」ノードに入る複数のエッジは、すべての入力が「0」の場合は常に「0」になり、それ以外の場合は「1」になります。ただし、モデルによっては、ある範囲の整数値を取るノードを持つものもあれば、ブール型のものもあります。

過去に、私はこのようなことをプログラムする方法に苦労しました。うそをつくつもりはありません。かなりの量のコードをコピーして貼り付けており、1 つのメソッドの変更を複数のファイルに反映する必要がある場合がありました。今回は、これを正しい方法で行うために時間を費やしたいと思っています

いくつかのオプション:

  1. 私はすでにこれを正しい方法で行っていました。最初にコーディングし、後で質問します。コードをコピーして貼り付け、モデルごとに 1 つのクラスを作成する方が高速です。世界は暗く無秩序な場所です...
  2. 各モデルは独自のクラスですが、一般的な BayesianNetwork モデルのサブクラスでもあります。この一般的なモデルは、オーバーライドされるいくつかの関数を使用します。Stroustrup は誇りに思うでしょう。
  3. 異なる尤度を計算する同じクラスにいくつかの関数を作成します。
  4. 一般的な BayesianNetwork ライブラリをコーディングし、このライブラリによって読み取られる特定のグラフとして推論の問題を実装します。ノードとエッジには、"Boolean" や "OrFunction" などのプロパティを指定する必要があります。これらは、親ノードの既知の状態を考慮して、さまざまな結果の確率を計算するために使用できます。これらのプロパティ文字列 ("OrFunction" など) を使用して、適切な関数を検索して呼び出すこともできます。おそらく数年後には、1988 年版の Mathematica に似たものを作ることになるでしょう!

どうもありがとうございました。

更新: ここではオブジェクト指向のアイデアが大いに役立ちます (各ノードには、特定のノード サブタイプの先行ノードの指定されたセットがあり、各ノードには、先行ノードの状態などを考慮して、さまざまな結果状態の可能性を計算する尤度関数があります。 )。おっと!

4

4 に答える 4

22

私はかなり長い間、空き時間にこの種の作業を行ってきました。私は今、この同じ問題の 3 番目または 4 番目のバージョンに取り組んでいると思います。私は実際に、動的ベイジアン モデルと異なる永続レイヤーを含む別のバージョンの Fathom (https://github.com/davidrichards/fathom/wiki) をリリースする準備をしています。

答えを明確にしようとしたので、かなり長くなりました。申し訳ありません。これが私が問題をどのように攻撃してきたかです。これは、あなたの質問のいくつかに(やや間接的に)答えているようです:

私は、ベイジアン ネットワークにおける信念伝播のジュデア パールの内訳から始めました。つまり、これは、親からの事前オッズ (因果的サポート) と、子供からの可能性 (診断サポート) を含むグラフです。このように、基本クラスは単なる BeliefNode であり、BeliefNode 間の追加ノードである LinkMatrix で説明したものとよく似ています。このように、使用する LinkMatrix のタイプによって、使用する尤度のタイプを明示的に選択します。これにより、ビリーフ ネットワークが後で何を行っているかを説明しやすくなり、計算をより簡単に保つことができます。

基本的な BeliefNode に対して行うサブクラス化または変更は、伝播ルールまたはノードの関連付けを変更するのではなく、連続変数をビニングするためのものです。

すべてのデータを BeliefNode 内に保持し、LinkedMatrix 内の固定データのみを保持することにしました。これは、最小限のネットワーク アクティビティでクリーンな信念の更新を確実に維持することに関係しています。これは、私の BeliefNode ストアが次のことを意味します。

  • 子参照の配列、各子からのフィルター処理された可能性、およびその子のフィルター処理を行っているリンク マトリックス
  • 親参照の配列、各親からのフィルタリングされた事前オッズ、およびその親のフィルタリングを行っているリンク マトリックス
  • ノードの結合尤度
  • ノードの結合された事前オッズ
  • 計算された信念、または事後確率
  • 以前のすべてのオッズと可能性が順守する属性の順序付けられたリスト

LinkMatrix は、ノード間の関係の性質に応じて、さまざまなアルゴリズムを使用して構築できます。説明しているすべてのモデルは、使用するさまざまなクラスにすぎません。おそらく最も簡単な方法は、or ゲートをデフォルトに設定し、ノード間に特別な関係がある場合は、LinkMatrix を処理する他の方法を選択することです。

永続化とキャッシュには MongoDB を使用しています。速度と非同期アクセスのために、イベント モデル内でこのデータにアクセスします。これにより、必要に応じてネットワークを非常に大きくすることができると同時に、ネットワークのパフォーマンスがかなり向上します。また、このように Mongo を使用しているため、同じナレッジ ベースの新しいコンテキストを簡単に作成できます。たとえば、診断ツリーがある場合、診断の診断サポートの一部は患者の症状と検査から得られます。私がしていることは、その患者の状況を作成し、その特定の患者からの証拠に基づいて私の信念を広めることです. 同様に、ある患者が 2 つ以上の病気にかかっている可能性が高いと医師が言った場合、リンク行列の一部を変更して、信念の更新を別の方法で伝播させることができます。

システムに Mongo のようなものを使用したくないが、複数のコンシューマーがナレッジ ベースで作業することを計画している場合は、何らかのキャッシュ システムを採用して、新たに作業していることを確認する必要があります。 -常に更新されたノード。

私の作品はオープンソースなので、よろしければフォローしてください。これはすべて Ruby であるため、Python に似ていますが、必ずしも簡単に置き換えられるわけではありません。この設計で気に入っている点の 1 つは、人間が結果を解釈するために必要なすべての情報が、コードではなくノード自体にあることです。これは、定性的な記述、またはネットワークの構造で行うことができます。

だから、ここに私があなたのデザインと持っているいくつかの重要な違いがあります:

  • クラス内で尤度モデルを計算するのではなく、リンク マトリックス内のノード間で計算します。このようにして、同じクラス内で複数の尤度関数を組み合わせるという問題はありません。また、あるモデルと別のモデルの問題もありません。同じナレッジ ベースに 2 つの異なるコンテキストを使用して、結果を比較するだけです。
  • 人間の決定を明らかにすることで、多くの透明性を追加しています。つまり、2 つのノード間でデフォルトの or-gate を使用することにした場合、いつそれを追加したか、それが単なるデフォルトの決定であったことがわかります。後で戻ってリンク マトリックスを変更し、ナレッジ ベースを再計算すると、ある方法を別の方法よりも選択しただけのアプリケーションではなく、なぜそれを行ったかについてのメモが残ります。消費者にそのようなことについてメモを取ってもらうことができます。それをどのように解決しても、アナリストから段階的な対話を得るのがおそらく良い考えです。
  • 私は以前のオッズと可能性についてより明確にするかもしれません. それについてはよくわかりませんが、さまざまなモデルを使用して可能性の数値を変更していることがわかりました。事後信念を計算するモデルがこのように分解されない場合、私が言っていることの多くは完全に無関係になる可能性があります。任意の順序で呼び出すことができる 3 つの非同期ステップを作成できるという利点があります。変更された可能性をネットワークに渡し、変更された以前のオッズをネットワークに渡し、ノード自体の結合された信念 (事後確率) を再計算します。 .

1 つの大きな警告: 私が話していることのいくつかはまだリリースされていません。今朝の 2 時頃まで、私が話している内容に取り組んでいたので、間違いなく最新であり、定期的に私から注意を引いていますが、まだすべてが公開されているわけではありません。これは私の情熱であるため、ご質問があれば喜んでお答えしたり、プロジェクトで一緒に作業したりできます。

于 2011-03-25T16:30:22.107 に答える
3

モーツァルト/Oz3制約ベースの推論システム同様の問題を解決します。有限ドメイン変数の制約、制約のプロパゲーターとディストリビューター、コスト関数の観点から問題を説明します。これ以上推論できないが、バインドされていない変数がまだある場合は、コスト関数を使用して、バインドされていない変数の問題空間を分割します。これにより、検索コストが削減される可能性があります。つまり、Xが[a、c]の間にある場合はそのような変数です。 、およびc(a <b <c)は、検索コストを削減する可能性が最も高いポイントです。Xが[a、b]の間にあり、Xが[b、c]の間にある、2つの問題インスタンスが発生します。 ]。モーツァルトは、変数のバインディングをファーストクラスのオブジェクトとして再構築するため、これをかなりエレガントに行います(モーツァルトは広範囲に並行して分散されているため、問題のあるスペースを別のノードに移動するのに非常に便利です)。その実装では、

グラフベースのライブラリにコピーオンライトスキームを確実に実装できます(ヒント:numpyはさまざまな戦略を使用してコピーを最小限に抑えます。グラフ表現をそれに基づいて作成すると、コピーオンライトのセマンティクスを無料で取得できます)。あなたの目標を達成します。

于 2011-03-25T07:08:48.720 に答える
2

デザインに影響を与えるいくつかの質問をする必要があると思います。

  1. どのくらいの頻度でモデルを追加しますか?
  2. あなたのライブラリの利用者は、新しいモデルを追加することを期待されていますか?
  3. モデルを追加するユーザーの割合と、既存のモデルを使用するユーザーの割合は?

ほとんどの時間が既存のモデルで費やされ、新しいモデルがあまり一般的ではない場合、継承はおそらく私が使用する設計です。ドキュメントの構造が簡単になり、それを使用するコードが理解しやすくなります。

ライブラリの主な目的が、さまざまなモデルを試すためのプラットフォームを提供することである場合、親に基づいて物事を計算するためのファンクターにマップされるプロパティを持つグラフを使用します。ライブラリはより複雑になり、グラフの作成もより複雑になりますが、ノードに基づいて計算ファンクターを変更するハイブリッド グラフを実行できるため、はるかに強力になります。

どのような最終設計に取り組むかに関係なく、単純な 1 クラス 1 の実装設計から始めます。一連の自動テストに合格し、その後、より完全な設計にリファクタリングします。また、バージョン管理を忘れないでください;-)

于 2011-03-23T14:17:08.043 に答える
2

私はベイジアンネットワークにあまり詳しくないので、以下が役立つことを願っています:

過去に、ベイジアン分類器ではなく、ガウスプロセスリグレッサーで一見同様の問題がありました。

私は継承を使用することになりましたが、うまくいきました。モデル固有のパラメーターはすべてコンストラクターで設定されます。calculate() 関数は仮想です。異なるメソッドのカスケード (たとえば、任意の数の他のメソッドを組み合わせた合計メソッド) も、このようにうまく機能します。

于 2011-03-23T14:04:14.120 に答える