๐Ÿฌ ML & Data/๐ŸŽซ ๋ผ์ดํŠธ ๋จธ์‹ ๋Ÿฌ๋‹

[๋ผ์ดํŠธ ๋จธ์‹ ๋Ÿฌ๋‹] Session 19. ์•™์ƒ๋ธ”์˜ ์ •์˜์™€ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ!

darly213 2020. 3. 8. 02:36
728x90

๋ณธ์˜ ์•„๋‹ˆ๊ฒŒ ์˜ค๋žœ๋งŒ์— ๋ผ์ดํŠธ ๋จธ์‹ ๋Ÿฌ๋‹ ๊ธ€์„ ์˜ฌ๋ฆฌ๊ฒŒ ๋˜์—ˆ๋„ค์š”! ์ด๋ฒˆ ์„ธ์…˜์—์„œ๋Š” ์ €๋ฒˆ์— ์˜ˆ๊ณ ํ–ˆ๋‹ค์‹ถ์ด ์•™์ƒ๋ธ” ํ•™์Šต์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋ ค๊ณ  ํ•˜๋Š”๋ฐ์š”, ์—ฌ๋Ÿฌ๋ถ„์€ ์•™์ƒ๋ธ”ํ•˜๋ฉด ๋ฌด์—‡์ด ๋– ์˜ค๋ฅด์‹œ๋‚˜์š”? ์†”์งํžˆ ์ €๋Š” ์Œ์•…์ด ๊ฐ€์žฅ ๋จผ์ € ๋– ์˜ฌ๋ž๋Š”๋ฐ์š”, ์‚ฌ์‹ค ๋น„์Šทํ•œ ๊ฐœ๋…์ด๊ธฐ๋Š” ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฒˆ ์„ธ์…˜์—์„œ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ํ•ฉ์ณ ์ข‹์€ ์„ฑ๋Šฅ์„ ๋‚ด๋Š” ์•™์ƒ๋ธ” ํ•™์Šต์˜ ์ •์˜์™€ ์ข…๋ฅ˜ ์ค‘ ํ•˜๋‚˜์ธ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!

A. ์•™์ƒ๋ธ” ํ•™์Šต

์•™์ƒ๋ธ” ํ•™์Šต(ensemble learning)์€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ํ•˜๋‚˜์˜ ๋ฉ”ํƒ€ ๋ถ„๋ฅ˜๊ธฐ๋กœ ์—ฐ๊ฒฐํ•ด์„œ ๋” ์ข‹์€ ์„ฑ๋Šฅ์„ ์ด๋Œ์–ด๋‚ด๋Š” ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค. ์•™์ƒ๋ธ” ํ•™์Šต์„ ์œ„ํ•œ ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋‹จ ๋จผ์ €, ์•™์ƒ๋ธ”์˜ ์ž‘๋™ ์›๋ฆฌ์™€ ์™œ ๋” ์„ฑ๋Šฅ์ด ์ข‹์€์ง€์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๋จผ์ €, ๊ฐ€์žฅ ์ธ๊ธฐ์žˆ๋Š” ์•™์ƒ๋ธ” ๋ฐฉ๋ฒ•์ธ ๊ณผ๋ฐ˜์ˆ˜ ํˆฌํ‘œ(majority voting) ๋ฐฉ์‹์— ๋Œ€ํ•ด์„œ ์ด์•ผ๊ธฐํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋ฆ„์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ๋ถ„๋ฅ˜๊ธฐ์˜ ๊ณผ๋ฐ˜์ˆ˜๊ฐ€ ์˜ˆ์ธกํ•œ ํด๋ž˜์Šค ๋ ˆ์ด๋ธ”์„ ์„ ํƒํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

๊ณผ๋ฐ˜์ˆ˜ ํˆฌํ‘œ๋Š” ์ด์ค‘ ํด๋ž˜์Šค ๋ถ„๋ฅ˜์— ํ•ด๋‹นํ•˜์ง€๋งŒ ๋‹ค์ค‘ ํด๋ž˜์Šค ๋ฌธ์ œ์—์„œ๋„ ์ผ๋ฐ˜ํ™”๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ(plurality voting)์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ์—๋Š” ์ตœ๋นˆ๊ฐ’์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ๊ทธ๋ฆผ์—์„œ ๊ณผ๋ฐ˜์ˆ˜ ํˆฌํ‘œ์™€ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ์˜ ๊ฐœ๋…์„ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ๊ฐ์˜ ๋ชจ์–‘์€ ํด๋ž˜์Šค ๋ ˆ์ด๋ธ”์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

