K-Nearest Neighbor (KNN)이란? 개념 이해, 예제 코드

기계 학습의 다양한 알고리즘 중에서도 가장 직관적이고 쉽게 이해할 수 있는 알고리즘 중 하나가 바로 K-최근접 이웃(K-Nearest Neighbor, KNN)입니다. KNN은 새로운 데이터를 분류할 때, 데이터 주변에 있는 가장 가까운 이웃 데이터를 기준으로 그 데이터가 어떤 범주에 속할지 결정하는 방식으로 동작합니다.

1. KNN의 개념과 원리

KNN의 원리를 현실의 예시를 들어 설명하면 매우 쉽게 이해할 수 있습니다. 예를 들어, 한 사람의 성격이나 특성을 알고 싶을 때, 그 사람과 친한 주변 친구들을 보면 대략적으로 알 수 있는 것과 유사합니다. 이처럼 KNN 알고리즘은 분류되지 않은 새로운 데이터가 주어지면 그 데이터로부터 가까운 'k'개의 이웃 데이터를 찾고, 가장 많이 포함된 범주로 그 데이터를 분류합니다.

쉬운 예시를 통해 이해해 봅시다.

k-최근접 이웃(k-NN) 분류를 나타냅니다. 축은 X1과 X2로 표시되며, 두 개의 특성을 의미합니다. 중앙의 주황색 별은 분류해야 할 테스트 데이터입니다. 두 개의 클래스가 존재하며, 클래스 A(노란색), 클래스 B(초록색)로 구분됩니다. 점선 원은 K=3 및 K=6일 때 고려되는 최근접 이웃을 나타냅니다. K값에 따라 분류 결과가 달라질 수 있음을 보여주는 시각적 예시입니다.

1-1. K값에 따른 분류 사례

예를 들어, X1과 X2 두 개의 축을 기준으로 주황색 원(Class A), 초록색 원(Class B)으로 나누어진 데이터가 있고, 새로운 데이터(오렌지색 별)가 추가되었을 때:

  • K=3인 경우: 가장 가까운 3개의 이웃 중에서 Class B가 2개, Class A가 1개 선택되었다면, 다수결 원칙에 따라 이 데이터는 Class B로 분류됩니다.
  • K=6인 경우: 가장 가까운 6개의 이웃 중에서 Class A가 4개, Class B가 2개 선택되었다면, 다수결 원칙에 따라 이 데이터는 Class A로 분류됩니다.

이처럼 k값에 따라 분류 결과가 달라질 수 있어, 적절한 k값 설정이 중요합니다.

1-2. K값 설정의 장단점

k값 설정 장점 단점
작은 k (예: k=1, k=3) 빠른 결정, 간단한 계산, 지역적 패턴을 잘 반영 데이터의 잡음(noise)이나 이상치에 민감하여 잘못된 분류 가능
큰 k (예: k=10, k=20) 잡음에 덜 민감, 안정적인 결과 제공 무관한 데이터의 영향을 받을 가능성이 높고, 결정 경계가 지나치게 부드러워져 정확도가 감소할 수 있음

1-3. 일반적인 K값 선택 방법

  • 보통 기본적으로 사용하는 k값은 5입니다.
  • 실제 문제에서는 데이터셋의 크기와 분포를 고려하여 교차 검증(cross-validation)을 통해 최적의 k값을 찾습니다.
  • k는 주로 홀수로 설정하는데, 이는 분류 시 동률(동점)이 발생하는 상황을 피하기 위함입니다.

1-4. KNN 알고리즘의 장점과 단점

장점 단점
- 이해가 쉽고 구현이 간단 - 데이터가 많아질수록 연산량 증가
- 비선형 데이터 및 복잡한 결정 경계에서도 우수한 성능 - 고차원 데이터에서 성능이 저하되는 "차원의 저주" 문제 발생 가능
- 지도 학습에서 강력한 성능 - K 값(최근접 이웃의 수) 설정에 따라 성능이 크게 좌우됨

