Linear Actuator를 이용한 2족 보행 로봇 제작 ~ #2


지난 글이 7월 12이고 그 지난 글이 6월 19일이었다.
지난 글은 3D 프린터 A/S 관련 글이었으니 로봇 제작과 관련된 글은 달 수로 2달이 지났다.
출력 시간과 여러가지 여건상 글 작성이 늦어질 것을 예상하긴 했으나 생각 이상으로 글 작성 간격이
길어지고 있다...ㅠ.ㅠ


이래서는 맥이 끊어질 것 같아 지금 새로운 방법을 모색하고 있다.


Scratch Building


Scratch Building의 정의는 이렇다.


Scratch Building란 상업용 킷을 이용하지 않고 처음부터 일상의 각종 재료를 이용하여 축적 모형을 제작하는 방법이다. — Wikipedia : https://en.wikipedia.org/wiki/Scratch_building


메이커 코스프레를 하고 있다보니 관련 분야에 대한 지식들이 하나 둘씩 모인다.
현재 제작 중인 로봇에 참고하기 위해 주로 pinterest와 youtube를 통해 mech, war robot, robot 등으로 검색을
하다보면 대체로 컨셉 아트에 해당하는 2D 일러스트나 3D 모델링들이 검색된다. 그런데 가끔 조금 독특한 형태의 로봇
모형들이 검색되는 경우가 었다. 꽤나 사실적이면서도 건담류의 기성 상용 제품들과는 달리 매우 거칠고 투박한, 그리고
매우 유니크한 로봇들...


물론 Scratch Building이 로봇에만 국한되는 제작법은 아니지만 내가 하고자 하는 것이 로봇 제작이기에 로봇을 위주로
찾게 되었다.






제작 영상 : https://youtu.be/ftmtvu9dB2s


한편으로는 Kitbashing이라는 제작법도 있다.

Kitbashing이란 서로 다른 기존 상용 모델 제품을 조합하여 새로운 축적 모형을 제작하는 방법이다.
-- Wikipedia : https://en.wikipedia.org/wiki/Kitbashing

사실 건프라를 이용하여 기존 상용 제품과는 다른 모델들을 만들어 내는 것은 엄격하게 말해 Kitbashing에 속하지만
넓은 범위에서 Scratch Building이라는 용어를 더 자주 사용하는 것 같다. 역으로 Scratch Building이라고 하지만
상용 밀리터리 모델 킷을 이용하여 모형을 만드는 경우도 많기는 하다.


중요한 것은 3D 프린터를 이용하면 수작업으로 표현하기 어려운 형태를 쉽게 만들어낼 수 있다는 장점이 있지만 출력
시간으로 소모되는 시간과 또 3D 프린터라고 해서 만능은 아니기에 이러한 Scratch Building 기술을 익혀놓는다면
좋은 시너지 효과가 생길 것으로 생각된다.



즉, 기본적인 형태는 단순한 모양의 입체로 3D 프린터로 출력하여 골격을 갖추고 디테일한 디자인은 scratch building
으로 보강을 하는 것이다. 왕년에 3T 두께의 하드보드를 커터로 수도 없이 잘랐던 내가 뭘 두려워하랴...-.-


로봇 제작 재개!


일단 이전 작성한 글에서 3D 프린터 A/S 후기를 올렸었다. 그런데...


A/S 받고 온지 3일만에 다시 문제가 생겼다. 이미 한 차례 방문으로 우리 집에서 그 곳이 얼마나 먼지를 학습했기에
마음은 급하지만 더이상 방문할 엄두는 내지 못하고 이번에는 택배로 A/S를 보냈다. 택배 배송기간, 수리기간 모두
합쳐 꼬박 1주일 간의 시간을 손가락 빨면서 기다렸다...ㅠ.ㅠ(물론 계속 모델링을 하긴 했다).


1주일의 기나긴 기다림 끝에 드디어 프린터가 돌아왔고. 약간 달리진 부분이 있어 문의했더니 모터쪽 결함으로 인해
수리 기간이 길어질 것 같아 새로 입고된 다른 제품을 대신 보내줬단다. 토요일에 프린터를 받아 주말 동안 테스트 출력을
해본 결과는 합격이었다.


그렇게 해서 그동안 모델링한 각 부위들을 차례차례 출력하였다. 우선 골반 부위에 SG90 서보모터 6개를 고정시킬
부품은 모두 출력이 되어 조립을 해보았다. 기존 포함된 서보 혼을 대체할 부품도 만들어 끼워주었다.


전체적으로 생각보다 크기가 상당히 컸다.




다음으로는 골반과 허벅지를 이어줄 연결부를 만들었다. 상당한 출력 시간이 필요한 부품이었는데 한 번 실패한 후
무사히 2개를 모두 출력하였다. 다만 한쪽에서 수축이 발생하여 조금 변형이 생겼는데 어차피 외장을 입힐테니 가볍게
넘어가기로 했다.




이렇게 만들어진 모든 부품을 조립하니 아래 사진과 같은 모양이 되었다. 다시 봐도 좀 크다.




일단 동작 범위를 보면 아래 영상과 같다. 이러한 동작을 아두이노를 이용하여 제어를 해야 한다.


https://youtu.be/N7aqzBunBYk



욕심같아서는 아래 사진과 같은 Stewart platform으로 구현을 하고 싶지만 그러려면 다리 한쪽에 6개의
actuator가 필요하게 되어 일단 조금 심플하게 만들어봤다.





> Stewart platform이란? : https://en.wikipedia.org/wiki/Stewart_platform


3D 프린팅의 어려움...


이렇게 한동안 신나게 출력하고 조립하고 잘 진행되는 듯하더니...또 다시 전조 증상이 발생하기 시작했다.




위 사진은 허벅지 부위에 이전에 만들었던 linear actuator를 고정시킬 부품인데, 보시다시피 상단부에 구멍이 숭숭
뚫린 것이 압출 불량의 증상이 나타났다. 여기까지는 그리 심하지 않고 역시나 외장 부품으로 상당부분 가려질 것이라서
용서가 되었는데...




이 부품은 말하자면 허벅지 뼈대로 고관절과 무릎을 이어주는 부품인데 무릎쪽 연결 부위가 이렇게 심하게 골다공증
증상을 보이고 있다. 그리고 이 것을 시작으로 이후 출력해야 할 부품들이 제대로 출력되지 않고있다. 최초 발생했던
문제들처럼 일정정도 출력 후에 필라멘트가 압출되지 않고 노즐만 헛돌다가 출력이 끝난다. 밤에 출력 걸어 놓은 것을
아침에 확인해봤을 때 반동가리도 안되는 부분만 오롯이 놓여있는 모습이 보이면...ㅠ.ㅠ




이 무더운 날 가뜩이나 잠 설치는데 프린터의 소음까지 견뎌가며 기다린 결과가 이런 것이라면...ㅠ.ㅠ


그런데 아무래도 이번에는 프린터 자체의 문제도 있겠지만 모델링 문제도 한몫 한 것이 아닌가 싶다.




그림에서 보는 것과 같이 분할면이 복잡하다. 아무래도 tweek을 너무 많이 사용한 것이 문제가 아닌가 싶다.
tweek을 쓰다 보면 면의 두께가 매우 얇아지는 문제가 많이 나타나고 또한 경사각이 많이 생긴다 이런 형태들이 출력에
영향을 주었을 것 같다. 이 것보다 복잡한 구조물이 더 많은데...걱정이다...ㅠ.ㅠ


우선은 급한대로 출력 속도를 좀 늦춰봐야겠다. 아무래도 좁은 간격을 빠르게 움직이면서 충분한 압출이 이뤄지지 않은 
것 같다. 만약 그래도 안되면 조금 더 단순한 모양으로 출력을 한 뒤 디테일은 scratch building으로 처리해야겠다.
더이상은 A/S 보내는 것도 힘들다...ㅠ.ㅠ 다음 3D 프린터는 무조건 자작이다!


글 작성 후 몇가지 테스트 중에 3D 프린터 문제를 해결하였다. 아래 사진의 빨간 표시가 된 부분은
thingiverse에 올라와 있는 필라멘트 필터라는 도구다.
저 안에는 솜뭉치가 들어있고 약간의 올리브유를 적셔주었다. 


원래 이 필터의 목적은 필라멘트에 묻은 먼지등을 제거해주는 역할인데 내 프린터는 저 필터가 없었을 경우
장시간 출력을 하게 되면 필라멘트가 빨려들어가는 입구에서 열로 인해 꺾여버린다.
거의 90도로 꺾이다보니 익스트루더 모터가 잡아당겨도 꺾어진 각도 때문에 제대로 필라멘트를 끌어오지
못하는 문제가 발생을 한 것이다.


결국 필터를 저 위치에 끼워주니 필라멘트가 꺾이는 상황을 방지할 수 있게 되었고
이후로 다시 출력이 잘 되고 있다^^.




정리


일단 다리까지만 모델링한 상태가 아래 사진과 같다.




아직도 출력할 부품들은 많은데 프린터는 위태위태하고...마음은 급하고. 내 계획으로는 8월까지는 로봇 제작을 완료해야
하는데...하....여차하면 16년도에 SEW 프로토타입을 만들었을 때처럼 동력부를 제외한 나머지 부분은 full scratch
building으로 처리해야 할지도 모르겠다...이 더운날에...


오늘도 우리 집 방 한구석에는 자그마한 3D 프린터가 
한여름 불볕더위를 더하는 데 한 몫 하고 있다...ㅠ.ㅠ


facebook page : https://www.facebook.com/HJ.WOO/?modal=admin_todo_tour

블로그 이미지

마즈다

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


3D 프린터 A/S 방문기~ USEED