ํ›ˆ๋ จ ์„ธํŠธ๋ฅผ ์‚ฌ์šฉํ•ด m๊ฐœ์˜ ๋‹ค๋ฅธ ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ํ›ˆ๋ จ์‹œํ‚ต๋‹ˆ๋‹ค. ์ด๊ฒƒ์„ C๋ผ๊ณ  ํ• ๊ฒŒ์š”. ๊ทธ๋ฆฌ๊ณ  ์•™์ƒ๋ธ” ๋ฐฉ๋ฒ•์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•ด ๊ตฌ์ถ•์„ ํ•˜๊ฑฐ๋‚˜ ๋ถ„๋ฅ˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ฐ™์€ ๊ฒƒ์„ ์‚ฌ์šฉํ•˜๊ณ  ํ›ˆ๋ จ ์„ธํŠธ์˜ ๋ถ€๋ถ„ ์ง‘ํ•ฉ์„ ๋‹ฌ๋ฆฌํ•ด ํ•™์Šตํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ ๋ช…ํ•œ ์•™์ƒ๋ธ” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋Š” ๋žœ๋ค ํฌ๋ ˆ์ŠคํŠธ(random forest)์ž…๋‹ˆ๋‹ค.

๊ณผ๋ฐ˜์ˆ˜ ํˆฌํ‘œ๋‚˜ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ๋กœ ์˜ˆ์ธกํ•˜๋ ค๋ฉด ๊ฐœ๋ณ„ ๋ถ„๋ฅ˜๊ธฐ์˜ ์˜ˆ์ธก ๋ ˆ์ด๋ธ”์„ ๋ชจ์•„์„œ ๊ฐ€์žฅ ๋งŽ์€ ํ‘œ๋ฅผ ๋ฐ›์€ ๋ ˆ์ด๋ธ” y hat์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ์‹์œผ๋กœ ๋‚˜ํƒ€๋‚ด๋ฉด ๋Œ€๊ฐ• ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ํด๋ž˜์Šค 1์ด -1์ด๊ณ  ํด๋ž˜์Šค 2๊ฐ€ +1์ด๋ฉด ๊ณผ๋ฐ˜์ˆ˜ ํˆฌํ‘œ ์˜ˆ์ธก์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ž, ๊ทธ๋Ÿผ ์•™์ƒ๋ธ” ํ•™์Šต์ด ์™œ ๋” ์„ฑ๋Šฅ์ด ์ข‹์€์ง€๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด ์กฐํ•ฉ ์ด๋ก ์„ ์ ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด์„œ, ์ด์ง„ ๋ถ„๋ฅ˜ ์ž‘์—…์„ ํ•  ๋•Œ ๋™์ผํ•œ ์—๋Ÿฌ์œจ e๋ฅผ ๊ฐ€์ง„ n๊ฐœ์˜ ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ์ƒ๊ฐํ•˜๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ๋ถ„๋ฅ˜๊ธฐ๋Š” ์„œ๋กœ์—๊ฒŒ ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ณ  ์ƒ๊ด€๊ด€๊ณ„ ์—†์ด ๋…๋ฆฝ์ ์ด๋ผ๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์ด ๋ถ„๋ฅ˜๊ธฐ๋“ค์˜ ์•™์ƒ๋ธ”์ด ๋งŒ๋“œ๋Š” ์˜ค์ฐจ ํ™•๋ฅ ์„ ์ดํ•ญ ๋ถ„ํฌ์˜ ํ™•๋ฅ  ์งˆ๋Ÿ‰ ํ•จ์ˆ˜๋กœ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ๋Š” ์ดํ•ญ ๊ณ„์ˆ˜๋กœ n๊ฐœ ์›์†Œ์—์„œ k๊ฐœ๋ฅผ ๋ฝ‘๋Š” ์กฐํ•ฉ์ž…๋‹ˆ๋‹ค. ์ด ์‹์€ ์•™์ƒ๋ธ”์ด ํ‹€๋ฆด ํ™•๋ฅ ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. ์ข€ ๋” ๊ตฌ์ฒด์ ์ธ ์˜ˆ๋กœ, ์—๋Ÿฌ์œจ e๊ฐ€ 0.25์ธ ๋ถ„๋ฅ˜๊ธฐ 11๊ฐœ๋กœ ๊ตฌ์„ฑ๋œ ์•™์ƒ๋ธ”์˜ ์—๋Ÿฌ์œจ์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ๊ฐ๊ฐ์˜ ๋ถ„๋ฅ˜๊ธฐ์˜ ์—๋Ÿฌ์œจ 0.25๋ณด๋‹ค ์•™์ƒ๋ธ” ๋ถ„๋ฅ˜๊ธฐ์˜ ์—๋Ÿฌ์œจ 0.034์˜ ์—๋Ÿฌ์œจ์ด ํ™•์—ฐํžˆ ๋‚ฎ์€ ๊ฒƒ์„ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ๋งŒ์ผ ์—๋Ÿฌ์œจ์ด 0.5์ธ ๋ถ„๋ฅ˜๊ธฐ๊ฐ€ ์ง์ˆ˜ ๊ฐœ๋ผ ์˜ˆ์ธก์ด ๋ฐ˜๋ฐ˜์œผ๋กœ ๋‚˜๋‰˜๋ฉด ์—๋Ÿฌ๋กœ ์ทจ๊ธ‰๋ฉ๋‹ˆ๋‹ค. ์ผ๋‹จ, ์ด์ƒ์  ์•™์ƒ๋ธ” ๋ถ„๋ฅ˜๊ธฐ์™€ ๋‹ค์–‘ํ•œ ๋ฒ”์œ„์˜ ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ๊ฐ€์ง„ ๊ฒฝ์šฐ๋ฅผ ๋น„๊ตํ•˜๊ธฐ ์œ„ํ•ด ํ™•๋ฅ  ์งˆ๋Ÿ‰ ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

