[PCCP 기출문제] 2번 / 퍼즐 게임 챌린지

[PCCP 기출문제] 2번 / 퍼즐 게임 챌린지

문제 설명

당신은 순서대로 n개의 퍼즐을 제한 시간 내에 풀어야 하는 퍼즐 게임을 하고 있습니다. 각 퍼즐은 난이도와 소요 시간이 정해져 있습니다. 당신의 숙련도에 따라 퍼즐을 풀 때 틀리는 횟수가 바뀌게 됩니다. 현재 퍼즐의 난이도를 diff, 현재 퍼즐의 소요 시간을 time_cur, 이전 퍼즐의 소요 시간을 time_prev, 당신의 숙련도를 level이라 하면, 게임은 다음과 같이 진행됩니다.

  • difflevel이면 퍼즐을 틀리지 않고 time_cur만큼의 시간을 사용하여 해결합니다.
  • diff > level이면, 퍼즐을 총 diff - level번 틀립니다. 퍼즐을 틀릴 때마다, time_cur만큼의 시간을 사용하며, 추가로 time_prev만큼의 시간을 사용해 이전 퍼즐을 다시 풀고 와야 합니다. 이전 퍼즐을 다시 풀 때는 이전 퍼즐의 난이도에 상관없이 틀리지 않습니다. diff - level번 틀린 이후에 다시 퍼즐을 풀면 time_cur만큼의 시간을 사용하여 퍼즐을 해결합니다.

예를 들어 diff = 3, time_cur = 2, time_prev = 4인 경우, level에 따라 퍼즐을 푸는데 걸리는 시간은 다음과 같습니다.

  • level = 1이면, 퍼즐을 3 - 1 = 2번 틀립니다. 한 번 틀릴 때마다 2 + 4 = 6의 시간을 사용하고, 다시 퍼즐을 푸는 데 2의 시간을 사용하므로 총 6 × 2 + 2 = 14의 시간을 사용하게 됩니다.
  • level = 2이면, 퍼즐을 3 - 2 = 1번 틀리므로, 6 + 2 = 8의 시간을 사용하게 됩니다.
  • level ≥ 3이면 퍼즐을 틀리지 않으며, 2의 시간을 사용하게 됩니다.

퍼즐 게임에는 전체 제한 시간 limit가 정해져 있습니다. 제한 시간 내에 퍼즐을 모두 해결하기 위한 숙련도의 최솟값을 구하려고 합니다. 난이도, 소요 시간은 모두 양의 정수며, 숙련도도 양의 정수여야 합니다.

퍼즐의 난이도를 순서대로 담은 1차원 정수 배열 diffs, 퍼즐의 소요 시간을 순서대로 담은 1차원 정수 배열 times, 전체 제한 시간 limit이 매개변수로 주어집니다. 제한 시간 내에 퍼즐을 모두 해결하기 위한 숙련도의 최솟값을 정수로 return 하도록 solution 함수를 완성해 주세요.


제한사항
  • 1 ≤ diffs의 길이 = times의 길이 = n ≤ 300,000
    • diffs[i]i번째 퍼즐의 난이도, times[i]i번째 퍼즐의 소요 시간입니다.
    • diffs[0] = 1
    • 1 ≤ diffs[i] ≤ 100,000
    • 1 ≤ times[i] ≤ 10,000
  • 1 ≤ limit ≤ 1015
    • 제한 시간 내에 퍼즐을 모두 해결할 수 있는 경우만 입력으로 주어집니다.

입출력 예
diffs times limit result
[1, 5, 3] [2, 4, 7] 30 3
[1, 4, 4, 2] [6, 3, 8, 2] 59 2
[1, 328, 467, 209, 54] [2, 7, 1, 4, 3] 1723 294
[1, 99999, 100000, 99995] [9999, 9001, 9999, 9001] 3456789012 39354

입출력 예 설명

입출력 예 #1

숙련도가 3인 경우 다음과 같이 진행됩니다.

  • 1번째 퍼즐을 2의 시간을 사용하여 해결합니다.
  • 2번째 퍼즐을 5 - 3 = 2번 틀려서 총 (4 + 2) × 2 + 4 = 16의 시간을 사용하여 해결합니다.
  • 3번째 퍼즐을 7의 시간을 사용하여 해결합니다.

총 2 + 16 + 7 = 25의 시간을 사용하여 모든 퍼즐을 해결할 수 있습니다. 숙련도가 3보다 작은 경우 제한 시간인 30 이내에 모든 퍼즐을 해결할 수 없습니다.

따라서 3을 return 해야 합니다.

입출력 예 #2

숙련도가 2인 경우 다음과 같이 진행됩니다.

  • 1번째 퍼즐을 6의 시간을 사용하여 해결합니다.
  • 2번째 퍼즐을 4 - 2 = 2번 틀려서 총 (3 + 6) × 2 + 3 = 21의 시간을 사용하여 해결합니다.
  • 3번째 퍼즐을 4 - 2 = 2번 틀려서 총 (8 + 3) × 2 + 8 = 30의 시간을 사용하여 해결합니다.
  • 4번째 퍼즐을 2의 시간을 사용하여 해결합니다.

총 6 + 21 + 30 + 2 = 59의 시간을 사용하여 모든 퍼즐을 해결할 수 있습니다. 숙련도가 2보다 작은 경우 제한 시간인 59 이내에 모든 퍼즐을 해결할 수 없습니다.

따라서 2를 return 해야 합니다.

입출력 예 #3

숙련도가 294인 경우 다음과 같이 진행됩니다.

  • 1번째 퍼즐을 2의 시간을 사용하여 해결합니다.
  • 2번째 퍼즐을 328 - 294 = 34번 틀려서 총 (7 + 2) × 34 + 7 = 313의 시간을 사용하여 해결합니다.
  • 3번째 퍼즐을 467 - 294 = 173번 틀려서 총 (1 + 7) × 173 + 1 = 1385의 시간을 사용하여 해결합니다.
  • 4번째 퍼즐을 4의 시간을 사용하여 해결합니다.
  • 5번째 퍼즐을 3의 시간을 사용하여 해결합니다.

총 2 + 313 + 1385 + 4 + 3 = 1707의 시간을 사용하여 모든 퍼즐을 해결할 수 있습니다. 숙련도가 294보다 작은 경우 제한 시간인 1723 이내에 모든 퍼즐을 해결할 수 없습니다.

따라서 294를 return 해야 합니다.

입출력 예 #4

숙련도가 39354인 경우 다음과 같이 진행됩니다.

  • 1번째 퍼즐을 9999의 시간을 사용하여 해결합니다.
  • 2번째 퍼즐을 99999 - 39354 = 60645번 틀려서 총 (9001 + 9999) × 60645 + 9001 = 1152264001의 시간을 사용하여 해결합니다.
  • 3번째 퍼즐을 100000 - 39354 = 60646번 틀려서 총 (9999 + 9001) × 60646 + 9999 = 1152283999의 시간을 사용하여 해결합니다.
  • 4번째 퍼즐을 99995 - 39354 = 60641번 틀려서 총 (9001 + 9999) × 60641 + 9001 = 1152188001의 시간을 사용하여 해결합니다.

총 9999 + 1152264001 + 1152283999 + 1152188001 = 3456746000의 시간을 사용하여 모든 퍼즐을 해결할 수 있습니다. 숙련도가 39354보다 작은 경우 제한 시간인 3456789012 이내에 모든 퍼즐을 해결할 수 없습니다.

따라서 39354를 return 해야 합니다.

풀이과정

이 문제에서는 어떻게 적절한 최소한의 level은 찾는 방법을 구하는 것과

주어진 level에 따라서 시간이 얼마나 걸리는지를 구하는 방법을 구하는 것이 핵심이었다.

특히 주어진 diffs중에서 0번째는 항상 난이도가 1이므로 이를 중심으로 풀어나가야했다.


diff에서 level을 뺀 후에 조심해야할 것은 음수가 time이 음수가 되지 않게 도와주는 것이다.
문제에서는 level이 diff보다 같거나 클 때, time_cur만 소요된다 하였으므로 max를 이용하여 해당 항을 조절할 수 있도록 했다.

1
2
3
4
5
def time_to_solve(level, diff, time_prev, time_cur):

t = (max(0,(diff-level))) * (time_prev+time_cur) + time_cur

return t

주어진 레벨에 따라서 총 소요시간을 구하는 방법.
언제나 첫번째 diff는 time_cur만 소요하므로
time_prev를 0으로 맞춰주고 그 이외에는 times[i-1]를 이용하여
list index out이 일어나지 않도록 해주며 total 소요시간을 구해준다.

1
2
3
4
5
6
7
8
9
10
def get_total_t(level, diffs, times):
total_t = 0
for i in range(len(diffs)):
if not i:
time_prev = 0
else:
time_prev = times[i-1]
t = time_to_solve(level, diffs[i], time_prev, times[i])
total_t += t
return total_t

중요한 것은 최소한의 level을 구하는 것. level이 무작정 높아도 문제는 해결이 되기에
이 문제를 해결할 수 있는 모든 level들을 찾아 그 중 최소값을 구하면 되겠다고 생각했다.
결국 Binary Search를 이용하여 모든 만족하는 level들을 찾아 그 중 최솟값을 answer로.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def solution(diffs, times, limit):
start = 1
end = 100000
candidates = []
# Binary Search 시작
while start <= end:
mid = (start+end)//2
total_t = get_total_t(mid, diffs, times)
if total_t > limit:
start = mid + 1
else:
candidates.append(mid)
end = mid - 1
print('level, total_t, limit', mid, total_t, limit)
# 조건을 만족하는 많은 후보들 중에서 minimum을 answer로 내놓는다.
minimum_level = min(candidates)
answer = minimum_level
return answer

Share 0 Comments

[카카오톡 블라인드 채용] 오픈채팅방

[카카오톡 블라인드 채용] 오픈채팅방

문제 설명

오픈채팅방
카카오톡 오픈채팅방에서는 친구가 아닌 사람들과 대화를 할 수 있는데, 본래 닉네임이 아닌 가상의 닉네임을 사용하여 채팅방에 들어갈 수 있다.

신입사원인 김크루는 카카오톡 오픈 채팅방을 개설한 사람을 위해, 다양한 사람들이 들어오고, 나가는 것을 지켜볼 수 있는 관리자창을 만들기로 했다.

채팅방에 누군가 들어오면 다음 메시지가 출력된다.

“[닉네임]님이 들어왔습니다.”

채팅방에서 누군가 나가면 다음 메시지가 출력된다.

“[닉네임]님이 나갔습니다.”

채팅방에서 닉네임을 변경하는 방법은 다음과 같이 두 가지이다.

채팅방을 나간 후, 새로운 닉네임으로 다시 들어간다.

채팅방에서 닉네임을 변경한다.

닉네임을 변경할 때는 기존에 채팅방에 출력되어 있던 메시지의 닉네임도 전부 변경된다.

예를 들어, 채팅방에 “Muzi”와 “Prodo”라는 닉네임을 사용하는 사람이 순서대로 들어오면 채팅방에는 다음과 같이 메시지가 출력된다.

