scikit-learn의 MLPClassifier(다층 퍼셉트론 분류기)는 학습 과정에서 각 반복(iteration)마다 계산된 손실(loss) 값을 loss_curve_ 속성에 저장합니다. 이 손실 곡선을 Matplotlib로 적절하게 시각화하면 학습률, 모멘텀, 솔버 등 하이퍼파라미터 설정에 따라 모델이 얼마나 잘 수렴하는지 한눈에 비교할 수 있습니다.
손실 곡선을 플롯하는 단계
- 그림 크기를 설정하고 서브플롯 주변 및 사이의 여백(padding)을 조정합니다.
- 다양한 학습 옵션을 담은 딕셔너리 리스트(params)를 만듭니다.
- 각 설정에 대한 레이블(labels)과 플롯 스타일(plot_args) 목록을 준비합니다.
- nrows=2, ncols=2로 구성된 figure와 subplot 집합을 생성합니다.
- Iris(붓꽃), Digits(숫자 필기체) 데이터셋과 make_circles, make_moons로 생성한 합성 데이터셋을 로드합니다.
- 각 데이터셋과 축(ax), 데이터셋 이름을 zip으로 묶어 순회합니다.
- plot_on_dataset() 함수 내에서 현재 축의 제목을 설정하고, MinMaxScaler로 특성 값을 정규화합니다.
- MLPClassifier 인스턴스를 각 파라미터 조합마다 생성하여 학습(fit)시키고, mlps 리스트에 저장합니다.
- 학습된 각 MLP 객체의 mlp.loss_curve_를 plot() 메서드로 그립니다.
- 마지막으로 show() 메서드로 그림을 화면에 출력합니다.
예제 코드
import warnings
import matplotlib.pyplot as plt
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import MinMaxScaler
from sklearn import datasets
from sklearn.exceptions import ConvergenceWarning
plt.rcParams["figure.figsize"] = [7.50, 3.50]
plt.rcParams["figure.autolayout"] = True
# 솔버, 학습률, 모멘텀 등 다양한 하이퍼파라미터 조합
params = [{'solver': 'sgd', 'learning_rate': 'constant', 'momentum': 0, 'learning_rate_init': 0.2},
{'solver': 'sgd', 'learning_rate': 'constant', 'momentum': .9, 'nesterovs_momentum': False, 'learning_rate_init': 0.2},
{'solver': 'sgd', 'learning_rate': 'constant', 'momentum': .9, 'nesterovs_momentum': True, 'learning_rate_init': 0.2},
{'solver': 'sgd', 'learning_rate': 'invscaling', 'momentum': 0, 'learning_rate_init': 0.2},
{'solver': 'sgd', 'learning_rate': 'invscaling', 'momentum': .9, 'nesterovs_momentum': True, 'learning_rate_init': 0.2},
{'solver': 'sgd', 'learning_rate': 'invscaling', 'momentum': .9, 'nesterovs_momentum': False, 'learning_rate_init': 0.2},
{'solver': 'adam', 'learning_rate_init': 0.01}]
labels = ["constant learning-rate", "constant with momentum", "constant with Nesterov's momentum",
"inv-scaling learning-rate", "inv-scaling with momentum", "inv-scaling with Nesterov's momentum", "adam"]
plot_args = [{'c': 'red', 'linestyle': '-'},
{'c': 'green', 'linestyle': '-'},
{'c': 'blue', 'linestyle': '-'},
{'c': 'red', 'linestyle': '--'},
{'c': 'green', 'linestyle': '--'},
{'c': 'blue', 'linestyle': '--'},
{'c': 'black', 'linestyle': '-'}]
def plot_on_dataset(X, y, ax, name):
ax.set_title(name)
X = MinMaxScaler().fit_transform(X)
mlps = []
if name == "digits":
max_iter = 15 # digits는 데이터가 많아 빠른 비교를 위해 반복 횟수를 줄임
else:
max_iter = 400
for label, param in zip(labels, params):
mlp = MLPClassifier(random_state=0, max_iter=max_iter, **param)
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=ConvergenceWarning, module="sklearn")
mlp.fit(X, y)
mlps.append(mlp)
for mlp, label, args in zip(mlps, labels, plot_args):
ax.plot(mlp.loss_curve_, label=label, **args)
fig, axes = plt.subplots(2, 2)
iris = datasets.load_iris()
X_digits, y_digits = datasets.load_digits(return_X_y=True)
data_sets = [(iris.data, iris.target), (X_digits, y_digits),
datasets.make_circles(noise=0.2, factor=0.5, random_state=1),
datasets.make_moons(noise=0.3, random_state=0)]
for ax, data, name in zip(axes.ravel(), data_sets,
['iris', 'digits', 'circles', 'moons']):
plot_on_dataset(*data, ax=ax, name=name)
fig.legend(ax.get_lines(), labels, ncol=3, loc="upper center")
plt.show()실행 결과
코드를 실행하면 iris, digits, circles, moons 네 개의 데이터셋에 대해 각각 2×2 격자 형태의 서브플롯에 손실 곡선이 그려집니다. 각 곡선은 색상과 선 스타일로 구분되며, 상단 중앙의 범례(legend)를 통해 어떤 하이퍼파라미터 조합인지 확인할 수 있습니다.


결과 해석 포인트
- Adam 옵티마이저: 대부분의 데이터셋에서 가장 빠르고 안정적으로 손실이 감소하는 경향을 보입니다.
- 모멘텀(momentum): SGD에 모멘텀을 추가하면 수렴 속도가 눈에 띄게 향상됩니다.
- Nesterov 모멘텀: 일반적인 모멘텀과 유사하지만 경우에 따라 더 부드러운 수렴을 보입니다.
- inv-scaling 학습률: 반복이 진행될수록 학습률이 점진적으로 감소하여 후반부에 안정적인 수렴을 보입니다.
이처럼 loss_curve_를 시각화하면 단순히 최종 정확도만 비교하는 것보다 각 옵티마이저와 학습률 전략의 수렴 특성을 직관적으로 이해할 수 있어, 모델 튜닝에 매우 유용합니다.