조합 최적화로 팀 나누기(야키나마시법)
소개
마지막 기사 에서 팀 분할 문제를 평균 편차 최소화 문제로 공식화하고 Python+PuLP로 엄격하게 해결하는 방법을 소개했습니다. 그 중 조합 최적화 문제는 문제가 대규모가 되면 엄격하게 풀기가 어려워진다고 말했습니다.
그래서 이번에는 Python+simanneal을 사용한 어닐링법에 의한 근사해법을 소개합니다.
simanneal이란?
simanneal은 어닐링 방법을위한 패키지입니다.
설치 방법
pip install simanneal
기본 사용법
최적화 문제의 제약은 move()와 energy() 속에서 표현할 필요가 있습니다.
아래의 "예제 : Python + simanneal에 의한 최적화"도 참조하십시오.
풀고 싶은 문제
이전과 같은 문제를 다룹니다.
배경은 이전 기사를 참조하도록 하여 다음과 같은 조합 최적화 문제를 해결합니다.
\begin{align}
\text{minimize} \quad &\sum_{j=1}^m \Bigl(\sum_{k=1}^l \Bigl|\sum_{i=1}^n a_{i,k} x_{i,j} - \mu_j \Bigr|\Bigr) + 1.5\sum_{j=1}^m\Bigl|\sum_{i=1}^n b_i x_{i,j} -\nu\Bigr| \tag{2.1}\\
\text{subject to} \quad &\mu_j = \frac{1}{l}\sum_{k=1}^l\sum_{i=1}^n a_{i,k}x_{i,j} &j=1,\ldots,m \tag{2.2}\\
& \nu = \frac{1}{m} \sum_{j=1}^m \sum_{i=1}^n b_i x_{i,j} \tag{2.3}\\
&\sum_{j=1}^m x_{i,j} = 1 &i=1,\ldots,n \tag{2.4}\\
& x_{i,j} \in \{0,1\} &i=1,\ldots,n;\, j=1,\ldots,m \tag{2.5}\\
\end{align}
전회는 여기에서 절대치를 제외해 선형 계획 문제로 하는 것 같은 변형을 실시했습니다만, 어닐링법에서는 그대로로 괜찮습니다.
예제: Python+simanneal에 의한 최적화
이전과 같은 예제를 다룹니다.
grouping.py
from simanneal import Annealer
import random
class GroupingProblem(Annealer):
def __init__(self, init_state):
super(GroupingProblem, self).__init__(init_state) # important!
# 探索点の移動ルール
def move(self):
# ランダムに選択したメンバーm1をチームt1からチームt2に移す
m1 = random.choice(list(range(nmembers)))
t1 = self.state[m1].index(1)
t2 = random.choice(list(range(nteams)))
self.state[m1][t1], self.state[m1][t2] = self.state[m1][t2], self.state[m1][t1]
# 目的関数
def energy(self):
v = 0
nu = sum(sum(b[i] * self.state[i][j] for i in range(nmembers)) for j in range(nteams)) / nteams
for j in range(nteams):
mu_j = sum(sum(scores[i][k] * self.state[i][j] for i in range(nmembers)) for k in range(nskills)) / nskills
for k in range(nskills):
v += abs(sum(scores[i][k] * self.state[i][j] for i in range(nmembers)) - mu_j)
v += 1.5 * abs(sum(b[i] * self.state[i][j] for i in range(nmembers)) - nu)
return v
if __name__ == '__main__':
# 例題
teams = ['A', 'B', 'C']
members = ['P', 'Q', 'R', 'S', 'T', 'U']
skills = ['Controller', 'Promoter', 'Supporter', 'Analyzer']
scores = [[6, 0, 1, 3],
[2, -1, 3, 5],
[2, 4, 0, 0],
[3, 4, 5, 0],
[0, 2, 1, 4],
[2, 3, -1, 1]]
nteams = len(teams) # チーム数
nmembers = len(members) # メンバー数
nskills = len(skills) # 能力種別数
b = [sum(ai) for ai in scores] # 各メンバーのスキルの総和
# 初期割当て
init_state = [[0 for j in range(nteams)] for i in range(nmembers)]
for i in range(nmembers):
init_state[i][0] = 1 # 最初は全員Aチームに所属
prob = GroupingProblem(init_state)
prob.steps = 100000
prob.copy_strategy = "deepcopy"
prob.anneal() # 焼きなまし実行
for i,s in enumerate(prob.state):
print(members[i], teams[s.index(1)])
이렇게하면 어닐링의 경과가 표시됩니다.
전회와 같은 해를 얻었습니다.
$ pypy grouping.py
Temperature Energy Accept Improve Elapsed Remaining
2.50000 23.50 35.60% 0.70% 0:00:04 0:00:00
('P', 'A')
('Q', 'C')
('R', 'C')
('S', 'B')
('T', 'A')
('U', 'B')
simanneal이 표시하는 각 항목에 대해 보충합니다.
결국 Temperature와 Improve가 작아지고 해가 수렴합니다.
결론
조합 최적화 문제를 Python+simanneal로 해결하는 방법을 소개했습니다.
simanneal은 쉽게 구운 방법을 수행 할 수있는 매우 편리한 패키지입니다.
또한 simanneal은 pypy에서도 실행할 수 있습니다. 반복마다의 조작(move()이나 enargy()의 내용)이 단순한 것이라면 pypy를 이용하는 것으로 고속화를 전망할 수 있습니다.
참고 : pypy에서 numpy.array를 사용하면 느려집니다. pypy를 사용하는 경우는 numpy.array의 벡터 연산을 사용하는 것보다 목록에서 map이나 for문을 돌리는 것이 빠릅니다.
참고
Reference
이 문제에 관하여(조합 최적화로 팀 나누기(야키나마시법)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/matsulib/items/bd50af2e2bc1e48522cd텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)