KNN 알고리즘의 성능을 최대한 활용하기 위해서는 위와 같은 특성을 이해하고, 데이터의 특성에 맞추어 적절한 K값을 선택하는 것이 필수적입니다.

2. KNN 알고리즘의 특징: 게으른 모델(Lazy Model)

KNN은 다른 머신러닝 모델들과 달리 사전에 모델을 학습하지 않고, 단지 훈련 데이터를 저장만 해둡니다. 새로운 데이터가 입력되면 그때서야 주변 이웃 데이터를 찾아 실시간으로 분류합니다. 이렇게 미리 학습하지 않고 필요할 때만 계산한다는 특징 때문에 KNN을 게으른 모델(Lazy Model) 또는 인스턴스 기반 학습(Instance-based learning) 이라고 부릅니다.

알고리즘 사전 학습 실시간 예측
KNN 불필요 가능
SVM, Decision Tree 등 필수 미리 학습된 모델로 예측

장점: 학습 시간이 거의 없고 직관적
단점: 예측 시 매번 계산이 필요해 데이터가 많으면 느려질 수 있음

3. 거리 계산 방법

KNN에서 데이터를 분류할 때 가장 중요한 것은 데이터를 비교하는 데 사용되는 '거리'를 계산하는 방식입니다. 일반적으로 사용하는 거리 계산 방식은 다음과 같이 두 가지입니다.

3-1. 유클리드 거리(Euclidean Distance)

유클리드 거리는 우리가 일반적으로 사용하는 '직선 거리'로, 두 점 사이의 거리를 직선으로 계산하는 방식입니다.

2차원 평면에서 두 점 (x1,y1),(x2,y2) 사이의 거리는 다음과 같이 구합니다:

d=(x2x1)2+(y2y1)2

이 이미지는 3차원 공간에서 두 점 A(0,0,0)와 B(3,4,5) 사이의 유클리드 거리를 나타낸다. 회색 큐브는 좌표계를 시각화하며, 파란색 선은 두 점을 연결하는 대각선 벡터를 보여준다. X, Y, Z 축이 표시되어 있으며, 점의 좌표는 빨간색으로 강조되어 있다.

3차원 이상의 공간에서도 같은 원리로 다음과 같이 계산합니다:

d=(x2x1)2+(y2y1)2+(z2z1)2+

위 이미지의 두 점 A(0,0,0)와 B(3,4,5)에 대해 계산하면: d=(30)2+(40)2+(50)2 =9+16+25 =507.07

3-2. 맨해튼 거리(Manhattan Distance)

점 A에서 점 B까지의 거리를 측정하는 두 가지 방법을 시각적으로 표현한 그래프이다. 파란색 선은 유클리드 거리로, 두 점 사이의 직선 거리(최단 거리)를 나타낸다. 빨간색 선은 맨해튼 거리로, 격자 모양의 경로를 따라 이동하는 방식이며, X축과 Y축 방향으로만 이동하는 경로를 보여준다. 녹색 선은 맨해튼 거리의 가능한 또 다른 경로를 의미한다.
맨해튼 거리 (source)

맨해튼 거리는 직선 거리가 아닌, 격자 모양의 경로를 따라가는 방식으로 X축과 Y축을 따라 거리를 더한 값입니다. 수식은 다음과 같습니다:

d=|x2x1|+|y2y1|

맨해튼 거리는 실제 도시의 도로에서 목적지를 갈 때, 직선으로 가지 않고 도로를 따라 가야 하는 경우와 같은 현실적인 제약 조건에서 자주 사용됩니다.

4. KNN 분류 예시 코드

Iris 데이터셋을 사용하여 KNN 알고리즘을 적용한 분류 예시 코드를 살펴보겠습니다.

4-1. 필요한 라이브러리 임포트

