12wk-2: 클래스 (2)

Author

최규빈

Published

May 24, 2024

1. 강의영상

2. Imports

import numpy as np
import pandas as pd

3. 상속 맛보기

A. 모티브: 클래스를 수정은 불편해

- 예시1: UpJump ver1

class Up:
    def __init__(self):
        self.value = 0
    def up(self):
        self.value = self.value + 1 
    def __repr__(self):
        return str(self.value) 
a = Up()
a
0
a.up()
a
1
a.jump(2)
AttributeError: 'Up' object has no attribute 'jump'
  • a.jump()는 아직 구현되지 않은 기능임.

- 예시2: UpJump

class UpJump:
    def __init__(self):
        self.value = 0
    def up(self):
        self.value = self.value + 1 
    def __repr__(self):
        return str(self.value) 
    #---# 여기까지는 똑같이
    def jump(self,jump_size):
        self.value = self.value + jump_size
a = UpJump()
a
0
a.up()
a
1
a.jump(5)
a
6

- 예시3: UpJump의 다른 구현

class UpJump(Up):
    def jump(self,jump_size):
        self.value = self.value + jump_size
a = UpJump()
a.up()
a
1
a.jump(5)
a
6

B. 꿀팁

- 클래스를 조금 수정하고 싶을때, 아래와 같은 문법을 이용하면 편리하다.

class 새로운_클래스_이름(수정할_클래스_이름):
    def 수정_및_추가할_함수이름(self,...):
        ...

- 사용예시

class UpJump2(UpJump):
    def __repr__(self):
        return f"현재 이 인스턴스는 {self.value}의 값을 가지고 있습니다."
a = UpJump2()
a
현재 이 인스턴스는 0의 값을 가지고 있습니다.
a.up()
a
현재 이 인스턴스는 1의 값을 가지고 있습니다.
a.jump(5)
a
현재 이 인스턴스는 6의 값을 가지고 있습니다.

4. __add__

A. 모티브

- 모티브: 아래의 연산구조를 관찰하자.

a=1
b=2
a+b
3
  • a라는 인스턴스와 b라는 인스턴스를 +라는 기호가 연결하고 있다.

- 이번에는 아래의 연산구조를 관찰하자.

a=[1,2]
b=[3,4]
a+b
[1, 2, 3, 4]
  • a라는 인스턴스와 b라는 인스턴스를 +라는 기호가 연결하고 있다.

- 동작이 다른 이유?

  • 클래스를 배우기 이전: int자료형의 +는 “정수의 덧셈”을 의미하고 list자료형의 +는 “자료의 추가”를 의미한다.
  • 클래스를 배운 이후: 아마 클래스는 +라는 연산을 정의하는 숨겨진 메소드가 있을것이다. (print가 그랬듯이) 그리고 int클래스에서는 그 메소드를 “정수의 덧셈”이 되도록 정의하였고 list클래스에서는 그 메소드를 “자료의 추가”를 의미하도록 정의하였다.

- 아래의 결과를 관찰

a = 1
b = -2
a.__add__(b) # a+b
-1
b.__add__(a) # a+b
-1
a = [1,2]
b = [3,4]
a.__add__(b) # a+b 
[1, 2, 3, 4]
b.__add__(a) # b+a 
[3, 4, 1, 2]

- 확인: a+b는 사실 내부적으로 a.__add__(b)의 축약구문이다.

- 추측: 따라서 만약 a.__add__(b)의 기능을 바꾸면 (재정의하면) a+b의 기능도 바뀔 것이다.

B. Student: __add__의 사용

- StudentWrong 클래스 선언

class StudentWrong:
    def __init__(self,age=20.0, semester=0):
        self.age = age 
        self.semester = semester
        print(f"입학을 축하합니다. 당신의 나이는 {self.age}이고 현재 학기는 {self.semester}학기 입니다.")
    def __repr__(self):
        text = f"나이: {self.age}\n학기: {self.semester}"
        return text
    def __add__(self,registration_status):
        if registration_status == "휴학":
            self.age = self.age +0.5 
        elif registration_status == "등록":
            self.age = self.age +0.5 
            self.semester = self.semester + 1
boram = StudentWrong()
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
boram
나이: 20.0
학기: 0

- 사용

boram + "휴학"
boram
나이: 20.5
학기: 0
boram + "등록"
boram
나이: 21.0
학기: 1
boram + "휴학"
boram
나이: 21.5
학기: 1
boram + "등록"
boram
나이: 22.0
학기: 2
boram + "등록"
boram
나이: 22.5
학기: 3
boram + "등록"
boram
나이: 23.0
학기: 4

- 잘못된 사용

seoyeon = StudentWrong()
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
seoyeon + "등록" + "등록"
seoyeon # 서연의 나이는 21, 학기는 2
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'

- 올바른 코드

class Student:
    def __init__(self,age=20.0, semester=0):
        self.age = age 
        self.semester = semester
        print(f"입학을 축하합니다. 당신의 나이는 {self.age}이고 현재 학기는 {self.semester}학기 입니다.")
    def __repr__(self):
        text = f"나이: {self.age}\n학기: {self.semester}"
        return text
    def __add__(self,registration_status):
        if registration_status == "휴학":
            self.age = self.age +0.5 
        elif registration_status == "등록":
            self.age = self.age +0.5 
            self.semester = self.semester + 1
        return self 
seoyeon = Student()
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
seoyeon + "등록" + "휴학" + "등록" + "등록"
나이: 22.0
학기: 3