3월경 처음 3D 프린터를 구매하고 개봉기를 한 번 썼는데, 다시 한 번 밝히면 USEED란 업체의 Creator mini라는
소형 3D 프린터였다. 출력 사이즈가 작은 것을 빼면 매우 훌륭한 프린터이고 나름 이 프린터로 만든 Linear Actuator
모델이 thingiverse(https://www.thingiverse.com/thing:2956768)에서 좋아요 100개를 찍었다^^


그리고 이제 본격적으로 로봇 제작에 착수하려는 순간...얘가 반항을 하네...-.-


기본적인 증상은 멀쩡히 잘 출력이 되다가 필라멘트가 압출되지 않고 혼자서 빙빙 도는 문제였다.
구매 초기에도 그런 적이 있었는데 그 때는 모델링에 문제가 있어서 일정 부분 출력된 후 멈추는 증상이었고
이번에는 압출되지 않으면서 노즐은 계속 왔다리 갔다리 한다...


마음은 급한데 이렇게 발목이 잡히니 더더욱 조급해져 오늘 반차까지 내고 장거리 A/S 여행을 다녀왔다.


완제품의 아쉬움


어찌보면 아쉬움이고 또 어찌보면 편리함이기도 한데, 문제가 생겼을 때 짐작가는 부분에 대해 스스로 조치해볼 여지가
별로 없다는 것이 완제품의 아쉬움이라면 아쉬움이다. 제조사에 A/S 문의를 하면서 혹시 노즐이나 익스트루더 기어의
자가 교체가 가능할지 물어보았으나 아무래도 직접 하기에는 어려울 것이라는 답변이었다. 


사실 어찌보면 3D 프린터라는 것이 세밀한 설정이 어려운 것이지 전체적인 구조는 웬만큼 기계 좀 뽀솨본 사람이라면
분해, 조립 정도는 쉽게 할 수 있을만큼 단순하게 생겼다. 하지만 역시나 직접 조립한 것이 아닌 완제품이다보니
괜시리 손대기가 껄적지근한 것이다.


뭐 그냥 A/S 턱 맡겨버리면 그만이니 편하기는 하지만...


문제의 증상과 원인


문제의 증상은 글의 서두에도 언급했듯이 어느 정도 출력이 잘 되다가 서서히 압출량이 줄어들다가 필라멘트 압출이
안되고 노즐만 춤추는 경우다. 간혹은 서서히 줄어는 것도 아니고 딱 끊어지고 노즐 혼자 논다.


일단 출력을 멈추고 온도를 올린 상태에서 필라멘트를 꾹 눌러주면 또 나오긴 하니 어딘가 막히거나 한 것은 아닌 
듯하고...다만 이렇게 필라멘트를 뽑아도 곧바로 아래로 출력되지 않고 약간 삐딱하게 뿜어져 나온다거나 압출량이
일정치 않다거나 하는 문제도 함께 보였다.


우선 하드웨어는 손대기가 어려운 상황이니 출력 속도를 좀 낮춰봤다. 3시간 예상 출력물이 9시간 걸렸다...ㅠ.ㅠ
2개는 잘 출력이 되었는데 하나는 역시나 출력되다 말았다. 앞 2개의 사진은 정상 출력된 것들, 마지막 사진은 출력이
중단된 출력물이다. 잠자는데 시끄럽다는 가족들의 클레임을 쌩까고 밤새 걸어놨는데...아침에 일어나 확인해보니
저모양...ㅠ.ㅠ




내가 전문가는 아니지만 특유의 감(?)으로 일단 익스트루더가 필라멘트를 제대로 공급해주지 못하고 있다고 판단을
했다. 사실 이런 압출 불량을 인터넷에서 검색하면 오만가지 원인이 다나온다. 초보자의 입장에서는 도대체 어디서부터
확인을 해봐야할지도 모를만큼...어쨌든 여러가지 경우를 따져봐서 판단을 하였고 업체에 증상을 말해주니 업체에서도 
익스트루더 문제인 것 같다고 했다.


A/S 받으러 고고씽~!


택배로도 A/S를 받는다고 했지만 일단 마음도 급하고 어떻게 수리를 하는지도 보고 싶고 몇가지 물어보고 싶은 것도
있고 해서 직접 방문하는 것으로 정했다. 물론 그 때까지 한국산업기술대학교가 우리 집에서 얼마나 먼지는 제대로
알지 못했다...ㅠ.ㅠ


워낙 크기가 작아서 무게도 적게 나가긴 하지만 그래도 3Kg이다. 이놈을 들고 당차게 출발을 했다. 그나마 다행이라면
다행일까? 지하철은 환승 없이 한 번만 타면 된다. 


그렇다! 그렇게 한 번만 타고 38정거장 1시간 33분만 가면 된다...-.- 웬만한 지방 출장이다...


4호선 정왕역에 내려서 또 버스로 2정거장 가야한다.
그런데 내가 평소 집을 나서는 시간인 6시쯤 출발을 했더니 정왕역 도착 시간이 7시 58분정도였다.
잠깐 화장실에 들려서 몸 속의 찌끄래기들 좀 빼주고 나오니 대략 8시 10분 정도...직원들 출근 시간이 9시라던데...
일단 2정거장 정도라니까 걸어가보기로 했다. 비가 안와서 다행이었다.


한참을 걷다보니 드디어 한국산업기술대학교가 나왔다.




처음 온 대학이다보니 정문이 어딘지도 모르고 그냥 뚫린데 아무데나 들어가서 좀 헤매다 드디어 목적지인 
산학융합관에 도착했다. 사진 잘못 찍어서 초점 나간 사진 지운다는게 멀쩡한 사진을 지우고 엉뚱한 사진이 남았다...ㅠ.ㅠ
하여간 아래 사진처럼 생겼다.




도착 시간 8시 40분...아직도 20분이 남았네. 
페북하면서 시간 좀 때우다가 9시 5분 정도에 사무실로 올라갔다.
많은 업체들이 입주해 있었다. 나의 목적지는 유씨드. 숨은 그림 찾기다~
유씨드는 어디에 있을까~요?




A/S의 시작과 끝~


사무실에 도착을 하니 나랑 직접 통화하신 분이 안계셔서 다른 분이 작업을 시작하셨다.
아마도 사무실 막내급이신 듯한데 꽤 준수한 외모의 소유자였다(초상권이 있으므로 사진은 안찍었다).


일단 미리 말한 증상에 대해 익스트루더쪽을 의심하여 분해를 하고 확인을 해보았더니 아니나 다를까
필라멘트 공급해주는 기어가 마모되었던 모양이다. 사실 3월에 구매해서 출력량이 그렇게 많지는 않았는데
아무래도 내가 뽑기에 실패한 것이 아닐까 싶다.


바로 새 기어로 갈아끼우고 테스트를 해보니 압출이 시원하게 잘된다. 변비약 먹고 X싸면 저렇게 나올까?


잘 수리가 되어서 다행이다 싶은 순간...생각지 못하게 베드 레벨링이 안맞았다. 그래도 집에서는 레벨링은
문제가 있을 정도는 아니었는데...압출량이 달라진 탓일까?


정작 익스트루더 수리는 5분만에 끝났는데 레벨링 잡느라 30분 정도 소요한 것 같다.


중요한 것은 직원들이 너무 친절했다는 것. 그리고 본인들이 만들고 판매하는 제품에 대해 잘 알고 있다는 신뢰감을
주었다. 게다가 멀리서 왔다고 선물까지...ㅠ.ㅠ A/S 받고 비용을 지불한 게 아니라 오히려 선물을 받아왔다.
혹시 이 글을 보고 너도나도 유씨드를 방문할까봐 선물이 뭐인지는 차마 밝히지 못하겠으나 하여간 꽤 좋은거 받았다
(어차피 나중에 선물 받은거 리뷰 한 번 쓸 것 같기는 하다...^^;). 


이렇게 한 시간 반 정도 A/S를 받고 다시 귀경길에 올랐다. 한국산업기술대학교에서 정왕역까지는 학교에서 운영하는
셔틀버스를 타고 갔다. 편하데...


그리고 종점 직전 역인만큼 서울까지는 앉아서 올 수 있었다. 해피하게~
사진은 수리 초반 사진만 간단하게 찍었다.




정리


구매하기 전에는 막연히 있었으면 좋겠다고 생각했던 3D 프린터인데 실제 사용을 해보고 3D 프린터가 없었다면 
내 꿈을 이루기 위해 내가 할 수 있는 일이 거의 없었겠구나 하는 생각이 들 정도로 3D 프린터의 가능성이 대단하게
느껴졌다. 그렇기에 문제가 생기니 더더욱 답답했고 또 이렇게 휴가까지 써가면서 A/S를 다녀온 것이다.


사실 구매한지 얼마 안된 제품에 문제가 생겨 속이 상하기도 했지만 친절한 A/S를 받고 나니 한결 좋아졌다.
하지만 3D 프린터라는 것이 아직까지는 조심해서 사용해야 할 기계인 것도 분명한 것 같다. 아무쪼록 앞으로는
별 탈없이 잘 움직여줬으면 좋겠다.


마지막으로 다시 한 번 친절함과 전문성으로 수리를 잘 해주신 USEED 직원분들께 감사 인사 드립니다.


그리고 3D 프린팅을 하는데도 시간이 오래 걸리지만 출력을 위한 모델링을 하는데도 시간이 오래 걸려 최근 포스팅
간격이 길어지고 있다. 그래서 좀 짧은 내용들은 페북에 페이지를 만들어 모아놓기로 했다. 그러다가 글이 좀 모이면
블로그로 정리하고...방문해서 좋아요 한 번씩 눌러주시길...^^;

https://www.facebook.com/HJ.WOO/

블로그 이미지

마즈다

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



Linear Actuator를 이용한 2족 보행 로봇 제작 ~ #1


일단 Linear Actuator는 LCP06-A03V-0136 모터를 이용하여 내가 만들 수 있는 최소한의 크기로 완성을 하였다.
처음 LCP06-A03V-0700 모터가 40RPM이라는 느린 속도로 인해 실사용이 어렵다고 판단하여 200RPM의 새
모터를 구입한 것인데 이 속도도 그리 빠르다고는 할 수 없지만 크기와 속도와 토크의 적정선에서 고를 수 있는 모터는 
이 LCP06-A03V-0136 모터 뿐이라고 봐야 할 것이다.


N20 기어 모터의 경우 Linear Actuator의 크기가 너무 커져서 차후 더 큰 크기의 로봇을 만들 때 사용하기로 했다.


몇가지 준비


LCP06-A03V-0136는 속도가 빠른 대신 토크가 낮아졌기 때문에 다리 한쪽에 Linear Actuator를 2개씩 장착하여
구동시키로 하였다. 그래서 주말동안 처음 샘플로 출력한 것 외에 추가로 3개를 더 출력하였다.




부품 중 내부 실린더로 사용할 외경 3mm의 스테인레스 파이프는 아직 자르지 않았는데 톱질의 노가다를 좀
피하기 위해 저렴한 파이프 커터를 하나 주문해놨다. 아무리 명필은 붓을 가리지 않는다지만 그래도 최소한 필요한
공구는 좀 있어야 작업이 편해지지 않겠는가?




더불어 부품에 구멍을 뚫거나 구멍을 넓히기 위해서 당장 사용하지는 않겠지만 추후 드릴 프레스를 만들기 위해서
모터와 드릴척도 banggood에 주문을 한 상태다. 또 한 20일 정도 마음을 비우고 기다려야겠지...-.-




드릴프레스는 thingiverse에 있는 다음 모델로 만들 생각이다.


https://www.thingiverse.com/thing:1846582




3D 모델링


만약 내가 이 프로젝트를 실패하게 된다면 그건 바로 이 3D 모델링 때문일 것이다.
나는 이제까지 3D 모델링이라고는 배워본 적도 툴을 다뤄본 적도 없다. 그저 최근에 3D 프린터를 구입하고 나서
123D Design이라는 말하자면 3D 모델링 프로그램계의 그림판이라고 할 수 있는 툴만 간간히 이용하여 이미
보아온 것과 같이 아주 단순한 형태의 모델만을 만들어봤을 뿐이다.


한마디로 내가 하고자 하는 작업은 이제 막 그림을 배우러 미술학원에 등록한 학생이 그림판을 이용하여 극사실주의
화풍의 그림을 그리고자 하는 것과 다름없다...ㅠ.ㅠ


상황이 이렇다보니, 보통은 전체적인 형태를 스케치 하고 그것을 바탕으로 3D 모델링을 하고 물리 효과를 적용하여
움직임을 확인하는 등의 과정을 거치는데, 나는 스케치조차 능력이 안되다보니 123D Design에 바로 핵심 부품을 
중심으로 그 주위에 하나하나 부품을 만들어 나가는 식으로 작업을 할 수밖에 없다.


더 심각한 것은 이 123D Design은 물리 효과는 전혀 줄 수가 없기 때문에 이놈이 만들어진 후 제대로 서있기나 할지,
혹은 동작 중에 서로 겹쳐지는 부분은 없는지, 축들이 서로 어긋나지는 않았는지를 확인할 방법이 없다. 그야말로
시행착오의 연속이고, 예약된 가시밭길인 것이다...ㅠ.ㅠ


따라서 아래 공개하는 습작은 그냥 습작일 뿐 실제 출력 과정에서 많은 변형이 예상된다. 우선 다리만 작업해보았다.




정리


시작이 반이라고 했다. 일단 뭐가 됐든 시작은 했으니 어떻게든 되겠지^^;


하지만 만드는 것도 만드는 것이지만 만든 후 아두이노를 통해 어떻게 제어를 해야 할지가 또 구만리다.
머릿속에서는 모든 것이 착착 진행되어 먼 미래로 날아가고 있는데 현실은...ㅠ.ㅠ


뭐 안되면 피규어라고 우기면 그만이지~
긍정적으로 살자! 긍정에 대한 유명한 얘기가 있지 않은가?


시험을 못봤을 때 부정적인 학생과 긍정적인 학생의 차이

부정적인 학생 : “어떡하지...ㅠ.ㅠ 시험을 망쳤네...ㅠ.ㅠ 죽어버릴까...ㅠ.ㅠ?”

긍정적인 학생 : “ㅎㅎ시험을 망쳤네~ 뭐 어때~ 죽으면 되지~^^”


그래! 안되면 죽으면 그만이다~^^;

블로그 이미지

마즈다

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



Linear Actuator Ver. 2, Ver. 3 제작


지난 포스팅에서 앞으로 내가 만들고자 하는 로봇과 그러한 로봇을 만들기 위한 준비로써 Linear Actuator
시제품을 만드는 과정을 설명하였다.


하지만 시제품이다보니 여러모로 한계가 있었다. 우선 지난 작업의 한계들을 조금 살펴보고 가자.


  1. 모터 선정의 문제 : 지난 번 사용한 모터는 8520 coreless 모터로 주 용도는 소형 드론을 만드는데 사용하는 모터다. 따라서 RPM은 매우 높으나 torque는 낮아서 지난 포스팅의 동영상에서 보듯이 작은 마찰에도 내부 실린더가 잘 올라오지 않거나 움직이는 속도가 잘 조절이 안되는 모습이었다. 게다가 모터 축이 원통형이어서 웬만큼 단단히 접착하지 않으면 모터 축과 연결하는 부품이 헛돌기 일쑤다.
  2. 멈춤 제어 장치 부재 : 내부 실린더가 위나 아래의 끝부분에 도달하게 되면 모터를 정지시켜 더이상 작동하지 않도록 해야 모터에 부하가 걸리지 않을 것이다. 하지만 개인이 제작하는 수준이다보니 아직은 구현이 쉽지 않다. 일단 소프트웨어적으로 구현을 하기 위해 encoder를 함께 구매해놨지만 아직 사용은 안하고 있다. 당분간은 아두이노 스케치상에서 적절한 시간 조절을 통해 멈춤을 제어해야 할 것 같다.
  3. 구조 설계의 문제 : 이 부분은 아직도 딱부러지게 해결을 못했다. 가급적 조립과 분해가 쉽도록 하기 위해 머리를 굴리고 있으나 이번 제작한 것들은 조립이 까다롭게 되었다. 그리고 내부 실린더가 헛도는 것을 막기 위해 내부 실린더 하단부를 사각형으로 만들고 그에 따라 외부 실린더의 내부도 사각형 형태로 만들었었는데 아무래도 마찰이 큰 것 같아 이번에는 조금 바꿔보았다. 하지만 3D 프린팅의 관점에서보면 지난번 구조가 더 효율적이다.


이러한 문제점들을 일부 개선하여 새롭게 버전 2와 버전 3의 Linear Actuator를 만들었다.
여전히 몇가지 문제는 남아있지만 동작은 한결 자연스러워졌다(버전 2와 버전 3의 차이는 모터와 전체 크기의 차이다). 


모터 선정


모터와 관련해서는 앞서 언급한대로 고 RPM, 저 torque 문제와 원통형의 모터축 문제를 해결해야 했다.
우선 torque 문제를 해결하기 위해 geared motor를 검색했다. 그냥 예전에 기어박스를 이용하여 속도를 줄이면 
torque가 올라간다는 것을 주워 들은 것 같다...-.- 다행히 많은 검색 결과가 나왔다. 게다가 대부분의 geared 모터는
모터 축(엄밀히 말하면 기어박스의 축)이 D컷이 되어있었다(D컷은 원통형 축의 한쪽을 잘라내어 모터 축이 헛도는 
것을 막는 방법으로 잘라내고 난 모양이 영문 D와 같다고 하여 D컷이라고 한다).




다음 기준으로는 가능한한 소형화를 해야 하기 때문에 모터의 크기가 중요 선정 기준이 되었다. 사실 지난 번 사용한
8520(지름 8.5mm, 높이 20mm) 모터정도가 딱 좋다 싶었는데 정작 Linear Actuator를 만들고 나니 생각보다
크기가 컸다. 어쨌든 8520 모터를 크기의 기준으로 삼았다.


이렇게 기준을 정하고 선정한 첫번째 모터가 바로 N20 모터를 베이스로 한 일군의 기어모터였다.
N20 모터는 가로, 세로, 높이가 각각 10mm X 12mm X 15mm정도되는 크기의 사각형 형태의 모터로 주로 소형
서보모터를 만드는데 사용되거나 앞단에 금속 기어박스를 달아 판매를 한다. 메이저 제조사는 pololu라는 업체인 것
같은데 가격이 비싸고, 중국산 카피제품은 허용 전압과 기어비에 따라 3천 원 대 후반에서 만 원대에서 구매 가능하다.





나는 일단 banggood에서 12V, 100RPM의 스펙을 갖는 모터를 선택하여 주문하였으나...2달이 다되가는 지금까지
도착하지 않고 있다는 슬픈 전설이...ㅠ.ㅠ


결국 국내 업체를 통해 6V, 450RPM에 뒤쪽으로 encoder를 달 수 있는 제품을 포함하여 추가로 2종류의 변형 
제품을 구매했다.


일단 이 제품은 생각보다 크기가 작고 스펙에 따라서는 꽤 높은 토크를 낸다는 점이 장점이다. 내가 처음 banggood
에서 주문한 제품의 경우 정격토크가 2.0kgf-cm정도다. 가격은 약 3,900원 정도. 하지만 미처 생각하지 못한 것이
있었으니...바로 모터의 형태다. 그 전에 사용한 8520 모터는 원통형의 모양이었지만 이 모터는 육면체 모양이다.
8.5mm와 12mm...별 차이가 없어보이지만 지름이 8.5mm인 것과 사각형의 한 변이 12mm인 것은 결과물에서
엄청난 차이를 만들게 된다(아래 버전별 비교사진 참고).


일단 N20모터를 기준으로 모델링을 하는 과정에서 크기가 상당히 커진다는 것을 확인하고 추가로 모터를 검색하였다.
그러다 발견한 것이 LCP 시리즈였다. 이 모터는 지름 6mm의 원통형 모터로 유성기어가 장착된 모델인데 가장
높은 토크를 가진 모델이 LCP06-A03V-0700으로 기어박스 포함 전체 길이는 약 21mm 정도로 8520모터와 비슷
한 크기를 가졌다. 3V에서 작동하며 정격토크는 200gf-cm이고 40RPM의 속도를 낸다.




이 모터를 사용하여 만든 Linear Actuator가 가장 적당한 크기였다.


벗뜨! 그러나!


일단 이 모터의 가장 큰 단점은 가격이다. 국내 업체 중 가장 싼 곳이 개당 8,400원에 판매하고 있다. N20 베이스 
모터와 비교하면 크기는 1/2, 가격은 X2 + 𝛼인 것이다. 게다가 40RPM...사실 이 방면으로는 젬병인 문돌이다보니
40RPM이 의미하는 바를 몰랐다. 1분에 40바퀴 돌아간다는 것 정도는 검색질로 알 수 있었으나 그게 어느 정도의
속도인지는 정말 몰랐다...ㅠ.ㅠ 


회전 운동이 물체를 움직이는 거리와 회전 운동을 직선 운동으로 바꾼 후 물체를 움직이는 거리는 천지 차이다.
물론 회전 운동의 경우 회전 축으로부터의 거리가 영향을 크게 미치고 직선 운동의 경우 나사선의 간격이 영향을
미치므로 직접 비교는 힘들지만 아무튼 M2나 M3 사이즈의 작은 볼트의 나사선 간격을 놓고 보면 모터의 회전 속도가
웬만큼 빠르지 않다면 움직이는 거리가 매우 작다(아래 동영상 참고). 


만일 이 모터를 사용한 Linear Actuator로 만들 수 있는 로봇이 있다면 그것은 단 하나! 바로 달팽이 로봇이다...-.-


결국 이 모터보다 한 등급 낮은 120gf-cm의 토크에 200RPM의 속도를 갖는 모터를 추가로 주문했다.


참고로 몇가지 모터들을 비교한 사진을 올린다. 첫 번째 사진의 오른쪽 모터는 직경 4mm 크기의 진동 모터다.
혹시나 어떻게 사용해볼 수 있을까 하고 덜컥 구입했지만…역시나 이 크기로는 무리다…



좌측부터 8520 coreless 모터, LCP06-A03V-0700 유성 기어 모터, 초소형 진동 모터




좌측부터 일반 DC 모터(학습용), N20 기어 모터, SG90 서보 모터, LCP06-A03V-0700 유성 기어 모터, 
마이크로 서보 모터


3D 프린팅


지난 포스팅에서도 언급한 바와 같이 3D 프린터를 이용하는데 있어 가장 큰 문제는 바로 공차 설정이다.
0.5mm를 줘도 안들어가는 일이 비일비재하여 아예 넉넉하게 0.8 ~ 1.0mm 정도 준 후 볼트로 조여 최종 고정시키는 
방식을 사용하기로 했다. 하지만 이 방법도 만능이 아닌 것이 FDM 방식의 3D 프린터는 녹은 필라멘트를 실처럼 얇게
뽑아 한층한층 쌓아가며 만드는 방식이다보니 이렇게 층이 쌓인 방향으로는 강도가 매우 약하다. 자칫 볼트를 심하게
조일 경우 이 층이 갈라져 깨져버린다. 이 문제를 막을 방법은 출력물의 두께를 두껍게 하는 것 뿐이지만 마냥 그럴 
수도 없고...


또 다른 문제는 개인차가 있는 문제이겠지만 간혹 출력 중 노즐이 막히는 경우가 있다. 대략 5시간 가까이 출력해야 
하는 부품이 3시간 출력후 노즐이 막혀 헛돌고 있으면...나무아미타불...ㅠ.ㅠ 한 몇번 이런 문제가 생기다보니 답답한
마음에 차라리 전문 출력 업체에 출력 의뢰를 하려고 했더니 이건 또 가격이 안드로메다인지라...ㅠ.ㅠ


최대한 출력 시간을 줄일 수 있도록 모델링을 하고 출력 중 환경(온도, 진동 등)이 일정하게 유지되도록 하는 외에는
달리 방법이 없다. 특히 출력 시간의 경우 동일한 모델을 180도 돌려 출력하는 것만으로도 시간을 줄일 수 있다.
가능한한 서포터가 적게 출력되도록 하는 것이 아무래도 출력 시간을 줄일 수 있지만 가급적이면 조립되는 면에는
서포터가 안생기도록 하는 것이 나중에 매끈한 면으로 서로 연결할 수 있어 유리하다. 결국 케바케로 출력 시간이
결정될 수밖에 없다.


그럼 버전 2와 버전 3의 3D 프린터 출력물 및 구성품을 알아보자.




버전 2

  1. 외부 실린더 하단 덮개 (O-ring으로 연결)
  2. 모터 하우징
  3. 외부 실린더
  4. 볼트와 모터 축 연결부 (볼트는 M2, 30mm 렌치 볼트)
  5. 너트와 외부 실린더 연결부 (너트는 M2 사이즈, 외부 실린더는 외경 3mm 스테인레스 파이프)
  6. 내부 실린더 덮개 (O-ring으로 연결)
  7. LCP06-A03V-00700 유성 기어 모터


버전 2의 조립 과정은 다음과 같다.




버전 3는 구성품만 설명한다. 조립 과정은 비슷비슷...




버전 3

  1. 외부 실린더 상단 덮개 (버전 2의 경우 이 부분이 실린더와 일체형이다)
  2. 외부 실린더 하단부 (모터 삽입부)
  3. 외부 실린더 상단부
  4. 볼트와 모터 축 연결부 (볼트는 M6, 60mm 육각볼트)
  5. 너트와 내부 실린터 연결부 (너트는 M6, 내부 실린더는 외경 8mm 스테인레스 파이프)
  6. 외부 실린더 하단 덮개 (처음에는 짧게 만들었으나 외부 실린더의 연결부가 보기 안좋아서 가리기 위해 길게 만들었음)
  7. N20 6V 450rpm 기어 모터
  8. 내부 실린더 덮개


버전 3의 경우 조립 과정에서 외부 실린더 상단과 하단 조립 후 외부 실린더 하단 덮개가 이 연결부를 덮게 되는데
꽤 여유있게 공차를 주었음에도, 출력물의 바닥과 천정이 살짝 퍼지는 현상과 함께 볼트를 체결하면서 볼트가 잘못 
들어가 연결부에 변형이 생기는 문제가 발생하여 조립이 빡빡하게 되었다.

 

덕분에 다시 분해하면서 외부 실린더 하단부의 연결 부위가 박살이 나서 4시간 넘는 출력물을 새로 뽑아야 했다...ㅠ.ㅠ 
새로 뽑은 부품으로 조립할 때는 줄로 연결부위를 조금 다듬어 주었더니 무리없이 조립이 되었다.


마지막으로 완성된 제품의 버전별 크기를 비교해보면 다음과 같다.




사진 왼쪽부터 
버전 3 : 길이 약 15cm / 무게 약 56g
프로토타입 : 길이 약 10cm / 무게 약 24g (사진에서는 내부 실린더가 약간 돌출된 상태라 조금 길어보임)
버전 2 : 길이 약 8.5cm / 무게 약 7g


3D 프린팅을 위한 모델링 파일은 아래 링크에서 받을 수 있다.


모델링 파일

Ver. 3 : https://www.thingiverse.com/thing:2956768

Ver. 2 : https://www.thingiverse.com/thing:2956757


동작 테스트


동작 테스트는 동영상으로 감상해보자.
타이머를 켜놓았으니 초당 얼마나 움직이는지 확인하면 되겠다. 다만 버전 3의 경우 원래 6V에서 작동하는 모터인데
버전 2와 동일한 3.7v의 리튬 폴리머 배터리로 작동을 시킨 만큼 이 동영상보다는 좀 더 속도가 나올 것이다.

다만 아직 얼마만큼의 무게를 움직일 수 있는지에 대해서는 테스트를 하지 않았다.




문제점


제법 개선을 했음에도 불구하고 여전히 문제가 있다. 앞서 말한 정지 제어가 안된다는 문제와 구조적으로 내부 
실린더와 너트를 연결하는 부위를 원형으로 하는 대신 외부 실린더 안쪽에는 돌기를, 너트 연결부에는 홈을 파서
내부 실린더가 헛도는 것을 막도록 바꾸었는데 조립의 편의성 때문에 모터와 볼트 연결부 역시 동일한 구조로 
만들었다. 이 구조는 만일 양 끝단이 당겨지는 힘을 받았을 때 모터 축과 볼트 연결부가 단단히 고정되어있지 않다면
내부 실린더가 빠져버리는 문제가 발생할 수 있다.


고쳐야 하겠지만...새로 설계하고 출력하는 시간이 어마무시 하므로, 괜찮겠지 하고 넘어가보자...ㅠ.ㅠ


속도 문제는 새로운 모터가 도착하는대로 다시 테스트를 해볼 것이다.


그나저나 본격적으로 로봇을 만들기 시작하게 되면 이 부품들을 몇세트 씩은 더 뽑아야 하는데...또 얼마나 많은
시간이 걸릴까...


정리


아직 많은 문제점이 있지만 그래도 제법 그럴듯한 모양으로 Linear Actuator가 만들어졌다(하지만 지금도 3D 
프린터 출력 시간을 생각하면 치가 떨린다). 이제 본격적으로 로봇을 설계하고 만들어야 하겠지만 최소한 버전 3를
1개 더 만들어야 하고 버전 2를 6개 정도는 더 만들어야 한다. 나의 생활 패턴 상 3D 프린팅을 하는 데만도 2주는
잡아야 할 것 같다. 물론 그 전에 적절한 힘을 낼 수 있는지 먼저 테스트도 해야 하고...


어쨌든 본격적인 로봇 작업이 시작되면 지금보다는 좀 더 재밌는 글을 쓸 수 있지 않을까 기대해본다^^

블로그 이미지

마즈다

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

Linear Actuator 제작 - 새로운 로봇 제작 프로젝트를 시작하며.


2016년 6월 경, 아두이노를 접한 지 얼마 되지 않아 4족보행 로봇 제작을 시작하였다.
그 시작이 아래 링크의 포스팅이었다.


http://mazdah.tistory.com/695


당시 끝을 보지 못한 가장 큰 이유는 전원 공급 문제를 해결하지 못한 것이라고 해야 할 것이다.
비록 방전률이 높기는 했으나 아무래도 니켈수소 배터리로는 한계가 있었던듯하다. 지금이야 드론을 만들면서
(역시나 아직 완성은 못했지만…-.-) 조금은 익숙해졌지만 당시에는 관리의 어려움(폭발 위험성) 때문에 쉽게 LiPO
배터리를 선택하지 못했었다.


이렇게 진척이 되지 않다보니 소프트웨어적인 부분도 마무리를 하지 못하고 로봇 제작 프로젝트는 방구석으로 쳐박히고
말았다…ㅠ.ㅠ


내가 꿈꾸는 로봇은…


어차피 나는 로봇 제작을 위해 필요한 전문 지식을 쌓지도 못했고 또 로봇 제작을 위해 필요한 부품을 선별하거나 조달할
수 있을만큼 여유가 있지도 않다. 때문에 내가 만들고자 하는 로봇은 실용적인 로봇과는 거리가 멀다(물론 추후 SCARA
방식의 로봇 팔을 만들 계획은 가지고 있다). 물론 로봇을 만드는 모든 사람들이 실용적인 목적으로 로봇을 만들지는 
않지만 말이다…


어쨌든 처음 시작할 당시에는 보고 들은 것이 별로 없다보니 오로지 서보모터를 구동부로 하여 제작하는 4족보행 로봇
만이 내가 할 수 있는 전부라고 생각했다. 바로 다음과 같은 로봇들 말이다(세 번째 로봇은 내가 만든 로봇이다^^;).




사실 위에 예로 든 로봇들도 충분히 멋지고 내가 만만하게 만들 수 있는 수준도 아니다. 하지만 이제 머리 좀 굵었다고(?)
더 멋지고 리얼해보이는 그런 로봇을 만들고 싶어졌다. 바로 아래 그림과 같은…




흔히 battle mech, war robot, war mech 등으로 검색하면 볼 수 있는 이런 로봇들은 대부분 컨셉아트이거나 게임을
위한 3D 모델링, 혹은 그 모델링을 기초로 만들어진 피규어인 경우가 대부분이다. 나는 여기에 생명을 불어넣고 싶은
것이다. 단지 그림이나 영상 속의 존재가 아닌, 그저 바라만 보고 있어야 하는 인형이 아닌, 멋지고 리얼하면서도 걷고 
움직이는 로봇을 만들고 싶은 것이다. 한마디로 예쁜 쓰레기 또는 쓸고퀄 장난감이라고나 할까…-.-


Actuator란? Linear Actuator란?


actuator란 단어의 생김새로도 짐작할 수 있듯이 기계장치의 ‘움직임’을 담당하는 부분이라고 생각하면 될 것이다.
일반적으로 기계공학에서는 유압이나 공압으로 작동하는 피스톤/실린더 형태의 장치를 의미하지만 좀 더 범위를 넓혀
전기로 움직이는 모터류도 actuator에 포함 시킬 수 있다. 피스톤/실린더 장치의 대표적인 것이 바로 아래 그림과 
같이 흔히 볼 수 있는 건설 장비들이다.




이러한 장치들과 모터와의 가장 큰 차이점은 피스톤/실린더 장치가 앞,뒤로 움직이는 선형적인 움직을 통해 기계장치를
구동시키는 반변 모터는 축의 회전을 통해 장치를 구동시킨다는 점이다.


제일 처음에 예로 든 4족보행 로봇들이 바로 모터를 관절 부위에 장착하여 그 회전력으로 관절을 움직이도록 만든 것이다.
제작은 간단하지만 뭔가 로망이 없다…-.- 이런 로망…




그렇다! 이 로망의 정체가 바로 Linear Actuator다. 모터의 회전력을 직선 운동으로 바꾸어주는 actuator인 것이다.
유공압 actuator들은 기본적으로 선형 운동을 하기 때문에 Linear Actuator라고 하면 보통 모터의 회전운동을 직선
운동으로 변환시켜주는 장치를 말한다고 생각하면 된다.


Linear Actuator를 만들어보자


이미 시중에 상용제품으로 파는 linear actuator 혹은 linear motor라고 불리우는 제품들이 많이 있다.
이런 제품들은 외형 디자인을 바꿀 수 없다는 점, 크기가 대체로 크다는 점, 결정적으로 가격이 비싸다는 점
(기본적으로 5만원대부터 시작한다…ㅠ.ㅠ)으로 인해 사서 쓰는 것은 포기. 아래 그림은 내가 즐겨 이용하는
banggood이라는 홍콩쪽 온라인 쇼핑몰에서 linear actuator로 검색한 결과이다.




이제는 3D 프린터도 생겼겠다, 뭔가 정체를 알 수 없는 자신감 충만! 그래서 한 번 만들어보기로 했다.


우선 외형을 3D 프린터를 이용해서 만들기로 했으니 thingiverse에서 혹시 비슷한 작업을 한 내용이 없나
검색을 해보았다. 비슷한 작업은 많았으나 아쉽게도 만족할만한 결과물은 없었다. 다만 영감을 준 모델이 하나 있어
그 것을 참고로 작업을 시작하였다.


원리는 매우 간단하다.


볼트를 고정시켜놓은 상태에서 원통의 한쪽 끝에 너트를 박아넣고 볼트에 끼워 돌리면 이 원통이 위,아래로 직선운동을
하게 되는 것이다. 이 때 이 돌리는 힘을 모터를 이용해서 주기만 하면 되는 것이다. 아래 이미지는 Wekipidia에서 가져온
것이다.



모델링하기


3D 프린터로 출력을 해야 하기 때문에 당연히 3D 모델링이 필요하다. 다행히 위에서 본 것 같은 화려한 로봇을
모델링하는 것이 아니고 단순히 원기둥 안에 구멍을 뚫는 정도의 작업이라 직접 하는 것이 가능했다. 그렇지 않았다면
그냥 꿈 속의 꿈으로 끝났을지도…물론 몇몇 부분은 조금 복잡하기도 하다^^;


모델링 툴은 초보자답게 오토데스크사의 무료 툴인 123D Design으로 진행하였다. 별다른 설계 없이 그냥 즉흥적으로
모델링을 하다보니 처음에 비해 형태가 많이 바뀌었다. 하지만 즉흥적이라 해도 몇가지 고려해야 할 사항이 있었으니
우선 모터를 사용하는 장치다보니 모터가 고장났을 경우 교체하기 쉽도록 분해 조립이 가능해야 한다. 분해 조립이 
원활하게 될 수 있으려면 적절한 공차를 계산해야 하는데…3D 프린터는 내가 생각한만큼 정밀한 기계가 아니었다…-.-


대략 0.1 ~ 0.3mm 정도의 공차를 두면 되겠거니 하고 출력을 해보면 헐겁거나 아예 들어가지 않는 일이 비일비재였다.
그래서 아예 엑셀 파일에 상황을 정리해가면서 시행착오를 거쳐 모델링을 할 수밖에 없었다(이마저도 나중에는 귀찮아서
하지 않았다…-.-). 




그 결과 실패한 부품들이 꽤나 쌓이게 되었다. 말하자면 사금파리 무덤이랄까…ㅠ.ㅠ




이러한 산고를 거쳐 완성된 형태가 바로 이 것이다!!!




출력하기


앞서 말한 바와 같이 3D 프린팅을 할 때 공차를 잡는 것이 쉽지 않다. 이 것은 필라멘트를 녹여 형상을 만든 후 
녹았던 필라멘트가 식으면서 생기는 수축에 의한 변형에 기인한다. 이런 수축에 의한 변형은 공차를 예측할 수
없게 만들뿐더러 또 한가지 문제로 면이나 선이 반듯하게 나오지 않는 문제가 있다. 파이프를 만든다고 할 경우 선이 
반듯하게 나오지 않다보니 2개의 부품을 결합할 때 처음에는 조금 들어가는 듯하다가 어느 시점부터 더이상 들어가지
않는 문제가 생긴다.


이렇게 모델링한 부품이 총 8개가 나왔는데 이 중 출력 시간이 긴 것은 대략 2시간, 짧은 것은 10분 정도 걸린다.
전체를 동시에 출력하면 7시간 30분 정도가 걸리는데 앞서 말한 문제들이 있다보니 만일 7시간 30분 걸려서 
전체를 출력했는데 제대로 조립이 안된다면? 생각만 해도 끔찍하다. 


사실 2시간 걸리는 부품도 4번 정도 출력한 후에야 크기를 맞출 수 있었다. 위에 사금파리 무덤 사진을 보면 알겠지만 나같은 초보에게 3D 프린팅은 시간과의 싸움이다…ㅠ.ㅠ


어쨌든 모델링을 완성하고 최종적으로 출력한 부품들은 아래와 같다(사진으로 찍어놓으니 출력 결과가 상당히 
지저분해 보이지만 육안으로 보면 봐줄만 하다^^;).






사진에 있는 숫자 순서대로 각각 아래 설명한 역할을 한다.


  1. Actuator의 가장 하단 덮개, 4개의 구멍은 고정부에 연결하기 위한 것이고 중앙에 갈라진 틈을 기준으로 상하부가 좌우로 회전한다. 하단부의 사각형 공간은 모터의 전선을 뺄 부분이다.
  2. 고정(외부) 실린더의 하단으로, 모터가 삽입된다. 아래쪽의 사각형 공간은 모터의 전선을 뺄 부분이다.
  3. 2번 부품과 4번 부품을 결합하기 위한 부품으로 위, 아래 각각 4개의 무두볼트로 결합하도록 하였다.
  4. 고정(외부) 실린더의 상단부로 5번의 내부 실린더가 이 안에서 움직인다. 3번 부품을 통해 4번 부품과 결합되며 너트와 연결된 내부 실린더가 통째로 돌아가는 것을 막기 위해 내부는 사각형 형태로 뚫려있다.
  5. 내부 실린더로 실제로 움직이는 부분이다. 전체를 원통형으로 할 경우 이 내부 실린더가 함께 돌아서 직선 운동이 발생하지 않을 수 있으므로 아래쪽은 사각형으로 만들서 외부 실린더의 사각형 형태와 맞물려 회전하지 않도록 만들었다.
  6. 고정(외부) 실린더의 상단부를 덮는 덮개로 내부 실린더가 빠져나가는 것을 막는다.
  7. 내부 실린더의 상단 덮개로 1번 부품이 연결되는 고정부의 반대편 고정부에 연결할 수 있고 구조는 1번 부품과 동일하나 크기만 작다.
  8. 흰색 부분만 3D 출력물이고 길게 나왔는 부분은 M3 규격의 육각볼트이다. 볼트의 반대편(지름이 작은 쪽)은 모터의 축과 연결된다. 이 사진 촬영 후 테스트 과정에서 모터 축과 이 부품 사이의 유격이 커 모터가 헛도는 문제가 생겨 모터 커플러와 동일한 방식으로 모터 축과 연결되는 부분의 옆쪽에 구멍을 내어 무두볼트로 고정시키도록 다시 만들었다.
  9. 전적으로 미관을 위한 부품으로 외경이 8mm인 스테인레스 파이프다. 5번 부품을 굵게 만들어도 되나 멋을 좀 내보려고 5번 부품에 끼워서 사용하도록 했다.
  10. 가장 중요한 모터이다. 8520사이즈의 코어리스 모터이며 주로 소형 드론 제작에 쓰이는 모터이다. 소형화를 위해 이 모터를 선택했지만 아마도 약한 토크 덕에 실사용은 어려울 것 같다.


이렇게 오랜 시간 시행착오를 거치면서 모든 부품을 출력할 수 있었다.


조립하기


조립은 아래 사진의 순서대로 진행을 하면 된다. 앞서도 누누히 말했지만 정밀한 출력이 어렵다보니 그토록 시행착오를
거쳤음에도 불구하고 지나치게 빡빡한 부분과 헐거워서 접착제를 써야 할 부분들이 존재했다. 아예 결합이 되지 않는 
경우가 아니라면 빡빡한 것은 괜찮은데 너무 헐거운 경우 접착제를 쓰게 되면 나중에 분해가 안되기에 조금 곤란하다.




이렇게 해서 3D 프린터를 이용한 나의 첫 작품이 완성되었다!


테스트


테스트는 아두이노와 L9110 모터 드라이버를 이용해서 했으며 스케치 코드는 단순하게 0.5초의 딜레이를 주어
정방향과 역박향 반복해서 모터를 회전시키도록 하였다. 따라서 제품은 0.5초 간격으로 위,아래 직진 운동을
반복해야 한다. 연결과 코드는 다음과 같다.




void setup () {
	pinMode(5, OUTPUT);              
	pinMode(6, OUTPUT);             
}

void loop() {
	analogWrite(5, 0);                   
	analogWrite(6, 150);             
	delay(500);
	analogWrite(5, 150);
	analogWrite(6, 0);
	delay(500);
}


우선 처음 두 동영상은 위 부품 설명의 8번 부품을 개선하기 전이며 사용한 배터리도 1.5V 알카라인 배터리를 직렬 
연결하여 진행한 테스트이다. 거의 실패작이라고 봐야 할 것이다...ㅠ.ㅠ





아래 여러가지 문제점을 설명하겠지만 우선 배터리의 힘이 약한 부분과 모터가 헛도는 문제를 개선하여 2차 테스트를
진행하였다. 배터리는 3.7V 1s 200mAh의 LiPo 배터리로 바꿨다. 역시 배터리를 바꾸니 모터 돌아가는 소리부터
다르다. 여전히 원하는 동작은 나오지 않지만 그래도 처음보다는 나아진 모습을 보여준다. 




또 다른 고려사항


이미 3D 프린터를 이용하여 외형을 만드는데만 해도 많은 시간을 소요했다. 하지만 결과는 투자한 시간만큼 훌륭하지
않았다...ㅠ.ㅠ 이에 몇가지 문제점을 짚어보고 앞으로의 해결 방향을 모색해봐야겠다.


  1. 모터의 선택 : 우선 최대한 크기를 줄이기 위해 현재 구할 수 있는 모터 중 가장 작은 크기라고 할 수 있는 8520 coreless 모터를 선택했다(물론 더 작은 모터들도 많지만 여기서 더 작아지면 3D 프린터로 출력이 어려울 것 같아 선택에서 제외했다). 이 모터의 문제점은 소형 드론용 모터이다보니 RPM이 엄청 빠른데 그만큼 힘이 없다는 것이다. 동영상에서 보이는 버벅대는 움직임은 내부실린더 회전을 막기 위해 사각형 구조로 만듦으로 해서 생긴 마찰 때문인데 이정도 마찰에도 힘겨워 한다면 관절을 움직일 수 있을지가 미지수이다. 이보다 작은 모터에 기어박스를 단 모터들을 알리바바에서 팔고있는데 개당 1만원 정도라서 사기가 부담스럽다...-.- 다행히 사이즈는 크게 차이나지 않으면서 기어박스가 달린 모터를 찾았다. 이 모터를 이용하여 다시 제작을 할 예정이다. 아래 이미지와 같이 아예 볼트 축이 달린 모터도 있었다(이미지는 애용하는 메카솔루션에서 가져왔다). 
  2. 마찰 : 다음으로는 위에도 잠깐 언급했지만 내부실린더 회전을 막기 위한 설계이다. 일단 직관적으로 사각형 구조로 만들어 회전을 막긴 했으나 이렇게 하니 마찰이 너무 심하다. 더군다나 3D 프린터 출력물에는 결이 있다보니 이같은 현상이 더 심해지는데 이를 보완할 수 있도록 새롭게 설계가 필요하다. 우선은 급한대로 모서리 부분을 모두 줄로 갈아서 마찰을 좀 줄이긴 했다.
  3. 크기 : 최종 결과물이 내가 예상했던 것보다 커졌다. 1번 부품이 많이 커졌는데 이 부분은 나중에 로봇을 설계하면서 재설계를 해야겠다. 추후 로봇을 만들게 되면 다리 부위에 제일 큰 actuator가 들어갈 것이고 그밖에 자잘한 관절부위에도 actuator가 들어가게 될 것인데 더 소형화 하지 않으면 로봇이 부담스러운 크기가 될 것 같다. 크기와 관련된 가장 큰 문제는 내가 가진 3D 프린터의 최대 출력 사이즈가 100X100X100mm라는 점이다. 크기가 커지면 3D 프린터로 한번에 출력할 수 없는 부품이 많아질 것이다...ㅠ.ㅠ

정리


애초에 관심도 없는 피규어나 출력하려고 3D 프린터를 산 것이 아닌 만큼 결과를 떠나 이번 작업은 매우 재미있었다.
물론 출력의 긴 시간을 기다리는 것은 지루하기 짝이 없었지만...-.-


우선은 linear actuator의 기본적인 동작 원리를 알았으니 그것만으로 성과라면 성과라 할 수 있을 것이다.
완전한 성공은 이루지 못했지만 처음 하는 작업으로써는 나쁘지 않은 성과다. 새로운 모터도 구입하고 했으니 이제
본격적으로 대들어볼 예정이다.


모델링한 STL 파일은 thingiverse에 공유할 예정이다(오늘 가입했더니 신참들은 24시간 지나야 모델이 등록
된단다...-.-). 이와 별개로 123D Design 원본 파일은 아래 첨부한다.


full_component3.123dx


앞으로 작업은 꾸준히 지속되겠지만 아무래도 3D프린팅 시간이 오래 걸리는 만큼 포스팅을 자주할 수는 없을 것 같다.
그리고 로봇과 드론 관련 블로그를 분리시켜 좀 더 본격적으로 작업 내용을 포스팅할 예정이니 기대하시라~^^


블로그 이미지

마즈다

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

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개의 포스팅으로
계획 중) 전체 소스를 공유하도록 하겠다.

