ポイントオブセール最適化 PuLP

ポイントオブセール最適化

ポイントオブセール(POS)最適化問題は、商品の配置や在庫管理など、販売効率を最大化するための問題です。

ここでは、簡単な在庫管理問題を例に取り、PythonのPuLPライブラリを使用して解きます。

問題設定:
  • 商品A、B、Cがあり、それぞれの在庫数は最大10個とします。
  • 商品A、B、Cの利益はそれぞれ100円、200円、300円とします。
  • 予算は2000円とし、この予算内で最大の利益を得られるように在庫を管理します。

この問題をPuLPで解くコードは以下の通りです:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pulp import LpMaximize, LpProblem, LpStatus, lpSum, LpVariable

# 問題の定義
model = LpProblem(name="small-shop-problem", sense=LpMaximize)

# 変数の定義
x = {i: LpVariable(name=f"x{i}", lowBound=0, upBound=10, cat="Integer") for i in range(1, 4)}

# 利益の定義
profit = {1: 100, 2: 200, 3: 300}

# 目的関数
model += lpSum(profit[i] * x[i] for i in range(1, 4))

# 制約条件
model += (lpSum(x[i] for i in range(1, 4)) <= 2000, "budget_constraint")

# 問題の解
status = model.solve()

for var in x.values():
print(f"{var.name}: {var.value()}")

print(f"Optimal profit: {model.objective.value()}")

このコードは、在庫数と利益を最大化するための最適な商品の組み合わせを求めます。

[実行結果]
x1: 10.0
x2: 10.0
x3: 10.0
Optimal profit: 6000.0

グラフ化

次に、この問題の解をグラフ化します。

matplotlibを使用して、各商品の最適な在庫数を棒グラフで表示します:

1
2
3
4
5
6
7
8
9
10
11
12
13
import matplotlib.pyplot as plt

# 商品名
items = ['Item A', 'Item B', 'Item C']

# 最適な在庫数
optimal_values = [x[i].value() for i in range(1, 4)]

plt.bar(items, optimal_values)
plt.xlabel('Items')
plt.ylabel('Optimal Stock')
plt.title('Optimal Stock Level for Each Item')
plt.show()

このグラフは、各商品の最適な在庫数を視覚的に示しています。

[実行結果]

効率的フロンティア最適化 PuLP

効率的フロンティア最適化

効率的フロンティアの最適化問題を定義します。

この問題では、与えられたリスクレベルで最大のリターンを達成する投資ポートフォリオを見つけることが目的です。

以下に示すように、PythonのPuLPライブラリを使用してこの問題を解くことができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from pulp import *
import numpy as np
import matplotlib.pyplot as plt

# 各投資商品のリターンとリスク
returns = {'A': 0.1, 'B': 0.12, 'C': 0.10, 'D': 0.07}
risks = {'A': 0.05, 'B': 0.07, 'C': 0.06, 'D': 0.03}

# 投資商品のリスト
assets = list(returns.keys())

# リスクの上限
risk_limit = 0.05

# 問題の定義
prob = LpProblem("Efficient Frontier", LpMaximize)

# 変数の定義
x = LpVariable.dicts("x", assets, 0)

# 目的関数
prob += lpSum([returns[i]*x[i] for i in assets])

# 制約条件
prob += lpSum([risks[i]*x[i] for i in assets]) <= risk_limit
prob += lpSum([x[i] for i in assets]) == 1

# 問題の解
prob.solve()

# 結果の表示
for i in assets:
print(f"Investment in asset {i}: {x[i].varValue}")

このコードは、与えられたリスクレベルで最大のリターンを達成する投資ポートフォリオを見つけます。

結果は、各投資商品に対する投資比率として表示されます。

[実行結果]
Investment in asset A: 1.0
Investment in asset B: 0.0
Investment in asset C: 0.0
Investment in asset D: 0.0

グラフ化

次に、効率的フロンティアをグラフ化します。

これは、可能なすべての投資ポートフォリオのリスクとリターンをプロットしたものです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# リスクとリターンのリスト
risks_list = np.linspace(0.01, 0.1, 100)
returns_list = []