“Muzi님이 들어왔습니다.”
“Prodo님이 들어왔습니다.”

채팅방에 있던 사람이 나가면 채팅방에는 다음과 같이 메시지가 남는다.

“Muzi님이 들어왔습니다.”
“Prodo님이 들어왔습니다.”
“Muzi님이 나갔습니다.”

Muzi가 나간후 다시 들어올 때, Prodo 라는 닉네임으로 들어올 경우 기존에 채팅방에 남아있던 Muzi도 Prodo로 다음과 같이 변경된다.

“Prodo님이 들어왔습니다.”
“Prodo님이 들어왔습니다.”
“Prodo님이 나갔습니다.”
“Prodo님이 들어왔습니다.”

채팅방은 중복 닉네임을 허용하기 때문에, 현재 채팅방에는 Prodo라는 닉네임을 사용하는 사람이 두 명이 있다. 이제, 채팅방에 두 번째로 들어왔던 Prodo가 Ryan으로 닉네임을 변경하면 채팅방 메시지는 다음과 같이 변경된다.

“Prodo님이 들어왔습니다.”
“Ryan님이 들어왔습니다.”
“Prodo님이 나갔습니다.”
“Prodo님이 들어왔습니다.”

채팅방에 들어오고 나가거나, 닉네임을 변경한 기록이 담긴 문자열 배열 record가 매개변수로 주어질 때, 모든 기록이 처리된 후, 최종적으로 방을 개설한 사람이 보게 되는 메시지를 문자열 배열 형태로 return 하도록 solution 함수를 완성하라.

제한사항
record는 다음과 같은 문자열이 담긴 배열이며, 길이는 1 이상 100,000 이하이다.

다음은 record에 담긴 문자열에 대한 설명이다.

모든 유저는 [유저 아이디]로 구분한다.

[유저 아이디] 사용자가 [닉네임]으로 채팅방에 입장 - “Enter [유저 아이디] [닉네임]” (ex. “Enter uid1234 Muzi”)

[유저 아이디] 사용자가 채팅방에서 퇴장 - “Leave [유저 아이디]” (ex. “Leave uid1234”)

[유저 아이디] 사용자가 닉네임을 [닉네임]으로 변경 - “Change [유저 아이디] [닉네임]” (ex. “Change uid1234 Muzi”)

첫 단어는 Enter, Leave, Change 중 하나이다.

각 단어는 공백으로 구분되어 있으며, 알파벳 대문자, 소문자, 숫자로만 이루어져있다.

유저 아이디와 닉네임은 알파벳 대문자, 소문자를 구별한다.

유저 아이디와 닉네임의 길이는 1 이상 10 이하이다.

채팅방에서 나간 유저가 닉네임을 변경하는 등 잘못 된 입력은 주어지지 않는다.

입출력 예
|record|result|
|——|——|
|[“Enter uid1234 Muzi”, “Enter uid4567 Prodo”,”Leave uid1234”,”Enter uid1234 Prodo”,”Change uid4567 Ryan”]|[“Prodo님이 들어왔습니다.”, “Ryan님이 들어왔습니다.”, “Prodo님이 나갔습니다.”, “Prodo님이 들어왔습니다.”]|

입출력 예 설명

입출력 예 #1
문제의 설명과 같다.

풀이과정 1

사실 문제를 처음 봤을 때에는 constructor를 사용해서 계속해서 업데이트를 해주면 좋겠다고 생각을 했다.

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
48
49
50
51
52
53
54
55
56
57
58
# User constructor 생성
class User:
def __init__(self, status, uid, nick):
self.status = status
self.uid = uid
self.nick = nick

def setStatus(self, new_status):
self.status = new_status

def setNick(self, new_nick):
self.nick = new_nick

def solution(record):
users = []
number = 0
for m in record:
# message의 시작 단어에 따라 다르게 알고리즘을 짜줌.
if m.startswith("Enter"):
s, u, n = m.split()
if users:
t = [user for user in users if user.uid == u]
if not t:
c = User(s, u, n)
users.append(c)
else:
t[0].setStatus("Enter")
t[0].setNick(n)
else:
c = User(s, u, n)
users.append(c)
elif m.startswith("Leave"):
s, u = m.split()
t = [user for user in users if user.uid ==u]
if t:
t[0].setStatus("Leave")
else:
s, u, n = m.split()
t = [user for user in users if user.uid == u]
if t:
t[0].setNick(n)
# 모든 메세지를 통해 유저들을 최신화 시킨 뒤에 output answer생성
answer = []
for m in record:
if m.startswith("Enter"):
s, u, n = m.split()
t = [user for user in users if user.uid == u]
nn = t[0].nick
output = "{}님이 들어왔습니다.".format(nn)
answer.append(output)
elif m.startswith("Leave"):
s, u = m.split()
t = [user for user in users if user.uid == u]
nn = t[0].nick
output = "{}님이 나갔습니다.".format(nn)
answer.append(output)

return answer

이면.. 풀릴 줄 알았는데 25번 문제부터 timeout이 걸리면서 멘붕..

어떻게 해야 풀릴 수 있을까 생각하다가 dictionary를 사용하기로 마음을 먹었다.

더 가볍게 풀릴 수 있을 것 같았다.

풀이과정 2

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
def solution(record):
users = []
number = 0
for m in record:
if m.startswith("Enter"):
s, u, n = m.split()
if users:
t = [user for user in users if user['uid'] == u]
if not t:
c = {'status':s, 'uid':u, 'nick':n}
users.append(c)
else:
t[0]['status'] = s
t[0]['nick'] = n
else:
c = {'status':s, 'uid':u, 'nick':n}
users.append(c)
elif m.startswith("Leave"):
s, u = m.split()
t = [user for user in users if user['uid'] == u]
if t:
t[0]['status'] = "Leave"
else:
s, u, n = m.split()
t = [user for user in users if user['uid'] == u]
if t:
t[0]['nick'] = n
answer = []
for m in record:
if m.startswith("Enter"):
s, u, n = m.split()
t = [user for user in users if user['uid'] == u]
nn = t[0]['nick']
output = "{}님이 들어왔습니다.".format(nn)
answer.append(output)
elif m.startswith("Leave"):
s, u = m.split()
t = [user for user in users if user['uid'] == u]
nn = t[0]['nick']
output = "{}님이 나갔습니다.".format(nn)
answer.append(output)

return answer

그런데… 이것도 안 풀린다!! 도대체 25번 문제에 얼마나 많은 레코드들이 들어있는 것인지?

어떻게 해야 더 빨리 할 수 있지??

풀이과정 3

그래서 결국 참고하게 된 다른 분의 코드를 약간 수정한 버전..

결국 ‘마지막’에 어떻게 nick이 남는지가 중요했고
그냥 하나의 dictionary에다가 dict[uid] = nickname로 형태를 정하고
Enter와 Change일 때마다 계속해서 덮어씌우면 되는 것이었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def solution(record):
dic = {}
answer = []
record = [i.split(' ') for i in record]

for i in record:
if i[0] == "Change" and i[1] in dic:
dic[i[1]] = i[2]
elif i[0] == "Enter":
dic[i[1]] = i[2]

for j in record:
if j[0] == "Enter":
answer.append("{}님이 들어왔습니다.".format(dic[j[1]]))
elif j[0] == "Leave":
answer.append("{}님이 나갔습니다.".format(dic[j[1]]))

return answer

재밌어보여서 시작했지만 코테에서 시간초과 걸려본건 처음이라 당황

코테 때 파이썬 쓸 때에는 dictionary를 잘 사용해봐야겠다.

Share 0 Comments

[PCCP 기출문제] 1번 / 동영상 재생기

[PCCP 기출문제] 1번 / 동영상 재생기

문제 설명

당신은 동영상 재생기를 만들고 있습니다. 당신의 동영상 재생기는 10초 전으로 이동, 10초 후로 이동, 오프닝 건너뛰기 3가지 기능을 지원합니다. 각 기능이 수행하는 작업은 다음과 같습니다.

  • 10초 전으로 이동: 사용자가 “prev” 명령을 입력할 경우 동영상의 재생 위치를 현재 위치에서 10초 전으로 이동합니다. 현재 위치가 10초 미만인 경우 영상의 처음 위치로 이동합니다. 영상의 처음 위치는 0분 0초입니다.
  • 10초 후로 이동: 사용자가 “next” 명령을 입력할 경우 동영상의 재생 위치를 현재 위치에서 10초 후로 이동합니다. 동영상의 남은 시간이 10초 미만일 경우 영상의 마지막 위치로 이동합니다. 영상의 마지막 위치는 동영상의 길이와 같습니다.
  • 오프닝 건너뛰기: 현재 재생 위치가 오프닝 구간(op_start ≤ 현재 재생 위치 ≤ op_end)인 경우 자동으로 오프닝이 끝나는 위치로 이동합니다.

동영상의 길이를 나타내는 문자열 video_len, 기능이 수행되기 직전의 재생위치를 나타내는 문자열 pos, 오프닝 시작 시각을 나타내는 문자열 op_start, 오프닝이 끝나는 시각을 나타내는 문자열 op_end, 사용자의 입력을 나타내는 1차원 문자열 배열 commands가 매개변수로 주어집니다. 이때 사용자의 입력이 모두 끝난 후 동영상의 위치를 “mm:ss“ 형식으로 return 하도록 solution 함수를 완성해 주세요.


제한사항
  • video_len의 길이 = pos의 길이 = op_start의 길이 = op_end의 길이 = 5
    • video_len, pos, op_start, op_end는 “mm:ss“ 형식으로 mmss초를 나타냅니다.
    • 0 ≤ mm ≤ 59
    • 0 ≤ ss ≤ 59
    • 분, 초가 한 자리일 경우 0을 붙여 두 자리로 나타냅니다.
    • 비디오의 현재 위치 혹은 오프닝이 끝나는 시각이 동영상의 범위 밖인 경우는 주어지지 않습니다.
    • 오프닝이 시작하는 시각은 항상 오프닝이 끝나는 시각보다 전입니다.
  • 1 ≤ commands의 길이 ≤ 100
    • commands의 원소는 “prev” 혹은 “next”입니다.
    • “prev”는 10초 전으로 이동하는 명령입니다.
    • “next”는 10초 후로 이동하는 명령입니다.

입출력 예
video_len pos op_start op_end commands result
“34:33” “13:00” “00:55” “02:55” [“next”, “prev”] 13:00”
“10:55” “00:05” “00:15” “06:55” [“prev”, “next”, “next”] “06:55”
“07:22” “04:05” “00:15” “04:07” [“next”] “04:17”

입출력 예 설명

입출력 예 #1

  • 시작 위치 13분 0초에서 10초 후로 이동하면 13분 10초입니다.
  • 13분 10초에서 10초 전으로 이동하면 13분 0초입니다.
  • 따라서 “13:00”을 return 하면 됩니다.

