제텔카스텐은 독일어로 메모를 뜻하는 제텔과 상자라는 뜻의 카스텐을 합쳐서 만든 단어로, 독일 빌레펠트 대학 사회학 교수인 니클라스 루만이 개발한 지식 관리 시스템이다. 니클라스 루만 교수는 잘 정리된 제텔카스텐 체계를 활용해 약 30년 간 9만 개의 메모를 작성했고, 350편의 논문과 58권의 책을 집필했다고 한다.
블로그나 노션에 내용을 정리하다보면 카테고리를 분류하기 어려운 경우가 종종 있다. 예를들면 Python과 MongoDB 연동하기, Ubuntu에 Docker 설치하기 등 2가지 이상의 주제가 한 개의 글에 겹칠 때인데 블로그나 노션, 책과 같은 일반적인 글들은 목차와 범주가 있어 트리 구조로 글이 분류되어 있기 때문이다. 하지만 제텔카스텐은 원자화된 메모(노드)간의 연결을 통해서 그래프 형태로 관리되기 때문에 Python과 MongoDB, Ubuntu와 Docker처럼 각 주제를 메모하고 각 주제를 통합하는 메모를 작성하고 연결을 지정하여, 직접 분류하지 않아도 서로 연관되는 메모들끼리 모이도록 관리한다. 일종의 군집화나 수동으로 학습시키는 인공지능이라고 생각할 수 있을 것 같다. 내용이 충분하게 축적되어 있고 구조가 복잡할수록 제 2의 뇌라고 불릴만큼 효과적인 체계가 된다.
제텔카스텐을 제대로 구축하려면 지켜야할 기본적인 원리들이 있다.
원자성 : 각 노트에는 하나의 아이디어만 기록한다.
자율성 : 각 노트는 관련있는 다른 노트로 이동, 처리, 분류하고 연결할 수 있어야 한다.
항상 다른 노트와 연결하기 : 새로운 노트는 기존 노트와 반드시 연결해줘야 한다.
연결 이유 설명하기 : 다른 노트와 연결되는 이유를 항상 설명해야한다.
자신의 언어를 사용하기 : 작성할 내용을 내가 이해하고 나만의 언어로 다시 적어야 한다.
참고 자료 보관하기 : 자료의 정보와 출처를 기록한다.
나의 생각 기록하기 : 아이디어가 떠오를 때 원칙을 지켜 작성한다.
직접 분류하지 않기 : 카테고리를 직접 만들지 말고 연결에 더 신경써야 한다.
연결 노트 만들기 : 연관성이 없어 보이는 노트의 연관성을 찾았을 때, 노트를 설명해줄수 있는 연결 노트를 만든다.
목차 만들기 : 노트가 특정 주제로 통합되는 경우는 목차 노트를 만들어서 서술, 논쟁할 수 있도록 순서를 배치한다.
노트 삭제 절대 금지 : 오래된 노트를 삭제하지 말고 왜 문제인지 설명하는 새로운 노트와 연결한다.
과감하게 노트 만들기 : 사용하지 않는 노트가 작성된다고 하더라고 시스템에는 문제가 없다.
옵시디언은 노션과 같은 마크다운 에디터이다. 다양한 플러그인들과 연동할 수 있고 디스코드나 카카오 오픈 채팅 등 국내 사용자 커뮤티니가 존재한다. 옵시디언의 장점이자 단점은 로컬에서 작동된다는 점인데, 메모가 클라우드에 저장되는 노션과 달리 로컬에만 저장된다는 점은 장점이지만 인터넷을 통한 접근이 어렵다는 것과 백업, 동기화 등의 작업이 따로 필요하다는 점은 단점이라고 볼 수 있을 것 같다. 템플릿을 지정하여 노트의 양식을 정의해둘 수 있고 그래프 뷰 기능이 있어 메모간의 연결 구조를 UI로 볼 수 잇다. 제텔카스텐 기법을 적용하기에 적합한 지식 관리 툴이라고 생각한다.
아래는 옵시디언 사이트에 접속하면 가장 먼저 보이는 문구이다.
오픈소스는 아니지만 개인이 사용할 경우 무료이고 동기화 기능은 매월 10달러 정도의 비용이 필요하다. 옵시디언을 다루는 내용은 직접 툴을 사용해보고 추가로 작성할 예정이다. 다운로드는 아래 링크에서 할 수 있다. https://obsidian.md/
물론 위 내용을 공부한 내용들 중 시험에 나오지 않은 부분이 훨씬 많지만 부분점수가 존재하고, 기출범위가 넓은 실기 시험 특성 상 평소에 조금씩이라도 꾸준히 공부하는 방법이 유리할 것 같다. 그리고 프로그래밍과 SQL 문제만 다 맞춰도 합격 확률이 매우 올라간다. (개인적으론 프로그래밍도 1문제 틀렸고, 못적은 답도 많았지만 부분점수 덕분에 겨우 합격한 것 같다.)
2022년 3회 실기 가답안 및 문제
시험 직후 아래 사이트를 보고 가답안과 부분 점수를 계산했는데 거의 실제 문제와 유사하게 정리된 것 같다.
1. 기술 면접 : 직무 관련 부분으로 해당 기술 팀의 팀장 등 실무자와 면접을 진행한다. 2. 임원 면접 : 회사의 임원진과 진행하는 면접으로 인성과 태도, 조직과의 융화도에 초점을 둔 인성 면접이라고 생각 할 수 있을 듯 하다.
구조화 면접 : 면접관의 경험과 판단에 의한 면접은 확증편향으로 인한 잘못된 판단의 위험이 있다. 이런 문제를 해결하기 위해 지원자에게 미리 체계적으로 구조화된 질문을 던져 답변하는 방식을 기반으로 판단하는 구조화 면접을 기반한 채용 과정들이 도입된다. 아래는 구글이 생각하는 구조화 면접의 요소들이라고 한다.
1. 직무와 관련된 검증된 질 좋은 질문들 2. 면접관들의 기록 (지원자의 답변에 대해 종합적인 피드백 등) 3. 모든 평가자들이 표준화된 기준에 따라 점수를 받음 4. 면접관 교육
예상 질문 및 답변 ( 사람인 참고 )
간단하게 자기소개 먼저 해주세요 (1분 자기소개) 또는 지원동기가 무엇인가요?
이 회사에 적합한 인재라고 생각하는 이유가 있나요?
살면서 가장 힘들었던 경험이 무엇이고 있다면 어떻게 극복했나요?
갈등이나 불화를 겪은 경험이 있나요? 있다면 어떻게 해결했나요?
남들과 차별화되는 강점이 무엇인가요?
(공백기가 있다면) 공백기 동안 무엇을 했나요?
지원 직군에 가장 중요한 역량은 뭐라고 생각하나요?
전 직장에서 가장 크게 기여한 부분이 무엇인가요?
이전 직장에서 이해관계자들과의 관계를 어땠나요?
우리 회사의 경쟁사를 알고 있나요??
(첫 이직 또는 이직횟수가 적은 경우) 이직에 대한 계기, 그만둔 이유 ** 회사가 해결해 줄 수 없는 문제 제시 (학업, 가정사, 개인사, 업무발전 등)
다른 회사에 갈 수도 있었는데, 우리 회사에 오고 싶은 이유는?
우리 회사에 대해서 어떻게 생각 하나요?
입사 후 바라는 점이 있나요? (또는 입사 후 포부가 뭔가요?)
본인의 꿈이 뭔지 설명해 줄 수 있나요?
회사에서 어떤 걸 얻고 싶은지, 어떤 사람이 되고 싶은지 설명해줄 수 있나요 ?
회사 상품을 보면서 느낀 생각이 있나요? 우리 회사의 강약점이 뭐라고 생각하나요?
우리 회사의 경쟁력이 뭐라고 생각하나요?
야근에 대해서 어떻게 생각하나요?
상사와 의견대립이 있다면 어떻게 할 건 가요?
마지막으로 질문하고 싶은건 없나요? ** 회사에 대한 나의 적극성과 관심도 표현하기
구조화 면접 질문
대인관계 능력
어떤 일에 있어 다른사람들을 설득하여 목표를 이뤄낸 경험이 있나요?
다양한 상황이나 환경에 잘 적응한다는 걸 설명할 수 있는 경험이 있나요?
같이 일하는 사람들과 갈등이 있었던 적이 있나요? 있었다면 어떻게 행동했고 결과는 어땠나요?
당신은 주로 어떤 방법으로 갈등을 조절하나요? 갈등을 성공적으로 조정한 사례를 소개해주세요.
상대방과 협상했던 경험 중 가장 어려웠던 적이 있으면 소개해주세요. 당신은 어떻게 했고, 협상 결과는 어땠나요?
직무 수행 능력
동시다발적으로 일어난 일들을 처리해야 했던 경험과 해결했던 방법을 알려주세요.
살면서 중요한 결정을 내려야 했던 경험이 있나요? 있다면 한가지만 예로 들어주고, 현재엔 어떤 영향이 있는지도 말해주세요.
직무 분야에서 성장하기 위해 지난 2년간 노력했던 것 들을 2 가지 소개해주세요.
당신이 했던 일이 부정적인 피드백을 받았던적이 있나요?
자기개발을 위해 지금 하고 있는게 있나요?
미리 잠재적인 문제가 발생할 것을 예상하여 예방적 차원의 조치를 했던 경험이 있나요?
올바른 판단력과 논리적인 사고를 통해 문제를 해결했던 사례가 있다면 구체적으로 말해주세요.
유연성/ 적응력
지난 2년간 당신이 업무를 수행하며 변화를 주어야했던 것이 있다면 소개해주세요.
다른 사람을 변화시켜야 할 책임이 있었던 상황이 있나요? 있다면 어떤 역할을 수행했고 결과는 어떤가요. 다시 돌아간다면 다른 방법을 시도할 건가요?
지난 기간동안 이뤄낸 주된 변화가 있나요? 있다면 어떻게 이뤄냈나요?
프로젝트를 수행하면서 난관에 봉착했던 경험을 말해주세요. 어떻게 극복했나요.
시스템적 사고
현재 수행하는 업무가 조직의 임무와 목표를 달성하는데 어떤 영향을 주고 있다고 생각하나요.
당신의 현재 업무가 조직에 중요하다고 생각하나요? 그렇다면 왜 그렇게 생각하는지 알려주세요.
당신에게 기대된 수준 이상으로 직무를 수행한 경험이 있다면 알려주세요. 어떤 계기로 추가적인 노력을 하게 되었나요? 다른 동료들도 자발적으로 추가적인 노력을 한 걸 알고 있나요? 알고있었다면 피드백이 있었나요?
당신이 속한 조직이나 직무 영역의 성과를 높이기 위해 이루어낸 변화가 있다면 설명해주세요. 어떻게 그 아이디어를 떠올리게 되었고 실행을 위해 어떻게 했나요?
조직내에서 했던 결정이 조직 외적으로 의도치 않은 결과를 가져온 적이 있나요?
비전/ 목표
입사 후 포부가 무엇입니까?
입사 5년 후, 10년 후 자신의 모습은 어떨 것이라고 생각합니까?
본인의 직업관은 무엇입니까?
당신에게 일이 왜 중요합니까?
직장은 어떤 면을 보고 선택합니까?
일하는 목적이 무엇입니까?
과거 근무 경험에서 가장 크게 배운 점은 ?
어떤 회사가 훌륭한 회사라고 생각합니까?
인생에서 가장 필요한 사항은 무엇이라 생각하나요?
중소기업을 선택한 이유가 무엇입니까?
바람직한 사원상은 무엇이라고 생각합니까?
일과 사생활에 대해서 어떻게 생각합니까?
인생의 목표는 무엇입니까?
회사 근무를 하면서 가장 중요하다고 생가가하는 것은 무엇입니까?
기업의 사회적인 책임은 무엇이라고 생각합니까?
어떤 아이템을 가지고 어떤 일을 해보고 싶습니까?
본인이 리더로 추진했던 일이 있습니까? 있다면 어떤 성과가 나왔는지 말해보세요.
본인은 따라가는 스타일입니까, 아니면 주도하는 스타일입니까?
자신의 능력을 벗어난 업무가 주어진다면 어떻게 하겠습니까?
인생에서 가장 열정적인 순간은 언제였습니까?
어린 시절의 꿈이 있습니까?
인생 최종 목표(또는 꿈)은 무엇입니까?
조직적응력
어떤 경영 스타일일 때, 자신의 능력이 최대가 되나요?
상사의 말이 확실히 틀렸을 다면 어떻게 할건가요?
본인 만의 커뮤니케이션 방법이 있나요?
집안이 무너질 때 도와줄 친구는 몇 명인가요?
직원으로서 필요한 덕목이 무엇이라고 생각합니까?
입사 후 회사와 맞지 않는다면 어떻게 하시겠습니까?
어떤 유형의 사람을 싫어하나요?
어울리기 힘들었던 사람과 공동의 이익을 만들 수 있었던 경험이 있다면, 그 경험에 대해서 말해주세요.
조직에서 무언가 성취해본 경험이 있나요?
어떤 상황에서 스트레스를 받나요?
본인의 업무스타일은 어떤 유형인가요?
업무강도가 센 편입니다. 괜찮습니까?
상사와 의견이 다를 때는 어떻게 대처할 건가요?
상사가 부당한 업무지시를 시킨다면 어떻게 할 건가요?
남이 하기 싫어하는 일을 한 경험을 말해보세요.
노래방에서 몇시간이나 놀 수 있나요?
거래처와의 갈등이 있을 경우 어떻게 대처하겠습니까?
고객이 불만사항을 제기하면 어떻게 해결하겠습니까?
단체활동에서 의견 충돌이 일어날 경우 어떻게 해결하겠습니까?
대인관계에서 가장 중요하게 생가가하는 건 뭔가요?
오늘 면접 보는 지원자들 중 누가 제일 먼저 말을 걸었습니까?
관심사/ 가치관
봉사 활동을 특별히 많이 했는데, 기업의 사회 환원 활동에 대한 본인의 생각은 어떻습니까?
가장 존경하는 인물은 누구입니까?
가장 인상 깊게 본 영화가 뭐고 이유는 뭔가요?
가장 존경하는 인물로 부모님을 언급했는데, 이유가 있나요?
매일 아침 신물을 읽는다고 했는데, 오늘 아침 신문의 톱기사는 무엇입니까?
한 달에 책을 몇권 정도 읽나요?
본인만의 스트레스 해소법이 있나요?
정보를 수집할 때 효율적인 자신만의 방안이 있나요?
공익과 사익 중 무엇을 더 추구해야 한다고 생각합니까?
평소에 결정할 때 과감하게 하는 편입니까? 아니면 신중하게 하는 편입니까?
본인의 가치관에 대해 말해보고 그 가치관이 우리 회사에선 어떻게 발휘 될 것인지 설명해보세요.
만약 일할 때 로비나 뒷거래가 일어난다면 어떻게 해결할 건 가요?
돌발/ 창의성
당신이 면접관이라면 어떤 걸 중심으로 평가하겠습니까?
한 달을 시한부로 살 수 있고, 5000만원의 돈이 주어진다면 어떻게 사용할 건가요?
빨간 벽돌을 건축자재 외에 사용할 수 있는 용도를 최대한 많이 말해보세요.
읽지않은 이메일이 2000통 있습니다. 이중 300통만 답변이 가능하다면 어떤 것 부터 하겠습니까?
전국에 치킨집이 몇개 있을까요?
서울 시내의 중국집 전체의 하루 판매량을 논리적인 근거를 제시하여 정산해보세요.
아이들을 웃게하는 방법이 뭘까요?
자신이 얼마짜리 사람이라고 생각하나요?
압박질문
다른 회사도 지원했습니까?
다른 회사는 전형이 어디까지 진행되었습니까?
다른 회사에도 합격하면 어디에 입사할 건가요?
열심히 일하겠다고 밝혔는데, 구체적으로 어떻게 열심히 할건가요?
우리 히사에 지원했다가 떨어진 이력이 있는데, 그 때는 왜 떨어진거 같나요? 다시 지원한 이유가 있나요?
Socket 으로 웹 페이지를 크롤링하는 HTTP 클라이언트를 직접 구현해야 한다면, 어떻게 하시겠습니까?
데이터 베이스
noSQL 과 RDB 의 특징, 차이에 대해 말씀해주세요. 어느 상황에 어떤 데이터베이스를 쓰는게 좋겠습니까?
데이터베이스가 자료를 빠르게 검색하기 위해 어떤 일을 할까요? 최대한 상세하게 설명해주세요.
RDBMS 의 여러 JOIN 중 아무거나 하나 골라서, 그림으로 설명해 주실 수 있습니까?
데이터베이스 샤딩 / 파티셔닝에 대해 설명해주세요.
데이터베이스가 Index 를 이용해 자료를 빠르게 검색하는 과정을, 우리 할머니도 알아들을 수 있도록 설명해 주시겠어요?
Stored procedure 를 이용한 시스템을 어떻게 유지보수 할 수 있습니까? Stored procedure 의 장점과 단점에 대해 말씀해 주세요.
Optimistic Locking(낙관적 락) 과 Pessimistic Locking(비관적 락) 에 대해 설명해주세요. 각각의 락을 사용할 상황 또는 제품 사례를 말씀해주세요.
어떤 서비스의 이용자 테이블이 있다고 가정합시다. 이용자 id 를 여러 테이블에서 FK 로 참조하고 있습니다. 그런데 이용자 테이블에 환경설정, 개인정보 등 정보를 한데 저장하다보니 Column 이 40개가 넘게 있는 상태입니다. 문제를 진단해 주시고, 해결 방안도 제시해 주시기 바랍니다.
Slow query 를 발견하고, 수정한 경험에 대해 말씀해 주세요.
저희는 도축장에서 전달받은 원육을 소매점에 도매하는 서비스를 운영하고 있습니다. 여기서, 원육과 소매점을 어떻게 테이블로 모델링 하시겠습니까?
어플리케이션의 문자열(String) 을 데이터베이스에 저장하기 위해 고려해야 할 사항에는 어떤 점이 있을까요?
Big data 를 다루려면 RDBMS 보다 NoSQL 이 더 좋다는 말이 많습니다. 그렇다면 large data set 에는 항상 NoSQL 만 써야 할까요? 반드시 RDBMS 만을 이용해 large data set 을 다루려면 어떻게 해야 할까요?
실 서비스의 데이터를 조작하고, 또 조회해야 하는 Admin app 을 만들어야 한다면, 어떻게 구현하시겠습니까?
데이터 마이그레이션 기간 동안 서비스 순단을 최소한으로 하고 싶습니다. 이런 요구사항에 맞는 테이블을 어떻게 설계하시겠습니까?
(JPA 경험자 한정) JPA 를 이용할 때, JPQL 을 쓰는 경우가 종종 있는데, JPQL 을 쓰시며 좋았던 점과 불편했던 점을 말씀해 주세요.
(JPA 경험자 한정) JPA 의 @Entity 란 뭔가요? 도메인 객체와 Entity 객체를 각각 어떻게 정의하시겠습니까?
(JVM 경험자 한정) 배열과 ArrayList, LinkedList 의 차이점은 무엇인가요?
자료 구조 및 알고리즘
지금 사용하시는 스마트폰의 화면을 캡쳐하는 프로그램을 만들어야 한다면 어떻게 하시겠습니까?
전화번호와 같은 민감한 정보를 어떻게 저장하는게 좋을까요? 관리자조차 모르게 저장하고 싶다면?
암호화란 무엇일까요? 알고 계신 암호화 방식 아무거나 하나만 설명해주세요.
어떤 알고리즘을 도입하기 전에, 성능을 판별해 보고자 합니다. 어떤 방법을 활용해, 어떤 기준으로 알고리즘의 효율을 판단하시겠습니까?
공개 키 암호화와 비밀 키 암호화에 대해 설명해주세요.
캡슐화에 대해 설명해주세요.
캐시란 뭐고, 어떤 목적으로 쓰는 건가요?
List 와 Set 의 차이에 대해 설명해주세요.
이진 탐색의 최선 / 최악의 경우에 대해 말씀해주세요.
손실 압축과 무손실 압축의 차이에 대해 설명해주세요.
RSA 로 공개 키를 만들 때, 키 길이를 정하는 기준이 있습니까? 키 길이를 길게 하면 암호화 문제를 완벽 해결할 수 있나요?
순환 Queue 를 만드려면 어떻게 하시겠습니까? 그리고 어떤 Queue 또는 Graph 가 무한 순환 구조라는 것을 어떻게 판단하시겠습니까?
압축 알고리즘을 설계하라는 요구사항이 들어왔다면, 어떻게 구현하시겠습니까?
전화번호부 앱을 만든다고 가정하겠습니다. 1명당 1KiB의 정보를 갖도록 모델을 설계했습니다. 앱이 온전히 사용할 수 있는 메모리가 4메가인 기기에서, 10000명(총 10메가)의 이용자 정보를 검색할 수 있도록 구현해야 합니다. 어떻게 하시겠습니까?
(JVM 경험자 한정) 배열과 ArrayList, LinkedList 의 차이점은 무엇인가요?
디자인 및 테스트
Singleton pattern 이란 무엇이고, 어떤 장점과 단점이 있을까요?
싱글턴 코드는 테스트를 어렵게 만드는 문제가 있습니다. 왜 그럴까요? 싱글턴이 좋지 않다는데 왜 스프링 프레임워크 같은 녀석들은 별다른 규칙이 없을 때 기본으로 Singleton bean 을 만들까요?
좋은 Test 라고 평가할 수 있는 가장 중요한 요소를 말씀해주세요. 후보자님은 어떤 Test 를 좋은 Test 라고 정의하십니까?
Callback function(또는 Closure) 이 뭔가요? 주의할 점이 있을까요?
Mutable, Immutable 이란 뭔가요? 각각은 어떤 특징이 있을까요?
Acceptance, Smoke, End-to-End, Integration, Unit test 같은 용어들을 본인만의 방법으로 구분짓는 기준이 있습니까?
메소드의 파라미터로 전달한 객체를 메소드 내에서 마음대로 바꾸지 못 하게 하려면 어떻게 코딩하는게 좋을까요?
왜 메소드가 파라미터를 조작하는 것이 문제가 될까요? 문제가 아닐 수도 있지 않을까요?
음료수 자판기에 탑재한 소프트웨어를 제작했다고 가정해 보겠습니다. 작성하신 소프트웨어의 통합 테스트 시나리오를 어떻게 작성하시겠습니까? 생각나는대로 말씀해 주세요.
현재 다루시는 플랫폼에서의 테스트 자동화를 어떻게 구축 하시겠습니까?
test code 를 작성하는 본인만의 기준이 있습니까? test 실행 속도를 높이려면 어떤 방법이 좋을까요?
blackbox testing, whitebox testing 의 차이에 대해 설명해주세요. 어떤 상황에서 어떤 테스트 방법을 사용하시겠습니까?
상속의 이점 중 "코드의 중복을 줄여준다" 는 말이 있습니다. 그런데 코드 중복을 줄이기 위해서 상속을 쓰는 것은 매우 좋지 않은 코딩 방식이라고 저희는 생각합니다. 이에 대한 후보자님의 의견을 듣고 싶습니다.
Java 의 Marker interface (아무 메소드도 없이 타입만 있는) 에 대해 어떻게 생각하시나요?
코드 응집성(cohesion)이란 말을 어떻게 설명하실 수 있습니까? 응집도가 낮은 코드와 높은 코드를 예를 들어 설명해 주세요.
NodeJS 로 실행하는 서버와 통신하는 Spring 또는 Python 서버를 구현할 때, 어떻게 하시겠습니까? HTTP 외의 방법을 쓰고 싶다면 어떻게 해야 할까요?
네이버 같은 서비스에서 IP 주소가 바뀔 경우 접속 경고 등의 오류를 발생시킨다. 어떻게 구현하시겠습니까?
서비스의 memory leak 을 어떻게 판단하고, 해결하시겠습니까?
우리 서비스가 대 성공해서 이용자가 4000만이 되었다고 가정합니다. 이용자 4천만 돌파 기념으로 선착순으로 접속한 사용자에게 보너스 포인트를 주는 이벤트를 운영하려 합니다. 모든 이용자들에게 공평하게, 플랫폼이 제공하는 Push 를 보내려 하는데요. 이 경우, 어떤 점들을 고려해야 할까요?
MSA vs Monolithic 을 선택하는 기준이 있습니까?
M 인프라 시스템이 A 시스템 대비 가격이 많이 싸졌다고 가정해 보겠습니다. 우리의 인프라 시스템을 A 에서 M 으로 최대한 빨리 바꾸려면 어떤 점을 고려해야 할까요?
우리 앱의 어떤 페이지(또는 특정 view) 의 로딩이 매우 늦다면 어떻게 개선할 수 있을까요?
우리가 사용하는 앱들의 API 는 예고없이 바뀌기도 합니다. 외부 API 가 마구 변경되는 상황에서도 우리 앱이 크래시 나지 않게 하려면 어떻게 해야 할까요?
제작한 애플리케이션이 얼마나 사용자 친화적인지를 측정할 수 있는 방법이 있을까요?
Java / JVM
JVM 에서의 autoboxing 이란 어떤 현상을 말하는 걸까요?
interface default implementation 이란? abstract class 를 상속받는 것과 기본 구현을 들고 있는 interface 를 implements 하는것은 어떤 차이가 있나요?
Java stream method 중 map 과 flatMap 의 차이에 대해 설명해주세요.
메소드에서 리스트 타입의 파라미터를 받을 때, ArrayList - List - Collection - Iterable 처럼 구체 타입 뿐 아니라 상위 타입도 받을 수 있습니다. 컬렉션을 받는 어떤 API 를 구현하실 때 구체 타입의 API 디자인을 선호하는지, 추상 타입의 API 디자인을 선호하는지를 설명해 주세요. 왜 그런 선택을 하시나요?
Java 의 equals 와 == 의 차이에 대해 설명해주세요. Kotlin 의 == 와 === 는 어떤 차이가 있나요?
스프링의 @Autowired 를 가급적 쓰지 말라는 이야기가 종종 들리는데 원인이 뭘까요?
final 키워드를 변수, 메소드, 클래스에 선언하는 것은 어떤 의미가 있습니까?
synchronized 를 메소드에 선언하는 것과, 특정 객체에 선언하는 것은 어떤 차이가 있습니까?
Reflection 을 유용하게 사용하는 사례를 말씀해 주세요.
JDK/JVM 은 대표적으로 OpenJDK 와 Oracle JDK 로 나뉘는데요, 업무에 어떤 JDK 를 사용하시겠습니까? 선택의 이유를 말씀해 주세요.
hashCode / equals 메소드의 역할에 대해 아시는 내용을 최대한 설명해주세요.
Java 의 Collections.unmodifiableList 같은 API 를 이용해 List 같은 collection 을 변경 불가능하게 만들 수 있습니다. 그렇다면 이 API 를 사용하면 immutability 를 달성할 수 있을까요?
다음 싱글턴 코드의 어떤 점을 개선하실 수 있습니까? (개선이 필요 없을 수도 있음 / 왜?)
class MySingleton {
private static MySingleton instance;
public static synchronized MySingleton getInstance() {
if (instance == null) {
instance = new MySingleton();
}
return instance;
}
}
java 9 이상에 도입된 추가 기능들 중 마음에 드는거 아무거나 하나만 설명해주세요.
민감한 정보를 String 으로 저장하는 것과, char[] 또는 StringBuilder/StringBuffer 같은 클래스로 저장하는 것은 어떤 차이가 있나요?
크기를 지정하지 않고 ArrayList 를 new 로 생성하면 크기 10의 ArrayList 가 생성됩니다. Array 는 크기를 넘길 수 없는데 반해 ArrayList 는 꽉 찬 List 에 element 를 추가로 더할 수 있습니다. 그렇다면 10개의 element 를 채워넣은 ArrayList 의 11번째 element 을 add 하기위해 어떤 일이 일어나는지 설명해주세요.
java.lang.String 의 hashCode 구현에 대해 고찰해 봅시다. 왜 그런 구현일지, 문제점은 없을지 이야기해주세요.
lambda 와 메소드 1개만 있는 익명 클래스 직접 선언은 문법적 차이 외에 어떤 내부적인 차이가 있을까요?
Java generics 에는 primitive type 을 쓸 수 없는 문제가 있습니다. 왜 그럴까요? 어떻게 해결할 수 있을까요?
I/O 를 Java nio 로 코딩할 때 주의점은 어떤게 있을까요?
Java 는 Pure OOP 언어가 아니라고 하는데, 왜 그런 걸까요?
java.lang.String 의 length 메소드는 정확한 결과를 반환하지 않는 경우가 종종 있습니다. 정확한 의 의미란 무엇이고, 왜 그럴까요?
Maven 이나 Gradle 이, 의존성 선언한 artifact 들을 찾는 과정에 대해 설명해주세요.
java.util.Property extends Hashtable, java.util.Stack extends Vector 같은 클래스는 상속으로 망한 대표 사례입니다. 이유를 설명해 주세요.
Spring boot 가 stereotype annotation 을 붙인 클래스들을 어떻게 찾고 bean 으로 등록하는지 그 과정을 최대한 상세하게 설명해주세요.
Spring 은 @Transactional 어노테이션 붙인 메소드를 어떻게 찾고 트랜잭션을 처리하나요? 그 내부 구현을 상세하게 설명해 주세요.
메소드에 @Transactional 을 붙이는 것과, TransactionTemplate 을 사용해 트랜잭션을 직접 제어하는 것에는 어떤 차이가 있나요? 어떤 방식을 더 선호하시는지 그 이유도 함께 설명해 주시기 바랍니다.
Kotlin
Kotlin 으로 작성한 jvm target 코드는 숨은 비용이 있습니다. 어떤 숨은 비용을 말하는걸까요? 그럼에도 불구하고 Kotlin 을 써야 할까요?
Kotlin extension function 이 실제 native code 로 바뀔 때 어떤 형태로 바뀌는지 설명해주세요.
Python
object 의 기본 메소드인 __eq__, __hash__ 에 대해 설명해 주세요. 두 메소드를 모두 구현할 때, 어떤 점을 주의해야 할까요?
Memory leak 을 유발하는 python 코딩 패턴의 사례를 말씀해주세요.
yield 키워드의 역할에 대해 설명해주세요.
Global Interperter Lock 에 대해 설명해주세요.
Node.js
Promise 에 대해 설명해 보세요.
Typescript 의 type 과 interface 의 공통점 및 차이점을 설명해 보세요.
Typescript 의 interface 의 특징? 컴파일 이후에는 어떻게 되는지 말씀해주세요.
Javascript 와 Java 의 차이를 우리 할머니도 알아들으실 수 있도록 설명해주실 수 있습니까?
Map 의 키로 number, string 대신 object 를 쓰고 싶다면, 어떤 점을 고려해야 합니까?
Prototype 기반 상속과 일반적인 OOP 에서의 상속은 어떤 차이가 있습니까?
Android
비동기로 처리한 작업을 UI 에 표시하기 위해 어떤 일이 필요한지 설명해주세요.
안드로이드 Activity 처리 결과를 다루는 onActivityResult` 메소드는 왜 deprecated 처리되었을까요?
ViewGroup 내에 선언한 View 들에 onClickListener 를 선언할 경우 안드로이드가 이벤트를 어떻게 핸들링하는지 설명해 주시기 바랍니다.
systrace 가 뭐고, 결과 분석은 어떻게?
Memory leak 을 유발하는 coding pattern?
Dagger 를 왜 쓸까요? 다른 대안은 없나요?
Android HAL(Hardware Abstraction Layer) 에 대해 설명해주세요.
ios
비동기로 처리한 작업을 UI 에 표시하기 위해 어떤 일이 필요한지 설명해주세요.
Web
비동기로 처리한 작업을 UI 에 표시하기 위해 어떤 일이 필요한지 설명해주세요.
DOM 의 class 와 id 의 차이?
Event bubbling 과 Event capturing 에 대해 설명해주세요.
웹 UI 프레임워크들은 SPA 로 개발하는게 2022년 현재 대세인데 native app 에서는 그런 방식이 일반적이지 않다. 왜 그럴까요?
flutter/RN 이 제공하는 hot reload 와 hot restart(live reload) 의 차이에 대해 설명해주세요.
yfinance, yahoo_fin 등의 Python 라이브러리로 시장 정보를 받아와 작성된 알고리즘을 테스트한다.
Ticker 별로 적합한 형태의 트레이딩 알고리즘을 개발하여 시뮬레이션해본다.
최적/최고 수익을 내는 파리미터와 알고리즘, 티커를 찾아내어 실거래에 적용한다.
알고리즘
존버, 물타기
기본적인 알고리즘의 전체 구조는 투자시 일반적으로 사용되는 존버(대기)와 물타기(추가 매수) 등을 기반으로 시작했다.
처음 작성된 알고리즘은 거래 시작 시점부터 현재까지 누적된 비율을 계산하여 미리 입력해둔 특정 값에 맞춰졌을 경우 매도 또는 매수를 판단하여 알려준다. 예를들면 아래 코드에서는 '변화심한그래프.거래행동판단()' 함수가 해당 기능을 담당하며 현재 티커의 주가를 입력받으면 클래스 내부에 저장된 이전 기록과 비율을 읽고 계산하여 대기(0), 매수(+a), 매도(-)를 결정해준다.
소스 코드
hwan_yfinance_module.py
from abc import *
import matplotlib.pyplot as plt
import yfinance as yf
from datetime import *
from datetime import timedelta
class yfinanace_Main(metaclass=ABCMeta):
@abstractmethod
def 현재가치판단(self, Ticker):
pass
@abstractmethod
def Date_Save(self):
pass
@abstractmethod
def Data_Load(self):
pass
# 상승장 형태에서 변화가 비교적 심한 종목일 경우 적용
class 변화심한그래프(yfinanace_Main):
# 초기 값 지정
def __init__(self):
self.int_보유주식수 = 0
self.int_전일가 = 0
self.dou_누적률 = 0.0
self.dou_전일기준등락률 = 0
self.dou_고정비율 = 20.0 # 주식 종목별 변경 필요
# set
def set_int_보유수량(self, int_보유주식수):
self.int_보유주식수 = int_보유주식수
def set_dou_누적률(self, dou_누적률):
self.dou_누적률 = dou_누적률
def set_int_보유수량(self, int_보유주식수):
self.int_보유주식수 = int_보유주식수
def set_int_전일가(self, int_전일가):
self.int_전일가 = int_전일가
def 내부변수_보여주기(self):
print("현재 보유수량 : ", self.int_보유주식수, ", 누적률 : ", self.dou_누적률,
", 전일가 : ", self.int_전일가)
# 현재 시세를 입력받아 매수, 매도, 대기 를 판단해서 알려주는 함수
def 거래행동판단(self, int_주가):
# ex) 현재 주가가 8000원, 전일 가격이 10000원이면 전일기준 등락률은 -20% (-2000 / 10000 * 100)가 됨
self.dou_전일기준등락률 = ((int_주가-self.int_전일가)/self.int_전일가) * 100
# 전일 등략률을 누적함
self.dou_누적률 = self.dou_누적률 + self.dou_전일기준등락률
# 내부 변수 현황 출력
#print("현재가 : "+ str(int_주가) +", 전일가 대비 등락률 :" + str(dou_전일기준등락률))
#self.내부변수_보여주기()
# 누적률이 고정비율 이상인 경우 전량 매도 후 공통 변수 초기화
if self.dou_누적률 >= self.dou_고정비율:
int_행동 = -1 * self.int_보유주식수
self.dou_누적률 = 0.0
self.int_보유주식수 = self.int_보유주식수 + int_행동
# 누적률이 -20%(-고정비율) 이하인 경우, 현재 누적률을 보유 주식수에 곱한 수 만큼 추가 매수
elif self.dou_누적률 <= -1 * self.dou_고정비율:
int_행동 = int(self.int_보유주식수 * ((self.dou_누적률/100) * -1))
self.set_int_보유수량(self.int_보유주식수+int_행동)
# 누적률이 +고정비율과 -고정비율 사이일 경우(20% ~ -20%) 행동은 없음
else:
int_행동 = 0
# 결정된 행동을 반환
return int_행동
def 현재가치판단(self, Ticker):
print("미구현 동작입니다.")
def Date_Save():
print("미구현 동작입니다.")
def Data_Load():
print("미구현 동작입니다.")
def Simulate_변화심한그래프(str_ticker, str_startdate):
#str_ticker = "GOOGL"
#str_startdate = "2021-03-01"
str_enddate = datetime.now()
yf_Ticker = yf.Ticker(str_ticker) #AAPL, TSLA, GOOGL
close_TickerData = yf_Ticker.history(start=str_startdate, end=str_enddate, interval="1d")['Close']
int_최초구매수량 = 10 # 주식 종목별 최적화 필요 변수
dou_시작주가 = close_TickerData[0]
dou_최초투자금 = int_최초구매수량 * dou_시작주가
# class set
f = 변화심한그래프()
f.set_int_보유수량(int_최초구매수량)
f.set_int_전일가(dou_시작주가)
print("\n초기값")
print("Tiker :" + str_ticker + ", Start : " + str_startdate + ", Now : " + str_enddate.strftime('%Y-%m-%d') )
f.내부변수_보여주기()
print("")
# 입력 기준 7일 단위로 값 가져오기
list_7일단위값 = []
for i in range(1, len(close_TickerData)):
if i%7 == 0:
list_7일단위값.append(close_TickerData[i])
# 시작
cnt = 1
int_전체추가투자금액 = 0
# 시뮬레이션 Start
for int_현재주가 in list_7일단위값:
int_행동 = f.거래행동판단(int_현재주가)
if int_행동 == 0:
print(str(cnt) + "회 행동 : 대기")
print("현재가 : "+ str(int_현재주가) +", 전일가 대비 등락률 :" + str(f.dou_전일기준등락률))
print("현재 보유수량 : ", f.int_보유주식수, ", 누적률 : ", f.dou_누적률, ", 전일가 : ", f.int_전일가)
elif int_행동 > 0:
print(str(cnt) + "회 행동 : ", int_행동, "주 매수 -> 현재보유 주식 수(주) : " + str(f.int_보유주식수) + ", 추가 투자금($) : " + str(int_행동 * int_현재주가))
int_전체추가투자금액 += (int_행동 * int_현재주가)
else:
print(str(cnt) + "회 행동 : 전량매도(", int_행동, "주), 매도 금액($) : " + str(int_행동*int_현재주가*-1))
print(" 투자 기간 : " + str_startdate + " ~ " + (datetime.strptime(str_startdate,"%Y-%m-%d") + timedelta(days=(cnt*7))).strftime('%Y-%m-%d') )
print(" 최초 투자금액 : " + str(dou_최초투자금))
print(" 추가 투자금액 : " + str(int_전체추가투자금액))
print(" 전체 투자금액 : " + str(dou_최초투자금+int_전체추가투자금액))
print(" 손익 : " + str((int_행동*int_현재주가*-1) - (dou_최초투자금 + int_전체추가투자금액)))
break
f.set_int_전일가(int_현재주가)
print("")
cnt += 1
# 데이터 셋 그래프
plt.plot(list_7일단위값)
plt.show()
make_money.py
#-*- encoding: utf-8 -*_
import hwan_yfinance_Module as my_class
str_startDate = "2020-01-01"
arr_Ticker = ["GOOGL", "AAPL", "TSLA"]
for str_Ticker in arr_Ticker:
my_class.Simulate_변화심한그래프(str_Ticker, str_startDate)
변화 심한 그래프 시뮬레이션 결과
Alphabet A (Google, Voting)/ 2020-01-01 시작
Google 시뮬레이션 결과 - 티커 : GOOGL - 기간 : 2020-01-01 ~ 2020-06-31 - 투자 : $15502 (최초 $13394 , 추가 $2108) - 손익 : $3413
AAPL(애플)/ 2020-01-01 시작
Apple 시뮬레이션 결과 - 티커 : AAPL - 기간 : 2020-01-01 ~ 2020-04-29 - 투자 : $837 (최초 $726, 추가 $111) - 손익 : $233
TSLA(테슬라) / 2020-01-01 시작
Tesla 시뮬레이션 결과 - 티커 : TSLA - 기간 : 2020-01-01 ~ 2020-01-15 - 투자 : $837 (최초 $837, 추가 $0) - 손익 : $302
리뷰 및 개선 사항
현재 시뮬레이션된 알고리즘은 특정 상황, 특정 조건에서는 수익이 발생하지만,변동성이 적은 종목에서 테스트했을 경우 수익이 나지 않고, 지속적으로 하락된 상황에서는 기하급수적으로 주식을 구매함
실거래 적용은 아직 큰 리스크가 있음
yfinance 라이브러리가 아닌 야후 finance의 개인키를 발급 받아 RestAPI를 직접 사용하는 모듈을 제작하면 더 좋을 듯함
Captcha 이미지를 읽어 내용을 알려주는 신경망 모델을 Python으로 작성하여 UiPath Activity 형태로 만들어준다.
2. Captcha 이미지 수집기 제작
RPA로 제작된 이미지 수집기
3. 이미지 Dataset 만들기
3-1) 폴더 구성
- C:\RPA\Captcha_ImageCollector\origin : 위에서 수집기가 수집한 파일들 위치
- C:\RPA\Captcha_ImageCollector\separate : 분리된 파일들 생성
- C:\RPA\Captcha_ImageCollector\complete : 분리가 완료된 파일 이동
폴더 구성
3-2) 이미지 분리용 코드
import tensorflow as tf
import cv2
import os
import PIL.Image as pimage
import numpy as np
import matplotlib.image as mimage
import matplotlib.pyplot as plt
from tensorflow.keras import datasets, layers, models
# Init Flag
is_Debug = False
# Init global variables
str_path_main = "C:\RPA\Captcha_ImageCollector"
str_path_origin = str_path_main + "\\origin"
str_name_origin = "\\[index].jpeg"
str_file_origin = str_path_origin + str_name_origin
str_path_separate = str_path_main + "\\separate"
str_name_separate = "\\[index].jpeg"
str_file_separate = str_path_separate + str_name_separate
str_path_complete = str_path_main + "\\complete"
str_path_dataset = str_path_main + "\\dataset"
str_path_testset = str_path_main + "\\testset"
# 입력 경로의 파일 수를 반환
def get_current_index(str_path):
return len(os.listdir(str_path))
# 이미지 Shape를 출력
def print_ImageShape(img, is_debug):
if is_debug:
_height, _width, _channel = img.shape
print("Image Shape : " + str(_height) +", "+ str(_width) + ", " + str(_channel))
# 분리된 이미지를 생성
def Image_Separate(str_originImage):
# separate 경로 내부의 현재 파일 수를 얻어옴
int_index = get_current_index(str_path_separate)
# 이미지 사이즈 변수 초기화
origin_w = 240 # 6*40
origin_h = 120 # 6*20
piece_x = int(origin_w/7)
# 원본 이미지 불러오기
img = cv2.imread(str_originImage)
print_ImageShape(img, is_Debug)
# 이미지 사이즈 변경 후 테두리 공백 제거
img = cv2.resize(img, dsize=(origin_w, origin_h)).copy()
img = img[int(origin_h*0.05):int(origin_h*0.75), int(origin_w*0.05):int(origin_w*0.90)].copy()
print_ImageShape(img, is_Debug)
# 이미지 분리
list_img = []
img_copy = img.copy()
for i in range(0, 6):
list_img.append(img[:, piece_x * i:piece_x * (i+1)])
if is_Debug:
# 디버그 모드일 경우 분리할 경계선을 보라색으로 나타내줌
result = cv2.rectangle(img_copy, (piece_x * i, 0), (piece_x * i, origin_h), (255, 0, 255), 1)
print(piece_x * i, ", ", piece_x*(i+1))
# 분리된 이미지를 separete 경로에 파일로 출력, 뒤로 이어붙이기
for i in range(0, 6):
int_index = int_index + 1
cv2.imwrite(str_file_separate.replace("[index]", str(int_index)), list_img[i])
if is_Debug:
cv2.imshow('img', list_img[i])
cv2.waitKey(0)
if is_Debug:
result = img_copy
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
return ;
# Function
def Start_Separate():
# 한번에 가져올 개수, 아래 코드는 현재 경로에 있는 파일 전부를 의미함
int_onetime = get_current_index(str_path_origin)
last_origin_index = get_current_index(str_path_origin)
last_complete_index = get_current_index(str_path_complete)
print("origin start count: ", last_origin_index)
print("complete start count : ", last_complete_index)
# origin의 마지막 index 파일부터 가져옴 (수집기가 뒤에서부터 추가하기 때문)
for i in reversed(range(last_origin_index + 1 - int_onetime, last_origin_index + 1)):
str_ImageFullPath = str_file_origin.replace("[index]" , str(i))
try:
print(str_ImageFullPath)
Image_Separate(str_ImageFullPath)
print(str_path_complete + "\\" + str(i)+".jpeg")
last_complete_index += 1
os.rename(str_ImageFullPath, str_path_complete + "\\" + str(last_complete_index) +".jpeg")
except:
print(str_ImageFullPath + " - 파일 없음.")
last_origin_index = get_current_index(str_path_origin)
last_complete_index = get_current_index(str_path_complete)
print("origin end count: ", last_origin_index)
print("complete end count : ", last_complete_index)
# 분리 시작
Start_Separate()
3-3) 결과
중간 과정 편집
3-4) 이미지 분류
- 이미지 내부의 숫자들 중 괜찮은 이미지를 각 숫자별 폴더로 분류
- 불량한 애들은 수작업으로 버려줌
분류 중인 이미지
4. 학습 모델 제작, 학습, 가중치 파일 생성
-C:\RPA\Captcha_ImageCollector\dataset : 학습 시 사용할 데이터들, 폴더 별로 분류된 이미지들이 위치
-C:\RPA\Captcha_ImageCollector\testset : 학습 완료 후 정확도를 검증하기 위한 테스트용 데이터 위치
- 위 분리용 코드 이어서 작성
4-1) 학습용 코드
# 한개 이미지 가져와서 테스트
def test_OneImage(model, image_path):
x = []
img = cv2.imread(image_path, 0)
cv2.imshow("current image", img)
cv2.waitKey(0)
tmp_x = cv2.resize(img, dsize = (84, 34))
tmp_x = tmp_x.astype('int32')
tmp_x = tmp_x.reshape(84, 34, 1)
tmp_x = tmp_x / 255.0
x.append(tmp_x)
predicted_number = model.predict_classes(np.array(x))[0]
return predicted_number
# 데이터셋 가져오기
def get_datasets(str_dataset_path):
dataset_images = []
dataset_labels = []
for i in range(0, 10):
for j in range(1, 100):
img_name = str_dataset_path + "\\" + str(i) + "\\" + str(i) + " (" + str(j) + ").jpeg"
try:
img = cv2.imread(img_name, 0)
x = cv2.resize(img, dsize = (84, 34))
x = x.astype('int32')
x = x.reshape(84, 34, 1)
x = x / 255.0
dataset_images.append(x)
dataset_labels.append(i)
except:
None
return dataset_images, dataset_labels
# CNN 모델
def model_CNN_ver1():
# model
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(84, 34, 1)))
model.add(layers.MaxPooling2D((2, 2)))
#model.add(layers.Dropout(0.5))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(0.5))
# dense layer
model.add(layers.Flatten())
model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
return model
# 데이터셋, 테스트셋 가져오기
train_images, train_labels = get_datasets(str_path_dataset)
test_images, test_labels = get_datasets(str_path_testset)
print("가져온 학습용 데이터 수 : ", len(train_images))
print("가져온 테스트용 데이터 수 : ", len(test_images))
# 모델 생성
model = model_CNN_ver1()
# 모델 8회 학습
model.fit(np.array(train_images), np.array(train_labels), epochs=8)
model.save("my_model.h5")
# h5 파일 가져와서 정확도 테스트
model22 = models.load_model("my_model.h5")
# 3회 정확도 테스트
test_loss, test_acc = model.evaluate(np.array(test_images), np.array(test_labels), verbose=3)
print("학습된 모델의 정확도 : {:5.2f}%".format(100*test_acc))
4-2) 학습 결과
학습 결과 91.85%
- 대충 10번의 학습 후에 91.85%의 테스트 정확도를 가지는 my_model.h5 가중치, 모델 파일이 생성됨.
4-3) 가중치 파일 사용해서 정확도 테스트하기
test_images, test_labels = get_datasets(str_path_testset)
print("가져온 테스트용 데이터 수 : ", len(test_images))
# 모델 생성
model = model_CNN_ver1()
# 파일을 불러오지 않고 모델의 정확도 테스트
test_loss, test_acc = model.evaluate(np.array(test_images), np.array(test_labels), verbose=3)
print("학습안된 모델의 정확도 : {:5.2f}%".format(100*test_acc))
# h5 파일 가져온 뒤 정확도 테스트
model22 = models.load_model("my_model.h5")
test_loss, test_acc = model22.evaluate(np.array(test_images), np.array(test_labels), verbose=3)
print("학습된 모델의 정확도 : {:5.2f}%".format(100*test_acc))
4-4) 테스트 결과
4-5) 이미지 한개씩 가져와서 예측된 값 보기
model22 = models.load_model("my_model.h5")
for i in range(0, 10):
for j in range(1, 100):
imgname = str_path_testset + "\\" + str(i) + "\\" + str(i) + " (" + str(j) + ").jpeg"
try:
print(test_OneImage(model22, imgname))
except:
print("no image files")
None
4-6) 결과
- 중간 중간 잘못 예측된 결과가 보인다. 그냥 진행한다.
현재 이미지와 결과
5. 원본 이미지 넣어서 한번에 결과 보기
5-1) 테스트용 원본 이미지, 학습 파일 경로 지정
학습이 완료된 h5 파일과 테스트용 이미지가 보인다.
5-2) 코드
# Image_Separate 기반, 6조각으로 분리된 이미지 자체를 return
def Image_Separate_2(str_originImagePath):
# init
origin_w = 240 # 6*40
origin_h = 120 # 6*20
piece_x = int(origin_w/7)
# Read Image
img = cv2.imread(str_originImagePath, 0)
img = cv2.resize(img, dsize=(origin_w, origin_h)).copy()
img = img[int(origin_h*0.05):int(origin_h*0.75), int(origin_w*0.05):int(origin_w*0.90)].copy()
# Separated Image
list_img = []
for i in range(0, 6):
list_img.append(img[:, piece_x * i:piece_x * (i+1)])
return list_img;
# 이미지를 받아서 예측 값 retrun
def test_OneImage_2(model, img):
x = []
tmp_x = cv2.resize(img, dsize = (84, 34))
tmp_x = tmp_x.astype('int32')
tmp_x = tmp_x.reshape(84, 34, 1)
tmp_x = tmp_x / 255.0
x.append(tmp_x)
predicted_number = model.predict_classes(np.array(x))[0]
return predicted_number
# 분리된 이미지 list를 하나씩 넣어서 결과값 list를 만듦
def completed_module(my_model, img_path):
list_img_2 = Image_Separate_2(img_path)
list_result = []
for int_index in range(0, 6):
list_result.append(test_OneImage_2(my_model, list_img_2[int_index]))
return list_result;
def test_FullImage():
oringin_img_path = "test (" + str(int_index) + ").jpeg"
for int_index in range(1, 400):
try:
# 결과 출력
print(completed_module(model22, oringin_img_path)
# 원본 이미지 보여주기
origin_img = cv2.imread(oringin_img_path)
cv2.imshow("result", origin_img)
cv2.waitKey(0)
except:
None
# 테스트
test_FullImage()
5-3) 결과
- 체감상 70~80% 확률로 성공
결과
6. 학습된 가중치 파일 c#에서 실행하기
- HDF.Invoke, IronPython, Keras.Net, Tensorflow.Net 으로 코드 테스트해본 결과,
정상동작하지 않거나 라이브러리 종속성 문제, 파이썬 3 지원안함 등의 문제로 사용 제한됨
-> 현재사용하는 python39버전과 PythonNet Nuget의 버전을 맞춰서 설치함
- 위에서 작성된 코드를 클래스 형태로 재작성
- 파이썬 코드와 h5 모듈은 실행되는 파일과 동일폴더에 위치하여야 함
(VS의 속성 창에서 해당 파일 우클릭 > 속성 > 파일복사 칸의 값을 항상 복사로 설정하면
실행 시마다 해당 파일이 실행 파일 옆에 복사됨)
- 이 과정은 C#-Python 연동을 위한 테스트 코드 부분임
Activity 제작 관련 내용은 아래 7번 항목으로 이동
6-1) C# 코드 (PythonNet)
nuget package manager에서 버전에 맞게 설치
using System;
using System.Text;
using System.IO;
using Python.Runtime; // PythonNet - python39버전 설치
using System.Collections.Generic;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
// 엔진 초기화
PythonEngine.Initialize();
// 정확하진 않지만 자원을 동결시켜 준다고함
using (Py.GIL())
{
// Import할 모듈명, HwanCaptchaModule.py
dynamic hcm = Py.Import("HwanCaptchaModule");
// 임포트한 모듈 내부의 Hwan_Captcha_Module 클래스 초기화
dynamic f = hcm.Hwan_Captcha_Module("cur.jpeg", "my_model.h5");
// 클래스 내의 start 메소드 실행 후 결과 출력
Console.WriteLine(f.start());
}
// 엔진 종료
PythonEngine.Shutdown();
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}
}
6-2) Python 코드
# -*- coding: utf-8 -*-
import os
# 로그 레벨 설정으로 Tensorflow 경고 문구 제거, Tensorflow를 import하기 전에 변경해 줘야한다.
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import sys
import cv2
import numpy as np
from tensorflow.keras import models
# Input : str_ImagePath, str_modelPath
# Output : str_result
class Hwan_Captcha_Module(object):
def __init__(self, str_ImagePath, str_modelPath):
self.str_ImagePath = str_ImagePath
self.str_modelPath = str_modelPath
self.model = models.load_model(self.str_modelPath)
def Image_Classification(self, img):
# 신경망에 넣기 전 데이터 형태 가공
x = []
tmp_x = cv2.resize(img, dsize = (84, 34)).astype('int32').reshape(84, 34, 1) / 255.0
x.append(tmp_x)
# predict로 반환된 SoftMax 값들을 리스트 형태로 바꾸어 그 중 가장 높은 확률의 인덱스를 구함
list = self.model.predict(np.array(x)).tolist()[0][:]
list = list.index(max(list))
# 찾아낸 인덱스가 신경망이 판단한 현재 이미지와 가장 가까운 숫자임
return list
def completed_module(self):
origin_w = 240 # 6*40
origin_h = 120 # 6*20
piece_x = int(origin_w/7)
# 흑백 이미지로 로드(이미지의 차원을 줄임)
img = cv2.imread(self.str_ImagePath, 0)
img = cv2.resize(img, dsize=(origin_w, origin_h))
# 주변 공백 제거
img = img[int(origin_h*0.05):int(origin_h*0.75), int(origin_w*0.05):int(origin_w*0.90)]
str_result = ""
# 위에서 계산된 한 조각 크기만큼 옆으로 이동하면서 숫자 이미지를 가져옴.
for i in range(0, 6):
str_result += str(self.Image_Classification(img[:, piece_x * i:piece_x * (i+1)]))
return str_result;
# 찾아낸 결과를 return 하고 에러 발생 시 문자열 return
def start(self):
try:
return self.completed_module()
except:
return "파일을 찾을 수 없습니다."
if __name__ == "__main__":
Hwan_Captcha_Module(sys.argv[1], sys.argv[2]).start()
6-3) 결과
- 한글자씩 틀리는 경우가 종종 있지만 대부분 성공 (체감상 60~70% 성공률인 듯 하다)
- 아래 411050은 마지막이 0인지 9인지 사람이 봐도 헷갈림
7. Custom Activity 제작
- Nuget Package Manager.exe 또는 Nuget.exe를 활용하여 .nupkg 파일 생성
- python 코드의 종속성을 없애기 위해 6번의 코드를 one directory - exe 형태로 만든 뒤 nupkg에 포함시킴
(auto-exe-to-py 환경 구성 및 사용법)
- nuspec 파일로 패키지 구성을 정의할 수 있음 (자세한 내용은 MSDN 참조)
7-1) C# 코드 (C# 클래스 라이브러리, .Net Framework 4.6.1)
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Activities;
using System.ComponentModel;
using System.Diagnostics;
namespace ClassLibrary
{
public class Captcha : CodeActivity
{
[Category("Input")]
[RequiredArgument]
public InArgument<String> in_str_imagepath { get; set; }
[Category("Output")]
public OutArgument<String> out_str_result { get; set; }
protected override void Execute(CodeActivityContext context)
{
var str_imagepath = in_str_imagepath.Get(context);
var str_result = "";
var str_error = "";
var str_id = "activities_captcha";
var str_version = "1.0.7";
var psi = new ProcessStartInfo();
var str_h5file = Environment.GetEnvironmentVariable("userprofile") + "\\.nuget\\packages\\"+ str_id + "\\"+ str_version + "\\hcm\\model.h5";
psi.FileName = Environment.GetEnvironmentVariable("userprofile") + "\\.nuget\\packages\\" + str_id + "\\" + str_version + "\\hcm\\Hwan_Captcha_Module.exe";
psi.Arguments = string.Format("{0} {1}", str_imagepath, str_h5file);
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
using (var process = Process.Start(psi))
{
str_error = process.StandardError.ReadToEnd();
str_result = process.StandardOutput.ReadToEnd();
// 에러가 비어있지 않으면 에러 출력
if (!string.IsNullOrEmpty(str_error))
{
Console.WriteLine("error : " + str_error);
}
}
out_str_result.Set(context, str_result);
}
}
}
7-2) .nuspec 파일 생성
<?xml version="1.0" encoding="utf-8"?>
<package >
<metadata>
<id>activities_captcha</id>
<version>1.0.7</version>
<title>캡챠!</title>
<authors>hwan</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<description>개꿀</description>
<releaseNotes>Summary of changes made in this release of the package.</releaseNotes>
<tags>Captcha</tags>
</metadata>
<files>
<file src="Hwan_Captcha_Module\**" target="hcm\" />
</files>
</package>
7-3) nuget.exe로 .nupkg 파일 생성하기
- nuget 파일 알아서 구하고 c:\windows 안에 두기
- nuspec있는 프로젝트 위치로 이동 후 아래 코드 실행
nuget pack
7-4) 생성된 .nupkg 파일을 특정 폴더(custom-package 배포용 폴더)에 위치시킨뒤 UiPath Stuido 에서 가져옴
2-4) 결론 - 알리익스프레스 어플로 21년 6월 6일에 주문, 21년 6월 20일 도착 예정 (현시점 6월 9일, 6월 20일 도착완료) - 금액은 약 19만원 소요됨 (배송비 포함, 무료 기본 배송, 카카오페이 됨) - 배터리 제외하고 거의 모든 부품을 저렴한 가격에 해결할 수있음