14wk-1: 클래스공부 4단계 – 파이썬의 비밀 (2)

Author

최규빈

Published

June 5, 2023

강의영상

youtube: https://youtube.com/playlist?list=PLQqh36zP38-xEFWxQ9YvfTo0wBN4CUZlP

imports

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

예비학습

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

- 예시1: UpJump ver1

class UpJump:
    def __init__(self):
        self.value = 0
    def up(self):
        self.value = self.value + 1  
    def __repr__(self):
        return str(self.value)
a = UpJump()
a.up()
a
1
a.jump(2)
AttributeError: 'UpJump' object has no attribute 'jump'
  • 점프는 아직 구현하지 않음.

- 예시2: UpJump ver2

class UpJump_Ver2:
    def __init__(self):
        self.value = 0
    def up(self):
        self.value = self.value + 1  
    def jump(self,jump_size):
        self.value = self.value + jump_size
    def __repr__(self):
        return str(self.value)        
a = UpJump_Ver2()
a.up()
a.jump(2)
a
3

- 예시3: UpJump ver2의 다른 구현

class UpJump_Ver2(UpJump):
    def jump(self,jump_size):
        self.value = self.value + jump_size    
a = UpJump_Ver2()
a.up()
a.jump(2)
a
3

꿀팁

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

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

- 사용예시

class UpJump_Ver3(UpJump_Ver2):
    def __repr__(self):
        return '현재의 이 인스턴스의 value는 {}입니다.'.format(self.value)
a=UpJump_Ver3()
a
현재의 이 인스턴스의 value는 0입니다.
a.jump(50)
a
현재의 이 인스턴스의 value는 50입니다.

클래스공부 4단계: 파이썬의 비밀 (2)

비밀6: __add__

motive

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

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)
3
b.__add__(a)
3
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의 기능도 바뀔 것이다.

Student_Ver2: __add__의 사용

- Student 클래스 선언

class Student:
    def __init__(self, age=20.0, semester=0): 
        self.age = age
        self.semester = semester
        print("입학을 축하합니다. 당신의 나이는 {}이고 현재 학기는 {}학기 입니다.".format(self.age,self.semester))
    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 
    def _repr_html_(self):
        html_str = """
        나이: {} <br/>
        학기: {} <br/>
        """
        return html_str.format(self.age,self.semester)

- 사용

boram = Student()
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
boram
나이: 20.0
학기: 0
boram + '등록'
boram
나이: 20.5
학기: 1
boram + '휴학'
boram
나이: 21.0
학기: 1

- 잘못된 사용

boram = Student()
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
boram + '등록'+ '휴학' + '등록' + '휴학'
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'
  • 에러가?

- 올바른 코드

class Student_Ver2:
    def __init__(self, age=20.0, semester=0): 
        self.age = age
        self.semester = semester
        print("입학을 축하합니다. 당신의 나이는 {}이고 현재 학기는 {}학기 입니다.".format(self.age,self.semester))
    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 
    def _repr_html_(self):
        html_str = """
        나이: {} <br/>
        학기: {} <br/>
        """
        return html_str.format(self.age,self.semester)
boram = Student_Ver2()
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
boram + "등록"
나이: 23.5
학기: 5

Student_Ver2의 다른구현1

class Student_Ver2(Student):
    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
boram = Student_Ver2()
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
boram + '등록'+ '휴학' + '등록' + '휴학'
나이: 22.0
학기: 2

Student_Ver2의 다른구현2

  • 요거까지는 지금 몰라도 됩니다
class Student_Ver2(Student):
    def __add__(self,registration_status): 
        super().__add__(registration_status)
        return self
boram = Student_Ver2()
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
boram + '등록' + '휴학' + '등록' + '휴학'
나이: 22.0
학기: 2

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

비밀7: __getitem__

motive

a=[11,22,32]
a[0]
11
  • 이거 좋아보인다?
a.__getitem__(0) # a[0]
11
  • 이런거였어?

RPS_Ver2: __getitem__의 사용