입출력 예 #2

  • 시작 위치 0분 5초에서 10초 전으로 이동합니다. 현재 위치가 10초 미만이기 때문에 0분 0초로 이동합니다.
  • 0분 0초에서 10초 후로 이동하면 0분 10초입니다.
  • 0분 10초에서 10초 후로 이동하면 0분 20초입니다. 0분 20초는 오프닝 구간이기 때문에 오프닝이 끝나는 위치인 6분 55초로 이동합니다. 따라서 “06:55”를 return 하면 됩니다.

입출력 예 #3

  • 시작 위치 4분 5초는 오프닝 구간이기 때문에 오프닝이 끝나는 위치인 4분 7초로 이동합니다. 4분 7초에서 10초 후로 이동하면 4분 17초입니다. 따라서 “04:17”을 return 하면 됩니다.

풀이과정

사실 이 문제에서 제일 헷갈리는 것은 바로 오프닝 스킵 기능이었다.

특히 입출력 예 3번에서 보여주는 4분 5초이기 때문에 4분 7초로 commands에 관련없이 ‘자동이동’한다는 것이었고 입출력 예 3번을 제대로 읽지 못한 나는 한동안 난리쇼를 펼쳤다는 것.


다른 분들은 mm, ss로 나누어서 비교를 많이 하셨던데
나는 그럴 필요 없이 그냥 시간을 전부 seconds로 바꾸어서 단위 일체화를 시켜두었다.

1
2
3
4
def time_to_sec(time):
mm, ss = map(int, time.split(":"))
secs = mm*60 + ss
return secs

answer를 내기 위해서 초단위가 되어버린 변수를 mm:ss 형태로 고치기 위해 필요한 함수

1
2
3
4
5
def sec_to_time(sec):
ss = sec%60
mm = sec//60
time = "{}:{}".format(str(mm).zfill(2),str(ss).zfill(2))
return time

오프닝을 스킵해야할지 말아야 할지에 대한 조건문

1
2
3
4
5
def check_skip(spos, sos, soe):
if spos >= sos and spos <= soe:
return True
else:
return False

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
def solution(video_len, pos, op_start, op_end, commands):
# 일단 모든 mm:ss화 된 변수들을 초단위로 바꾸어 비교하기 쉽게 만들어준다.
spos= time_to_sec(pos)
stotal = time_to_sec(video_len)
sos = time_to_sec(op_start)
soe = time_to_sec(op_end)

# answer가 될 position은 일단 시작 시간인 현재 pos과 일치하게 initialize
new_spos = spos
# 시작부터 오프닝을 스킵해야 할지 말지 결정해준다.
if check_skip(spos, sos, soe) : new_spos = soe

# 스킵을 했건 안했건 그 다음으로 커맨드를 받아준다.
for c in commands:
# next면 10초를 추가해주고
if c == 'next':
new_spos += 10
# prev면 10초를 감소해준다.
else:
new_spos -= 10
# 다만 영상길이가 -가 될 수는 없으므로 -가 되면 0으로 초기화 해준다.
if new_spos < 0:
new_spos = 0
# 커맨드를 받고 영상길이가 재조정 되었으니 이제 또 다시 오프닝 스킵인지 체크
if check_skip(new_spos, sos, soe) : new_spos = soe
# 마지막으로 영상 재조정 된 길이가 영상 총 길이보다 길면 안되므로 조절해준다.
if new_spos > stotal:
new_spos = stotal
answer = sec_to_time(new_spos)
print(answer)
return answer
Share 0 Comments

M1 맥북 오류 / 크롬 오류 / 빨간 줄 / 빨간 썸네일 해결 방법

M1 맥북 오류 / 크롬 오류 / 빨간 줄 / 빨간 썸네일 해결 방법

갑자기 어느날부터 M1 맥북을 쓰는 나에게

크롬을 키면 이상한 현상이 일어나기 시작했다.

그 현상은 바로 밑과 같이

issue1

issue2

다른 브라우저에서는 잘만 보이던 이미지 / 사이트들이 이상하게 보인다는 것.

일단 간단하게 해결방법을 먼저 소개하겠다.

issue3

크롬에서 상단 오른쪽 맨 끝에 위치하고 있는 점 3개짜리 아이콘을 클릭하고나서

‘고급’탭을 누르고 ‘시스템’ 탭을 누르면 가능한 경우 하드웨어 가속 사용이라는 탭이 적용되어 있을 것이다.
( 위의 에러가 나타난다면)

issue4

위와 같이 하드웨어 가속 사용을 사용하지 않기로 설정을 하고

‘다시 시작’ 버튼을 누르게 된다면

issue5

위와 같이 에러가 없어진 상태로

복구가 될 것이다.

원인 분석을 해보자면 아마도

기존 Intel 코어를 기준으로 만들어진 웹 라이브러리나 코드가

하드웨어 가속(Intel 코어가 아닌 M1 코어를 사용할 때)을 쓸 때 나타나는 오류인 것 같다.

구글에서도 찾아보았는데

이는 random issue(정해지지 않은 정상적이지 않은 버그)에서 발견이 되는 것 같다.

정확한 해결책은 아래의 링크에서 제공해주었다.

Reference

#chrome #크롬 #크롬오류 #빨간줄 #빨간아이콘 #빨간화면 #빨간썸네일 #검은줄 #검은아이콘 #검은화면 #검은썸네일 #M1 #맥북

Share 0 Comments

[DL:scratch] 0 - basic python

Deep Learning from scratch : Plan

  1. Basic Python for Deep Learning
    • Deep Learning을 위한 python을 기본적으로 다루는 방법을 배웁니다.
  2. Perceptron
    • Perceptron을 통해 weight와 bias를 배우고 퍼셉트론의 한계, 그리고 다층 perceptron을 다룹니다.
  3. Neural Networks
    • Activation Function과 vector에 대한 계산을 다루며, Neural Network를 직접 구현해봅니다.
  4. Training Neural Networks
    • Neural Network를 학습시키기 위해 필요한 loss function과 간단한 미분/편미분에 대한 설명, 그리고 training algorithm을 구현합니다.
  5. Backpropagation
    • Neural Network를 Training 시키는 방법인 backpropagation에 대해 좀 더 심층적으로 다루고 이를 구현합니다.
  6. Training methods
    • Neural Network를 보다 잘 훈련시키기 위해 사용되어지는 여러가지 훈련방법을 소개하고 적용해봅니다.
  7. Type of Neural Networks
    • 다양한 Neural Network를 살펴보고 어떻게 적용되는지, 어떤 분야에 사용하는지에 대해 알아봅니다.
  8. Deep Learning
    • 여러가지 예제를 통해 직접 Deep Learning 모델을 만들어보고 이를 평가해봅니다.

Basic python for Deep Learning

Deep Learning을 직접 다루어보기 위해 쓰는 programming language는 다양한 것이 있지만, 본 블로그에서는 python을 다루기로 합니다.

Colab

python을 직접 실행하기 위해서도 다양한 방법이 있지만, google에서 지원하는 Colab을 사용하여 브라우저에서 손쉽게 실행할 수 있는 방법을 쓰기로 하겠습니다.

Colab

‘새 노트’를 눌러서 python을 실행시킬 수 있는 창을 띄운 뒤에 단순한 계산을 실행시켜보겠습니다.

simple_sum

1+2를 입력하고 왼쪽의 재생버튼을 누르시면, 3이라는 결과가 하단에 뜨게 됩니다.
화면에서 보시는 것처럼, 1+2를 입력하는 상자안에 입력하는 것을 ‘코드’라 하며, 한줄 뿐만 아니라 여러줄을 입력하여 한번에 실행시킬 수 있습니다.

이제부터는 화면으로 보여드리는 것보다 상자안에 입력할 것을 다음과 같은 포맷으로 보여드릴 예정입니다.

1
2
3
4
1+2
3*8
15/2
5 ** 4

위의 코드를 입력해보시고 결과를 확인해보면 다음과 같은 결과가 나오게 됩니다.

simple_calculations

625는 맨 마지막줄 계산의 결과입니다.
저희가 상단에 써놓았던 모든 수식의 결과를 보려면, 각각 계산의 결과를 ‘변수’라는 것에 저장한 뒤에, 확인하는 작업이 필요합니다. 다음과 같이 코드를 작성하여 봅시다.

1
2
3
4
5
6
7
8
9
a = 1+2
b = 3*8
c = 15/2
d = 5 ** 4

print(a)
print(b)
print(c)
print(d)

그렇다면 다음과 같은 결과가 나오게 됩니다.

using_variations

이와같이 프로그래밍은, ‘변수’라는 것을 선언하고 자신이 원하는 ‘작업’을 ‘변수’안에 저장한 뒤, 그 작업의 결과인 ‘변수’를 출력함으로써 원하는 결과를 얻는 것이라고 볼 수 있습니다.

Type of Data

python 안에서 사용할 수 있는 Data의 type에는 다양한 것들이 있습니다.

여러개의 data를 한줄로 줄세워서 저장할 수 있는 List형
여러개의 data를 key, value로 나누어서 원하는 value를 찾기 위해 key라는 것을 사용할 수 있는 dictionary형
참과 거짓을 뜻하는 bool형이 대표적이며, int, float와 같은 숫자 표현형, 그리고 ‘문자열’을 뜻하는 string형이 있습니다.

직접 결과를 확인해보면서 보도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
a = [0, 1, 2, 3, 4 ,5] # this is list
b = {'zero':0, 'one':1, 'two':2} # this is dictionary
c = True # this is bool
d = 10 # this is int
e = 10. # this is float
f = 'hello' # this is string
print(type(a))
print(type(b))
print(type(c))
print(type(d))
print(type(e))
print(type(f))

type(변수)는 변수의 type이 무엇인지를 확인할 수 있는 코드입니다.
위 코드의 결과는 다음과 같이 나옵니다.

1
2
3
4
5
6
<class 'list'>
<class 'dict'>
<class 'bool'>
<class 'int'>
<class 'float'>
<class 'str'>

list와 dictionary형의 내부에 접근하는 방법은 다음과 같습니다.
먼저 list인 a에서 첫번째 요소인 0에 접근하고 싶을 때와
dictionary안에서 ‘one’라는 key를 통해, 1이라는 value에 접근하고 싶을 때를 확인해보겠습니다.

1
2
3
4
5
6
7
8
a = [0, 1, 2, 3, 4 ,5] # this is list
b = {'zero':0, 'one':1, 'two':2} # this is dictionary
c = True # this is bool
d = 10 # this is int
e = 10. # this is float
f = 'hello' # this is string
print(a[0]) # print first element of list a
print(b['one']) # print value of key 'zero'

위의 코드를 통해 0과 1이라는 결과가 출력되는 것을 확인할 수 있습니다.
a와 b의 데이터를 바꿔보시면서 출력을 해보시거나, 대괄호 안에 들어가는 순서(index)나 key(‘one’)를 바꿔보며 익숙해지는 것이 좋습니다.

If else

~한 조건이 ‘참’일때, ~을 실행하고 싶을 때 쓰는 ‘조건문’입니다.
예를 들어, 사용자가 입력한 숫자가 10보다 작으면, 10보다 작다는 문구를 출력하고, 10이거나 10보다 크면 10보다 크다는 문구를 출력하고 싶다는 프로그램을 짜려면 어떻게 해야할까요?

