๐ŸŽ„ ๊ฒฐ์ •ํŠธ๋ฆฌ, ๊ฒฐ์ •ํŠธ๋ฆฌ ์•™์ƒ๋ธ” ๐ŸŽ„

parkeuยท2022๋…„ 9์›” 26์ผ
0

ABC๋ถ€ํŠธ์บ ํ”„

๋ชฉ๋ก ๋ณด๊ธฐ
31/55

๐ŸŽ„ ๊ฒฐ์ • ํŠธ๋ฆฌ

- ๋ถ„๋ฅ˜์™€ ํšŒ๊ท€ ๋ฌธ์ œ์— ๋„๋ฆฌ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋ธ

  • ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฒฐ์ •์— ๋‹ค๋‹ค๋ฅด๊ธฐ ์œ„ํ•ด ์˜ˆ/์•„๋‹ˆ์˜ค ์งˆ๋ฌธ์„ ์ด์–ด๋‚˜๊ฐ€๋ฉด์„œ ํ•™์Šต
  • ๋งจ ์œ„ ๋…ธ๋“œ : ๋ฃจํŠธ๋…ธ๋“œ
  • ํŠธ๋ฆฌ๋…ธ๋“œ๋Š” ์งˆ๋ฌธ์ด๋‚˜ ์ •๋‹ต์„ ๋‹ด์€ ๋„ค๋ชจ์ƒ์ž
  • ์—ฃ์ง€๋Š” ์งˆ๋ฌธ์˜ ๋‹ต๊ณผ ๋‹ค์Œ ์งˆ๋ฌธ์„ ์—ฐ๊ฒฐ
  • ๋งˆ์ง€๋ง‰ ๋…ธ๋“œ : ๋ฆฌํ”„(leaf)๋ผ๊ณ  ํ•จ
  • ๋ชจ๋ธ์„ ์ง์ ‘ ๋งŒ๋“œ๋Š” ๋Œ€์‹  ์ง€๋„ ํ•™์Šต ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋กœ๋ถ€ํ„ฐ ํ•™์Šต

๐Ÿ‘ฉโ€โš•๏ธ DecisionTreeClassifier ์‚ฌ์šฉํ•˜์—ฌ ์œ ๋ฐฉ์•” ์–‘์„ฑ(2), ์•…์„ฑ(4)

๐Ÿผ ์ค€๋น„

from sklearn import preprocessing
from sklearn.model_selection import train_test_split
from sklearn import tree
from sklearn import metrics

import pandas as pd
import numpy as np

# ํ•œ๊ธ€ ๊นจ์ง ๋ฐฉ์ง€
import matplotlib as mpl
import matplotlib.pyplot as plt

%config InlineBackend.figure_format = 'retina'

!apt -qq -y install fonts-nanum

import matplotlib.font_manager as fm
fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
font = fm.FontProperties(fname=fontpath, size=9)
plt.rc('font', family='NanumBarunGothic') 
mpl.font_manager._rebuild()

๋ฐ์ดํ„ฐ ์ค€๋น„ํ•˜๊ณ  ํ™•์ธ

# UCI ML Repository ์ œ๊ณตํ•˜๋Š” Breast Cancer ๋ฐ์ดํ„ฐ์…‹ ๊ฐ€์ ธ์˜ค๊ธฐ
uci_path = 'https://archive.ics.uci.edu/ml/machine-learning-databases/\breast-cancer-wisconsin/breast-cancer-wisconsin.data'
df = pd.read_csv(uci_path, header=None)

# ์—ด ์ด๋ฆ„ ์ง€์ •
df.columns = ['id', 'clump', 'cell_size', 'cell_shape', 'adhesion', 'epithlial', 'bare_nuclei', 'chromatin', 'normal_nucleoli', 'mitoses', 'class']

๐Ÿ‘€ bare_nuclei๋งŒ object์ธ ๊ฒƒ์— ์˜๋ฌธ -> unique()๋กœ ํ™•์ธ

df['bare_nuclei'].unique()

๐Ÿ‘€ '?' ๋•Œ๋ฌธ์ž„์„ ์•Œ ์ˆ˜ ์žˆ์Œ -> '?' ๋ฅผ ์—†์• ๊ธฐ ์œ„ํ•ด์„œ ๋‹ค์Œ์˜ ๊ณผ์ •์„ ๋”ฐ๋ฆ„

1) '?' -> np.nan์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ์ˆ˜๋ฅผ ํ™•์ธ