블로그 이미지

마즈다

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

3D 프린터 개봉기 및 간단 사용기 - USEED Creator mini


2016년, 아두이노를 처음 접하면서 나에겐 신세계가 열렸다!
애초에 현재의 직업인 개발자를 선택한 이유 중의 하나도 새로운(?) 무언가를 만들어낸다는 점이었는데 아두이노는
실물을 만지고 만들어낸다는 점이 더 역동적이고 흥미를 느끼게 한 것이다. 그리고 항상 좋은 소프트웨어는 좋은
하드웨어와 결합했을 때 그 진가가 발휘된다는 생각을 갖고 있던 나에게 아두이노는 더없는 도전의 대상이었다.


그러나 여기에도 장벽이 있었으니…알맹이는 어찌어찌 만들만한 기술을 익혔는데 이 알맹이를 실용적으로 포장할만한
껍데기는 도무지 제대로 만들 수가 없었다. 커터와 자 그리고 하드보드를 이용해서 쌩 노가다도 해보았고 돈을 들여
MDF로 레이저 커팅도 해보았으나 마음에 쏙 드는 결과물은 얻을 수가 없었다. 특히나 외장 문제로 가장 힘들었던 것이
바로 4족 보행 로봇과 싱글콥터(프로펠러 1개짜리 드론)를 만들 때였다. 이 두 작업은 외장 부품을 제대로 만들지
못해서 현재 중단 상태라고 봐도 과언이 아니다(사실은 본업과 관련된 프로젝트 때문에 손을 못대고 있는 부분이 더 크다). 그렇게 나에게 3D 프린터 장만은 중요한 숙원 사업이 되었다. 


