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


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


2019/03/03 - [Study/아두이노] - [아두이노] 미니 드론 만들기 #3


2019/03/11 - [Study/아두이노] - [아두이노] 미니 드론 만들기 #4(이번 글)


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


미니 드론 만들기의 첫 번째 포스팅이 1월 13일이었으니…벌써 시작한 지도 2달여가 되어간다.
하지만 아직도 하드웨어 조립에서 맴돌고 있다…ㅠ.ㅠ


지난 주에 포스팅한 SMD 타입의 nRF24L01 모듈 구입과 보드의 소형화로 하드웨어 작업은 금방 끝이 날 것이라고 
생각했으나 여전히 납땜 할 부분들이 많아 선뜻 손이 가지 않았다. 그리고 지난 주 조립한 아두이노 프로 미니를 중심으로
한 모듈의 조립 상태도 높이가 조금 높아 구입한 프레임에 장착하기가 썩 만족스럽지 않은 상황이었다.


어쨌든 이제 모터 드라이버와 외부전원부까지 납땜을 마치고 하드웨어쪽은 마무리를 지으려는 찰나. 새로운 부품이
눈에 들어왔다.


초소형 DC 모터 드라이버


사실 미니 드론을 만드는 틈틈이 드론 이후 만들 싱글콥터에 대해 이것 저것 아이디어를 짜내고 있던 터였다.
프레임을 만들 플라스틱 용기도 몇 개 준비했고 그동안 사다놓은 모터 스펙도 다시 한 번 확인하고 배터리도 무게와 
용량을 가늠해보고…그리고 싱글콥터에서 가장 부담스러운 부분인 방향 제어를 위한 4개의 블레이드를 작동시킬
서보모터와 관련하여 적당한 서보모터 드라이버를 찾고 있었다.


현재 가지고 있는 서보모터 드라이버는 16개의 서버모터를 컨트롤하는 놈이다보니 크기와 무게 여러모로 부담스러웠다.
딱 4개만 컨트롤 가능한 드라이버가 있으면 좋으련만…그러던 중 그나마 가장 작은 크기로 6개의 서보모터를 컨트롤 할
수 있는 서보 모터 드라이버를 찾았다. 그리고 그 드라이버를 찾은 곳에서…초소형 DC 모터 드라이버를 발견하였다.


일단 크기는 아래 사진으로 확인할 수 있다. 기존 드라이버(요 것도 그나마 작다고 해서 구매한 것이었다)와의 비교, 
그리고 100원짜리 동전과의 비교이다.



작은만큼 허용하는 외부 전압은 10V까지이고 1.5A까지 모터에 공급할 수 있다. 어차피 사용하는 모터가 8520 코어리스
모터로 이정도 스펙이면 충분히 구동시킬 수 있을 것 같았다. 또한 아두이도와의 연결도 3.3V와 5V 모두 가능하여 크게
신경쓰지 않고 사용할 수 있었다.


최종 조립


새로운 초소형 DC 모터 드라이버의 사용으로 전체적으로 하드웨어를 재배치 할 수 있게 되었다. 기존 모터 드라이버가
드론 프레임의 아랫면을 모두 차지하여 나머지 중요 부품들을 프레임 상단에 배치할 수밖에 없었는데 이제는 가능한한
정확하게 수평을 맞추기 위해 MPU-9250 9축 자이로 센서만 프레임 상단에 부착하고 나머지 부품들은 모두 프레임의
하단에 배치시켰다. 드론 프레임과 거의 일체화 되었다고 봐도 될 정도로 잘 들어맞았다.


다만 케이블로 이어 납땜을 하는 과정에서 전선들이 너무 어지럽게 배치되어 보기 안좋게 된 것이 못내 아쉬울 뿐…


어쨌든 더 미룰 수도 없고 해서 납땜을 해야 할 부분은 모두 해버렸다. 이렇게나 열심히 해놓았는데 만일 납땜의 문제로
뭔가 작동을 하지 않는다면…비뚤어지고 말테다…-.-!





총 5개의 보드를 붙인 것 치고는 꽤 슬림하게 잘 배치가 되었다.


삽질…


납땜을 하는 과정에서 무지하게 삽질을 했다. 당연하게도 SMD 타입의 nRF24L01 모듈이 문제였다.
지난 포스팅에서 언급한대로 이번에는 칼팁이 아닌 송곳 팁으로 남땜을 해보았다. 확실히 사용했던 팁 중에 가장
수월하게 작업이 되었다. 하지만 그럼에도 불구하고 SMD 타입의 nRF24L01 모듈같이 작은 영역은 땜질하기가 
너무나 어려웠다. 


첫 번째 시도에서는 기존 코드가 동작하지 않았다. 육안으로도 얼핏 납이 엉겨붙은 부분이 보였는데 아마도 이 때문이 
아닐까 싶었다.


그런데 어렵게 땜질한 것을 다 뜯어내고 새 모듈을 다시 땜질하여 붙였는데 이번에도 작동을 하지 않는 것이었다. 
이번에는 육안으로(고배율 루페로 확인) 확인했을 때에도 특별한 문제가 없어보였는데 작동을 하지 않았다.
이번 주말에 번거로운 작업을 모두 마무리하자는 일념으로 귀차니즘을 무릅쓰고 다시 다 뜯어내고 이번에는 기존의
nRF24L01 모듈을 연결시켜보았다. 그런데 이번에도 역시 동작을 하지 않았다. 그래서 혹시나 하고 송신부를 켠 후
다시 확인해보니 그제서야 신호가 잡혔다.


처음 시도에서는 송신부를 켰음에서 안되었던 것으로 봐서 확실히 문제가 있었던 것 같은데 두 번째 시도에서는
깜빠하고 송신부를 켜지 않은 채 그냥 잘 안된다고 판단하고 뜯어버리고 말았다. 제대로 테스트를 해보았더라면
아마도 일찍 마무리 지을 수 있었을지도…ㅠ.ㅠ


앞으로는 소프트웨어적인 테스트에서도 빠짐없이 모든 경우의 수를 다 테스트 해봐야겠다. 


정리


이렇게 해서 하드웨어 조립은 모두 마쳤다. 하지만 이게 정상적으로 작동을 할 지는 아직 미지수이다. 사실 간단하게 모터
구동 테스트를 위한 코드까지 넣어놓긴 했지만 잘 안될 것이 두려워 실제로 구동시켜 보지는 못했다. 배터리만 연결하면
바로 확인이 되겠지만…ㅠ.ㅠ


진행하다보니 이게 드론을 만들자는 것인지 소형화된 부품을 찾자는 것인지 모르게 되어버렸지만 어쨌든 이제 납땜은
모두 마쳤으니 다음 주말에는 모터의 장상 구동 여부 확인 후 다행히 잘 동작을 하면 본격적인 드론 동작을 구현하는 
단계로 나가고 그렇지 않다면….다시 조립해야 하나…ㅠ.ㅠ?


그저 프로펠러가 돌기를 바랄 뿐이다…

블로그 이미지

마즈다

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



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


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


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


2019/03/11 - [Study/아두이노] - [아두이노] 미니 드론 만들기 #4





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

갈길은 먼데…내 주력 PC인 2012년 산 맥미니가 말썽을 부렸다…
워낙 맥에 아두이노 관련 중국산 칩들의 호환성이 떨어지는지라 아무래도 윈도우 PC에서 작업을 해야 할 것 같은데
집에 있는 윈도우 PC는 아이들이 주로 사용하다보니 내가 사용할 수 있는 시간이 별로 없었다.


결국 Parallels를 이용하여 맥미니에 윈도우를 설치하고 거기서 작업을 하려고 했는데…
왜??? 맥용 소프트웨어들은 메이저 버전이 올라가면 돈을 새로 받냔 말이다…ㅠ.ㅠ 돈 좀 아껴보려고 크랙버전을 찾아
설치를 좀 해볼까 시도하다가 결국 맥미니가 맛이 갔다. 딱 느낌이 쥬토피아의 나무늘보 같았다…-.-


그리고 Mac OSX 모하비를 클린 설치하기를 수차례…겨우 지난 주말에야 정상 복구 시킬 수 있었다.
하지만 2012년 맥미니의 성능으로는 아무리 메모리를 16Gb 장착을 해도 가상 환경에서 윈도우를 구동하는 것이
만만한 일은 아니었다. 아무래도 2018년 맥미니을 구해야 하나…ㅠ.ㅠ


각설하고 이런 이유로 한동안 드론에 손을 못댔고 그나마 작업한 부분도 블로그에 정리할 수가 없어서 이제야 겨우
조금이나마 진행한 내용을 올려본다.


개선