먼저 필요한 라이브러리를 임포트합니다:


        
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import seaborn as sns
  • numpy: 배열 연산을 위해 사용.
  • matplotlib.pyplot: 시각화를 위한 플롯 생성.
  • pandas: 데이터셋을 로드하고 데이터프레임으로 처리.
  • sklearn.model_selection.train_test_split: 데이터셋을 훈련/테스트로 분리.
  • sklearn.preprocessing.StandardScaler: 데이터 스케일링.
  • sklearn.neighbors.KNeighborsClassifier: KNN 분류 모델.
  • sklearn.metrics.accuracy_score, confusion_matrix, classification_report: 모델 성능 평가.
  • seaborn: 혼동 행렬 시각화를 위한 히트맵 생성.

4-2. 데이터 로드 및 준비

Iris 데이터셋을 로드하고, 특징과 레이블을 준비합니다:


        
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"
names = ['sepal-length', 'sepal-width', 'petal-length', 'petal-width', 'Class']
dataset = pd.read_csv(url, names=names)
X = dataset.iloc[:, :-1].values
y = dataset.iloc[:, 4].values
  • pd.read_csv: UCI 저장소에서 Iris 데이터셋을 다운로드해 데이터프레임으로 로드합니다.
  • names: 열 이름을 지정합니다(꽃받침 길이/너비, 꽃잎 길이/너비, 클래스).
  • X: 특징 데이터(4가지 특징: sepal-length, sepal-width, petal-length, petal-width).
  • y: 타겟 레이블(setosa, versicolor, virginica).

4-3. 훈련/테스트 데이터 분리

모델을 훈련시키고 평가하기 위해 데이터를 훈련 세트와 테스트 세트로 나눕니다:


        
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
  • train_test_split: 데이터를 80%는 훈련용(X_train, y_train), 20%는 테스트용(X_test, y_test)으로 나눕니다.
  • random_state=42: 결과 재현성을 위해 랜덤 시드를 고정합니다.

4-4. 데이터 스케일링

KNN은 거리 기반 알고리즘이므로, 특징 값의 스케일이 모델 성능에 큰 영향을 미칩니다. 따라서 StandardScaler를 사용해 데이터를 표준화합니다:


        
scaler = StandardScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
  • StandardScaler: 데이터를 평균 0, 표준편차 1로 표준화합니다.
  • fit: 훈련 데이터에 대해 스케일링 기준(평균, 표준편차)을 학습합니다.
  • transform: 훈련 데이터와 테스트 데이터를 각각 변환합니다(테스트 데이터는 훈련 데이터의 기준으로 변환).

4-5. KNN 모델 생성 및 훈련

KNN 모델을 생성하고 훈련시킵니다:


        
classifier = KNeighborsClassifier(n_neighbors=5)
classifier.fit(X_train, y_train)
  • KNeighborsClassifier: KNN 분류기를 생성합니다.
  • n_neighbors=5: k=5로 설정해 가장 가까운 5개의 이웃을 기준으로 분류합니다.
  • fit: 스케일링된 훈련 데이터를 사용해 모델을 학습시킵니다.

4-6. 예측 및 성능 평가

테스트 데이터로 예측을 수행하고 혼동 행렬을 시각화합니다:


        
y_pred = classifier.predict(X_test)
# 혼동 행렬 시각화
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=['class_0', 'class_1', 'class_2'],
yticklabels=['class_0', 'class_1', 'class_2'])
plt.title('Confusion Matrix')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.show()
  • predict: 테스트 데이터를 사용해 예측값(y_pred)을 생성합니다.
  • confusion_matrix: 실제 레이블(y_test)과 예측 레이블(y_pred)을 비교해 혼동 행렬을 생성합니다.
  • sns.heatmap: 혼동 행렬을 히트맵으로 시각화합니다.
    • annot=True: 각 셀에 숫자를 표시.
    • fmt='d': 숫자를 정수로 표시.
    • cmap='Blues': 파란색 계열 색상 사용.
    • xticklabels, yticklabels: 클래스 이름을 설정(Iris 데이터셋에서는 setosa, versicolor, virginica로 대응).