1
2
3
4
5
number = input()
if number < 10:
print(f'{number} is less than 10')
else:
print(f'{number} is equal with 10 or greater than 10')

즉, if 바로 뒤에 오는 조건이 ‘True’일 경우에는 less than 문구를, False일 경우 그 이외의 경우인 ‘else’가 되어 출력되어지게 됩니다.
만약, 사용자가 입력한 숫자가 10이면, 10입니다. 라고 출력을 하고 싶다면 다음과 같이 조건을 걸어주시면 됩니다.

1
2
3
4
5
6
7
number = int(input())
if number < 10:
print(f'{number} is less than 10')
elif number == 10:
print(f'{number} is equal with 10')
else:
print(f'{number} is greater than 10')

Loop

0부터 9까지 숫자를 출력하고 싶으시다면, print를 여러번 써서 해결할 수도 있지만, 일정의 규칙이 있는 경우라면 loop 문을 쓰실 수 있습니다.

1
2
3
4
5
number = 0
for i in range(0,10):
print(i)
print(number)
number+=1

여기에서 i와 number의 차이는, i는 loop를 얼마나 반복할지를 참고하는 변수라는 것입니다.
원래 출력에서는 i와 number가 계속 같이 1씩 증가하는 모습을 보여주었지만, 만약 저 코드에서 number+=1 부분을 없앤다면 어떻게 될까요? i만 증가하고 number는 0에 멈춰있는 것을 볼 수 있습니다.

Function

위의 if else에서 작성한 코드가 있습니다. 사용자가 입력한 변수가 같으면, 크면, 작으면 ~이다를 출력해주는 작업을 반복하게 되었다고 생각해보면, 저 7줄의 코드를 매번 써줘야 할까요? 한줄로 해결할 수는 없을까요? ‘반복’해서 쓰이지만, 특정 ‘변수’만 달라지는 환경이라면 ‘function’기능을 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

def isTen(number):
if number < 10:
print(f'{number} is less than answer')
return False
elif number == 10:
print(f'answer is : {number}')
return True
else:
print(f'{number} is greater than answer')
return False

for i in range(0,5):
number = int(input())
correct = isTen(number)
if correct:
break # end the loop

위의 코드는 사용자가 10을 입력하면 꺼지는 숫자 맞추기 게임입니다.
기회는 총 5번이 주어지며 (loop 5번) 5번 이내에 맞추면 승리하는 게임입니다.

이처럼 반복해서 필요한 기능들은 ‘function’화 하여서 코드를 더 간결하고 정확하게 만들 수 있습니다.

Class

Share 0 Comments

Google Certificate for ML

Google Certificate for ML

구글 Certificate에서 요구하는 능력은 다음과 같습니다.

(합격기준 100점만점 90점 이상)

  1. Basic / Simple model
  2. Model from learning dataset
  3. CNN with real world image dataset
  4. NLP text classification with real world text dataset
  5. Sequence model with real world numeric dataset

시험에 참여하기 위해서는 다음을 만족해야합니다.

  1. ML과 DL의 중요한 개념들을 사용할 줄 알아야함
  2. Tensorflow를 사용해서 ML models를 만들어야함
  3. DNN, CNN을 이용하여 Image Recognition을 할줄 알며, 현실이미지를 다루는 것에 익숙하며 loss, accuracy를 plot하고 overfitting을 방지하는 전략들을 알아야함(augmentation, dropout등)
  4. Image Recognition뿐만 아니라, object detection, text recognition을 사용할 줄 알아야함(RNN(GRU, LSTMS)을 위한 text tokenization 포함)
  5. NLP 문제를 해결하기 위한 neural network를 설계할 줄 알아야함.(using Tensorflow)

시험 환경은 다음과 같습니다.

  1. 파이참 IDE를 사용하여 인터넷이 되는 어디든 본인의 컴퓨터로 시험을 볼 수 있습니다.
  2. 시험은 온라인으로 진행되며, 파이참을 통해 tensorflow를 이용합니다.
  3. 시험은 5시간동안 진행됩니다.
  4. 시험을 치르기 위해 파이참 IDE를 이용하여 Tensorflow Exam Plugin을 설치하게 됩니다. 파이참 IDE를 이용하는 것에 익숙해지기를 추천합니다.
  5. 5시간이 지나기 전에 시험을 완료했다면, 일찍 submit하실 수 있습니다. 만약 5시간이 지나게 되면, 자동적으로 submit이 진행됩니다.
  6. 이 문서를 모두 읽어보셨다면, Exam URL에 방문할 수 있습니다.

참여자 identification and authentication

여권/운전면허증/주민등록증 중 1개 (primary)

시험 관련

  1. 시험을 보는 도중에는 브라우저를 이용하여 Tensorflow Documentation만을 참고 가능합니다.
  2. 반드시 혼자 시험을 치루셔야 합니다.
  3. 시험을 치르는 데에는 100불이 소요됩니다.

재시험 관련

  1. 1번째 시험 낙방 -> 2주가 지나야 시험을 칠 수 있습니다.
  2. 2번째 시험 낙방 -> 2달이 지나야 시험을 칠 수 있습니다.
  3. 3번째 시험 낙방 이후 -> 1년이 지나야 시험을 칠 수 있습니다.

Reference

Share 0 Comments

chatGPT의 놀라운 대화능력에 대한 실험

chatGPT의 놀라운 대화능력에 대한 실험

최근 openAI에서 발표한 chatGPT가 놀라운 대화능력을 보여주고 있다며 올라온 한 커뮤니티의 게시물을 보았다.

​보면 볼수록 놀랍고 이런 통찰력과 지식을 가지고 사람과 이야기 할 수 있다는 것이 너무 놀라웠다.

​해당 커뮤니티의 게시물처럼 나도 이 chatGPT와 서로 대화를 해보려는 시간을 가지게 되었다.


  1. 지금 현대인으로 봤을 때 성경의 많은 부분은 과학적으로 믿기 어려운 일이 일어났음을 보여주지만, 기독교인들은 이것이 모두 사실이라고 믿고있다. 나도 기독교인으로써, 이에 대해 어떻게 생각해야하는 것이 좋은가?

1A) 기독교인으로서 성경은 과학적 문서가 아니며 그렇게 해석되어서는 안 된다는 것을 기억하는 것이 중요하다.
그것은 우리가 하나님과 하나님과의 관계를 이해하는 것을 돕기 위한 이야기와 가르침을 포함하는 종교적인 본문입니다.
성경에 묘사된 사건들 중 일부는 과학적으로 불가능해 보일 수 있지만,
이러한 이야기들을 적절한 맥락에서 해석하고 문자 그대로 받아들이지 않는 것이 중요하다.
기독교인으로서 열린 마음과 마음으로 성경에 접근하고 기도와 공부를 통해 성경의 가르침과 메시지에 대한 더 깊은 이해를 구하는 것이 중요하다.

1-1. 그럼 기독교인이 아닌 사람으로써는?

1-1A) 기독교인이 아닌 사람으로서, 여러분은 성경과 성경의 가르침을 여러분에게 가장 이치에 맞는 어떤 방식으로든 자유롭게 해석할 수 있습니다.
어떤 사람들은 성경을 허구의 작품으로 보는 반면, 다른 사람들은 그것을 지혜와 지침의 원천으로 보는 것을 선택할 수 있다.
궁극적으로 성경을 어떻게 해석하느냐는 당신에게 달려 있으며,
무엇이 당신에게 옳은지, 무엇이 당신 자신의 가치관과 신념과 일치하는지에 근거해야 한다.
비록 다른 사람들의 믿음이 자신의 믿음과 다르더라도 그것을 존중하는 것은 중요하다.


과학력이 모여있는 chatGPT가 과연 종교적인 것과 과학적인 것을 구분 가능하고 무엇에 더 집중을 두는가를 확인하고 싶었다.

또한 각각의 입장에 따라 어떻게 생각해야할지를 구분하며 답변하는 부분도 볼 수 있었다. 놀라운 답변이었다.


Share 0 Comments

[Pytorch]MNIST DNN부터 CNN까지

MNIST DNN부터 CNN까지

Deep Learning을 공부함에 있어서 제일 처음으로 접하는 Data는 바로 MNSIT라고 할 수 있습니다. MNIST는 사람들이 직접 필기로 쓴 숫자로써 0부터 9까지 검은 배경에 하얀 글씨로 쓰여져있는 28 x 28 사이즈의 이미지 데이터입니다. 이 포스팅을 통해서 MNIST 데이터를 Deep Learning을 통해서 숫자들을 구별할 수 있는 모델을 설계하고, DNN을 이용한 모델과 CNN을 이용한 모델을 직접 구현해 볼 것입니다.

MNSIT 살펴보기

MNIST데이터는 다음 그림과 같은 이미지로 구성되어있습니다.
overview_mnist

위에서 설명한 바와 같이 위와 같은 이미지처럼 데이터가 구성되어 있으며, 데이터 하나하나는 다음과 같은 이미지를 구성하고 있습니다.

mnist_sample

Deep Learning 모델이 해야할 일은 input으로써 위와 같은 정보를 받고, 해당 데이터가 0부터 9라는 숫자중에 어떤 숫자인지를 알아맞추는 것입니다.

하지만 이 이미지 파일을 어떻게 Deep learning 모델이 받아들일 수 있도록 할 것인가? 보통 MNIST 데이터는 28 x 28의 숫자를 가진 텐서로 표현이 됩니다. 예를 들어서, 하나의 이미지 파일을 텐서로 표현한 데이터를 출력해보면 다음과 같이 나옵니다.

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
[...
0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0.
0. 0. 0.01171875 0.0703125 0.0703125 0.0703125
0.4921875 0.53125 0.68359375 0.1015625 0.6484375 0.99609375
0.96484375 0.49609375 0. 0. 0. 0.
0. 0. 0. 0. 0. 0.
0. 0. 0.1171875 0.140625 0.3671875 0.6015625
0.6640625 0.98828125 0.98828125 0.98828125 0.98828125 0.98828125
0.87890625 0.671875 0.98828125 0.9453125 0.76171875 0.25
0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0.19140625
0.9296875 0.98828125 0.98828125 0.98828125 0.98828125 0.98828125
0.98828125 0.98828125 0.98828125 0.98046875 0.36328125 0.3203125
0.3203125 0.21875 0.15234375 0. 0. 0.
0. 0. 0. 0. 0. 0.
0. 0. 0. 0.0703125 0.85546875 0.98828125
0.98828125 0.98828125 0.98828125 0.98828125 0.7734375 0.7109375
0.96484375 0.94140625 0. 0. 0. 0.
0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0.
0. 0. 0.3125 0.609375 0.41796875 0.98828125
0.98828125 0.80078125 0.04296875 0. 0.16796875 0.6015]

출력된 값 전부를 표현하기에는 너무 많은 숫자들이기에 약간의 데이터만 포스팅하지만, 간단하게 총 784개의 숫자를 가진 데이터임을 확인할 수 있습니다.(28*28=784)
DNN을 이용할 때에는 784의 길이를 가진 형태로 집어넣을 것이고, CNN을 이용할 때에는 28x28을 지닌 그대로의 형태로 집어넣을 것입니다.