class RPS:
    def __init__(self,candidate):
        self.candidate = candidate
        self.actions = list() 
    def pick(self):
        self.actions.append(np.random.choice(self.candidate))        
    def _repr_html_(self):
        html_str = """
        낼 수 있는 패: {} <br/>
        기록: {}
        """        
        return html_str.format(self.candidate,self.actions)
a = RPS(['가위','바위','보'])
a.pick()
a.pick()
a.actions
['바위', '바위']
a[0], a[1]
TypeError: 'RPS' object is not subscriptable
  • 결과가 ‘가위’, ’바위’로 나오면 좋겠다.. 물론 지금은 불가능해
class RPS_Ver2(RPS):
    def __getitem__(self,item):
        return self.actions[item]
a = RPS_Ver2(['가위','바위','보'])
a.pick()
a.pick()
a.pick()
a
낼 수 있는 패: ['가위', '바위', '보']
기록: ['가위', '가위', '바위']
a[0]
'가위'
a[:2]
['가위', '가위']

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

비밀8: __setitem__

motive

a = RPS_Ver2(['가위','바위'])
a
낼 수 있는 패: ['가위', '바위']
기록: []
a.pick()
a.pick()
a
낼 수 있는 패: ['가위', '바위']
기록: ['가위', '바위']
a[0]
'가위'
a[0] = '보' 
TypeError: 'RPS_Ver2' object does not support item assignment
  • 경우에 따라서는 이런 문법이 필요하기도 하다.

RPS_Ver3: __setitem__ 사용

- 관찰

lst = [1,2,3]
lst.__setitem__(0,11)
lst
[11, 2, 3]

- RPS예제

class RPS_Ver3(RPS_Ver2):
    def __setitem__(self,index,val):
        self.actions[index] = val
a=RPS_Ver3(['가위','바위','보'])
a.pick()
a
낼 수 있는 패: ['가위', '바위', '보']
기록: ['보']
a[0]='가위'
a
낼 수 있는 패: ['가위', '바위', '보']
기록: ['가위']

파이썬의 비밀8: a[0]=11a.__setitem__(0,11)의 축약형이다.

비밀9: __len__

motive

a= RPS_Ver3(['가위','바위','보'])
a
낼 수 있는 패: ['가위', '바위', '보']
기록: []
a.pick()
a.pick()
a.pick()
a
낼 수 있는 패: ['가위', '바위', '보']
기록: ['보', '가위', '바위']
a[0],a[1],a[2]
('보', '가위', '바위')
len(a)
TypeError: object of type 'RPS_Ver3' has no len()

RPS_Ver4: __len__ 의 사용

class RPS_Ver4(RPS_Ver2):
    def __len__(self):
        return len(self.actions)
a = RPS_Ver4(['가위','바위','보'])
a
낼 수 있는 패: ['가위', '바위', '보']
기록: []
a.pick()
a.pick()
a.pick()
a.pick()
a
낼 수 있는 패: ['가위', '바위', '보']
기록: ['가위', '가위', '보', '바위']
a.__len__() # len(a.actions)
4
len(a) # a.__len__()
4

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

비밀10: 비교연산자

motive1

a = RPS_Ver4(['가위'])
b = RPS_Ver4(['가위'])
a.pick()
b.pick()
a
낼 수 있는 패: ['가위']
기록: ['가위']
b
낼 수 있는 패: ['가위']
기록: ['가위']
a == b
False
  • 가위 == 가위 이면 True가 나오면 좋겠구만..
a[-1] == b[-1] 
True
  • 이렇게 하면 되긴하지..

RPS_Ver5: __eq__ 의 사용

- 관찰

a = 1
a.__eq__(1) # a == 1
True
a.__eq__(2) # a == 2 
False

- 구현

class RPS_Ver5(RPS_Ver4):
    def __eq__(self,other):
        return self[-1] == other[-1]
a = RPS_Ver5(['가위','바위'])
b = RPS_Ver5(['가위','바위'])

- 1회 대결