for risk_limit in risks_list:
prob = LpProblem("Efficient Frontier", LpMaximize)
x = LpVariable.dicts("x", assets, 0)
prob += lpSum([returns[i]*x[i] for i in assets])
prob += lpSum([risks[i]*x[i] for i in assets]) <= risk_limit
prob += lpSum([x[i] for i in assets]) == 1
prob.solve()
returns_list.append(value(prob.objective))

# グラフの描画
plt.plot(risks_list, returns_list)
plt.xlabel('Risk')
plt.ylabel('Return')
plt.title('Efficient Frontier')
plt.show()
[実行結果]

このグラフは、リスクとリターンのトレードオフを視覚的に示しています。

効率的フロンティアは、与えられたリスクレベルで達成可能な最大のリターンを示しています。

物流最適化問題 PuLP

物流最適化問題

物流最適化問題の一つとして、輸送問題を考えてみましょう。

輸送問題は、複数の供給地点から複数の需要地点への最小コストの輸送計画を求める問題です。

以下に、PythonのPuLPを用いて輸送問題を解く例を示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from pulp import LpProblem, LpMinimize, LpVariable, lpSum
import pandas as pd
import matplotlib.pyplot as plt

# データの設定
# 供給地点と供給量
supply_points = {'Factory1': 500, 'Factory2': 600}
# 需要地点と需要量
demand_points = {'Shop1': 300, 'Shop2': 400, 'Shop3': 400}
# 供給地点から需要地点への輸送コスト
costs = {
'Factory1': {'Shop1': 2, 'Shop2': 4, 'Shop3': 5},
'Factory2': {'Shop1': 3, 'Shop2': 1, 'Shop3': 3}
}

# 問題の定義
prob = LpProblem('Transportation', LpMinimize)

# 変数の定義
vars = {
i: {
j: LpVariable(f"x({i},{j})", lowBound=0)
for j in demand_points
}
for i in supply_points
}

# 目的関数の定義
prob += lpSum(costs[i][j] * vars[i][j] for i in supply_points for j in demand_points)

# 制約条件の定義
for i in supply_points:
prob += lpSum(vars[i][j] for j in demand_points) <= supply_points[i]
for j in demand_points:
prob += lpSum(vars[i][j] for i in supply_points) >= demand_points[j]

# 問題の解法
prob.solve()

# 結果の表示
for v in prob.variables():
print(v.name, "=", v.varValue)

# 結果のグラフ化
df = pd.DataFrame([(v.name, v.varValue) for v in prob.variables()], columns=['Variable', 'Value'])
df.plot(kind='bar', x='Variable', y='Value')
plt.show()

このコードは、2つの工場から3つの店舗への最小コストの輸送計画を求めます。

各工場から各店舗への輸送コストと供給量、需要量を元に、PuLPを用いて最適化問題を解きます。

最後に、各ルートの輸送量を棒グラフで表示します。

[実行結果]

ソースコード解説

1行目から6行目:

必要なライブラリのインポートを行います。

pulpは線形最適化モデリングライブラリであり、pandasとmatplotlibはデータ処理とグラフ表示のためのライブラリです。


9行目から11行目:

輸送問題で必要なデータの設定を行います。

供給地点と供給量、需要地点と需要量、および供給地点から需要地点への輸送コストを辞書として定義しています。


14行目:

LpProblemオブジェクトを作成し、最小化問題を定義します。

‘Transportation’は問題の名前で、LpMinimizeは最小化問題を指定しています。


17行目から22行目:

変数の定義を行います。

変数は「供給地点iから需要地点jへの輸送量」として定義され、LpVariableオブジェクトとして作成されます。

変数名は”x(i,j)”となります。


25行目:

目的関数を定義します。輸送コストと変数の積の総和として定義され、最小化対象となります。


28行目から30行目:

制約条件を定義します。各供給地点からの輸送量は供給量以下でなければならず、各需要地点への輸送量は需要量以上でなければなりません。


33行目:

prob.solve()を呼び出して、問題を解きます。

具体的な最適解が計算されます。


36行目から38行目:

解の結果を表示します。

各変数の値(輸送量)が表示されます。


41行目から45行目:

結果を棒グラフとして表示します。

解の各変数の値をDataFrameに格納し、pandasとmatplotlibを使用して棒グラフを描画します。