그동안 진행한 작업이라고는 부품들을 조금이나마 부피를 줄여 조립한 것이 다이다.
현재 사용할 보드들은 아두이노 프로 미니, MPU-9250 9축 자이로 센서, 그리고 통신을 위한 nRF24L01+ 이렇게
3개이다. 모두 크기가 그닥 크지 않은 모듈들이긴 하지만 지난 포스팅에서 정리한 작업처럼 핀헤더를 붙이고 케이블로
연결해서는 답이 안나온다는 것을 확인했다. 내가 구입한 드론 프레임에 올리기에는 높이가 너무 높아져버렸다.


결국 다시 처음의 계획대로 핀헤더 없이 각 보드들을 케이블로 직접 연결하기로 하고 추가로 nRF24L01 모듈이 더 작은
사이즈로 만들어진 것이 있기에 이놈을 추가로 구매해서 부피를 더 줄였다. 아래 사진을 보면 기존 모듈과의 크기를
비교할 수 있다. 거의 절반 가까이 크기가 줄었다.



새로 구입한 소형 nRF24L01 모듈은 부피가 작아진 것은 좋은데 연결 핀이 SMD 타입으로 되어있어(소형화의 당연한
결과이겠지만…) 과연 제대로 땜질을 할 수 있을지 매우 불안했다. 일단은 땜질 하기 전에 어떻게 연결해야 하는지 확인을
해보자. 첫 번째 사진은 기존 모듈의 핀맵이고 두 번째 사진은 새로 구입한 모듈의 핀맵이다.




핀 맵을 확인한 후 인두기를 꺼냈다. 검색을 해보니 작은 부분을 땜질할 때는 칼팁으로 하는 것이 좋다는 말에 처음으로
칼팁을 사용해 보았다. 나는 현재 인두기가 3개가 있는데 제일 처음 샀던 저가형 자야 인두기…사실 이놈으로도 땜질을
잘만 했는데 어느날 땜납이 잘 녹지 않는 것이었다. 싸구려라 맛이 갔구나…생각했는데…선무당이 사람 잡는다고…나중에
보니 당시 사용한 납이 무연납이었던 것이다…ㅠ.ㅠ 어쨌든 당시에는 인두기가 맛이 갔다고 생각해서 다시 그 유명한 하코
인두기 중 가장 저렴한 980 이두기를 새로 구입했다. 이 때 추가로 하코 정품은 아니고 호환 제품으로 칼팁을 하나 더
구했다. 그러다가 다시 banggood.com 검색하다가 싼맛에 인두기를 추가로 하나 질렀는데 여기에는 팁이 5개 정도가
기본 포함되어있었다. 아래 사진의 위에서부터 중국산 인두기, 하코 980, 자야 인두기 이다.



아무튼…그동안 하코 인두기로 잘 썼었는데 팁 갈아 끼워 가면서 쓰기가 귀찮아서 중국산 인두기에 칼팁을 끼워 사용해
보았다. 아니나 다를까…노안과 수전증의 합작으로 막 옆에 핀과 같이 붙여주기도 하고, 가끔은 납이 에베레스트만큼 솟아
오르기도 하고, 물론 아주 가끔은 뿌듯할만큼 잘 붙기도 하고…암튼 솔더위크 엄청 써가면서 겨우겨우 땜질을 마쳤다.
아무래도 땜린이에게는 칼팁도 어려운 것 같고 다음에는 중국산 인두기에 딸려 온 송곳 팁을 써봐야겠다.


언제 어디서 단선이 되거나 쇼트가 날지 모를 아슬아슬한 상태로 땜질을 마치고 나니 제법 아담한 사이즈로 완성이 
되었다. 특히나 새로 구한 nRF24L01 SMD 모듈이 아두이노 프로 미니에 포~옥 안기는 형태가 되어 보기가 참 좋았다^^.
추가로 nRF24L01모듈에 컨덴서(커패시터)를 붙여주면 더 안정적으로 통신을 할 수 있다고 하기에 붙여주었다.






작동 테스트


지난 포스팅까지 진행한 작업에서는 MPU-9250 9축 자이로 센서 동작만 확인을 했다. 그 후 nRF24L01 모듈의
통신을 테스트 해보았는데…이게 뭔 조화인지 전혀 통신이 되지 않는 것이다. 분명 코드는 맞게 작성을 했고 
시리얼 모니터를 보면 nRF24L01 모듈도 정상적으로 연결이 되었는데 조이스틱쪽과 드론쪽의 통신이 되지 않았다.


그러다가 예전에 싱글콥터 만들 때 테스트 하던 아두이노 나노에 연결해서 하니 정상적으로 통신이 되는 것이 아닌가…
그리고 다시 아두이노 프로 미니에 연결을 했더니 그 뒤로 통신이 아주 잘된다…-.- 손톱만한 모듈들에 농락당하는 
삶이라니…ㅠ.ㅠ



일단 통신은 되었지만 수정할 부분이 좀 있다. 조이스틱쪽 코드가 예전에 싱글콥터 개발 시 작성된 코드라 지금 만드는
쿼드콥터에 맞게 수정을 해야 하고 드론쪽도 코딩을 시작해야 하고…일단은 간단하게 코드의 중요 부분만 올려본다.


정리


별로 한 것도 없는데 컴퓨터가 맛이 가는 바람에 시간을 많이 잡아먹었다.
그래도 어영부영 한 1/3은 온 것 같고 이제 진짜로 중요한 코딩과 드론의 최종 조립만 남았다. 이제 컴퓨터도 복구가 
되었으니 조금 더 서둘러서 진행을 해야겠다.

그나저나 모듈들을 이렇게 소형화 하고 나니 다시 싱글콥터에 대한 욕심이 스멀스멀 밀려온다. 이번 미니 드론 작업을
마치면 바로 이어서 싱글콥터 작업을 시작해봐야겠다.

블로그 이미지

마즈다

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



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 (이번 글)

2019/03/03 - [Study/아두이노] - [아두이노] 미니 드론 만들기 #3

2019/03/11 - [Study/아두이노] - [아두이노] 미니 드론 만들기 #4




아두이노 미니 드론 만들기 #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

2019/03/03 - [Study/아두이노] - [아두이노] 미니 드론 만들기 #3

2019/03/11 - [Study/아두이노] - [아두이노] 미니 드론 만들기 #4



아두이노 미니 드론 만들기 #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모듈을 이용하여 프로그램을 업로드 해야 하기도 하고
각 부품의 배선을 어떻게 하느냐도 문제다. 당연히 프로그래밍도…ㅠ.ㅠ


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


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


블로그 이미지

마즈다

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


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

2019/01/28 - [Study/인공지능학습] - [머신러닝 Reboot] - 개념잡기 - 경사 하강법 3 - 경사 하강법의 종류


머신러닝 Reboot - 개념 잡기 : 경사 하강법 2 - step 공식 이해하기


지난 시간에는 어설프게나마 경사 하강법이 왜 특성에 민감한지 그래프를 통해 알아보았다. 여전히 논리적으로 설명하기 
힘든 부분이 있어 아쉬움이 남지만 직관적으로 봤을 때도 경사 하강법을 수행하기 위해서는 일단 특성들의 스케일을 
맞추는 것이 좋다는 것은 알게 되었다.


오늘은 이어서 어떤식으로 다음 기울기를 찾아 움직이는지 그 과정을 공식을 통해 알아보자.
이번 정리는 오로지 “핸즈온 머신러닝”의 166쪽에 있는 ‘식 4-7 경사 하강법의 스텝’을 이해하기 위한 것이다.


선형 회귀 관련 공식 복습 - 가설 함수와 비용 함수


우선 복습 차원에서 선형 회귀의 가설함수와 비용 함수를 다시 한 번 보자. 여러 표현 방법이 있지만 여기서는 “핸즈온
머신러닝”에서 발췌한 내용으로 정리를 해보겠다.


먼저 가설함수를 보자. 간단한 식임에도 불구하고 다양한 표현이 존재하여 혼란을 주기 일쑤이다. 아래 표현들을 보면서
정리해보자.


선형 회귀 가설 함수그림 1


1번 같은 경우 단순 선형 회귀라고 생각하면 되겠다. 𝜭와 𝑥가 모두 스칼라인 경우인 것이다. 다시 말해 특성이 1개인
경우…


2번과 3번은 모두 다중 선형 회귀를 표현한 식이며 𝜭와 𝑿는 모두 벡터이다. 그런데 묘하게 표현이 다르다.
왜 다른지 차근차근 살펴보자.



기본적으로 벡터는 종벡터(𝑛 X 1) 형태를 취한다. 이 때 𝑛은 특성의 수이다. 우리가 이미 잘 알고 있듯이 이 식들은
가설 함수의 원래 형태인 아래의 형식을 벡터의 곱으로 표현한 것이다.