a.pick()
b.pick()
a
낼 수 있는 패: ['가위', '바위']
기록: ['바위']
b
낼 수 있는 패: ['가위', '바위']
기록: ['바위']
a == b
True

- 2회 대결

a.pick()
b.pick()
a
낼 수 있는 패: ['가위', '바위']
기록: ['바위', '가위']
b
낼 수 있는 패: ['가위', '바위']
기록: ['바위', '가위']
a == b
True

- 3회 대결

a.pick()
b.pick()
a
낼 수 있는 패: ['가위', '바위']
기록: ['바위', '가위', '가위']
b
낼 수 있는 패: ['가위', '바위']
기록: ['바위', '가위', '바위']
a == b
False

motive2

a[-1], b[-1]
('바위', '바위')
a > b
TypeError: '>' not supported between instances of 'RPS_Ver5' and 'RPS_Ver5'
  • False 가 나오면 좋겟구만..

RPS_Ver6: __gt__ 의 사용

- 관찰

a = 1
a.__gt__(1) # a>1
False
a.__gt__(0) # a>0 
True

- 예비학습

[a[-1],b[-1]]
['가위', '바위']
[a[-1],b[-1]] in [['가위','보'],['바위','가위'],['보','바위']]
False

- 구현

class RPS_Ver6(RPS_Ver5):
    def __gt__(self,other):
        return [self[-1],other[-1]] in [['가위','보'],['바위','가위'],['보','바위']]
a = RPS_Ver6(['가위','바위','보'])
b = RPS_Ver6(['가위','바위','보'])

- 1회대결

a.pick() 
b.pick()
a
낼 수 있는 패: ['가위', '바위', '보']
기록: ['바위']
b
낼 수 있는 패: ['가위', '바위', '보']
기록: ['바위']
a>b, a==b
(False, True)

- 2회대결

a.pick() 
b.pick()
a
낼 수 있는 패: ['가위', '바위', '보']
기록: ['바위', '보']
b
낼 수 있는 패: ['가위', '바위', '보']
기록: ['바위', '바위']
a>b, a==b
(True, False)

motive3

a
낼 수 있는 패: ['가위', '바위', '보']
기록: ['바위', '보']
b
낼 수 있는 패: ['가위', '바위', '보']
기록: ['바위', '바위']
a>=b
TypeError: '>=' not supported between instances of 'RPS_Ver6' and 'RPS_Ver6'
  • True로 나오면 좋겠구만..
a < b
False
  • 이것도 정의 새로 해야겠는걸?

RPS_Ver7: __ge__, __lt__, __le__ 의 사용

- 관찰: 생략

- 비교연산자 정리

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

- 구현

class RPS_Ver7(RPS_Ver6):
    def __ge__(self,other):
        return (self == other) or (self > other)
    def __lt__(self,other):
        return not (self >= other)
    def __le__(self,other):
        return (self == other) or (self < other)
a = RPS_Ver7(['가위','바위','보'])
b = RPS_Ver7(['가위','바위','보'])
a.pick()
b.pick()
a
낼 수 있는 패: ['가위', '바위', '보']
기록: ['보']
b
낼 수 있는 패: ['가위', '바위', '보']
기록: ['가위']
a==b, a>b, a<b, a>=b, a<=b 
(False, False, True, False, True)

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

비밀11: __call__ \((\star\star\star)\)

함수공부

- 다시 함수를 공부해봅시다.

def f(x):
    return x+1 
f(3)
4

- 함수의 사용방법?

  • 입력으로 변수 x를 받음 = 입력으로 인스턴스 x를 받음.
  • 출력으로 변수 x+1을 리턴 = 출력으로 인스턴스 x+1을 리턴.

- 사실1: 파이썬에서 함수는 인스턴스를 입력으로 받고 인스턴스를 출력한다.

- 함수의 자료형?

?f
Signature: f(x)
Docstring: <no docstring>
File:      ~/Dropbox/07_lectures/PP2023/posts/03_Class/<ipython-input-264-76b767537386>
Type:      function
  • type이 function이다.
  • f는 function class의 instance이다.
  • 결국 f 역시 하나의 오브젝트에 불과하다.