그리고 드디어 그 숙원 사업을 달성하게 되었다.
오늘은 간단하게 개봉기와 샘플 출력기를 적어볼까 한다.
이 글은 아무런 협찬도 받지 않았으며 생애 첫 3D 프린터인 만큼 비교 대상도 없어 장점과 단점에 대해서는
완전히 개인적인 판단임을 참고하기 바란다.


USEED Creator mini


사실 그 동안 3D 프린터를 장만하지 못했던 것은 어처구니 없게도 공간의 문제였다. 사실 이미 banggood.com 같은
해외 사이트에 보면 10만원대 초반 가격대의 제품도 심심치않게 찾아볼 수 있을 만큼 이미 3D 프린터의 가격 장벽은
거의 없다고 보아야 할 것이다. 물론 출력물의 품질은 큰 문제가 되니 꼼꼼히 따져볼 필요가 있다. 그렇기에 아무래도
너무 싼 가격의 제품은 꺼려질 수밖에 없기도 하다. 아래 이미지는 banggood.com에서 15만원 ~ 25만원 사이의
3D 프린터를 검색한 결과 중 일부이다.




어쨌든 나에게 중요한 것은 그 무엇도 아니고 “공간”이었다. 그러다가 발견한 것이 바로 USEED라는 업체의
Creator mini라는 제품이었다(크레이터 미니라고 읽는다). 이 제품을 처음 접한 곳은 페이스북의 타임라인에 
게시된 광고였다.