MNIST Practice

Pytorch에서 제공하는 기본 MNIST 예제는 CNN으로 이루어져있지만 MNIST는 간단한 데이터이기 때문에, DNN만으로도 충분히 다룰 수 있습니다. 먼저 전체적인 코드를 큰 파트별로 먼저 크게 살펴보고, 그 다음에 하나하나의 파트들을 Line by Line으로 살펴보도록 하겠습니다.

DNN 모델 설계하기

먼저 Pytorch에서 제공하는 라이브러리를 사용하기 위해 각 라이브러리들을 Import 해주는 작업을 해줍니다.

1
2
3
4
5
6
7
8

#Importing Library

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms

그 다음으로는 DNN을 설계를 할 것인데, 우리가 가진 MNIST데이터는 (1,784)의 데이터 형태를 가지고 있고, 구분하려는 숫자의 종류는 총 10가지라는 것을 생각한 뒤 모델을 설계한다고 생각하면 간단한 DNN은 대략 다음과 같이 구성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

#Define Neural Networks Model.

class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(784, 512)
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, 128)
self.fc4 = nn.Linear(128, 64)
self.fc5 = nn.Linear(64, 32)
self.fc6 = nn.Linear(32, 10)

def forward(self, x):
x = x.float()
h1 = F.relu(self.fc1(x.view(-1, 784)))
h2 = F.relu(self.fc2(h1))
h3 = F.relu(self.fc3(h2))
h4 = F.relu(self.fc4(h3))
h5 = F.relu(self.fc5(h4))
h6 = self.fc6(h5)
return F.log_softmax(h6, dim=1)

print("init model done")

여러층의 Feed Forward Network를 설계해서, input size는 784, output size는 10이 되도록 설정해줍니다.(input data는 (1, 784)의 형태이고 정답(숫자의 종류)는 총 10가지)

이 다음에는 DNN을 Training시키기 위해 필요한 여러가지 변수들 및 hyper parameter들을 설정해줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Set Hyper parameters and other variables to train the model.

batch_size = 64
test_batch_size = 1000
epochs = 10
lr = 0.01
momentum = 0.5
no_cuda = True
seed = 1
log_interval = 200

use_cuda = not no_cuda and torch.cuda.is_available()

torch.manual_seed(seed)

device = torch.device("cuda" if use_cuda else "cpu")

kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}

print("set vars and device done")

어느정도 필요한 변수들을 선언해줬다면, 그 다음으로는 pytorch의 torchvision이 제공하는 MNIST 데이터들과 데이터들을 읽어올 수 있는 Loader들을 선언해줍니다.
그리고 Training을 위한 데이터와 모델의 성능을 평가할 수 있는 Test를 위한 데이터 로더를 선언해줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#Prepare Data Loader for Training and Validation

transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))])

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=True, download=True,
transform=transform),
batch_size = batch_size, shuffle=True, **kwargs)

test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, download=True,
transform=transform),
batch_size=test_batch_size, shuffle=True, **kwargs)

위와 같이 Data Loader들을 선언해주셨다면, 그 다음으로는 위에서 우리가 설계했던 DNN 모델을 불러오고, Training에 필요한 Optimizer를 선언해줍니다.

1
2
model = Net().to(device)
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)

그 다음으로는 모델을 직접 Training시키는 함수과 Test하는 함수를 구현해줍니다.

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
#Define Train function and Test function to validate.

def train(log_interval, model, device, train_loader, optimizer, epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
if batch_idx % log_interval == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))

def test(log_interval, model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += F.nll_loss(output, target, reduction='sum').item()
pred = output.argmax(dim=1, keepdim=True)
correct += pred.eq(target.view_as(pred)).sum().item()

test_loss /= len(test_loader.dataset)

print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format
(test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))

Training Data를 한차례 전부 학습에 사용했다면 그것을 하나의 Epoch이라고 부르는데, 모델을 학습시키는데에 하나의 Epoch보다 더 많은 Epoch이 필요하므로 Train과 Test의 과정을 반복하는 반복문을 선언해줍니다. 이 반복문이 끝나면, 마지막으로 Training된 모델을 저장합니다.

1
2
3
4
5
6
# Train and Test the model and save it.

for epoch in range(1, 11):
train(log_interval, model, device, train_loader, optimizer, epoch)
test(log_interval, model, device, test_loader)
torch.save(model, './model.pt')

여기까지가 Pytorch를 이용해서 DNN으로 MNSIT 데이터들을 분류하는 작업의 코드입니다.

이제는 각 파트별로 소스코드들이 무엇을 의미하는지를 알아보도록 하겠습니다.

DNN Model.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(784, 512)
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, 128)
self.fc4 = nn.Linear(128, 64)
self.fc5 = nn.Linear(64, 32)
self.fc6 = nn.Linear(32, 10)

def forward(self, x):
x = x.float()
h1 = F.relu(self.fc1(x.view(-1, 784)))
h2 = F.relu(self.fc2(h1))
h3 = F.relu(self.fc3(h2))
h4 = F.relu(self.fc4(h3))
h5 = F.relu(self.fc5(h4))
h6 = self.fc6(h5)
return F.log_softmax(h6, dim=1)

위의 DNN 모델은 총 6개의 Linear 레이어를 통해서 학습하게 됩니다.

MNIST data는 간단한 toy data이고, 간단한 데이터이기 때문에 위처럼

단순한 neural networks만으로도 가능합니다.

784 dimension을 가진 MNIST data를 512, 256, 128, … dimension으로

옮겨가며 feature extraction을 할 수 있도록 하며, 각 레이어마다 끝단에는

Relu라는 activation function을 통해 neural network에 nonlinearity를 추가해줍니다.

마지막 단에 Log Softmax를 통해 마지막 레이어를 지난 10개의 값들을 return하는데

Log Softmax의 역할은 마지막 나온 결과값들을 확률로 취급하여 해석하기 위한 하나의 연산입니다.

Hyper Parameters and Variables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
batch_size = 64
test_batch_size = 1000
epochs = 10
lr = 0.01
momentum = 0.5
no_cuda = True
seed = 1
log_interval = 200

use_cuda = not no_cuda and torch.cuda.is_available()

torch.manual_seed(seed)

device = torch.device("cuda" if use_cuda else "cpu")

kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}

print("set vars and device done")

위의 parameter들은 보통 딥러닝 모델을 train할 때, 많이 쓰이는 변수들이며

딥러닝 모델에 큰 영향을 주는 parameter는 hyper parameter라고 합니다.

batch_size란, cpu 혹은 gpu에 한번에 몇개씩의 data를 넣어줄 것인지를 정하는 것입니다.

batch_size의 갯수에 따라서도 딥러닝 모델의 성능이 크게 달라집니다.

epochs같은 경우에는 training data를 1번씩 모두 썼을 때까지를 1 epoch이 지났다고 합니다.

즉, training data가 10개가 있고, 그 data들을 모두 한번씩 training에 썼다면, 1 epoch이 지난 것입니다.

Data Loader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#Prepare Data Loader for Training and Validation

transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))])

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=True, download=True,
transform=transform),
batch_size = batch_size, shuffle=True, **kwargs)

test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, download=True,
transform=transform),
batch_size=test_batch_size, shuffle=True, **kwargs)

transform을 통해서 data를 어떻게 처리해줄지 결정을 해줍니다.

함수를 보면, 이미지 데이터를 .ToTensor()를 통해서 tensor형태로 데이터를 변환해준 뒤

Normalize과정을 해주기 위해서 standard deviation와 variation 값을 직접 입력해줍니다. (모든 MNIST이미지를 통해 미리 구해놓은 값입니다.)

그리고 위의 함수에 따라 training set과 test set을 구분해서 만들어줍니다.

현재 torchvision의 함수 자체가 ‘train’이라는 parameter를 통해 training set과 test set을 쉽게 준비할 수 있도록 설계해놨기 때문에, 이를 그대로 사용하시면 됩니다.

Optimizer

1
2
model = Net().to(device)
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)

Optimizer를 통해서 모델이 어떤 방식으로 training할지를 고르게 됩니다.

대표적인 training방식은 SGD, RMSprop, Adam, AdaDelta 등 여러가지 방식이 있는데

위의 코드는 SGD와 momentum을 사용하는 optimizer를 설정해준 뒤, model내의 parameter들을 training하도록 했습니다.

Train and Test

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
#Define Train function and Test function to validate.

def train(log_interval, model, device, train_loader, optimizer, epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device) # send data to gpu or cpu
optimizer.zero_grad() # set gradient zero.
output = model(data) # get output from model
loss = F.nll_loss(output, target) # calculate nll loss
loss.backward() # do backpropagation
optimizer.step() # update weight and biases
if batch_idx % log_interval == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))

def test(log_interval, model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += F.nll_loss(output, target, reduction='sum').item()
pred = output.argmax(dim=1, keepdim=True)
correct += pred.eq(target.view_as(pred)).sum().item()

test_loss /= len(test_loader.dataset)

print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format
(test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))

train함수를 통해서 train data들을 한번씩 살피도록 합니다.

test함수를 통해서 test data를 통해 모델의 성능을 평가하게 됩니다.

Make machine works

1
2
3
4
5
6
# Train and Test the model and save it.

for epoch in range(1, 11):
train(log_interval, model, device, train_loader, optimizer, epoch)
test(log_interval, model, device, test_loader)
torch.save(model, './model.pt')

train 함수와 test함수를 통해 model을 트레이닝 시키고
모든 epoch이 끝나게 되면 torch.save를 통해 모델을 저장시켜줍니다.

CNN model

CNN 모델은 28x28의 MNIST data를 1x784로 고쳐서 쓰는 것이 아닌, 2d data의 형태 그대로 사용하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 32, 3, 1)
self.conv2 = nn.Conv2d(32, 64, 3, 1)
self.dropout1 = nn.Dropout2d(0.25)
self.dropout2 = nn.Dropout2d(0.5)
self.fc1 = nn.Linear(9216, 128)
self.fc2 = nn.Linear(128, 10)

def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = self.conv2(x)
x = F.relu(x)
x = F.max_pool2d(x, 2)
x = self.dropout1(x)
x = torch.flatten(x, 1)
x = self.fc1(x)
x = F.relu(x)
x = self.dropout2(x)
x = self.fc2(x)
output = F.log_softmax(x, dim=1)
return output

CNN을 이해하기 위해서는 filter size, padding, stride 라는 개념을 알아두셔야 합니다.

Share 0 Comments

알파스타, 실시간 전략 게임 스타크래프트2 정복하기[딥마인드 소식 번역]

AlphaStar: Mastering 실시간 전략 게임 스타크래프트2

본문 LINK
어설프지만 공부겸 번/의역 해가면서 글을 작성했습니다. 모든 오역에 대한 지적은 감사히 받습니다. 도움이 되었으면 합니다.