선형 회귀그림 2


이 식의 𝜭와 𝑥를 각각 벡터로 표시해보자. 벡터는 기본적으로 종벡터 형태를 취한다고 했으니 다음과 같이 표현할 수
있다(여기서 𝜭 의 0번째 요소는 편향을 의미하며 따라서 X의 0번째 요소는 항상 1이다).


그림 3


그림 3-1


그런데 𝜭와 𝑥 가 모두 𝑛 X 1벡터라고 한다면 (𝑛 X 1) ∙ (𝑛 X 1)이 되어 벡터(행렬)의 연산 법칙으로 인해 계산을 할 수 
없게 된다. 따라서 앞에 있는 𝜭를 전치행렬로 만들어 (1 X 𝑛) ∙ (𝑛 X 1)이 되게 함으로써 연산이 가능하게 만드는 것이다.
이 것이 바로 두 번째 식이다. 물론 전치행렬의 성질에 따라 다음과 같이 표현할 수도 있다.


선형 회귀 가설 함수그림 4


3번째 식은 2번째 식을 조금 더 확장한 것이라고 볼 수 있다. 2번이 식에서 𝑋는 𝑛개의 요소를 갖는 벡터였다.
이러한 식이 𝑚개, 즉 𝑛개의 특성을 갖는 샘플이 𝑚개가 있다고 보는 것이다. 따라서 이 때는 식의 결과 역시 
벡터가 되는 것이다. 즉, 3번의 식을 구성하는 각 요소는 다음의 의미가 있다(물론 이 때 편향을 생각하여
𝑋 행렬의 1열은 모두 1로 채워져야 한다).


그림 5


그림 6


그림 3


여기에서 식은 2가지로 표현이 가능하다 𝑋를 𝑛 X 𝑚 행렬로 만든다면 식은 2번의 식과 동일한 형태가 만들어질
것이다. 이렇게 본다면 2번의 식이 가장 일반적인 선형 회귀의 가설함수라고 볼 수 있을 것이다. 그리고 이 식을
선형 회귀의 비용 함수에 대입하게 되면 아래와 같은 비용 함수의 식이 만들어진다.


선형 회귀 비용 함수그림 7


하지만 𝑋를 𝑚 X 𝑛 행렬로 만든다면 3번의 식이 된다. 이 3번의 식은 곧이어 설명할 경사 하강법의 step을
계산하는 공식에 등장하게 된다.


배치 경사 하강법


경사 하강법은 가중치 𝜭의 변화에 따라 비용 함수의 결과가 얼마나 바뀌는지를 확인하는 연속되는 과정이고
이를 알기 위해서는 비용 함수를 𝜭에 대해 미분해야 한다. 위에 언급한 그림7의 비용 함수를 𝜭에 대해 미분하면
다음과 같은 식을 얻을 수 있다(이 과정에서도 변형이 있는데 식 맨 앞의 2/m에서 2를 없애기 위해 미리 비용 함수에
1/2를 곱하는 경우도 있다. 이런 경우 2/m이 아닌 1/m이 된다).


선형 회귀 비용 함수의 편도함수그림 8


우리는 수알못이니 이 과정을 잠깐 설명하면 우선 미분의 성질 중 다음 성질을 알아야 한다. 바로 미분의 연쇄법칙이다.


 (f(g(x)))'=f'(g(x))g'(x)


미분의 연쇄법칙을 적용해보자면 선형 회귀 비용 함수는 다음과 같이 구성되어있다.


미분의 연쇄법칙그림 9


따라서 차례차례 미분을 해보면 다음과 같이 풀이될 수 있다.


미분의 연쇄법칙그림 10


이와 같이 선형 회귀의 비용 함수에서 𝜭에 대해 미분한 도함수는 그림8의 식이 되는 것이다. 이 도함수는 곧 비용 함수의
기울기를 의미하므로 경사 하강법은 이 도함수의 변화를 이용여 최솟값을 찾는 과정이고, 이는 초깃값으로 주어진 𝜭0에서 
학습률과 비용 함수의 도함수를 곱한 값을 빼서 다음 𝜭1를 구하고 다시 이 𝜭1에서 학습률과 비용 함수의 도함수를 곱한 
값을 빼서 𝜭2를 구하는 식으로 이 과정을 반복해 나가는 것이다.


이 과정에서 비용 함수의 도함수를 그대로 사용하는 경우도 있지만 식의 단순화를 위해 이 비용 함수의 도함수의 변화량을
행렬식으로 만들어 한방에 처리하는 방법도 있다. 이 것은 얼마전 포스팅한 정규방정식 관련 글에서 언급했듯이 𝚺는 
행렬로 변환 가능하다는 것으로 설명할 수 있다.


비용 함수의 도함수를 풀어보면 다음과 같다.


선형 회귀 비용 함수의 도함수그림 11


여기서 괄호 안에 있는 각 요소의 점곱(∙)을 기준으로 앞뒤로 분리를 하면 각각 다음과 같은 종벡터를 만들 수 있다.


그림 12


그림 13



각각의 종벡터는 m X 1 형태의 종벡터로 그대로는 곱셈이 성립하지 않으므로 𝑋가 요소인 종벡터를 전치시켜서
1 X m 형태의 횡벡터를 만들어 곱하면 동일한 식이 된다.


그림 14


그림 15


이제 마지막으로 𝜭가 포함된 종벡터를 풀이해보자. 이 종벡터는 다시 아래와 같이 나눠볼 수 있다.


그림 16


여기서 다시 뺄셈 식의 앞부분을 생각해보면 𝑋(i)는 특성 수만큼의 요소를 갖는 벡터들이다. 즉 m행 n열의 행렬이
되는 것이다.


그림 17


하지만 이렇게 되면 𝜭T는 1 X n의 벡터이고 𝑋는 m X n의 행렬이 되어 곱셈식이 성립되지 않는다. 따라서 𝜭T를
다시 전치시켜 n X 1의 종벡터를 만든 후 𝑋 뒤에 곱하면 m X n 행렬과 n X 1 벡터의 곱이 성립된다. 이렇게하여
최종적으로 정리된 선형 회귀 비용 함수를 𝜭에 대해 미분한 도함수의 변화량은 다음과 같이 표현할 수 있다.


그림 18


그리고 경사 하강법의 STEP을 구하는 공식은 아래와 같다.


경사 하강법의 step 계산 공식그림 19


정리


여전히 수학은 어렵다. 나름 치환과 간략화에 주의하면서 각종 공식을 이리 저리 변형시켜가면서 이해하려고 하지만
깔끔하게 정리되지 않는 것은 어쩔 수가 없다. 일단 오늘의 소득이라면 행렬을 횡벡터를 요소로 갖는 종벡터로 생각
하면 조금 더 쉽게 이해되는 경우가 있다는 것 정도…


오늘의 주된 내용은 “핸즈온 머신러닝”의 166쪽에 있는 ‘식 4-7 경사 하강법의 스텝’에 대한 풀이였는데 사실 책을
보면 여전히 이해되지 않는 부분이 있다. 165쪽에 있는 식 4-5 비용 함수의 편도함수 식에서 j의 의미를 잘 모르겠다.
얼핏 봤을 때 특성의 수를 의미할 것 같은데…그리고 괄호 안의 x와 괄호 밖의 x가 다르게 표기된 부분도 잘 이해가
안간다. 이렇게 기호 하나가 추가되는 것만으로도 풀이가 안드로메다로 향하는 것을 보면 아직도 한참 멀었다…ㅠ.ㅠ


일단 내가 정리한 식도 얼추 앞뒤가 맞아 들어가는 것 같으니 우선 오늘의 정리는 마무리 하고 다음 포스팅에서는
여기서 정리한 식을 바탕으로 코드를 통해 배치 경사 하강법, 확률적 경사 하강법, 미니 배치 경사 하강법에 대해
알아보도록 하겠다.


피곤하다…ㅠ.ㅠ

블로그 이미지

마즈다

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



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

2019/01/28 - [Study/인공지능학습] - [머신러닝 Reboot] - 개념잡기 - 경사 하강법 3 - 경사 하강법의 종류


머신러닝 Reboot - 개념 잡기 : 경사 하강법 1 - 특성의 scale


새롭게 시작하는 머신러닝 학습은 기존에 진행하던 학습에서 장애가 되었던 용어와 공식에 대한 몰이해를 극복하고자
진행하려고 한다. 다시 말해 직관적으로는 이해가 가지만 논리적으로 설명할 수없는 개념을 논리적으로 설명 가능하도록
정리해보고자 하는 것이다.


따라서 전체적으로 연관성을 가지고 이어지는 내용이라기 보다는 단편적인 용어의 정의나 공식의 풀이를 중심으로 
하면서 관련 내용을 정리하는 방식으로 진행이 될 것이다. 