from scipy.special import comb
import math

def ensemble_error(n_classifier, error):
    k_start = int(math.ceil(n_classifier / 2.))
    probs = [comb(n_classifier, k) * error**k * (1-error)**(n_classifier - k)
             for k in range(k_start, n_classifier + 1)]
    return sum(probs)

ensemble_error(n_classifier=11, error=0.25)

ensemble_error ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•œ ํ›„์— ๋ถ„๋ฅ˜๊ธฐ ์—๋Ÿฌ๊ฐ€ 0.0๋ถ€ํ„ฐ 1.0 ์‚ฌ์ด์— ์žˆ์„ ๋•Œ ์•™์ƒ๋ธ” ์—๋Ÿฌ์œจ์„ ๊ณ„์‚ฐํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์•™์ƒ๋ธ”๊ณผ ๊ฐœ๋ณ„ ๋ถ„๋ฅ˜๊ธฐ ์—๋Ÿฌ ์‚ฌ์ด ๊ด€๊ณ„๋ฅผ ์‹œ๊ฐํ™” ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

import numpy as np

error_range = np.arange(0.0, 1.01, 0.01)
ens_errors = [ensemble_error(n_classifier=11, error=error)
              for error in error_range]

import matplotlib.pyplot as plt


plt.plot(error_range, 
         ens_errors, 
         label='Ensemble error', 
         linewidth=2)

plt.plot(error_range, 
         error_range, 
         linestyle='--',
         label='Base error',
         linewidth=2)

plt.xlabel('Base error')
plt.ylabel('Base/Ensemble error')
plt.legend(loc='upper left')
plt.grid(alpha=0.5)
plt.show()

์œ„์˜ ๊ฒฐ๊ณผ ๊ทธ๋ž˜ํ”„์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ ์•™์ƒ๋ธ”์˜ ์—๋Ÿฌ ํ™•๋ฅ ์€ ๊ฐœ๋ณ„ ๋ถ„๋ฅ˜๊ธฐ๋ณด๋‹ค ํ•ญ์ƒ ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๊ฐœ๋ณ„ ๋ถ„๋ฅ˜๊ธฐ์˜ ์„ฑ๋Šฅ, ์—๋Ÿฌ์œจ์ด 0.5๋ณด๋‹ค ๋‚ฎ์•„์•ผํ•œ๋‹ค๋Š” ์กฐ๊ฑด์ด ์žˆ์Šต๋‹ˆ๋‹ค.

B. ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ๋ฅผ ์‚ฌ์šฉํ•œ ๋ถ„๋ฅ˜ ์•™์ƒ๋ธ”

1. ๊ฐ„๋‹จํ•œ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ ๋ถ„๋ฅ˜๊ธฐ ๊ตฌํ˜„

๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ์—์„œ๋Š” ๋ถ„๋ฅ˜ ๋ชจ๋ธ์˜ ์‹ ๋ขฐ๋„์— ๊ฐ€์ค‘์น˜๋ฅผ ๋ถ€์—ฌํ•ด ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ์ผ๋‹จ ์ˆ˜ํ•™์ ์œผ๋กœ ํ‘œํ˜„ํ•œ ๊ฐ€์ค‘์น˜๊ฐ€ ์ ์šฉ๋œ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ w๋Š” c๋ผ๋Š” ๋ถ„๋ฅ˜๊ธฐ์— ์—ฐ๊ด€๋œ ๊ฐ€์ค‘์น˜์ด๊ณ , y hat์€ ํ•ญ์ƒ ๊ทธ๋žซ๋“ฏ์ด ์•™์ƒ๋ธ”์ด ์˜ˆ์ธกํ•œ ํด๋ž˜์Šค ๋ ˆ์ด๋ธ”์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฌธ์ž X ์ฒ˜๋Ÿผ ์ƒ๊ธด ์นด์ด๋Š” ํŠน์„ฑํ•จ์ˆ˜๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ํŠน์„ฑํ•จ์ˆ˜๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด ํŠน์„ฑํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด์„œ ๊ฐ€์ค‘์น˜๊ฐ€ ๋™์ผํ•˜๋‹ค๋Š” ๊ฐ€์ • ํ•˜์— ์œ„์— ๋‚˜์™”๋˜ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ์˜ ์‹์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