C. Student의 다른구현1

class Student(StudentWrong):
    def __add__(self,registration_status):
        if registration_status == "휴학":
            self.age = self.age +0.5 
        elif registration_status == "등록":
            self.age = self.age +0.5 
            self.semester = self.semester + 1
        return self
seoyeon = Student()
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
seoyeon + "등록" + "휴학" + "등록" + "등록"
나이: 22.0
학기: 3

D. Student의 다른구현2

class Student(StudentWrong):
    def __add__(self,registration_status):
        super().__add__(registration_status)
        return self
seoyeon = Student()
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
seoyeon + "등록" + "휴학" + "등록" + "등록"
나이: 22.0
학기: 3

파이썬의 비밀: a+b는 사실 a.__add__(b)의 축약형이다.

5. __getitem__

A. 모티브

a = [11,22,33]
a
[11, 22, 33]
a[0]
11
  • 이거 좋아보인다?
a.__getitem__(0) # a[0]의 축약표현
11
a.__getitem__(1) # a[1]의 축약표현
22
a.__getitem__(-1) # a[-1]의 축약표현
33
  • 이런거였어??

B. RPS1: __getitem__의 사용

class RPSBase:
    def __init__(self,action_space):
        self.action_space = action_space 
        self.actions = []
    def act(self):
        action = np.random.choice(self.action_space)
        self.actions.append(action)
        return action
    def __repr__(self):
        text = f"낼 수 있는 패: {self.action_space}\n기록: {self.actions}"
        return text
A = RPSBase(['가위','바위','보'])
A
낼 수 있는 패: ['가위', '바위', '보']
기록: []
A.act()
A.act()
'바위'
A
낼 수 있는 패: ['가위', '바위', '보']
기록: ['바위', '바위']
A[0], A[1]
TypeError: 'RPSBase' object is not subscriptable
  • 이 결과가 “A.actions[0]=바위”, “A.actions[1]=가위”로 나왔으면 좋겠다.. 그런데 지금은 불가능함.
A.__getitem__(0)
AttributeError: 'RPSBase' object has no attribute '__getitem__'
class RPS1(RPSBase):
    def __getitem__(self,idx):
        return self.actions[idx]
A = RPS1(['가위','바위','보'])
A
낼 수 있는 패: ['가위', '바위', '보']
기록: []
A.act()
A.act()
A.act()
A.act()
'보'
A
낼 수 있는 패: ['가위', '바위', '보']
기록: ['바위', '보', '보', '보']
A[0],A[1],A[2],A[-1]
('바위', '보', '보', '보')

파이썬의 비밀: A[0]A.__getitem__(0)의 축약형이다.

6. __setitem__

A. 모티브

A = RPS1(['가위','바위'])
A
낼 수 있는 패: ['가위', '바위']
기록: []
A.act()
A.act()
A.act()
A.act()
'바위'
A
낼 수 있는 패: ['가위', '바위']
기록: ['바위', '바위', '가위', '바위']
A[0]
'바위'
A[0] = "보"
TypeError: 'RPS1' object does not support item assignment
  • 여기서 이 문법이 가능하면 결과 조작이긴 함.
  • 그런데 경우에 따라서는 이런 문법이 필요할 때도 있음.

B. RPS2: __setitem__ 사용

- 관찰

lst = [1,2,3] 
lst
[1, 2, 3]
lst[0] = 11
lst
[11, 2, 3]
lst.__setitem__(0,-11) # lst[0] = -11 의 역할
lst
[-11, 2, 3]

- RPS예제

class RPS2(RPS1):
    def __setitem__(self,idx,val):
        self.actions[idx] = val 
A = RPS2(['가위','바위'])
A
낼 수 있는 패: ['가위', '바위']
기록: []
A.act()
A.act()
A.act()
A.act()
'가위'
A
낼 수 있는 패: ['가위', '바위']
기록: ['바위', '가위', '바위', '가위']
A[0]
'바위'
A[0] = "보"
# A.__setitem__(0,"보")
# A.actions[0] = "보" 
A
낼 수 있는 패: ['가위', '바위']
기록: ['보', '가위', '바위', '가위']

파이썬의 비밀: A[0]="보"A.__setitem__(0,"보")의 축약형이다.

7. __len__

A. 모티브

A = RPS2(['가위','바위'])
A
낼 수 있는 패: ['가위', '바위']
기록: []
A.act()
A.act()
A.act()
A.act()
'가위'
A
낼 수 있는 패: ['가위', '바위']
기록: ['가위', '가위', '바위', '가위']
A[0],A[1],A[2],A[3]
('가위', '가위', '바위', '가위')
len(A)
TypeError: object of type 'RPS2' has no len()
  • 이게 4라고 나왔으면..

B. RPS3: __len__ 의 사용

- 관찰

a = [1,2,3]
len(a)
3
a.__len__()
3

- 구현

class RPS3(RPS2):
    def __len__(self):
        return len(self.actions)
A = RPS3(['가위','바위'])
A
낼 수 있는 패: ['가위', '바위']
기록: []
A.act()
A.act()
A.act()
A.act()
A.act()
A.act()
A.act()
'가위'
A
낼 수 있는 패: ['가위', '바위']
기록: ['가위', '가위', '바위', '가위', '가위', '바위', '가위']
len(A)
7

파이썬의 비밀: len(A)A.__len__()의 축약형이다.

8. __eq__

