안녕하세요. 보랏입니다.
오늘은 머신러닝 기법 중 확률적 경사 하강법, 오차역전파와
트리 구조에 대해서 배웠습니다.
바로 복습 시작하겠습니다.
1. 확률적 경사 하강법
- 경사 = 기울기 / 하강법 : 내려가는 방법
- 아래 그림처럼 내려오는 보폭이 너무 크면 원하는 지점을 지나쳐 갈 수 있음
- 확률적이라는 말은 실제로는 훈련 데이터 셋을 사용해 모델을 훈련하여 경사 하강법도 당연히 훈련 세트를 사용하여 가장 가파른 길을 찾는 방법
- 훈련 세트에서 랜덤하게 하나의 샘플을 선택하여 경사를 내려가고, 그 다음 훈련 세트에서 랜덤하게 또 다른 샘플을 하나 선택하여 경사를 조금 내려가는 방법
- 이런 식으로 전체 샘플을 모두 사용할 때까지 계속해서 진행
- 이렇게 해서 답을 찾지 못하면 다시 처음부터 시작합니다. 그 다음 다시 랜덤하게 하나의 샘플을 선택해 이어서 경사를 내려갑니다.
- 에포크(epoch) : 확률적 경사 하강법에서 훈련 세트를 모두 사용하는 과정
- 미니배치 경사 하강법(minibatch gradient descent) : 1개의 데이터셋을 사용하여 내려오는 것보다 여러 개를 한 번에 묶어 사용하면 시간이 더 절약될 수 있음
- 배치 경사 하강법 : 전체 샘플을 사용하여 진행
2. 손실함수
- loss(cost) function 는 어떤 문제에서 머신러닝 알고리즘이 얼마나 엉터리인지를 측정하는 기준
- 값이 작을수록 좋지만 어떤 값이 최솟값인지는 알지 못함
- 손실 함수는 샘플 하나에 대한 손실을 정의
- 비용 함수는 훈련 세트에 있는 모든 샘플에 대한 손실 함수의 합을 의미
3. 로지스틱 손실 함수
- 예측 확률에 로그 함수를 적용하면 더 좋아지며, 예측 확률의 범위는 0~1사이로 로그 함수는 이 사이에서 음수가 되므로 최종 손실 값은 양수가 됨
- 손실이 양수가 되면 이해하기 더 쉽습니다. 또 로그 함수는 0에 가까울수록 아주 큰 음수가 되기 때문에 손실을 아주 크게 만들어 모델에 큰 영향을 미칠수 있습니다.
4. 확률적 경사 하강법 예제
from sklearn.datasets import load_diabetes
diabetes = load_diabetes()
diabetes
### diabetes 산점도
import matplotlib.pyplot as plt
plt.scatter(diabetes.data[:, 2], diabetes.target)
plt.show()
- 훈련 데이터와 잘 맞는 w와 b를 찾는 법
- 무작위로 w와 b를 정하기 (무작위 모델 만들기)
- x에서 샘플 하나를 선택하여 y^를 계산 (무작위 모델 예측)
- y^과 선택한 샘플의 진짜 y를 비교 (예측값과 진짜 정답 비교)
- y^이 y와 더 가까워 지도록 w, b를 조정 (모델 조정)
- 모든 샘플을 처리할 때까지 위의 항목 반복
y = wx + b
w = 1.0
b = 1.0
### 예측값
y_hat = x[0] * w +b
y_hat
-> 1.0616962065186886
### 정답
### 정답과 예측값과의 차이가 크다 -> w 값을 증가시켜야함
y[0]
-> 151.0
w_inc = w + 0.1 #w 값을 증가시킴
y_hat_inc = x[0] * w_inc + b
y_hat_inc
-> 1.0678658271705574
### w값 조정한 후 예측값이 얼마나 변했는지 변화값 확인
w_new = w + w_rate
w_new
-> 1.0616962065186888
### 변화율로 bias 업데이트
b_inc = b + 0.1
y_hat_inc = x[0] * w_inc + b_inc
print(y_hat_inc)
-> 1.1678658271705575
b_rate = (y_hat_inc-y_hat) / (b_inc-b)
print(b_rate)
-> 1.0616962065186888
5. 오차역전파
- 결과값을 통해서 다시 역으로 input 방향으로 오차를 다시 보내며 가중치를 재업데이트 하는 것
- 위의 그림을 보면 input이 들어오는 방향(순전파)으로 output layer 에서 결과 값이 나오며, 결과값은 오차 (error)를 가지게 되는데 역전파는 이 오차(error)를 다시 역방향으로 hidden layer와 input layer로 오차를 다시 보내면서 가중치를 계산하면서 output에서 발생했던 오차를 적용시키는 것
# 실제예측된값과 실제정답과의 차이를 w값을 반영한 값
err = y[0] - y_hat
w_new = w + w_rate * err
b_new = b + 1 * err
# 결과값이 더욱 커짐
print(w_new, b_new)
-> 10.250624555904514 150.9383037934813
y_hat = x[1] * w_new + b_new
err = y[1] - y_hat
w_rate = x[1]
w_new = w_new + w_rate * err
b_new = b_new + 1 * err
print(w_new, b_new)
-> 14.122059942557254 75.72691977830128
for x_i, y_i in zip(x , y ):
y_hat = x_i * w + b
err = y_i - y_hat
w_rate = x_i
w = w + w_rate * err
b = b + 1 * err
print(w, b)
-> 913.5973364345901 123.39414383177201
import matplotlib.pyplot as plt
plt.scatter(diabetes.data[:, 2], diabetes.target)
plt.plot([-0.1, 0.15 ], [-0.1*w+b, 0.15*w+b ], color='r')
plt.show()
6. 트리(Tree) 구조
- 노드들이 나무가지처럼 연결된 비선형 계층적 자료구조
- 트리는 다른 하위 트리가 있으며 하위 트리 안에는 또 다른 트리가 있는 재귀적 자료구조
- 노드(Node)
- 트리를 구성하고 있는 기본 요소
- 노드에는 키 또는 값과 하위 노드에 대한 포인터를 가지고 있음
- 노드와 노드 간에는 간선(Edge)으로 연결
- 깊이(depth) : 루트에서 어떤 노드까지의 간선 수
- 높이(height) : 어떤 노드에서 리프 노드까지 가자 긴 경로의 간선 수
- Level : 루트에서 어떤 노드까지의 간선 수
- 트리 특징
- 하나의 루트노드와 0개 이상의 하위 트리로 구성
- 데이터를 순차적으로 저장하지 않기 때문에 비선형 자료구조
- 재귀적 자료구조
- 단순순환(Loop)을 갖지 않고, 연결된 무방향 그래프
- 노드 간에 부모 자식 관계를 갖고 있는 계층형 자료구조로, 모든 자식 노드는 부모 노드만 가지게 됨
- 노드가 n개인 트리는 항상 n-1개의 간선을 가짐
7. 이진탐색 트리(Binary search tree)
- 순서화된 이진 트리(각 노드의 차수(자식노드)가 2이하인 트리)
- 노드의 왼쪽 자식은 부모의 값보다 작은 값을 가져야 하며 오른쪽 자식은 부모의 값보다 큰 값을 가져야 함
- 사용 사례
- 계층적 데이터 저장 : 트리는 데이터를 계층 구조로 저장하는 데 사용 ex) 파일 및 폴더
- 효율적인 검색 속도 : 효율적인 삽입, 삭제 및 검색을 위해 트리 구조 사용
- 힙(Heap)
- 데이터베이스 인덱싱 ex) B-Tree
- 코드 구현
class Node :
def __init__(self, data) :
self.left = None
self.right = None
self.data = data
# 루트 노드에서 자식노드가 없으므로 left,right에 None을 넣기
def __repr__(self) :
return str(self.data)
# 노드 삽입 클래스 구현
class BinarySearchTree :
# root노드는 None값을 가지고 있음
def __init__(self) :
self.__root = None
# method의 기본값을 'iteractive'로 가지고, method가 'recursion'값이 아니기 때문에 _insert_iter메서드로 이동
def insert(self, data, method = 'iterative') :
if method in 'recursion' :
self.__root = self._insert_rec(self.__root, data)
else :
self._insert_iter(data)
# node값이 None이기 때문에 if not = None으로 되어 True
# 1번째 값은 루트노드를 생성하고 다음값부터는 data(입력변수)가 루트노드보다 크면 오른쪽, 작으면 왼쪽
def _insert_rec(self, node, data):
if not node :
node = Node(data)
else :
if node.data > data :
node.left = self._insert_rec(node.left, data)
else :
node.right = self._inser_rec(node.right, data)
def _insert_iter(self, data) :
if not self.__root : #self.__root에서 None값이 때문에 true
self.__root = Node(data) #루트노드 생성
return
new_node = Node(data) #new_node 값은 Node클래스 입력변수
curr = self.__root
parent = None
# 이 반복문은 새로운 값의 위치를 찾는 구문 -> level 따라 내려오기
while (curr != None) : # 그 후 현재 루트노드 값이 존재하면
parent = curr # 부모노드는 curr
if curr.data > data : # 이 후 입력변수가 부모노드보다 작은면 left, 크면 right로 이동
curr = curr.left
else :
curr = curr.right
# 찾은 위치의 새로운 값을 입력하는 구문
if parent.data > data :
parent.left = new_node
else :
parent.right = new_node
오늘은 이렇게 이론 내용 위주로 공부하였습니다.
오늘 복습 마무리 하겠습니다.
감사합니다.
'DB 공부하기' 카테고리의 다른 글
230404_DB복습 (0) | 2023.04.04 |
---|---|
230403_DB복습 (0) | 2023.04.03 |
230327_DB복습 (0) | 2023.03.27 |
230323_DB복습 (0) | 2023.03.23 |
230322_DB복습 (0) | 2023.03.22 |