- 사실2: 함수도 결국 인스턴스이다. -> 함수의 입력으로 함수를 쓸 수도 있고 함수의 출력으로 함수가 나올 수도 있다.

“파이썬의 모든 것은 오브젝트이다.” <– 명언처럼 외우세여

함수도 인스턴스다

(예제1) 숫자입력, 함수출력

def f(a):
    def _f(x):
        return (x-a)**2 
    return _f
g=f(10) # g(x)=(x-10)**2 
g(2) # (2-10)**2 = 64
64
  • 해석: \(f(a)\)\(a\)를 입력으로 받고 \(g(x)=(x-a)^2\)라는 함수를 리턴해주는 함수

(예제1)의 다른표현: 익명함수 lambda

def f(x):
    return x+1
f= lambda x: x+1

표현1

def f(a):
    _f = lambda x: (x-a)**2 ### lambda x: (x-a)**2 가 실행되는 순간 함수오브젝트가 만들어지고 그것이 _f 로 저장됨 
    return _f
g=f(10) # g(x)=(x-10)**2 
g(3) # (3-10)**2 = 49
49

표현2

def f(a):
    return lambda x: (x-a)**2 
g=f(10) # g(x)=(x-10)**2 
g(3) # (3-10)**2 = 49
49
  • lambda x: (x-a)**2\(\text{lambda}(x) = (x-a)^2\)의 느낌으로 기억하면 외우기 쉽다.
  • lambda x: (x-a)**2 는 “아직 이름이 없는 함수오브젝트를 (가칭 lambda 라고 하자) 만들고 기능은 x를 입력으로 하고 (x-a)**2를 출력하도록 하자” 라는 뜻으로 해석하면 된다.

(예제2) 함수입력, 숫자출력

def d(f,x): # 함수를 입력을 받는 함수를 정의 
    h=0.000000000001
    return (f(x+h)-f(x))/h 
d(f,4) # f'(4) = 2*4 = 8
8.000711204658728

(예제3) 함수입력, 함수출력

def f(x): 
    return x**2 
def derivate(f): 
    # step1: 함수오브젝트 f는 입력으로 받은상태 
    # step2: 함수오브젝트 f를 이용하여 df라는 함수를 정의 
    def df(x): 
        h=0.000000000001
        return (f(x+h)-f(x))/h 
    # step3: 정의된 df를 리턴
    return df
ff = derivate(f)
ff(7) # f의 도함수
14.004797321831575

원래함수 시각화

x = np.linspace(-1,1,100)
plt.plot(x,f(x))

도함수 시각화

x = np.linspace(-1,1,100)
plt.plot(x,f(x),label=r'$f(x)=x^2$')
plt.plot(x,ff(x),label=r'$ff(x)=2x$')
plt.legend()
<matplotlib.legend.Legend at 0x7f4c15ef3d00>

(예제3)의 다른표현

def f(x): 
    return x**2 
def derivate(f): 
    h=0.000000000001
    return lambda x: (f(x+h)-f(x))/h 
ff = derivate(f)
ff(10)
20.00888343900442
x = np.linspace(-1,1,100)
plt.plot(x,f(x),label=r'$f(x)=x^2$')
plt.plot(x,ff(x),label=r'$ff(x)=2x$')
plt.legend()
<matplotlib.legend.Legend at 0x7f4c1686aa90>

(예제4) 함수들의 리스트

flst = [lambda x: x, lambda x: x**2, lambda x: x**3] 
flst
[<function __main__.<lambda>(x)>,
 <function __main__.<lambda>(x)>,
 <function __main__.<lambda>(x)>]
for f in flst:
    print(f(2))
2
4
8
for f in flst: 
    plt.plot(x,f(x),'--')

위의코드는 아래와 같음

plt.plot(x,(lambda x: x)(x),'--')
plt.plot(x,(lambda x: x**2)(x),'--')
plt.plot(x,(lambda x: x**3)(x),'--')