이렇게 정리할 대상은 주로 ’핸즈온 머신러닝’이라는 책을 읽으면서 이해가 안가는 부분들을 대상으로 풀이할 것이며
전체적인 순서 역시 ‘핸즈온 머신러닝’의 목차를 따를 것이다. 


들어가는 말


지난 시간에는 선형 회귀 분석의 비용함수로부터 가중치(𝜽 또는 W)의 최솟값을 한방에 알아낼 수 있는 정규방정식
대해 알아보았다. 미분 등 복잡한 계산이 필요 없고 학습률같은 하이퍼파라미터를 관리할 필요가 없으며 또 빠른 예측이
가능하다는 장점이 있지만 특성 수가 늘어남에 따라 속도가 많이 느려지는 단점이 있었다.


오늘은 정규방정식의 단점을 해결할 수 있는, 다시 말해 특성 수에 관계 없이 일정 수준의 성능을 보장해주는 
경사하강법애 대한 내용 중 특성의 스케일에 대해 알아보려고 한다. 


경사하강법은 대체로 특성의 스케일에 민감한 것으로 알려져 있으며 일반적으로 아래 그래프로 그 사실을 설명한다.


핸즈온 머신러닝 발췌핸즈온 머신러닝 발췌

오늘은 경사하강법이 특성에 민감하다는 것을 예제 코드를 통해 조금 더 직관적으로 설명을 하고자 한다. 
사실 논리적으로 증명을 하고싶었으나 역시 나의 실력으로는 역부족이었다. 이 내용과 위의 그래프를 이해하지
못하여 이 포스팅을 준비하는데 무려 3주가 걸겼다…ㅠ.ㅠ


게다가 내가 그간 얼마나 공부를 설렁설렁 했는 지 이번 기회에 알게 되었다. 그동안 나는 선형회귀의 비용함수와 
경사하강법을 동일시 하여 생각했던 것이다. 서로 다른 함수를 동일하다고 생각하고 분석하고 있었으니 답이
나올리가 있나…-.- 겨우 최근에야 경사하강법은 특정 함수(특히 convex 함수)의 최적값을 찾아낼 수 있는
일반적인 알고리즘이라는 말을 이해하게 되었다.


다시 말해 경사하강법은 선형회귀의 비용함수 뿐만 아니라 볼록(또는 오목)한 그래프가 그려지는 함수라면 어떤
함수이든 그 최저점을 찾아낼 수 있는 방법이라는 것이다.


지금부터 코드를 통해 이 내용을 간단히 살펴보자. 너무 간단해서 들어가는 말보다 본문이 짧을지도…-.-


Python 코드로 보는 경사 하강법


이 내용의 원본 소스 출처는 다음과 같다.


https://github.com/shuyangsun/Cost-Function-Graph


이 원본 소스 중 non-convex 함수들에 대한 내용은 제거 하고 convex 함수에 대한 내용만을 남겨 확인해보았다.


일반적으로 특성이 2개인 함수까지는 시각화(그래프로 표현)할 수 있다. 이 부분은 내가 처음 머신러닝을 공부한다고
정리를 하면서 다항로지스틱으로 넘어갈 때 꽤나 답답해 했던 부분이기도 하다. 특성이 2개인 경우까지는 시각화가
가능해서 직관적으로 이해를 할 수 있었는데 특성이 3개 이상 되니 복잡한 수식만으로는 도무지 이해가 가지 않는
것이었다…ㅠ.ㅠ


여전히 특성이 3개 이상인 경우는 이해가 힘들기 때문에 오늘은 특성이 2개인 케이스를 대상으로 설명을 해보겠다.


이 코드에서 사용할 함수는 비용함수는 아니고 f(a,b) = a^2 + b^2 이라는 함수이다. 이 함수가 표현하는 범위를
3차원 그래프로 그려보고 그 범위 안에서 경사하강법이 어떤 경로로 최저점을 찾아가는지 보여주는 것이 아래의
코드이다.


import numpy as np
import matplotlib.pyplot as plt
import math
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D

def f(a,b):
	return a**2 + b**2

먼저 필요한 라이브러리들을 import하고 경사하강법을 통해 최저점을 찾아낼 대상 함수 f를 정의했다.


def gradient_descent(theta0, iters, alpha):
	history = [theta0] # to store all thetas
	theta = theta0     # initial values for thetas
	# main loop by iterations:
	for i in range(iters):
		# gradient is [2x, 2y]:
		gradient = [2.0*x for x in theta] #함수 f(x,y)의 미분
		# update parameters:
		theta = [a - alpha*b for a,b in zip(theta, gradient)]
		history.append(theta)
	return history

history = gradient_descent(theta0 = [-1.8, 1.6], iters =30, alpha = 0.03)


다음으로 경사하강법을 함수로 정의하고 호출하여 그 결과를 history에 저장을 한다. 경사하강법의 다음 스텝을 결정하는
일반식은 다음과 같으며 이를 python 코드로 구현한 것이다.




경사하강법을 구현한 함수는 파라미터로 특성의 초깃값과 반복 횟수 그리고 학습률을 전달받는다. 이 코드에서 초깃값은 각 특성의 max에 가까운 값으로 정했다.


# f(x,y) = x^2 + y^2 함수의 그래프 그리기 fig = plt.figure(figsize=(20, 15)) ax = fig.gca(projection='3d') #plt.hold(True) a = np.arange(-2, 2, 0.25) b = np.arange(-2, 2, 0.25) a, b = np.meshgrid(a, b) c = f(a,b) surf = ax.plot_surface(a, b, c, rstride=1, cstride=1, alpha=0.3, linewidth=0, antialiased=False, cmap='rainbow')



주석된 내용처럼 f 함수가 표현하는 함수의 범위를 3차원으로 그려주는 코드이다. 특성 a와 b 모두 -2부터 2 사이의 값을
가지며 0.25씩 증가 하도록 값을 주었다. 함수가 a^2 + b^2이기 때문에 최솟값 0부터 최댓값 8까지의 그릇 모양으로
그래프가 표시된다.


a = np.array([x[0] for x in history])
b = np.array([x[1] for x in history])
c = f(a,b)
ax.scatter(a, b, c, color="r"); 

print(c)

plt.xlabel('Feature A')
plt.ylabel('Feature B')

plt.axis('equal')

plt.show()


이제 마지막으로 이전에 그려진 그래프 내에서 경사하강법을 통해 산출한 위치를 표시해준다. 특성이 2개이기 때문에
각각의 특성에 경사하강법을 적용한 결과를 그래프에 그려보면 최종적으로 아래와 같은 그래프를 볼 수 있다.



이 때 a = np.arange(-2, 2, 0.25)의 범위를 a = np.arange(-10, 10, 0.25)로 늘리게 되면 그래프의 형태가
오목한 그릇 형태가 아닌 u자 모양으로 휘어진 판자의 형태가 된다.



이런 상황에서는 가중치의 초깃값이 커질 수 있고 초깃값이 커지면 최솟값을 찾는데 그만큼 더 시간이 오래 걸리게 되며 이는 곧 특성값의 스케일 차이가 크게 되면 경사하강법의 성능이 나빠지게 된다고 볼 수 있는 것이다. 또한 내가 제대로 
이해하고 있는지 모르겠으나 이 그래프 표현만 놓고 보면 단지 두 개의 특성간에 스케일의 차이가 있을 때 뿐만 아니라 
두 특성의 스케일이 동일하더라도 그 규모가 커지면(예를들어 a와 b의 범위가 모두 10인 경우와 모두 100인 경우)
이 때 역시 경사하강법의 성능이 나빠져 더 많은 횟수를 진행해야 최솟값에 가까워지게 된다.


정리


앞서도 말했지만 이 부분을 이해하기 위해 장장 3주 이상이 걸렸다. 그러다가 위의 python 코드를 발견했고 처음 코드를
실행해봤을 때는 ‘유레카’를 외쳤지만 지금 다시 찬찬히 살펴보는 과정에서는 또 수많은 의문들이 일어나면서 내가 
제대로 이해한 것이 맞는지 알 수 없게 되었다…ㅠ.ㅠ 일단 직관적으로 생각했을 때 작은 수를 계산할 때보다 큰 수를 
계산할 때 더 많은 자원이 필요한 것은 당연한 것이니 특성의 스케일이 크면 그만큼 연산이 오래 걸린다고 보면 될 것이나
역시 완전한 이해는 되지 못한다.


