このページは、nAGライブラリのJupyterノートブックExampleの日本語翻訳版です。オリジナルのノートブックはインタラクティブに操作することができます。
nAGライブラリを使用した収益管理における動的価格設定
適切な製品を適切な顧客に適切なタイミングで販売するために、収益管理において規律ある価格設定戦術が重要です。一般的に実施される価格設定戦略には、コストベース価格設定、市場ベース価格設定、動的価格設定、価格スキミングなどがあります。最適な戦略を決定するには、ビジネスの性質や対象市場など、さまざまな要因に依存します。価格設定プロセスにおいて、需要予測と収益最大化に最適化が重要な役割を果たします。このノートブックでは、nAG最適化モデリングスイート(nAGライブラリに含まれる)を使用して、ランダム価格設定や価格マッチング戦略よりも収益を増加させる動的価格設定戦略を実装する方法を示します。
ここで考慮されるモデルは、価格の関数としての需要が事前に分かっていることを前提としていません。むしろ、時間とともに学習されるパラメトリックな需要関数の族を仮定しています。学習プロセス中、nAGライブラリの線形計画法(LP)ソルバーhandle_solve_lp_ipmを使用してモデルを構築し解きます。nAG最適化モデリングスイートに含まれるLPソルバーは、問題の構築と修正に大きな柔軟性を提供するため、このタスクに適しています。
# nAG Library for Pythonから最適化モジュールと必要なパッケージをインポートする
from naginterfaces.library import opt
import numpy as npはじめに
単一製品の動的環境において2社が競合する市場に焦点を当てます。各企業は自社の需要を推定するだけでなく、競合他社の需要と価格戦略も予測する必要があります。簡単のため、各企業が割り当てる必要のある容量に制限がない、無制限の設定を想定します。
線形関数は、理論と実践の両方で最も一般的に使用される需要関数の1つです。真の需要パラメータが時間依存であると仮定すると、企業 \(k=1,2\) の需要関数は次のように定義できます: \[d_{k,t} = \beta_{k,t}^0 + \beta_{k,t}^1p_{1,t} + \beta_{k,t}^2p_{2,t} + \epsilon_{k,t},\] ここで、\(p_{1,t}\) と \(p_{2,t}\) はそれぞれ時間 t における企業1と2の価格です。このモデルは、各企業 \(k = 1, 2\) の需要が、自社および競合他社の現在の期間の価格 \(p_{1,t}\) と \(p_{2,t}\) に依存すると仮定しています。\(\beta_{k,t}^i\)、\(i = 0, 1, 2\) は未知のパラメータで、\(\epsilon_{k,t} \sim N(0, \sigma_{k,t}^2)\) はランダムノイズです。
各期間 t の始めに、企業1は自社の需要 \(d_{1,s}\)、自社の価格 \(p_{1,s}\)、および競合他社の価格 \(p_{2,s}\) の実現値を \(s = 1, \ldots, t-1\) について知っています。競合他社の需要は直接観察しません。目的は、自社の需要を推定し、競合他社の反応を予測し、最終的に総期待収益を最大化するために自社の価格を動的に設定することです。
この演習では、需要の真のモデルを以下のように定義します: \[ d_{1,t} = 50 -0.05p_{1,t} + 0.03p_{2,t} + \epsilon_{1,t}, \] \[ d_{2,t} = 50 +0.02p_{1,t} - 0.06p_{2,t} + \epsilon_{2,t}, \] ここで、\(\epsilon_{1,t},~\epsilon_{2,t}\sim N(0, 16)\) です。
def demand_true(company, p1, p2):
if company == 1:
return 50 - 0.05*p1 + 0.03*p2 + np.random.normal(scale=4.0)
elif company == 2:
return 50 + 0.02*p1 - 0.06*p2 + np.random.normal(scale=4.0)需要推定
以下の最適化問題は、企業1の需要を推定するために解かれます。 \[ \min_{\hat{\beta}_1}~~\sum_{\tau=1}^{t-1} |d_{1,\tau} - (\hat{\beta}_{1,\tau}^0+\hat{\beta}_{1,\tau}^1p_{1,\tau}+\hat{\beta}_{1,\tau}^2p_{2,\tau})|, \] \[ s.t. ~~ |\hat{\beta}_{1,\tau}^i - \hat{\beta}_{1,\tau+1}^i| \leq \delta_1(i), ~ i = 0, 1, 2, ~\tau = 1,2,\ldots, t-2, \] \[ ~~ \hat{\beta}_{1,\tau}^0\geq0,~\hat{\beta}_{1,\tau}^1 \leq0,~\hat{\beta}_{1,\tau}^2\geq0,~\tau = 1,2,\ldots, t-1, \] ここで、\(\hat{\beta}_1\)はすべての変数の配列として定義されます。最初の制約セットはパラメータの変化を制御するためのものであることに注意してください。
最適解\((\hat{\beta}_{1, \tau}^i)^*\)、\(i = 0, 1, 2\)、\(\tau = 1, \ldots, t-1\)が得られたら、新しい推定値は過去N期間の推定値の平均として与えられます \[ \hat{\beta}_{1, t}^i = \frac{1}{N}\sum_{l=t-N}^{t-1}(\hat{\beta}_{1, l}^i)^*,~ i = 0, 1, 2. \]
補助変数\(y_\tau\)、\(\tau = 1, \ldots, t-1\)を導入することで、上記の最適化問題は以下の線形計画問題として再定式化できます。 \[ \min_{({\hat{\beta}_1}, y)}~~\sum_{\tau=1}^{t-1} y_{\tau}, \] \[ s.t. ~~-y_\tau \leq d_{1,\tau} - (\hat{\beta}_{1,\tau}^0+\hat{\beta}_{1,\tau}^1p_{1,\tau}+\hat{\beta}_{1,\tau}^2p_{2,\tau}) \leq y_\tau,~\tau = 1,2,\ldots, t-1, \] \[ ~~ -\delta_1(i)\leq\hat{\beta}_{1,\tau}^i - \hat{\beta}_{1,\tau+1}^i \leq \delta_1(i), ~ i = 0, 1, 2, ~\tau = 1,2,\ldots, t-2, \] \[ ~~ \hat{\beta}_{1,\tau}^0\geq0,~\hat{\beta}_{1,\tau}^1 \leq0,~\hat{\beta}_{1,\tau}^2\geq0,~\tau = 1,2,\ldots, t-1. \] ここでは、両社の価格が区間\([100, 900]\)の範囲内にあり、時間地平線が\(T = 150\)であると仮定します。そして、初期価格は\(p_{1,1} = p_{2,1} = 500\)です。
# 乱数シードを固定する
np.random.seed(5)
# 大きな値でソルバーに無限大を知らせる
bigbnd = 1.e20
# 最適化モデルのパラメータ
delta = np.array([30, 0.05, 0.05])
T = 150
# 初期価格
p1 = [500]
p2 = [500]
# 会社1の実現した真の需要を格納する配列
# 最終的な収益計算のために
demand_1_true = []それでは、空のモデルを初期化することができます。
# 空の問題を初期化する
nvar = 0
handle = opt.handle_init(nvar)\(t = 2, \ldots, T\) について、モデルはより多くの変数と制約が追加されることで大きくなっていくことに注目してください。各時間期間 t において、4つの新しい変数 \([\hat{\beta}_{1,t-1}^0, \hat{\beta}_{1,t-1}^1, \hat{\beta}_{1,t-1}^2, y_{t-1}]\) の新しいセットが追加され、それらに関連する制約も追加されます。
価格最適化
会社1は、現在の期間 t の収益を最大化することで価格を設定できます。つまり、以下の制約のない二次計画問題を解くことになります: \[ \max_{p_1,t}~~ p_{1,t} (\hat{\beta}_{1,t}^0+\hat{\beta}_{1,t}^1p_{1,t}+\hat{\beta}_{1,t}^2\hat{p}_{2,t}), \] これは以下の形式の最適解を持ちます: \[ p_{1,t}^* = \frac{\hat{\beta}_{1,t}^0+\hat{\beta}_{1,t}^2\hat{p}_{2,t}}{-2\hat{\beta}_{1,t}^1}. \] 上記の価格戦略を使用するためには、時間 t における会社2の価格をまだ推定する必要があることに注意してください。簡単のため、会社2が過去N期間に設定した価格の平均を使用します: \[ \hat{p}_{2,t} = \frac{1}{N}\sum_{l=t-N}^{t-1} p_{2, l}. \]
# 会社1による会社2の価格設定の推定
p2_est = lambda p2 : sum(p2)/len(p2)
# 会社1の最適価格設定
def optimize_p1(t, p1, beta_t, p2_est):
# t = 2 のとき、データが不十分です
# 線形計画問題には複数の最適解がある
# ヒューリスティックにより価格1が適切に定義されていることを確認する
if (beta_t[1] == 0):
p1_opt = p1[t-2]
else:
p1_opt = (beta_t[0] + beta_t[2]*p2_est)/(-2.0*beta_t[1])
# 区間外の場合のプロジェクト価格
p1_opt = max(100, min(900, p1_opt))
return p1_opt価格設定関数が準備され、空のモデルが初期化されたので、ついにモデルを構築し、異なる時間帯に沿って解を求める準備が整いました。nAG最適化モデリングスイートを使用すると、変数と制約の追加が非常に簡単で、各時間帯の後に新しいデータが入ってくるたびに需要関数の係数を学習することが非常に容易になります。以下のコードで示されているように。
for t in range(2, T+1):
# 各需要推定において、4つの新しい変数がモデルに追加されます
# [beta_0, beta_1, beta_2, y]
opt.handle_add_vars(handle, 4)
# 目的関数には補助変数yのみが現れます
# 線形目的関数のy係数を設定する
opt.handle_set_linobj_coeff(handle, nvar+4, 1.0)
# ベータに対する境界制約を設定する
# Beta_0
opt.handle_set_bound(handle, 'X', nvar+1, 0.0, bigbnd)
# ベータ_1
opt.handle_set_bound(handle, 'X', nvar+2, -bigbnd, 0.0)
# ベータ_2
opt.handle_set_bound(handle, 'X', nvar+3, 0.0, bigbnd)
# 会社1の前期の実現需要を取得する
d1_true = demand_true(1, p1[t-2], p2[t-2])
# 前期の真の需要を保存
demand_1_true.append(d1_true)
# 最初の制約条件のセットを追加する
opt.handle_set_linconstr(handle, -bigbnd, d1_true, [1, 1, 1, 1],
[nvar+1, nvar+2, nvar+3, nvar+4], [1, p1[t-2], p2[t-2], -1.0])
opt.handle_set_linconstr(handle, d1_true, bigbnd, [1, 1, 1, 1],
[nvar+1, nvar+2, nvar+3, nvar+4], [1, p1[t-2], p2[t-2], 1.0])
# ベータがゆっくりと変化するように制御するための2番目の制約セットを追加する
if (t >= 3):
opt.handle_set_linconstr(handle, -delta, delta, [1, 1, 2, 2, 3, 3],
[nvar+1, nvar+1-4, nvar+2, nvar+2-4, nvar+3, nvar+3-4],
[1.0, -1.0, 1.0, -1.0, 1.0, -1.0])
# 線形計画法ソルバーを呼び出す
# アルゴリズムを選択してください
opt.handle_opt_set(handle, 'LPIPM Algorithm = SD')
# ログの印刷をオフにする
opt.handle_opt_set(handle, 'Print File = -1')
sol = opt.handle_solve_lp_ipm(handle)
# 前期の推定値に基づいて期間tのベータを計算する
beta_t = [0.0, 0.0, 0.0]
for i in range(0, nvar+4, 4):
beta_t+=sol.x[i:i+3]
beta_t /= t-1
# 時刻tにおける会社1の最適価格を取得する
p1_opt = optimize_p1(t, p1, beta_t, p2_est(p2))
# 時刻tにおける最適価格を保存
p1.append(p1_opt)
# 企業2が価格マッチを実施していると仮定
p2.append(p1[t-2])
# モデル内の変数の総数を更新する
nvar += 4
# 解決プロセスが終了しました。問題モデルを解放します
opt.handle_free(handle)結果
価格設定段階の後、各戦略の実際の収益を計算する準備が整いました。比較のために、他の2つの戦略も実装しました:ランダムと価格マッチング。ランダム価格は価格範囲内の一様分布から生成されます。価格マッチング方針は、現在の期間の価格を、会社1が前の期間に設定した価格に合わせます。
# 価格戦略に基づいて収益を計算する
revenue_opt = 0.0
revenue_price_match= 0.0
revenue_random = 0.0
# 会社1の期間Tにおける真の需要を取得する
# 価格設定段階で使用/生成されなかったため
demand_1_true.append(demand_true(1, p1[T-1], p2[T-1]))
# 比較のために一様分布に基づいて価格を生成する
p3 = np.random.uniform(100, 900, T)
# 各戦略の収益を計算する
# 収益 = 価格 * 需要
for i in range(T):
revenue_opt += p1[i]*demand_1_true[i]
revenue_price_match += p2[i]*demand_true(2, p1[i], p2[i])
revenue_random += p3[i]*demand_true(2, p1[i], p3[i])
import matplotlib.pyplot as plt
fig = plt.figure(figsize=plt.figaspect(0.3))
ax = fig.add_subplot(1, 2, 1)
ax.plot(np.arange(1, T+1), p1, 'b', label='Company 1')
ax.plot(np.arange(1, T+1), p2, 'g', label='Company 2')
ax.set_xlabel('Time period')
ax.set_ylabel('Price')
ax.legend()
ax = fig.add_subplot(1, 2, 2)
strategies = ['Opt', 'Price matching', 'Random']
ax.set_xlabel('Strategy')
ax.set_ylabel('Revenue')
ax.bar(strategies, [revenue_opt, revenue_price_match, revenue_random])
plt.show()
上記のプロット(価格の時系列推移)から、最適化学習アプローチを採用した会社1の価格が、時間の経過とともに安定した状態に収束していくのが分かります。会社2は価格マッチングを使用しているため、常に1期遅れています。右側の図から、この単一のシミュレーションでは、最適化アプローチを使用することで、価格マッチングよりもはるかに高い収益を達成していることが分かります。そしてランダム価格設定は、このシナリオでは最悪の結果となっています。
このノートブックでは、複雑な市場条件と需要下での価格戦略にnAG最適化モデリングスイートを使用する方法を示しました。提供される柔軟性により、最適な価格戦略を探索するためのシミュレーションを非常に効率的に実施することができます。
nAGライブラリとnAG最適化モデリングスイートについてさらに詳しく学ぶ
