본문 바로가기

Python/AI 수학 with Python

[Python] 로지스틱 회귀 구현

 

데이터 설정

[In]

# 데이터 설정
%matplotlib inline
 
import numpy as np
import matplotlib.pyplot as plt

n_data = 1000 # 데이터 개수
X = np.zeros((n_data, 2))  # 입력 데이터
T = np.zeros(n_data) # 정답 데이터

for i in range(n_data):
    # x1, x2 좌표를 랜덤으로 설정
    x1_rand = np.random.rand() # x1 좌표
    x2_rand = np.random.rand() # x2 좌표
    X[i, 0] = x1_rand
    X[i, 1] = x2_rand
    
    # x1가 x2보다 큰 경우일 때 정답 라벨 1로 지정
    # 정규분포를 이용해 조금 불명확하게 설정
    if x1_rand > x2_rand + 0.2 * np.random.randn():
        T[i] = 1
        
plt.scatter(X[:, 0], X[:, 1], c = T) # 정답라벨을 색으로 구분
plt.xlabel('x1', size = 14)
plt.ylabel('x2', size = 14)
plt.colorbar()
plt.show()

[Out]

데이터는 정답 라벨에 의해 0과 1의 그룹으로 나뉘어져 있다. 그리고 산포도 내에는 두 그룹이 겹친 부분도 존재하는 것을 알 수 있다.

 

로지스틱 회귀의 구현

[In]

# 로지스틱 회귀의 구현
n = 0.01 # 학습 계수

# ---- 출력 함수(분류 시행) ----
def classify(x, a_params, b_param):
    u = np.dot(x, a_params) + b_param
    
    return 1 / (1 + np.exp(-u))

# ---- 교차 엔트로피 계산 ----
def cross_entropy(Y, T):
    delta = 1e-7 # 미소값
    return -np.sum(T * np.log(Y + delta) + (1 - T) * np.log(1 - Y + delta))

# ---- 각 파라미터의 그라디언트 ----
def grad_a_params(X, T, a_params, b_param): # a의 그라디언트
    grad_a = np.zeros(len(a_params))
    for i in range(len(a_params)):
        for j in range(len(X)):
            grad_a[i] += (classify(X[j], a_params, b_param) - T[j]) * X[j, i]
            
    return grad_a

def grad_b_param(X, T, a_params, b_param): # b의 그라디언트
    grad_b = 0
    for j in range(len(X)):
        grad_b += classify(X[j], a_params, b_param) - T[j]
    
    return grad_b

# ---- 학습 ----
error_x = [] # 가로축 : Epoch 
error_y = [] # 세로축 : Cross Entropy Error

# dim : 데이터 차원, epoch : 반복 횟수
def fit(X, T, dim, epoch):
    # ---- 파라미터 초깃값 설정 ----
    a_params = np.random.randn(dim)
    b_param = np.random.randn()
    
    # ---- 파라미터 갱신 ----
    for i in range(epoch):
        grad_a = grad_a_params(X, T, a_params, b_param)
        grad_b = grad_b_param(X, T, a_params, b_param)
        a_params -= n * grad_a # a_params 갱신
        b_param -= n * grad_b  # b_params 갱신
        
        Y = classify(X, a_params, b_param) # 분류 실행
        
        error_x.append(i)
        error_y.append(cross_entropy(Y, T)) # 오차 기록
        
    return (a_params, b_param)

# ---- 확률분포 표시 ----
a_params, b_param = fit(X, T, dim = 2, epoch = 200) # 학습 실행
Y = classify(X, a_params, b_param) # 학습 결과를 가지고 분류 실행

result_x1 = [] # x1 좌표
result_x2 = [] # x2 좌표
result_z = []  # 확률

for i in range(len(Y)):
    result_x1.append(X[i, 0])
    result_x2.append(X[i, 1])
    result_z.append(Y[i])
    

print("---- 확률 분포 ----")
plt.scatter(result_x1, result_x2, c = result_z) # 확률을 표시
plt.xlabel('x1', size = 14)
plt.ylabel('x2', size = 14)
plt.colorbar()
plt.show()

# ---- 오차의 추이 ----
print("---- 오차의 추이 ----")
plt.plot(error_x, error_y)
plt.xlabel('Epoch', size = 14)
plt.ylabel('Cross Entropy', size = 14)
plt.show()

[Out]

---- 확률 분포 ----

---- 오차의 추이 ----

 

로지스틱 회귀에서는 출력을 확률로 해석할 수 있으므로, 그래프의 색상은 정답 라벨 1로 분류될 확률이라고 생각할 수 있다. 원본 데이터에서 0과 1의 라벨이 뒤섞인 경계영역에서의 확률은 0과 1의 중간값을 취하고 있다.

또한 오차는 파라미터가 갱신됨에 따라 점점 줄어드는 것을 알 수 있다. 이 경우에는 정답 라벨이 뒤섞인 영역이 많기 때문에 오차가 0에 그다지 가까워지지 않는다.