더 나은 해법을 찾다가 contour라는 등고선 형태의 그래프를 그리는 방법이 있다는 것을 알아냈고 이 그래프가 위에
언급한 핸즈온 머신러닝의 그래프와 유사해서 더 설명하기가 좋지 않을까 생각했으나 실제 코드에 적용하는 방법을
몰라 이번 포스팅에서는 다루지 못했다. 시간 날 때 다시 정리를 해봐야겠다.


다음 시간에는 경사하강법의 3가지 종류(배치 경사하강법, 확률적 경사하강법, 미니 배치 경사하강법)에 대해 간단하게
정리해보겠다.

블로그 이미지

마즈다

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


인공지능 공부해보겠다고 설레발치기 시작한 것이 어언 2년여가 다되간다.

그간 나름 책도 좀 보고 동영상 강좌도 좀 보고...페이퍼나 논문은 하나도 안보고...ㅠ.ㅠ


그간 읽은 책들과 읽는 중인 책 그리고 읽기 위해 사놓은 책을 좀 정리해보면


읽은 책

텐서플로우 첫걸음
골빈해커의3분 딥러닝
머신러닝 워크북
밑바닥부터 시작하는 딥러닝

읽는 중인 책

핸즈 온 머신러닝
머신러닝 탐구생활


읽다가 보류한 책

강화학습 첫걸음
기초 수학으로 이해하는 머신러닝 알고리즘
처음 배우는 딥러닝 수학
프로그래머를 위한 선형대수


사놓기만 한 책

머신러닝 실무 프로젝트
딥러닝의 정석
러닝 텐서플로우
머신러닝 딥러닝 실전 개발 입문




하지만 공부를 해 가면 해 갈수록 궁금한 것은 더 많아지고 이제는 과연 내가 뭔가를 이해 하고는 있는 건가 하는 생각에

자괴감이 들기 시작했다. 똑같은 내용을 공부하고 있는데 볼수록 새로운 느낌?


결국 자괴감을 이기지 못하고 처음부터 다시 시작하는 길을 선택했다. 처음 시작부터 이해하지 못하고 넘어간 부분들을

차근차근 정리하고, 이해하고 넘어가야 할 것 같아서...(그런 의미에서 현재 읽고있는 2권의 책은 꽤 도움이 되는 것 같다)


특히나 전반적으로 내용을 이해하지 못하게 하는 주된 이유 중 하나가 어려운 용어들과 복잡한 공식들이기도 하고 

머신러닝의 기초가 되는 선형 회귀 등을 제대로 이해하지 않고 겁대가리 없이 덥썩 딥러닝으로 직행한 무모함도 충분히 

일조를 했기에 다시 처음부터 하나 하나를 정리하는 것으로부터 시작하기로 했다. 그리고 


패배적인 자기 만족일지는 모르겠으나 어차피 이 공부는 내가 이 분야의 전문가가 되기 위한 것이라기 보다는 나의 지적 만족을

위한 것이니 쉬엄쉬엄 간들 어떠랴 싶다. 태공망 여상은 나이 80에 주문왕을 만나 그 재능을 펼치기 시작했다는데 그렇다면

나에게는 아직 30년이란 시간이 남은 것 아닌가(뜻밖의 나이 공개가...ㅠ.ㅠ)


느려도 황소 걸음이랬으니 차분하게 한걸음 한걸음 가보자.

그 시작은 정규 방정식이다.


정규방정식

사실 그동안 비용함수를 최소화 하는 가중치를 찾기 위한 방법으로 경사하강법만을 알고 있었는데 이번에 핸즈 온 머신러닝을

읽으면서 처음 정규방정식이란 것이 있다는 것을 알게 되었다.


문제는 이 정규방적이라는 것이 행렬식으로 표현이 되어있어 문돌이의 사고방식으로는 이 것이 어떻게 경사하강법과 동일한

기능을 하게 되는지 이해가 가지 않는 것이었다. 그래서 새로운 시작의 첫 출발을 단순 선형회귀의 비용함수로부터 정규방정식을

도출하는 과정을 정리해보고자 한다.



선형 회귀 비용함수로부터 정규방정식 도출하기


복습

선형회귀의 가설함수 식에서 편향을 제거하자. 방법은 그냥 b = 0으로 초기화 하는 것이다.




    •비용함수도 다시 한 번 확인하자.



















사전 확인1 - ∑를 행렬로



∑ 로 표현되는 제곱의 합은 그 수들을 요소로 하는행렬과그 행렬의 전치행렬의 곱과 같다(복잡하니
1
행짜리 행렬로 확인해보자).




















사전 확인2전치행렬의 성질


     •전치행렬은 다음과 같은 성질이 있다.




























우선 cost함수는 W에 대한 함수이므로 함수 표기를 바꿔보자(함수명MSE는 최소제곱법의 영문 표기인 Mean Square Error의 약어이다).

이제 명확하게 이 함수는 x와 y에 대한 함수가 아니라 W에 대한 함수로 보일 것이다.









함수는W에 대한 함수인데 정작 함수 식에는 W가 안보이니 내부 함수도 원래대로 치환하자.






















사전 확인한 내용을 상기하면서 번 식으로 변환해보자
전치행렬의 성질에 따라 번 식으로 전개할 수 있다.
W를 포함한 식들을 다시 정리하면 번 식이 된다.
다시 한 번 전치행렬의 성질에 따라 식을 전개하면 번 식이 된다.
이 변형은 최초의 시그마 식을 전개해서 진행해도 동일한 결과가 나온다.






최종 정리된 식은 과 같고 이제 이 값을 미분하여비용함수가 최솟값을 갖는 W를 찾을 것이다.

비용함수가 최솟값을 갖기 위해서는 비용함수를 미분한 값이 0이 되어야 한다.
미분 과정을 명확하게 하기 위해 식을 한 번 정리해 주자(식 ).주의할 것은 W에 대해 미분한다는 점이다.
행렬 A에 대해 자신과 자신의 전치행렬과의 곱은제곱과 같다고 했다.그리고 전치행렬의 성질에 따라W와 X의 곱의 전치행렬은 X의 전치행렬과 W의 전치행렬의 곱과 같다(W와 X의 순서가 바뀜에 주의).




이제 거의 다 왔다.
미분한 함수는 식 와 같고 이제 
거추장스러운1/m도 없앨 수 
있다(사실 진작에 1/m을 없애고
보다 깔끔하게 식을 전개할 수도 
있었으나 나같은문돌이는 갑자기 
저런거 하나 없어져도 극도의
멘붕에 시달릴 수 있기에 끝까지 
가져왔다-.-).










최종 미분식을 W에 대해 정리해보자.
    •이렇게 해서 단순 선형 회귀의 비용함수로부터 정규방정식을 도출해보았다.













선형 회귀 비용함수로부터 정규방정식 도출하기2



정규방정식은 다른 형태로도 
도출할 수 있다우선 최초의 
식을 전개해보자.














이후 전개한 식을 W에 대해 미분한다.














최종 정리한 후 시그마를 
행렬로 변환해보자














하지만 아직 이해하지 못한 것이 하나 있다.
가설함수에서 편향을 제거하지않고 WX + b의 형태로 이 과정을 진행하게 되면 최종 정규방정식은 좌측과 같이 나온다.이 것이 앞서 도출해본 정규방정식과 동일한 식이란 것을 문돌이의 두뇌로는 이해하기 힘들다.ㅠ (분모와 분자 각각 - 뒤에 붙어있는 값들은 대체 어쩔...ㅠ.ㅠ)






일단 정규방정식은 좌측의 식으로 

알아두자

정규방정식은 행렬식으로 경사
하강법에 비해 많은 연산량이 필요
하지도 않고 학습률 설정 등
골치아픈 하이퍼파라미터의 
설정을 신경쓰지 않아도 된다.
하지만 행렬 연산이다보니 특성의 
수가 늘어나면 계산속도가 많이 
느려지게 된다.다만 샘플 수에 
대해서는 선형적으로 비례한다고 
한다.
또한 정규방정식으로 학습된 선형 
모델은 예측이 매우 빠르다고 한다
(핸즈 온 머신러닝)



정리


이렇게 해서 새롭게 시작하는 인공지능 학습의 첫 단추를 꿰었다. 하지만 이렇게 차근차근 분석을 하면서도 여전히

어떤 부분에 대해서는 완전하게 이해하지 못한 채 그저 직관적인 이해로 두루뭉술하게 넘어가고 있는 상황이다.

사실 이러한 상태가 가장 환장하는 상태이다. 전체적인 흐름은 대충 이해가 가는데 어떤 디테일한 부분에서

뭔가 막혀있는 듯한 느낌...



첫 대상인 정규방정식도 정리를 하고 보니 아직은 부족한 상태라는 것을 알게 되었다.

이러한 과정이 큰 도움이 되는 것 같다.