정리

- 지금까지 개념

  • 함수: 변수를 입력으로 받아서 변수를 출력하는 개념
  • 변수: 어떠한 값을 저장하는 용도로 쓰거나 함수의 입력 혹은 출력으로 사용함

- R과 구별되는 파이썬의 독특한 테크닉 (부제: 파이썬에서 함수를 잘 쓰려면?)

  • 변수든 함수이든 둘다 인스턴스임. (즉 어떠한 클래스에서 찍힌 똑같은 오브젝트라는 의미)
  • 변수를 함수처럼: 메소드 lst.append(1)은 마치 append(lst,1)와 같은 함수로 쓸 수 있음
  • 함수를 변수처럼(\(\star\)): 함수자체를 함수의 입력으로 혹은 출력으로 쓸 수도 있음. 함수를 특정 값처럼 생각해서 함수들의 list를 만들 수도 있다.

callable object

- 함수 오브젝트의 비밀?

f = lambda x: x+1 
a=11
set(dir(f)) & {'__call__'}
{'__call__'}
  • 함수 오브젝트에는 숨겨진 기능 __call__이 있다.
f.__call__(3) # f(3)
4
f.__call__(4) # f(4)
5
  • 여기에 우리가 정의한 내용이 있다.

- 함수처럼 쓸 수 없는 인스턴스는 단지 call이 없는 것일 뿐이다.

class Klass: 
    def __init__(self):
        self.name='guebin'
a=Klass()
a()
TypeError: 'Klass' object is not callable
  • a는 callable이 아니라고 한다.
class Klass2(Klass): 
    def __call__(self):
        print(self.name)
b=Klass2()
b()
guebin
  • b는 callable object! 즉 숨겨진 메서드로 __call__를 가진 오브젝트!
  • Klass는 callable object를 만들지 못하지만 Klass2는 callable object를 만든다.

- 클래스로 함수를 만들기 (=함수인스턴스를 찍어내는 클래스 설계)

class AddConstant: 
    def __init__(self,const):
        self.const = const 
    def __call__(self,x):
        return x + self.const
f = AddConstant(3) # callable object생성, f.const에는 3이 저장되어있음. 
f(5) # f.const 와 5를 더하는 기능을 수행, # 즉 f(x) = x+3 을 수행함
8
f(10) 
13

- 클래스도 그러고 보니까 오브젝트 아니었나?

Student_Ver2?
Init signature: Student_Ver2(age=20.0, semester=0)
Docstring:      <no docstring>
Type:           type
Subclasses:     
  • 이것도 type 이라는 또 다른 클래스에서 (클래스를 찍는 클래스) 찍힌 오브젝트이구나..

- 클래스 “오브젝트”도 함수 “오브젝트”처럼 Student_Ver2()와 같이 사용하면 인스턴스를 만들었음. -> Student_Ver2.__call__()Student_Ver2()와 같은 역할을 할 것이다.

일반적인 구현에서 “클래스 \(\to\) 인스턴스” 과정

boram = Student_Ver2(age=20.0,semester=0)
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
boram + '등록' + '휴학' + '휴학'
나이: 21.5
학기: 1

공부를 위해 call을 이용해본 “클래스 \(\to\) 인스턴스” 과정

boram = Student_Ver2.__call__(age=20.0,semester=0) #
# boram = Student_Ver2(age=20.0,semester=0)
입학을 축하합니다. 당신의 나이는 20.0이고 현재 학기는 0학기 입니다.
boram + '등록' + '휴학' + '휴학'
나이: 21.5
학기: 1

파이썬의 비밀11: f()와 같이 쓸 수 있는 오브젝트는 단지 __call__ 이 정의되어있는 오브젝트일 뿐이다.

숙제 - 2022년 파이썬 입문 기말고사 참고