レストランのメニュー最適化

レストランのメニュー最適化

レストランのメニュー最適化の一例として、メニュー項目の人気度利益率を考慮して最適なメニュー構成を求める問題を考えてみましょう。

この問題は、各メニュー項目の人気度と利益率を元に、特定の制約(例えば、メニュー項目の最大数)の下で最大の利益を得られるメニュー構成を求めるというものです。


この問題は、一種のナップサック問題としてモデル化できます。

ナップサック問題は、与えられた容量制限の下で、アイテムの総価値を最大化するようなアイテムの組み合わせを求めるという問題です。

以下に、この問題を解くためのPythonコードを示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import matplotlib.pyplot as plt
import numpy as np

# メニュー項目の人気度と利益率
popularity = [5, 8, 3, 6, 9, 2]
profit = [7, 12, 4, 8, 15, 3]

# メニュー項目の最大数
max_items = 3

# メニュー項目の数
n = len(profit)

# メニュー最適化問題を解くための動的計画法
dp = np.zeros((n+1, max_items+1))

for i in range(n+1):
for j in range(max_items+1):
if i == 0 or j == 0:
dp[i][j] = 0
elif popularity[i-1] <= j:
dp[i][j] = max(profit[i-1] + dp[i-1][j-popularity[i-1]], dp[i-1][j])
else:
dp[i][j] = dp[i-1][j]

# 最適なメニュー構成の利益
optimal_profit = dp[n][max_items]

print("最適なメニュー構成の利益: ", optimal_profit)

# グラフ化
plt.bar(range(1, n+1), profit)
plt.xlabel('menu')
plt.ylabel('profit')
plt.title('profit of menu')
plt.show()

このコードは、各メニュー項目の人気度利益率を元に、最大の利益を得られるメニュー構成を求めます。

また、最後に各メニュー項目の利益を棒グラフで表示します。

[実行結果]

ファシリティ配置問題 SciPy

ファシリティ配置問題(1次元)

ファシリティ配置問題は、一般的には、特定の制約の下で最適な場所に施設を配置する問題です。

ここでは、シンプルな例として、一次元の空間に存在する複数の都市に対して、最適な場所に施設を配置する問題を考えてみましょう。

目標は、全ての都市から施設までの距離の合計を最小化することです。

この問題は、PythonのSciPyライブラリを使用して解くことができます。

以下にそのコードを示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt

# 都市の位置
cities = np.array([2, 5, 11, 16, 20, 23, 27, 30])

# 目的関数(全ての都市から施設までの距離の合計)
def objective(x):
return np.sum(np.abs(cities - x))

# 初期値
x0 = np.array([15])

# 最適化
result = minimize(objective, x0, method='Nelder-Mead')

# 結果の表示
print("Optimal location: ", result.x)

# グラフの描画
plt.figure(figsize=(8, 4))
plt.plot(cities, [1]*len(cities), 'ro')
plt.plot(result.x, [1], 'bo')
plt.yticks([])
plt.grid(True)
plt.show()

このコードでは、都市の位置を表す配列citiesを定義し、目的関数objectiveを定義しています。

目的関数は、施設の位置xから各都市までの距離の絶対値の合計を計算します。

次に、初期値x0を設定し、SciPyのminimize関数を使用して目的関数を最小化します。

最適化の結果は、result.xに格納されます。

最後に、都市の位置と最適な施設の位置をグラフに描画します。

赤い点が都市の位置を、青い点が最適な施設の位置を示しています。

[実行結果]

ファシリティ配置問題(2次元)

二次元空間に存在する複数の都市に対して施設を配置する問題も同様に解くことができます。

ここでは、都市の座標を2次元のnumpy配列で表現し、目的関数もそれに合わせて変更します。

以下にそのPythonコードを示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt

# 都市の位置
cities = np.array([[2, 3], [5, 1], [1, 6], [8, 6], [7, 4], [4, 7], [6, 2], [3, 4]])

# 目的関数(全ての都市から施設までの距離の合計)
def objective(x):
return np.sum(np.sqrt(np.sum((cities - x)**2, axis=1)))

# 初期値
x0 = np.array([5, 5])

# 最適化
result = minimize(objective, x0, method='Nelder-Mead')

