TAVE 동아리에서 데이터 분석 스터디에 참여하였습니다!
머신러닝 교과서 with 파이썬, 사이킷런, 텐서플로
책을 채택하여 원하는 챕터를 중점으로 스터디를 진행할 예정입니다. 이 블로그에 제가 공부한 내용들을 기록해보려고 합니다!
퍼셉트론
수학적 용어 정리
- 인공뉴런 : 이진분류 (1과 -1)
- 입력 값 x와 상응하는 가중치 벡터 w의 선형 조합으로 결정함수(ϕ(z))를 정의
- 최종입력=z=w1x1+w2x2+…+wmxm=wTx
- x(i)의 최종입력이 사전에 정의된 임계 값 wTx보다 크면 1로 예측 / 작으면 -1로 예측
- 퍼셉트론의 결정 함수
- 절편=음수 임계 값 or 가중치 w0=-θ
퍼센트론 알고리즘 (추가학습)
- 샘플 x를 입력 받아 가중치를 연결해 최종 입력을 계산한다.
- 최종 입력은 임계 함수로 전달되어 샘플의 예측 클래스 레이블인 -1 or 1로 이진 출력한다.
- 학습 단계에서 이 출력을 사용하여 예측 오차를 계산하도 가중치를 업데이트한다.
(추가적으로 학습한 부분입니다)
참고링크: https://heytech.tistory.com/332
퍼셉트론 : 이진분류 학습하기 위한 지도학습 기반의 알고리즘
다수의 값 x를 입력 받고, 입력된 값마다 가중치(weight=w)를 곱한다.
편향(bias=b)은 최적화의 중요 변수 중 하나이다.
가중합(=wTx+b)를 임계값(θ)과 비교해 활성화 함수를 거쳐 최종 출력값을 결정한다. 계단 함수 사용.
붓꽃 데이터에 퍼셉트론 활용 코드
import numpy as np
#퍼셉트론 함수 설정
class Perceptron(object):
def __init__(self, eta=0.01, n_iter=50, random_state=1):
self.eta = eta #학습률 (0.0과 1.0 사이)
self.n_iter = n_iter #에포크 횟수
self.random_state = random_state
def fit(self, X, y):
#가중치를 초기화한 후 훈련 데이터셋을 반복 순회하여 가중치를 업데이트함
rgen = np.random.RandomState(self.random_state)
self.w_ = rgen.normal(loc=0.0, scale=0.01, size=1 + X.shape[1])
#self.w_ : 가중치
#rgen.normal(loc=0.0, scale=0.01, size=1 + X.shape[1]) : 표준편차가 0.01인 정규분포에서 뽑은 랜덤한 작은 수
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] #wTx 계산
def predict(self, X):
return np.where(self.net_input(X) >= 0.0, 1, -1) #클래스 레이블 예측
#붓꽃 데이터에 퍼셉트론 적용하기
import os
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
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')
# 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
# 산점도를 그립니다, setosa-음성, versicolor-양성
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.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')
#결정 경계 그래프 함수
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=None if idx==1 else 'black')
plot_decision_regions(X, y, classifier=ppn)
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc='upper left')
plt.show()
퍼셉트론이 학습한 결정 경계는 두 개의 붓꽃 샘플을 완벽하게 분류한다.
적응형 선형 뉴런
→ 단일층 신경망의 종류
아달린
- 퍼센트론 향상 버전
- 연속 함수로 비용 함수 정의 및 최소화하는 핵심 개념 보여줌
- 가중치 업데이트를 선형 활성화 함수를 사용 ← 퍼셉트론은 단위 계단 함수 사용
- 선형 활성화 함수 = 항등 함수 ϕ(z)=z
- 퍼셉트론과의 차이점퍼셉트론 : 진짜 클래스 레이블 vs 예측 클래스 레이블 비교
- 아달린 : 진짜 클래스 레이블 vs 선형 활성화 함수의 실수 출력 값 비교
경사 하강법 → 비용 함수 최소화 (추가 학습)
(추가학습에 내용입니다.)
참고 링크 : https://bigdaheta.tistory.com/85
- 비용 함수 : 내가 만든 모델이 실제 정답과 얼마나 다른지를 측정하는 수단
- 비용함수가 클수록 모델의 정확도가 낮음
- 오류를 제곱한 뒤 더하면서 이차식의 형태가 되고 최소인 지점을 최적점이라고 한다.
- 훈련세트에 있는 모든 샘플에 대한 손실함수의 합
- 손실 함수 : 샘플 하나에 대한 손실
- 목적 함수 : 최적화하기 위해 정의 / 최대의 이익 or 최소의 손실
- 목적 함수 == 비용 함수 == 손실 함수
- 제곱 오차합(SSE)
- J(w)=12∑i(y(i)−ϕ(z(i)))2
- 경사하강법 : 최적화 알고리즘으로 비용 함수를 최소화하는 가중치를 찾을 수 있다.
아달린 구현 코드
class AdalineGD(object):
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):
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):
#전체 훈련 데이터셋을 기반으로 gradient 계산
#활성화 함수는 항등함수여서 영향X
net_input = self.net_input(X)
output = self.activation(net_input)
errors = (y - output)
self.w_[1:] += self.eta * X.T.dot(errors)
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)
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.show()
확률적 경사 하강법
- 확률적 경사 하강법
- 배치 경사 하강법의 대안으로 각 훈련 샘플에 대해 조금씩 가중치 업데이트
- 가중치가 더 자주 업데이트 되어서 수렴 속도가 빠르다.
- 훈련 샘플 순서를 무작위하게 주입하면 만족스러운 결과 얻을 수 있다.
- 온라인 학습 가능 → 새로우 훈련 데이터가 들어오면 도착하는 대로 훈련, 모델을 업데이트한 후 훈련 데이터를 버려 저장 공간 제약 문제를 해결할 수 있다.
코드
# 특성 표준화
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()
class AdalineSGD(object):
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):
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))
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)
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.show()