게임은 수십 년 동안 인공 지능 시스템의 성능을 테스트하고 평가하는 중요한 방법으로 사용되어 왔습니다. 역량(capability)이 향상됨에 따라 연구 커뮤니티는 과학 및 실제 문제를 해결하는 데 필요한 다양한 지능 요소를 포착하는, 복잡성이 증가하는 게임을 찾아 왔습니다. 최근 몇 년 동안, 가장 도전적인 실시간 전략 (RTS) 게임 중 하나로 간주되는 스타크래프트는 가장 긴 시간 동안 행해지는 스포츠 중 하나였으며, 인공 지능 연구를 위한 “큰 도전”이라는 것으로 의견이 일치되었습니다.

Download 11 replays

최고의 프로 선수를 물리 칠 수 있는 최초의 인공지능 스타크래프트2 프로그램 AlphaStar를 소개합니다. 12월 19일에 열린 테스트 매치에서 AlphaStar는 세계 최강의 프로 스타크래프트 플레이어 중 하나인 팀 리퀴드의 Gregorz ‘ManA’ Komincz를 5-0으로 승리를 보이고, 그의 팀 동료 인 Dario ‘TLO’를 상대로도 승리를 거두었습니다. 경기는 프로 매치 경기에서 쓰이는 에서 경기를 하였고 게임에는 그 어떤 제약도 없었습니다.

인공지능 기술이 Atari, Mario, Quake Quake III Arena Capture the Flag, 그리고 Dota2에서는 뛰어난 성공 사례들이 있었지만, 아직까지는 스타크래프트와 같은 복잡성을 지닌 게임에는 좋은 결과를 내지 못했습니다. 가장 좋은 결과들은 시스템의 요소를 직접 제작하거나, 게임 규칙에 큰 제한을 걸거나, 초인적인 능력을 시스템에 주거나, 단순한 맵에서 게임을 진행함으로써 얻은 결과였습니다. 이런 노력에도 불구하고 그 어떤 시스템도 프로 선수들의 기술에 필적할만한 시스템은 거의 없습니다. 하지만, AlphaStar는 deep neural network(supervised learning and reinforcement learning)을 사용하여 게임의 raw data를 학습합니다.

MaNa와의 경기에 대한 기록

스타크래프트가 “큰 도전”인 이유

블리자드가 제작한 스타크래프트2는 공상과학세계를 배경으로 인간의 지성에 도전 할 수 있는 풍부한 멀티 플레이 게임을 지원합니다. 스타크래프트1과 함께, 스타크래프트2는 20년 이상 esports 토너먼트에서 경쟁하는 가장 크고 성공적인 게임중 하나입니다.

alphastar_play

게임을 플레이하는 방법은 여러 가지 방법이 있지만 가장 일반적인 방법은 5개의 게임에서 1v1 토너먼트를 하는 것입니다. 시작하려면 플레이어는 저그, 프로토스 또는 테란이라는 세 가지 “종족” 중 하나를 선택해야합니다. 각각의 종족들은 특색 있는 특성과 능력을 지니고 있습니다(프로 선수는 한 종족을 전문으로 하는 경향이 있지만요). 각 플레이어는 많은 유닛과 구조물을 만들고 새로운 기술을 창출 할 수 있는 기본 자원을 모으는 다수의 일꾼 유닛으로 시작합니다. 이것들은 플레이어가 자원을 수확 할 수 있게 해주며, 보다 정교한 기반과 구조를 만들고, 상대방을 속일 수 있는 새로운 능력을 개발합니다. 게임에서 이기기 위해서는 큰 그림을 그려 정교하게 자원을 관리해야 하며(Macro라 알려진 것), 각각의 유닛을 직접 컨트롤 하는 것(Micro라 알려진 것)을 잘 관리해야 합니다.

장단기 목표의 균형을 맞추고 예상치 못한 상황에 적응해야하는 필요성 때문에 시스템은 종종 불안정하며 융통성이 없기도 합니다. 이 문제를 해결하기 위해서는 다음과 같은 몇 가지 인공 지능 연구 과제에서 혁신이 필요합니다.

  • 게임 이론 : 스타크래프트는 가위바위보처럼 최고의 전략이 없는 게임입니다. 따라서 인공지능 교육 과정은 전략적 지식의 영역을 지속적으로 탐구하고 확장해야합니다.
  • 불완전한 정보 : 플레이어가 모든 것을 볼 수있는 체스나 바둑 같은 게임과는 달리, 중요한 정보는 스타크래프트 플레이어에게 숨겨져 있으며 “정찰”로 직접 발견해야 합니다.
  • 장기 계획 : 많은 실제 문제와 마찬가지로 인과 관계가 즉각적인 것은 아닙니다. 또한 게임을 완료하는 데 최대 1시간이 걸릴 수 있으므로 게임 초기에 수행한 작업이 오랜 기간 동안 성과를 내지 못할 수 있습니다.
  • 실시간 : 선수가 후속 동작을 번갈아 번갈아하는 전통적인 보드 게임과 달리 스타크래프트 플레이어는 게임이 실시간으로 진행됨에 따라 계속해서 작업을 수행해야합니다.
  • 대규모 작업 공간 : 수백 개의 다른 유닛과 건물을 실시간으로 한 번에 제어해야하며 조합 가능성이 생깁니다. 또한 행동은 계층적이며 변경 및 보완 될 수 있습니다. 인공지능은 매 time step마다 10개에서 26가지의 행동을 합니다.

이러한 엄청난 요구사항으로 인해 스타크래프트는 인공지능 연구를 위한 “큰 도전”으로 떠올랐습니다. 2009년 BroodWar API 출시 이후, 스타크래프트와 스타크래프트2가 현재 진행 중인 경쟁은 AIIDE 스타크래프트 AI 공모전, CIG 스타크래프트 공모전, 학생 스타크래프트 AI 토너먼트, 그리고 스타크래프트2 AI Ladder가 있습니다. 커뮤니티가 이러한 문제를 더욱 효과적으로 해결할 수 있도록 2016년과 2017년에 블리자드와 협력하여 PySC2로 알려진 오픈소스 도구 세트를 출시했습니다. 여기에는 지금까지 출시 된 익명의 게임 리플레이 세트가 포함됩니다. 저희는 AlphaStar를 제작하기위해 엔지니어링 및 알고리즘 혁신을 결합하여 이 작업을 수행했습니다.

alphastar_interface
MaNa와의 2번째 경기 중 AlphaStar가 플레이하는 게임을 시각화한 모습. 즉, neural network에 있는 그대로의 화면을 input으로 넣어주고, neural network를 활성화시키고, AlphaStar가 클릭 할 위치와 대상, 그리고 예측된 결과와 같은 출력 값을 가질 수 있다. 그림에는 MaNa의 화면이 보이지만, AlphaStar는 이에 접근할 수 없다.

AlphaStar는 어떻게 훈련되는가

AlphaStar는 있는 그대로의 게임 인터페이스(유닛과 유닛속성의 목록)로부터 input을 받고 게임 내에서 조작하는 명령어를 생성하게 됩니다. 자세히 이야기하자면, neural network 구조중에 하나인 transformer를 메인으로 deep LSTM core, pointer networkauto-regressive policy head, 그리고 centralised value baseline을 사용하였습니다. 저희는 이 진보된 모델이 long-term sequence와 큰 output spaces를 가지는 번역이나 언어 모델링, 그리고 시각적인 표현에 큰 도움을 줄 수 있을 거라 생각합니다.

AlphaStar는 새로운 멀티 에이전트 학습 알고리즘을 사용합니다. 이 Neural network는 한 익명의 사람이 처음에 supervised learning으로 학습을 시켰고 블리자드는 이를 공개하였습니다. 이를 통해서 AlphaStar는 사람들을 따라하며 기본적인 Macro, Micro 전략을 배울 수 있었습니다. 초기의 에이전트는 게임내의 인공지능 레벨 ‘엘리트’를 95%의 승률로 이기기 시작했습니다.

alpha_league
AlphaStar 리그. 에이전트는 처음에는 사람의 게임 리플레이에서 훈련을 한 후 리그내의 다른 경쟁자와 경기를 하며 훈련을 합니다. 각 iteration마다 새로운 경쟁자가 나타나고 원래의 경쟁자가 동결되며 각 에이전트의 매치를 결정하는 확률과 하이퍼 파라미터를 결정하는 학습 목표가 적용되며, 다양성을 유지하면서 난이도는 올라가도록 하였습니다. 에이전트의 parameter는 경쟁자에 대한 게임 결과로부터 강화 학습을 통해 업데이트됩니다. 최종 에이전트는 리그의 Nash 분포에서 샘플링됩니다.

그런 다음 멀티 에이전트 reinforcement learning을 통해 학습을 시작하였습니다. 지속적으로 리그가 생성되며 에이전트들은 경쟁자와 맞서 게임을 하게 됩니다. Starcraft Ladder와 같이, 에이전트들은 사람처럼 스타크래프트를 플레이하게 되는 것입니다. 새로운 경쟁자들이 계속해서 리그에 등장하게 되며, 새로운 경쟁자들은 원래의 에이전트들에게서 branch 되어 나옵니다. 각 에이전트들은 경쟁을 통해 학습을 진행하게 됩니다. Population-based reinforcement learning의 아이디어를 가져온 새로운 형태의 학습 과정으로, 각 에이전트가 가진 가장 강력한 전술을 상대하고, 빨리 상대방을 무릎을 꿇게 함과 동시에 지속적으로 스타크래프트의 방대한 양의 전술을 익히고 경험하게 됩니다.

MMR
추측 MMR(Match Making Rating)은 대략적인 플레이어의 실력을 측정한 것으로써, 현재 블리자드 온라인 리그와 비교를 해보았습니다.

리그가 진행되고 새로운 경쟁자가 만들어지면 이전 전략을 무력화 할 수 있는 새로운 대응 전략이 등장합니다. 일부 새로운 에이전트는 이전 전략을 단순화 한 전략을 실행하는 반면 다른 일부 에이전트는 새로운 빌드(전략)과 유닛 구성 및 micro-관리하는 방법 등 획기적인 새로운 전략을 발견했습니다.

예를 들면, AlphaStar 리그 초반에는 광자포암흑 기사를 이용하여 빠르게 적을 공략하는 “Cheesy”(역주: 싸구려) 전략이 선호되었습니다. 하지만 이러한 위험한 전략은 훈련이 진행됨에 따라 폐기되어 다른 전략을 이끌어 냈습니다. 예를 들어, 더 많은 일꾼들로 기지를 과도하게 확장함으로써 경제적 힘을 얻거나 두 마리의 예언자(역주. 일꾼 사냥에 좋은 유닛)을 통해 상대방의 일꾼과 경제를 혼란스럽게 합니다. 이 과정은 스타크래프트가 출시 된 이후로 플레이어가 새로운 전략을 발견하고 이전에 선호했던 전략을 버렸던 것과 유사합니다.

unit
훈련이 진행됨에 따라 AlphaStar가 만드는 유닛의 조합이 바뀌었습니다.