# 結果の表示
print("Optimal location: ", result.x)

# グラフの描画
plt.figure(figsize=(8, 8))
plt.plot(cities[:, 0], cities[:, 1], 'ro')
plt.plot(result.x[0], result.x[1], 'bo')
plt.grid(True)
plt.show()

このコードでは、都市の位置を表す2次元配列citiesを定義し、目的関数objectiveを定義しています。

目的関数は、施設の位置xから各都市までのユークリッド距離の合計を計算します。

次に、初期値x0を設定し、SciPyのminimize関数を使用して目的関数を最小化します。

最適化の結果は、result.xに格納されます。

最後に、都市の位置と最適な施設の位置をグラフに描画します。

赤い点が都市の位置を、青い点が最適な施設の位置を示しています。

[実行結果]

ファシリティ配置問題(3次元)

三次元空間に存在する複数の都市に対して施設を配置する問題も同様に解くことできます。

ここでは、都市の座標を3次元のnumpy配列で表現し、目的関数もそれに合わせて変更します。

以下にそのPythonコードを示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import numpy as np
from scipy.optimize import minimize
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

# 都市の位置
cities = np.array([[2, 3, 1], [5, 1, 6], [1, 6, 4], [8, 6, 2], [7, 4, 5], [4, 7, 3], [6, 2, 7], [3, 4, 2]])

# 目的関数(全ての都市から施設までの距離の合計)
def objective(x):
return np.sum(np.sqrt(np.sum((cities - x)**2, axis=1)))

# 初期値
x0 = np.array([5, 5, 5])

# 最適化
result = minimize(objective, x0, method='Nelder-Mead')

# 結果の表示
print("Optimal location: ", result.x)

# グラフの描画
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(cities[:, 0], cities[:, 1], cities[:, 2], c='r', marker='o')
ax.scatter(result.x[0], result.x[1], result.x[2], c='b', marker='x')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()

このコードでは、都市の位置を表す配列citiesを定義し、目的関数objectiveを定義しています。

目的関数は、施設の位置xから各都市までのユークリッド距離の合計を計算します。

次に、初期値x0を設定し、SciPyのminimize関数を使用して目的関数を最小化します。

最適化の結果は、result.xに格納されます。

最後に、都市の位置と最適な施設の位置を3Dグラフに描画します。

赤い点が都市の位置を、青い点が最適な施設の位置を示しています。

[実行結果]

移住最適化 SciPy

移住最適化

次のような移住に関する最適化問題を考えてみます。

  • ある国が、各地域への人口分布を最適化したいと考えている。
  • 各地域には、収容可能な最大人口があり、また、各地域の生活水準や仕事の機会などにより、その地域への人々の満足度が決まる。
  • 目的は、全体の満足度を最大化するような人口分布を見つけること。

この問題は、制約付き最適化問題として表現できます。

SciPyのscipy.optimize.minimize関数を使って解くことができます。

以下に、PythonとSciPyを使ったサンプルコードを示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from scipy.optimize import minimize
import numpy as np

# 各地域の最大人口
max_populations = np.array([1000, 2000, 1500, 500, 1200])

# 各地域の満足度関数(ここでは単純化のためにランダムな値を使用)
satisfaction = np.random.rand(5)

# 目的関数(最小化するために-1を掛ける)
def objective(populations):
return -np.sum(satisfaction * populations)

# 制約(各地域の人口はその地域の最大人口を超えない)
constraints = [{'type': 'ineq', 'fun': lambda x: max_populations - x}]

# 初期値
x0 = np.array([500, 500, 500, 500, 500])

# 最適化
res = minimize(objective, x0, method='SLSQP', constraints=constraints)

print("Optimal population distribution:", res.x)

このコードは、各地域の人口分布を最適化して、全体の満足度を最大化します。

ただし、各地域の人口はその地域の最大人口を超えないという制約があります。

なお、このコードはあくまで一例であり、実際の問題に応じて目的関数や制約、最適化手法などを適切に設定する必要があります。

実行結果

上記コードを実行すると、下記のような結果が表示されます。

[実行結果]
Optimal population distribution: [1000.00000001 2000.00000002 1500.00000003  500.         1200.00000002]