A. 모티브

A = RPS3(['가위','바위'])
B = RPS3(['가위','보'])
A.act()
B.act()
'보'
A,B
(낼 수 있는 패: ['가위', '바위']
 기록: ['가위'],
 낼 수 있는 패: ['가위', '보']
 기록: ['보'])
A[0] = "가위"
B[0] = "가위"
A,B
(낼 수 있는 패: ['가위', '바위']
 기록: ['가위'],
 낼 수 있는 패: ['가위', '보']
 기록: ['가위'])
A == B
False
  • 의미상으로는 A와 B 모두 “가위”를 냈으므로, 현재는 비겼음.
  • 그래서 A==B의 결과가 True가 나오는 것이 더 의미상 맞는것 같다.

B. RPS4: __eq__ 의 사용

- 관찰

a = 33 
b = 33 
c = 55
a.__eq__(b)  # a==b
True
a.__eq__(c)  # a==c
False

- 구현

class RPS4(RPS3):
    def __eq__(self,other):
        return self[-1] == other[-1]
A = RPS4(['가위','바위'])
B = RPS4(['가위','보'])

1회 대결

A.act()
B.act()
'보'
A,B
(낼 수 있는 패: ['가위', '바위']
 기록: ['바위'],
 낼 수 있는 패: ['가위', '보']
 기록: ['보'])
A == B
False

2회 대결

A.act()
B.act()
'가위'
A,B
(낼 수 있는 패: ['가위', '바위']
 기록: ['바위', '바위'],
 낼 수 있는 패: ['가위', '보']
 기록: ['보', '가위'])
A == B
False

9 __gt__

A. 모티브

A = RPS4(['가위','바위'])
B = RPS4(['가위','보'])

1회 대결

A.act()
B.act()
'보'
A,B
(낼 수 있는 패: ['가위', '바위']
 기록: ['가위'],
 낼 수 있는 패: ['가위', '보']
 기록: ['보'])
A>B
TypeError: '>' not supported between instances of 'RPS4' and 'RPS4'

B. RPS5: __gt__ 의 사용

- 관찰

a = 11
b = 22 
a.__gt__(b) # a<b 
False
b.__gt__(a) # b>a
True

- 구현

class RPS5(RPS4):
    def __gt__(self,other):
        return [self[-1], other[-1]] in [["가위","보"], ["바위","가위"], ["보","바위"]]
A = RPS5(['가위','바위'])
B = RPS5(['가위','보'])

1회대결

A.act()
B.act()
'보'
A,B
(낼 수 있는 패: ['가위', '바위']
 기록: ['가위'],
 낼 수 있는 패: ['가위', '보']
 기록: ['보'])
A > B
True

2회대결

A.act()
B.act()
'보'
A,B
(낼 수 있는 패: ['가위', '바위']
 기록: ['가위', '가위'],
 낼 수 있는 패: ['가위', '보']
 기록: ['보', '보'])
A > B # 이건 원래 A.__gt__(B) 의 실행임 
True

- 이상태에서 A<B 를 한다면?

A < B # 이건 원래 A.__lt__(B) 의 실행임 
False
A < B, A == B, A > B
(False, False, True)

- 비교연산자 정리

특수메소드 의미
__eq__ self == other
__gt__ self > other
__lt__ self < other
__ge__ self >= other
__le__ self <= other

파이썬의 비밀10: __eq__, __gt__, __lt__, __ge__, __le__는 각각 ==, >, <, >=, <= 를 재정의한다.

10. __mul__

A. 모티브

a = 3 
b = 2 
a*b
6
a.__mul__(b)
6

- 하고싶은것: A*B 가 두 플레이어 A,B의 “대결”을 의미하도록 하고싶다!

B. RPS6: __mul__ 의 사용

- 구현

class RPS6(RPS5):
    def __init__(self,action_space):
        super().__init__(action_space)
        self.results = [] 
    def __mul__(self,other):
        self.act()
        other.act()
        if self>other:
            self.results.append("승리")
            other.results.append("패배") 
        elif self<other:
            self.results.append("패배")
            other.results.append("승리")
        else:
            self.results.append("무승부")
            other.results.append("무승부")
    def __repr__(self):
        text = super().__repr__()
        text = text + f"\n승패: {self.results}"
        return text
A = RPS6(['가위','바위'])
B = RPS6(['가위','보'])
A,B
(낼 수 있는 패: ['가위', '바위']
 기록: []
 승패: [],
 낼 수 있는 패: ['가위', '보']
 기록: []
 승패: [])
A*B
A,B
(낼 수 있는 패: ['가위', '바위']
 기록: ['바위']
 승패: ['승리'],
 낼 수 있는 패: ['가위', '보']
 기록: ['가위']
 승패: ['패배'])
A*B
A,B
(낼 수 있는 패: ['가위', '바위']
 기록: ['바위', '가위']
 승패: ['승리', '무승부'],
 낼 수 있는 패: ['가위', '보']
 기록: ['가위', '가위']
 승패: ['패배', '무승부'])

11. 가위,바위,보

(1) 플레이어A는 (가위,가위) 중 하나를 선택할 수 있고 플레이어B는 (가위,바위) 중 하나를 선택할 수 있다. 각 플레이어는 각 패 중 하나를 랜덤으로 선택하는 액션을 한다고 가정하자. 즉 아래의 상황을 가정하자.

