2장 간단한 분류 알고리즘 훈련
2.1 인공 뉴런: 초기 머신 러닝의 간단한 역사
맥컬록-피츠 뉴런 : 간소화된 뇌의 뉴런 개념 -> 신경 세포를 이진 출력을 내는 간단한 논리 회로로 표현
퍼셉트론 : 자동으로 최적의 가중치를 학습하는 알고리즘을 제안
2.1.1 인공 뉴런의 수학적 정의
인공 뉴런 아이디어 - 두 개의 클래스가 있는 이진 분류 작업
결정 함수 : 단위 계단 함수를 변형한 것
2.1.2 퍼셉트론 학습 규칙
☑️과정☑️
1) 가중치를 0 또는 랜덤한 작은 값으로 초기화
2) 각 훈련 샘플 x(i)에서 다음 작업을 함
- a. 출력 값 y(단위 계단 함수로 예측한 클래스 레이블)를 계산
- b. 가중치를 업데이트(n은 학습률, y(i)는 i번째 훈련 샘플의 진짜 클래스 레이블)
퍼셉트론은 두 클래스가 선형적으로 구분되고, 학습률이 충분히 작을 때만 수렴이 보장됨 -> 두 클래스를 선형 결정 경계로 나눌 수 없다면 훈련 데이터셋을 반복할 최대 횟수(에포크)를 지정하고 분류 허용 오차를 지정
2.2 파이썬으로 퍼셉트론 학습 알고리즘 구현
2.2.1 객체 지향 퍼셉트론 API
rgen : 넘파이 난수 생성기로 사용자가 지정한 랜덤 시드로 이전과 동일한 결과 재현 가능
np.across : 역코사인 삼각 함수
np.linalg.norm : 벡터 길이를 계산하는 함수
np.dot : 벡터 점곱을 계산
import numpy as np
class Perceptron(object):
"""퍼셉트론 분류기
매개변수
------------
eta : float
학습률 (0.0과 1.0 사이)
n_iter : int
훈련 데이터셋 반복 횟수
random_state : int
가중치 무작위 초기화를 위한 난수 생성기 시드
속성
-----------
w_ : 1d-array
학습된 가중치
errors_ : list
에포크마다 누적된 분류 오류
"""
def __init__(self, eta=0.01, n_iter=50, random_state=1):
self.eta = eta
self.n_iter = n_iter
self.random_state = random_state
# 가중치를 초기화한 후 훈련 데이터셋에 있는 모든 개개의 샘플을 반복 순회하면서 가중치를 업데이트
def fit(self, X, y):
"""훈련 데이터 학습
매개변수
----------
X : {array-like}, shape = [n_samples, n_features]
n_samples개의 샘플과 n_features개의 특성으로 이루어진 훈련 데이터
y : array-like, shape = [n_samples]
타깃값
반환값
-------
self : object
"""
rgen = np.random.RandomState(self.random_state)
self.w_ = rgen.normal(loc=0.0, scale=0.01, size=1 + X.shape[1]) # 가중치를 벡터 R(m+1)로 초기화, m은 데이터셋에 있는 특성 개수
self.errors_ = []
for _ in range(self.n_iter):
errors = 0
for xi, target in zip(X, y):
update = self.eta * (target - self.predict(xi))
self.w_[1:] += update * xi
self.w_[0] += update
errors += int(update != 0.0)
self.errors_.append(errors) # 잘못 분류된 횟수를 기록
return self
def net_input(self, X):
"""입력 계산"""
return np.dot(X, self.w_[1:]) + self.w_[0]
# 클래스 레이블 예측
def predict(self, X):
"""단위 계단 함수를 사용하여 클래스 레이블을 반환합니다"""
return np.where(self.net_input(X) >= 0.0, 1, -1)
2.2.2 붓꽃 데이터셋에서 퍼셉트론 훈련
꽃받침 길이와 꽃잎 길이만 사용
이진 분류기(일대다 전략) -> 붓꽃 데이터셋에서 두 개의 꽃만 사용(다중 클래스 분류로 확장 가능)
☑️ pandas 라이브러리를 사용하여 UCI 머신 러닝 저장소에서 붓꽃 데이터셋을 DataFrame 객체로 직접 로드 ☑️ tail 메서드로 마지막 다섯 줄을 출력
import os
import pandas as pd
s = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
print('URL:', s)
df = pd.read_csv(s,
header=None,
encoding='utf-8')
df.tail()
☑️ 50개의 Iris-setoa와 50개의 Iris-verisicolor 꽃에 해당하는 처음 100개의 클래스 레이블을 추출
☑️ 클래스 레이블을 두 개의 정수 클래스 1(verisicollor)와 -1(setosa)로 바꾼 후 벡터 y에 저장
☑️ 100개의 훈련 샘플에서 첫 번째 특성 열(꽃받침 길이)과 세 번째 특성 열(꽃잎 길이)을 추출하여 특성 행렬 x에 저장
☑️ 2차원 산점도로 시각화
import matplotlib.pyplot as plt
import numpy as np
# setosa와 versicolor를 선택합니다
y = df.iloc[0:100, 4].values
y = np.where(y == 'Iris-setosa', -1, 1)
# 꽃받침 길이와 꽃잎 길이를 추출합니다
X = df.iloc[0:100, [0, 2]].values # 넘파이 배열을 반환
# 산점도를 그립니다
plt.scatter(X[:50, 0], X[:50, 1],
color='red', marker='o', label='setosa')
plt.scatter(X[50:100, 0], X[50:100, 1],
color='blue', marker='x', label='versicolor')
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc='upper left')
# plt.savefig('images/02_06.png', dpi=300)
plt.show()
☑️ 붓꽃 데이터셋에서 추출한 일부 데이터에서 퍼셉트론 알고리즘을 훈련
☑️ 에포크 대비 잘못 분류된 오차를 그래프로 그려서, 알고리즘이 수렴하여 두 붓꽃 클래스를 구분하는 결정 경계를 찾는지 확인
ppn = Perceptron(eta=0.1, n_iter=10)
ppn.fit(X, y)
plt.plot(range(1, len(ppn.errors_) + 1), ppn.errors_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Number of updates')
# plt.savefig('images/02_07.png', dpi=300)
plt.show()
-> 여섯번째 에포크 이후에 수렴했고 훈련 샘플을 완벽하게 분류함
<2차원 데이터셋의 결정 경계를 시각화>
☑️ colors와 markers를 정의 -> ListendColormap을 사용하여 colors 리스트에서 컬러맵을 만듦
☑️ 두 특성의 최솟값과 최댓값을 찾고 이 벡터로 넘파이 meshgrid 함수로 그리드 배열 xx1과 xx2 쌍을 만듦
☑️ 그리드 배열을 펼치고 훈련 데이터와 같은 개수의 열이 되도록 행렬을 만듦
☑️ predict 메서드로 그리드 각 포인트에 데ㅐ응하는 클래스 레이블 Z을 예측
☑️ 클래스 레이블 Z를 xx1, xx2 같은 차원의 그리드로 크기 변경 -> matplotlib의 contourf 함수로 등고선 그래프 그리기 (결정 영역 나타냄)
from matplotlib.colors import ListedColormap
def plot_decision_regions(X, y, classifier, resolution=0.02):
# 마커와 컬러맵을 설정합니다
markers = ('s', 'x', 'o', '^', 'v')
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(y))])
# 결정 경계를 그립니다
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
np.arange(x2_min, x2_max, resolution))
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
Z = Z.reshape(xx1.shape)
plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
# 샘플의 산점도를 그립니다
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, 0],
y=X[y == cl, 1],
alpha=0.8,
c=colors[idx],
marker=markers[idx],
label=cl,
edgecolor='black')
plot_decision_regions(X, y, classifier=ppn)
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc='upper left')
# plt.savefig('images/02_08.png', dpi=300)
plt.show()
2.3 적응형 선형 뉴런과 학습의 수렴
아달린 규칙과 로젠블라트 퍼셉트론의 차이점 : 가중치를 업데이트하는 데 선형 활성화 함수(단순한 항등 함수)를 사용
아달린 알고리즘 - 진짜 클래스 레이블 & 선형 활성화 함수의 실수 출력 값 비교 -> 모델 오차 계산하고 가중치 업데이트
퍼셉트론 - 진짜 클래스 레이블 & 예측 클래스 레이블을 비교
2.3.1 경사 하강법으로 비용 함수 최소화
경사하강법 : 1차 근삿값 발견용 최적화 알고리즘으로, 기울기를 구하고 경사의 절댓값이 낮은 쪽으로 계속 이동시켜 극값에 이를 때까지 이를 반복시키는 것
배치 경사 하강법 : 전체 데이터에 대한 기울기를 한번만 계산하여 가중치를 업데이트하는 방법
2.3.2 파이썬으로 아달린 구현
☑️ fit 메서드를 바꾸어 경사 하강법으로 비용 함수가 최소화되도록 가중치를 업데이트 (최종 입력, 활성화, 출력 순으로 진행)
class AdalineGD(object):
"""적응형 선형 뉴런 분류기
매개변수
------------
eta : float
학습률 (0.0과 1.0 사이)
n_iter : int
훈련 데이터셋 반복 횟수
random_state : int
가중치 무작위 초기화를 위한 난수 생성기 시드
속성
-----------
w_ : 1d-array
학습된 가중치
cost_ : list
에포크마다 누적된 비용 함수의 제곱합
"""
def __init__(self, eta=0.01, n_iter=50, random_state=1):
self.eta = eta
self.n_iter = n_iter
self.random_state = random_state
def fit(self, X, y):
"""훈련 데이터 학습
매개변수
----------
X : {array-like}, shape = [n_samples, n_features]
n_samples 개의 샘플과 n_features 개의 특성으로 이루어진 훈련 데이터
y : array-like, shape = [n_samples]
타깃값
반환값
-------
self : object
"""
rgen = np.random.RandomState(self.random_state)
self.w_ = rgen.normal(loc=0.0, scale=0.01, size=1 + X.shape[1])
self.cost_ = []
for i in range(self.n_iter):
net_input = self.net_input(X)
# 이 코드의 활성화 함수는 항등 함수(identity function)이기 때문에
# 아무런 효과가 없습니다.
# 이 대신 `output = self.net_input(X)`로 바로 쓸 수 있습니다.
# 이 활성화 함수는 개념적인 목적을 위해 만들었습니다.
# (잠시 후에 보게 될) 로지스틱 회귀의 경우 이 함수를 시그모이드 함수로
# 바꾸어 로지스틱 회귀 분류기를 구현합니다.
output = self.activation(net_input)
errors = (y - output)
# X.T.dot(errors) : 특성 행렬과 오차 벡터 간의 행렬-벡터 곱셈
self.w_[1:] += self.eta * X.T.dot(errors) # 가중치 1에서 m까지
self.w_[0] += self.eta * errors.sum() # 절편(0번째 가중치)
cost = (errors**2).sum() / 2.0
self.cost_.append(cost)
return self
def net_input(self, X):
"""최종 입력 계산"""
return np.dot(X, self.w_[1:]) + self.w_[0]
def activation(self, X):
"""선형 활성화 계산"""
return X
def predict(self, X):
"""단위 계단 함수를 사용하여 클래스 레이블을 반환합니다"""
return np.where(self.activation(self.net_input(X)) >= 0.0, 1, -1)
☑️ 두 개의 학습률(0.1, 0.0001)에서 에포크 횟수 대비 비용 그래프 그리기
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))
ada1 = AdalineGD(n_iter=10, eta=0.01).fit(X, y)
ax[0].plot(range(1, len(ada1.cost_) + 1), np.log10(ada1.cost_), marker='o')
ax[0].set_xlabel('Epochs')
ax[0].set_ylabel('log(Sum-squared-error)')
ax[0].set_title('Adaline - Learning rate 0.01')
ada2 = AdalineGD(n_iter=10, eta=0.0001).fit(X, y)
ax[1].plot(range(1, len(ada2.cost_) + 1), ada2.cost_, marker='o')
ax[1].set_xlabel('Epochs')
ax[1].set_ylabel('Sum-squared-error')
ax[1].set_title('Adaline - Learning rate 0.0001')
# plt.savefig('images/02_11.png', dpi=300)
plt.show()
-> 적절한 학습률로 하는 것이 중요
2.3.3 특성 스케일을 조정하여 경사 하강법 결과 향상
경사 하강법은 표준화라는 특성 스케일 방법을 사용
☑️ 넘파이 내장 함수 mean과 std로 표준화
# 특성을 표준화합니다.
X_std = np.copy(X)
X_std[:, 0] = (X[:, 0] - X[:, 0].mean()) / X[:, 0].std()
X_std[:, 1] = (X[:, 1] - X[:, 1].mean()) / X[:, 1].std()
☑️ 표준화한 후 다시 아달린 모델을 훈련하고 학습률 0.01에서 몇 번의 에포크만에 수렴하는지 확인
ada_gd = AdalineGD(n_iter=15, eta=0.01)
ada_gd.fit(X_std, y)
plot_decision_regions(X_std, y, classifier=ada_gd)
plt.title('Adaline - Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
# plt.savefig('images/02_14_1.png', dpi=300)
plt.show()
plt.plot(range(1, len(ada_gd.cost_) + 1), ada_gd.cost_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Sum-squared-error')
plt.tight_layout()
# plt.savefig('images/02_14_2.png', dpi=300)
plt.show()
-> 학습률 0.01을 사용하고 표준화된 특성에서 훈련하여 아달린 모델 수렴됨
2.3.4 대규모 머신 러닝과 확률적 경사 하강법
확률적 경사 하강법 : 추출된 각 훈련 샘플에 대해서 가중치를 계산하고, 경사 하강법을 적용하는 방법
- 수렴 속도가 훨씬 빠름
- 온라인 학습으로 사용 가능(새로운 훈련 데이터가 도착하는 대로 모델 훈련 -> 시스템은 변화에 즉시 적응)
☑️ fit 메서드 안에서 각 훈련 샘플에 대해 가중치를 업데이트
☑️ partial_fit 메서드 구현(온라인 학습에서 사용 가능)
☑️ 훈련 후에는 알고리즘이 수렴하는지 확인하기 위해 에포크마다 훈련 샘플의 평균 비용 계산
import numpy as np
class AdalineSGD(object):
"""ADAptive LInear NEuron 분류기
Parameters
------------
eta : float
학습률 (0.0과 1.0 사이)
n_iter : int
훈련 데이터셋 반복 횟수
shuffle : bool (default: True)
True로 설정하면 같은 반복이 되지 않도록 에포크마다 훈련 데이터를 섞습니다
random_state : int
가중치 무작위 초기화를 위한 난수 생성기 시드
Attributes
-----------
w_ : 1d-array
학습된 가중치
cost_ : list
모든 훈련 샘플에 대해 에포크마다 누적된 평균 비용 함수의 제곱합
"""
def __init__(self, eta=0.01, n_iter=10, shuffle=True, random_state=None):
self.eta = eta
self.n_iter = n_iter
self.w_initialized = False
self.shuffle = shuffle
self.random_state = random_state
def fit(self, X, y):
"""훈련 데이터 학습
Parameters
----------
X : {array-like}, shape = [n_samples, n_features]
n_samples 개의 샘플과 n_features 개의 특성으로 이루어진 훈련 데이터
y : array-like, shape = [n_samples]
타깃 벡터
반환값
-------
self : object
"""
self._initialize_weights(X.shape[1])
self.cost_ = []
for i in range(self.n_iter):
if self.shuffle:
X, y = self._shuffle(X, y)
cost = []
for xi, target in zip(X, y):
cost.append(self._update_weights(xi, target))
avg_cost = sum(cost) / len(y)
self.cost_.append(avg_cost)
return self
def partial_fit(self, X, y):
"""가중치를 다시 초기화하지 않고 훈련 데이터를 학습합니다"""
if not self.w_initialized:
self._initialize_weights(X.shape[1])
if y.ravel().shape[0] > 1:
for xi, target in zip(X, y):
self._update_weights(xi, target)
else:
self._update_weights(X, y)
return self
def _shuffle(self, X, y):
"""훈련 데이터를 섞습니다"""
r = self.rgen.permutation(len(y)) # 0에서 100까지 중복되지 않은 랜덤한 숫자 시퀀스를 생성
return X[r], y[r] # 나온 숫자 시퀀스를 특성 행렬과 클래스 레이블 벡터를 섞는 인덱스로 사용
def _initialize_weights(self, m):
"""랜덤한 작은 수로 가중치를 초기화합니다"""
self.rgen = np.random.RandomState(self.random_state)
self.w_ = self.rgen.normal(loc=0.0, scale=0.01, size=1 + m)
self.w_initialized = True
def _update_weights(self, xi, target):
"""아달린 학습 규칙을 적용하여 가중치를 업데이트합니다"""
output = self.activation(self.net_input(xi))
error = (target - output)
self.w_[1:] += self.eta * xi.dot(error)
self.w_[0] += self.eta * error
cost = 0.5 * error ** 2
return cost
def net_input(self, X):
"""입력 계산"""
return np.dot(X, self.w_[1:]) + self.w_[0]
def activation(self, X):
"""선형 활성화 계산"""
return X
def predict(self, X):
"""단위 계단 함수를 사용하여 클래스 레이블을 반환합니다"""
return np.where(self.activation(self.net_input(X)) >= 0.0, 1, -1)
☑️ fit 메서드로 AdalineSGD 분류기 훈련
☑️ plot_deicision_regions로 훈련 결과를 그래프로 그림
ada_sgd = AdalineSGD(n_iter=15, eta=0.01, random_state=1)
ada_sgd.fit(X_std, y)
plot_decision_regions(X_std, y, classifier=ada_sgd)
plt.title('Adaline - Stochastic Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
# plt.savefig('images/02_15_1.png', dpi=300)
plt.show()
plt.plot(range(1, len(ada_sgd.cost_) + 1), ada_sgd.cost_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Average Cost')
plt.tight_layout()
# plt.savefig('images/02_15_2.png', dpi=300)
plt.show()
-> 평균 비용이 상당히 빠르게 감소
-> 스트리밍 데이터를 사용하는 온라인 학습 방식으로 훈련하려면 개개의 샘플마다 partial_fit 메서드를 호출
# 예시
ada.partial_fit(X_std[0, :], y[0])
Author And Source
이 문제에 관하여(2장 간단한 분류 알고리즘 훈련), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@rosesua318/2장-간단한-분류-알고리즘-훈련저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)