처음 호감을 갖게된 것은 탄탄해보이는 외관과 완성품이라는 점이었다. 저가 모델의 대부분이 직접 조립을 해야
하는 경우가 많은데 아무리 아두이노로 무언가를 만드는게 좋아도 이건 또 별개의 문제다…-.- 차라리 모든 부품을
직접 하나 하나 구매해서 만들면 또 모를까…(사실 다음 아두이노 프로젝트로 계획하고 있는 것이 로봇 Arm 형태의
3D 프린터이다)


그리고 제품 판매처의 사용자 후기에 올라온 샘플 출력 이미지도 한몫 했다. 3D 프린터 무경험자인 내가 보기엔
더없이 훌륭한 품질을 보여주었다. 비슷한 크기의 XYZ사의 다빈치 나노도 고려를 했지만 샘플 출력물의 품질이
영 맘에 들지 않아 고민을 했었는데 크레이터 미니의 출력물을 보고 마음을 굳히게 되었다. 물론 설정에 따라 품질은
달라지고 또 실물과 사진은 차이가 있겠지만…


Creator mini의 스펙은 판매처 이미지로 대신한다.


Creator mini



개봉기


역시나 제품 사이즈는 아담했다. 포장 박스의 사이즈도 작아 사무실에서 물건을 받고 집으로 가져오는데도 큰 부담이 
없었다. 집에 들고올 때 손잡이 만드느라고 박스에 테이프가 조금 지저분하게 붙어있다…^^;;




드디어 박스 개봉!
가장 위에는 작은 박스가 하나 있는데 여기에는 기본 제공되는 필라멘트와 각종 도구들 및 부품들이 들어있다.
도구와 부품은 측면을 막아주는 투명 아크릴판, 정면의 투명 아크릴 문, 필라멘트 거치용 부품, 핀셋, USB 메모리,
USB 케이블, SD카드 1장과 SD카드 어댑터, 전원 케이블과 어댑터 등이다.


Creator mini boxCreator mini 부품Creator mini 부품



부품 박스를 들어내면 완충재가 보이고 이 완충재를 걷어내면 드디어 아담한 3D 프린터 본체가 보인다.




그리고 사용법을 적은 프린트물 1권, 제품 안내서 1 장, USEED의 타 제품에 대한 안내 브로셔 1장이 들어있다.




이제 본체를 꺼내보자! 탄탄한 철제 프레임이 내구성에 대한 신뢰감을 주었다. 색상이 검은색이어서 그런지 겉보기에는
크 작은 크기와는 달리 꽤나 묵직하고 듬직해보였다. 각각 정면, 상단, 측면에서 찍은 사진들이다.


Creator mini frontCreator mini topCreator mini side



후면에는 필라멘트 거치대를 위한 홈들과 필라멘트가 이동할 튜브가 붙어있다. 부품박스에 있는 필라멘트 거치대 부품
2개를 원통형으로 조립한 후 후면의 홈에 꽂고 시계방향으로 돌리면 거치대 설치는 완료다. 그런데 거치대가 꼭 맞지
않고 조금 헐렁거려 살짝 불안하기도 하다. 이렇게 한 후 필라멘트를 걸고 튜브 안으로 필라멘트를 넣어 반대편으로
나온 필라멘트를 익스트루더에 꽂아주면 된다(간단한 작업이라 사진은 생략…^^;;;).


Creator mini back



측면 보호 판넬은 네 귀퉁이를 동봉된 핀으로 고정하면 되고 문은 경첩부위에 위에서 아래로 꽂아주면 끝이다.
이렇게 해서 모든 조립이 완료되었다.


Creator mini full setting



이렇게 완성된 프린터를 책상위 올려주었다. 책상 위에 올리니 정말로 딱 알맞은 사이즈의 프린터를 샀구나 하는
생각이 절로 났다. 원래 사용하지 않는 17인치 LCD 모니터가 있던 자리인데 치우고나니 답답했던 분위기도 가시고
여러모로 좋았다.


Creator mini setting complete



평가


사실 장점이나 단점을 따지는 것이 무의미하게 느껴진다. 앞서 말한바와 같이 다른 기기를 사용해본 적이 없는 상태로
마땅히 비교할 대상도 없는데다가 단점으로 꼽을만한 것들이 대체로 제조사에서 ‘가정용, 교육용’으로 용도를 명시해서
판매하고 있는만큼 그 용도에서는 단점이 될 수 없다는 것이 내 생각이다. 따라서 몇가지 개인적으로 느낀점만 적어
보도록 하겠다.


가장 만족스러운 부분은 제조사의 서비스이다. 호불호가 갈릴 수도 있지만 택배사를 이용하지 않고 직접 직원이 배송을
하여 설치 지원도 해주고 4개정도의 출력물 샘플도 주고 네이버 톡을 이용하여 늦은시간까지 질문에 답변을 해주는 등
개인적으로는 매우 만족스러웠다.


앞에서도 계속 언급한 바와 같이 작은 사이즈는 공간 활용면에서 충분히 매리트가 있다. 다만 출력사이즈 역시
작으므로 이 점은 고려하여야 한다.


