소프트맥스 함수란?

소프트맥스(Softmax)는 출력중에 사용되는 함수로 보통 다중 클래스 분류 모델을 만들때 사용한다.

결과를 확률로 해석할 수 있게 변환해주는 함수로 높은 확률을 가지는 class로 분류한다. 이는 결과값을 정규화 시키는 것으로도 생각할 수 있다.

이를 경사하강법(Gradient Descent)와 함께 사용하면 softmax로 확률을 만들고 행동을 평가하고 gradient descentweight를 수정해서 효율적으로 학습시킬 수 있다.

경사하강법 이란?

경사하강법(Gradient Descent)는 어떤 함수를 최소값(minimum)으로 만드는 방법이다.

딥러닝에서는 손실(loss)최소화 할때 사용한다

예를들어 x^2에 대해서 기울기가 양수면 왼쪽으로 이동하고 음수면 오른쪽으로 이동하는 방식으로 이동하면 최소값에 도달할 수 있다.

포켓몬 배틀에서의 적용

이번에 만든 INURoGue에서 적 포켓몬의 행동함수에는 이 기법이 적용되고 있다.

Data, Modules Loading

path : asset/script/main/softmax.py

import numpy as np


Functions

가중치 부여

weights = np.random.randn(3, 3)

def softmax(x)

def softmax(x):
    exps = np.exp(x - np.max(x))
    return exps / np.sum(exps)
  • 위에서 말했듯이 softmax는 list를 확률로 바꿔준다
  • 예를 들어 [3, 2, 4, 1]를 [0.1, 0.5, 0.3, 0.1] 처럼 합이 1인 분포로 바꿔준다.
  • 너무 큰값이 들어가면 exp()가 overflow가 날수 있기 때문에 안정성을 위해 전체에 최대값을 빼준다
  • 전체의 합을 각각 나누면 전체 합이 1인 분포가 된다


def choose_skill(state)

def choose_skill(state):
    logits = weights @ state
    probs = softmax(logits)
    skill_index = np.random.choice(3, p=probs)
    return skill_index, probs


세부 분석

logits = weights @ state
  • @는 dot product(행렬내적)이다
  • wights는 가중치 행렬이고(R^3 공간) state는 상태 벡터이다
probs = softmax(logits)
  • 둘을 내적하면 각 스킬에 대한 점수(logits)가 나온다
skill_index = np.random.choice(3, p=probs)
  • 이를 softmax에 넣어서 확률(probs)로 바꿔준다
skill_index = np.random.choice(3, p=probs)
    return skill_index, probs
  • 이를 probs에 따라 선택한다

def update_weights(state, skill_index, reward, probs, lr=0.01)

def update_weights(state, skill_index, reward, probs, lr=0.01):
    grad = -reward * (1 - probs[skill_index]) * state
    weights[skill_index] -= lr * grad

state: 현재 상태 벡터 (입력)
skill_index: 선택된 스킬
reward: 해당 선택에 대한 보상
probs: softmax로 나온 확률 분포
lr: 학습률 (learning rate)

선택한 스킬의 weight만 update 하기위해 사용한다

weights[skill_index] -= lr * grad
  • 이부분이 핵심이 되는 경사하강법이다.
  • 확률이 높을수록 reward를 받으면 변화량이 작아지고, 낮았는데 reward가 높으면 더 크게 올려준다

실제 도입

def get_state(self):
    return np.array([
        self.pokemon.hp / self.pokemon.max,
        self.player.pokemon.hp / self.player.pokemon.max,
        1.0  
    ])

def skill_algorithm(self):
    state = self.get_state()

    enemy_skill_idx, probs = choose_skill(state)

    enemy_skill_name = self.pokemon.skill[enemy_skill_idx]
    enemy_skill_key = skill_dict[enemy_skill_name]
    enemy_skill = self.skill_handler.skill_list[enemy_skill_key]

    result = cal_damage(enemy_skill, enemy_skill.type, self.player.pokemon.type)

    reward = result[0]

    update_weights(state, enemy_skill_idx, reward, probs)

    self.dealt = True

    return enemy_skill_name, result

여기서 get state()에서 bias 1.0을 넣어서 결정 경계를 옮겨준다.
모델을 더 유연하게 예측하는데 필요하다. 예를 들어 ax + b에서 b가 없을경우 무조건 원점을 지나기 때문에 입력값이 0일때 출력도 0이 되어서 학습할 수 있는 함수가 제한된다.

  • 결정함수와 정해진 수식 cal_damage로 reward를 계산해서 학습시킨다