플레이어A 플레이어B
action_space (가위,가위) (가위,바위)
prob {‘가위’: 0.5, ‘가위’: 0.5} {‘가위’:0.5,‘바위’:0.5}

아래에 해당하는 확률을 시뮬레이션을 이용하여 추정하라.

  • 플레이어A가 승리할 확률:
  • 플레이어B가 승리할 확률:
  • 플레이어A와 플레이어B가 비길 확률:

hint: 50% 확률로 b가 승리하고 50% 확률로 비긴다.

(풀이)

A = RPS6(['가위','가위'])
B = RPS6(['가위','바위'])
for _ in range(100):
    A*B
{s:A.results.count(s) for s in set(A.results)}
{'패배': 52, '무승부': 48}
{s:B.results.count(s) for s in set(B.results)}
{'무승부': 48, '승리': 52}

(2) 아래의 상황을 가정하자.

플레이어A 플레이어B
action_space (가위,가위) (가위,바위)
prob {‘가위’: 0.5, ‘가위’: 0.5} {‘가위’:0.5,‘바위’:0.5}

각 플레이어는 아래와 같은 규칙으로 가위바위보 결과에 따른 보상점수를 적립한다고 하자. - 승리: 보상점수 2점 적립 - 무승부: 보상점수 1점 적립 - 패배: 보상점수 0점 적립

100번째 대결까지 시뮬레이션을 시행하고 플레이어B가 가위를 낼 경우 얻은 보상점수의 총합과 바위를 낼 경우 얻은 보상점수의 총합을 각각 구하라. 플레이어B는 가위를 내는것이 유리한가? 바위를 내는것이 유리한가?

hint: 플레이어B는 바위를 내는 것이 유리하다.

hint: 플레이어B가 100번중에 49번 가위를 내고 51번 바위를 낸다면 플레이어B가 적립할 보상점수는 각각 아래와 같다. - 가위를 내었을 경우: 49 * 1 = 49점 - 바위를 내었을 경우: 51 * 2 = 102점 - 총 보상점수 = 49점 + 102점 = 151점

(풀이)

class RPSBase2: 
    def __init__(self,action_space):
        self.action_space = action_space 
        self.actions = [] 
        self.results = [] 
        self.score_dct = {'패배':0, '무승부':1, '승리':2}
        self.rewards = [] 
        self.prob = [1/2,1/2]        
    def act(self):
        action = np.random.choice(self.action_space,p=self.prob)
        self.actions.append(action)
        return action 
    def __repr__(self):
        text = (
            f"낼 수 있는 패: {self.action_space}\n"
            f"최근기록: {self.actions[-10:]}\n"
            f"최근승패: {self.results[-10:]}\n"
            f"확률: {dict(zip(self.action_space,self.prob))}"
        )
        return text
    def __eq__(self,other):
        return self.actions[-1] == other.actions[-1]
    def __gt__(self,other): 
        return [self.actions[-1], other.actions[-1]] in [["가위","보"], ["바위","가위"], ["보","바위"]]
    def __mul__(self,other):
        self.act()
        other.act()
        if self>other:
            self.results.append("승리")
            other.results.append("패배") 
        elif self<other:
            self.results.append("패배")
            other.results.append("승리")
        else:
            self.results.append("무승부")
            other.results.append("무승부")
        self.save_reward()
        other.save_reward()
    def save_reward(self):
        self.rewards.append(self.score_dct[self.results[-1]]) 
A = RPSBase2(['가위','가위'])
B = RPSBase2(['가위','바위'])
for t in range(100):
    A*B
df = pd.DataFrame({"A행동":A.actions,"B행동":B.actions,"A보상":A.rewards,"B보상":B.rewards})
df
A행동 B행동 A보상 B보상
0 가위 가위 1 1
1 가위 바위 0 2
2 가위 바위 0 2
3 가위 바위 0 2
4 가위 가위 1 1
... ... ... ... ...
95 가위 가위 1 1
96 가위 바위 0 2
97 가위 가위 1 1
98 가위 가위 1 1
99 가위 바위 0 2

100 rows × 4 columns

df[df.B행동 == "가위"].B보상.sum(), df[df.B행동 == "바위"].B보상.sum()
(46, 108)

(3) 아래의 상황을 가정하자.

플레이어A 플레이어B
action_space (가위,가위) (가위,바위)
prob {‘가위’: 0.5, ‘가위’: 0.5} {‘가위’:0.5,‘바위’:0.5}

100번의 대결에서 얻은 데이터를 학습하여, 이후의 대결부터는 플레이어B가 “가위” 혹은 “바위” 를 선택할 확률을 매시점 조금씩 조정한다고 가정하자. 구체적으로는 현재시점까지 얻은 보상점수의 비율로 확률을 결정한다. 예를들어 플레이어B가 100회의 대결동안 누적한 보상점수의 총합이 아래와 같다고 하자.

  • 가위를 내었을 경우 보상점수 총합 = 50점
  • 바위를 내었을 경우 보상점수 총합 = 100점

그렇다면 플레이어B는 각각 (50/150,100/150) 의 확률로 (가위,바위) 중 하나를 선택한다. 101번째 대결에 플레이어B가 가위를 내서 비겼다면 이후에는 (51/151,100/151) 의 확률로 (가위,바위) 중 하나를 선택한다. 102번째 대결에 플레이어B가 바위를 내서 이겼다면 이후에는 각각 (51/153,102/153) 의 확률로 (가위,바위) 중 하나를 선택한다. 이러한 상황을 요약하여 표로 정리하면 아래와 같다.