출력 품질은 개인적으로 매우 만족스럽다. 아래 이미지는 제조사에서 샘플 출력물로 제공해준 것 중 하나인데
꽤나 매끄러운 표면을 보여준다.




그리고 출력 시간과 소음인데…이 부분은 다른 제품과의 비교를 통해 평가해야 할 부분인 것 같아 간단히 샘플
출력 기준으로 테스트 내용만 적어보자면 위 사자의 경우 속도 25%로 하여 진행해보니 약 10시간 정도 걸릴 것 같고
(너무 오래 걸려서 프린팅 진행 중 중지시켜버렸다…-.-) 샘플로 들어있던 실사이즈 레고 캐릭터의 경우 속도를 50%로 
하여 약 4시간 정도 걸렸다. 자세한 내용은 출력기를 따로 포스팅하겠다. 소음은 개인차가 있을 것 같지만 나같이
무던한 성격인 경우 옆에 있어도 그냥저냥 참을만 하다^^;;


매뉴얼의 경우 품질이 다소 아쉬웠다. A4 용지에 인쇄한후 스테플러로 찍은 것으로 일단 인쇄된 사진이 알아보기
힘들었고 전체적으로 내용도 아쉬움이 있다. 최소한 전체 메뉴에 대한 설명이라도 있었으면 좋겠다. 모든 메뉴를
다 사용하는 것은 아니지만 그래도 눈에 보이는 메뉴들인데 각각이 어떤 기능을 하는지는 알아 두는 것이 좋지 않을까?
그밖에 실사용과 관련된 내용들은 유튜브에 동영상으로 잘 정리가 되어있어 이 점은 맘에 들었다.


그밖에 제품 디자인과 프레임은 크게 만족스럽지만 측면과 정면의 투명 아크릴은 조금 약해 보여 충격에 주의해야 할 
것같다.


슬라이서 프로그램으로는 Repetire라는 프로그램을 동영상과 매뉴얼로 설명하고 있으나 스펙에도 있듯이 CURA도
사용 가능하다.


정리


작년 말에 3D 프린터 구매가 힘들 것 같아 무료로 사용할 수 있는 공간을 알아본 후 성수 메이커스페이스에 교육
신청을 하고 이용해보려고 하였다. 그런데 교육 당일 갑자기 집에 우환이 생기는 바람에 사전 연락도 못한 채 교육을
불참하게 되었다. 그래서 더더욱 미련이 남았었는데…이렇게 구입을 하게 되었다.


아무리 명필이 붓을 가리지 않는다고는 하지만 커터와 자로 3D 프린터를 당해낼 수는 없는 것이다…ㅠ.ㅠ
하지만 좋은 도구라도 어떻게 사용하느냐에 따라 그 가치가 달라지는 것 역시 사실이다.


당분간은 본업과 관련된 공부로 아이들 장난감이 좀 뽑아줘야 할 것 같고 틈틈이 준비를 하여 올 하반기부터
본격적으로 그동안 미뤄두었던 로봇, 드론 등의 작업을 다시 시작하게 될 것이다. 3D 프린터를 이용하여 작업을
할 때마다 간단하게 포스팅을 하여 좀 더 많은 정보를 공유하도록 하겠다.

블로그 이미지

마즈다

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

TensorFlow







목차



3소스코드로 텐서플로우 맛보기 : [CNN] CIFAR-10 ~ cifar10_train.py (이번 글)





소스코드로 텐서플로우 맛보기 : [CNN] CIFAR-10


이제 가장 중요한 부분은 지나갔다.
생각해보면 전체적인 흐름을 먼저 살펴보고 세부적인 내용들을 분석했어야 할 것 같은데 순서가 거꾸로 되어버렸다.
아무래도 전체 포스팅을 마무리한 후 다시 한 번 되짚는 과정을 거쳐야 할 것 같다.


앞서 분석한 내용들은 모델을 구성하고 loss값을 생성하고 optimizer를 적용하는 구체적인 내용들이었다.
처음 딥러닝을 공부할 때는 각각의 단계가 거의 1줄 코딩이었던 것을 생각하면 이 소스는 매우 복잡해보인다.
그러나 세부적인 설정들이 더 추가되었을 뿐 근본적인 맥락은 다를 바가 없다.


자세한 내용은 복습 시간에 다시 살펴보고 오늘은 사용자와 인터페이스하는 소스를 살펴보도록 하자.


cifar10_train.py


소스 분석에 들어가기 전에 참고로 이 소스를 훈련시켰을 때의 정확도가 소스 첫머리의 주석에 표시되어있다.


accuracy



뭐 흙수저가 사용할 수 있을만한 장비는 아닌 듯하니 그냥 그런가보다 하고 넘어가자…-.-

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from datetime import datetime
import time

import tensorflow as tf

import cifar10


첫 3줄은 앞서도 보았듯이 python 2와 3의 호환을 위한 것이고 datetime과 time은 이름에서도 알 수 있듯이 날짜와
시간을 사용하기 위한 것으로 print를 하거나 수행 시간을 체크하기 위한 용도로 import 하였다. 마지막 2줄도 생략

FLAGS = tf.app.flags.FLAGS

tf.app.flags.DEFINE_string('train_dir', '/tmp/cifar10_train',
                           """Directory where to write event logs """
                           """and checkpoint.""")
tf.app.flags.DEFINE_integer('max_steps', 1000000,
                            """Number of batches to run.""")
tf.app.flags.DEFINE_boolean('log_device_placement', False,
                            """Whether to log device placement.""")
tf.app.flags.DEFINE_integer('log_frequency', 10,
                            """How often to log results to the console.""")


FLAG 역시 이전 포스팅에서 설명을 하였는데 그 아래 tf.app.flags.DEFINE_XXX로 지정한 이름으로 그 값을
사용할 수 있다. 즉, FLAG.train_dir은 '/tmp/cifar10_train’라는 값을 가지고 있게 된다. 두 번째 줄에 보면 학습
step을 1000000회로 설정하였다.


train()

# 학습을 실행시키는 함수
def train():
  """Train CIFAR-10 for a number of steps."""

# with tf.Graph().as_default() 문장은 지금까지 만들었던 모든 그래프 구성 요소(operation과 tensor들)을
# 하나의 전역 Graph 안에서 사용하겠다는 의미이다.  
  with tf.Graph().as_default():
# global_step은 학습의 step 카운트를 자동으로 관리해주는 tensor로 사용자가 별도로 step을 카운트
# 할 필요가 없이 이 global_step을 이용하면 된다.
    global_step = tf.train.get_or_create_global_step()

    # Get images and labels for CIFAR-10.
    # Force input pipeline to CPU:0 to avoid operations sometimes ending up on
    # GPU and resulting in a slow down.
# 학습을 수행할 장치를 지정. 첫 번째 CPU를 사용하도록 지정하고 있다. GPU를 사용하는 방법은
# cifar10_multi_gpu_train.py 소스를 참조하면 된다. 비록 multi gpu를 사용하는 소스지만...-.-
    with tf.device('/cpu:0'):
# 학습에 사용할 미니 배치 크기의 image와 label을 가져온다.
# 자세한 내용은 cifar10.py 소스의 distorted_inputs함수 참조
# http://mazdah.tistory.com/814
      images, labels = cifar10.distorted_inputs()

    # Build a Graph that computes the logits predictions from the
    # inference model.
# 학습 모델 생성. 자세한 내용은 cifar10.py 소스의 inference함수 참조
# http://mazdah.tistory.com/814
    logits = cifar10.inference(images)

    # Calculate loss.
# 손실값 계산. 자세한 내용은 cifar10.py 소스의 loss함수 참조
# http://mazdah.tistory.com/814
    loss = cifar10.loss(logits, labels)

    # Build a Graph that trains the model with one batch of examples and
    # updates the model parameters.
# 실제 학습을 수행할 operation 생성. 자세한 내용은 cifar10.py 소스의 loss함수 참조
# http://mazdah.tistory.com/814
    train_op = cifar10.train(loss, global_step)

# 아래 나오는 tf.train.MonitoredTrainingSession에 사용하기 위한 로그 hooker
# MonitoredTrainingSession.run() 호출에 대한 로그들을 hooking하는 역할을 한다.
# Pythons에서는 클래스 선언 시 ( )안에는 상속할 클래스를 지정한다. 즉, _LoogerHook 클래스는
# tf.train.SessionRunHook 클래스를 상속하여 만들어지게 되며 정의된 함수들은 이 클래스의
# 함수들을 Overriding해서 구현한 함수들이다.
    class _LoggerHook(tf.train.SessionRunHook):
      """Logs loss and runtime."""

# session을 이용할 때 처음 한 번 호출되는 함수
      def begin(self):
        self._step = -1
        self._start_time = time.time()

# run() 함수가 호출되기 전에 호출되는 함수
      def before_run(self, run_context):
        self._step += 1
        return tf.train.SessionRunArgs(loss)  # Asks for loss value.

# run() 함수가 호출된 후에 호출되는 함수
      def after_run(self, run_context, run_values):
        if self._step % FLAGS.log_frequency == 0:
          current_time = time.time()
          duration = current_time - self._start_time
          self._start_time = current_time

          loss_value = run_values.results
          examples_per_sec = FLAGS.log_frequency * FLAGS.batch_size / duration
          sec_per_batch = float(duration / FLAGS.log_frequency)

          format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f '
                        'sec/batch)')
          print (format_str % (datetime.now(), self._step, loss_value,
                               examples_per_sec, sec_per_batch))

# 분산 환경에서 학습을 실행할 때 사용하는 Session. 분산 환경에 대한 지원을 해준다.
# (Hook를 이용한 로그 관리, 오류 발생시 복구 처리 등)
    with tf.train.MonitoredTrainingSession(
        checkpoint_dir=FLAGS.train_dir,
        hooks=[tf.train.StopAtStepHook(last_step=FLAGS.max_steps),
               tf.train.NanTensorHook(loss),
               _LoggerHook()],
        config=tf.ConfigProto(
            log_device_placement=FLAGS.log_device_placement)) as mon_sess:
      while not mon_sess.should_stop():
# 드디어 마무리~ 학습 operation을 실제로 수행시킨다.
        mon_sess.run(train_op)



main(argv=None)

# CIFAR-10 데이터를 다운로드 받아 저장. cifar10.py 소스 참조
#  http://mazdah.tistory.com/814
cifar10.maybe_download_and_extract()

# 학습 수행 중의 로그를 저장할 디렉토리 생성. 기존에 동일 디렉토리가 있다면 삭제 후 생성.
if tf.gfile.Exists(FLAGS.train_dir):
  tf.gfile.DeleteRecursively(FLAGS.train_dir)
tf.gfile.MakeDirs(FLAGS.train_dir)

# 학습 시작
train()



정리


소스 길이에 비해 분석하는 데 너무 많은 시간이 걸렸다…ㅠ.ㅠ
지난 포스팅에서도 언급한 것처럼 매개 변수나 리턴값들이 모두 tensor 형태이고 TensorFlow의 API 문서에 있는
내용들이 수학적인 내용을 많이 포함하고 있어 다른 언어나 프레임워크의 문서를 읽는 해석하는 것에 비해 원문
해석도 꽤나 어려웠다.


포스팅한 내용에 부정확한 내용이 있을지도 모르겠기에 일단 CIFAR-10 예제 코드를 실제로 돌려보고
그 중간 로그나 결과 값들과 비교해가면서 다시 한 번 찬찬히 살펴볼 필요가 있을 것 같다. 그리고 추후에 이 소스에 
쓰인 API들을 별도로 정리해보겠다.


소스 중에는 아직 평가를 위한 cifar10_eval.py이 남아있는데 요건 우선 학습 관련 내용을 마무리하고 
진행해보도록 하겠다.

블로그 이미지

마즈다

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

Tensorflow









소스코드로 텐서플로우 맛보기 : [CNN] CIFAR-10


지난 포스팅에서 살펴보았던 cifar10_input.py는 데이터를 불러와서 이미지를 임의 조작한 후 배치 사이즈 크기로
나누어 담아 리턴해주는 기능을 하였다. 전체 프로세스의 가장 첫 단계라고도 할 수 있다.


오늘 살펴볼 cifar10.py는 가장 핵심적인 소스로 모델과 네트워크를 구성하는 내용이 주를 이루고 있다.
그만큼 코드의 길이도 전체 소스 중 가장 길다.


차근차근 살펴보도록 하자.


cifar10.py

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

지난 포스팅과 마찬가지로 위 3줄의 import 문은 python 2와 3의 호환성을 위한 것이다.


# OS의 자원을 사용할 수 있게 해주는 모듈
import os
# 정규표현식을 사용할 수 있게 해주는 모듈
import re
# python interpreter에 의해 관리되는 변수나 함수에 접근할 수 있도록 해주는 모듈
import sys
# tar 압축을 핸들링할 수 있게 해주는 모듈
import tarfile

여러 다양한 기능을 사용할 수 있게 해주는 import문들이다. os, sys, tarfile 등은 원격으로 cifar10 데이터셋을
다운로드 받기 위해 쓰인다.