(1) 플레이어A는 (가위,가위) 중 하나를 선택할 수 있고 플레이어B는 (가위,바위) 중 하나를 선택할 수 있다. 각 플레이어는 각 패 중 하나를 랜덤으로 선택하는 액션을 한다고 가정하자. 아래에 해당하는 확률을 시뮬레이션을 이용하여 추정하라.

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

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

(2) 문제 (1)과 같이 아래의 상황을 가정하자.

플레이어A 플레이어B
각 플레이어가 낼 수 있는 패 (candidate) (가위,가위) (가위,바위)
각 패를 선택할 확률 (prob) (0.5,0.5) (0.5,0.5)

각 플레이어는 아래와 같은 규칙으로 가위바위보 결과에 따른 보상점수를 적립한다고 하자.

  • 승리: 보상점수 2점 적립
  • 무승부: 보상점수 1점 적립
  • 패배: 보상점수 0점 적립

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

Note

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

hint2: 플레이어B가 100번중에 49번 가위를 내고 51번 바위를 낸다면 플레이어B가 적립할 보상점수는 각각 아래와 같다.

  • 가위를 내었을 경우: 49 * 1 = 49점
  • 바위를 내었을 경우: 51 * 2 = 102점
  • 총 보상점수 = 49점 + 102점 = 151점

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

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

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

시점 플레이어B가 가위를 냈을 경우 얻은 점수 총합 플레이어B가 바위를 냈을 경우 얻은 점수 총합 t+1시점에서 플레이어B가 (가위,바위)를 낼 확률
t=100 50 100 (50/150, 100/150)
t=101 51 100 (51/151, 100/151)
t=102 51 102 (51/153, 102/153)

이러한 방식으로 500회까지 게임을 진행하며 확률을 수정하였을 경우 501번째 대결에서 플레이어B가 (가위,바위)를 낼 확률은 각각 얼마인가?

Note

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


앞으로 아래와 같은 용어를 사용한다.

정의: 학습모드상태

어떠한 플레이어가 양손 중 하나를 선택하는 확률을 데이터를 바탕으로 매 순간 업데이트 한다면 그 플레이어는 “학습모드 상태이다”라고 표현한다. 반대로 어떠한 플레이어가 양손 중 하나를 항상 동일한 확률로 낸다면 그 플레이어는 “학습모드 상태가 아니다” 라고 표현한다.


(4) 새로운 두명의 플레이어C와 플레이어D를 만들어라. 두 플레이어는 모두 동일하게 (가위,바위) 중 하나를 선택할 수 있다. 두 명의 플레이어는 100번째 대결까지는 두 가지 패중 하나를 랜덤하게 선택하고 101번째 대결부터 500번째 대결까지는 문제(3)의 플레이어B와 같은 방식으로 확률을 업데이트 하여 두 가지 패를 서로 다른 확률로 낸다고 하자. 즉 100번째 대결까지는 두 플레이어가 모두 학습모드 상태가 아니고 101번째부터 500번째 대결까지는 두 플레이어가 모두 학습모드 상태이다. 500번째 대결까지의 학습이 끝났을 경우 플레이어 C와 플레이어D가 각 패를 낼 확률은 각각 얼마인가?

시점 플레이어C가 (가위,바위)를 낼 확률 플레이어D가 (가위,바위)를 낼 확률 비고
t <= 100 (1/2, 1/2) (1/2, 1/2) 양쪽 플레이어 모두 학습모드가 아님
t <= 500 대결 데이터를 학습하여 수정한 확률 대결 데이터를 학습하여 수정한 확률 양쪽 플레이어 모두 학습모드임
Note

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

(5) 새로운 플레이어 E와 F를 생각하자. 플레이어E와 플레이어F는 각각 (가위,바위) 그리고 (가위,보) 중 하나를 선택할 수 있다고 가정하자. 시뮬레이션 대결결과를 이용하여 아래의 확률을 근사적으로 추정하라.

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

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