df['bare_nuclei'].replace('?', np.nan, inplace=True)
df['bare_nuclei'].isna().sum() # 16๊ฐœ์˜ ๋ฌผ์Œํ‘œ๋ฅผ np.nan๋ณ€ํ™˜

2) NaN ๋ฐ์ดํ„ฐ ์‚ญ์ œ

df.dropna(subset=['bare_nuclei'], axis=0, inplace=True)

3) bare_nuclei ์ปฌ๋Ÿผ ํ˜•๋ณ€ํ™˜ int

df['bare_nuclei'] = df['bare_nuclei'].astype('int')

df.info() ๊ฒฐ๊ณผ type์ด int๋กœ ๋ณ€ํ™˜๋˜์—ˆ์Œ


๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌํ•˜๊ธฐ

X = df[['clump', 'cell_size', 'cell_shape', 'adhesion', 'epithlial',
              'bare_nuclei', 'chromatin', 'normal_nucleoli', 'mitoses']]
y = df['class']

# X ๋…๋ฆฝ๋ณ€์ˆ˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ •๊ทœํ™”
X = preprocessing.StandardScaler().fit(X).transform(X)
X

# train, test set ๋ถ„๋ฆฌ(7:3)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=7)
print('X_train.shape : ', X_train.shape)
print('X_test.shape : ', X_test.shape)

DecisionTree ๋ถ„๋ฅ˜๋ชจ๋ธ ์„ค์ •

# ๋ชจ๋ธ ๊ฐ์ฒด ์ƒ์„ฑ (์ตœ์ ์˜ ์†์„ฑ์„ ์ฐพ๊ธฐ ์œ„ํ•ด criterion='entropy'์ ์šฉ) * ์ ์ •ํ•œ ๋ ˆ๋ฒจ ๊ฐ’ ์ฐพ๋Š” ๊ฒƒ์ด ์ค‘์š”
tree_model = tree.DecisionTreeClassifier(criterion='entropy', max_depth = 5) # 5๊ฐœ๋กœ๋งŒ ๋ชจ๋ธ ๊ตฌ์„ฑ

๋ชจ๋ธ ํ•™์Šต, ์˜ˆ์ธก

# ๋ชจ๋ธ ํ•™์Šต
tree_model.fit(X_train, y_train)
# ๋ชจ๋ธ ์˜ˆ์ธก
y_pred = tree_model.predict(X_test)

๋ชจ๋ธ ์„ฑ๋Šฅํ‰๊ฐ€

print('ํ›ˆ๋ จ ์„ธํŠธ ์ •ํ™•๋„ : {:.2f}%'.format(tree_model.score(X_train, y_train)*100))
print('ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ •ํ™•๋„ : {:.2f}%'.format(tree_model.score(X_test, y_test)*100))
tree_report = metrics.classification_report(y_test, y_pred)
print(tree_report)


๊ฒฐ์ •ํŠธ๋ฆฌ ๊ทธ๋ž˜ํ”„๊ทธ๋ฆฌ๊ธฐ

from sklearn.tree import export_graphviz
export_graphviz(tree_model, out_file='tree.dot', class_names=['์•…์„ฑ','์–‘์„ฑ'],
                feature_names=df.columns[1:10], impurity=False, filled=True)

import graphviz
with open('tree.dot') as f:
  dot_graph = f.read()
display(graphviz.Source(dot_graph))


๐ŸŽ„ ๊ฒฐ์ •ํŠธ๋ฆฌ ์•™์ƒ๋ธ”

  • ๋žœ๋ค ํฌ๋ ˆ์ŠคํŠธ ๊ตฌ์ถ•
    ํšŒ๊ท€์™€ ๋ถ„๋ฅ˜์— ์žˆ์–ด์„œ ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋จธ์‹ ๋Ÿฌ๋‹ ์•Œ๊ณ ๋ฆฌ์ฆ˜
    ํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ๊ฐ™์ด ๋งค์šฐ ์ฐจ์›์ด ๋†’๊ณ  ํฌ์†Œํ•œ ๋ฐ์ดํ„ฐ๋“ค์—๋Š” ์ž˜ ์ž‘๋™ํ•˜์ง€ ์•Š์Œ

  • ๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ… ํšŒ๊ท€ํŠธ๋ฆฌ
    ์—ฌ๋Ÿฌ๊ฐœ์˜ ๊ฒฐ์ • ํŠธ๋ฆฌ๋ฅผ ๋ฌถ์–ด ๊ฐ•๋ ฅํ•œ ๋ชจ๋ธ์„ ๋งŒ๋“ฆ