argmax์™€ bincountํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•˜๋ฉด ๊ฐ€์ค‘์น˜๊ฐ€ ์ ์šฉ๋œ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. bincountํ•จ์ˆ˜๋Š” 0 ์ด์ƒ์˜ ์ •์ˆ˜๋กœ ๋œ ๋ฐฐ์—ด์„ ์ž…๋ ฅ๋ฐ›์•„ ๊ฐ ์ •์ˆ˜๊ฐ€ ๋“ฑ์žฅํ•˜๋Š” ํšŸ์ˆ˜๋ฅผ ์นด์šดํŠธํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

import numpy as np

np.argmax(np.bincount([0,0,1], weights=[0.2,0.2,0.6]))

์ด์ „ ์„ธ์…˜์—์„œ ์ด์•ผ๊ธฐ ํ–ˆ๋˜ ์ ์ด ์žˆ๋Š”๋ฐ, ์‚ฌ์ดํ‚ท๋Ÿฐ์˜ ์ผ๋ถ€ ๋ถ„๋ฅ˜๊ธฐ๋Š” predict_proba ๋ฉ”์„œ๋“œ๋กœ ์˜ˆ์ธก ํด๋ž˜์Šค ๋ ˆ์ด๋ธ” ํ™•๋ฅ ์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•™์ƒ๋ธ” ๋ถ„๋ฅ˜๊ธฐ๊ฐ€ ๋ณด์ •์ด ์ž˜ ๋˜์–ด์žˆ๋‹ค๋ฉด ํด๋ž˜์Šค ๋ ˆ์ด๋ธ” ๋Œ€์‹  ์˜ˆ์ธก ํด๋ž˜์Šค ํ™•๋ฅ ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ํ™•๋ฅ ์„ ์ด์šฉํ•œ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ๋Š” ์•„๋ž˜ ์‹๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ p๋Š” ํด๋ž˜์Šค๋ ˆ์ด๋ธ” i์— ๋Œ€ํ•œ j๋ฒˆ์งธ ๋ถ„๋ฅ˜๊ธฐ์˜ ์˜ˆ์ธก ํ™•๋ฅ ์ž…๋‹ˆ๋‹ค. ๋„˜ํŒŒ์ด์˜ average์™€ argmax๋ฅผ ์ด์šฉํ•ด์„œ ํด๋ž˜์Šค ํ™•๋ฅ  ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐ€์ค‘์น˜ ์ ์šฉ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ex = np.array([0.9,0.1], [0.8,0.2], [0.4,0.6])
p = np.average(ex, axis=0, weights=[0.2, 0.2, 0.6])
print(p)
np.argmax(p)

์•ž์„  ์นœ๊ตฌ๋“ค์„ ํ•จ์ณ์„œ MajorityVoteClassifier ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

from sklearn.base import BaseEstimator
from sklearn.base import ClassifierMixin
from sklearn.preprocessing import LabelEncoder
from sklearn.externals import six
from sklearn.base import clone
from sklearn.pipeline import _name_estimators
import numpy as np
import operator
class MajorityVoteClassifier(BaseEstimator, ClassifierMixin):

    def __init__(self, classifiers,
        vote='classlabel', weights=None):

         #vote์˜ ๊ธฐ๋ณธ๊ฐ’์€ classlabel. classlabel์ด๋ฉด ๋‹ค์ˆ˜์ธ ํด๋ž˜์Šค ๋ ˆ์ด๋ธ” ์ธ๋ฑ์Šค, 'probability'์ด๋ฉด ํ™•๋ฅ  ํ•ฉ์ด ๊ฐ€์žฅ ํฐ ์ธ๋ฑ์Šค๋กœ ์˜ˆ์ธก
         #weight๋Š” ๋ฐฐ์—ดํƒ€์ž…. ๊ธฐ๋ณธ๊ฐ’ None
         #Classifier์€ ์•™์ƒ๋ธ”์— ์‚ฌ์šฉํ•  ๋ถ„๋ฅ˜๊ธฐ

        self.classifiers = classifiers 
        self.named_classifiers = {key: value for key, value in _name_estimators(classifiers)}
        self.vote = vote
        self.weights = weights

    def fit(self, X, y):

         #X = ํ›ˆ๋ จ ์ƒ˜ํ”Œ ํ–‰๋ ฌ. 
         #y = 

        self.lablenc_ = LabelEncoder()
        self.lablenc_.fit(y)
        self.classes_ = self.lablenc_.classes_
        self.classifiers_ = []
         for clf in self.classifiers:
            fitted_clf = clone(clf).fit(X, self.lablenc_.transform(y))
            self.classifiers_.append(fitted_clf)
        return self

