サプライチェーンマネジメントのためのデータサイエンス/オペレーションズ・リサーチ入門

# はじめに  
この記事では、会社でデジタルトランスフォーメーション（以下、DX)として、幸か不幸かサプライチェーンマネジメント(以下、SCM)に関わることになった人たちが知っておくと良さそうなデータサイエンス/オペレーションズ・リサーチに関する情報をまとめました。

仕事でSCMのDXを担当することになったけど、何から取り組んでいいかわからないという方の参考になれば幸いです。


# 目次  
- サプライチェーンマネジメント（SCM)とは
- SCMとデータサイエンスとオペレーションズ・リサーチ
- よくあるSCM課題とアプローチ
- SCM関係技術情報の収集場所

# サプライチェーンマネジメント（SCM）とは  
サプライチェーンとは、製造業等における原料調達→生産→物流→販売商品までのプロセス全体のことです。
SCMとはこうした”モノの流れ”と”情報の流れ”を結びつけ、サプライチェーン全体で情報を連携・活用して全体最適化を図る経営手法です。
<div style="text-align: center;">
![l_bit201801311847598917.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1461278/baaf44c3-ab2a-29e4-eb17-d667233c89df.jpeg)
</div>

# SCMとデータサイエンスとオペレーションズ・リサーチ  
前述のように、SCMでは”モノの流れ”と”情報の流れ”が重要になります。
この”情報の流れ”から得られるデータの分析・活用にデータサイエンス（統計や機械学習など）が関わってきます。

また、SCMはオペレーションズ・リサーチ（以下、OR）とも密接な関係があります。
ORとは数理最適化・シミュレーションなど、人間の高度な意思決定を支援する数学的アプローチのことです。
例えば、在庫や配送の最適化など、いくつかの制約がある状況の中でコストや時間といった目標を最大化するときなど、SCMで扱うプロセスの最適化にORは使われています。

様々なデータがあらゆる場所で発生し、複雑に絡みあう現代のサプライチェーンを最適化するためには、データサイエンスとORを両輪とした複合的なアプローチが必要です。

# よくあるSCM課題とアプローチ  
以下ではよくあるSCM課題とアプローチ方法についてPythonコードも交えて紹介していきます。
ここでは世の中にあるごく一部を紹介しますが、詳しくは末尾の文献が参考になりますので適宜ご参照ください。　
### 需要予測
需要予測では代表的なものとして、移動平均法や回帰分析といったデータサイエンスのアプローチがよく使われます。

- 移動平均法   
移動平均法では窓の大きさ分だけデータを切り出してその区間の平均を取りますので、変化の傾向を捉えることができます。（よく株価のチャートでも見かけますね）  
移動平均は株式チャートや月次の在庫量履歴のように、データの前後に時系列的な関係性があるデータ（時系列データ）で使えます。

```python:移動平均/移動標準偏差
#例として、データ列から12個分のデータを取り出して平均や標準偏差を計算。
#窓を一行ずつスライドさせていくことで、移動平均が求められる。
rolling_mean = df.rolling(window = 12).mean()
rolling_std = df.rolling(window = 12).std()
```

- 回帰分析  
需要予測の手法としては回帰分析が一般的です。  
例えば、知りたい需要（目的変数）とそれに関係しそうなデータ（説明変数）があれば、Scikit-learnライブラリのLinerRegression（）やPLSRegression（）を使って簡単に回帰分析ができます。  
ここでは、部分的最小２乗法(Partial Least Squares Regression：PLS)についてコード例を書きたいと思います。

```python:回帰分析（PLS）
#サンプルデータセットをimport
from sklearn import datasets

#bostonにデータを格納
boston = datasets.load_boston()

#pandasのデータフレーム形式に変更してx_dfに格納、目的変数をy_df
x_df = pd.DataFrame(boston.data, columns=boston.feature_names)
y_df = boston.target

#PLS regression
from sklearn.cross_decomposition import PLSRegression
#モデルをセット、PLSの潜在変数（n_component）は今回は適当に２程度に。
pls = PLSRegression(n_components=2)

#トレーニング用データを入れてトレーニング→トレーニング済みモデルができる。
pls.fit(x_df, y_df)

#最後はトレーニングしたPLS回帰モデルにテストデータ(x_test)を入れて未知のデータに対しての目的変数(y_pred)を予測する。
y_pred = pls.predict(x_test)
```


### 生産計画・在庫量の最適化
最適化問題では線形計画法のような目的関数と制約条件からなる問題をシンプレックス法などのアルゴリズムで解く、というようなORのアプローチがよく使われます。
また、最適化計算は専用のソフトを買わなくても無料で使えるPythonライブラリが充実していますので、まずはPythonでやってみると良いと思います。