(6) (5)와 동일한 두 명의 플레이어E, F를 생각하자. 두 플레이어는 100회까지는 랜덤으로 자신의 패를 선택한다. 그리고 101회부터 500회까지는 플레이어F만 데이터로 부터 학습을 하여 수정된 확률을 사용한다. 500번의 대결이 끝나고 플레이어F가 (가위,보)를 선택하는 확률이 어떻게 업데이트 되어있는가?

시점 플레이어E가 (가위,바위)를 낼 확률 플레이어F가 (가위,보)를 낼 확률 비고
t <= 100 (1/2, 1/2) (1/2, 1/2) 양쪽 플레이어 모두 학습모드가 아님
t <= 500 (1/2, 1/2) 데이터를 학습하여 수정한 확률 플레이어E는 학습모드아님 / 플레이어F는 학습모드
Note

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

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

시점 플레이어E가 (가위,바위)를 낼 확률 플레이어F가 (가위,보)를 낼 확률 비고
t <= 100 (1/2, 1/2) (1/2, 1/2) 양쪽 플레이어 모두 학습모드가 아님
t <= 500 (1/2, 1/2) 데이터를 학습하여 수정한 확률 플레이어E는 학습모드아님 / 플레이어F는 학습모드
t <= 1000 데이터를 학습하여 수정한 확률 t=500시점에 업데이트된 확률 플레이어E는 학습모드 / 플레이어F는 학습모드아님
Note

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

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

시점 플레이어E가 (가위,바위)를 낼 확률 플레이어F가 (가위,보)를 낼 확률 비고
t <= 100 (1/2, 1/2) (1/2, 1/2) 양쪽 플레이어 모두 학습모드가 아님
t <= 500 (1/2, 1/2) 데이터를 학습하여 수정한 확률 플레이어E는 학습모드아님 / 플레이어F는 학습모드
t <= 1000 데이터를 학습하여 수정한 확률 t=500시점에 업데이트된 확률 플레이어E는 학습모드 / 플레이어F는 학습모드아님
t <= 30000 t=1000시점에 업데이트된 확률 데이터를 학습하여 수정한 확률 플레이어E는 학습모드아님 / 플레이어F는 학습모드
Note

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

(9) 플레이어E와 플레이어F의 대결기록을 초기화 한다. 이번에는 플레이어F가 항상 (3/4)의 확률로 가위를 (1/4)의 확률로 보를 낸다고 가정한다. 플레이어E는 100번의 대결까지는 랜덤으로 (가위,바위)중 하나를 내고 101번째 대결부터 1000번째 대결까지는 대결 데이터를 학습하여 수정한 확률을 사용한다고 하자. 1000번째 대결이후에 플레이어E가 (가위,바위)를 내는 확률이 어떻게 업데이트 되어있는가?

시점 플레이어E가 (가위,바위)를 낼 확률 플레이어F가 (가위,보)를 낼 확률 비고
t <= 100 (1/2, 1/2) (3/4, 1/4) 양쪽 플레이어 모두 학습모드가 아님
t <= 1000 데이터를 학습하여 수정한 확률 (3/4, 1/4) 플레이어E는 학습모드 / 플레이어F는 학습모드 아님

(10) 플레이어E와 플레이어F의 대결기록을 초기화 한다. 이번에는 플레이어F가 항상 (2/3)의 확률로 가위를 (1/3)의 확률로 보를 낸다고 가정한다. 플레이어E는 100번의 대결까지는 랜덤으로 (가위,바위)중 하나를 내고 101번째 대결부터 1000번째 대결까지는 대결 데이터를 학습하여 수정한 확률을 사용한다고 하자. 1000번째 대결이후에 플레이어E가 (가위,바위)를 내는 확률이 어떻게 업데이트 되어있는가?

시점 플레이어E가 (가위,바위)를 낼 확률 플레이어F가 (가위,보)를 낼 확률 비고
t <= 100 (1/2, 1/2) (2/3, 1/3) 양쪽 플레이어 모두 학습모드가 아님
t <= 1000 데이터를 학습하여 수정한 확률 (2/3, 1/3) 플레이어E는 학습모드 / 플레이어F는 학습모드 아님