์œ„ ํด๋ž˜์Šค๋Š” sklearn.base์˜ BaseEstimator ์™€ ClassifierMixin ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜์—ฌ ๊ธฐ๋ณธ์  ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ”๊ณ , ์—ฌ๊ธฐ์—๋Š” ๋ถ„๋ฅ˜๊ธฐ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ค์ • ๋ฐ ๋ฐ˜ํ™˜ํ•˜๋Š” get_params์™€ set_params ๋ฉ”์„œ๋“œ์™€ ์˜ˆ์ธก ์ •ํ™•๋„๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” score ๋ฉ”์„œ๋“œ๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  predict ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” vote='classlabel'๋กœ MajorityVoteClassifier ๊ฐ์ฒด๊ฐ€ ๋งŒ๋“ค์–ด์ง€๋ฉด ํด๋ž˜์Šค ๋ ˆ์ด๋ธ” ๊ธฐ๋ฐ˜ ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ๋ฅผ ์‚ฌ์šฉํ•ด ์˜ˆ์ธกํ•ฉ๋‹ˆ๋‹ค. vote='probability'๋กœ ๋งŒ๋“ค์–ด์ง€๋ฉด ํ™•๋ฅ  ๊ธฐ๋ฐ˜ ํด๋ž˜์Šค ๋ ˆ์ด๋ธ”์„ ์˜ˆ์ธกํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿผ ๊ณ„์†ํ•ด์„œ predict ๋ฉ”์„œ๋“œ์™€ predict_proba ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

def predict(self, X):
""" 
X : ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ํ–‰๋ ฌ
maj_vote : ์˜ˆ์ธก๋œ ํด๋ž˜์Šค ๋ ˆ์ด๋ธ”
"""
    if self.vote == 'probability':
       maj_vote = np.argmax(self.predict_proba(X),axis=1)
    else: # 'classlabel' ํˆฌํ‘œ
# clf.predict ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ชจ์๋‹ˆ๋‹ค.
        predictions = np.asarray([clf.predict(X) for clf in self.classifiers_]).T
    maj_vote = np.apply_along_axis(
        lambda x:
        np.argmax(np.bincount(x, weights=self.weights)),
        axis=1,
        arr=predictions)
maj_vote = self.lablenc_.inverse_transform(maj_vote)
return maj_vote

def predict_proba(self, X):
""" 
X : ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ํ–‰๋ ฌ
avg_proba : ์ƒ˜ํ”Œ๋งˆ๋‹ค ๊ฐ€์ค‘์น˜๊ฐ€ ์ ์šฉ๋œ ํด๋ž˜์Šค์˜ ํ‰๊ท  ํ™•๋ฅ 
"""
    probas = np.asarray([clf.predict_proba(X)
                         for clf in self.classifiers_])
    avg_proba = np.average(probas, axis=0, weights=self.weights)
    return avg_proba

def get_params(self, deep=True):
""" GridSearch๋ฅผ ์œ„ํ•ด ๋ถ„๋ฅ˜๊ธฐ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ๋ฐ˜ํ™˜ """
    if not deep:
    return super(MajorityVoteClassifier,
                 self).get_params(deep=False)
    else:
        out = self.named_classifiers.copy()
        for name, step in\
        six.iteritems(self.named_classifiers):
    for key, value in six.iteritems(
            step.get_params(deep=True)):
        out['%s__%s' % (name, key)] = value
return out

์•™์ƒ๋ธ”์— ์žˆ๋Š” ๋ถ„๋ฅ˜๊ธฐ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์— ๊ฐ๊ฐ ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด _name_estimators ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ–ˆ๊ณ , get_params ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

2. ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ด ์˜ˆ์ธก ๋งŒ๋“ค๊ธฐ

์ด์ œ ์œ„์—์„œ ๋งŒ๋“  ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ผ๋‹จ์€ ๋จผ์ € ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์™€์•ผํ•˜๋Š”๋ฐ์š”, datasets๋ชจ๋“ˆ์„ ์ด์šฉํ•ด ๊ฐ„ํŽธํ•˜๊ฒŒ ๋ถ“๊ฝƒ ๋ฐ์ดํ„ฐ์…‹์„ ์ฝ์–ด์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ฝƒ์€ Iris-versicolor์™€ Iris-verginica ๋‘ ๊ฐ€์ง€๋งŒ ๋ถ„๋ฅ˜ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split

iris = datasets.load_iris()
X, y = iris.data[50:, [1, 2]], iris.target[50:]
le = LabelEncoder()
y = le.fit_transform(y)

X_train, X_test, y_train, y_test =\
       train_test_split(X, y, 
                        test_size=0.5, 
                        random_state=1,
                        stratify=y)