๐Ÿง‘โ€โš•๏ธ Ensemble Modeling Heart Disease ๋ถ„๋ฅ˜๋ชจ๋ธ ๋น„๊ต

๐Ÿผ ์ค€๋น„

๋ฐ์ดํ„ฐ ๋‹ค์šด๋กœ๋“œ : https://archive.ics.uci.edu/ml/datasets/heart+disease

# ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž„ํฌํŠธ
import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px

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

from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier

from sklearn import metrics

# ๋ฐ์ดํ„ฐ ์ค€๋น„
df = pd.read_csv('/content/heart.csv')

๋ฐ์ดํ„ฐ ์ „์ฒ˜๋ฆฌ

์นดํ…Œ๊ณ ๋ฆฌ(๋ฒ”์ฃผํ˜•) ์ปฌ๋Ÿผ -> Dtype category -> ์›ํ•ซ์ธ์ฝ”๋”ฉ
์ˆซ์žํ˜•(์—ฐ์†ํ˜•) ์ปฌ๋Ÿผ -> ์ •๊ทœํ™”

  • ์ปฌ๋Ÿผ๋“ค unique()๋กœ ํ™•์ธํ•ด๋ณด๊ธฐ
# ๋ฒ”์ฃผํ˜•
categorical_var = ['sex', 'cp', 'fbs', 'restecg', 'exng', 'slp', 'caa', 'thall']
df[categorical_var] = df[categorical_var].astype('category')
df.info()

# ์ˆซ์žํ˜•
numberic_var = [i for i in df.columns if i not in categorical_var][:-1] # categorical_var์ด ์•„๋‹Œ ์ปฌ๋Ÿผ๋“ค

๋ฐ์ดํ„ฐ๋ถ„์„

sns.countplot(df.sex) # sex (1 = male; 0 = female)


๐Ÿ‘€ ๋‚จ์„ฑ(1)์˜ ๋น„์œจ์ด ๋†’์Œ


px.bar(df.groupby('cp').sum().reset_index()[['cp', 'output']], x='cp', y='output',color='cp')

cp : chest pain type

-- Value 1: typical angina
-- Value 2: atypical angina
-- Value 3: non-anginal pain
-- Value 4: asymptomatic


๋ฒ”์ฃผํ˜• ๋ณ€์ˆ˜(categorical_var)์™€ output(y) ๊ด€๊ณ„ ์‹œ๊ฐํ™”

fig, ax = plt.subplots(2,4, figsize=(10,5), dpi=200)
for axis, cat_var in zip(ax.ravel(), categorical_var):
  sns.countplot(x=cat_var, data=df, hue='output', ax=axis)
plt.tight_layout()

์ˆ˜์น˜ํ˜•(numberic_var)์™€ output(y)๊ด€๊ณ„ ์‹œ๊ฐํ™”

fig, ax = plt.subplots(1, 5, figsize=(15,5), dpi=200)
for axis, num_var in zip(ax.ravel(), numberic_var):
  sns.boxplot(y=num_var, data=df, x='output', ax=axis)
plt.tight_layout()

์ˆ˜์น˜ํ˜•(numberic_var) ์ปฌ๋Ÿผ ์ค‘ ๋‚˜์ด ์ปฌ๋Ÿผ์„ ์ œ๊ฑฐํ•˜๊ณ  5%์˜ ์ด์ƒ์น˜๋ฅผ ์ œ๊ฑฐ

trtbps , chol, oldpeak : ์ƒ์œ„ 5% ์ด์ƒ์น˜ ์ œ๊ฑฐ
thalachh : ํ•˜์œ„ 5% ์ด์ƒ์น˜ ์ œ๊ฑฐ

df = df[df['trtbps'] < df['trtbps'].quantile(0.95)] # 100% - 5% = 95%๋งŒ
df = df[df['chol'] < df['chol'].quantile(0.95)]
df = df[df['oldpeak'] < df['oldpeak'].quantile(0.95)]
df = df[df['thalachh'] > df['thalachh'].quantile(0.05)]

์ˆ˜์น˜ํ˜•(numberic_var)์™€ output(y)๊ด€๊ณ„ ์‹œ๊ฐํ™”

# subplot์€ ๋‘๊ฐœ์˜ ๊ฐ’์„ return ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ณ€์ˆ˜ ๋‘๊ฐœ ํ•„์š”
fig, ax = plt.subplots(1, 5, figsize=(15,5), dpi=200)
for axis, num_var in zip(ax.ravel(), numberic_var):
  sns.boxplot(y=num_var, data=df, x='output', ax=axis)
plt.tight_layout()


๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌํ•˜๊ธฐ