この結果は、最適化によって得られた最適な人口分布を示しています。

各地域の最大人口制約を考慮しながら、満足度を最大化するように人口が配分されました。

結果の配列 [1000.00000001, 2000.00000002, 1500.00000003, 500.0, 1200.00000002] は、各地域の最適な人口を示しています。

値の小数点以下に微小な誤差が含まれていることに注意してください(これは数値計算の精度の限界によるものです)。

最適な人口分布は次のようになります:

🔹地域1: 1000人
🔹地域2: 2000人
🔹地域3: 1500人
🔹地域4: 500人
🔹地域5: 1200人

これは、目的関数で定義した満足度を最大化する人口分布であり、各地域の最大人口制約を満たしています。

制約付き最小化問題 SciPy

制約付き最小化問題(Constrained Minimization Problem)

制約付き最小化問題(Constrained Minimization Problem)は、最小化したい目的関数の下で、特定の制約条件を満たす変数の最適解を求める問題です

例題

制約付き最小化問題(Constrained Minimization Problem)の例題として、目的関数と制約条件を設定したコードを表します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from scipy.optimize import minimize

# 目的関数
def objective(x):
return x[0]**2 + x[1]**2 # 最小化したい関数(例: x^2 + y^2)

# 制約条件
def constraint(x):
return x[0] + x[1] - 1 # 制約条件(例: x + y <= 1)

# 初期値
x0 = [0, 0] # 初期値の設定

# 制約付き最小化問題を解く
result = minimize(objective, x0, constraints={'type': 'ineq', 'fun': constraint})

# 結果を表示
print("最適解:", result.x)
print("最適な目的関数値:", result.fun)

この例では、2変数の目的関数 objective と制約条件 constraint を定義しています。

目的関数は最小化したい関数(ここでは $ x^2 + y^2 $)を示しており、制約条件は $x + y \leqq 1$ のような形式で設定しています。


minimize 関数を使用して、目的関数と制約条件を引数として渡しています。

制約条件は constraints パラメータに辞書型として渡されており、'type' には制約の種類(この例では不等式制約 'ineq')を、'fun' には制約条件の関数を指定しています。


最適化の結果は result に格納され、result.x に最適解の変数の値が、result.fun に最適な目的関数値が返されます。これらを表示しています。

実行結果

上記のコードを実行すると下記のような結果が表示されます。

[実行結果]
最適解: [0.5 0.5]
最適な目的関数値: 0.5000000000000002

この結果は、制約付き最小化問題の最適解と最適な目的関数値を示しています。

🔹最適解: [0.5, 0.5]
 この結果は、最適解の変数の値を示しています。
 ここでは、x の値が 0.5、y の値が 0.5 であることを意味します。
 この値は、制約条件 $x + y \leqq 1$ を満たす中で、目的関数 $x^2 + y^2$ を最小化するための最適な値です。

🔹最適な目的関数値: 0.5000000000000002
 この結果は、最適な目的関数の値を示しています。
 ここでは、目的関数 $ x^2 + y^2 $ の最小値が $0.5$ であることを示しています。
 ただし、浮動小数点数の誤差があるため、表示された値は正確な最小値ではありません。
 厳密な結果は result.fun の値として得られます。

この結果から、与えられた目的関数 $ x^2 + y^2 $ を最小化するための最適解は、$x = 0.5$ および $y = 0.5$ であり、そのときの最適な目的関数値は約 $0.5$ であることがわかります。

糖尿病患者の病状進行予測 Scikit-learn

糖尿病患者の病状進行予測 Scikit-learn

Scikit-learnは、Pythonの機械学習ライブラリで、分類回帰クラスタリング次元削減などの機能が含まれています。

現実的な問題として、糖尿病患者のデータを使って、患者の1年後の病状進行を予測する回帰モデルを作成してみましょう。

この例では、Scikit-learnのデータセットから糖尿病患者のデータをロードし、線形回帰モデルを使って予測を行います。


まず、必要なライブラリをインポートします。

1
2
3
4
5
6
import numpy as np
import pandas as pd
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

次に、糖尿病患者のデータセットをロードします。

1
diabetes = datasets.load_diabetes()