아무튼 이번에는 용어 하나, 공식 하나도 집중해서 보면서 차근차근 진행을 해나가 보자. 

머신러닝 reboot는 이제 시작이다!




블로그 이미지

마즈다

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

Elasticsearch








Elasticserach에 Excel 데이터 입력하기 - JAVA API와 몇가지 설정


지난 시간에는 간단에서 Spring boot를 설정하면서 확인해야 했던 부분들을 중심으로 정리를 하였다.
일단 웹 프레이임워크가 갖추어졌으니 이제 시스템을 만들어가는 일만 남았다. 물론 파일 업로드, 엑셀 파싱 등의
기능들도 필요하지만 역시 가장 중요한 것은 Elasticsearch를 이용할 수 있게 해주는 API일 것이다.


지난 포스팅에서도 언급했지만 이미 Spring에는 Elasticsearch와 관련된 프로젝트가 있다. 하지만 안타깝게도
Spring Data Elasticsearch 프로젝트의 최신 버전도 아직은 Elasticsearch의 6.x 버전을 지원하지 못한다
(내가 이 작업을 시작하면서 검색했을 때는 Elasticsearch 2.4까지만 지원한다고 했었는데 그새 지원 버전이 조금
올라가긴 했다).


그래서 별도의 API 라이브러리를 참조하여 작업을 진행하였다.
물론 많은 API들이 존재하지만 오늘은 간단하게 Index 생성과 관련된 내용들만 살펴보도록 하겠다.


Client 연결


Index를 생성하기 위해서는 우선 Elasticsearch cluster의 노드에 접근을 해야 한다. API에서는 Client 인스턴스를
생성하여 연결한다. Client 클래스는 몇가지가 있는데 Low Level REST Client로 RestClient 클래스를 사용할 수
있고 이 RestClient를 wrapping한 RestHighLevelClient 클래스는 High Level REST Client라고 부른다.
여기에 다시 Indices(Elasticsearch 내부에서 관리하는 index들을 indices라 부른다)접근하기 위해
RestHighLevelClient를 한번 더 wrapping한 IndicesClient가 있고, 이 외에 TransportClient가 있다.



그런데 이 TransportClient는 조금 독특하게 HTTP가 아닌 TCP 프로토콜을 이용하며 따라서 사용하는 포트도
REST Client들이 기본 값을 기준으로 9200포트를 이용하는데 반해 TransportClient는 9300 포트를 이용한다.


TransportClient는 Elasticsearch 7.0에서 deprecate 예정이며 8.0에서는 제거될 것이라고 한다. 
TransportClient에 대한 자세한 내용은 아래 링크를 참조하도록 하자.


https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/client.html


처음에는 TransportClient를 이용하느라 고생을 좀 했다. 그러다가 deprecate 예정이라는 정보를 보고는 미련 없이
REST Client로 바꾸어 사용하기로 했다.


기본적으로 High Level REST Client인 RestHighLevelClient 클래스를 사용하게 되겠지만 그 전에 Low Level 
REST Client에서 중요하게 짚고 넘어가야 할 부분이 있다(어차피 RescClient의 builder를 통해 생성하니 당연한
이야기이겠지만...). 바로 다음 링크에 있는 내용들 때문이다.


https://www.elastic.co/guide/en/elasticsearch/client/java-rest/6.2/java-rest-low.html


내용을 간략하게 보자면 Low Level REST Client에는 load balancing이라든지 failover, 장애 노드에 대한
패널티 부여, 그리고 옵션 사항이지만 전체 클러스터에서의 노드 찾기 등 클러스터를 관리하기 위해 필요한 많은
기능들이 구현되어있다. 특히 load balancing의 경우 clietn 생성시 파라미터로 전달된 각 노드들을 round-robin
방식으로 접근하여 rquest를 보내게 된다. 자세한 내용은 아래 링크에서 확인할 수 있다.


https://artifacts.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client/6.2.3/org/elasticsearch/client/RestClient.html


마지막으로 client 연결 시 애를 먹었던 부분이 X-pack을 설치한 후 Elasticsearch 접근 시 계정 인증이 필요하게
되었는데 이에 대한 처리를 하느라 고생을 좀 했다. 이 부분은 샘플 코드로 설명을 대신한다.


public static RestHighLevelClient newRestHighLevelClient() {
 // X-pack 설치 시 아래와 같이 자격 증명을 해주어야 한다. user와 password에 각각 X-pack을 통해 설정한
 // ID와 비밀번호를 입력하면 되는데 ID는 보통 elastic이고 비밀번호는 자동 생성된 값이다. 
	final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
	credentialsProvider.setCredentials(AuthScope.ANY,
		        new UsernamePasswordCredentials(user, password));
		
	RestHighLevelClient client = new RestHighLevelClient(
		RestClient.builder(
			new HttpHost(hostData1, Integer.valueOf(httpPort), "http"))
		        .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
		            @Override
		            public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
		                httpClientBuilder.disableAuthCaching(); 
		                return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
			}
	 }));

	return client;
}



API 구현


아직까지는 Excel 데이터를 Elasticsearch로 입력하는 기능만을 구현하였기에 실제로 사용하는 API는 Create Index API(Index 생성 시 사용)와 Index API(Index를 이용하여 데이터를 입력하는 작업에 사용) 뿐이다.


Elasticsearch의 JAVA API들은 모두 2가지 종류가 있는데 바로 synchronous와 asynchronous 방식이다.
익히 알고 있듯이 synchronous는 요청을 한 후 그 결과를 리턴받은 후 프로세스가 진행되지만 asynchronous의
경우 요청후 바로 다음 프로세스가 진행되며 요청한 프로세스에 대한 결과는 별도로 구현된 listener에 의해 처리된다.
따라서 asynchronous API를 구현하는 경우에는 listener를 구현한 후 이 listener를 파라미터로 전달해야 한다. 


나같은 경우 처음에 asynchronous 방식을 알지 못한 상태에서 Elasticsearch API들을 모두 util성 클래스에
static 메소드로 구현을 했는데 아무래도 한 번 뒤집어 엎어야겠다...ㅠ.ㅠ


각 API 구현은 아래 링크의 예제를 거의 그대로 사용하였다.


https://www.elastic.co/guide/en/elasticsearch/client/java-rest/6.2/java-rest-high-create-index.html

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/6.2/java-rest-high-document-index.html


전체적인 흐름은 우선 기존에 생성된 Index가 없는 경우에는 새로운 Index를 생성하도록 하고 이미 생성된 Index가
있는 경우에는 기존 Index와 type을 select 박스를 통해 선택하여 데이터를 입력하거나 아니면 새로운 Index를
생성하는 작업부터 시작하는 것을 선택하도록 하였다.


새로운 Index 생성 시에는 다음과 같은 파라미터를 입력받는다(아직 validation 체크 기능은 없다...-.-).


  1. Index 명
  2. alias
  3. type
  4. shard 수
  5. replica 수
  6. mapping 정보


Index가 생성되고 나면 생성된 Index들과 type들을 선택하여 데이터를 업로드 하는 화면으로 전환된다.
파일 업로드 기능을 통해 엑셀 파일을 업로드 하면 되는데 파일만 업로드한 후 나중에 데이터를 입력할 수도 있고
파일 업로드가 끝나면 바로 데이터 입력이 시작되도록 할 수도 있다.


문제는 데이터의 양이다.


이전 포스팅에서 말한 것처럼 현재 작업을 하려는 데이터는 대략 18개의 열과 50만개의 행으로 구성된 엑셀 파일이다.
가급적이면 다른 전처리(데이터 정제 작업 제외) 없이 한 번에 입력하기를 원하지만 웬만한 시스템이 아니면 입력 중
OOM을 맞닥뜨려야만 했다(물론 개발자 PC로써도 사양은 좀 낮았다...ㅠ.ㅠ).


다수의 데이터를 한 번에 입력하는 작업인만큼 bulk API를 이용하여 작업을 하였다. 처음에는 전체 데이터를 입력
하도록 해보았으나 Elasticsearch에서 timeout이 걸리고 말았다. kibana로 확인해보니 데이터는 모두 입력 된 것
같은데 정상적으로 종료 처리가 되지 않았다. 결국 현재 내 시스템에서 안정적인 입력 량인 10만 건 단위로 나누어
bulk request를 보내도록 구현하였다. 이렇게 하니 50만 건 입력하는데 대략 3분 전후가 걸렸다.


Elasticsearch 설정


하지만 API 구현쪽에서만 처리한다고 모든 것이 해결되는 것은 아니었다.
사실 개발자로서 굳이 알아야 하나 하는 생각도 들긴 하지만 그래도 어렵지 않은 내용이니 아주 얕은 수준에서는
튜닝(이라고 말하기는 부끄럽지만...-.-)은 해주는 것이 좋을 것 같았다. 유일하게 해준 작업은 jvm.options 파일에서
Xms와 Xmx를 수정한 것이다. 파일 경로는 ${ELASTIC_HOME}/config/jvm.options이다.