4-7. 최종 코드


        
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, confusion_matrix
import seaborn as sns
# 1. 데이터 로드 (와인 데이터셋)
wine = load_wine()
X = wine.data[:, [0, 9]] # 간단히 2개의 특징만 사용 (알코올 도수, 색상 강도)
y = wine.target
# 2. 훈련/테스트 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
# 3. 데이터 스케일링 (MinMaxScaler 사용)
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 4. KNN 모델 생성 및 훈련
knn = KNeighborsClassifier(n_neighbors=7, weights='distance') # 거리 가중치 추가
knn.fit(X_train_scaled, y_train)
# 5. 예측
y_pred = knn.predict(X_test_scaled)
# 6. 결과 평가
accuracy = accuracy_score(y_test, y_pred)
print(f"정확도: {accuracy:.2f}")
# 7. 혼동 행렬 시각화
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=wine.target_names, yticklabels=wine.target_names)
plt.title('Confusion Matrix')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.show()
# 8. 결정 경계 시각화
def plot_decision_boundary(X, y, model):
h = 0.02 # step size in the mesh
x_min, x_max = X[:, 0].min() - 0.1, X[:, 0].max() + 0.1
y_min, y_max = X[:, 1].min() - 0.1, X[:, 1].max() + 0.1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm')
scatter = plt.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='k')
plt.colorbar(scatter)
plt.xlabel(wine.feature_names[0])
plt.ylabel(wine.feature_names[9])
plt.title('KNN Decision Boundary (k=7)')
plt.show()
# 결정 경계 플롯
plot_decision_boundary(X_train_scaled, y_train, knn)

5. 결과 분석: 혼동 행렬과 결정 경계 해석

실행 결과로 나온 혼동 행렬과 결정 경계는 다음과 같습니다:

5-1. 혼동 행렬(Confusion Matrix)

혼동 행렬(Confusion Matrix)을 나타내며, 분류 모델의 성능을 평가하는 데 사용됩니다. X축은 예측된 클래스(Predicted), Y축은 실제 클래스(Actual)를 나타내며, 각 셀의 숫자는 특정 클래스가 얼마나 정확하게 예측되었는지를 보여줍니다. 예를 들어, class_0은 14번 올바르게 예측되었고, 1번 class_2로 잘못 분류되었습니다. class_1은 대부분 정확하게 예측되었으며(class_1 → class_1: 17회), class_2는 4번 class_0으로 잘못 분류되었습니다.

5-1-1. 혼동 행렬 분석

  • 행(Actual): 실제 클래스 (class_0, class_1, class_2)
  • 열(Predicted): 모델이 예측한 클래스
  • 대각선 값: 올바르게 예측된 샘플 수 (True Positives)
  • 비대각선 값: 잘못 예측된 샘플 수 (False Positives/Negatives)

5-1-2. 세부 분석:

  1. class_0:

    • 14개 샘플이 class_0으로 올바르게 예측됨.
    • 0개 샘플이 class_1로 잘못 예측됨.
    • 1개 샘플이 class_2로 잘못 예측됨.
    • 총 15개 샘플 중 14개가 올바르게 분류됨 (정확도: 14/15 = 93.3%).
  2. class_1:

    • 0개 샘플이 class_0으로 잘못 예측됨.
    • 17개 샘플이 class_1로 올바르게 예측됨.
    • 1개 샘플이 class_2로 잘못 예측됨.
    • 총 18개 샘플 중 17개가 올바르게 분류됨 (정확도: 17/18 = 94.4%).
  3. class_2:

    • 4개 샘플이 class_0으로 잘못 예측됨.
    • 0개 샘플이 class_1로 잘못 예측됨.
    • 8개 샘플이 class_2로 올바르게 예측됨.
    • 총 12개 샘플 중 8개가 올바르게 분류됨 (정확도: 8/12 = 66.7%).