์ด ๊ฒฝ์šฐ ๋ฐ˜์€ ํ›ˆ๋ จ ๋ฐ์ดํ„ฐ๋กœ, ๋ฐ˜์€ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ํ›ˆ๋ จ ์„ธํŠธ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์„ธ ๊ฐœ์˜ ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ํ›ˆ๋ จํ•ด๋ณด๋„๋ก ํ•˜์ฃ . ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€, ๊ฒฐ์ •ํŠธ๋ฆฌ, k-์ตœ๊ทผ์ ‘ ์ด์›ƒ ๋ถ„๋ฅ˜๊ธฐ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ผ๋‹จ ์•™์ƒ๋ธ”๋กœ ๋ฌถ๊ธฐ ์ „์—, 10๊ฒน ๊ต์ฐจ๊ฒ€์ฆ์œผ๋กœ ์„ฑ๋Šฅ์„ ํ‰๊ฐ€ํ•˜๊ณ  ์‹œ์ž‘ํ•ด๋ณด๋„๋ก ํ• ๊ฒŒ์š”. ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier 
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score

clf1 = LogisticRegression(solver='liblinear',
                          penalty='l2', 
                          C=0.001,
                          random_state=1)

clf2 = DecisionTreeClassifier(max_depth=1,
                              criterion='entropy',
                              random_state=0)

clf3 = KNeighborsClassifier(n_neighbors=1,
                            p=2,
                            metric='minkowski')

pipe1 = Pipeline([['sc', StandardScaler()],
                  ['clf', clf1]])
pipe3 = Pipeline([['sc', StandardScaler()],
                  ['clf', clf3]])

clf_labels = ['Logistic regression', 'Decision tree', 'KNN']

print('10-๊ฒน ๊ต์ฐจ ๊ฒ€์ฆ:\n')
for clf, label in zip([pipe1, clf2, pipe3], clf_labels):
    scores = cross_val_score(estimator=clf,
                             X=X_train,
                             y=y_train,
                             cv=10,
                             scoring='roc_auc')
    print("ROC AUC: %0.2f (+/- %0.2f) [%s]"
          % (scores.mean(), scores.std(), label))

๊ฒฐ๊ณผ์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ๋“ฏ์ด ์„ฑ๋Šฅ์€ ๋‹ค๋“ค ๋น„์Šทํ•˜๋„ค์š”. ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€์™€ k-์ตœ๊ทผ์ ‘ ์ด์›ƒ ๋ถ„๋ฅ˜๊ธฐ๋Š” ์™œ ํŒŒ์ดํ”„ ๋ผ์ธ์œผ๋กœ ํ›ˆ๋ จํ–ˆ๋ƒ๋ฉด, ์ด ๋‘ ๊ฐ€์ง€๋Š” ๊ฒฐ์ •ํŠธ๋ฆฌ์™€ ๋‹ฌ๋ฆฌ ์Šค์ผ€์ผ์— ๋ฏผ๊ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ํ‘œ์ค€ํ™” ์ „์ฒ˜๋ฆฌ๊ฐ€ ์ค‘์š”ํ•œ ์นœ๊ตฌ๋“ค์ด์ฃ . ๋ฌผ๋ก  ๋ถ“๊ฝƒ ๋ฐ์ดํ„ฐ์…‹ ํŠน์„ฑ์ด ๋ชจ๋‘ ๊ฐ™์€ ์Šค์ผ€์ผ๋กœ ์ธก์ •๋˜์—ˆ์ง€๋งŒ ํ‘œ์ค€ํ™” ์ „์ฒ˜๋ฆฌ๋ฅผ ์Šต๊ด€ํ™”ํ•ด๋ด…์‹œ๋‹ค!

์ด์ œ ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ๋งŒ๋“ค์–ด๋‘” ๋งŒ๋“ค์–ด๋‘” ํด๋ž˜์Šค๋กœ ์•™์ƒ๋ธ”์„ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!

# ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ (ํด๋ž˜์Šค ๋ ˆ์ด๋ธ” ์นด์šดํŠธ)

mv_clf = MajorityVoteClassifier(classifiers=[pipe1, clf2, pipe3])

clf_labels += ['Majority voting']
all_clf = [pipe1, clf2, pipe3, mv_clf]

for clf, label in zip(all_clf, clf_labels):
    scores = cross_val_score(estimator=clf,
                             X=X_train,
                             y=y_train,
                             cv=10,
                             scoring='roc_auc')
    print("ROC AUC: %0.2f (+/- %0.2f) [%s]"
          % (scores.mean(), scores.std(), label))

๋งˆ์ง€๋ง‰ ์ค„์ด ์•™์ƒ๋ธ” ์ž…๋‹ˆ๋‹ค. ์„ฑ๋Šฅ์ด ํ™•์—ฐํžˆ ๋›ฐ์–ด๋‚œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

3. ์•™์ƒ๋ธ” ๋ถ„๋ฅ˜๊ธฐ ํ‰๊ฐ€์™€ ํŠœ๋‹