다만 이렇게 heap size를 설정할 때 주의할 사항이 있는데 일단 heap size가 커지면 가용성은 좋아지지만 GC 수행
시간이 오래 걸리는 단점이 있고 Xmx의 경우 OS의 커널 시스템이 사용할 부분을 고려하여 전체 메모리의 50%를
넘지 않도록 권고하고 있다. 그밖에 compressed ordinary object pointers라는 조금은 전문적인 내용에 대한
권고사항이 있는데 이는 링크로 대신한다.


https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html


위 링크에 보면 heap size를 jvm.options 파일이 아닌 시스템 환경변수에 설정하는 방법도 나와있으니 참고하자.


다음으로는 Web을 통한 접근과 관련된 설정으로 시스템 구현 후 뭔가 허전하여 간단하게 Elasticsearch의 몇가지
정보를 확인할 수 있는 버튼을 추가하였다. 이 작업은 다음 번에 포스팅하겠지만 jQuery를 통해 직접 REST API를
호출하도록 하였는데, 이 때 몇가지 오류가 발생을 하였다. 웹에서 접근시 발생하는 오류를 막기 위해서는 설정 파일인
elasticsearch.yml 파일에 다음의 내용을 추가해주어야 한다.


http.cors.enabled: true
http.cors.allow-origin: "*"
http.cors.allow-credentials: true
http.cors.allow-headers: "X-Requested-With, Content-Type, Content-Length, Authorization"
http.cors.allow-methods: OPTIONS, HEAD, GET, POST, PUT, DELETE


당장에 운영 시스템을 관리할 것이 아니라면 그냥 이정도 설정이면 충분할 것이다.


정리


Elasticsearch의 API는 워낙 간단하게 구현되어있어서 달리 설명할 것도 없을뿐더러 오히려 공식 홈페이지에 더 잘
설명이 되어있기에 굳이 이 자리에서 다시 설명할 필요를 못느낀다. 실제로 구현한 내용도 몇가지 시스템에 특화된 
내용을 제외하고는 공식 홈페이지의 예제 snippet를 그대로 Copy & Paste한 수준이다.


어쨌든 50만건의 데이터를 파일 업로드 한 번으로 3분정도의 시간에 Elasticsearch로 입력할 수 있게 되어 나름
만족스럽다. 다만 경력 18년차의 코드로 보기에는 너무 형편없는 코드를 공개해야 하나 생각하니 부끄부끄할 뿐...*^^*


잠깐 삼천포를 좀 들르자면 사실 현재 실질적으로 가장 필요하고 또 공부하고 싶은 부분은 테스트 코드에 관한
부분이다. TDD든 아니면 단순 Unit Test든...테스트 코드도 없는 소스를 공개하려니 뭔가 알맹이는 홀랑 까먹고
껍데기만 올리는 기분이랄까? (거꾸로인가?)


기본적인 시스템 구현 내용은 이정도에서 마무리하고 마지막 포스팅에서는 짧게나마 jQuery에서 REST API를
호출하는 부분을 살펴보고 만들어진 시스템에 대해 설명하고 마치고자 한다. 지금은 형편없고 특정 목적을 위해
만들어진 시스템이지만 평생 목표로 다듬어가야겠다.

블로그 이미지

마즈다

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










Elasticserach에 Excel 데이터 입력하기 - 기본 설정과 적용 라이브러리


올해에는 선택과 집중을 분명히 하기로 했는데…제 버릇 개 못준다더니…어김없이 또 여기저기를 들쑤시기 시작했다…-.-


Hadoop이니 Hbase니 Spark니 잔뜩 설치해놓고는 다시 Elasticsearch에 관심을 갖게 된 것이다. 어떤 것인지 한 번
설치나 해보자고 시작한 것이 쉬운 설치 방법과 사용법에 혹해서 더 깊은 내용을 알고 싶어진 것이다. 마침 분석해보고
싶은 데이터가 있어 이참에 한번 Elasticsearch를 이용해보자고 마음먹었다.


현재까지 진행된 작업은 spring boot 기반의 웹 시스템을 통해 Excel파일을 업로드하면 이를 파싱하여 
Elasticsearch로 입력하고 업로드된 파일들은 별도로 관리 가능하도록 만든 것이다. 앞으로 3차례에 걸쳐 이 개발 
과정을 정리해보도록 하겠다.


발단


2월 경…새 집으로 이사를 좀 하는게 어떨까 싶어 새 집을 물색하였다. 그리고 기왕지사 옮기는 것, 가급적이면
앞으로 집 값도 좀 올라 주면 좋을 것 같다는 생각이 들었다. 하지만 부동산이라고는 쥐뿔도 모르는 상태에서 어디
그게 쉬운일이던가…그냥 새 집은 새 집이고 마침 어떤 데이터로 빅데이터나 AI를 공부해보나 하던 참이라 부동산
데이터를 사용해보면 어떨까 하는 생각이 들어 공공 데이터 포털에서 부동산 관련 데이터를 모으기 시작했다.


처음 모은 데이터는 1996년 부터 2017년까지의 공시지가 데이터였다. 그리고 첫 난관이 시작되었다.
데이터는 모았는데 이 데이터를 어떻게 Elasticsearch에 넣어야 할지 방법을 몰랐던 것이다. 그렇게 Excel
데이터를 Elasticsearch로 입력하는 방법을 찾다가 가장 적절해 보이는 솔루션으로 찾은 것이 excelastic이라는
vert.x 기반의 stand alone 애플리케이션이었다.


그런데 말입니다…안타깝게도 이 애플리케이션도 문제가 있었다. 클라이언트 PC 및 Elasticsearch가 설치된 서버의
사양과도 관계가 있겠지만 10만 건 정도 입력을 시도하면 여지없이 OOM이 발생하여 정상 입력이 되지 않았다.
시행착오를 거쳐 확인한 안정적인 입력 건수는 약 5만 건 정도였다. 공시지가 데이터가 년도당 약 50만 건의 데이터가
있는데 이 파일을 Elasticsearch로 입력하려면 파일을 10개로 쪼개는 작업을 해주어야 한다는 말이다. 이 작업 조차
웬만한 PC에서는 쉽지 않다. 내 맥미니가 i5(2.5GHz)에 RAM 16Gb인데도 50만 건 들어있는 Excel 파일을 열어서
5만 건씩 10개로 쪼개다보면 버벅거리기가 일쑤였다.


그래서 목마른 놈이 우물을 판다고…직접 하나 만들기로 했다. 그리고 기왕 만드는 김에 이것저것 기능을 좀 추가해보자
했는데 마침 또 회사에서 KMS를 Elasticsearch 기반으로 만들면 어떻겠냐는 이야기가 나와 겸사겸사 함께 진행해
보기로 했다. 


관련 기술들


이 작업에 사용된 기술들은 다음과 같다.


  1. Spring boot 2.0.0
  2. jQuery + bootstrap (UI는 AdminLTE라는 오픈소스 사용)
  3. Elasticsearch 6.2.1 ( + X-Pack)
  4. MySQL 5.6.38
  5. Spring Tool Suite 3.9.2


각 기술의 세세한 부분보다는 작업을 진행하면서 어려움을 겪었던 부분들 혹은 편리했던 기능들에 대한 팁 수준의
정리를 진행하고자 한다.


Spring boot로 삽질하기


작년까지는 현재 일하는 곳의 업무 시스템 개발을 위해 Spring으로 2차례 정도 가벼운 웹 시스템을 개발한 적이 있다.
그 과정에서 Spring boot를 개인 프로젝트에 사용한 적은 있지만 잠깐 건드려보다가 방치되고 말았다. 그리고는 이번에
다시 Spring boot를 이용하기로 했다. 마음같아서는 마이크로서비스에 대한 공부도 곁들여 하면서 구현을 해보고
싶었으나 너무 학습의 범위가 넓어질 것 같아 그냥 Spring을 쓰듯 Spring boot를 쓰기로 했다…-.-


STS에서 서버(Tomcat) 사용하기


경력에 걸맞지 않은 초보적인 실수가 참 많다…ㅠ.ㅠ 늘 겪는 실수 중 하나가 프로젝트를 생성한 후 STS에서 바로
서버 연결하여 실행하는 부분인데 이번에도 여지없이 프로젝트를 생성하고 나니 프로젝트의 서버 설정이 뭔가 이상하다.


서버를 선택할 수 있는 화면이 나오지 않고 “This project is not associated with any server”라는 문구가 보인다.