플레이어 B의 확률업데이트 예시

시점 점수 확률
0<= t < 100 B = {가위: 50, 바위:100} B = {가위: 50/150, 바위: 100/150}
t= 100 B= {가위: 51, 바위:100} B = {가위: 51/151, 바위: 100/151}
t=101 B = {가위: 51, 바위:102} B = {가위: 51/153, 바위: 102/153}

첫 100회 까지는 확률을 업데이트 하지 않고 (=학습하지 않고) 100~500회까지는 플레이어B가 데이터를 보고 확률을 수정한다고 하자. 즉 아래와 같은 방식으로 수정한다고 하자.

플레이어 A,B의 시점별 확률업데이트

시점 확률 학습
0<= t < 100 A = {가위: 1/2, 가위: 1/2},
B = {가위:1/2, 바위:1/2}
A = False, B = False
100<= t <500 A= {가위: 1/2, 가위:1/2},
B= {가위: ??, 바위: ??}
A = False, B = True

500번의 대결이 끝난 이후 플레이어B가 (가위,바위)를 낼 확률은 각각 얼마로 업데이트 되었는가?

hint: 시간이 지날수록 플레이어B는 (가위,바위)중 바위를 내는 쪽이 유리하다는 것을 알게 될 것이다.

(풀이)

class RPSlrn(RPSBase2):
    def learn(self):
        a0,a1 = self.action_space
        p0 = sum([r for (a,r) in zip(self.actions,self.rewards) if a == a0])/sum(self.rewards)
        p1 = sum([r for (a,r) in zip(self.actions,self.rewards) if a == a1])/sum(self.rewards)
        self.prob = [p0,p1]
A = RPSlrn(['가위','가위'])
B = RPSlrn(['가위','바위'])
for t in range(100):
    A*B 
A
낼 수 있는 패: ['가위', '가위']
최근기록: ['가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위']
최근승패: ['패배', '패배', '무승부', '무승부', '패배', '패배', '무승부', '무승부', '패배', '패배']
확률: {'가위': 0.5}
B
낼 수 있는 패: ['가위', '바위']
최근기록: ['바위', '바위', '가위', '가위', '바위', '바위', '가위', '가위', '바위', '바위']
최근승패: ['승리', '승리', '무승부', '무승부', '승리', '승리', '무승부', '무승부', '승리', '승리']
확률: {'가위': 0.5, '바위': 0.5}
# for t in range(100):
#     A*B 
for t in range(100,500):
    A*B 
    B.learn()
A
낼 수 있는 패: ['가위', '가위']
최근기록: ['가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위']
최근승패: ['패배', '패배', '패배', '패배', '패배', '패배', '패배', '무승부', '무승부', '패배']
확률: {'가위': 0.5}
B
낼 수 있는 패: ['가위', '바위']
최근기록: ['바위', '바위', '바위', '바위', '바위', '바위', '바위', '가위', '가위', '바위']
최근승패: ['승리', '승리', '승리', '승리', '승리', '승리', '승리', '무승부', '무승부', '승리']
확률: {'가위': 0.11982082866741321, '바위': 0.8801791713325868}

(4) 플레이어 C와 플레이어 D를 만들아라.

플레이어C 플레이어D
action_space (가위,바위) (가위,바위)
prob {‘가위’: 0.5, ‘바위’: 0.5} {‘가위’:0.5,‘바위’:0.5}

두 플레이어는 처음 100번의 대결 동안 플레이어 C와 플레이어 D는 둘 다 ’가위’와 ’바위’를 랜덤하게 선택한다. 즉, 어떤 규칙 없이 무작위로 선택한다. 그리고 500번째 대결까지는 문제 (3)와 같은 방식으로 확률을 수정한다.

플레이어 C,D의 시점별 확률업데이트

시점 확률 학습
0<= t <100 C = {가위: 1/2, 바위: 1/2},
D = {가위:1/2, 바위:1/2}
C = False, D = False
100<= t <500 C = {가위: ??, 바위:??},
D = {가위: ??, 바위: ??}
C = True, D = True

500번의 대결이 끝난 이후 플레이어C,D가 (가위,바위)를 낼 확률은 각각 얼마로 업데이트 되었는가?

hint: 시간이 지날수록 두 플레이어 모두 바위를 내는 쪽이 유리하다는 것을 알게 될 것이다.

(풀이)

C = RPSlrn(['가위','바위'])
D = RPSlrn(['가위','바위'])
for t in range(100):
    C*D 
C
낼 수 있는 패: ['가위', '바위']
최근기록: ['바위', '가위', '바위', '가위', '가위', '가위', '바위', '가위', '바위', '바위']
최근승패: ['무승부', '무승부', '승리', '무승부', '무승부', '패배', '무승부', '패배', '승리', '무승부']
확률: {'가위': 0.5, '바위': 0.5}
D
낼 수 있는 패: ['가위', '바위']
최근기록: ['바위', '가위', '가위', '가위', '가위', '바위', '바위', '바위', '가위', '바위']
최근승패: ['무승부', '무승부', '패배', '무승부', '무승부', '승리', '무승부', '승리', '패배', '무승부']
확률: {'가위': 0.5, '바위': 0.5}
# for t in range(100):
#     C*D 
for t in range(100,500):
    C*D 
    C.learn()
    D.learn()
