【C++】Boost.Mathでガンマ分布を扱う:PDF・CDF計算と乱数生成
c++ boost でガンマ分布を扱うには Boost.Math のboost::math::gamma_distribution<>
が便利です。
形状母数(α)と尺度母数(β)を指定し、確率密度関数(PDF)や累積分布関数(CDF)、乱数生成が手軽に行えます。
Boost.Mathで必要なヘッダと準備
C++でガンマ分布を扱う際に、Boostライブラリを利用する場合には、いくつかの準備が必要です。
まず、Boostの特定のヘッダファイルをインクルードし、その後に名前空間を設定することで、コードの見通しやすさと記述の簡潔さを向上させることができます。
<boost/math/distributions/gamma.hpp>のインクルード
Boostライブラリの中でガンマ分布に関する機能を利用するためには、<boost/math/distributions/gamma.hpp>
をインクルードします。
このヘッダには、boost::math::gamma_distribution
クラスや、確率密度関数(PDF)、累積分布関数(CDF)、逆関数(逆CDF)などの統計関数が定義されています。
#include <boost/math/distributions/gamma.hpp>
この一行をコードの先頭に追加することで、Boostのガンマ分布に関する機能を利用できるようになります。
名前空間の設定
Boostの関数やクラスは、boost::math
名前空間に属しています。
これを毎回記述するのは冗長になるため、using
ディレクティブを使って名前空間を省略することが一般的です。
using namespace boost::math;
ただし、名前空間の使用には注意が必要です。
特に大規模なプログラムや複数のライブラリを併用している場合には、名前の衝突を避けるために、必要な部分だけを明示的に指定する方法もあります。
例えば、特定の関数だけを使いたい場合は次のように記述します。
using boost::math::pdf;
using boost::math::cdf;
using boost::math::gamma_distribution;
このように設定しておくと、コード内でboost::math::
を省略して、pdf()
やcdf()
、gamma_distribution
と記述できるため、コードの見通しが良くなります。
以上の準備を整えることで、Boostライブラリのガンマ分布に関する機能をスムーズに利用できるようになります。
次のセクションでは、実際にガンマ分布のインスタンスを作成し、確率密度関数や累積分布関数の計算に進みます。
gamma_distributionオブジェクトの生成
boost::math::gamma_distribution
クラスは、ガンマ分布のパラメータを指定してインスタンスを作成します。
このオブジェクトを生成する際には、分布の形状母数(α)と尺度母数(β)をコンストラクタに渡す必要があります。
これらのパラメータは、分布の形状やスケールを決定し、確率密度関数(PDF)や累積分布関数(CDF)の計算に利用されます。
コンストラクタのパラメータ
boost::math::gamma_distribution
のコンストラクタは、次の2つのパラメータを受け取ります。
boost::math::gamma_distribution<double> dist(α, β);
ここで、α
は形状母数(Shape parameter)、β
は尺度母数(Scale parameter)です。
形状母数(α)
形状母数は、ガンマ分布の「形状」を決定します。
値が大きくなるほど、分布はより鋭くなり、平均値や分散も変化します。
一般的に、αは正の実数でなければなりません。
例えば、α=2.0は比較的標準的な値です。
尺度母数(β)
尺度母数は、分布のスケールを調整します。
値が大きくなると、分布はより広がり、平均値も増加します。
こちらも正の実数で指定します。
例えば、β=1.0やβ=2.0などがよく使われます。
初期化例
次の例では、形状母数を2.0、尺度母数を3.0に設定してガンマ分布のオブジェクトを作成しています。
#include <boost/math/distributions/gamma.hpp>
#include <iostream>
int main() {
// 形状母数を2.0、尺度母数を3.0に設定
boost::math::gamma_distribution<> dist(2.0, 3.0);
// 作成した分布の情報を出力
std::cout << "ガンマ分布のパラメータ:" << std::endl;
std::cout << "形状母数 (α): 2.0" << std::endl;
std::cout << "尺度母数 (β): 3.0" << std::endl;
return 0;
}
このコードでは、boost::math::gamma_distribution<>
のインスタンスdist
を、α=2.0、β=3.0のパラメータで生成しています。
これにより、以降の確率密度関数や累積分布関数の計算にこの分布を利用できるようになります。
ガンマ分布のパラメータ:
形状母数 (α): 2.0
尺度母数 (β): 3.0
このように、パラメータを設定したgamma_distribution
オブジェクトは、さまざまな統計計算や乱数生成に役立ちます。
次のセクションでは、このオブジェクトを用いた具体的な確率密度関数や累積分布関数の計算例を紹介します。
確率密度関数(PDF)の計算
boost::math::gamma_distribution
クラスには、特定の点における確率密度関数(PDF)の値を計算するためのpdf
関数が用意されています。
この関数を呼び出すことで、分布の形状やスケールに基づいた確率密度を得ることができ、統計解析やシミュレーションに役立ちます。
pdf関数の呼び出しと戻り値
pdf
関数は、次のようにして使用します。
#include <boost/math/distributions/gamma.hpp>
#include <iostream>
int main() {
// 形状母数2.0、尺度母数3.0のガンマ分布を生成
boost::math::gamma_distribution<> dist(2.0, 3.0);
// 計算したい点を設定
double x = 4.0;
// 確率密度関数の値を計算
double density = boost::math::pdf(dist, x);
// 結果を出力
std::cout << "x = " << x << "におけるPDFの値: " << density << std::endl;
return 0;
}
x = 4におけるPDFの値: 0.117154
この例では、dist
というガンマ分布のオブジェクトに対して、点x=4.0
における確率密度を計算しています。
pdf
関数は、引数に分布のオブジェクトと評価したい点を渡すと、その点における確率密度の値を返します。
具体的な使用例
次の例では、形状母数を3.0、尺度母数を2.0に設定したガンマ分布の確率密度を、複数の点で計算しています。
#include <boost/math/distributions/gamma.hpp>
#include <iostream>
#include <vector>
int main() {
// 形状母数3.0、尺度母数2.0のガンマ分布を生成
boost::math::gamma_distribution<> dist(3.0, 2.0);
// 評価したい点のリスト
std::vector<double> points = {1.0, 2.0, 3.0, 4.0, 5.0};
// 各点でのPDF値を計算し出力
for (double x : points) {
double density = boost::math::pdf(dist, x);
std::cout << "x = " << x << " のときのPDF値: " << density << std::endl;
}
return 0;
}
このコードでは、points
に格納された複数の点に対して、pdf
関数を呼び出し、それぞれの確率密度を計算しています。
出力例は以下のようになります。
x = 1 のときのPDF値: 0.0379082
x = 2 のときのPDF値: 0.0919699
x = 3 のときのPDF値: 0.125511
x = 4 のときのPDF値: 0.135335
x = 5 のときのPDF値: 0.128258
このように、pdf
関数は任意の点における確率密度を簡単に計算でき、分布の性質を理解したり、確率密度に基づく解析を行ったりする際に非常に便利です。
次のセクションでは、累積分布関数(CDF)の計算方法について解説します。
累積分布関数(CDF)と逆CDF
boost::math::gamma_distribution
クラスには、特定の点における累積分布関数(CDF)を計算するcdf
関数と、その逆操作である逆CDF(またはパーセンタイル)を計算するquantile
関数が用意されています。
これらの関数を使うことで、確率やパーセンタイルの計算を効率的に行うことができ、統計解析やシミュレーションにおいて重要な役割を果たします。
cdf関数の利用方法
cdf
関数は、与えられた点までの確率を計算します。
具体的には、分布の累積確率を求めるために使用します。
#include <boost/math/distributions/gamma.hpp>
#include <iostream>
int main() {
// 形状母数2.0、尺度母数3.0のガンマ分布を生成
boost::math::gamma_distribution<> dist(2.0, 3.0);
// 評価したい点
double x = 4.0;
// 累積分布関数の値を計算
double cdf_value = boost::math::cdf(dist, x);
// 結果を出力
std::cout << "x = " << x << "におけるCDFの値: " << cdf_value << std::endl;
return 0;
}
この例では、dist
というガンマ分布のオブジェクトに対して、点x=4.0
までの確率を計算しています。
cdf
関数は、引数に分布のオブジェクトと評価点を渡すと、その点以下の確率を返します。
出力例は以下の通りです。
x = 4におけるCDFの値: 0.38494
この値は、x=4.0
以下の範囲に分布の確率質量の約63.21%が含まれることを示しています。
quantile関数による逆CDF取得
quantile
関数は、指定した確率に対応する分位点(パーセンタイル)を計算します。
これにより、ある確率値に最も近い値を求めることができ、例えば信頼区間の下限や上限の計算に役立ちます。
#include <boost/math/distributions/gamma.hpp>
#include <iostream>
int main() {
// 形状母数2.0、尺度母数3.0のガンマ分布を生成
boost::math::gamma_distribution<> dist(2.0, 3.0);
// 例えば、0.75の確率に対応する値を求める
double probability = 0.75;
// 逆CDF(パーセンタイル)を計算
double quantile_value = boost::math::quantile(dist, probability);
// 結果を出力
std::cout << "確率 " << probability << " に対応する分位点: " << quantile_value << std::endl;
return 0;
}
この例では、確率0.75に対応する値を計算しています。
出力例は以下の通りです。
確率 0.75 に対応する分位点: 8.0779
この値は、分布の中で75%の確率を超えない値が約6.385であることを示しています。
これらの関数を組み合わせることで、確率と値の関係を自在に操作でき、統計的な推定やシミュレーションの精度向上に役立ちます。
次のセクションでは、これらの関数を用いた具体的な応用例について解説します。
乱数生成との連携
Boostライブラリを用いてガンマ分布から乱数を生成するには、boost::random
の乱数エンジンとboost::variate_generator
を組み合わせて使用します。
これにより、指定した分布に従った乱数を効率的にサンプリングでき、シミュレーションや統計的解析において非常に便利です。
boost::randomエンジンとの併用
まず、乱数エンジンを選択します。
Boostでは複数のエンジンが用意されており、代表的なものにboost::random::mt19937
(メルセンヌ・ツイスタ)があります。
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/math/distributions/gamma.hpp>
#include <iostream>
int main() {
// 乱数エンジンの生成
boost::random::mt19937 engine;
// 乱数エンジンを用いた一様分布の例(必要に応じて他の分布も使用可能)
boost::random::uniform_real_distribution<> dist_uniform(0.0, 1.0);
// 一様乱数を生成
double uniform_sample = dist_uniform(engine);
std::cout << "一様乱数: " << uniform_sample << std::endl;
return 0;
}
一様乱数: 0.814724
この例では、mt19937
エンジンを用いて0から1までの一様乱数を生成しています。
これを基に、他の分布の乱数も生成可能です。
分布パラメータの効果
ガンマ分布の形状やスケールは、パラメータの変動によって大きく変化します。
特に、形状母数(α)と尺度母数(β)の違いが分布の見た目や性質にどのように影響するのかを理解することは、適切なモデル選択やパラメータ推定に役立ちます。
αの変動による分布の形状変化
形状母数(α)は、ガンマ分布の「形状」を決定します。
αの値を変動させると、分布の形状がどのように変化するのかを見ていきます。
- αが小さい場合(例:0.5や1.0)
分布は右に長い尾を持ち、偏りが強くなります。
特にα=1の場合は指数分布となり、非常に偏った形状になります。
例:α=0.5のとき、分布は非常に鋭く尖った形になり、0付近に多くの確率質量が集中します。
- αが大きい場合(例:5.0や10.0)
分布はより正規分布に近い形状に近づき、ピークが高くなり、左右対称に近づきます。
例:α=10のとき、分布は平均値付近に集中し、尾は短くなります。
- αの値が1を超えると
分布は単峰性を持ち、平均値付近にピークを形成します。
αが増加するほど、分布はより鋭くなり、分散は減少します。
まとめ:
αが小さいときは偏りが強く、尾が長くなる傾向があり、αが大きいときは分布が鋭くなり、平均値付近に集中します。
βの変動によるスケーリング
尺度母数(β)は、分布の「スケール」や「広がり」を調整します。
βの値を変動させると、分布の位置や広がりがどのように変化するのかを見ていきます。
- βが小さい場合(例:0.5や1.0)
分布は狭くなり、値の範囲が限定されます。
確率密度はピークが高くなり、分散も小さくなります。
例:β=1.0のとき、平均値はα×βに等しくなり、分散はα×β²となるため、値が集中します。
- βが大きい場合(例:5.0や10.0)
分布は広がり、値の範囲が広くなります。
ピークは低くなり、尾も長くなります。
例:β=10のとき、平均値は20、分散は200となり、より広範囲にわたる値をとるようになります。
- βの値が変わると
分布の位置が変化し、スケールが調整されるため、同じαでも異なる平均値や分散を持つ分布を表現できます。
βを増加させると、分布は広がり、平均値や分散も増加します。
逆にβを小さくすると、分布は狭まり、集中度が高まります。
これらのパラメータの調整によって、ガンマ分布はさまざまなデータの性質に適応させることが可能です。
次のセクションでは、これらのパラメータの変動が実際にどのように分布の形状に影響を与えるのか、具体的な例を交えて解説します。
エラー処理と例外
boost::math::gamma_distribution
を使用する際には、パラメータの設定や関数呼び出しにおいて不正な値が入力された場合に、例外が発生することがあります。
これらの例外を適切に処理し、安全にプログラムを動作させるための方法について解説します。
不正パラメータ時の例外発生
boost::math::gamma_distribution
のコンストラクタや関数呼び出しにおいて、次のような不正な値が入力された場合に例外が投げられます。
- 形状母数(α)が0以下の場合
- 尺度母数(β)が0以下の場合
pdf
やcdf
、quantile
関数に対して、定義域外の値を渡した場合(例:負の値や極端に大きい値)
これらの状況では、std::domain_error
などの例外がスローされることがあります。
#include <boost/math/distributions/gamma.hpp>
#include <iostream>
int main() {
try {
// 不正なパラメータ(負の値)を設定
boost::math::gamma_distribution<> dist(-1.0, 2.0);
} catch (const std::domain_error& e) {
std::cerr << "ドメインエラー: " << e.what() << std::endl;
}
return 0;
}
この例では、形状母数に負の値を設定しているため、例外がスローされ、キャッチしてエラーメッセージを出力します。
安全な利用方法
例外を適切に処理し、プログラムの安定性を保つためには、次のような対策が必要です。
- パラメータの検証
パラメータを設定する前に、値が正の数であるかどうかを確認します。
例えば、if
文を使って値の妥当性をチェックします。
double alpha = 2.0;
double beta = 3.0;
if (alpha <= 0 || beta <= 0) {
std::cerr << "パラメータは正の値でなければなりません。" << std::endl;
// 必要に応じて処理を中断またはデフォルト値に設定
}
- 例外処理の導入
関数呼び出し部分では、try-catchブロックを用いて例外を捕捉し、エラーに応じた処理を行います。
try {
boost::math::gamma_distribution<> dist(alpha, beta);
double result = boost::math::pdf(dist, x);
// 以降の処理
} catch (const boost::math::domain_error& e) {
std::cerr << "パラメータまたは評価点が不正です: " << e.what() << std::endl;
// 例外発生時の処理(例:デフォルト値の設定やエラー通知)
}
- 入力値の範囲制限
ユーザからの入力や外部データを扱う場合は、範囲制限や正規化を行い、不正な値が渡らないように工夫します。
これらの対策を講じることで、例外によるプログラムのクラッシュを防ぎ、堅牢な実装が可能となります。
次のセクションでは、実際のエラー処理例や例外の詳細についてさらに解説します。
高度な利用例
モンテカルロシミュレーションへの応用
モンテカルロシミュレーションは、確率分布に従った乱数を大量に生成し、その結果を統計的に解析する手法です。
boost::random::gamma_distribution
とboost::random
の乱数生成機能を組み合わせることで、ガンマ分布に従う乱数を効率的に生成し、さまざまなシミュレーションに応用できます。
以下の例では、ガンマ分布に従う乱数を用いて、平均値や分散の推定、確率の計算などを行います。
#include <boost/random/gamma_distribution.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <iostream>
#include <random>
#include <vector>
int main() {
// 乱数エンジンの初期化
std::random_device rd;
boost::random::mt19937 engine(rd());
// ガンマ分布のパラメータ設定
double shape = 2.0;
double scale = 3.0;
// gamma_distributionを作成
boost::random::gamma_distribution<> gamma_dist(shape, scale);
// サンプル数
const int sample_size = 10000;
double sum = 0.0;
double sum_sq = 0.0;
// サンプルを生成し、平均と分散を計算
for (int i = 0; i < sample_size; ++i) {
double sample_value = gamma_dist(engine); // 直接エンジンに呼び出し
sum += sample_value;
sum_sq += sample_value * sample_value;
}
double mean = sum / sample_size;
double variance = (sum_sq / sample_size) - (mean * mean);
std::cout << "サンプル平均: " << mean << std::endl;
std::cout << "サンプル分散: " << variance << std::endl;
// 確率の推定例:値が5未満の確率
int count_below_5 = 0;
for (int i = 0; i < sample_size; ++i) {
double sample_value = gamma_dist(engine);
if (sample_value < 5.0) {
++count_below_5;
}
}
double prob_below_5 = static_cast<double>(count_below_5) / sample_size;
std::cout << "値が5未満の確率の推定: " << prob_below_5 << std::endl;
return 0;
}
サンプル平均: 6.01233
サンプル分散: 17.6753
値が5未満の確率の推定: 0.4997
この例では、ガンマ分布に従う乱数を10,000個生成し、その平均や分散を計算しています。
また、値が5未満となる確率も推定しています。
これにより、実際のデータ解析やリスク評価、最適化問題など、多様なシミュレーションに応用可能です。
他の分布との組み合わせ
ガンマ分布は、他の確率分布と組み合わせて複雑なモデルを構築することも可能です。
例えば、ガンマ分布を用いた階層モデルや、ガンマ分布と正規分布の混合モデルなどです。
具体的な例として、ガンマ分布を用いて待ち時間や寿命のモデル化を行い、その結果を正規分布と比較したり、他の分布と連携させてシミュレーションを行ったりします。
#include <boost/random/gamma_distribution.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/normal_distribution.hpp>
#include <iostream>
#include <random> // C++11
int main() {
const int sample_size = 1000;
double shape = 2.0;
double scale = 2.0;
// === C++11 <random> を使う場合 ===
{
std::mt19937 engine(12345); // 任意のシード
std::gamma_distribution<double> gamma_dist(shape, scale);
std::normal_distribution<double> normal_dist(0.0, 1.0);
double sum = 0;
for (int i = 0; i < sample_size; ++i) {
double g = gamma_dist(engine);
double n = normal_dist(engine);
sum += (g + n);
}
std::cout << "[std::random] 平均値: " << (sum / sample_size) << "\n";
}
// === Boost.Random を使う場合 ===
{
boost::random::mt19937 engine(12345);
boost::random::gamma_distribution<double> gamma_dist(shape, scale);
boost::random::normal_distribution<double> normal_dist(0.0, 1.0);
double sum = 0;
for (int i = 0; i < sample_size; ++i) {
double g = gamma_dist(engine);
double n = normal_dist(engine);
sum += (g + n);
}
std::cout << "[Boost.Random] 平均値: " << (sum / sample_size)
<< std::endl;
}
return 0;
}
このように、ガンマ分布と他の分布を組み合わせることで、より複雑な確率モデルやシミュレーションを構築できます。
これにより、実世界の多様な現象をより正確に表現し、解析や予測の精度を高めることが可能です。
性能最適化
C++でガンマ分布を扱う際の性能最適化は、特に大量の乱数生成や頻繁な関数呼び出しが必要なシミュレーションや解析において重要です。
ここでは、オブジェクトの再利用とスレッド環境での注意点について解説します。
オブジェクト再利用の工夫
boost::math::gamma_distribution
やboost::variate_generator
は、作成後に何度も使い回すことが可能です。
これにより、毎回新たにオブジェクトを生成するコストを削減し、パフォーマンスを向上させることができます。
- 分布オブジェクトの再利用
一度作成したgamma_distribution
やnormal_distribution
のインスタンスは、パラメータを変更しない限り、何度でもpdf
やcdf
、quantile
、乱数生成に利用できます。
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/math/distributions/gamma.hpp>
#include <iostream>
int main() {
// 分布オブジェクトを一度だけ作成
boost::math::gamma_distribution<> dist(2.0, 3.0);
// 乱数エンジンの作成
boost::random::mt19937 engine;
// variate_generatorも一度だけ作成
boost::variate_generator<boost::random::mt19937&, boost::math::gamma_distribution<> > sampler(engine, dist);
// 何度もサンプリング
for (int i = 0; i < 10000; ++i) {
double sample = sampler();
// サンプルの処理
}
return 0;
}
- パラメータの変更時は新規作成
パラメータを変更する場合は、新たに分布オブジェクトを作成し直す必要があります。
頻繁にパラメータを変える必要がある場合は、事前に複数の分布オブジェクトを用意しておくと効率的です。
スレッド環境での注意点
マルチスレッド環境では、boost::random
の乱数エンジンやboost::variate_generator
のインスタンスを共有すると、競合状態や予期しない動作を引き起こす可能性があります。
- エンジンのスレッドごとの独立性確保
各スレッドで独立した乱数エンジンを用意し、それぞれに対してvariate_generator
を作成します。
これにより、乱数の偏りや競合を防止できます。
#include <boost/thread.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/math/distributions/gamma.hpp>
#include <vector>
void thread_func() {
// スレッドごとにエンジンを作成
boost::random::mt19937 engine;
boost::math::gamma_distribution<> dist(2.0, 3.0);
boost::variate_generator<boost::random::mt19937&, boost::math::gamma_distribution<> > sampler(engine, dist);
// 乱数生成
for (int i = 0; i < 1000; ++i) {
double sample = sampler();
// 乱数を使った処理
}
}
int main() {
const int thread_count = 4;
std::vector<boost::thread> threads;
for (int i = 0; i < thread_count; ++i) {
threads.emplace_back(thread_func);
}
for (auto& t : threads) {
t.join();
}
return 0;
}
- エンジンのシード管理
各スレッドのエンジンには異なるシードを設定し、乱数の偏りや重複を避けることが重要です。
- 同期の必要性
共有リソースや出力にアクセスする場合は、適切なロックや同期機構を用いて競合を防止します。
性能最適化は、プログラムの規模や用途に応じて適切に設計する必要があります。
オブジェクトの再利用とスレッドごとの独立性確保を意識することで、効率的かつ安全に高性能なシミュレーションや解析を実現できます。
まとめ
この記事では、Boostライブラリを用いたガンマ分布の基本的な扱い方と応用例について解説しました。
分布の生成や確率密度・累積分布の計算、乱数生成の方法、パラメータの効果やエラー処理、安全な利用法、さらにモンテカルロシミュレーションや他の分布との組み合わせ例も紹介しています。
これにより、効率的かつ安全に高性能な統計解析やシミュレーションを行うための知識が得られます。