5-1-3. 전체 성능:

  • 총 샘플 수: 15 + 18 + 12 = 45개
  • 올바르게 예측된 샘플 수: 14 + 17 + 8 = 39개
  • 전체 정확도(Accuracy): 39/45 = 0.8667 (약 86.7%)

5-1-4. 추가 지표:

  • 정밀도(Precision), 재현율(Recall), F1-Score:
    • class_0:
      • 정밀도: 14/(14+4) = 0.7778 (class_0으로 예측한 것 중 실제 class_0 비율)
      • 재현율: 14/15 = 0.9333 (실제 class_0 중 올바르게 예측한 비율)
      • F1-Score: 2 * (0.7778 * 0.9333) / (0.7778 + 0.9333) ≈ 0.8485
    • class_1:
      • 정밀도: 17/(17+0) = 1.0
      • 재현율: 17/18 = 0.9444
      • F1-Score: 2 * (1.0 * 0.9444) / (1.0 + 0.9444) ≈ 0.9714
    • class_2:
      • 정밀도: 8/(8+2) = 0.8
      • 재현율: 8/12 = 0.6667
      • F1-Score: 2 * (0.8 * 0.6667) / (0.8 + 0.6667) ≈ 0.7273

5-2. 결정 경계(Decision Boundary)

KNN(K-Nearest Neighbors) 알고리즘의 결정 경계(Decision Boundary) 시각화입니다. X축은 "alcohol" 변수, Y축은 "color_intensity" 변수이며, 데이터 포인트는 각각 세 개의 클래스로 분류됩니다. 배경 색상은 모델이 특정 영역에서 어느 클래스로 분류하는지를 나타냅니다. 붉은색 영역은 class_2, 푸른색 영역은 class_1, 흰색 영역은 class_0에 해당합니다. 점들은 실제 데이터 포인트이며, 색상은 해당 샘플의 실제 클래스를 의미합니다. K값이 7로 설정되어 있어, 각 포인트의 가장 가까운 7개의 이웃을 기준으로 분류가 이루어집니다.

5-2-1. 결정 경계 분석

결정 경계 플롯은 KNN이 두 특징(알코올 도수, 색상 강도)을 기준으로 데이터를 어떻게 분류하는지 보여줍니다:

  • 색상 영역: 각 클래스의 경계를 나타냅니다.
    • 흰색 영역: class_0
    • 분홍색 영역: class_1
    • 파란색 영역: class_2
  • 데이터 포인트: 훈련 데이터의 실제 클래스(색상)와 위치를 나타냅니다.
    • 흰색 원: class_0
    • 분홍색 원: class_1
    • 파란색 원: class_2

5-2-2. 해석:

  • class_0 (흰색 영역): 알코올 도수가 낮고 색상 강도가 낮은 영역에서 주로 분포합니다. 이 클래스는 다른 클래스와 비교적 명확하게 분리되어 있습니다.
  • class_1 (분홍색 영역): 알코올 도수가 높고 색상 강도가 높은 영역에서 주로 분포합니다. 이 클래스는 class_2와 일부 겹치는 부분이 있지만, 대체로 잘 구분됩니다.
  • class_2 (파란색 영역): 알코올 도수와 색상 강도가 중간에서 높은 영역에서 분포합니다. 이 클래스는 class_1과 경계가 모호한 부분이 많아 혼동이 발생하는 경우가 있습니다.

혼동 행렬에서 class_2의 4개 샘플이 class_0으로 잘못 분류된 이유를 결정 경계에서 확인할 수 있습니다. class_2의 데이터 포인트 중 일부가 class_0의 영역(알코올 도수와 색상 강도가 낮은 곳)에 위치해 있어 모델이 잘못 예측한 것으로 보입니다.

6. 결과 요약 및 개선 방안