์ผ๋‹จ ์•ž์„  ์„ธ์…˜์—์„œ ๊ณต๋ถ€ํ–ˆ๋˜ ROC๋ฅผ ํ†ตํ•ด์„œ MajorityVoteClassifier์˜ ์ผ๋ฐ˜ํ™” ์„ฑ๋Šฅ์„ ํ™•์ธํ•ด๋ณผ๊ฒŒ์š”. ํ…Œ์ŠคํŠธ ์„ธํŠธ๋ฅผ ์ด์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

from sklearn.metrics import roc_curve
from sklearn.metrics import auc

colors = ['black', 'orange', 'blue', 'green']
linestyles = [':', '--', '-.', '-']
for clf, label, clr, ls \
        in zip(all_clf,
               clf_labels, colors, linestyles):

    # assuming the label of the positive class is 1
    y_pred = clf.fit(X_train,
                     y_train).predict_proba(X_test)[:, 1]
    fpr, tpr, thresholds = roc_curve(y_true=y_test,
                                     y_score=y_pred)
    roc_auc = auc(x=fpr, y=tpr)
    plt.plot(fpr, tpr,
             color=clr,
             linestyle=ls,
             label='%s (auc = %0.2f)' % (label, roc_auc))

plt.legend(loc='lower right')
plt.plot([0, 1], [0, 1],
         linestyle='--',
         color='gray',
         linewidth=2)

plt.xlim([-0.1, 1.1])
plt.ylim([-0.1, 1.1])
plt.grid(alpha=0.5)
plt.xlabel('False positive rate (FPR)')
plt.ylabel('True positive rate (TPR)')

plt.show()

ROC๊ณก์„ ์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ๋“ฏ์ด ์•™์ƒ๋ธ” ๋ถ„๋ฅ˜๊ธฐ๊ฐ€ ํ…Œ์ŠคํŠธ์„ธํŠธ ์—์„œ๋„ ๋งค์šฐ ์ข‹์€ ์„ฑ๋Šฅ์„ ๋‚ด๊ณ  ์žˆ๋„ค์š”. ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€๋„ ๋น„์Šทํ•˜๊ฒŒ ์ข‹์€ ์„ฑ์ ์„ ๋‚ด๊ณ  ์žˆ๋Š”๋ฐ, ์ด๋Š” ์ž‘์€ ๋ฐ์ดํ„ฐ ์…‹์—์„œ ์ƒ๊ธฐ๋Š” ๋†’์€ ๋ถ„์‚ฐ ๋•Œ๋ฌธ์œผ๋กœ ์ถ”์ •๋ฉ๋‹ˆ๋‹ค. ์ด ์ด์•ผ๊ธฐ๊ฐ€ ๊ถ๊ธˆํ•˜์‹  ๋ถ„๋“ค์€ ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€ ์„ธ์…˜์„ ๋ณด๊ณ ์˜ค์„ธ์š”!

์—ฌ๊ธฐ์„œ๋Š” ํŠน์„ฑ์ด ๋‘ ๊ฐœ๋งŒ ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฒฐ์ • ๊ฒฝ๊ณ„๋ฅผ ํ™•์ธํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€์™€ k-์ตœ๊ทผ์ ‘ ์ด์›ƒ ํŒŒ์ดํ”„๋ผ์ธ์—๋Š” ์ „์ฒ˜๋ฆฌ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํŠน์„ฑ์„ ํ‘œ์ค€ํ™”ํ•˜๋Š” ๊ฑด ์ƒ๋žตํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” ๊ฒฐ์ •ํŠธ๋ฆฌ ๊ฒฐ์ • ๊ฒฝ๊ณ„๋ฅผ ๋‹ค๋ฅธ ๋ชจ๋ธ๊ณผ ์Šค์ผ€์ผ์„ ๋งž์ถ”๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)

from itertools import product

all_clf = [pipe1, clf2, pipe3, mv_clf]

x_min = X_train_std[:, 0].min() - 1
x_max = X_train_std[:, 0].max() + 1
y_min = X_train_std[:, 1].min() - 1
y_max = X_train_std[:, 1].max() + 1

xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                     np.arange(y_min, y_max, 0.1))

f, axarr = plt.subplots(nrows=2, ncols=2, 
                        sharex='col', 
                        sharey='row', 
                        figsize=(7, 5))

for idx, clf, tt in zip(product([0, 1], [0, 1]),
                        all_clf, clf_labels):
    clf.fit(X_train_std, y_train)

    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    axarr[idx[0], idx[1]].contourf(xx, yy, Z, alpha=0.3)

    axarr[idx[0], idx[1]].scatter(X_train_std[y_train==0, 0], 
                                  X_train_std[y_train==0, 1], 
                                  c='blue', 
                                  marker='^',
                                  s=50)

    axarr[idx[0], idx[1]].scatter(X_train_std[y_train==1, 0], 
                                  X_train_std[y_train==1, 1], 
                                  c='green', 
                                  marker='o',
                                  s=50)

    axarr[idx[0], idx[1]].set_title(tt)