리그의 다양성을 장려하기 위해 각 에이전트는 자체 학습 목표를 가지고 있습니다. 예를 들어, 어떤 에이전트는 다른 한 에이전트를 이겨야만 하는 추가적인 내부 motivation을 추가해줍니다. 한 에이전트는 특정 경쟁자를 이길 수 있는 목표를 가질 수 있지만 다른 에이전트는 대다수의 에이전트를 이겨야하는 경우도 있습니다. 그렇게 하기 위해서는 특정 유닛을 더 많이 만들어야 합니다. 이러한 학습 목표는 학습 과정에서 조정됩니다.

strategy map
AlphaStar 리그의 경쟁자를 보여주는 visualization. TLO 및 MaNa와 경쟁을 한 에이전트에는 특별히 레이블이 지정됩니다.(html을 긁어오기가 힘들어서 사진 캡처로 대체. detail in 본문)

각 에이전트들의 neural network weight은 경쟁자에 대한 게임으로부터 reinforcement learning을 통해 업데이트되어 개인 학습 목표를 최적화합니다. Weight을 업데이트하는 규칙으로는, off-policy actor-critic 방법과 함께 experience replay, self-imitation learning, 그리고 policy distillation이 사용되었습니다.

MaNa Agent
이 그림은 MaNa와의 플레이를 위해 선택된 한 명의 에이전트(검은 점)가 훈련 과정에서 전략과 경쟁자(색이 있는 점)들을 발전 시켰음을 보여줍니다. 각 점은 AlphaStar 리그의 경쟁자를 나타냅니다. 점의 위치는 전략(inset)을 나타내며 점의 크기는 Mana 에이전트와 얼마나 자주 겨루며 학습을 했는지를 보여줍니다.

AlphaStar를 학습시키기 위해 저희는 구글의 스타크래프트2를 학습하는 수많은 에이전트들이 수천개의 병렬 instance들 안에서 학습할 수 있도록 지원하는 구글 v3 TPU를 사용하여 확장성이 뛰어난 distributed 학습 환경을 만들었습니다. AlphaStar 리그는 14일 동안, 16개의 TPU를 사용하며 훈련이 이루어졌고 각 에이전트들은 현실시간으로 약 200년의 스타크래프트 게임을 경험하게 되었습니다. 최종적인 AlphaStar 에이전트는 Nash 분포 리그를 구성하고 있으며, 이는 곧 가장 효율적인 전략들이 혼합된 모델이라는 것입니다. 그리고 이 모델은 단 한개의 GPU에서 구동이 가능합니다.

모든 기술적인 설명은 peer-reviewd 저널에 투고될 예정입니다.

progression
AlphaStar 리그가 진행되면서 Nash 분포 안에서도 새로운 경쟁자들이 등장했습니다. Nash 분포 안에서 상호 보완적인 경쟁자 중 가장 취약한 에이전트는 새로운 경쟁자에게 가장 큰 비중을 두고 있으며 이전 경쟁자들에 대한 지속적인 진전을 보여줍니다.

AlphaStar는 어떻게 게임을 플레이하며 게임을 관찰하는가

TLO와 MaNa같은 프로 스타크래프트 플레이어는 평균 분당 수백번의 동작(APM)을 실행할 수 있습니다. 이는 각 유닛을 독립적으로 제어하고 수천 또는 수만 개의 APM을 일관성 있게 유지 및 관리하는 기존 Bot들에 비해 현저하게 낮습니다. TLO와 MaNa와의 경기에서 AlphaStar의 평균 APM은 280으로 아무리 게임 플레이가 정교했다 하더라도 프로 선수보다 훨씬 낮았습니다. 이 낮은 APM은 부분적으로 AlphaStar가 리플레이를 사용하여 교육을 시작하기 때문에 인간이 게임을 하는 방식을 모방하기 때문입니다. 또한 AlphaStar는 관찰과 동작 사이의 평균 350ms의 지연시간을 가지고 있습니다.

APM
MaNa와 TLO에 대한 경기에서 AlphaStar가 가졌던 APM 분포와 총 지연 시간.

TLO와 MaNa와의 경기 도중 AlphaStar는 직접 게임의 인터페이스를 통해 스타크래프트 게임 엔진을 사용하였습니다. 즉, 카메라를 움직이지 않고도 직접 맵에서 자신과 상대방의 눈에 보이는 유닛의 속성을 직접 관찰 할 수 있었습니다. 반대로 사람은 카메라를 사용해서 어디에 집중해야 할지 명확하게 결정을 해야만 합니다. 하지만 분석해보니 AlphaStar의 게임은 AlphaStar 스스로가 어디에 집중을 해야 할지 암시적으로 관리하는 모습을 보였습니다. AlphaStar는 TLO나 MaNa와 비슷하게 평균적으로 분당 30회 정도의 “switched context”를 하였습니다.

또한, 경기 후에 저희는 AlphaStar의 두 번째 버전을 개발했습니다. 인간 선수와 마찬가지로 AlphaStar의 두 번째 버전은 카메라를 언제, 어디서 움직일지를 결정하며, 화면의 정보로만 인식되며, 동작 위치는 볼 수있는 영역으로 제한됩니다.

Comparison of interfaces for training
Raw 인터페이스와 카메라 인터페이스를 사용하는 AlphaStar의 성능. 새로 훈련된 카메라 에이전트가 raw 인터페이스를 사용하여 에이전트의 성능을 따르게 따라 잡을 수 있고 거의 동일하다는 것을 보여준다.

Raw 인터페이스를 사용하는 것과 AlphaStar 리그를 상대로 카메라 제어법을 배우는 두 가지 새로운 에이전트를 교육했습니다. 각 에이전트는 처음에 인간 데이터를 통한 supervised learning과 위에서 설명한 reinforcement learning을 통해 훈련되었습니다. 카메라 인터페이스를 사용하는 AlphaStar 버전은 내부 인터페이스에서 7000 MMR을 초과하는 raw 인터페이스와 거의 비슷했습니다. 전시회 경기에서, MaNa는 7일 동안 훈련된 카메라 인터페이스를 사용하는 프로토타입의 AlphaStar를 이겼습니다.

이 결과는 MaNa 및 TLO에 대한 AlphaStar의 성공이 우수한 클릭률, 빠른 반응 시간 또는 raw 인터페이스보다는 우수한 macro와 micro-strategic decision-making(역주. 컨트롤) 때문인 것으로 나타났습니다.

프로선수의 AlphaStar 평가

스타크래프트 게임을 통해 플레이어는 테란, 저그 또는 프로토스의 세 가지 종족 중 하나를 선택할 수 있습니다. 저희는 AlphaStar를 학습시키는 것에 교육시간과 편차를 줄이기 위해 현재 단 하나의 종족인 프로토스를 전문적으로 다루기로 결정했습니다. Note that, 동일한 교육을 모든 종족에 적용을 시킬 수 있습니다. 저희의 에이전트는 스타크래프트2(v4.6.2)에서 프로토스vs프로토스 게임을 진행하였고, CatalystLE라는 맵에서 진행되었습니다. AlphaStar의 성능을 평가하기 위해 저희는 TLO(저그가 주종족인 프로선수, 프로토스는 그랜드마스터의 실력)를 통해 테스트 하였고 AlphaStar는 다양한 빌드와 유닛으로 5-0으로 경기를 마무리했습니다. TLO는 “진짜 잘해서 놀랐어요. AlphaStar는 잘 알려진 전략과는 거리가 먼 플레이를 보여주었어요. 전혀 생각하지도 못한 전략으로 다가왔죠. 그 말은 곧 아직 우리가 경험하지 못한 새로운 방식의 전략들이 존재하다는 거겠죠” 라고 말했다.

AlphaStar:inside story

1주일간 에이전트를 교육 한 후, 저희는 세계에서 가장 강력한 스타크래프트2의 플레이어 중 하나인 MaNa 와 가장 강력한 10명의 프로토스 선수들과 대전했습니다. AlphaStar는 5경기에서 무패로 우승하여 강력한 전략 기술을 보여주었습니다. “AlphaStar의 놀라운 움직임과 매 경기 다른 전략으로 다가오는 것에 큰 감명을 받았다. 내가 전혀 생각하지 못했던 사람 같은 플레이를 보여주었다.” “나는 내 전략이 상대방의 실수를 유도하고 그의 인간적인(감정적인) 반응을 나에게 유리하도록 하는 것에 의존하고 있다는 사실을 깨달았다.”고 말했다.

AlphaStar, 그리고 다른 복잡한 문제들

스타크래프트는 그냥 게임이지만, 아주 복잡한 게임입니다. 저희는 AlphaStar를 뒷받힘 하는 기술들이 다른 문제 해결에 유용할 것이라 생각합니다. 예를 들면, 장시간 동안 많은 행동이 필요한 긴 sequence(때로는 수천, 수만의 움직임을 하는 1시간 경기 같은)같은 것들-완전하지 않은 정보들을 기반으로-에 도움이 될 수 있다고 생각합니다. 스타크래프트의 각 프레임은 입력의 한 단계로 사용되며, neural network는 매 프레임마다 나머지 게임에 대한 예상 동작 순서를 예측합니다. 매우 긴 데이터 sequence에 대해 복잡한 예측을 하는 근본적인 문제는 날씨 예측, 기후 모델링, 언어 이해 등과 같은 많은 실제 과제에서 나타납니다.

저희는 또한 우리의 훈련 방법 중 일부가 안전하고 견고한 인공지능 연구에 유용 할 수 있다고 생각합니다. AI에서 커다란 난제 중 하나는 시스템이 잘못 훈련 될 수 있는 가능성이 있으며 스타크래프트 프로 선수들은 이러한 실수를 유발할 창의적인 방법을 찾아 인공지능 시스템을 쉽게 이길 수 있음을 알았습니다. AlphaStar의 혁신적인 리그 기반 학습 과정은 가장 신뢰할 수 있고 잘못 될 가능성이 적은 접근 방식을 찾습니다. 저희는 AI 시스템의 안전성과 견고성을 향상시킬 수있는 이러한 접근법의 잠재력, 특히 에너지와 같이 안전성이 중요한 분야에서 복잡한 상황을 다루는 것이 필수적이라는 점을 기쁘게 생각합니다.

AlphaStar를 통해 만들어진 스타크래프트의 최고 수준의 플레이는 지금까지 만들어진 가장 복잡한 게임 중 하나를 돌파했다는 것을 의미합니다. 우리는 AlphaZeroAlphaFold와 같은 프로젝트와 함께 이러한 발전들은 언젠가는 세계에서 가장 중요하고 근본적인 과학적 문제에 대한 새로운 솔루션을 열어 줄 수 있는 지능형 시스템을 개발하려는 우리의 사명을 한 단계 진전시킨 것이라고 믿습니다.

Team Liquid의 TLO 및 MaNa에 대한 지원과 엄청난 기술에 감사드립니다. 우리는 블리자드와 스타크래프트 커뮤니티가 이 작업을 가능하게 하기 위해 지속적으로 지원해 주신 것에 대해서도 감사드립니다.


Download 11 replays here
Watch the exhibition game against MaNa
Watch a visualisation of AlphaStar’s entire second game against MaNa
AlphaStar Team:

Oriol Vinyals, Igor Babuschkin, Junyoung Chung, Michael Mathieu, Max Jaderberg, Wojtek Czarnecki, Andrew Dudzik, Aja Huang, Petko Georgiev, Richard Powell, Timo Ewalds, Dan Horgan, Manuel Kroiss, Ivo Danihelka, John Agapiou, Junhyuk Oh, Valentin Dalibard, David Choi, Laurent Sifre, Yury Sulsky, Sasha Vezhnevets, James Molloy, Trevor Cai, David Budden, Tom Paine, Caglar Gulcehre, Ziyu Wang, Tobias Pfaff, Toby Pohlen, Dani Yogatama, Julia Cohen, Katrina McKinney, Oliver Smith, Tom Schaul, Timothy Lillicrap, Chris Apps, Koray Kavukcuoglu, Demis Hassabis, David Silver

With thanks to:

Ali Razavi, Daniel Toyama, David Balduzzi, Doug Fritz, Eser Aygün, Florian Strub, Guillaume Alain, Haoran Tang, Jaume Sanchez, Jonathan Fildes, Julian Schrittwieser, Justin Novosad, Karen Simonyan, Karol Kurach, Philippe Hamel, Ricardo Barreira, Scott Reed, Sergey Bartunov, Shibl Mourad, Steve Gaffney, Thomas Hubert, Yuhuai Wu, the team that created PySC2 and the whole DeepMind Team, with special thanks to the RPT, comms and events teams.

Share 0 Comments

[챗봇] 페이스북 챗봇 만들기

나만의 페이스북 메신저 봇 만들기

2020.03.19 Updates

수정사항 : 원래 사용했던 nodejs 대신 python을 이용합니다.

이 글은 Python, Heroku server를 이용하여 자신만의 페이스북 챗봇을 만들어보는 템플릿 예제입니다.

챗봇이란?

챗봇은 사용자가 별도로 웹사이트나 앱을 따로 실행하지 않고도 대화하듯 정보를 얻을 수 있는 서비스다. 기존 사용자 자신이 쓰는 메신저를 통해 정보를 얻을 수 있다는 점이 현재 구글, 페이스북, 마이크로소프트, 텔레그램을 비롯해 국내 네이버, 다음 등 IT 분야 기업들이 챗봇을 기반으로 한 메신저 플랫폼을 선보이는 중이다.

[네이버 지식백과] 채팅봇, 챗봇 - 메신저 서비스 인공지능(AI)과 만나다 (용어로 보는 IT)

준비

  1. Python Version 3
  2. Terminal
    • 터미널 기반으로 배포하기 때문에 윈도우 사용자는 cmd나 터미널 환경을 갖추신 후에 따라하시면 됩니다. 윈도우 터미널 설치 가 도움이 되실겁니다.
  3. Git
  4. Heroku
    • heroku 에 가입합니다.
    • Heroku CLI가 필요합니다.
  5. Facebook Account
    • Facebook account가 필요합니다.
  6. Ngrok
    • Ngrok을 사용하여 Heroku에 배포하기 전, 내부 망에서 테스트를 하기 위해서 필요합니다.

디렉토리 설정 및 코드 작성

이 프로젝트를 위한 디렉토리를 하나 만들어줍니다.

1
2
3
4
mkdir chatbot
cd chatbot
pwd
/Users/username/chatbot

또한 python library들을 다운 받습니다.

1
sudo pip3 install Flask requests gunicorn

혹은

1
pip3 install --user Flask requests gunicorn

폴더를 만드셨다면 chatbot 폴더 내부에 파이썬 코드를 작성해줍니다.

app.py 라는 이름으로 코드를 작성해주시면 됩니다.

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
from flask import Flask, request
import requests
app = Flask(__name__)
FB_API_URL = 'https://graph.facebook.com/v2.6/me/messages'
VERIFY_TOKEN=''
PAGE_ACCESS_TOKEN=''
def send_message(recipient_id, text):
"""Send a response to Facebook"""
payload = {
'message': {
'text': text
},
'recipient': {
'id': recipient_id
},
'notification_type': 'regular'
}

auth = {
'access_token': PAGE_ACCESS_TOKEN
}

response = requests.post(
FB_API_URL,
params=auth,
json=payload
)

return response.json()

def get_bot_response(message):
"""This is just a dummy function, returning a variation of what
the user said. Replace this function with one connected to chatbot."""
return "This is a dummy response to '{}'".format(message)


def verify_webhook(req):
if req.args.get("hub.verify_token") == VERIFY_TOKEN:
return req.args.get("hub.challenge")
else:
return "incorrect"

def respond(sender, message):
"""Formulate a response to the user and
pass it on to a function that sends it."""
response = get_bot_response(message)
send_message(sender, response)


def is_user_message(message):
"""Check if the message is a message from the user"""
return (message.get('message') and
message['message'].get('text') and
not message['message'].get("is_echo"))


@app.route("/webhook", methods=['GET'])
def listen():
"""This is the main function flask uses to
listen at the `/webhook` endpoint"""
if request.method == 'GET':
return verify_webhook(request)

@app.route("/webhook", methods=['POST'])
def talk():
payload = request.get_json()
event = payload['entry'][0]['messaging']
for x in event:
if is_user_message(x):
text = x['message']['text']
sender_id = x['sender']['id']
respond(sender_id, text)

return "ok"

@app.route('/')
def hello():
return 'hello'

if __name__ == '__main__':
app.run(threaded=True, port=5000)

VERIFY_TOKEN와 PAGE_ACCESS_TOKEN은 이 다음 과정에서 페이스북 세팅 후, 채워넣을 예정입니다.

위와 같이 코드를 작성하셨다면 다음 단계로 넘어가겠습니다.

페이스북 설정

  1. Facebook Developer Page에 접속한 후 Create App을 눌러줍니다.

Create App

  1. App의 이름을 정해준 후에 Create App ID를 누릅니다.

Create App ID

  1. App이 만들어졌다면 다음과 같은 화면에서 Messenger의 Set Up 버튼을 눌러줍니다.

Messenger Set Up

  1. Access Tokens 내부 화면에서 Create New Page를 눌러서 페이지 생성을 진행합니다.

Access Tokens
Create New Page
Public Figure
Page Setting
Skip Photo
Page Done

  1. Page 생성이 완료되었다면 다시 Access Tokens 내부 화면으로 돌아와서 하단에 Add or Remove Pages를 눌러줍니다.

Access Tokens Add Page
What Pages
Allowed to
Add Page Done

  1. Generate Token을 눌러줍니다.

Add Page Done Generate

  1. I Understand에 체크표시하시고 밑에 생성된 Token을 복사한 뒤 따로 보관해둡니다.(나중에 코드에 들어가게 됩니다.)

Token Generated

Ngrok으로 테스트해보기

Ngrok으로 챗봇을 테스트 하기전에 먼저 app.py 파일 내부를 수정해줍니다.

1
2
3
4
5
6
from flask import Flask, request
import requests
app = Flask(__name__)
FB_API_URL = 'https://graph.facebook.com/v2.6/me/messages'
VERIFY_TOKEN='my_secret_token'
PAGE_ACCESS_TOKEN='ENNJLCNDJSLKABJVLNEJLKWANJLCNJEL.........'

위의 부분에서 VERIFY_TOKEN은 여러분이 원하는 비밀번호로, PAGE_ACCESS_TOKEN 안에는 아까 발급 받았던 그 token을 ‘’ 사이에 넣어주시면 됩니다.(이 파일을 open된 곳에 upload하지 마세요. 추후에 보안이 필요한 작업입니다.)

수정을 다 해주셨다면 Ngrok에 들어가셔서 Sign Up을 하신 뒤, ngrok을 다운받습니다. 저같은 경우에는 app.py가 위치하고 있는 폴더에 다운로드하였습니다.

ngrok

ngrok에서 설명하는 것 그대로 진행합니다.

다운로드 -> 압축해제를 진행한 뒤에 command 창에 3번 그대로 복사붙여넣기를 해주면, 접속을 위한 세팅이 완료되고 4번의 명령어와는 다르게 80대신 5000을 써줍니다.

1
./ngrok http 5000

을 실행하게 되면 다음과 같은 화면이 뜨게 됩니다.

ngrok_80

그리고 또 다른 터미널 창을 열어주신 뒤, app.py 파일을 실행해줍니다.

1
python app.py

run_app.py

이와 같이 ngrok과 app.py 가 모두 준비가 되었다면 다시 브라우저를 통해 access tokens 하단에 있는 webhook에서 Add Callback URL을 클릭합니다.

add_callback
set_callback
webhook
webhook_sub

이제 대략적인 세팅은 끝이 났습니다.

본인의 page에 hi라고 한번 메세지를 보내봅니다.

test_hi

성공적으로 제가 한 말을 따라합니다.

Heroku에 배포하기

Heroku에 배포하기 위해서 설치했던 Heroku CLI를 이용하여 배포해줄 것입니다.

heroku login을 실행하시고 기다리셨다가 press any key… 가 나오시면 enter 눌러주세요.

1
2
3
4
5
heroku login

heroku: Press any key to open up the browser to login or q to exit:
Opening browser to https://cli-auth.heroku.com/auth/cli/browser/.......................
heroku: Waiting for login... ⣷

heroku_login

로그인을 진행해주시면 다음과 같이 커맨드창에 나타납니다.

1
2
Logging in... done
Logged in as <your email address>

로그인이 성공적으로 완료되었다면 다음 명령어를 실행합니다.

1
heroku create

실행하면, heroku는 자동적으로 웹 서버를 만들기 시작합니다.

heroku_create

이제 생성된 Heroku App에 우리가 만든 소스를 배포시키려고 합니다.

배포하기 전에 다음과 같은 내용을 가진 requirements.txt을 만들어주고

1
2
3
Flask==1.0.2
requests==2.21.0
gunicorn==20.0.4

다음 내용을 가진 Procfile 을 만들어 줍니다.

1
web: gunicorn app:app

위의 과정까지 마치셨다면 다음 명령어들을 차례대로 실행합니다.

1
2
3
4
5
git init
git add .
git commit -m 'init'
heroku git:remote -a <your_project_name>
git push heroku master

밑의 사진들은 제가 실행시킨 결과입니다.

git_process
deploy_process

이와 같이 heroku에 배포를 완료했다면, 실행중인 ngrok을 꺼주시고 브라우저에서 callback을 수정합니다.

edit_callback
edit_callback_heroku

이 과정을 모두 수행하셨다면

마지막으로 page에 테스트 메세지를 보내봅니다.

final_test

마치며

기존 버전이 2017년 버전으로 되어있어서 많은 부분이 다르기 때문에

급하게 블로그 내용을 수정하였습니다.

추가적으로 내용 수정을 들어가겠으며 질문이 있으시면 얼마든지 코멘트 달아주세요.

제가 다룬 코드는 제 Github에서 확인하실 수 있습니다.

감사합니다.

Ref :

  1. https://github.com/davidchua/pymessenger
  2. https://hackernoon.com/beginners-guide-simple-chat-bot-fb-based-on-flask-and-heroku-2g7v32ab
Share 0 Comments