2018/11/25 - [Study/인공지능학습] - [머신러닝 reboot] 개념 잡기 : 경사 하강법 1 - 특성의 scale


2018/12/10 - [Study/인공지능학습] - [머신러닝 reboot] 개념 잡기 : 경사 하강법 2 - step 공식 이해하기


머신러닝 Reboot - 개념 잡기 : 경사 하강법 3 - 경사 하강법의 종류


지난 포스팅에서 경사 하강법의 특성 스케일과 스텝에 대해서 알아보았다.
여전히 이해도가 낮은데다가 너무 세세한 부분까지 이해하려고 하다보니 진도가 잘 안나간다.
그렇다고 뭔가를 확실히 알게된 것도 아니고…ㅠ.ㅠ 그저 ‘아~ 그래서 이럴 것 같다…’라는 느낌적인 느낌을
갖게 된 것 정도가 소득이라면 소득일까…


아직은 계란으로 바위치는 기분이지만 그래도 학습의 끈을 놓을 수는 없다.
그래서 오늘도 정리는 계속된다~


오늘은 경사 하강법의 종류에 대해 정리해보도록 하자.


이 포스팅에 사용된 코드는 모두 핸즈온 머신러닝(오렐리앙 제롱 저, 박해선 역, 한빛미디어 출판)에서
가져왔거나 일부 수정한 코드입니다.



경사 하강법 복습


경사 하강법이란 비용 함수를 최소화 할 수 있는 가중치(𝜭)를 찾아가는 과정으로 그 방법은 비용 함수를 가중치(𝜭)에 
대해 미분하고 이 결과가 작아지는 방향으로 반복해서 진행을 하는 것이다. 이 때 매 반복이 각 단계 사이에 얼마만큼의 
거리를 둘 것인지 하는 학습률이 매우 중요한데 학습률이 너무 높으면 중구난방 발산을 하게 되고 반대로 너무
낮으면 시간이 오래 걸려 최솟값에 도달하지 못할 수 있다.


학습률 적절학습률 적절학습률 낮음학습률 낮음학습률 높음학습률 높음


경사 하강법은 매우 일반적이고 단순한 알고리즘이지만 함수의 그래프가 단 한개의 오목한 점을 가질 때(convex)만
잘 작동을 한다. 함수의 그래프에 오복한 부분이 많은 경우(non-convex) 초기화가 어떻게 되느냐에 따라 지역 
최솟값에서 멈출 수 있다. 



또한 지난 포스팅에서 본 것처럼 특성의 스케일에 민감하여 2개 이상의 특성이 있을 때 각 
특성간의 스케일이 크게 차이가 나면 최솟값에 도달하는데 오랜 시간이 걸리게 된다.


