이전 내용
강화 학습(Reinforcement Learning, RL)은 기계 학습의 한 분야로, 에이전트가 환경과 상호작용하여 보상을 최대화할 수 있는 행동을 학습하는 방법이다. 다양한 상황에서 의사 결정을 하는 방법을 배우고, 결과에 따라 조정하는 과정을 통해 성능을 향상시킨다. 강화 학습은 특히 동적이고 복잡한 환경에서 최적의 전략을 찾는 데 유용하다.
강화 학습에서 소프트웨어 에이전트는 관측을 하고 주어진 환경에서 행동을 한다. 그리고 결과에 따라 환경으로부터 보상을 받는다. 에이전트의 목적은 보상의 장기간 기대치를 최대로 만드는 행동을 학습하는 것이다. 긍정적 보상은 기쁨으로, 부정적 보상은 아픔으로 생각할 수 있다. 즉, 에이전트는 환경 안에서 행동하고 시행착오를 겪으며 기쁨이 최대가 되고 아픔이 최소가 되도록 학습한다.
[딥러닝] 강화 학습(Reinforcement Learning) - 1
이전 내용 [딥러닝] 비지도 학습: 오토인코더, GAN, 확산 모델이전 내용 오토인코더 오토인코더는 지도 방식을 사용하지 않고 잠재 표현 또는 입력 데이터의 밀집 표현(코딩)을 학습할 수 있는
puppy-foot-it.tistory.com
Q-러닝
Q-러닝 알고리즘은 전이 확률과 보상을 초기에 알지 못한 상황에서 Q-가치 반복 알고리즘을 적용한 것이다. Q-러닝은 에이전트가 플레이하는 것을 보고 점진적으로 Q-가치 추정을 향상하는 방식으로 작동한다. 정확한 Q-가치 추정을 얻게 되면 최적의 정책은 가장 높은 Q-가치를 가지는 행동을 선택하는 탐욕적 정책이다.
◆ Q-가치 반복 알고리즘(Q-value Iteration Algorithm)
Q-가치 반복 알고리즘은 Q-러닝과 관련된 알고리즘으로, 에이전트가 최적의 Q-값(상태-행동 가치 함수)을 학습하는 데 사용된다. 이 알고리즘은 벨만 최적 방정식을 반복적으로 사용하여 Q-값을 계산한다.
- Q-가치 반복의 기본 아이디어
Q-가치 반복은 다음과 같은 재귀적인 업데이트 규칙을 사용한다.
[ Q(s, a) \leftarrow R(s, a) + \gamma \sum_{s'} P(s'|s, a) \max_{a'} Q(s', a') ]
여기서:
- ( Q(s, a) )는 상태 ( s )에서 행동 ( a )를 했을 때의 Q-값(상태-행동 가치).
- ( R(s, a) )는 상태 ( s )에서 행동 ( a )를 했을 때의 즉각적인 보상.
- ( \gamma )는 할인율로, 미래 보상의 현재 가치.
- ( P(s'|s, a) )는 상태 ( s )에서 행동 ( a )를 했을 때 다음 상태 ( s' )로 전이될 확률.
- Q-가치 반복 알고리즘 단계
- 초기화: 모든 상태-행동 쌍의 Q-값을 임의의 값으로 초기화(일반적으로 0).
- 반복 업데이트:
- Q-값을 업데이트.
- 알고리즘이 수렴할 때까지 반복.
- 정책 도출: 업데이트된 Q-값을 사용하여 최적의 정책을 도출.
※ 탐욕적 정책 (Greedy Policy)
탐욕적 정책은 현재 상태에서 가장 높은 Q-값을 갖는 행동을 선택하는 정책이다. 이는 탐험보다는 최적의 행동을 따르는 방법으로, 에이전트가 학습을 통해 얻은 정보에 기반하여 가장 좋은 행동을 선택한다.
◆ Q-러닝 알고리즘 구현
먼저 에이전트가 환경을 탐색하게 만들어야 하는데, 이를 위해 에이전트가 한 행동을 실행하고 결과 상태와 보상을 받을 수 있는 스텝 함수를 만든다.
def step(state, action):
probas = transition_probabilities[state][action]
next_state = np.random.choice([0, 1, 2], p=probas)
reward = rewards[state][action][next_state]
return next_state, reward
에이전트의 탐색 정책을 구현한다. 이 상태 공간은 매우 작기 때문에 단순한 랜덤 정책으로 충분하다. 알고리즘을 충분히 오랫동안 실행하면 에이전트가 각 상태를 여러 번 방문하고 가능한 모든 행동을 여러 번 실행할 수 있다.
def exploration_policy(state):
return np.random.choice(possible_actions[state])
그리고 Q-가치를 초기화한 후 학습률 감쇠(거듭제곱 기반 스케줄링)를 사용해 Q-러닝 알고리즘을 실행한다.
alpha0 = 0.05 # 초기 학습률
decay = 0.005 # 학습률 감쇠
gamma = 0.90 # 할인 계수
state = 0 # 초기 상태
for iteration in range(10_000):
action = exploration_policy(state)
next_state, reward = step(state, action)
next_value = Q_values[next_state].max() # 다음 스텝에서 탐욕적 정책
alpha = alpha0 / (1 + iteration * decay)
Q_values[state, action] *= 1 - alpha
Q_values[state, action] += alpha * (reward + gamma * next_value)
state = next_state
Q-러닝은 탐험 정책이 MDP를 충분히 탐험해야 작동한다. 완전한 랜덤 정책이 결국에는 모든 상태와 전이를 여러 번 경험하도록 보장하지만 이렇게 하려면 극단적으로 오랜 시간이 걸릴 수 있다.
더 나은 방법은 입실론-그리디 정책을 사용하는 것으로, 각 스텝에서 입실론 확률로 랜덤하게 행동하거나 1-입실론 확률로 그 순간 가장 최선인 것으로 가장 높은 Q-가치를 선택하여 행동한다.
이 정책의 장점은 Q-가치 추정이 점점 더 향상되기 때문에 환경에서 관심 있는 부분을 살피는 데 점점 더 많은 시간을 사용한다는 점이다. 입실론의 값은 높게 시작해서 점점 감소되는 것이 일반적이다.
Q-러닝의 주요 문제는 많은 상태와 행동을 가진 대규모 또는 중간 규모의 MDP에 적용하기 어렵다는 것이다.
해결책은 어떤 상태-행동 쌍의 Q-가치를 근사하는 함수를 적절한 개수의 파라미터를 사용하여 찾는 것으로, 이를 근사Q-러닝이라고 한다.
그러다 2013년 딥마인드는 심층 신경망을 사용해 특히 복잡한 문제에서 훨씬 더 나은 결과를 냈는데, Q-가치를 추정하기 위해 사용하는 DNN을 심층 Q-네트워크(DQN)라 하고, 근사 Q-러닝을 위해 DQN을 사용하는 것을 심층 Q-러닝 이라 한다.
심층 Q-러닝 구현
심층 Q-러닝을 구현하기 위해 첫 번째로 필요한 것은 심층 Q-네트워크(DQN)이다. 실전에서는 상태만 입력으로 받고 가능한 모든 행동에 대한 근사 Q-가치를 각각 출력하는 것이 상태-행동 쌍을 입력으로 받고 근사 Q-가치를 출력하는 것보다 효율적이다.
이전에 했던 CartPole 환경을 풀기 위해서는 은닉 층 몇 개로 해결할 수 있다.
tf.random.set_seed(42)
input_shape = [4]
n_outputs = 2
model = tf.keras.Sequential([
tf.keras.layers.Dense(32, activation='elu', input_shape=input_shape),
tf.keras.layers.Dense(32, activation='elu'),
tf.keras.layers.Dense(n_outputs)
])
이 DQN으로 행동을 선택하려면 예측 Q-가치가 가장 큰 행동을 선택한다. 에이전트가 환경을 탐험하도록 만들기 위해 입실론-그리디 정책을 사용한다. (확률 입실론 만큼 랜덤한 행동을 선택)
def epsilon_greedy_policy(state, epsilon=0):
if np.random.rand() < epsilon:
return np.random.randint(n_outputs) # 랜덤 행동
else:
Q_values = model.predict(state[np.newaxis], verbose=0)[0]
return Q_values.argmax() # DQN에 따른 최적의 행동
최근의 경험에만 의지하여 DQN을 훈련하는 대신 재생 버퍼 또는 재생 메모리에 모든 경험을 저장하고 훈련 반복마다 여기에서 랜덤한 훈련 배치를 샘플링할 수 있다. 이렇게 하면 경험과 훈련 배치 사이의 상관관계가 줄어들어 훈련에 큰 도움이 된다.
from collections import deque
replay_buffer = deque(maxlen=2000)
※ deque (덱)은 양쪽 끝에서 원소를 효율적으로 추가하거나 제거할 수 있는 큐이다. 큐의 끝에서 항목을 삽입하고 삭제하는 작업은 매우 빠르지만 큐가 길어지면 랜덤 액세스가 느려질 수 있다. 매우 큰 재생 버퍼가 필요하다면 순환 버퍼나 딥마인드의 Reverb 라이브러리를 사용하는 게 좋다.
각 경험은 원소 6개로 구성된다.
- 상태(s)
- 에이전트가 선택한 행동(a)
- 결과 보상(r)
- 도달한 다음 상태 (s')
- 에피소드가 이때 종료되었는지 여부 (done, 불리언값)
- 에피소드가 중단되었는지 여부 (truncated, 불리언값)
재생 버퍼에서 경험을 랜덤하게 샘플링하기 위해 함수를 만든다. 이 함수는 경험 원소 6개에 상응하는 넘파이 배열 6개를 반환한다.
def sample_experiences(batch_size):
indices = np.random.randint(len(replay_buffer), size=batch_size)
batch = [replay_buffer[index] for index in indices]
return [
np.array([experience[field_index] for experience in batch])
for field_index in range(6)
] # [상태, 행동, 보상, 다음 상태, 종료 여부, 중지 여부]
입실론-그리디 정책을 사용해 하나의 스텝을 플레이하고 반한된 경험을 재생 버퍼에 저장하는 함수를 만든다.
def play_one_step(env, state, epsilon):
action = epsilon_greedy_policy(state, epsilon)
next_state, reward, done, truncated, info = env.step(action)
replay_buffer.append((state, action, reward, next_state, done, truncated))
return next_state, reward, done, truncated, info
재생 버퍼에서 경험 배치를 샘플링하고 이 배치에서 경사 하강법 한 스텝을 수행하여 DQN을 훈련하는 함수를 만든다.
batch_size = 32
discount_factor = 0.95
optimizer = tf.keras.optimizers.Nadam(learning_rate=0.01)
loss_fn = tf.keras.losses.mean_squared_error
def training_step(batch_size):
experiences = sample_experiences(batch_size)
states, actions, rewards, next_states, dones, truncateds = experiences
next_Q_values = model.predict(next_states, verbose=0)
max_next_Q_values = next_Q_values.max(axis=1)
runs = 1.0 - (dones | truncateds) # 에피소드가 중지되거나 종료되지 않음
target_Q_values = rewards + runs * discount_factor * max_next_Q_values
target_Q_values = target_Q_values.reshape(-1, 1)
mask = tf.one_hot(actions, n_outputs)
with tf.GradientTape() as tape:
all_Q_values = model(states)
Q_values = tf.reduce_sum(all_Q_values * mask, axis=1, keepdims=True)
loss = tf.reduce_mean(loss_fn(target_Q_values, Q_values))
grads = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
[코드 설명]
- batch_size ~ loss_fn: 하이퍼파라미터 몇 개를 정의하고 옵티마이저와 손실 함수 생성
- def training_step ~ target_Q_values = target_Q_values.reshape(-1, 1); training_step() 함수를 만든다. 이 함수는 경험 배치를 샘플링한 다음 DQN을 사용하여 각 경험의 다음 상태에서 가능한 모든 행동에 대한 Q-가치를 예측한다. 에이전트가 최적으로 플레이한다고 가정하므로 다음 상태에 대한 최대 Q-가치만 저장한다. 그다음 각 경험의 상태-행동 쌍에 대한 타깃 Q-가치를 계산한다.
- mask ~ keepdims=True): DQN이 경험한 각 상태-행동 쌍의 Q-가치를 계산하길 원하나, 이 DQN은 에이전트가 실제로 선택한 행동뿐만 아니라 다른 가능한 행동에 대한 Q-가치도 출력한다. 따라서 필요하지 않은 모든 Q-가치를 마스크 처리 해야 한다. tf.one_hot() 함수는 행동 인덱스의 배열을 마스크로 변환해 준다. 이 마스크를 DQN의 출력과 곱하여 원하지 않은 Q-가치를 0으로 만들 수 있다. 그다음 0을 없애기 위해 열(axis=1) 방향으로 덧셈하여 경험된 상태-행동 쌍의 Q-가치만 남긴다. 결국 배치에 있는 각 경험에 대해 예측된 Q-가치 하나를 담은 텐서인 Q_values 를 얻는다.
- loss: 손실을 계산한다. 손실은 경험된 상태-행동 쌍에 대한 타깃과 예측된 Q-가치 사이의 평균 제곱 오차이다.
- grads ~ optimizer.apply_gradients(zip(grads, model.trainable_variables)): 모델의 훈련 가능한 변수에 관한 손실을 최소화하기 위해 경사 하강법 수행.
모델 훈련을 해본다.
for episode in range(600):
obs, info = env.reset()
for step in range(200):
epsilon = max(1 - episode / 500, 0.01)
obs, reward, done, truncated, info = play_one_step(env, obs, epsilon)
if done or truncated:
break
if episode > 50:
training_step(batch_size)
▶ 최대 스텝 200번으로 이루어진 에피소드 600개를 실행한다.
각 스텝에서 먼저 입실론-그리디 정책에 대한 epsilon 값을 계산하는데, 이 값은 500 에피소드 직전까지 1에서 0.01로 선형적으로 줄어든다.
그다음 play_one_step() 함수를 호출한다. 이 함수는 입실론-그리디 정책을 사용해 행동을 선택하여 실행하고 그 경험을 재생 버퍼에 기록한다. 에피소드가 중단되거나 종료되면 반복을 끝낸다.
50 에피소드 이후에는 training_step() 함수를 호출해 재생 버퍼에서 샘플링한 배치로 모델을 훈련한다. 훈련 없이 에피소드를 50번 플레이하는 이유는 재생 버퍼가 채워질 시간을 주기 위해서이며, 충분한 시간을 주지 않으면 재생 버퍼에 다양성이 부족해진다.
심층 Q-러닝 변형
◆ 고정 Q-가치 타깃
기본 심층 Q-러닝 알골리즘에서 모델은 예측을 만들고 타깃을 설정하는 데 모두 사용된다.이런 피드백 순환 과정은 네트워크를 불안하게 만들어 발산, 진동, 동결 등의 문제가 생긴다. 이 문제를 해결하기 위해 딥마인드 연구자들은 두 개의 DQN을 사용했는데,
- 첫 번째 DQN: 온라인 모델 - 각 스텝에서 학습하고 에이전트를 움직이는 데 사용
- 두 번재 DQN: 타깃 모델 - 타깃을 정의하기 위해서만 사용 (온라인 모델의 단순한 복사본)
target = tf.keras.models.clone_model(model) # 모델 구조 복사
target.set_weights(model.get_weights()) # 가중치 복사
그 다음 training_step() 함수에서 다음 상태의 Q-가치를 계산할 때 온라인 모델 대신 타깃 모델을 사용하도록 한 줄을 바꿔야 한다.
next_Q_values = target.predict(next_states, verbose=0)
훈련 반복에서 일정한 간격으로 온라인 모델의 가중치를 타깃 모델로 복사해야 한다.
if episode > 50:
training_step(batch_size)
if episode % 50 == 0:
target.set_weights(model.get_weights())
고정 Q-가치 타깃의 각 에피소드에서 에이전트가 얻은 총 보상을 학습 곡선으로 시각화해본다.
plt.figure(figsize=(8, 4))
plt.plot(rewards)
plt.xlabel("에피소드", fontsize=14)
plt.ylabel("보상의 합", fontsize=14)
plt.grid(True)
plt.show()
타깃 모델은 온라인 모델보다 자주 업데이트되지 않으므로 Q-가치 타깃이 더 안정적이며 피드백 반복을 완화하고 이에 대한 영향이 감소된다.
◆ 더블 DQN
2015년 딥마인드 연구자들은 DQN 알고리즘을 개선하여 성능과 훈련의 안정성을 향상시켰다. 이 개선된 알고리즘을 더블 DQN이라 부른다. 기존의 고정 Q-가치 타깃과 달리 다음 상태에서 최선의 행동을 선택할 때 타깃 모델 대신 온라인 모델을 사용하도록 제안했다. 타깃 모델은 최선의 행동에 대한 Q-가치를 추정할 때만 사용된다.
아래는 수정된 training_step() 함수이다.
def training_step(batch_size):
experiences = sample_experiences(batch_size)
states, actions, rewards, next_states, dones, truncateds = experiences
### 변경된 부분###
next_Q_values = model.predict(next_states, verbose=0) # ≠ target.predict()
best_next_actions = next_Q_values.argmax(axis=1)
next_mask = tf.one_hot(best_next_actions, n_outputs).numpy()
max_next_Q_values = (target.predict(next_states, verbose=0) * next_mask
).sum(axis=1)
### 동일한 부분 ###
runs = 1.0 - (dones | truncateds) # 에피소드가 중지되거나 종료되지 않음
target_Q_values = rewards + runs * discount_factor * max_next_Q_values
target_Q_values = target_Q_values.reshape(-1, 1)
mask = tf.one_hot(actions, n_outputs)
with tf.GradientTape() as tape:
all_Q_values = model(states)
Q_values = tf.reduce_sum(all_Q_values * mask, axis=1, keepdims=True)
loss = tf.reduce_mean(loss_fn(target_Q_values, Q_values))
grads = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
replay_buffer = deque(maxlen=2000)
더블 DQN 알고리즘의 학습 곡선도 그려본다.
◆ 듀얼링 DQN
듀얼링 DQN(DDQN)에서는 모델이 상태의 가치와 가능한 각 행동의 이익을 모두 추정한다. 최선의 행동은 이익이 0이기 때문에 모델이 예측한 모든 이익에서 모든 최대 이익을 뺀다.
다음은 함수형 API로 구현한 간단한 듀얼링 DQN 모델이다.
# 듀얼링 DQN
input_state = tf.keras.layers.Input(shape=[4])
hidden1 = tf.keras.layers.Dense(32, activation='elu')(input_states)
hidden2 = tf.keras.layers.Dense(32, activation='elu')(hidden1)
state_values = tf.keras.layers.Dense(1)(hidden2)
raw_advantages = tf.keras.layers.Dense(n_outputs)(hidden2)
advantages = raw_advantages - tf.reduce_max(raw_advantages, axis=1, keepdims=True)
Q_values = state_values + advantages
model = tf.keras.Model(inputs=[input_states], outputs=[Q_values])
이 알고리즘도 300 에피소드를 실행하여 훈련해보고 학습 곡선을 그려본다.
심층 강화 학습은 빠르게 발전하고 있으며 다양해지고 있다.
AI 와 딥러닝의 다음 단계··· 개발자를 위한 '심층 강화 학습' 가이드
시행착오(trial-and-error) 방식으로 알고리즘 보상 함수를 극대화하는 강화 학습은, 이 밖에도 IT 운영 관리, 에너지, 헬스케어, 상업, 금융, 교통 등의 분야에서 적응적 통제(adaptive-control) 및 멀티에
www.cio.com
다음 내용
[출처]
핸즈 온 머신러닝
www.cio.com
'[파이썬 Projects] > <파이썬 딥러닝, 신경망>' 카테고리의 다른 글
[딥러닝] RNN 자연어 처리: 어텐션 매커니즘, 트랜스포머 (0) | 2024.12.06 |
---|---|
[딥러닝] RNN을 사용한 자연어 처리: 신경망 기계 번역 (2) | 2024.12.05 |
[딥러닝] RNN을 사용한 자연어 처리: 감성분석 (0) | 2024.12.05 |
[문제 해결] 주피터노트북에 GPU 연결하기 (2) | 2024.12.04 |
[딥러닝] 강화 학습(Reinforcement Learning) - 1 (4) | 2024.12.04 |