6-1. 모델 성능 요약

  • 모델은 class_1을 가장 잘 예측했습니다(F1-Score: 0.9714). 정밀도와 재현율 모두 높아 이 클래스에 대해 매우 안정적인 성능을 보입니다.
  • class_0도 비교적 잘 예측했습니다(F1-Score: 0.8485). 단, 1개 샘플이 class_2로 잘못 분류되었습니다.
  • class_2는 성능이 상대적으로 낮습니다(F1-Score: 0.7273). 특히 4개 샘플이 class_0으로 잘못 분류된 점이 눈에 띕니다. 이는 결정 경계에서 class_2와 class_0의 특징이 일부 겹치는 부분이 있음을 시사합니다.

6-2. 개선 방안

  • k값 조정: 현재 k=7로 설정되어 있습니다. k값을 3, 5, 9 등으로 변경하며 성능 변화를 확인해보세요. k값이 너무 크면 과소적합, 너무 작으면 과대적합될 수 있습니다.
  • 더 많은 특징 사용: 현재는 알코올 도수와 색상 강도만 사용했지만, 와인 데이터셋의 13가지 특징을 모두 활용하거나 PCA(주성분 분석)를 적용해 차원을 축소해볼 수 있습니다.
  • 클래스 불균형 확인: class_2의 샘플 수가 상대적으로 적을 수 있으니, 데이터셋의 클래스 분포를 확인하고 필요하면 오버샘플링(SMOTE 등) 기법을 적용해보세요.
  • 스케일링 방법 변경: MinMaxScaler 대신 StandardScaler를 사용해 성능을 비교해볼 수 있습니다.

7. 추가 분석: k값에 따른 성능 변화

k값에 따라 모델 성능이 어떻게 변하는지 확인하기 위해 아래 코드를 추가해볼 수 있습니다:


        
k_range = range(1, 16)
scores = []
for k in k_range:
knn = KNeighborsClassifier(n_neighbors=k, weights='distance')
knn.fit(X_train_scaled, y_train)
y_pred = knn.predict(X_test_scaled)
scores.append(accuracy_score(y_test, y_pred))
plt.figure(figsize=(8, 6))
plt.plot(k_range, scores, marker='o', linestyle='--', color='b')
plt.xlabel('k value')
plt.ylabel('Accuracy')
plt.title('Accuracy vs. k in KNN')
plt.grid(True)
plt.show()

이 코드는 k값을 1부터 15까지 변경하며 정확도를 계산하고, 결과를 선 그래프로 시각화합니다. 이를 통해 최적의 k값을 찾을 수 있습니다.

KNN(K-Nearest Neighbors) 알고리즘에서 k 값에 따른 분류 정확도 변화를 나타내는 그래프입니다. X축은 k 값(K의 크기)을, Y축은 정확도(Accuracy)를 의미하며, 파란 점과 점선을 이용해 변화 추이를 시각적으로 표현하고 있습니다. 그래프는 k 값이 증가함에 따라 정확도가 변동하는 모습을 보여주며, 특정 k 값에서 최고 정확도를 기록합니다.

7-1. 최적 값 도출

또는, 최적 값을 정확히 출력하는 파이썬 코드를 다음과 같이 삽입할 수도 있습니다.


        
# scores를 numpy 배열로 변환
scores = np.array(scores)
# 최대 정확도 찾기
max_score = np.max(scores)
# 최대 정확도를 가지는 모든 k값 찾기
optimal_k_values = [k for k, score in zip(k_range, scores) if score == max_score]
# 결과 출력
print(f"최적의 k값: {optimal_k_values}, 정확도: {max_score:.2f}")

이 코드를 실행하면 다음과 같은 결과를 얻습니다.

최적의 k값: [1, 2, 6, 7], 정확도: 0.87

위 그래프와 일치합니다.

마치며

KNN은 매우 직관적이고 간단한 알고리즘으로 빠른 예측을 요구하는 여러 분야에서 널리 사용됩니다. 그러나 데이터가 복잡하고 많아질수록 계산량이 증가하고 성능이 저하될 수 있으므로, 상황에 맞는 거리 계산법과 k값 선정이 중요합니다. KNN의 단점은 다른 알고리즘으로 보완하여 보다 강력한 분석 시스템을 구성할 수 있습니다.