# six 모듈은 python 2와 3의 함수를 함께 사용할 수 있도록 해줌. urllib는 URL 관련 모듈로 역시
# 데이터 셋 다운로드에 사용된다.
from six.moves import urllib
#텐서플로우 모듈
import tensorflow as tf

# 지난 포스팅에서 살펴본 cifar10_input.py 참조
import cifar10_input

몇가지 모듈이 추가로 import 되었으나 대부분 CIFAR10 데이터 셋을 원격으로 다운로드 받기 위한 것으로 이미
별도로 데이터 셋을 다운로드 받아두었다면 무시해도 좋을 것이다.


소스 앞부분에 영문으로 중요한 함수에 대한 설명이 주석으로 달려있다. 일단 간단하게 그 내용을 살펴보면 
다음과 같다.


  • inputs, labels = distorted_inputs( )
    : 학습에 사용할 데이터를 불러온다. 이 함수 안에서 cifar10_input.py에 있는 distorted_inputs( )
    함수를 호출하여 처리한다.
  • predictions = inference(inputs)
    : 파라미터로 전달되는 모델(inputs)에 대한 추론을 계산하여 추측한다.
  • loss = loss(predictions, labels)
    : 해당 라벨에 대해 예측값에 대한 총 손실 값을 구한다.
  • train_op = train(loss, global_step)
    : 학습을 수행한다.


위의 4개 함수가 가장 핵심적인 내용이라 할 수 있다.
이제 전체 코드를 차근차근 살펴보자.


# tf.app.flags.FLAGS는 상수의 성격을 갖는 값을 관리하는 기능을 한다.
# tf.app.flags.DEFINE_xxx 함수를 이용하여 첫 번째 파라미터에 사용할 이름을 넣고
# 두 번째 파라미터에 사용할 값을 설정하면 이후 'FLAGS.사용할 이름' 형식으로 그 값을
# 사용할 수 있다. 아래 첫 번째 코드의 경우 FLAGS.batch_size라는 코드로 128이라는 값을
# 사용할 수 있다.
FLAGS = tf.app.flags.FLAGS

# Basic model parameters.
tf.app.flags.DEFINE_integer('batch_size', 128,
                            """Number of images to process in a batch.""")
tf.app.flags.DEFINE_string('data_dir', '/tmp/cifar10_data',
                           """Path to the CIFAR-10 data directory.""")
tf.app.flags.DEFINE_boolean('use_fp16', False,
                            """Train the model using fp16.""")

# Global constants describing the CIFAR-10 data set.
# cifar10_input.py에 정의되어있던 값들
IMAGE_SIZE = cifar10_input.IMAGE_SIZE  #24
NUM_CLASSES = cifar10_input.NUM_CLASSES  #10
NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = cifar10_input.NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN  #50000
NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = cifar10_input.NUM_EXAMPLES_PER_EPOCH_FOR_EVAL #10000


# Constants describing the training process.
# tf.train.ExponentialMovingAverage에서 사용할 가중치 값
MOVING_AVERAGE_DECAY = 0.9999     # The decay to use for the moving average.
# 학습 속도 감소 후의 epoch 수
NUM_EPOCHS_PER_DECAY = 350.0      # Epochs after which learning rate decays.
# 학습률 감소를 위한 변수
LEARNING_RATE_DECAY_FACTOR = 0.1  # Learning rate decay factor.
# 초기 학습률
INITIAL_LEARNING_RATE = 0.1       # Initial learning rate.


음…수학을 깊이 들어가긴 싫지만 얼레벌레 그냥 넘어가는 것도 그러니 몇 가지 개념은 좀 알아보고 가자.


Exponential Moving Average

우선 이동평균(Moving Average)라는 것은 특정 기간동안 내에 측정된 값의 평균을 의미한다.
이 이동평균에는 단순이동평균, 가중이동평균, 그리고 여기서 사용하는 지수이동평균이 있는데
이 지수이동평균은 가장 최근 값에 더 큰 가중치를 두어 평균을 계산하는 방식이라고 한다.


일단 위 코드 중 MOVING_AVERAGE_DECAY 이후의 설정들은 모두 학습률 조정을 위한 것으로
train( ) 함수에서 사용을 하게 된다. 기본적으로 학습이 진행됨에 따라 학습률을 기하급수적으로 감소시켜
나가는 방법을 취하고 있다. 자세한 내용은 train( ) 함수 설명에서 다시 한 번 분석해보자.


# If a model is trained with multiple GPUs, prefix all Op names with tower_name
# to differentiate the operations. Note that this prefix is removed from the
# names of the summaries when visualizing a model.
# 멀티 GPU를 사용하여 병렬 처리할 때 작업 이름을 구분하기 위한 구분자...언제 써볼 수 있을까...ㅠ.ㅠ
TOWER_NAME = 'tower'

# CIFAR-10의 데이터 경로
DATA_URL = 'https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz'


이제부터는 함수를 하나 하나 살펴보도록 하겠다.


_activation_summary(x)

# 이 함수는 전체적으로 각 레이어들을 텐서 보드에 시각화하기 위한 summary를 만드는 작업을 한다.
"""Helper to create summaries for activations.
  Creates a summary that provides a histogram of activations.
  Creates a summary that measures the sparsity of activations.
  Args:
    x: Tensor
  Returns:
    nothing
  """
  # Remove 'tower_[0-9]/' from the name in case this is a multi-GPU training
  # session. This helps the clarity of presentation on tensorboard.
  tensor_name = re.sub('%s_[0-9]*/' % TOWER_NAME, '', x.op.name)
  tf.summary.histogram(tensor_name + '/activations', x)
  tf.summary.scalar(tensor_name + '/sparsity',
                                       tf.nn.zero_fraction(x))


_variable_on_cpu(name, shape, initializer)

# 파라미터로 전달받은 값을 이용하여 CPU를 이용하여 처리할 변수를 생성
"""Helper to create a Variable stored on CPU memory.
  Args:
    name: name of the variable
    shape: list of ints
    initializer: initializer for Variable
  Returns:
    Variable Tensor
  """
# 0번째 CPU를 사용하겠다고 지정
  with tf.device('/cpu:0'):
# python의 3항 연산 FLAGS.use_fp16이 true이면 tf.float16을 사용하고 false이면 
# else 뒤의 tf.float32를 사용. 이 코드에서는 FLAGS.use_fp16를 false로 설정했으므로
# tf.float32를 사용하게 됨
    dtype = tf.float16 if FLAGS.use_fp16 else tf.float32
# 파라미터로 전달된 변수가 이미 존재하면 재활용하고 존재하지 않으면 새로 만든다.
# 참고로 tf.Variable는 무조건 새로운 변수를 만든다. 자세한 사용법은 아래 링크 참조
# https://tensorflowkorea.gitbooks.io/tensorflow-kr/content/g3doc/how_tos/variable_scope/
    var = tf.get_variable(name, shape, initializer=initializer, dtype=dtype)
  return var


_variable_with_weight_decay(name, shape, stddev, wd)

# 위의 _variable_on_cpu(name, shape, initializer) 함수를 이용하여 정규화 처리를 한 변수를 생성.
"""Helper to create an initialized Variable with weight decay.
  Note that the Variable is initialized with a truncated normal distribution.
  A weight decay is added only if one is specified.
  Args:
    name: name of the variable
    shape: list of ints
    stddev: standard deviation of a truncated Gaussian
    wd: add L2Loss weight decay multiplied by this float. If None, weight
        decay is not added for this Variable.
  Returns:
    Variable Tensor
  """
# 데이터 타입 설정
# 세 번째 파라미터는 초기화 함수를 리턴하여 넘기는 것으로 truncated_normal_initializer는
# 정규분포 기반의 초기화 함수로 표준편차의 양 끝단을 잘라낸 값으로 새로운 정규분포를 만들어 
# 초기화 한다.
  dtype = tf.float16 if FLAGS.use_fp16 else tf.float32
  var = _variable_on_cpu(
      name,
      shape,
      tf.truncated_normal_initializer(stddev=stddev, dtype=dtype))

# L2 정규화 처리를 위한 코드. wd(아마도 Weight Decay)값이 None이 아닌 경우 if문
# 안의 코드를 수행하여 정규화 처리를 하고 그래프에 추가한다.
# tf.nn.l2_loss는 전달받은 텐서의 요소들의 제곱의 합을 2로 나누어 리턴한다.
  if wd is not None:
    weight_decay = tf.multiply(tf.nn.l2_loss(var), wd, name='weight_loss')
    tf.add_to_collection('losses', weight_decay)
  return var


위 함수들은 실제로 학습을 진행하면서 결과 값을 예측하는 과정에 사용되는 함수들이다.
자세한 내용들은 올바른 예측을 하기 위한 알고리즘을 구성하는 수학적인 내용이 포함되어있어
당장에는 이해가 쉽지 않다. 예를 들어 tf.truncated_normal_initializer의 경우 정규분포
그래프에서 2개 이상의 표준편차를 제거한 값들로 새롭게 만들어진 그래프로 초기화 한다고 해석이
되는데 사실 내용자체도 이해가 되지 않고 더 심각한 것은 수학적 개념이 포함된 영어를 해석하자니
제대로 해석이 되었는지도 모르겠다…ㅠ.ㅠ 일단은 학습을 최적화 시키고자 하는 목적으로 이러한
장치들을 사용한다는 것만 알아두면 되겠다.


distorted_inputs()

# cifar10_input.py에 있는 같은 이름의 함수를 이용하여 학습할 데이터를 불러온다.
"""Construct distorted input for CIFAR training using the Reader ops.
  Returns:
    images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
    labels: Labels. 1D tensor of [batch_size] size.
  Raises:
    ValueError: If no data_dir
  """

# 데이터 경로가 지정되어있지 않으면 에러~
  if not FLAGS.data_dir:
    raise ValueError('Please supply a data_dir')

# 데이터 경로를 조합하여 최종적으로 사용할 이미지와 라벨을 가져옴
  data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin')
  images, labels = cifar10_input.distorted_inputs(data_dir=data_dir,
                                                  batch_size=FLAGS.batch_size)

# FLAGS.use_fp16 값이 true이면 이미지와 라벨 텐서의 요소들을 tf.float16 타입으로 형변환 한다.
# 하지만 코드에는 False로 지정되어있으므로 무시.
  if FLAGS.use_fp16:
    images = tf.cast(images, tf.float16)
    labels = tf.cast(labels, tf.float16)
  return images, labels


inputs(eval_data)

# 역시 cifar10_input.py에 있는 같은 이름의 함수를 이용하여 평가할 데이터를 불러온다.
# eval_data라는 파라미터가 추가된 것 외에는 distorted_inputs 함수와 내용 동일
"""Construct input for CIFAR evaluation using the Reader ops.
  Args:
    eval_data: bool, indicating if one should use the train or eval data set.
  Returns:
    images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
    labels: Labels. 1D tensor of [batch_size] size.
  Raises:
    ValueError: If no data_dir
  """
  if not FLAGS.data_dir:
    raise ValueError('Please supply a data_dir')
  data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin')
  images, labels = cifar10_input.inputs(eval_data=eval_data,
                                        data_dir=data_dir,
                                        batch_size=FLAGS.batch_size)
  if FLAGS.use_fp16:
    images = tf.cast(images, tf.float16)
    labels = tf.cast(labels, tf.float16)
  return images, labels


inference(images)

# 이 소스의 핵심으로 예측을 위한 모델을 구성하는 함수
"""Build the CIFAR-10 model.
  Args:
    images: Images returned from distorted_inputs() or inputs().
  Returns:
    Logits.
  """
  # We instantiate all variables using tf.get_variable() instead of
  # tf.Variable() in order to share variables across multiple GPU training runs.
  # If we only ran this model on a single GPU, we could simplify this function
  # by replacing all instances of tf.get_variable() with tf.Variable().
  #
  # conv1
# convolution 레이어 1
  with tf.variable_scope('conv1') as scope:
# 커널(필터) 초기화 : 5 X 5 크기의 3채널 필터를 만들며 64개의 커널을 사용한다.
    kernel = _variable_with_weight_decay('weights',
                                         shape=[5, 5, 3, 64],
                                         stddev=5e-2,
                                         wd=None)
    conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME')


이 부분은 CNN의 핵심이며 가장 중요한 부분이므로 좀 더 상세하게 알아보자.
일단 필터(커널보다 친숙하므로 앞으로는 ‘필터’로만 표기하겠다. 또한 원칙적으로는 bias까지 +되어야 완성된
필터라 할 수 있으나 우선은 bias를 무시하고 생각해보자)가 하는 역할이 무엇인지부터 알아보면 말 그대로 
이미지에서 지정된 영역의 특징만을 ‘걸러내는’ 역할을 한다. 


그러면 어떤 방식으로 특징을 걸러내는가?
바로 머신러닝이나 딥러닝을 처음 배울때 배웠던 xW + b의 함수를 사용해서 처리한다. 일단 bias는 무시하기로
했으니 xW만 생각해본다면 입력받은 이미지에서 필터와 겹치는 부분을 x라 보고 해당 위치의 필터를 W라 보아
x1* W1 + x2 * W2 + … + xn * Wn이 되는 것이다. 만약 3 X 3 필터를 사용하였다면 아래와 같이 계산할 수
있다.