X = df.iloc[:, :-1] # iloc[row,column]
y = df['output']

# ๋ฒ”์ฃผํ˜• -> ์›ํ•ซ์ธ์ฝ”๋”ฉ
temp_x = pd.get_dummies(X[categorical_var])

# ์›ํ•ซ์ธ์ฝ”๋”ฉ ์ปฌ๋Ÿผ ์ถ”๊ฐ€
X_modified = pd.concat([X, temp_x], axis=1)

# ๊ธฐ์กด ์ปฌ๋Ÿผ ์‚ญ์ œ
X_modified.drop(categorical_var, axis=1, inplace=True)
X_modified

# ์ˆ˜์น˜ํ˜• ๋ณ€์ˆ˜ -> ์ •๊ทœํ™” -> ์Šค์ผ€์ผ๋ง
X_modified[numberic_var] = StandardScaler().fit_transform(X_modified[numberic_var])
X_modified.head()

# test, train ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ
# 80:20 ๋น„์œจ๋กœ ๋ถ„๋ฆฌ
X_train, X_test, y_train, y_test = train_test_split(X_modified, y, test_size=0.2, random_state=7)

๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ ์„ค์ • ๋ฐ ํ•™์Šต

1) LogisticRegression

logreg = LogisticRegression(C=0.301).fit(X_train, y_train)
print('logreg ํ›ˆ๋ จ ์„ธํŠธ ์ •ํ™•๋„ : {:.5f}%'.format(logreg.score(X_train, y_train)*100))
print('logreg ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ •ํ™•๋„ : {:.5f}%'.format(logreg.score(X_test, y_test)*100)) 

logreg ํ›ˆ๋ จ ์„ธํŠธ ์ •ํ™•๋„ : 88.82979%
logreg ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ •ํ™•๋„ : 85.41667%

2) DecisionTree

tree = DecisionTreeClassifier(max_depth=5, min_samples_leaf=10, min_samples_split=40).fit(X_train, y_train)
print('DecisionTree ํ›ˆ๋ จ ์„ธํŠธ ์ •ํ™•๋„ : {:.5f}%'.format(tree.score(X_train, y_train)*100))
print('DecisionTree ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ •ํ™•๋„ : {:.5f}%'.format(tree.score(X_test, y_test)*100)) 

DecisionTree ํ›ˆ๋ จ ์„ธํŠธ ์ •ํ™•๋„ : 81.91489%
DecisionTree ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ •ํ™•๋„ : 81.25000%

3) RandomForest

random = RandomForestClassifier(n_estimators=400, random_state=7).fit(X_train, y_train)
print('RandomForest ํ›ˆ๋ จ ์„ธํŠธ ์ •ํ™•๋„ : {:.5f}%'.format(random.score(X_train, y_train)*100))
print('RandomForest ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ •ํ™•๋„ : {:.5f}%'.format(random.score(X_test, y_test)*100)) 

RandomForest ํ›ˆ๋ จ ์„ธํŠธ ์ •ํ™•๋„ : 100.00000%
RandomForest ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ •ํ™•๋„ : 83.33333%

4) GradientBoosting

boost = GradientBoostingClassifier(max_depth=2, learning_rate=0.05).fit(X_train, y_train)
print('GradientBoosting ํ›ˆ๋ จ ์„ธํŠธ ์ •ํ™•๋„ : {:.5f}%'.format(boost.score(X_train, y_train)*100))
print('GradientBoosting ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ •ํ™•๋„ : {:.5f}%'.format(boost.score(X_test, y_test)*100)) 

GradientBoosting ํ›ˆ๋ จ ์„ธํŠธ ์ •ํ™•๋„ : 93.61702%
GradientBoosting ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ •ํ™•๋„ : 85.41667%

๐Ÿ‘€ ์—ฌ๋Ÿฌ ์š”์ธ๋“ค(max_depth, n_estimators)์„ ๋ฐ”๊พธ๋ฉด ์ •ํ™•๋„๊ฐ€ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Œ!


ํ…Œ์ŠคํŠธ ์„ธํŠธ ์ •ํ™•๋„ ๋†’์ด๊ธฐ !

๐Ÿผ ์šฐ๋ฆฌ์กฐ ๐Ÿฅฐ 3์กฐ(1๋“ฑ) ๐Ÿฅฐ


๐Ÿ“ฐ ์ง€๋„ํ•™์Šต ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์š”์•ฝ ์ •๋ฆฌ

๊ฐ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์˜ ํŠน์ง•

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

0๊ฐœ์˜ ๋Œ“๊ธ€