データセットを説明変数(特徴量)と目的変数(1年後の病状進行)に分割します。

1
2
X = diabetes.data
y = diabetes.target

データをトレーニングセットとテストセットに分割します(トレーニングセット:75%、テストセット:25%)。

1
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)

線形回帰モデルを作成し、トレーニングセットで学習させます。

1
2
model = LinearRegression()
model.fit(X_train, y_train)

モデルを使ってテストセットの予測を行い、平均二乗誤差(MSE)を計算してモデルの性能を評価します。

1
2
3
y_pred = model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
print("Mean squared error: ", mse)
[実行結果]
Mean squared error:  2848.3106508475053

この例では、糖尿病患者のデータを使って線形回帰モデルを作成し、1年後の病状進行を予測しました。

Scikit-learnを使って、さまざまな機械学習アルゴリズムを試すことができます。

現実的な問題に対して最適なモデルを選択し、パラメータを調整することで、より高い性能の予測モデルを作成することができます。

グラフ化

上記の例では、糖尿病患者のデータを使って線形回帰モデルを作成し、1年後の病状進行を予測しました。

予測結果をグラフ化して視覚的に評価するために、matplotlibというPythonのグラフ描画ライブラリを使用します。

まず、matplotlibをインポートします。

1
import matplotlib.pyplot as plt

次に、実際の目的変数(y_test)と予測値(y_pred)を散布図でプロットします。

対角線上にプロットされるほど、予測が正確であることを示します。

1
2
3
4
5
6
7
8
9
10
plt.scatter(y_test, y_pred)
plt.xlabel("Actual Progression")
plt.ylabel("Predicted Progression")
plt.title("Actual vs Predicted Progression")

# 対角線を描画
diagonal_line = np.linspace(min(y_test), max(y_test), 100)
plt.plot(diagonal_line, diagonal_line, color='red', linestyle='--')

plt.show()
[実行結果]

このグラフは、実際の病状進行(x軸)と予測された病状進行(y軸)を比較しています。

赤い破線は、予測が完全に正確である場合にデータポイントが存在するべき場所を示しています。

グラフから、多くのデータポイントが対角線の近くにあることがわかりますが、いくつかの外れ値も存在しています。

これは、モデルが完全に正確ではないことを示していますが、ある程度の予測性能があることがわかります。


このようなグラフを使って、モデルの性能を視覚的に評価することができます。

さらに、他の機械学習アルゴリズムを試したり、ハイパーパラメータを調整して、予測性能を向上させることができます。

レコメンデーションシステム Scikit-learn

レコメンデーションシステム Scikit-learn

Scikit-learnを使ってレコメンデーションシステムを実装する例として、k近傍法(K-Nearest Neighbors)を用いた協調フィルタリング(Collaborative Filtering)を紹介します。

以下は、ユーザーとアイテムの評価データを使って、ユーザーに対するアイテムのレコメンデーションを行うサンプルコードです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import NearestNeighbors

# データの読み込み(ここでは仮のデータを使用)
data = pd.DataFrame({
'user_id': [1, 1, 2, 2, 3, 3, 4, 4, 5, 5],
'item_id': [1, 2, 1, 3, 2, 3, 1, 4, 4, 5],
'rating': [5, 4, 4, 5, 3, 4, 5, 4, 5, 4]
})

# 訓練データとテストデータに分割
train_data, test_data = train_test_split(data, test_size=0.2, random_state=42)

# ユーザー-アイテム行列の作成
user_item_matrix = train_data.pivot_table(index='user_id', columns='item_id', values='rating').fillna(0)

# k近傍法モデルの作成と訓練
model = NearestNeighbors(metric='cosine', algorithm='brute')
model.fit(user_item_matrix)

# ユーザーに対するアイテムのレコメンデーション
user_id = 1
n_recommendations = 3

# 類似ユーザーの検索
distances, indices = model.kneighbors(user_item_matrix.loc[user_id].values.reshape(1, -1), n_neighbors=n_recommendations+1)

# 類似ユーザーの評価データを取得
similar_users_ratings = user_item_matrix.iloc[indices.flatten()[1:]]

# 類似ユーザーの評価の平均を計算
mean_ratings = similar_users_ratings.mean(axis=0)