x1 * W1 + x2 * W2 + x3 * W3 + ... x9 * W9


여기에 만일 입력 채널(이미지의 색상 채널)이 3이라면 각 채널마다 위의 계산을 적용한 후 각 채널별 출력값을
최종 더해서 하나의 feature map을 만들게 된다. 결국 하나의 필터가 하나의 feature map을 만들게 되므로
만일 필터를 여러개 사용한다면 feature map의 개수도 필터의 개수와 동일하게 만들어지고 이 수가 곧 
feature map의 채널이 된다(그리고 이 각각의 채널에 bias를 +하게 된다). 


이 내용을 이해 못해 수없이 구글링을 했으나 적절한 자료를 찾지 못했는데 아래 이미지를 보고 쉽게 이해할 수 있었다.


CNN Filter feature map

이미지 출처 : http://taewan.kim/post/cnn/


이 코드를 가지고 계산을 해보면 24 X 24 크기의 3채널 이미지를 입력으로 받아 5 X 5 크기의 3채널 필터 64개를
사용하고 padding이 원본 크기와 동일한 출력이 나오도록 SAME으로 지정되었으므로 24 X 24 크기에 64 채널을
가진 출력이 나올 것이다. 여기에 배치 사이즈가 128이므로 최종 출력 텐서의 shape는 [128, 24, 24, 64]가 된다.


# 바이어스 초기화
# 채널의 개수가 64개이므로 bias도 64개 설정. biases는 64개의 요소가 0.0으로 채워진
# vector
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0))

# 가중치 + 바이어스. biases는 conv의 마지막 차수와 일치하는 1차원 텐서여야 한다.
    pre_activation = tf.nn.bias_add(conv, biases)

# 활성화 함수 추가
    conv1 = tf.nn.relu(pre_activation, name=scope.name)

# 텐서 보드에서 확인하기 위한 호출
    _activation_summary(conv1)

  # pool1
# 풀링 레이어 1
# pooling은 간단하게 말해 이미지를 축소하는 단계로 필터로 주어진 영역 내에서 특정한 값(평균,최대,최소)을
뽑아내는 작업이다. 일단 최대값을 뽑는 것이 가장 성능이 좋다고 하여 max pooling을 주로 사용한단다.
# 이 코드에서는 필터 크기가 3 X 3이므로 이 영역에서 가장 큰 값만을 뽑아 사용한다. stride는 2를 사용한다.
  pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                         padding='SAME', name='pool1')
  # norm1
# local response normalization라는 정규화 처리인데 ReLu 사용시 에러율 개선에 
# 효과가 있다고 하는데 이 부분은 좀 더 확인이 필요함
  norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                    name='norm1')

  # conv2
# convolution 레이어 2
  with tf.variable_scope('conv2') as scope:
    kernel = _variable_with_weight_decay('weights',
                                         shape=[5, 5, 64, 64],
                                         stddev=5e-2,
                                         wd=None)
    conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME')
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1))
    pre_activation = tf.nn.bias_add(conv, biases)
    conv2 = tf.nn.relu(pre_activation, name=scope.name)
    _activation_summary(conv2)

  # norm2
# local response normalization 2
  norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                    name='norm2')
  # pool2
# 풀링 레이어 2
  pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1],
                         strides=[1, 2, 2, 1], padding='SAME', name='pool2')

  # local3
# fully connected layer 
  with tf.variable_scope('local3') as scope:
    # Move everything into depth so we can perform a single matrix multiply.
    reshape = tf.reshape(pool2, [FLAGS.batch_size, -1])
    dim = reshape.get_shape()[1].value
    weights = _variable_with_weight_decay('weights', shape=[dim, 384],
                                          stddev=0.04, wd=0.004)
    biases = _variable_on_cpu('biases', [384], tf.constant_initializer(0.1))
    local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)
    _activation_summary(local3)

  # local4
# fully connected layer 2
  with tf.variable_scope('local4') as scope:
    weights = _variable_with_weight_decay('weights', shape=[384, 192],
                                          stddev=0.04, wd=0.004)
    biases = _variable_on_cpu('biases', [192], tf.constant_initializer(0.1))
    local4 = tf.nn.relu(tf.matmul(local3, weights) + biases, name=scope.name)
    _activation_summary(local4)

  # linear layer(WX + b),
  # We don't apply softmax here because
  # tf.nn.sparse_softmax_cross_entropy_with_logits accepts the unscaled logits
  # and performs the softmax internally for efficiency.
# softmax layer
  with tf.variable_scope('softmax_linear') as scope:
    weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES],
                                          stddev=1/192.0, wd=None)
    biases = _variable_on_cpu('biases', [NUM_CLASSES],
                              tf.constant_initializer(0.0))
    softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name)
    _activation_summary(softmax_linear)

  return softmax_linear


이 함수의 코드는 Convolutional layer > ReLu layer > Pooling Layer > Norm layer > Convolutional layer 
> ReLu layer > Norm layer > Pooling layer > Fully connected layer > Fully connected layer > 
Softmax layer의 순으로 구성이 되어있는데 이 중 Norm layer가 정확히 어떤 역할을 하는지는 아직 잘 모르겠다.
일단 ReLu를 보조하는 것 같은데 더 알아봐야겠다.


loss(logits, labels)

# 손실 값 계산을 위한 함수
# 아래 주석에서 보이듯 logits 파라미터는 inference() 함수의 리턴 값이고 labels는 distorted_input()
# 또는 input() 함수의 리턴 튜플 중 labels 부분이다. cross entropy를 이용하여 loss를 구한다.
"""Add L2Loss to all the trainable variables.
  Add summary for "Loss" and "Loss/avg".
  Args:
    logits: Logits from inference().
    labels: Labels from distorted_inputs or inputs(). 1-D tensor
            of shape [batch_size]
  Returns:
    Loss tensor of type float.
  """
  # Calculate the average cross entropy loss across the batch.
# 여기서는 sparse_softmax_cross_entropy_with_logits 함수가 사용되고 있는데
# softmax_cross_entropy_with_logits와의 차이라면 softmax_cross_entropy_with_logits
# 함수가 확률분포를를 따른다면 sparse_softmax_cross_entropy_with_logits는 독점적인 확률로
# label이 주어진다고 하는데...무슨 의미인지 잘 모르겠다...ㅠ.ㅠ 확인이 필요한 내용
  labels = tf.cast(labels, tf.int64)
  cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
      labels=labels, logits=logits, name='cross_entropy_per_example')
  cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
  tf.add_to_collection('losses', cross_entropy_mean)

  # The total loss is defined as the cross entropy loss plus all of the weight
  # decay terms (L2 loss).
  return tf.add_n(tf.get_collection('losses'), name='total_loss')


_add_loss_summaries(total_loss)

# 텐서 보드에 손실값 표시를 위해 손실 값에 대한 summary 추가하고
# 손실값들의 이동 평균을 구하여 리턴. 여기서 사용하는 이동 평균은 가장 최근 값에 가중치를 두는
# tf.train.ExponentialMovingAverage을 사용하여 구한다.
"""Add summaries for losses in CIFAR-10 model.
  Generates moving average for all losses and associated summaries for
  visualizing the performance of the network.
  Args:
    total_loss: Total loss from loss().
  Returns:
    loss_averages_op: op for generating moving averages of losses.
  """
  # Compute the moving average of all individual losses and the total loss.
  loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg')
  losses = tf.get_collection('losses')
  loss_averages_op = loss_averages.apply(losses + [total_loss])

  # Attach a scalar summary to all individual losses and the total loss; do the
  # same for the averaged version of the losses.
  for l in losses + [total_loss]:
    # Name each loss as '(raw)' and name the moving average version of the loss
    # as the original loss name.
    tf.summary.scalar(l.op.name + ' (raw)', l)
    tf.summary.scalar(l.op.name, loss_averages.average(l))

  return loss_averages_op


train(total_loss, global_step)

# 학습을 실행시키는 함수
"""Train CIFAR-10 model.
  Create an optimizer and apply to all trainable variables. Add moving
  average for all trainable variables.
  Args:
    total_loss: Total loss from loss().
    global_step: Integer Variable counting the number of training steps
      processed.
  Returns:
    train_op: op for training.
  """
  # Variables that affect learning rate.
# 미리 정의한 변수들을 이용하여 러닝 rate를 조정할 파라미터를 결정한다. 
  num_batches_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / FLAGS.batch_size
  decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY)

  # Decay the learning rate exponentially based on the number of steps.
# 학습 step이 증가할 수록 러닝 rate를 기하급수적으로 감소시키도록 처리한다.
# tf.train.exponential_decay 함수는 아래 식의 결과를 리턴한다.
# INITIAL_LEARNING_RATE * LEARNING_RATE_DECAY_FACTOR ^ (global_step / decay_steps)
  lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE,
                                  global_step,
                                  decay_steps,
                                  LEARNING_RATE_DECAY_FACTOR,
                                  staircase=True)
  tf.summary.scalar('learning_rate', lr)

  # Generate moving averages of all losses and associated summaries.
  loss_averages_op = _add_loss_summaries(total_loss)

# Optimizer 설정 및 텐서 보드에 표시하기 위한 summary 생성 후 추가
  # Compute gradients.
  with tf.control_dependencies([loss_averages_op]):
    opt = tf.train.GradientDescentOptimizer(lr)
    grads = opt.compute_gradients(total_loss)

  # Apply gradients.
  apply_gradient_op = opt.apply_gradients(grads, global_step=global_step)

  # Add histograms for trainable variables.
  for var in tf.trainable_variables():
    tf.summary.histogram(var.op.name, var)

  # Add histograms for gradients.
  for grad, var in grads:
    if grad is not None:
      tf.summary.histogram(var.op.name + '/gradients', grad)

  # Track the moving averages of all trainable variables.
  variable_averages = tf.train.ExponentialMovingAverage(
      MOVING_AVERAGE_DECAY, global_step)
  variables_averages_op = variable_averages.apply(tf.trainable_variables())

# tf.control_dependencies 함수는 오퍼레이션간의 의존성을 지정하는 함수로 with와 함께
# 사용하면 파라미터로 전달된 오퍼레이션이 우선 수행된 뒤 다음 문장, 여기서는 with문 아래 있는
# train_op = tf.no_op(name='train')이 수행된다. 
  with tf.control_dependencies([apply_gradient_op, variables_averages_op]):
    train_op = tf.no_op(name='train')

# 이미 알다시피 여기까지는 그저 그래프를 만든 것 뿐, 이제 tf.Session을 통해 run을 하면
# 이전까지 구성된 그래프가 실행된다. 실제로 실행시키는 내용은 cifar10_tranin.py에 들어있다. 
  return train_op


maybe_download_and_extract()

# 웹사이트로부터 CIFAR-10 데이터 셋을 다운로드 받아 사용할 경로에 압축을 풀게 하는 함수
# 이미 별도로 데이터 셋을 받아놓은 경우는 필요 없음
"""Download and extract the tarball from Alex's website."""
  dest_directory = FLAGS.data_dir
  if not os.path.exists(dest_directory):
    os.makedirs(dest_directory)
  filename = DATA_URL.split('/')[-1]
  filepath = os.path.join(dest_directory, filename)
  if not os.path.exists(filepath):
    def _progress(count, block_size, total_size):
      sys.stdout.write('\r>> Downloading %s %.1f%%' % (filename,
          float(count * block_size) / float(total_size) * 100.0))
      sys.stdout.flush()
    filepath, _ = urllib.request.urlretrieve(DATA_URL, filepath, _progress)
    print()
    statinfo = os.stat(filepath)
    print('Successfully downloaded', filename, statinfo.st_size, 'bytes.')
  extracted_dir_path = os.path.join(dest_directory, 'cifar-10-batches-bin')
  if not os.path.exists(extracted_dir_path):
    tarfile.open(filepath, 'r:gz').extractall(dest_directory)


정리


핵심적인 내용들이 대부분 들어있는 소스이다보니 잊어버린 내용 되찾으랴 또 생소한 API 확인하랴 시간이
많이 걸렸다.


단지 시간만 많이 걸린 것이면 그나마 다행이지만 꽤 많은 부분을 이해하지 못한다는 것은 참으로 난감한 일이
아닐 수 없다…ㅠ.ㅠ 그래도 기본적인 CNN의 흐름을 따라 어찌어찌 정리는 했지만 여전히 확인해야 할 내용들이
많이 남아있다. 특히나 API의 경우 기본적으로 파라미터나 리턴 값들이 텐서를 기반으로 하고 있는데다가 설명
또한 수학적인 내용이나 용어들을 포함하고 있다보니 java나 python 같은 프로그래밍 언어의 API 문서를
대하는 것과는 그 이해의 차원이 다르다.


일단 중요한 고비는 넘겼으니 다음 포스팅에서 학습을 진행하기 위한 메인 소스인 cifar10_train.py를
살펴보고 그 다음 마지막으로 cifar10_eval.py를 살펴본 후 이 소스 코드에 등장했던 API들을 모두
차근차근 번역해봐야겠다.

블로그 이미지

마즈다

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

티스토리 툴바