이 것은 프로젝트 생성 처음 설정에서 packaging 항목을 jar로 선택한 결과이다. jar로 선택한 경우 Stand alone
프로젝트로 판단하여 외부 서버와 연결하는 설정이 나타나지 않는다. 




packaging을 war로 하면 서버 설정 창에서 Tomcat 등의 was와 연결이 가능해진다.




Security 사용


프로젝트를 생성한 후 기본적인 REST API를 구현하였고 테스트를 위해 STS 내에서 Tomcat을 구동시켜 브라우저를
통해 URL을 호출하여보았다. 그런데…난데없이 계정 입력창이 뜨는 것이 아닌가?


Spring security


확인 결과 이 것은 프로젝트 생성 시 의존성 설정 부분에서 Security를 체크했기 때문이었다. 



Security를 체크함으로 해서 많은 부분에 영향을 받았다. 파일 업로드, iframe 사용 등에서도 문제가 생겨 확인해보면
모두 Security 관련 설정 때문이었다. 계정 로그인 처리, 파일 업로드 문제, iframe 사용과 관련된 각각의 내용들을
모두 확인 후 적용하였으나 아직은 잘 모르는 부분이 많기에 아래 코드로 Security는 bypass하는 수준에서 적용을
마무리 하였다.


@Override
public void configure(WebSecurity web) throws Exception {
	// TODO Auto-generated method stub
	super.configure(web);
		
	web.ignoring().antMatchers("/**");
}


위 코드는 JAVA config 설정을 이용할 경우 WebSecurityConfigurerAdapter를 상속받은 config 클래스를 
생성하여 코딩하면 된다. Spring (boot)에서 Security를 사용하는 방법은 아래 링크를 참조하였다.


https://spring.io/guides/gs/securing-web/


DB 연결 설정


Spring을 이용하는 경우에는 보통 다음과 같은 과정을 거쳐 DB를 연결하고 CRUD 작업을 진행하였다.


  1. DataSource 처리를 위한 Config 클래스 생성
  2. 필요에 따라 properties 파일에 DB 연결정보 추가
  3. Service 인터페이스와 그 구현 클래스 생성
  4. Controller 클래스에서 의존성 주입을 통해 Service에 선언한 CRUD 메서드를 이용하여 작업


처음엔 이 과정만 생각하고 진행하다가 꽤 심한 삽질을 했다. 내가 ORM과 관련하여 JPA를 사용하도록 설정한 것을
깜빡 한 것이다. JPA를 이용할 경우에는 1번과 3번의 과정이 필요없다. application.properties에 DB 연결 설정만
등록하면 바로 DB와 연결이 되며 Entity 클래스와 Repository 인터페이스를 구현하여 사용하면 된다.


JPA를 통한 MySQL 연동은 아래 두 곳의 사이트에서 도움을 받았다.


https://docs.spring.io/spring-data/jpa/docs/2.0.5.RELEASE/reference/html/

https://www.callicoder.com/spring-boot-rest-api-tutorial-with-mysql-jpa-hibernate/


Elasticsearch API 사용


Spring 프로젝트 중에도 Spring Data Elasticsearch라는 관련 프로젝트가 있다. 관련 링크는 아래와 같다.


https://projects.spring.io/spring-data-elasticsearch/


하지만 내용을 살펴보면 알겠지만 가장 최신 버전의 릴리즈도 Elasticsearch 5.5.0까지만 지원을 한다.
아래 링크를 보면 Spring Data Elasticsearch의 각 버전과 그 버전에서 지원하는 Elasticsearch 버전이
정리되어 있다.


https://github.com/spring-projects/spring-data-elasticsearch


하지만 나는 이미 Ealsticsearch 6.2.1 버전을 설치한터라 안타깝게도 Spring Data Elasticsearch는
사용하지 못하고 별도로 6.2 버전대의 라이브러리를 pom.xml 파일에 다음과 같이 추가하였다.


<dependency>
	<groupId>org.elasticsearch</groupId>
	<artifactId>elasticsearch-core</artifactId>
	<version>6.2.2</version>
</dependency>
<dependency>
	<groupId>org.elasticsearch</groupId>
	<artifactId>elasticsearch-hadoop-mr</artifactId>
	<version>6.2.2</version>
</dependency>
<dependency>
	<groupId>org.elasticsearch</groupId>
	<artifactId>elasticsearch-spark-20_2.11</artifactId>
	<version>6.2.2</version>
</dependency>
<dependency>
	<groupId>org.elasticsearch.client</groupId>
	<artifactId>elasticsearch-rest-high-level-client</artifactId>
	<version>6.2.2</version>
</dependency>
<dependency>
	<groupId>org.elasticsearch</groupId>
	<artifactId>elasticsearch-hadoop</artifactId>
	<version>6.2.2</version>
</dependency>
<dependency>
	<groupId>org.elasticsearch.client</groupId>
	<artifactId>elasticsearch-rest-client</artifactId>
	<version>6.2.2</version>
</dependency>
<dependency>
	<groupId>org.elasticsearch</groupId>
	<artifactId>elasticsearch</artifactId>
	<version>6.2.2</version><!--$NO-MVN-MAN-VER$-->
</dependency>
<dependency>
	<groupId>org.elasticsearch.client</groupId>
	<artifactId>transport</artifactId>
	<version>6.2.2</version>
</dependency>
<dependency>
	<groupId>org.elasticsearch.plugin</groupId>
	<artifactId>transport-netty4-client</artifactId>
	<version>6.2.2</version>
</dependency>
<!-- Elasticsearch 설치 후 X-Pack을 설치했기 때문에 추가 -->
<dependency>
	<groupId>org.elasticsearch.client</groupId>
	<artifactId>x-pack-transport</artifactId>
	<version>6.2.2</version>
</dependency>


현재 상태에서 모든 라이브러리가 다 필요한 것은 아니지만 추후 Hadoop이나 Spark와의 연동도 염두에 두고 있기에
그냥 함께 설치하였다.


Excel parsing


Excel 파일을 분석하는 것은 가장 많이 사용하는 POI 라이브러리를 사용하였다.
하지만 일반적으로 알려진 사용법으로는 벌써 이 단계에서 50만건을 처리하는데 OOM이 발생하였다.
해결책을 찾아야 했다. 게으른 개발자의 숙명으로 직접 코딩을 해야 하는 방법보다는 누군가 만들어놓은 라이브러리가
없을까를 우선하여 구글링을 하였다…ㅠ.ㅠ


역시나 세상에는 나같은 불쌍한 중생을 거둬 먹이는 천사같은 분들이 늘 존재한다. 마침 내가 필요로 하는 용도의
라이브러리가 똭! 눈에 띄였다. 이 라이브러리를 설치하여 사용하니 50만건을 OOM 없이 빠른 속도로 parsing해
주었다. 라이브러리는 pom.xml에 다음과 같이 추가하면 된다.


<dependency>
    <groupId>com.monitorjbl</groupId>
    <artifactId>xlsx-streamer</artifactId>
    <version>1.2.0</version>
</dependency>


라이브러리 소스는 아래 링크에서 확인할 수 있다.


https://github.com/monitorjbl/excel-streaming-reader


정리


가장 첫 단계로 Spring boot 및 java 프로그래밍 관련된 내용을 가지고 포스팅을 해보았다. 주 목적이 Spring boot나
java 프로그래밍이 아니므로 부족한 내용이 많겠지만 이와 관련해서는 더 자세하고 정확한 설명이 있는 사이트나 
블로그를 참조하는 편이 더 나을 것이다.


자꾸 요상한(?) 것들에 관심을 가지게 되면서 최종 롤인 iOS 개발자로서도 또 그 이전까지의 롤이었던 java개발자로서도
점점 역량이 떨어지는 것 같다…ㅠ.ㅠ 드문드문 Spring 또는 Spring boot를 접하다보니 별 것 아닌 일로 시간을 
허비하기 일쑤다. 그래도 불행 중 다행이랄까? 요즘은 워낙 양질의 자료를 다양한 경로로 쉽게 구할 수 있다보니 그럭저럭
이정도나마 하고 있지 않나 싶다.


다음 포스팅에서는 Elasticsearch의 JAVA API와 관련된 내용을 조금 더 상세하게 살펴보고 또 Elasticsearch의
설정 몇가지를 함께 알아보겠다.


전체 소스를 공유하려고 했는데 오늘 문득 프로젝트명, github의 레포지터리명, 프로젝트의 패키지명 등이 맘에 들지
않아 전체적으로 수정을 하고 있어 당장에는 힘들 것 같다. 이 시리즈가 마무리되는 시점에(이 글 포함 3개의 포스팅으로
계획 중) 전체 소스를 공유하도록 하겠다.

블로그 이미지

마즈다

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