# すでに評価済みのアイテムを除外
recommended_items = mean_ratings[user_item_matrix.loc[user_id] == 0].sort_values(ascending=False).index.tolist()

print(f"ユーザー {user_id} におすすめのアイテム: {recommended_items[:n_recommendations]}")
[実行結果]
ユーザー 1 におすすめのアイテム: [3, 4, 2]

このサンプルコードでは、仮のデータを使用していますが、実際のアプリケーションでは、ユーザーとアイテムの評価データを含むデータセットを用意する必要があります。

また、レコメンデーションの精度を向上させるために、ハイーパラメータの調整や他のアルゴリズムの検討も検討してください。

グラフ化

k近傍法を用いた協調フィルタリングによるレコメンデーションシステムをグラフで説明します。

まず、matplotlibを使ってデータを可視化し、その後、k近傍法の適用方法を説明します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.neighbors import NearestNeighbors

# データの読み込み(ここでは仮のデータを使用)
data = pd.DataFrame({
'user_id': [1, 1, 2, 2, 3, 3, 4, 4, 5, 5],
'item_id': [1, 2, 1, 3, 2, 3, 1, 4, 4, 5],
'rating': [5, 4, 4, 5, 3, 4, 5, 4, 5, 4]
})

# ユーザー-アイテム行列の作成
user_item_matrix = data.pivot_table(index='user_id', columns='item_id', values='rating').fillna(0)

# グラフの作成
fig, ax = plt.subplots()
cax = ax.matshow(user_item_matrix, cmap='viridis')
fig.colorbar(cax)

# 軸の設定
ax.set_xticks(np.arange(len(user_item_matrix.columns)))
ax.set_yticks(np.arange(len(user_item_matrix.index)))
ax.set_xticklabels(user_item_matrix.columns)
ax.set_yticklabels(user_item_matrix.index)

# グラフの表示
plt.xlabel('Item ID')
plt.ylabel('User ID')
plt.title('User-Item Matrix')
plt.show()

このグラフは、ユーザー-アイテム行列を表しています。

行はユーザーID、列はアイテムIDを表し、セルの色は評価値を示しています。

色が暗いほど評価が低く、色が明るいほど評価が高いことを示します。

k近傍法を適用する際、類似度を計算するためにコサイン類似度などの距離指標を使用します。

この例では、コサイン類似度を用いています。ユーザー間の類似度を計算し、最も類似度が高いk人のユーザーを見つけます。

その後、これらの類似ユーザーが高く評価したアイテムを推薦します。

[実行結果]

このグラフを使ってk近傍法を説明すると、例えばユーザー1に対してアイテムを推薦する場合、ユーザー1と類似した評価パターンを持つユーザー(この場合はユーザー2とユーザー3)を見つます。

次に、これらの類似ユーザーが高く評価したアイテムをユーザー1に推薦します。

この例では、アイテム3が推薦される可能性が高いです。

線形計画問題 SciPy

線形計画問題

経済に関する最適化問題の一例として、線形計画問題(Linear Programming Problem, LP)を考えます。

この問題では、目的関数を最大化(または最小化)するように、制約条件のもとで変数を選択します。

ここでは、生産計画問題を例に取り上げます。

問題設定:

ある工場では、製品Aと製品Bを生産しています。製品Aの利益は100ドル、製品Bの利益は150ドルです。

🔹製品Aを1つ生産するのに、原料Xが1単位、原料Yが3単位必要です。
🔹製品Bを1つ生産するのに、原料Xが2単位、原料Yが1単位必要です。
🔹工場には、原料Xが4単位、原料Yが6単位しかありません。

この制約条件のもとで、利益を最大化する生産計画を求めます。

解法

この問題をSciPyを使って解くために、scipy.optimize.linprog関数を使用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
from scipy.optimize import linprog

# 目的関数の係数 (最大化のためにマイナスにする)
c = np.array([-100, -150])

# 制約条件の係数
A = np.array([[1, 2], [3, 1]])
b = np.array([4, 6])

# 変数の範囲 (0以上)
x_bounds = (0, None)
y_bounds = (0, None)

# 最適化問題を解く
res = linprog(c, A_ub=A, b_ub=b, bounds=[x_bounds, y_bounds], method='highs')