참고 : 특성의 스케일 (https://mazdah.tistory.com/833)


선형 회귀의 경우 비용함수의 그래프는 전역 최솟값 하나만 존재하는 그래프이기 때문에 경사 하강법은 좋은 방법이
될 수 있으며 특성의 스케일만 적절하게 조정을 해주면 될 것이다. 


이러한 경사 하강법은 훈련세트의 범위를 어떻게 지정하여 계산하느냐에 따라 배치 경사 하강법, 확률적 경사 하강법,
미니 배치 경사 하강법으로 나누어 볼 수 있다. 


배치 경사 하강법


배치 경사 하강법은 매 스텝에서 전체 훈련 데이터를 사용하여 계산을 한다. 따라서 매우 큰 훈련 세트에서는 성능이 
떨어지게 된다. 


배치 경사 하강법에 대해서는 이전 포스팅에서 상세하게 다루었으므로 아래 링크를 참조하기 바란다.


참고 : 배치 경사 하강법의 STEP (http://mazdah.tistory.com/834)


아래는 핸즈온 머신러닝(이하 책)에 실린 예제 코드이다. 그래프 그리는 부분을 약간 수정했다. 라이브러리 import와
데이터 생성 부분은 책을 참조하시기 바란다.


# 다른 경사 하강법과의 비교를 위해 theta값을 저장할 배열 theta_path_bgd = [] eta = 0.1 # 학습률 n_iterations = 1000 # 반복 횟수 m = 100 # 샘플 수 theta = np.random.randn(2, 1) # theta 초기화 for iteration in range(n_iterations): gradient = 2/m * X_b.T.dot(X_b.dot(theta) - y) theta = theta - eta * gradient theta_path_bgd.append(theta) # 처음 10번의 step에 대한 그래프와 마지막 step의 그래프를 그리기 위한 코드 if iteration <= 10 or iteration == 999: X_new = np.array([[0], [2]]) X_new_b = np.c_[np.ones((2, 1)), X_new] y_predict = X_new_b.dot(theta) y_predict style = "g--" if iteration == 999: style = "r-" plt.plot(X_new, y_predict, style) plt.plot(X, y, "b.") plt.axis([0,2,0,15]) plt.show() theta ########################################################## # API 설명 # ########################################################## # np.ones((2,1))는 파라미터로 전달된 shape의 배열을 1로 채운다. (기본 데이터 타입은 실수로 1.으로 채워진다.) # np.c_는 파라미터로 전달된 2개의 배열에 대해 동일행의 요소를 배열로 합친 (첫 번째 파라미터의 요소는 0열, 두 번째 파라미터의 요소는 1열) 새로운 배열을 만든다. # 예) # >>> np.c_[[np.array([1,2,3]), np.array([4,5,6])]] # array([[1, 4], # [2, 5], # [3, 6]]) # >>> np.c_[np.array([[1,2,3]]), 0, 0, np.array([[4,5,6]])] # array([[1, 2, 3, 0, 0, 4, 5, 6]])


책에는 처음 10번의 예측 그래프만을 그리고 있으나 배치 경사 하강법 마지막에 설명한 반복 횟수에 대한 설명을 확인
해보고자 마지박 반복 그래프를 하나 더 추가하였다. 처음 10번의 그래프는 초록색 대시로, 마지막 그래프는 빨간 실선
으로 표시했다.


배치 경사 하강법배치 경사 하강법


그래프를 보면 알 수 있듯이 10번째 step의 그래프와 마지막 step의 그래프 사이에는 큰 차이가 없다. 결국 990번은
큰 의미가 없는 step이었다는 뜻인데, 이는 책에서 언급한 바와 같이 반복 횟수의 지정 또한 중요하다는 것을 의미한다.


확률적 경사 하강법


확률적 경사 하강법은 배치 경사 하강법이 훈련 세트가 커짐에 따라 느려지는 단점을 보완한다.


확률적 경사 하강법은 매 스텝에서 무작위로 선택한 한 개의 샘플에 대해서만 그래디언트를 계산하므로 속도가 빠를 뿐만
아니라 메모리 사용에 있어서도 효율이 좋아 매우 큰 훈련 세트도 적용 가능하다. 또한 불규칙성이 지역 최솟값을 건너
뛰게 하여 전역 최솟값을 찾을 가능성을 높여주는 것도 장점으로 볼 수 있다.


다만 최적값에 도달하는 과정이 배치 경사 하강법에 비해 불안정한 것이 단점이다. 또한 최적값에 근접은 하겠지만
말 그대로의 최적값은 얻기 힘들다. 이러한 단점을 해결하는 방법으로 학습률을 조정하는 방법이 있다. 처음에는 학습률을
크게 했다가 점차 학습률을 줄여나가는 방법으로 이 때 학습률을 조정하는 함수를 학습 스케쥴(learning schedule)이라
부른다.


이렇게 확률적 경사 하강법에서는 학습률자체의 크고 작음 뿐만 아니라 학습률을 줄여나가는 속도 역시 중요한 하이퍼 
파라미터가 된다. 너무 빨리 줄여나가면 최솟값에 도달하지 못하고 너무 천천히 줄여나가면 시간이 오래 걸리거나 지역
최솟값에 머물 수 있다.


또다른 특징(단점)으로는 훈련세트를 한 번 도는 동안(1 epoch) 여러번 선택되는 샘플과 한 번도 선택되지 않는 샘플이 
존재할 수 있는데 이를 방지하기 위해 매 epoch에서 훈련 세트를 섞는 작업을 추가할 수 있다. 다만 이렇게 하면 속도가
느려지게 된다.


# 다른 경사 하강법과의 비교를 위해 theta값을 저장할 배열 theta_path_sgd = [] # 확률적 경사 하강법 n_epochs = 50 t0, t1 = 5, 50 # 학습 스케쥴 하이퍼파라미터 # 학습률을 감소시키는 학습 스케쥴 함수 def learning_schedule(t): return t0 / (t + t1) theta = np.random.randn(2,1) for epoch in range(n_epochs): # epoch : 훈련세트 전체가 한 차례 반복 되는 단위. 50번 반복될 동안 전체 샘플 m만큼 # 반복되므로 50 epoch를 실행한다. for i in range(m): # 첫 번째 epoch에서 처음 20개의 샘플에 대한 그래프와 최종 그래프를 그림. if (epoch == 0 and i < 20) or (epoch == 49 and i == 99): y_predict = X_new_b.dot(theta) # 처음 그래프는 초록색 대시로, 나머지 그래프는 파란색 실선으로, 마지막 그래프는 # 빨간색 실선으로 표시한다. style = "" if epoch == 0 and i > 0: style = "b-" elif epoch == 0 and i == 0: style = "g--" elif epoch == 49 and i == 99: style = "r-" plt.plot(X_new, y_predict, style) # 0부터 99(m은 100개) 중에 하나를 랜덤하게 가져옴 random_index = np.random.randint(m) # 무작위로 선택된 인덱스를 확인해보자. 특정 인덱스는 중복해서 사용되고 또 다른 인덱스는 # 사용되지 않는다. if epoch == 0: print("random_index = ", random_index) # 훈련세트와 실제값의 배열에서 랜덤하게 가져온 인덱스 위치의 값 1개씩만 가져온 후 # gradient를 계산한다. xi = X_b[random_index:random_index+1] yi = y[random_index:random_index+1] gradients = 2 * xi.T.dot(xi.dot(theta) - yi) # 배치 경사 하강법과 달리 학습률을 계속해서 감소시킨다. eta = learning_schedule(epoch * m + i) # print("eta : ", eta) theta = theta - eta * gradients theta_path_sgd.append(theta) plt.plot(X, y, "b.") plt.xlabel("$x_1$", fontsize=18) plt.ylabel("$y$", rotation=0, fontsize=18) plt.axis([0,2,0,15]) plt.show() ########################################################## # API 설명 # ########################################################## # # Numpy random # # - randn(d0, d1, ..., dn) : 표준 정규분포를 따르는 무작위 실수를 파라미터로 전달받 차수의 배열에 # 채워 리턴한다. 파라미터가 없는 경우 한 개의 실수만 리턴한다. # 예) # >>> theta = np.random.randn(2,1) # >>> theta # array([[ 0.44730268], # [-0.04431121]]) # # - randint(low, high=None, size=None, dtype='l') : 무작위 정수를 리턴한다. low 파라미터는 # 필수로 low 파라미터만 있는 경우에는 0부터 low-1까지의 범위에서 무작위 정수가 리턴되며 # low와 high가 전달되는 경우 low부터 high-1까지의 범위에서 무작위 정수가 리턴된다. # size가 주어질 경우 size에 해당하는 배열 형태로 리턴된다. # 예) # >>> rival = np.random.randint(2) # >>> rival # 1 (0또는 1이 무작위로 출력됨) # >>> rival = np.random.randint(2, 5) # >>> rival # 1 (2 ~ 4 사이의 정수가 무작위로 출력됨) # >>> rival = np.random.randint(2,5, (2,3)) # >>> rival # array([[3, 3, 4], (2 ~ 4 사이의 정수가 2행 3열의 배열에 무작위로 할당되어 출력됨) # [3, 2, 3]])

확률적 경사 하강법


동일한 내용을 Scikt Learn으로는 다음과 같이 구현 가능하다.


from sklearn.linear_model import SGDRegressor # epoch는 50, 초기 학습률은 0.1로 학습한다. sgd_reg = SGDRegressor(max_iter=50, penalty=None, eta0=0.1) sgd_reg.fit(X, y.ravel()) # 결과값 보기 : intercept_ = 편향, coef_ = 가중치 sgd_reg.intercept_, sgd_reg.coef_ ########################################################## # API 설명 # ########################################################## # # Scikit-learn # # - SGDRegressor (loss=’squared_loss’, penalty=’l2’, alpha=0.0001, l1_ratio=0.15, # fit_intercept=True, max_iter=None, tol=None, shuffle=True, # verbose=0, epsilon=0.1, random_state=None, # learning_rate=’invscaling’, eta0=0.01, power_t=0.25, # warm_start=False, average=False, n_iter=None) # . 확률적 경사 하강법을 수행하는 클래스로 linear_model 모듈에 포함되어있다. # . 파라미터 (상당히 많은 파라미터가 있는데 예제에 명시한 파라미터만 간단히 알아보자) # : max_iter - 전체 훈련 데이터의 반복 횟수(epoch 수)를 지정한다. # : penalty - 정규화 식. 여기서는 사용하지 않음. l2, l1, elasticnet 등을 사용할 수 # 있다. # : eta0 - 학습률의 초깃값. API상에 learning_rate라는 파라미터는 학습 스케쥴을 뜻한다. # 기본값인 ‘invscaling’는 eta = eta0 / pow(t, power_t) 공식에 따라 # 학습률을 조정한다. (자세한 내용은 핸즈온 머신러닝 171쪽 하단의 역주 참조)



미니배치 경사 하강법


미니배치 경사 하강법은 한 epoch에서 미니배치라는 임의의 샘플 세트만으로 계산을 진행하는 것으로 확률적 경사 
하강법이 1개의 샘플만 사용하여 계산하는 것과 비교된다. 한편으로는 확률적 경사 하강법은 미니배치의 크기가 1인
미니배치 경사 하강법으로 볼 수도 있다.


이러한 미니배치 경사 하강법은 확률적 경사 하강법에 비해 안정적으로 최솟값에 접근하지만 지역 최솟값을 벗어나기는
더 힘들다.


장점으로는 행렬 연산에 최적화 되어있으며 GPU를 통해 큰 성능 향상을 얻을 수 있다.


# 다른 경사 하강법과의 비교를 위해 theta값을 저장할 배열 theta_path_mgd = [] n_iterations = 50 # 미니배치 크기를 20으로 주었다. # 이 크기를 1로 주면 확률적 경사 하강법과 동일한 형태의 그래프를 볼 수 있다. minibatch_size =20 np.random.seed(42) theta = np.random.randn(2,1) # 무작위 초기화 t0, t1 = 200, 1000 def learning_schedule(t): return t0 / (t + t1) t = 0 for epoch in range(n_iterations): # 인자로 전달된 벡터 또는 행렬 요소의 순서를 무작위로 바꿈. # 행렬의 경우 첫 번째 인덱스의 순서만 바꾼다. # 매 epoch마다 순서를 뒤섞어 훈련 세트를 고르게 사용하도록 한다. shuffled_indices = np.random.permutation(m) X_b_shuffled = X_b[shuffled_indices] y_shuffled = y[shuffled_indices] # minibatch_size만큼 샘플을 뽑아 계산하므로 for문에서도 minibatch_size만큼씩 # 증가시킨다. for i in range(0, m, minibatch_size): #print(epoch , " : ", i) # 그래프 그리기. minibatch_size가 20이므로 한 epoch에서 5번만에 연산이 끝난다. # 따라서 2 epoch가 진행될 동안 총 10개의 그래프가 그려진다. if epoch < 2 and i < 100: y_predict = X_new_b.dot(theta) style = "b-" if i > 0 else "r--" plt.plot(X_new, y_predict, style) t += 1 # 매 epoch마다 뒤섞은 훈련 세트에서 minibatch_size만큼의 샘플만을 가져와서 # gradient를 계산한다. xi = X_b_shuffled[i:i+minibatch_size] yi = y_shuffled[i:i+minibatch_size] gradients = 2/minibatch_size * xi.T.dot(xi.dot(theta) - yi) eta = learning_schedule(t) theta = theta - eta * gradients theta_path_mgd.append(theta) plt.plot(X, y, "b.") plt.xlabel("$x_1$", fontsize=18) plt.ylabel("$y$", rotation=0, fontsize=18) plt.axis([0,2,0,15]) plt.show() ########################################################## # API 설명 # ########################################################## # # Numpy random # # - permutation(x) : 파라미터로 주어진 배열의 순서를 무작위로 바꾸어 출력한다. # 예) # >>> x = np.random.permutation([1,2,3,4,5,6,7,8,9,10]) # >>> x # array([ 7, 2, 1, 4, 3, 8, 9, 5, 10, 6])



미니배치 크기 20미니배치 크기 20미니배치 크기 1미니배치 크기 1


위의 그래프를 보면 확률적 경사 하강법의 그래프보다 미니배치 경사 하강법에서 미니배치를 1로 준 그래프가 좀 더
발산이 심한데 이는 아마도 하이퍼파라미터가 달라서 발생하는 현상일 것이다.


비교


마지막으로 3개의 경사하강법을 실행하면서 저장한 theta값의 배열을 기반으로 그래프를 그려보면 아래와 같다.



배치 경사 하강법이 곧장 최솟값으로 향한 반면 확률적 경사 하강법과 미니배치 경사 하강법은 최솟값 부근에서 매우
불안정하게 움직인다. 하지만 앞서 살펴본대로 배치 경사 하강법은 샘플이 많아질수록 느려지고 확률적 경사 하강법과
미니배치 경사 하강법도 적절한 하이퍼파라미터를 사용하면 결국은 최솟값에 도달한다.


마지막으로 각 경사 하강법의 특징을 정리하면 다음과 같다.


배치 경사 하강법 : 샘플 수가 클 때 느림, 특성 수에 상관없이 빠름, 특성 스케일 조정 필요

확률적 경사 하강법 : 샘플 수에 상관없이 빠름, 특성 수에 상관없이 빠름, 특성 스케일 조정 필요

미니배치 경사 하강법 : 샘플 수에 상관없이 빠름, 특성 수에 상관없이 빠름, 특성 스케일 조정 필요


더 상세한 비교 내용은 핸즈온 머신러닝 172쪽을 참고하라


다항 회귀


다항회귀는 데이터가 비선형인 경우(2차 이상의 함수 형태를 띠는 경우) 사용할 수 있는 회귀 분석이다.
다항회귀의 경우 직관적으로 접근하는 것이 좀 더 이해하기가 쉬운데 데이터의 그래프를 보고 적절한 차수를
제곱한 특성을 추가하여 분석하는 것이다.


비선형의 데이터를 선형 회귀 분석으로 계산할 수 있는 이유는 선형의 의미가 가설함수의 독립변수에 대한 것이 아니라
가중치 𝜭에 대한 것이기 때문이다. 즉 𝜭에 대한 1차 식(선형 식)으로 풀어낼 수 있다는 의미인 것이다.


이렇게 봤을 때 결과론적이긴 하지만 아래 코드를 보면 결국 이 예제의 가설함수는 위에 언급한 것과 같이 𝜭를 기준으로
본다면 특성이 2개인 함수일 뿐이다. 다만 기존에는 서로 독립적인 특성들, 즉 x1, x2, x3…xn에 대해 다뤘다면 다항
회귀에서는 x, x^2, x^3…x^n과 같이 하나의 특성 x에 대해 그 거듭제곱들이 특성이 되는 점이 다르다고 할 수있다.
결국 다항회귀는 다중회귀의 특수한 형태라고 볼 수 있는 것이다.


다만 우리가 훈련세트를 받았을 때 특성은 오직 x 하나만 주어지기 때문에 추가적인 특성(x의 거듭제곱)을 어떻게
처리할 것인지에 대해서는 선택이 쉽지 않은데 처음 이야기한 바와 같이 직관에 의존해서 결정을 할 수도 있겠지만
너무 차수가 높은 특성이 추가되는 경우 과대적합에 빠지기 쉬운 문제가 있다. 때문에 보통은 교차 검증을 사용하거나
학습 곡선을 살펴 결정하게 된다.


import numpy as np import numpy.random as rnd # 맷플롯립 설정 %matplotlib inline import matplotlib import matplotlib.pyplot as plt from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline # 각각 차수를 300, 2, 1로 하여 그래프를 그려본다. 튜플의 앞 두 개의 요소는 그래프 스타일이다. for style, width, degree in (("g-", 1, 300), ("b--", 2, 2), ("r-+", 2, 1)): # 특성을 추가한다. 300차, 2차, 1차를 적용해본다. polybig_features = PolynomialFeatures(degree=degree, include_bias=False) # 표준화 인스턴스를 만든다. std_scaler = StandardScaler() # 추정기로 선형 회귀를 사용한다. lin_reg = LinearRegression() # 2개의 변환기와 1개의 추정기로 구성된 Pipeline을 만든다. polynomial_regression = Pipeline([ ("poly_features", polybig_features), ("std_scaler", std_scaler), ("lin_reg", lin_reg), ]) # Pipeline 실행 polynomial_regression.fit(X, y) y_newbig = polynomial_regression.predict(X_new) plt.plot(X_new, y_newbig, style, label=str(degree), linewidth=width) plt.plot(X, y, "b.", linewidth=3) plt.legend(loc="upper left") plt.xlabel("$x_1$", fontsize=18) plt.ylabel("$y$", rotation=0, fontsize=18) plt.axis([-3, 3, 0, 10]) #save_fig("high_degree_polynomials_plot") plt.show() ########################################################## # API 설명 # ########################################################## # # Scikit-learn # # - LinearRegression(fit_intercept=True, normalize=False, copy_X=True, n_jobs=1) # . linear_model 모듈에 있는 클래스로 최소제곱법을 통한 선형회귀를 수행한다. # . 파라미터 (모든 파라미터는 기본 값이 있는 optional이다.) # : fit_intercept - 연산에 편향(bias)를 포함 시킬 것인지를 결정한다. # : normalize - 특성을 정규화 할 지 여부를 설정한다. fit_intercept가 False이면 # 무시된다. # : copy_X - True면 특성을 복사하고 False면 특성을 덮어씌운다. # : n_jobs - 수행할 job의 수로 사용할 CPU의 수라고 생각하면 된다. -1이면 모든 CPU를 # 사용한다. # # - PolynomialFeatures(degree=2, interaction_only=False, include_bias=True) # . 파라미터로 설정된 차수(degree)와 같거나 작은 차수의 모든 다항식 조합을 특성에 추가한다. # 차수가 2이고 전달된 특성이 [2, 3]라면 [2, 3, 2^2, 2 * 3, 3^2]이 생성된다. # 이 때 사용되는 특성은 2D 이상의 배열이어야 한다. 즉 [[2, 3]] 형태여야 한다. # . 파라미터(모든 파라미터는 기본 값이 있는 optional이다.) # : degree - 다항식의 차수 # : interaction_only - True로 설정되면 제곱수가 포함된 수는 모두 빠지고 원래의 특성 # 값들과 특성들의 곱만 포함된다. 즉 [[2,3]]에 대해 [2, 3, 6]이 # 출력된다. # : include_bias - 편향을 포함할 것인지를 설정한다. # # - Pipeline(steps, memory=None) # . list 타입으로 전달된 첫 번째 파라미터의 요소들을 순차적으로 실행한다. 이 때 앞 단계의 결과가 # 다음 다계의 입력으로 들어간다. 이 파라미터에는 반드시 변환기가 있어야 하며 변환기는 fit() # 함수와 transform() 함수가 구현되어 있어야 한다. Pipeline의 마지막에는 추정기가 실행되며 # 추정기는 fit() 함수만 구현되어 있으면 된다. # * 추정기와 변환기에 대해서는 핸즈온 머신러닝 101쪽의 "사이킷런의 설계 철학"을 참고하라. # ** Pipeline에 대해서는 핸즈온 머신러닝 108쪽 "2.5.5 변환 파이프라인"을 참조하라 # . 파라미터 # : steps - list 타입이어야 하며 변환기와 추정기가 포함되어있어야 한다. 각 요소는 # 이름/추정기(변환기) 쌍으로 되어있으며 마지막 단계는 추정기나 변환기를 모두 # 사용할 수 있지만 이전 단계는 모두 변환기여야 한다. # : memory - 변환기의 캐시 사용 여부를 결정한다. # # - StandardScaler(copy=True, with_mean=True, with_std=True) # . 특성의 평균을 뺀 후 표준편차로 나누어 표준화 해주는 클래스. 평균이 0 표준편차가 1인 # 정규분포로 표준화 한다. # * 자세한 내용은 핸즈온 머신러닝 107쪽 "2.5.4 특성 스케일링" 하단의 표준화를 참조하라. # . 파라미터 # : copy - 복사본 사용 여부를 결정한다. # : with_mean - 스케일링 하기 전에 데이터의 중간을 맞춰준다(의미를 잘 모르겠음...ㅠ.ㅠ). # : with_std - 분산 혹은 표준편차에 맞게 스케일링한다(역시 명확한 의미를 모르겠음...ㅠ.ㅠ).


다항회귀다항회귀


위 그래프를 보면 데이터의 형태가 아래로 오목한 2차 함수의 형태와 유사하다(물론 책의 전개상 데이터를 생성한 
함수가 2차 함수에 노이즈를 추가한 것임을 알고 있으나 실제로는 이 함수를 찾아내는 것이 머신러닝의 역할이다).
따라서 제공된 특성을 제곱한 값으로 특성을 추가한 예측 그래프가 가장 적절해 보인다. 차수를 300으로 올리면
보다 많은 데이터와 일치하게 되지만 너무 훈련 데이터에 과대적합된 형태라 볼 수 있다.


일반적으로 과소적합인 경우에는 더 복잡한 모델을 사용해야 하며, 과대적합인 경우에는 더 많은 훈련 샘플을 추가해야
한다.


정리


가장 기초단계라고 할 수 있는 선형 회귀 이지만 꾸역꾸역 깊이 파고들다보니 역시 수학적인 요소들로 가득 차있다.
현 상황에서야 굳이 각 API들의 세부적인 부분들까지 알 필요 없이 기본적인 사용법만으로 충분하겠으나 실무에
적용하기 위해서는 분명 파라미터들의 의미를 이해하고 자유자재로 사용할 수 있어야 할 것이다.


다만 Scikit Learn의 경우 국내에서는 텐서플로우나 케라스에 밀려 인기가 없는 탓인지 한글로 된 자료 찾기가 쉽지
않았다. 우선은 공식 문서를 이용해 공부를 해야겠으나 우리말로도 못알아듣는 수학적 내용들을 영어로 어찌 이해할 수 
있을지…ㅠ.ㅠ 일단 Scikit Learn의 공식 문서를 링크한다.


https://scikit-learn.org/0.19/_downloads/scikit-learn-docs.pdf


선형회귀의 종류에 대해서는 이정도로 정리하고 다음 포스팅에서는 규제가 있는 선형 모델에 대해 간단하게 알아보도록

하겠다.

블로그 이미지

마즈다

이미 마흔을 넘어섰지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^


2019/01/13 - [Study/아두이노] - [아두이노] 미니 드론 만들기 #1

2019/01/21 - [Study/아두이노] - [아두이노] 미니 드론 만들기 #2 (이번 글)




아두이노 미니 드론 만들기 #2


지난 포스팅에서는 우선 부품 구매와 간단하게 가조립을 통해 부품들을 어떻게 배치하는 것이 좋을 것인지에 대해
정리를 해보았다. 무게가 좀 불만스럽기는 하지만 대체로 만들만 하겠다는 판단이 섰다. 그리고 이번 주부터는 본격적으로
부품을 조립하고 아두이노 스케치를 작성하는 작업에 들어가기로 했다.


두 번째 삽질…


지난 번 첫 포스팅을 페이스북에 공유했을 때 페이스북의 한국 아두이노 사용자모임에서 ‘현자’님이 DC 모터 드라이버
보다 모스펫을 이용하여 모터를 제어하는 것이 어떻겠냐는 댓글을 달아주셨다. 안그래도 DC 모터 드라이버가 덩치도
큰데다가 역방향 회전이라는 불필요한 기능도 포함되어있어 불만스러웠던 차에 모스펫 이야기를 듣고나니 한번 모터
드라이버를 자작해볼까 하는 생각이 들었다.


당장에는 못만들어도 언젠가 만들겠거니 하고 적절한 사양의 모스펫을 주문을 했는데…이게 SMD 타입으로 너무
작아서 만능기판에는 납땜하기도 힘들고 아무래도 제대로 회로 기판을 만들어 써야할 것 같았다…ㅠ.ㅠ



그래서 덩치가 큰 트랜지스터 형태의 모스펫을 주문을 했는데…아뿔싸…ㅠ.ㅠ 이게 모스펫이 아니라 트라이액이라는
소자네…ㅠ.ㅠ 그냥 모양은 똑같고 triac이라고 써있길래 모스펫 브랜드 이름인가보다 했는데(실제로 triac이 브랜드
이름이 맞긴 하다)…모스펫하고 아예 성격이 다른 놈이었다…ㅠ.ㅠ 


모스펫모스펫빌어먹을 트라이액...-.-



모스펫과 트라이액에 대한 설명은 링크로 대신한다.


모스펫 : https://ko.wikipedia.org/wiki/MOSFET

트라이액 : https://ko.wikipedia.org/wiki/사이리스터#쌍방향_사이리스터_(TRIAC)


결국 처음 예정대로 구입한 DC 모터 드라이버를 사용하고 드라이버 자작은 훗날로 미루기로 했다.


하드웨어 조립


뭐든 시작하기 전에는 설계를 하기 마련! 그래서 일단 Fritzing을 통해 도면을 좀 그려봤다. 사실 그리 많은 부품이
있는 것은 아니라서 복잡하지는 않지만 송수신 부분에 사용하기로 한 nRF24 모듈이 핀을 7개나 잡아먹다보니
은근히 거미줄같은 도면이 나왔다.



지난 포스팅에서 말한 바와 같이 메인 컨트롤러는 아두이노 프로 미니를 사용하고 DC 모터 드라이버 2개, 수신용
nRF24, 그리고 자세 제어를 위한 MPU-9250 9축 센서가 전부이다.


처음에는 가능한한 부피와 무게를 줄이기 위해 핀헤더를 붙이지 않고 AWG30짜리 래핑 와이어를 이용하여 바로
연결을 할 생각이었다. 그리고 번거로움을 피하기 위해 납땜도 하지 않고 그냥 와이어를 절연테이프로 고정시키려고
했는데…역시나 절연테이프로는 제대로 고정이 되지 않아 접촉 불량이 심했다. 결국 납땜은 피할 수 없는 선택이
되고 말았다.


게다가 래핑 와이어를 직접 납땜을 해보니 와이어가 너무 가늘어서 피복 부분까지 덮어씌우지 않으면 납땜한 위치에서
똑똑 부러지기가 일쑤였다. 아무래도 불안하여 처음 MPU-9250 센서만 래핑 와이어로 직결하고 나머지 부분은 
핀헤더를 붙이고 말았다.



모터 부분도 문제가 좀 있었는데 8520 모터에 달린 커넥터가 DC 모터 드라이버의 소켓보다 작은 사이즈라서 모터 
드라이버의 핀을 안쪽으로 약간 휜 후 억지로 욱여 넣었다…ㅠ.ㅠ 육안으로 식별이 안되어 제대로 들어간 것인지 
모르겠지만 일단 느낌상으로는 뭔가 들어간 것 같긴 하다…-.- 그리고 DC 모터 드라이버의 input쪽에는 연결을 
하나씩 빼주었다. 어차피 역방향 제어는 필요 없으니까…



다음으로 문제가 되는 부분은 전원 분배 문제였다. 만능기판을 이용하여 만들려다가 그냥 케이블만 연결을 시켰다.
게다가 사용할 배터리는 2핀짜리 molex 51005 커넥터였는데 현재 가지고 있지도 않고 파는 곳도 국내 검색으로는
잘 안나오고…일단 아두이노용 점퍼 케이블로 배터리와 아두이노 프로 미니를 연결하기로 하고 DC 모터 드라이버로
들어가는 부분은 8520 모터와 동일한 규격의 커넥터(1.25mm JST Plug)를 연결하여 꽂기로 했다. 



전체가 마무리 된 것은 아니지만 이렇게 해서 일단 1차 조립은 완료가 되었다.


아두이노 스케치 하기


아두이노 스케치는 시작 하기도 전에 문제가 있었다. 나는 매킨토시를 사용 중인데 많은 호환 칩들이 Mac에서 쉽게
작동하지 않는다. 게다가 드라이버 설치도 만만치 않고 심할 경우 Mac 자체가 맛이 가는 경우도 있어 드라이버
설치가 꺼려지는 것도 사실이다.


아두이노 프로 미니에 스케치를 업로드 하기 위해서는 USB to TTL 시리얼 컨버터를 사용해야 하는데 구입한 것이
FTDI 칩을 사용한 것이었고 역시나 이 칩 역시 Mac에 별도의 드라이버를 설치해야 했다. 집에 윈도우즈 노트북이
있어 거기서 하면 편하긴 하지만 주로 아이들 학습용으로 쓰이고 있고 100만년만에 윈도우 환경에서 작업을 하려니
집중이 잘 안된다…ㅠ.ㅠ


결국 구글링을 통해 무사히 드라이버를 설치한 후 정상적으로 아두이노 프로 미니가 연결이 되었다.


Mac에 FTDI 드라이버 설치하기 : https://youtu.be/Ir2PVz1870E

드라이버 다운로드 : https://www.ftdichip.com/Drivers/D2XX.htm


하지만 여기서 끝이 아니었다. 스케치를 업로드 하려는데 컴파일 단계에서 다음과 같은 오류가 발생하였다.


fork/exec /Users/mazdah/Library/Arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-g++: no such file or directory


조금 당황스러웠으나 역시나 구글링을 통해 해답을 찾았다.


air-g++ 오류 관련 해결책 : https://forum.arduino.cc/index.php?topic=547545.0


이렇게 해서 겨우겨우 아두이노 프로 미니에 스케치를 업로드 할 수 있었다.


이렇게 준비를 마친 후 우선은 제일 먼저 연결을 한 것이 MPU-9250이기 때문에 이쪽 예제부터 찾아보았다.
에듀이노 오픈랩 사이트에 예제가 첨부되어있어 간단하게 다운로드 받아 실행을 해보았다. 실행은 잘 되는 것 
같은데 아직 값을 해석하는 방법을 몰라 공부를 좀 해야 할 것 같다.


MPU-9250(GY-9250) 예제 : https://blog.naver.com/eduino/220887867791


스케치를 업로드 한 후 모듈을 움직여보니 시리얼 모니터에 아래 동영상과 같이 값이 출력된다.



정리


삽질도 많았고 아직 모르는 내용도 많고 생각보다 만만치 않은 작업이었다. 그래도 납땜과 같은 번거로운 작업은
모두 마무리가 되었고 다음 주부터는 간단하게 점퍼 케이블 연결하고 아두이노 스케치에만 전념하면 된다(은근히 
판 벌리는 것을 싫어해서 납땜같은 작업은 정말 작심하고 해야한다…-.-).


사실 아직은 9축 자이로 센서가 드론에서 어떤 역할을 하는지도 잘 모른다…ㅠ.ㅠ 우선 다음 주에는 모터를 제어하는 부분부터 정리를 좀 하고 자이로 센서는 느긋하게 알아봐야겠다.

블로그 이미지

마즈다

이미 마흔을 넘어섰지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^


2019/01/13 - [Study/아두이노] - [아두이노] 미니 드론 만들기 #1(이번 글)

2019/01/21 - [Study/아두이노] - [아두이노] 미니 드론 만들기 #2



아두이노 미니 드론 만들기 #1


2017년 7월 경…아두이노를 이용하여 싱글콥터(프로펠러가 하나인 드론)을 만들겠다고 한참 삽질을 했었다.
물론 성공하지는 못했다. 다만 잠깐 띄우는 정도…(유튜브 영상 : https://youtu.be/sI5e4swuRTM)


다양한 시도를 하였지만 일부 구현되지 않은 기능(자이로 센서를 이용한 균형 잡기 등)과 적절하게 프레임을 구성하지
못하는 등의 한계로 보류를 해두었다.


그런데 겨울방학을 맞아 아이들이 다니는 태권도장에서 드론 만들기 수업을 한다고 하길래 아이가 하고 싶어하기도 하고
또 어떻게 진행되고 어떤 드론을 만들어 오는지도 궁금하여 시켜주려 하였는데 다른 곳에 돈을 쓰게 되어 결국은 시켜주지
못하게 되었다. 대신 아쉬워하는 둘째 아이를 위해 아빠가 직접 만들어주겠다고 호언장담을 해버렸다…ㅠ.ㅠ


그래서 한동안 묵혀두었던 부품들을 주섬주섬 꺼내고 또 일부 부품들은 새로 구입을 하여 드론 만들기에 돌입을 하였다.
물론 이번에는 싱글콥터가 아닌 쿼드콥터로…


구상


처음에는 시중에 판매하는 아두이노 드론 키트를 구매해볼까 하는 생각도 했다. 그런데 검색 사이트를 통해 아두이노 
드론 키트를 검색하게 되면 대부분 10만원이 넘는 금액의 키트들이 검색된다. 아무래도 비용이 애들 엄마의 허락 범위를
넘을 것 같기도 하고 또 뭔가 남이 상당부분 만들어놓은 것을 조립하는 것은 별로 재미도 없을 것 같아 밑바닥부터 직접 
만들어보기로 했다.


직접 만들기로 하면서 가장 문제가 되었던 것은 바로 프레임이었다. 이제는 3D 프린터가 있어 쉽게 만들 수 있을 것이라
생각했지만 내가 가진 3D 프린터의 출력 사이즈가 작다보니 한 번에 프레임을 찍어낼 수가 없는 상황이었다. 조금은
색다른 형태의 드론을 만들어보고 싶었지만 아무래도 다음 기회로 넘겨야겠다.


결국 프레임도 구매하기로 하고 나머지 부품들도 아두이노와 아두이노 관련 모듈들로 구매하여 조립을 해보기로 하였다.


부품의 준비


이렇게 결정한 후 구매한(혹은 이미 구매를 해놓은) 부품들을 하나씩 정리해보도록 하겠다.
일단 전체 부품들은 다음 사진과 같다.



각각의 부품들을 좀 더 상세하게 살펴보자.


드론 프레임 
- Q100 카피 제품으로 사진은 별도로 안찍었다. 위 사진의 프레임이다. 모터는 8520 모터를 사용
가능하고 프로펠러는 65mm까지 사용 가능하다. 가격은 4,260원


Flight Controller 
- 메인 컨트롤러는 아두이노 프로 미니 3.3v 8Mhz이다. 아두이노 프로 미니를 구매할 때 삽질을 조금
하였는데…아무 생각 없이 전에 하던대로 5V로 작동하는 제품을 선택한 것이다. 드론 자체는 3.7V로
작동을 해야 하는데 아두이노가 5V로 작동을 하게 되면 아두이노를 위해 별도의 배터리를 장착해야 하는
상황이 생긴다. 늦게서야 이 사실을 파악하고 부랴부랴 배송 전에 3.3V짜리로 교환 요청을 했다. 덕분에
배송은 좀 늦어졌지만…ㅠ.ㅠ 가격은 3,300원



자이로 센서 
- MPU-9250 9축 자이로 센서로 구입을 하였다. 원래 전에 구입해놓은 것이 있는데 찾지를 못해 새로
구입을 하였다…ㅠ.ㅠ 가격은 6,050원



DC 모터 드라이버 
- 현재 키트로 판매되고 있는 아두이노 드론들을 보면 베이스 보드라 하여 모터 드라이버 기능이 포함된 별도의
보드가 부품으로 포함되어있다. 내가 회로 설계를 할 줄 안다면 하나 만들어보겠으나 그런 재주는 없으니…-.-
이 부품으로 인해 무게에서 많은 손해를 보고 들어간다…ㅠ.ㅠ 일단 L9110 모터 드라이버 중 가장 작아보이는 
놈으로 골랐다. 보통 DC모터 드라이버가 드라이버 1개 당 DC모터 2개를 제어할 수 있으므로 2개를 구매했다. 
개당 가격은 3,740원



배터리 
- 배터리는 전에 구매해놓은 적이 있는 3.7V 500mah Li-Po 배터리를 사용하기로 했다. 
가격은 잘 기억이 안나지만 5,200원 선일 것이다.



8520 코어리스 모터 
- 모터 역시 예전에 구매해놓은 것인데 유튜브 영상에서 레이서스타 제품이 성능이 높게 나온 것을 보고 뱅굿에서
구매한 모터이다. 일단 RPM이 53500으로 보통 45000정도인 다른 제품보다 높다. 가격은 4개 한묶음으로
15,922원
유튜브 : https://youtu.be/AMWXXCHrHto



프로펠러
- 프로펠러 역시 모터와 함께 구매해 놓은 것으로 유튜브 동영상에서 가장 성능이 높게 나온 것으로 선택한 것이다.
킹콩 65mm 프로펠러이고 10쌍 가격은 5,679원 
유튜브 : https://youtu.be/VtKI4Pjx8Sk



수신기 
- DC 모터 드라이버와 마찬가지로 송수신기 역시 별도의 부품으로 만들어야 한다. 실내 공간이므로 블루투스로도
충분하지만 보다 저렴하기도 하고 예전에 싱글콥터 만들 때 이미 nRF24L01+ 모듈 기반으로 만들어 둔 송신기와 
코드를 재활용할 수도 있어 수신기 역시 nRF24L01+ 모듈을 선택했다. 가격은 1,100원



송신기
- 앞에서 말했듯이 2017년에 싱글콥터 만들 때 만들어놓은 송신기를 재활용 할 것이다. 송신기는 아두이노 나노와
듀얼 조이스틱 모듈 그리고 nRF24L01+ 모듈로 만들어져있다.


가조립과 무게


일단 모든 것을 직접 제작하기로 한 후 가장 걱정이 되는 것은 드론의 무게였다. 워낙에 무게가 적게 나가는 미니 드론이다
보니 1~2g도 영향을 크게 미친다고 하는데 많은 부품들이 기판에 소형화되어 올려진 전용 보드에 비해 개별로 부품을
사용하다보니 무게가 너무 많이 나가지 않을까 걱정이 되었던 것이다. 일단 부품들을 쌓아 올려 무게를 한 번 재보았다.
완성이 된다면 케이블 등으로 인해 무게가 조금 더 늘겠지만 일단 배터리를 포함한 부품만의 무게는약 60g이었고 여기서
배터리를 제외하면 약 42g으로 생각보다는 많이 나가지 않았다.




그리고 부품들을 프레임에 배치를 해보았다. 두 번째 걱정은 작은 프레임 안에 덩치가 큰 부품들을 제대로 배치할 수
있을까 하는 문제였다. 특히나 DC모터 드라이버가 덩치가 커서 조금 걱정이 되었는데 드론 바닥쪽으로 붙여보니 나름
적절하게 배치가 되었다.




정리


일단 이번 주말은 여기까지 진행을 해보았다. 아마 다음주부터는 제대로 골머리좀 썪을 것 같다.
우선 아두이노 프로 미니가 USP 포트가 없는 관계로 USB to TTL모듈을 이용하여 프로그램을 업로드 해야 하기도 하고
각 부품의 배선을 어떻게 하느냐도 문제다. 당연히 프로그래밍도…ㅠ.ㅠ


특히 부품간의 배선은 무게를 최소화 하기 위해 핀헤더라든지 아두이노용 점퍼 케이블은 사용하지 않고 굵기가 가는
래핑와이어를 사용하려고 하는데 납땜을 하자니 조금 귀찮고 그렇다고 절연 테이프로 하자니 그건 또 그것 대로 귀찮고…


일주일간 방법을 잘 모색해보고 다음 주말부터 본격적으로 시작해보자.


블로그 이미지

마즈다

이미 마흔을 넘어섰지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^



안드로이드 스튜디오 : Gradle sync failed: Uninitialized object exists on backward branch 70


백만 년만에 안드로이드 스튜디오를 3.2.1로 업그레이드한 후 프로젝트를 하나 생성했더니 아래와 같은 오류가
발생을 하였다.


Uninitialized object exists on backward branch 70
Exception Details:
  Location:
	com/android/build/gradle/internal/scope/BuildArtifactsHolder.newArtifact(Lcom/android/build/api/artifact/BuildableArtifact;)Lcom/android/build/gradle/internal/scope/BuildArtifactsHolder$BuildableArtifactData; @119: goto
  Reason:
	Error exists in the bytecode
  Bytecode:
	0x0000000: bb02 0559 2bb9 0208 0100 c000 b82b b902
	0x0000010: 0c01 0001 b902 1202 0059 1302 14b8 0051
	0x0000020: c001 734d 3a0d 3a0c 3a0b 2c4e bb01 a659
	0x0000030: 2c10 0ab8 0177 b701 a7c0 00b8 3a04 2db9
	0x0000040: 018e 0100 3a05 1905 b901 9401 0099 002d
	0x0000050: 1905 b901 9801 003a 0619 0419 06c0 00f1
	0x0000060: 3a07 3a0e 1907 b902 1501 003a 0f19 0e19
	0x0000070: 0fb9 01ad 0200 57a7 ffcf 1904 c001 af3a
	0x0000080: 0e19 0b19 0c19 0d19 0eb7 0218 b0       
  Stackmap Table:
	full_frame(@70,{Object[#2],Object[#83],Object[#371],Object[#371],Object[#184],Object[#400],Top,Top,Top,Top,Top,Uninitialized[#0],Uninitialized[#0],Object[#184]},{})
	same_frame(@122)

Open File


아래는 에러 화면이다.




사실상 기본적인 순서에 따라 프로젝트 하나 생성했을 뿐인데 이러한 문제가 생겨버리면 참으로 당황스럽지 아니할 수 
없다…ㅠ.ㅠ 열심히 구글링한 끝에 JDK 설정이 문제라는 것을 확인할 수 있었다.


해결 방법


해결 방법은 간단하다. 안드로이드 스튜디오의 Welcome 화면의 우측 하단에 보면 Configure 메뉴가 있는데
클릭한 후 가장 아래 있는 Project Defaults > Project Structure를 선택하면 Project Structure 창이 열린다.
여기서 가운데 있는 JDK Location 항목의 Use Embedded JDK (recommended)를 체크해주면 된다.
친절하게 recommended라고 되어있지 않은가…-.-


구글링하다보니 답변에 Project Structure에서 Use Embedded JDK를 체크하라고만 되어있어서 Project 
Structure를 찾는데도 한참 걸렸다. 꾸준히 공부 안하면 이렇게 된다…ㅠ.ㅠ


아래에 참고삼아 스크린샷도 함께 올린다.




블로그 이미지

마즈다

이미 마흔을 넘어섰지만 아직도 꿈을 좇고 있습니다. 그래서 그 꿈에 다가가기 위한 단편들을 하나 둘 씩 모아가고 있지요. 이 곳에 그 단편들이 모일 겁니다...^^