plt.text(-3.5, -5., 
         s='Sepal width [standardized]', 
         ha='center', va='center', fontsize=12)
plt.text(-12.5, 4.5, 
         s='Petal length [standardized]', 
         ha='center', va='center', 
         fontsize=12, rotation=90)

plt.show()

์•™์ƒ๋ธ” ํŠœ๋‹์„ ์œ„ํ•ด์„œ ๊ฐœ๋ณ„ ๋ถ„๋ฅ˜๊ธฐ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํŠœ๋‹ํ•˜๊ธฐ ์ „์— GridSearchCV ๊ฐ์ฒด ์•ˆ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์— ์ ‘๊ทผํ•˜๋Š” ๋ฒ•์„ ์ฐพ๊ธฐ ์œ„ํ•ด์„œ get_params ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด๋ณผ๊ฒŒ์š”.

mv_clf.get_params()

์—„์ฒญ๋‚˜๊ฒŒ ๊ธฐ๋„ค์š”. ์ด ๊ธธ๊ณ  ๊ธด ๋ฐ˜ํ™˜ ๊ฐ’์„ ์ž˜ ์‚ดํŽด๋ณด๋ฉด ๊ฐœ๋ณ„๋ถ„๋ฅ˜๊ธฐ ์†์„ฑ์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฝ์–ด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์‹œ๋ฅผ ์œ„ํ•ด ๊ทธ๋ฆฌ๋“œ ์„œ์น˜๋กœ ๋กœ์ง€์Šคํ‹ฑ ํšŒ๊ท€์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ C์™€ ๊ฒฐ์ •ํŠธ๋ฆฌ ๊นŠ์ด๋ฅผ ํŠœ๋‹ํ•ด๋ณผ๊ฒŒ์š”.

from sklearn.model_selection import GridSearchCV

params = {'decisiontreeclassifier__max_depth': [1, 2],
          'pipeline-1__clf__C': [0.001, 0.1, 100.0]}

grid = GridSearchCV(estimator=mv_clf,
                    param_grid=params,
                    cv=10,
                    scoring='roc_auc',
                    iid=False)
grid.fit(X_train, y_train)

๊ทธ๋ฆฌ๋“œ ์„œ์น˜ ์‹คํ–‰์ด ์™„๋ฃŒ๋˜๋ฉด ROC AUC ์ ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

for params, mean_score, scores in grid.grid_scores_:
    print("%0.3f+/-%0.2f %r"
         % (mean_score, scores.std()/2, params))

print('์ตœ์ ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜: %s' %grid.best_params_)
print('์ •ํ™•๋„: %.2f' %grid.best_score_)

์—ฌ๊ธฐ์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ๋“ฏ์ด ๊ทœ์ œ ๊ฐ•๋„๊ฐ€ ๊ฐ€์žฅ ๋‚ฎ์„ ๋•Œ ๊ฐ€์žฅ ๊ฒฐ๊ณผ๊ฐ€ ์ข‹์€๋ฐ, ํŠธ๋ฆฌ์˜ ๊นŠ์ด๋Š” ๊ทธ๋ฆฌ ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™๋„ค์š”. ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„ํ• ํ•˜๋Š”๋ฐ์—๋Š” ๊นŠ์ด 1๋กœ๋„ ์ถฉ๋ถ„ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.


์—ฌ๊ธฐ๊นŒ์ง€ ์•™์ƒ๋ธ” ํ•™์Šต๊ณผ ์•™์ƒ๋ธ” ํ•™์Šต์˜ ๋Œ€ํ‘œ ์ฃผ์ž, ๋‹ค์ˆ˜๊ฒฐ ํˆฌํ‘œ์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์•™์ƒ๋ธ” ํ•™์Šต์€ ๋จธ์‹ ๋Ÿฌ๋‹์— ์žˆ์–ด์„œ๋Š” ๊ผญ๊ผญ๊ผญ ์•Œ์•„์•ผํ•˜๋Š” ๊ฐœ๋…์ด๋ผ๊ณ  ์ €๋Š” ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์•ž์œผ๋กœ ํ•œ ์„ธ์…˜ ๋” ์•™์ƒ๋ธ”์— ๋Œ€ํ•œ ๊ฒƒ์„ ์•Œ์•„๋ณด๊ฒŒ ๋  ํ…๋ฐ์š”, ์ด๋ฒˆ ์„ธ์…˜์ด๋ž‘ ํ•จ๊ป˜ ์ฝ์–ด์ฃผ์‹œ๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ ๋‹ค์Œ ์„ธ์…˜ ๋ดฌ์š”!

 

728x90