- HOME
- S4で始める強化学習(第10回)在庫管理問題で作ってみる(2/3)
2024年7月 5日 14:33
本記事は当社が発行しているシミュレーションメールマガジンVol. 15の記事です。 シミュレーションメールマガジンの詳細・購読申込はこちらのサポートページから
- S4 で始める強化学習(第1回)離散イベントシミュレーションで使ってみる
- S4 で始める強化学習(第2回)離散イベントシミュレーションで作ってみる
- S4 で始める強化学習(第3回)離散イベントシミュレーションで理解を深める
- S4 で始める強化学習(第4回)エージェントシミュレーションで使ってみる
- S4 で始める強化学習(第5回)エージェントシミュレーションで作ってみる(1/2)
- S4 で始める強化学習(第6回)エージェントシミュレーションで作ってみる(2/2)
- S4 で始める強化学習(第7回)エージェントシミュレーションで理解を深める
- S4 で始める強化学習(第8回)在庫管理問題で使ってみる
- S4 で始める強化学習(第9回)在庫管理問題で作ってみる(1/3)
- S4 で始める強化学習(第10回)在庫管理問題で作ってみる(2/3)
はじめに
S4 で強化学習を使ったシミュレーションモデルを作成してみようという講座の10回目の記事になります。 第8回でご紹介した在庫管理問題のシミュレーションモデルの作成について、3回に分けてご紹介していきます。最初の2回では在庫管理問題のモデルの組み立て、最後の1回で強化学習モデルの組み込みを行う予定です。今回は在庫管理問題のモデルの組み立ての後半になります。 前回の記事をご覧になっていない方はS4 で始める強化学習(第9回)在庫管理問題で作ってみる(1/3)をご覧ください。
実装の方針
今回のモデルの実装は次のように4段階に分けて行いました。
- 在庫消費フローの実装
- 在庫補充フローの実装
- 強化学習モデルの組み込み
- 強化学習結果の可視化
分量が多いため、今回はstep2の実装のみを行います。step1の実装についてはS4 で始める強化学習(第9回)在庫管理問題で作ってみる(1/3)をご覧ください。step2まで実装を行ったs4プロジェクトはこちらからダウンロードいただけます。
step2:在庫補充フローの実装
在庫補充フローで実現すること
在庫補充フローでも同様に実現すべきことを前回の問題設定をもとに改めて整理します。
- 小売店は毎日製品の発注数を決定する(製品は最小単位order_unit単位でしか発注できないものとする)
- 注文された製品はL日後に在庫に補充される
- 到着待ち製品数を記録する
- 小売店の在庫管理には毎日在庫の量に応じてコストが発生する
- 累積利益の値を更新し記録する
発注数の決定は強化学習で実装しますが、今回は適当にランダムに割り振ることにします。利益の更新部分のみ、少し厄介な計算が入るため最後に実装していきます。 また、注文された製品が何日後に補充されるかという日数(L)や発注最小単位(unit_order)、1製品あたりの仕入れ値・在庫管理コストなどの利益計算に関わる数値は、今後変更することを見込んでパラメータとして定義しておくことにします。
フローの組み立て
step1のプロジェクトにさらに以下のアイテムと資源を配置します。
編集後のアイテム/資源名 | デフォルトアイテム/資源名 | タブ | 説明 |
---|---|---|---|
注文 | アイテム | アイテム | 小売店から上流への在庫補充注文を表す。このフローを流れていくモノ。 属性リストにdummyという名前の属性を追加しておく[1]。 |
配送中 | ストア | 資源 | 注文後、まだ到着していない製品を蓄えておくストア。 容量:制限なしと設定しておく。 |
終端2 | 終端 | 資源 |
上記の2つと、step1で配置した「在庫」部品が今回のフローの登場人物です。
次に「部品」タブの基本部品を使ってフローを組み立てます。step1と同様に、以下の最終的なフローを意識しながら部品を配置します。
在庫補充フローでは、毎日在庫を確認する手順を開始し、発注数を設定します。発注数が0の場合は下側の分岐に流れ、そのまま終了します。発注数が1以上の時は上側の分岐に流れ、「配送中」というストアに一時的に製品をとどめておきます。事前に設定した配送日数分だけ時間が経過すると、配送中ストアから在庫に製品が移されます。
それぞれの部品の名前と編集後の名前、設定についての一覧は以下のようになります。
編集後の部品名 | デフォルト部品名 | 設定箇所 |
---|---|---|
在庫確認 | 生成 | フローアイテム名:Item2(注文) 初期生成時間:固定, 実数, 1に設定 |
発注数設定 | 設定 | アイテム属性orderを追加する 値: 経験分布、値を0,1,2,3,4として等しい重みをのせる[2] |
判断 | 判断 | 出力ポートoutput2を追加
条件式を追加 条件式:item.order_pattern > 0 出力先: output(出力) それ以外: output2(新規出力) |
配送開始 | ストアへ追加 | ストアリスト: rStore3(配送中) 追加する離散量資源: アイテム属性を追加する 属性名:dummy 追加個数:固定, 整数, item.order*param.carton 追加の記録: 時系列モニター |
配送 | 遅延 | 出力前の待ち時間: 固定、実数、param.L 並列処理: 並列数Infに設定 |
配送終了 | ストアから取得 | 取得個数: 固定、整数、item.order*param.carton 取得の記録: 時系列モニター |
在庫補充 | ストアへ追加 | 追加個数: 固定, 実数, item.order*param.carton 追加の記録: 時系列モニター |
- アイテムの属性設定の効果についてはこの後のセクションでご説明します。
- 経験分布を選択すると重み表が表示されます。こちらの重み表の上で右クリックすることで行を追加できます。値や重みは直接編集可能です。
アイテムの属性設定
step1:在庫消費フローの際は分割部品を使うことで、同時に流れる複数個のアイテムを1つずつ処理させていましたが、今回は同時に流れるアイテムは1つとして、その1つのアイテムにアイテム属性として発注数情報を持たせることで処理を行っています。S4ではどちらも良く使う処理ですが、前者はとくに同時に流れてくるものを部分的に別の分岐に流したいとき、後者はその必要がないときや、アイテムに付随する情報を保持したり書き換える場合に便利です。
実際にどんな処理を行っているか確認してみます。以下の図は、アイテム「注文」の属性がフローの中でどのように変化するかを表した図になります。
まず、今回はアイテム「注文」の設定ではじめからdummyという属性が作成されています。このdummyには適当な値(何でもよいですが、今回はデフォルトの"1")を設定しておきます。
次に「発注数設定」部品で、アイテム「注文」にorderという属性を割り当てています。この属性の値は最小単位を1としていくつ発注するかという数になっています。ここで設定した値は、item.orderなどとしてアクセスできるようになります。
判断部品で発注数が0かどうか判断したのち、「配送開始」部品では上で設定した属性orderの値とパラメータとして設定した発注最小単位order_unitの値をかけた数(つまり、実際に配送される製品の数)だけ「配送中」ストアに追加を行っています。ここで、注目するべきは「追加する離散量資源」という設定項目です。
通常は「アイテムを追加する」という項目が選択されているのですが、この場合出力ポートからは空のアイテムが出力されます。その結果、先ほど設定したorderという発注数を表す属性はこの後のフローで利用できなくなってしまいます。
このような場合は、「アイテム属性を追加する」という項目を選択し、order以外の属性を設定しておくことでorder属性をこの後も参照できるようになります。こちらを選択した場合、指定されたアイテム属性の値をNoneに書き換えたアイテムが出力されますが、指定されていないアイテム属性の値はそのまま保持さます。アイテム「注文」の設定時にdummyという名前の属性を作成したのは、このようにorder属性をあとから参照できるようにするためです。
この後の「配送終了」部品、「在庫補充」部品では、「配送開始」部品と同様に取得・追加製品数を計算しています。
記録の確認
今回は「配送開始」部品、「配送終了」部品、「在庫補充」部品で記録を取っているので、モデル実行後に出力フォルダにそれぞれ対応した記録が作成されます。こちらを確認していくと、n日目に配送開始した製品量とn+2日目に配送完了した製品量、在庫に追加される製品量が一致することが確認できます。今回は配送にかかる時間として2日を設定しているので、きちんと意図通りに動作していることを確認できます。
利益の更新
最後に利益の更新を行います。今回は在庫管理にかかるコストと発注時のコストを利益から引く部分を実装します。発注時のコストを計算するには発注数が確定している必要があるため、「発注数設定」の直後に設定部品を差し込み、「利益更新(コスト)」という名前に変更しました。
step1での売り上げ文の利益更新と同じようにパラメータprofitの値を更新すればよいのですが、今回は流れてくるアイテムの数以外にも「在庫に今どれだけ製品があるか」という情報が必要です。これを実現するために、カスタムコードに次のような関数を定義しました。
def calc_cost(item, sim): cost = sim.param.cost * item.order * sim.param.order_unit +\ sim.rStore.sizeBuffer() * sim.param.m_cost return cost
この関数では、アイテムとシミュレータを受け取って、発注コストと在庫管理コストを返します。シミュレータを受け取っているのは、在庫の情報にアクセスする必要があるためです。2行目が発注コスト、3行目が在庫管理コストに当たります。sim.rStore.sizeBuffer()は在庫のバッファ(つまり在庫の量)を表す量です。
この関数を定義した上で、設定部品を使いパラメータprofitの値を param.profit-calc_cost(item, parent) に更新します。 calc_costのシミュレータに当たる引数がparentになっていますが、この部品からはparentという変数でシミュレータにアクセスできるため、このように記述しています。
おわりに
ここまでで在庫管理モデルの実装が出来ました。今回は発注数決定をランダムに行っていますが、確認してみると在庫過剰に陥り、利益はとんでもないマイナスになっていることが見て取れます。
もちろん、発注数決定部分をcalc_costで行ったように関数化して与えることで、在庫の量に応じて発注数を変えるような実装を行うことも可能です。次回はこの発注数決定部分に強化学習を組み込んでいく予定です。
http://www.msi.co.jp NTTデータ数理システムができること