パスの確率が極端になってしまう理由は、ロジットモデルの計算において、コストの違いが非常に大きくなってしまい、指数関数の結果がほぼゼロか一に収束してしまうことにあります。
これを改善するために、コストのスケーリングを行い、各パスのコストがもっと小さな範囲に収まるようにします。一般的には、コストを標準化するか、計算のスケールを調整することで、確率がバラけるようにします。
以下のコードでは、コストをスケールするための定数(scalingFactor
)を導入し、その定数で割ることで、コストをスケーリングします。
修正版コード
package main
import (
"fmt"
"math"
)
// TransportationMethod は移動手段を表します
type TransportationMethod struct {
Name string
Distance float64
CostFunc func(float64) float64
}
// Path は一つの移動ルートを表します(複数の移動手段を組み合わせることができます)
type Path struct {
Methods []TransportationMethod
}
// TotalCost はPathに含まれる全ての移動手段の総コストを計算します
func (p *Path) TotalCost() float64 {
totalCost := 0.0
for _, method := range p.Methods {
totalCost += method.CostFunc(method.Distance)
}
return totalCost
}
// LogitProbability はロジットモデルを使用して、各パスの選択確率を計算します
func LogitProbability(paths []Path, scalingFactor float64) []float64 {
// 全てのパスの総コストの指数を計算
expSums := 0.0
expCosts := make([]float64, len(paths))
for i, path := range paths {
expCost := math.Exp(-path.TotalCost() / scalingFactor)
expCosts[i] = expCost
expSums += expCost
}
// 各パスの選択確率を計算
probabilities := make([]float64, len(paths))
for i, expCost := range expCosts {
probabilities[i] = expCost / expSums
}
return probabilities
}
func main() {
// 移動手段のコスト関数の定義
trainCost := func(distance float64) float64 {
return 6.5*distance + 120
}
taxiCost := func(distance float64) float64 {
if distance <= 1.2 {
return 500
}
return 333*distance + 100
}
carCost := func(distance float64) float64 {
return 18.43*distance + 500
}
bikeCost := func(distance float64) float64 {
return 8.38 * distance
}
bicycleCost := func(distance float64) float64 {
return 7 * distance
}
walkCost := func(distance float64) float64 {
return 11 * distance
}
// 各パスの定義(複数の移動手段を組み合わせる)
paths := []Path{
{Methods: []TransportationMethod{
{"Train", 10, trainCost},
{"Walk", 1, walkCost},
}},
{Methods: []TransportationMethod{
{"Taxi", 5, taxiCost},
}},
{Methods: []TransportationMethod{
{"Car", 15, carCost},
{"Bicycle", 2, bicycleCost},
}},
{Methods: []TransportationMethod{
{"Bike", 20, bikeCost},
}},
}
// スケーリングファクターの定義
scalingFactor := 500.0
// ロジットモデルによる選択確率を計算
probabilities := LogitProbability(paths, scalingFactor)
// 結果を表示
for i, prob := range probabilities {
fmt.Printf("Path %d: %.4f\n", i+1, prob)
}
}
修正ポイント
- スケーリングファクター (
scalingFactor
) を導入しました。この値を適切に調整することで、各パスのコストの差が緩和され、選択確率がバラけやすくなります。今回は 500.0
を使用しましたが、状況に応じてこの値を調整することが可能です。
実行方法
コードを再度実行していただくと、各パスの確率がバラけるようになるはずです。スケーリングファクターの値は、望ましい確率の分布に応じて調整してみてください。
実行結果
tomoi@DESKTOP-1QS7OI7 MSYS /g/home/ebata/tomioka3B/src/others
$ go run main97.go
Path 1: 0.4156
Path 2: 0.0180
Path 3: 0.1266
Path 4: 0.4399
よし、バラけた。
bicycleCost := func(distance float64) float64 {
return 7 * distance
}
walkCost := func(distance float64) float64 {
return 11 * distance
}
によれば、自転車より歩く方がお金がかかることになりますが、これは妥当でしょうか
ご指摘の通り、設定したコスト関数に基づくと、歩行の方が自転車よりもコストが高くなる結果となっていますが、これは一般的な状況に照らし合わせると妥当ではありません。
通常、自転車は初期購入費用やメンテナンス費用がかかるため、長距離を移動する場合は歩行よりもコストが高くなるのが一般的です。歩行は特に道具を必要としないため、実際には距離に応じたコストは低く抑えられます。
コスト関数の修正提案
自転車と歩行のコスト関数をより現実的にするためには、以下のように修正するのが適切です。
自転車のコスト: 自転車の購入費用やメンテナンス費用を考慮し、歩行よりも高めに設定します。
歩行のコスト: 道具がほとんど必要ないため、距離あたりのコストを低めに設定します。
bicycleCost := func(distance float64) float64 {
return 5 * distance // 自転車のコストを調整
}
walkCost := func(distance float64) float64 {
return 1 * distance // 歩行のコストを調整
}
自転車のコストは、例えば距離1kmあたり5円程度に設定し、メンテナンスやタイヤ交換などの費用を反映します。
歩行のコストは、距離1kmあたり1円程度に設定し、シューズの消耗やその他のわずかな費用を反映します。
これにより、自転車は歩行よりも一般的に高コストとなり、現実的なモデルになります。
trainCost := func(distance float64) float64 {
return 6.5*distance + 120
}
taxiCost := func(distance float64) float64 {
if distance <= 1.2 {
return 500
}
return 333*distance + 100
}
carCost := func(distance float64) float64 {
return 18.43*distance + 500
}
bikeCost := func(distance float64) float64 {
return 8.38 * distance
}
bicycleCost := func(distance float64) float64 {
return 7 * distance
}
walkCost := func(distance float64) float64 {
return 11 * distance
}
を全体として見直してみて下さい。
1. 鉄道のコスト
鉄道は初乗り料金があり、距離に応じて追加料金が発生します。一般的には比較的安価で、一定の距離を超えるとコストは緩やかに増加します。
trainCost := func(distance float64) float64 {
return 6.0*distance + 150 // 少し低めの傾斜で調整、初乗り料金を150円に設定
}
2. タクシーのコスト
タクシーは初乗り料金が高く、距離に応じて大幅にコストが増加します。
taxiCost := func(distance float64) float64 {
if distance <= 1.2 {
return 500 // 初乗り500円(1.2kmまで)
}
return 300*distance + 200 // 距離が長くなるほど、加算額がやや低くなるよう調整
}
3. 自家用車のコスト
自家用車はガソリン代やメンテナンス費用、駐車場代などがかかり、距離に応じてコストが増加します。
carCost := func(distance float64) float64 {
return 15.0*distance + 300 // 距離あたりのコストを抑えつつ、固定費用を調整
}
4. バイクのコスト
バイクは燃費が良く、比較的低コストで移動できるため、コスト関数は緩やかです。
bikeCost := func(distance float64) float64 {
return 5.0 * distance // 燃費が良いため、距離あたりのコストを低めに設定
}
5. 自転車のコスト
自転車はメンテナンス費用が主なコストとなり、歩行よりも若干高めのコストになりますが、それでも安価です。
bicycleCost := func(distance float64) float64 {
return 2.0 * distance // 非常に安価な移動手段として設定
}
6. 歩行のコスト
歩行は非常に低コストで、道具や消耗品の費用がわずかにかかる程度です。
walkCost := func(distance float64) float64 {
return 1.0 * distance // 極めて低コストに設定
}
全体としての調整ポイント
- 鉄道のコスト: 初乗り料金を考慮しつつ、距離あたりのコストをやや低めに設定しました。
- タクシーのコスト: 初乗り後の距離に応じたコスト増加を少し緩やかにしました。
- 自家用車のコスト: ガソリン代とメンテナンス費用を含めた現実的なコストに調整しました。
- バイクのコスト: 燃費の良さを反映し、距離あたりのコストを低く設定しました。
- 自転車のコスト: 非常に安価な移動手段として設定しましたが、メンテナンス費用は加味しました。
- 歩行のコスト: ほぼコストがかからない設定にし、他の手段と比較しても極めて低コストに設定しました。
この設定により、各移動手段の特性に合ったコストが反映され、現実的な選択確率を求めることができます。
最終的に落ち着いたコード
/*
// G:\home\ebata\tomioka3B\src\others\main97.go
これらの関係式から、入力値を(1)移動手段、(2)移動距離、(3)移動コストとするロジットモデルを使った算出アルゴリズムをGo言語で作成して下さい。具体的には、候補となる複数のパスを入力して、それぞれの選択確率を求めるものです。
ただし、1の移動には複数の移動手段を用いるものもあり、複数の移動手段であっても1の移動として取り扱えるようにして下さい。
ロジットモデルを使用して、複数の移動手段と移動距離に基づいて選択確率を計算するGo言語のアルゴリズムを作成します。このモデルでは、各候補パスの総コストを計算し、そのコストに基づいて各パスが選択される確率を算出します。
2. アルゴリズムの解説
TransportationMethod: 移動手段を表す構造体で、名前、距離、コスト計算関数を持っています。
Path: 一つの移動ルートを表す構造体で、複数の移動手段を組み合わせることができます。
TotalCost: Pathに含まれる全ての移動手段のコストを計算します。
LogitProbability: ロジットモデルを使用して、各パスの選択確率を計算します。各パスの総コストに基づき、指数関数を用いて確率を計算しています。
3. 実行結果
main 関数で定義された複数の移動パスに対して、それぞれの選択確率を算出し、結果を表示します。
このアルゴリズムを利用することで、様々な移動手段を組み合わせたパスの中から、最も選択されやすいパスをロジットモデルを用いて判断することができます。
あなた:
tomoi@DESKTOP-1QS7OI7 MSYS /g/home/ebata/tomioka3B/src/others
$ go run main97.go
Path 1: 0.0000
Path 2: 0.0000
Path 3: 0.0000
Path 4: 1.0000
となってしまいました。パスの確率がもうすこしバラけるように、
paths := []Path{
{Methods: []TransportationMethod{
{"Train", 10, trainCost},
{"Walk", 1, walkCost},
}},
{Methods: []TransportationMethod{
{"Taxi", 5, taxiCost},
}},
{Methods: []TransportationMethod{
{"Car", 15, carCost},
{"Bicycle", 2, bicycleCost},
}},
{Methods: []TransportationMethod{
{"Bike", 20, bikeCost},
}},
}
を改造して下さい。
修正ポイント
スケーリングファクター (scalingFactor) を導入しました。この値を適切に調整することで、各パスのコストの差が緩和され、選択確率がバラけやすくなります。今回は 500.0 を使用しましたが、状況に応じてこの値を調整することが可能です。
実行方法
コードを再度実行していただくと、各パスの確率がバラけるようになるはずです。スケーリングファクターの値は、望ましい確率の分布に応じて調整してみてください。
現時点での出力結果
tomoi@DESKTOP-1QS7OI7 MSYS /g/home/ebata/tomioka3B/src/others
$ go run main97.go
Path 1: 0.3535
Path 2: 0.0180
Path 3: 0.1871
Path 4: 0.4414
ロジットモデルは距離、経過時間、およびコストの3つで決定すると言われていると聞きますが、現在のプログラムには、距離とコストの値は入っているが、時間の値が入っていないようです。時間の値を入れたプログラムとして修正して下さい。
*/
package main
import (
"fmt"
"math"
"time"
"golang.org/x/exp/rand"
)
// TransportationMethod は移動手段を表します
type Logit_TransportationMethod struct {
Name string
Distance float64
CostFunc func(float64) float64
Time float64 // 時間(分)を直接入力
}
// Path は一つの移動ルートを表します(複数の移動手段を組み合わせることができます)
type Path struct {
Methods []Logit_TransportationMethod
}
// TotalCost はPathに含まれる全ての移動手段の総コストを計算します
func (p *Path) Logit_TotalCost() float64 {
totalCost := 0.0
for _, method := range p.Methods {
totalCost += method.CostFunc(method.Distance)
}
return totalCost
}
// TotalTime はPathに含まれる全ての移動手段の総時間を計算します
func (p *Path) Logit_TotalTime() float64 {
totalTime := 0.0
for _, method := range p.Methods {
totalTime += method.Time
}
return totalTime
}
// Logit_Probability はロジットモデルを使用して、各パスの選択確率を計算します
func Logit_Probability(paths []Path, costScalingFactor, timeScalingFactor float64) []float64 {
// 全てのパスの総コストと時間の指数を計算
expSums := 0.0
expValues := make([]float64, len(paths))
for i, path := range paths {
totalCost := path.Logit_TotalCost()
totalTime := path.Logit_TotalTime()
expValue := math.Exp(-(totalCost/costScalingFactor + totalTime/timeScalingFactor))
expValues[i] = expValue
expSums += expValue
}
// 各パスの選択確率を計算
probabilities := make([]float64, len(paths))
for i, expValue := range expValues {
probabilities[i] = expValue / expSums
}
return probabilities
}
// Logit_DefineCostFunctions は各移動手段のコスト関数を定義して返します
func Logit_DefineCostFunctions() (func(float64) float64, func(float64) float64, func(float64) float64, func(float64) float64, func(float64) float64, func(float64) float64, func(float64) float64) {
trainCost := func(distance float64) float64 {
return 6.0*distance + 150 // 鉄道のコスト
}
taxiCost := func(distance float64) float64 {
if distance <= 1.2 {
return 500 // 初乗り500円
}
return 300*distance + 200 // タクシーのコスト
}
carCost := func(distance float64) float64 {
return 15.0*distance + 300 // 自家用車のコスト
}
bikeCost := func(distance float64) float64 {
return 5.0 * distance // バイクのコスト
}
bicycleCost := func(distance float64) float64 {
return 2.0 * distance // 自転車のコスト
}
walkCost := func(distance float64) float64 {
return 1.0 * distance // 歩行のコスト
}
busCost := func(distance float64) float64 {
if distance <= 2.0 {
return 210 // 初乗り210円(2kmまで)
}
return 60*distance + 90 // 2kmを超えた場合の運賃
}
return trainCost, taxiCost, carCost, bikeCost, bicycleCost, walkCost, busCost
}
// InputPaths は事前に定義されたパスを受け取ります
func Logit_InputPaths(paths []Path) []Path {
return paths
}
// Logit_SelectRandomPath は確率分布に基づいてランダムにパスを選択します
func Logit_SelectRandomPath(probabilities []float64) int {
// 累積確率を計算
cumulativeProbabilities := make([]float64, len(probabilities))
cumulativeSum := 0.0
for i, prob := range probabilities {
cumulativeSum += prob
cumulativeProbabilities[i] = cumulativeSum
}
// ランダムな値を生成
rand.Seed(uint64(time.Now().UnixNano()))
randValue := rand.Float64()
// 累積確率に基づいてパスを選択
for i, cumulativeProb := range cumulativeProbabilities {
if randValue < cumulativeProb {
return i
}
}
// 万が一選択されなかった場合、最後のパスを返す
return len(probabilities) - 1
}
func main() {
// 各移動手段のコスト関数を取得
trainCost, taxiCost, carCost, bikeCost, bicycleCost, walkCost, busCost := Logit_DefineCostFunctions()
// 個別の移動手段を定義してから methods に追加
methods1 := []Logit_TransportationMethod{}
methods1 = append(methods1, Logit_TransportationMethod{"Train", 10, trainCost, 30}) // 例: 鉄道10kmで30分
methods1 = append(methods1, Logit_TransportationMethod{"Walk", 1, walkCost, 12}) // 例: 歩行1kmで12分
methods2 := []Logit_TransportationMethod{}
methods2 = append(methods2, Logit_TransportationMethod{"Taxi", 5, taxiCost, 15}) // 例: タクシー5kmで15分
methods3 := []Logit_TransportationMethod{}
methods3 = append(methods3, Logit_TransportationMethod{"Car", 15, carCost, 25}) // 例: 自家用車15kmで25分
methods3 = append(methods3, Logit_TransportationMethod{"Bicycle", 2, bicycleCost, 8}) // 例: 自転車2kmで8分
methods4 := []Logit_TransportationMethod{}
methods4 = append(methods4, Logit_TransportationMethod{"Bike", 20, bikeCost, 45}) // 例: バイク20kmで45分
methods5 := []Logit_TransportationMethod{}
methods5 = append(methods5, Logit_TransportationMethod{"Bus", 8, busCost, 20}) // 例: バス8kmで20分
methods5 = append(methods5, Logit_TransportationMethod{"Walk", 0.5, walkCost, 6}) // 例: バス停から目的地まで徒歩0.5kmで6分
// Path を作成して paths に追加
paths := []Path{}
paths = append(paths, Path{Methods: methods1})
paths = append(paths, Path{Methods: methods2})
paths = append(paths, Path{Methods: methods3})
paths = append(paths, Path{Methods: methods4})
paths = append(paths, Path{Methods: methods5})
// InputPaths関数に事前定義されたパスを渡す
paths = Logit_InputPaths(paths)
// スケーリングファクターの定義
costScalingFactor := 500.0
timeScalingFactor := 10.0
// ロジットモデルによる選択確率を計算
probabilities := Logit_Probability(paths, costScalingFactor, timeScalingFactor)
// 結果を表示
for i, prob := range probabilities {
fmt.Printf("Path %d: %.4f\n", i+1, prob)
}
// 確率に基づいてランダムにパスを選択
selectedPathIndex := Logit_SelectRandomPath(probabilities)
fmt.Printf("Selected Path: %d\n", selectedPathIndex+1)
// 繰り返しパスを定義して再計算するか確認
for {
fmt.Println("再度パスを計算しますか? (yes/no)")
var continueInput string
fmt.Scan(&continueInput)
if continueInput != "yes" {
break
}
// 新しいパスを入力する場合
paths = Logit_InputPaths(paths)
probabilities = Logit_Probability(paths, costScalingFactor, timeScalingFactor)
// 再度結果を表示
for i, prob := range probabilities {
fmt.Printf("Path %d: %.4f\n", i+1, prob)
}
// 確率に基づいてランダムにパスを選択
selectedPathIndex = Logit_SelectRandomPath(probabilities)
fmt.Printf("Selected Path: %d\n", selectedPathIndex+1)
}
}