print(res)

実行すると、最適な生産計画が得られます。

res.xには、製品Aと製品Bの最適な生産量が格納されています。

res.funには、最大化された利益の値が格納されています(マイナスになっているので、正の値に戻す必要があります)。

[実行結果]
        message: Optimization terminated successfully. (HiGHS Status 7: Optimal)
        success: True
         status: 0
            fun: -339.9999999999999
              x: [ 1.600e+00  1.200e+00]
            nit: 2
          lower:  residual: [ 1.600e+00  1.200e+00]
                 marginals: [ 0.000e+00  0.000e+00]
          upper:  residual: [       inf        inf]
                 marginals: [ 0.000e+00  0.000e+00]
          eqlin:  residual: []
                 marginals: []
        ineqlin:  residual: [ 0.000e+00  0.000e+00]
                 marginals: [-7.000e+01 -1.000e+01]
 mip_node_count: 0
 mip_dual_bound: 0.0
        mip_gap: 0.0

各要素の詳細は以下の通りです。

  • message:
    最適化が正常に終了したことを示すメッセージです。
    HiGHSソルバーが最適解を見つけたことを示しています。
  • success:
    最適化が成功したかどうかを示すブール値です。
    Trueは成功を意味します。
  • status:
    最適化の終了ステータスを示す整数です。
    0は成功を意味します。
  • fun:
    目的関数の最適値です。
    この問題では、最大化を行っているため、値はマイナスになっています。
    実際の最大利益は、この値の正の逆数である340ドルです。
  • x:
    最適解を示す配列です。
    この場合、製品Aの最適生産量は1.6個、製品Bの最適生産量は1.2個です。
  • nit:
    反復回数です。
    この問題では、2回の反復で最適解が見つかりました。
  • lower, upper, eqlin, ineqlin:
    これらは、最適化問題の制約条件に関する情報を提供します。
    residualは制約条件の残差を示し、marginalsは制約条件の影響を示します。
    この問題では、ineqlinmarginalsが-70と-10であり、これは制約条件が目的関数に与える影響を示しています。
  • mip_node_count, mip_dual_bound, mip_gap:
    これらはMixed Integer Programming (MIP)問題に関する情報ですが、この問題ではMIPではないため、関連性がありません。

この結果から、最適化問題が正常に解決され、最適な生産計画が得られたことがわかります。

製品Aの最適生産量は1.6個、製品Bの最適生産量は1.2個で、最大利益は340ドルです。

グラフ化

この生産計画問題の結果をグラフ化して説明するために、matplotlibを使用します。

グラフには、原料Xと原料Yの制約条件を示す直線と、最適な生産計画を示す点を描画します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import numpy as np
import matplotlib.pyplot as plt

# 制約条件の直線を描画する関数
def plot_constraints(x):
y1 = (4 - x) / 2
y2 = 6 - 3 * x
return y1, y2

# xの範囲を設定
x = np.linspace(0, 4, 100)

# 制約条件の直線を計算
y1, y2 = plot_constraints(x)

# 最適な生産計画を計算
optimal_x = res.x[0]
optimal_y = res.x[1]

# グラフを描画
plt.plot(x, y1, label="1x + 2y <= 4")
plt.plot(x, y2, label="3x + 1y <= 6")
plt.xlim(0, 4)
plt.ylim(0, 4)
plt.xlabel("Product A")
plt.ylabel("Product B")
plt.legend()

# 最適な生産計画を示す点を描画
plt.plot(optimal_x, optimal_y, 'ro', label="Optimal Production Plan")
plt.annotate(f"({optimal_x:.2f}, {optimal_y:.2f})", (optimal_x + 0.1, optimal_y - 0.2))

plt.show()
[実行結果]

このグラフでは、x軸が製品Aの生産量、y軸が製品Bの生産量を表しています。

青い線は原料Xの制約条件$(1x + 2y <= 4)$を示し、オレンジの線は原料Yの制約条件$(3x + 1y <= 6)$を示しています。

赤い点は最適な生産計画を示しており、その座標は最適な生産量(製品Aと製品B)です。

このグラフから、最適な生産計画が制約条件の範囲内で利益を最大化することがわかります。