- 線形計画法  
線形計画法とは下図のような線形の目的関数（x1+2*x2）と線形の制約条件（①~⑤）からなる最適化問題（線形計画問題）を解く手法です。  
ここでは、参考例として下図のような単純な線形計画問題をPythonライブラリのPuLPを使って解いていきます。  
線形計画問題の一般化した式や細かな概念については[参考文献1](https://www.slideshare.net/shunjiumetani/ss-15148950)、PuLPの詳細については[参考文献2](https://qiita.com/SaitoTsutomu/items/070ca9cb37c6b2b492f0)を参照いただければと思います。
<div style="text-align: center;">
![スクリーンショット 2021-06-04 20.09.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1461278/9362ad57-fa21-98fb-5f42-b2d1a4afdd1e.png)

</div>

```python:線形計画法
import pulp
#数理モデル（線形計画）の箱を作成。今回は最大化問題。
problem = pulp.LpProblem("sample_problem", pulp.LpMaximize)

#変数を設定。x1とx2の2変数。どちらも最小値0。
x1 = pulp.LpVariable("x1", lowBound=0)#④
x2 = pulp.LpVariable("x2", lowBound=0)#⑤

#目的関数を設定
problem += x1 + 2*x2

#制約条件を設定
problem += x1 + x2 <= 6#①
problem += x1 + 3*x2 <= 12#②
problem += 2*x1 + x2 <= 10#③

#ソルバーで上で設定した線形計画問題を解く。
problem.solve()

#結果確認
print("Result")
print("x1 :",x1.value())#3.0
print("x2 :", x2.value())#3.0
```

次に線形計画法の具体例として生産計画への適用（[参考文献３](https://data-scientist.hatenadiary.jp/entry/2019/02/24/210100）))として以下のような線形計画問題を考えてみます。
>①ある化学工場では2種類の原料AとBを加工して、2種類のProduct1、 Product2を生産している工場が、先2ヶ月間の生産計画を立てようとしている。
②各製品を1単位生産するために必要な原料の使用量、各製品の生産／在庫コスト、各製品の月ごとの出荷量、各原料の月ごとの利用可能量は以下のように与えられているとする。
③各月に出荷する製品をその月中に全て生産できるとは限らないので、前の月に生産した製品を在庫として保管して来月に出荷することもOK。
このような状況の下で、要求された製品の出荷量と与えられた原料の利用可能量の制約を満たしつつ総コストを最小にするには、各月における各製品の生産量と在庫量をどのようにするか。

原料使用量

|  |Product1  |Product2  |
|---|---|---|
|原料A  |3  |7  |
|原料B  |4  |3  |

生産/在庫コスト

|  |Product1  |Product2  |
|---|---|---|
|生産コスト  |60  |80  |
|在庫コスト  |5  |6  |

製品の出荷量

|  |Product1  |Product2  |
|---|---|---|
|1ヶ月目  |40  |20  |
|２ヶ月目  |100  |40  |

原料の利用可能量

|  |A  |B  |
|---|---|---|
|1ヶ月目  |800  |650  |
|２ヶ月目  |650  |600  |

```python:線形計画法（生産計画）
import pulp
#数理モデルの箱を作成。今回は最小化問題。
problem = pulp.LpProblem("Total_cost", pulp.LpMinimize)

#変数の設定。生産量をｘ、在庫量をｙ、添字１つ目はProduct１or２、添字２つ目は生産月。
x11 = pulp.LpVariable("Product1の1ヶ月目の生産量", lowBound=0)
x12 = pulp.LpVariable("Product1の2ヶ月目の生産量", lowBound=0)

x21 = pulp.LpVariable("Product2の1ヶ月目の生産量", lowBound=0)
x22 = pulp.LpVariable("Product2の2ヶ月目の生産量", lowBound=0)

y11 = pulp.LpVariable("Product1の1ヶ月目の在庫量", lowBound=0)
y21 = pulp.LpVariable("Product2の1ヶ月目の在庫量", lowBound=0)

#目的関数を設定。Total_cost＝生産コスト＋在庫コスト。これを最小化する。
problem += 60*x11 + 80*x12 + 5*y11 + 60*x21 + 80*x22 + 6*y21

#原料の利用可能量の制約。
problem += 3*x11 + 7*x21 <=800#1ヶ月目のAを使用する際の制約
problem += 4*x11 + 3*x21 <=650#1ヶ月目のBを使用する際の制約
problem += 3*x21 + 7*x22 <=650#2ヶ月目のAを使用する際の制約
problem += 4*x21 + 3*x22 <=600#2ヶ月目のBを使用する際の制約

#製品出荷量の制約。
problem += x11 - y11 == 40#1ヶ月目のproduct1の生産量と在庫量の差が出荷量。
problem += x21 - y21 == 20#1ヶ月目のproduct2の生産量と在庫量の差。
problem += x12 + y11 == 100#2ヶ月目のproduct1の生産量と在庫量の差。前月分の在庫も考慮。
problem += x22 + y21 == 40#2ヶ月目のproduct2の生産量と在庫量の差。前月分の在庫も考慮。

#ソルバーで上で設定した線形計画問題を解く。
problem.solve()

#結果確認
print("x11 :", x11.value())#117.5
print("x12 :", x12.value())#22.5
print("x21 :", x21.value())#60.0
print("x22 :", x22.value())#0.0
print("y11 :", y11.value())#77.5
print("y21 :", y21.value())#40.0
print("total_cost :", problem.objective.value())#13077.5
```
実際には生産計画を最適化するためには、多くのコスト因子（固定費、人件費etc.）及び制約条件（在庫の最低数量、工場のメンテ等で稼働しない日数etc.）があり、上記の例よりもかなり複雑な線形計画法（複合整数計画法）を解くことになりますが、イメージだけでも伝われば幸いです。  
<br>  
  
- 整数計画法  
ナップサック問題に代表される組み合わせ最適化の問題を解く手法です。  
ナップサック問題とは「大きさの決まったナップサックに、どの品物を選んで詰めると価値の合計が最大になるか？」という問題で、先程の線形計画問題と違って、変数（品物）は小数点以下の数字を取れません。  
解き方としては動的計画法や貪欲法、近似解法、ソルバーによる解法など色々ありますが、ここではPuLPを使って、以下のような価値最大化のナップサック問題を解いてみます。
<div style="text-align: center;">
![ナップサック問題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1461278/d7ef034f-9ed4-a58a-a906-bddda43db187.png)
</div>

```python:ナップサック問題
import pulp
#牛乳、おにぎり、サンドイッチの容量のリスト。それぞれ一個ずつある。
w=[1, 0.7, 0.5]
#牛乳、おにぎり、サンドイッチの値段のリスト
v=[200, 150, 120]
#制約条件：ナップサックの容量
W = 2

#後で使うfor loopのためにリストの長さを変数rに格納
r = len(w)

#数理モデルの箱を作成。今回は最大化問題。
problem = pulp.LpProblem(sense = pulp.LpMaximize)

#変数の設定。ｘというリストを使う。
x = [pulp.LpVariable("x%d"%i, cat = LpBinary) for i in range(r)]

#目的関数を設定
problem += pulp.lpDot(v, x)

#制約条件を設定
problem += pulp.lpDot(w, x) <= W

#ソルバーで上で設定した整数計画問題を解く。
problem.solve()

#結果確認
print('最大価値:{} / 組み合わせ:{}'.format(value(problem.objective), [i for i in range(r) if value(x[i]) > 0.5]))
#最大価値:350.0 / 組み合わせ:[0, 1] →つまりリストの0番目と1番目にある牛乳とおにぎりの組み合わせが最適。
```

- 混合整数計画法  
倉庫配置問題や配送問題、ネットワーク設計問題などに取り組む際に使える手法です。  
読んで字のごとく線形計画法と整数計画法をあわせたもので、変数の一部は整数でなければならないという制約があるような問題です。  
簡単な例として、以下のような制約の問題を[google OR-tools](https://developers.google.com/optimization/mip/integer_opt#mip-example)　を使って解いてみます。  
![スクリーンショット 2021-07-03 18.15.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1461278/7abe90a0-5b71-59b3-11a0-7cdced7a6675.png)
![feasible_region_sol.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1461278/b7efd5de-6e40-40ab-bced-a0c839e00b69.png)


```python:混合整数問題
from ortools.linear_solver import pywraplp#線形計画法のライブラリ

#混合整数問題（MIP)のソルバーの設定（バックエンドはSCIP）。
solver = pywraplp.Solver.CreateSolver('SCIP')

#変数の設定。IntVarで整数制約。非負制約も入れておく。
infinity = solver.infinity()
x = solver.IntVar(0.0, infinity, 'x')
y = solver.IntVar(0.0, infinity, 'y')

#制約条件の追加。
solver.Add(x + 7 * y  <= 17.5)
solver.Add(x <= 3.5)

# 目的関数の設定。x+10yを最大化する。
solver.Maximize(x + 10 * y)

# ソルバーで設定した混合整数問題を解く。
solver.Solve()

#結果を確認する
print('Solution:')
print('Objective value =', solver.Objective().Value())#23.0
print('x =', x.solution_value())#3.0
print('y =', y.solution_value())#2.0
```

もっと複雑な問題例としては、[ブレインパッドブログ](https://blog.brainpad.co.jp/entry/2020/10/30/000004)にて在庫問題と配送問題を組み合わせた混合整数線形計画問題について参考になる例がありましたので、リンク先を参考にしていただければと思います。

### ロジスティクス最適化
大きな視点で見ると、先の生産計画・在庫の最適化を含む問題として、ロジスティクス最適化があります。
久保幹雄先生の[スライド](https://www.slideserve.com/vaughan-moody/6098041)から抜粋すると、サプライチェーンの中にあるロジスティクスに関する最適化問題は下図のようになります。
意思決定のレベルが日々のオペレーションなのか、中長期的なタクティカル・ストラテジックなのかによってそれぞれ使う最適化モデルが異なります。
こちらも例示しやすいコンパクトな例が無いので、コードでの紹介は割愛します。
<div style="text-align: center;">
![スクリーンショット 2021-06-19 23.30.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1461278/e0db3b0c-033c-2eb7-1b91-63a32e234fe0.png)
</dev>

# SCM関係技術情報の収集場所
### [日本オペレーションズ・リサーチ学会](http://www.orsj.or.jp/index.html)
私自身、何から取り組めばいいかわからないときに探索していてたどり着きましたが、SCMに限らず幅広い技術（数理最適化、金融工学、グラフ・ネットワークなどなど）を扱う学会です。
過去の学会誌はPDFで公開されており、良質な情報収集ができます。

### [ブレインパッドの数理最適化ブログ](https://blog.brainpad.co.jp/entry/2020/10/01/000000)
最近、数理最適化に力を入れているブレインパッドのデータサイエンティストたちが数理最適化（先に紹介した線形計画法などの総称）についてまとめています。
数理最適化手法の紹介のみならず、SCM系のデータサイエンティストとしての心構えなど大変参考になる記事がたくさんあります。

### [久保幹雄先生の講演資料](https://www.logopt.com/mikiokubo/presen.htm)
東京海洋大学の久保幹雄先生の資料。非常にわかりやすいオペレーションズ・リサーチの論文や講演資料が多数あります。Pythonを使ったオペレーションズ・リサーチを学ぶならこの先生の資料や書籍を参考にされると良いです。


### [サプライチェイン最適化チャンネル（Youtube）](https://www.youtube.com/c/kubomikio/videos)
同じく東京海洋大学の久保幹雄先生のYoutubeチャンネル。かなりしっかりした講義を無料公開していますので、サプライチェーンマネジメントについて自習する際に参考になります。

### その他（随時更新します）

# おわりに  
今回はSCMに取り組むにあたって、データサイエンスやORといったデータの取扱に関する技術的な側面をピックアップして紹介しました。

一方、サプライチェーンのプロセス一つ一つの現場の実務において最も重要なことは、AIを使って需要予測を精度良くすることや数理的に最適な配送計画を出すことではなく、現実に発生しうる誤差を管理して、異常事態が発生したら適切な行動が取れるように準備をすることです。
データサイエンスやORはあくまで意思決定のための手段であることを念頭に、全体最適を意識してSCMに取り組むことが重要です。

以上、浅学ながら私がSCMに取り組むにあたって収集した情報・学んだことの中で参考になりそうなものを紹介させていただきましたが、皆様のご参考になっていれば幸いです。

# 参考文献/記事  
- [Python言語によるサプライ・チェイン・アナリティクス](http://www.orsj.or.jp/archive2/or63-12/or63_12_741.pdf)
- [サプライチェーンマネジメント（SCM）とは何か？](https://www.sbbit.jp/article/cont1/34345)
- [オペレーションズ・リサーチとは](http://www.orsj.or.jp/whatisor/whatisor.html)
- [需要予測の手法](https://media.management-club.jp/data-juyo-yosoku-20190601/)
- [単体法を理解しよう！](http://www.orsj.or.jp/archive2/or64-4/or64_4_209.pdf)
- [線形計画法入門](https://www.slideshare.net/shunjiumetani/ss-15148950)
- [部分的最小二乗回帰(Partial Least Squares Regression, PLS)～回帰分析は最初にこれ！～](https://datachemeng.com/partialleastsquares/)
- [線形計画法超入門](https://qiita.com/Dason08/items/e1bafb9ddc766d1c8fd0)
- [Python言語による実務で使える１００+の最適化問題](https://mikiokubo.github.io/opt100/)
- [ナップサック問題を色々な方法で解いてみた](https://blog.brainpad.co.jp/entry/2020/10/09/000002)
- [PuLPによる数理最適化超入門](http://www.nct9.ne.jp/m_hiroi/light/pulp01.html)
- [数理最適化によるロジスティクス戦略](https://www.slideserve.com/vaughan-moody/6098041)