C
낼 수 있는 패: ['가위', '바위']
최근기록: ['바위', '바위', '바위', '바위', '바위', '바위', '바위', '바위', '바위', '바위']
최근승패: ['무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '승리', '무승부']
확률: {'가위': 0.0796812749003984, '바위': 0.9203187250996016}
D
낼 수 있는 패: ['가위', '바위']
최근기록: ['바위', '바위', '바위', '바위', '바위', '바위', '바위', '바위', '가위', '바위']
최근승패: ['무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '패배', '무승부']
확률: {'가위': 0.08032128514056225, '바위': 0.9196787148594378}

(5) 새로운 플레이어 E와 F를 생각하자. 플레이어E와 플레이어F는 각각 (가위,바위) 그리고 (가위,보) 중 하나를 선택할 수 있다고 가정하자.

플레이어E 플레이어F
action_space (가위,바위) (가위,보)
prob {‘가위’: 0.5, ‘바위’: 0.5} {‘가위’:0.5,‘바위’:0.5}

시뮬레이션 대결결과를 이용하여 아래의 확률을 근사적으로 추정하라.

  • 플레이어E가 승리할 확률:
  • 플레이어F가 승리할 확률:
  • 플레이어E와 플레이어F가 비길 확률:

hint: 플레이어E가 가위를 낸다면 최소한 지지는 않기 때문에 플레이어E가 좀 더 유리한 패를 가지고 있다. 따라서 플레이어E의 결과가 더 좋을 것이다.

(풀이)

E = RPSlrn(['가위','바위'])
F = RPSlrn(['가위','보'])
for _ in range(1000):
    E*F 
{s:E.results.count(s) for s in set(E.results)}
{'패배': 256, '무승부': 239, '승리': 505}
{s:F.results.count(s) for s in set(F.results)}
{'패배': 505, '무승부': 239, '승리': 256}

(6) (5)와 동일한 두 명의 플레이어E, F를 생각하자.

플레이어E 플레이어F
action_space (가위,바위) (가위,보)
prob {‘가위’: 0.5, ‘바위’: 0.5} {‘가위’:0.5,‘보’:0.5}

두 플레이어는 100회까지는 랜덤으로 자신의 패를 선택한다. 그리고 101회부터 500회까지는 플레이어F만 데이터로 부터 학습을 하여 수정된 확률을 사용한다.

플레이어 E,F의 시점별 확률업데이트

시점 확률 학습
0<= t <100 E = {가위: 1/2, 바위: 1/2},
F = {가위:1/2, 보:1/2}
E = False, F = False
100<= t <500 E = {가위: 1/2, 바위: 1/2},
F = {가위: ??, 보: ??}
E = False, F = True

500번의 대결이 끝나고 플레이어F가 (가위,보)를 선택하는 확률이 어떻게 업데이트 되어있는가?

hint: 플레이어F는 보를 내는 것이 낫다고 생각할 것이다. (가위를 내면 지거나 비기지만 보를 내면 지거나 이긴다.)

(풀이)

E = RPSlrn(['가위','바위'])
F = RPSlrn(['가위','보'])
for t in range(100):
    E*F 
E
낼 수 있는 패: ['가위', '바위']
최근기록: ['바위', '바위', '바위', '가위', '가위', '가위', '바위', '가위', '가위', '가위']
최근승패: ['패배', '승리', '승리', '승리', '승리', '무승부', '패배', '무승부', '승리', '승리']
확률: {'가위': 0.5, '바위': 0.5}
F
낼 수 있는 패: ['가위', '보']
최근기록: ['보', '가위', '가위', '보', '보', '가위', '보', '가위', '보', '보']
최근승패: ['승리', '패배', '패배', '패배', '패배', '무승부', '승리', '무승부', '패배', '패배']
확률: {'가위': 0.5, '보': 0.5}
# for t in range(100):
#     E*F 
for t in range(100,500):
    E*F 
    F.learn()
E
낼 수 있는 패: ['가위', '바위']
최근기록: ['가위', '바위', '가위', '바위', '바위', '바위', '바위', '가위', '가위', '가위']
최근승패: ['승리', '패배', '승리', '패배', '패배', '패배', '패배', '승리', '승리', '승리']
확률: {'가위': 0.5, '바위': 0.5}
F
낼 수 있는 패: ['가위', '보']
최근기록: ['보', '보', '보', '보', '보', '보', '보', '보', '보', '보']
최근승패: ['패배', '승리', '패배', '승리', '승리', '승리', '승리', '패배', '패배', '패배']
확률: {'가위': 0.20884520884520885, '보': 0.7911547911547911}

(7) (6)번의 플레이어E와 플레이어F가 500회~1000회까지 추가로 게임을 한다. 이번에는 플레이어E만 데이터로부터 학습한다. 1000회까지 대결을 끝낸 이후 플레이어E가 (가위,바위)를 내는 확률은 어떻게 업데이트 되었는가?

플레이어 E,F의 시점별 확률업데이트

시점 확률 학습
0<= t <100 E = {가위: 1/2, 바위: 1/2},
F = {가위:1/2, 보:1/2}
E = False, F = False
100<= t <500 E = {가위: 1/2, 바위: 1/2},
F = {가위: ??, 보: ??}
E = False, F = True
- - -
500<= t < 1000 E = {가위: ??, 바위: ??},
F = {가위: ??, 보: ??}
E = True, F = False

hint: 플레이어F는 보를 내도록 학습되어 있다. 따라서 플레이어E가 바위를 내면 지고 가위를 내면 이길것이다. 따라서 플레이어E는 가위가 유리하다고 생각할 것이다.

(풀이)

# for t in range(100):
#     E*F 
# for t in range(100,500):
#     E*F 
#     F.learn()
for t in range(500,1000):
    E*F 
    E.learn()
E
낼 수 있는 패: ['가위', '바위']
최근기록: ['가위', '가위', '가위', '바위', '가위', '가위', '가위', '바위', '가위', '가위']
최근승패: ['무승부', '승리', '승리', '패배', '승리', '승리', '승리', '패배', '승리', '무승부']
확률: {'가위': 0.8745493871665465, '바위': 0.1254506128334535}
F
낼 수 있는 패: ['가위', '보']
최근기록: ['가위', '보', '보', '보', '보', '보', '보', '보', '보', '가위']
최근승패: ['무승부', '패배', '패배', '승리', '패배', '패배', '패배', '승리', '패배', '무승부']
확률: {'가위': 0.20884520884520885, '보': 0.7911547911547911}

(8) (7)번의 플레이어E와 플레이어F가 1000회~50000회까지 추가로 게임을 한다. 이번에는 플레이어F만 데이터로부터 학습한다. 50000회까지 대결을 끝낸 이후 플레이어F가 (가위,보)를 내는 확률은 어떻게 업데이트 되었는가?

플레이어 E,F의 시점별 확률업데이트

시점 확률 학습
0<= t <100 E = {가위: 1/2, 바위: 1/2},
F = {가위:1/2, 보:1/2}
E = False, F = False
100<= t <500 E = {가위: 1/2, 바위: 1/2},
F = {가위: ??, 보: ??}
E = False, F = True
500<= t < 1000 E = {가위: ??, 바위: ??},
F = {가위: ??, 보: ??}
E = True, F = False
- - -
1000<= t < 50000 E = {가위: ??, 바위: ??},
F = {가위: ??, 보: ??}
E = False, F = True

hint: 플레이어F는 원래 보가 유리하다고 생각하여 보를 자주 내도록 학습되었다. 하지만 플레이어E가 그러한 플레이어F의 성향을 파악하고 가위를 주로 내도록 학습하였다. 플레이어F는 그러한 플레이어E의 성향을 다시 파악하여 이번에는 가위을 자주 내는 것이 유리하다고 생각할 것이다.

(풀이)

# for t in range(100):
#     E*F 
# for t in range(100,500):
#     E*F 
#     F.learn()
# for t in range(500,1000):
#     E*F 
#     E.learn()
for t in range(1000,50000):
    E*F 
    F.learn()
E
낼 수 있는 패: ['가위', '바위']
최근기록: ['가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위']
최근승패: ['무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부']
확률: {'가위': 0.8745493871665465, '바위': 0.1254506128334535}
F
낼 수 있는 패: ['가위', '보']
최근기록: ['가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위', '가위']
최근승패: ['무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부', '무승부']
확률: {'가위': 0.9515378389316066, '보': 0.048462161068393364}

(9) 플레이어E와 플레이어F의 대결기록을 초기화 한다. 이번에는 플레이어F가 항상 (3/4)의 확률로 가위를 (1/4)의 확률로 보를 낸다고 가정한다.

플레이어E 플레이어F
action_space (가위,바위) (가위,보)
prob {‘가위’: 0.5, ‘바위’: 0.5} {‘가위’:3/4,‘보’:1/4}

플레이어E는 처음 1000번의 대결까지는 랜덤으로 (가위,바위)중 하나를 내고, 이후의 1000번의 대결에서는 데이터를 학습하여 수정한 확률을 사용한다고 하자. 대결이후에 플레이어E가 (가위,바위)를 내는 확률이 어떻게 업데이트 되어있는가?

시점 확률 학습
0<= t <1000 E = {가위: 1/2, 바위: 1/2},
F = {가위: 3/4, 보: 1/4}
E = False, F = False
1000<= t <2000 E = {가위: ??, 바위: ??},
F = {가위: 3/4, 보: 1/4}
E = True, F = False

(풀이)

E = RPSlrn(['가위','바위'])
F = RPSlrn(['가위','보'])
F.prob = [3/4, 1/4] 
E
낼 수 있는 패: ['가위', '바위']
최근기록: []
최근승패: []
확률: {'가위': 0.5, '바위': 0.5}
F
낼 수 있는 패: ['가위', '보']
최근기록: []
최근승패: []
확률: {'가위': 0.75, '보': 0.25}
for t in range(1000):
    E*F 
for t in range(1000,2000):
    E*F 
    E.learn()
E
낼 수 있는 패: ['가위', '바위']
최근기록: ['가위', '가위', '가위', '가위', '가위', '바위', '가위', '바위', '바위', '가위']
최근승패: ['무승부', '무승부', '무승부', '무승부', '승리', '승리', '무승부', '승리', '패배', '승리']
확률: {'가위': 0.42478510028653294, '바위': 0.5752148997134671}
F
낼 수 있는 패: ['가위', '보']
최근기록: ['가위', '가위', '가위', '가위', '보', '가위', '가위', '가위', '보', '보']
최근승패: ['무승부', '무승부', '무승부', '무승부', '패배', '패배', '무승부', '패배', '승리', '패배']
확률: {'가위': 0.75, '보': 0.25}

E가 가위를 냈을 경우 보상의 기대값은 1*3/4 + 2*1/4 = 5/4 이고 바위를 냈을 경우 보상의 기대값은 2*3/4 + 0*1/4 = 6/4 이므로 바위가 유리하다. 따라서 점점 바위를 내도록 학습한다.

(10) 플레이어E와 플레이어F의 대결기록을 초기화 한다. 이번에는 플레이어F가 항상 (2/3)의 확률로 가위를 (1/3)의 확률로 보를 낸다고 가정한다.

플레이어E 플레이어F
action_space (가위,바위) (가위,보)
prob {‘가위’: 0.5, ‘바위’: 0.5} {‘가위’:2/3,‘보’:1/3}

플레이어E는 처음 1000번의 대결까지는 랜덤으로 (가위,바위)중 하나를 내고, 이후의 1000번의 대결에서는 데이터를 학습하여 수정한 확률을 사용한다고 하자. 대결이후에 플레이어E가 (가위,바위)를 내는 확률이 어떻게 업데이트 되어있는가?

시점 확률 학습
0<= t <1000 E = {가위: 1/2, 바위: 1/2},
F = {가위: 2/3, 보: 1/3}
E = False, F = False
1000<= t <2000 E = {가위: ??, 바위: ??},
F = {가위: 2/3, 보: 1/3}
E = True, F = False

(풀이)

E = RPSlrn(['가위','바위'])
F = RPSlrn(['가위','보'])
F.prob = [2/3, 1/3] 
E
낼 수 있는 패: ['가위', '바위']
최근기록: []
최근승패: []
확률: {'가위': 0.5, '바위': 0.5}
F
낼 수 있는 패: ['가위', '보']
최근기록: []
최근승패: []
확률: {'가위': 0.6666666666666666, '보': 0.3333333333333333}
for t in range(1000):
    E*F 
for t in range(1000,2000):
    E*F 
    E.learn()
E
낼 수 있는 패: ['가위', '바위']
최근기록: ['가위', '바위', '가위', '가위', '가위', '가위', '가위', '가위', '바위', '바위']
최근승패: ['무승부', '패배', '무승부', '무승부', '무승부', '승리', '무승부', '승리', '승리', '승리']
확률: {'가위': 0.4884233737596472, '바위': 0.5115766262403528}
F
낼 수 있는 패: ['가위', '보']
최근기록: ['가위', '보', '가위', '가위', '가위', '보', '가위', '보', '가위', '가위']
최근승패: ['무승부', '승리', '무승부', '무승부', '무승부', '패배', '무승부', '패배', '패배', '패배']
확률: {'가위': 0.6666666666666666, '보': 0.3333333333333333}

E가 가위를 냈을 경우 보상의 기대값은 1*2/3 + 2*1/3 = 4/3 이고 바위를 냈을 경우 보상의 기대값은 2*2/3 + 0*1/3 = 4/3 이므로 가위를 내나 바위를 내나 상관이 없다.

(11) 플레이어E와 플레이어F의 대결기록을 초기화 한다. 이번에는 플레이어E가 항상 (2/3)의 확률로 가위를 (1/3)의 확률로 바위를 낸다고 가정한다.

플레이어E 플레이어F
action_space (가위,바위) (가위,보)
prob {‘가위’: 2/3, ‘바위’: 1/3} {‘가위’: 0.5,‘보’: 0.5}

플레이어F는 처음 1000번의 대결까지는 랜덤으로 (가위,바위)중 하나를 내고, 이후의 1000번의 대결에서는 데이터를 학습하여 수정한 확률을 사용한다고 하자. 대결이후에 플레이어F가 (가위,바위)를 내는 확률이 어떻게 업데이트 되어있는가?

시점 확률 학습
0<= t <1000 E = {가위: 2/3, 바위: 1/3},
F = {가위: 0.5, 보: 0.5}
E = False, F = False
1000<= t <2000 E = {가위: 2/3, 바위: 1/3},
F = {가위: ??, 보: ??}
E = False, F = True

(풀이)

E = RPSlrn(['가위','바위'])
F = RPSlrn(['가위','보'])
E.prob = [2/3, 1/3] 
E
낼 수 있는 패: ['가위', '바위']
최근기록: []
최근승패: []
확률: {'가위': 0.6666666666666666, '바위': 0.3333333333333333}
F
낼 수 있는 패: ['가위', '보']
최근기록: []
최근승패: []
확률: {'가위': 0.5, '보': 0.5}
for t in range(1000):
    E*F 
for t in range(1000,2000):
    E*F 
    F.learn()
E
낼 수 있는 패: ['가위', '바위']
최근기록: ['가위', '가위', '가위', '바위', '가위', '바위', '가위', '바위', '바위', '가위']
최근승패: ['무승부', '무승부', '무승부', '승리', '승리', '패배', '승리', '패배', '승리', '무승부']
확률: {'가위': 0.6666666666666666, '바위': 0.3333333333333333}
F
낼 수 있는 패: ['가위', '보']
최근기록: ['가위', '가위', '가위', '가위', '보', '보', '보', '보', '가위', '가위']
최근승패: ['무승부', '무승부', '무승부', '패배', '패배', '승리', '패배', '승리', '패배', '무승부']
확률: {'가위': 0.4801498127340824, '보': 0.5198501872659176}

F가 가위를 냈을 경우 보상의 기대값은 1*2/3 + 0*1/3 = 2/3 이고 바위를 냈을 경우 보상의 기대값은 0*2/3 + 2*1/3 = 2/3 이므로 가위를 내나 바위를 내나 상관이 없다.