2020. 4월 #1 – 블록체인에 데이터를 저장하는 방법

스마트 컨트랙트에는 컨트랙트에 필요한 데이터만

다른 데이터를 저장하려면 이벤트 형태로 Emit

Emit한 이벤트를 Frontend에서 Retrieve


스마트 컨트랙트에 데이터를 저장하지 말자


원제: Smart Contracts are not Databases
원작자: Alberto Cuesta Cañada
(원작자분의 요청으로 출처를 명시합니다.)

이미지 출처

들어가며

첫 글 치고는 꽤 늦었지만 4월의 화창한 봄날을 맞이하는 첫 포스팅으로 블록체인을 다룹니다. 이 번역글에서는 이더리움의 스마트 컨트랙트를 사용해 블록체인 상에 데이터를 저장하고자 할 때 유용한 개념을 소개합니다. 이더리움과 스마트 컨트랙트를 소개하려면 별도의 포스팅을 올려야 하므로 여기서는 그냥 지나갑니다. 저는 현재 카카오의 블록체인 자회사인 Ground X에서 테크니컬 라이터로 일하고 있습니다. 당분간 업무에 빨리 적응할 겸(?), 인공지능 보다는 블록체인에 대한 포스팅을 올리려고 합니다.


혹시 스마트 컨트랙트 안에 DB 테이블을 넣고 있는 사람이 있다면 분명 실수를 저지르고 있는 것일 겁니다. 제가 최근까지 그런 것 처럼 말이지요.

블록체인 솔루션을 개발하려면 데이터를 대하고 다루는 방식, 개인정보를 대하는 방식이 달라져야 합니다. 블록체인 솔루션 개발은 금전거래 기록만 가지고 화폐를 구현하고 프로그램으로 이 거래 과정을 제어한다는 점에서 그리 어렵지 않지만, 개발자가 거의 통제할 수 없는 솔루션(역자 주 – 누구나 스마트 컨트랙트를 배포할 수 있고 모든 데이터가 공개된 퍼블릭 블록체인의 속성을 뜻하는 것으로 보입니다.)을 만든다는 점에서 매우 까다롭습니다.

제가 최근까지 한 가지 놓쳤던 점은 “퍼블릭 블록체인에 올려진 데이터는 대체 무엇이냐”입니다. 트랜잭션 히스토리를 손쉽게 기록하는 메커니즘을 찾던 저는 우연찮게 매우 세련된 방법을 찾았습니다. 단, 이 과정에서 개인 정보를 다룰 때 얼마나 조심해야 하는지도 분명히 깨달았습니다.

이더리움 블록체인에서, 상태 변수(State Variables)에는 오직 스마트 컨트랙트가 사용할 데이터만 저장해야 합니다. 아카이빙 하고 싶은 다른 데이터가 있다면 꼭 이벤트(Event) 형식으로 저장해야 합니다.

이 글에서, 저는 로우레벨 이더리움 아키텍처, 블록체인 개발 방법론, 그리고 코드 몇 줄을 사용하여 블록체인 상태를 변화시키고 이를 추적하는 방법, 스마트 컨트랙트를 더 간단히 작성하는 방법, 그리고 당신이 남의 허락 없이 타인의 데이터를 블록체인 상에 공개하고 있을 지도 모른다는 점을 보여 드리겠습니다.

데이터를 추적하는 더 좋은 방법

분산화된 금융 플랫폼은 거래 기록을 하나도 빠짐없이 제공해야 합니다. MiFID II 같은 규제는 규제 담당자가 요구할 때마다 모든 금융 기록을 제출하는 것을 의무화하고 있습니다. 이 모든 금융 기록은 단순히 토큰 전송 기록만을 의미하는 게 아니라, 사용자가 블록체인 상태를 변화시키기 위해 수행한 모든 종류의 행동 기록을 포함합니다.

저는 이렇게 블록체인 상에 기록된 모든 상태 변이 기록을 어떻게 추적할지 고민하면서 먼저 다음과 같이 생각했습니다: “모든 상태 변이는 블록체인에 기록되므로 반드시 블록들을 이동해 가면서 트랜잭션 히스토리를 얻는 방법이 있을 것이다.” 이는 모든 트랜잭션 데이터를 스마트 컨트랙트 안에 데이터 구조(Data Structure)에 우겨 넣는 것보다는 더 우아한 방법 같았습니다. 저는 여태 스마트 컨트랙트는 최대한 간단하게 작성하고 외부 도구를 최대한 많이 활용하라고 배웠기 때문입니다.

트랜잭션 히스토리 위에 만들어진 데이터베이스는 데이터가 블록체인에 담겨있기 때문에, 사용자는 어디에서든 필요할 때마다 데이터베이스를 다시 생성할 수 있습니다(역자 주 – 블록체인상에 DB가 있기 때문에 어디에서든 필요할 때마다 DB 액세스 가능함을 말함). 우리가 사용하는 스마트 컨트랙트는 데이터를 아카이브처럼 저장할 필요가 없습니다. 저는 제 능력자 친구인 Bernardo Vieira에게 이렇게 트랜잭션 히스토리에 기반한 DB를 개발하려면 어떤 도구를 써야 하는지 알려달라고 졸랐습니다. 그는 누가 무엇을 해야 하는지 알 만한 사람이기도 하고 보통 쓸 만한 것들을 가르쳐주곤 합니다.

저는 Bernardo가 저를 다시 찾아와 “블록체인에서 발생한 이벤트를 기록하는 것은 어떤가? 이러면 자네 문제가 해결된 건가?”라고 묻기 전까지는 이벤트에 대해 생각조차 하지 못했습니다.

이벤트, 그것이 답이었습니다. 머릿속이 새롭게 열어졌습니다.

이벤트 사용하기

그 때까지만 해도 저는 이더리움 이벤트에 관해 별 관심이 없었습니다. 물론 블록체인 상태를 변화시키는 것은 어떤 값을 리턴 받는 게 아니라 어떤 이벤트를 발생시키는 것인줄은 알고 있었습니다. 또 그런 이벤트를 프론트엔드단(Frontend Layer)에서 캡처해 원하는 데이터를 뽑아내는 법을 이미 알고 있었고, 그렇게 얻은 데이터를 잘 활용해왔습니다. 하지만 정작, 어떤 이벤트가 어떤 역할을 하는지 몰랐습니다.

스마트 컨트랙트에서 이벤트를 발생시키면 이벤트는 블록체인상에 반정형(semi-structured) 포맷으로 기록됩니다. 이벤트의 몇몇 컬럼은 항상 동일한 값을 지니며 스마트 컨트랙트에서 사용자가 이벤트를 다룰 때 정의한 변수들이 나머지 컬럼 값을 구성합니다. 이벤트는 가스 비용 측면에서 이더리움 블록체인에서 가장 저렴한 연산 중 하나입니다. 4000 가스(이더리움의 트랜잭션 비용)면 정말 싼 거죠.

초기에는 TheGraph를 사용해 블록체인에서 발생한 이벤트에 접근하려고 했습니다. 이 툴은 블록체인 이벤트를 캡처해 GraphQL 인터페이스로 이 이벤트들을 쿼리하도록 합니다. 이 방법은 저희 케이스에서는 결과적으로 너무 복잡한 방법이지만, 덕분에 쿼리를 좀 날려보면서 블록체인 이벤트는 쓰기-전용(write-only) 데이터베이스임을 이해할 수 있었습니다. 어떤 컬럼들은 모든 이벤트에서 동일하며 사용자가 원하는 값을 이벤트에 쓰기 위한 사용자 컬럼을 정의할 수 있습니다.

그러면 어떻게 블록체인 이벤트를 쿼리해야 할까?

블록체인 이벤트가 동작하는 원리는 그 자체로는 어려운 개념이 아니지만, 저희가 이벤트 쿼리를 위한 솔루션을 개발하는 과정에 매우 큰 영향을 미쳤습니다. 저희가 맞이한 첫 번째 변화는 저희가 지금에서야 제대로 이해하게 된 다음의 개발 원칙을 받아들이는 것이었습니다:

모든 상태 변화는 반드시 이벤트를 만든다.

모든 상태 변화에는 이벤트가 뒤따르기 때문에 저희는 트랜잭션을 추적하는 데 신경쓰지 않아도 됩니다. 모든 상태 변화는 이벤트를 발생시키고 그저 프론트엔드에서 이벤트 정보를 추출하는 도구를 입맛대로 골라쓰면 됩니다.

그런데 이 과정에서 개인 정보는 어떻게 보호될까요? 제가 쿼리할 때마다 민감한 개인 정보를 포함한 모든 데이터를 받게 됨을 걱정해야 할까요? 글쎄요, 어차피 모든 데이터는 트랜잭션 데이터만 있으면 접근이 가능하니까, 저는 그저 블록체인 이벤트에 더 쉽게 접근할 수 있는 방법을 찾은 것 뿐이지요.

기억하시죠? 퍼블릭 블록체인에 있는 모든 데이터는 누구에게나 공개되어 있습니다.

제 다른 글을 읽어보셨다면 저는 항상 일을 가장 간단한 방법으로 해결하기를 좋아합니다(저도요…복잡한 것은 정말 싫습니다). 지금까지, 저는 단순히 블록체인에 문서를 저장하는 일 정도는 밑에서 소개할 코드 라인 수준에서 만족해왔습니다.

pragma solidity ^0.5.10;
contract DocumentRegistry {
  event Registered(uint256 hash);
  mapping (uint256 => uint256) documents;
  
  function register(uint256 hash) public {
    documents[hash] = msg.sender;
    emit Registered(hash);
  }
  function verify(uint256 hash) public view returns (uint256) {
    return documents[hash];
  }
}

이 정도면 충분히 간단한 코드이지만, 여기서 더 간단하고 효율적으로 만들 수도 있습니다.

pragma solidity ^0.5.10;
contract DocumentRegistry { 
  event Registered(uint256 hash, address sender);
  function register(uint256 hash) public { emit Registered(hash, msg.sender); }
} 

두 번째 컨트랙트 코드는 보시다시피 내부에 아무 것도 저장하지 않고 데이터를 추출하는 함수도 없습니다. 그럼에도, 첫 번째 컨트랙트와 동일한 일을 수행합니다.

(역자 주 – 스마트 컨트랙트로 register 함수를 실행하면 Registered 이벤트를 발생시켜 데이터를 블록체인에 저장하며 Registered 이벤트의 입력 파라미터로 사용자가 저장하고 싶은 정보인 msg.sender를 이 데이터를 retrieve할 수 있는 key값인 hash값과 함께 넘겨주어 블록체인 상에 기록하는 것으로 보입니다)

보통 이벤트는 잠깐 생성되었다가 사라진다고 생각하곤 하는데 블록체인에는 이벤트를 포함해 모든 것이 영구적으로 남아있습니다. 만약 당신이 블록체인 상에 존재하는 어떤 문서가 진본인지 확인하고 싶다면 (혹은 위/변조가 없는지 확인하고 싶다면), 그저 문서를 생성한 트랜잭션의 해쉬값을 계산한 다음 캐쉬에 쿼리를 날리면 됩니다. 한 가지 신경써야 할 것은 외부 인프라를 사용해 이벤트를 추적하고 블록체인 상태를 오프체인(체인에서 분리된 오프라인 상태)에서 복제해야 한다는 점입니다. 지금까지 소개한대로 블록체인에 데이터를 DB처럼 쓰고 읽는 방식은 같은 목적을 이루기 위한 다른 방법보다 상대적으로 쉬울 뿐 아니라 퍼포먼스 측면에서도 가장 좋다고 생각합니다. starter-kit에 방문하시면 sample event cache를 찾을 수 있습니다. 이 항목에는 저희의 모든 프로젝트에서 사용하는 도구가 있으며 빠르게 개선되고 있습니다.

결론

이 방법을 개발하면서 꽤나 큰 인식의 전환이 이루어졌기에 갑자기 제 머릿속에서는 “스마트 컨트랙트 내부에 데이터 구조를 구현해야 해.”와 “그냥 이벤트 발생시키고 나머진 잊어버려.”로 생각이 분명히 나뉘어졌습니다. 스마트 컨트랙트가 데이터를 필요로 한다면 상태 변수(State Variable)에 데이터를 저장하시고 그렇지 않다면 이벤트 형태로 내보내십시오. 저는 평상시에 어떤 블록체인 솔루션이든 전체 코드의 약 10%만이 스마트 컨트랙트 코드라고 말해왔습니다. 또 트랜잭션을 제대로 추적하는 솔루션이라면 10% 미만에 그칠 것입니다. 이제, 저희는 앞으로 몇 개월 정도 지금까지 이 글에서 설명한 내용과 자체 개발한 도구를 활용해 스마트 컨트랙트를 개발할 것입니다. 이 리포지터리에서 개발을 진행하고 있으니 궁금하신 분들은 오셔서 저희의 스마트 컨트랙트와 툴을 구경하세요.

2020.3월 – [Google AI] 구글, 새 NLU모델 ELECTRA 공개

ELECTRA: GAN의 개념으로 MLM과 LM의 장점 결합

BERT 시리즈와 맞먹는 성능, 훨씬 더 효율적인 컴퓨팅

TF로 공개, 영어 모델만 지원


2019년 3월 13일, 모습을 드러낸 NLU 신형엔진

들어가며…

전 세계가 코로나로 어수선하고 실리콘밸리마저 시장 붕괴에 대한 공포감에 휩싸이는 가운데, 개인적으로 이번 2월은 프리랜서에서 정규직 테크니컬 라이터로 전환하기에 바쁜 한 달이었습니다. 곧 직장에서 열심히 일할 예정인지라 다시 최선을 다해 연재하려고 합니다.

3월의 첫 소식으로 구글에서 기존 NLU 모델보다 훨씬 더 효율적이면서도 좋은 성능을 자랑하는 ELECTRA라는 모델을 공개했다는 내용을 전합니다. 이 포스팅은 구글 AI 블로그에 있는 내용을 번역/해설하여 공유하는 것입니다. 이 블로그의 특성상 상업적 이용 따위는 현재 전혀 고려하고 있지 않지만 차후 문제가 있을 경우 삭제하겠습니다.

ELECTRA 모델을 짧게 요약하면, 작은 BERT(Bidirectional Encoder Representations from Transformers)로 어떤 문장을 생성(Generate)하고 별도의 Transformer 모델로 이 문장 속에 있는 모든 단어를 검증(Discriminate)함으로써 Masked Words(BERT의 15% subset)만 검증하던 BERT에 비해 훨씬 더 sample-efficient한 학습이 가능한 모델입니다. 눈치채셨겠지만 이는 GAN(Generative Adversarial Network)의 Generator/Discriminator 구조를 빌려온 것이라고 합니다.


원문:More Efficient NLP Model Pre-training with ELECTRA
게재일:Tuesday, March 10, 2020
원문링크:https://ai.googleblog.com/
원 논문링크:https://openreview.net/forum?id=r1xMH1BtvB
저자: Kevin Clark, Student Researcher and Thang Luong, Senior Research Scientist, Google Research, Brain Team

ELECTRA: 더 효율적인 NLP 사전학습 모델

최근 BERT, RoBERTa, XLNet, ALBERT, T5 등 최신 사전학습(Pre-training) 기법이 개발되면서 NLP 분야에는 큰 진전이 있었습니다.

이 기법들은 서로 조금씩 다른 구조를 갖고 있지만, 공통적으로 대규모 텍스트 데이터를 비지도학습(Unsupervised Learning)하여 인간 언어를 넓고 깊게 표현할 수 있는 언어 모델(General Language Model)을 만든 다음 이를 특정한 NLP 분야(감성 분석, Q&A)에 baseline model로 사용하기 위해 만들어졌습니다.

기존 사전학습 기법은 크게 2가지로 나뉩니다: LM(Language Model)과 MLM(Masked Language Model)입니다.

LM은 한 문장에 있는 단어들을 첫 단어부터 끝 단어까지 하나씩 차례대로 학습하면서 ‘지금까지 학습한 단어들의 맥락을 고려할 때 이 다음 단어는 무엇이 와야 할까?’라는 개념으로 인간 언어를 학습합니다(e.g> GPT).

MLM은 한 문장에 있는 단어들을 차례대로 학습하는 것이 아니라 양방향(문장 시작 단어부터 끝 단어 방향으로, 또한 끝 단어부터 시작 단어 방향으로)으로 학습하면서 마스킹처리(Masked out, 모델이 인간 문장을 이해해서 단어를 예측하도록 “가려놓은” 일부 단어들)된 일부 단어들을 예측하는 모델입니다. MLM 방식으로는 BERT, RoBERTa, ALBERT 등 BERT 시리즈가 있습니다.

MLM은 양방향(bi-directional) 학습이 가능하므로 문장의 첫 단어부터 끝 단어까지 순서대로 문장 맥락을 이해하는 것이 아니라, 맥락을 문장 전체를 토대로 이해해가며 인간 언어를 학습한다는 장점이 있습니다. 하지만 MLM은 LM처럼 문장 내 모든 단어를 예측하는 것이 아니라 마스킹된 단어만 예측합니다. MLM은 문장 내 뚫린 구멍(마스킹된 일부 단어)을 메꾸면서(메꾸고 틀리고 다시 메꾸고 틀리고 다시 메꾸기를 반복하면서…) 인간 언어를 이해하므로, 뚫린 구멍에 위치해있던 단어들만 학습에 사용되고 나머지 단어들은 버려집니다.

이 마스킹된 단어가 전체 단어에서 차지하는 비율은 BERT에서는 15%로 알려져 있으며 때문에 BERT에서는 전체 텍스트 데이터(단어 수 기준)의 15%만이 학습에 사용됩니다.

LM과 MLM 학습방식 비교. 출처: Google AI Blog

구글 연구진이 개발한 ELECTRA(Efficiently Learning an Encoder that Classifies Token Replacements Accurately)는 BERT의 장점을 유지하면서도 훨씬 더 효율적으로 학습하는 모델입니다. ELECTRA는 동일한 컴퓨팅 자원으로 다른 모델보다 압도적인 성능을 보입니다.

예를 들어 ELECTRA는 GLUE 벤치마크에서 RoBERTa, XLNet과 동일한 성능을 기록하면서도 겨우 25%의 컴퓨팅 리소스만 사용합니다. 또 SQuAD Q&A 벤치마크에서는 최고 성능을 기록했습니다. 단일 GPU 환경에서 몇 일 동안 학습한 ELECTRA는 30배 이상 더 많은 컴퓨팅 리소스를 잡아먹는 GPT보다 더 높은 정확도를 보입니다.

ELECTRA는 TensorFlow 기반 오픈소스로써 릴리즈되고 있으며 사용가능한 pre-trained LM을 제공합니다.

사전학습을 더 빠르게!

ELECTRA는 RTD(Replaced Token Detection)이라는 새로운 사전학습 기법을 사용합니다. RTD는 MLM처럼 양방향 학습을 하면서도 LM처럼 문장 내에 모든 단어를 학습에 사용합니다. ELECTRA는 GAN과 비슷하게 “진짜”와 “가짜” 데이터를 구별하기 위한 학습을 합니다.

ELECTRA는 BERT처럼 문장 내 일부 토큰(단어)을 “[MASK]”로 표시하는 방식으로 마스킹하는 것이 아니라 이들을 “가짜” 단어로 바꿔치기 합니다. 예를 들어 데이터셋에 있는 원래 문장이 “The chef cooked the meal.”라면 “cooked”를 “[MASK]”로 표시하는 것이 아니라 “ate”로 바꿔치기할 수 있습니다. “ate”로 바꾸면 문장이 말이 되긴 하지만 본래 텍스트 데이터셋에서 말하고자 했던 맥락과는 차이가 있습니다.

일부 단어를 가짜 단어로 바꿔치기 했다면, Discriminator(판별자) 신경망 모델이 이 문장 내 모든 단어를 살펴보면서 바꿔치기한 단어(“가짜”)가 무엇이고 원래 데이터셋에 있던 단어(“진짜”)가 무엇인지 판단합니다(Binary Classification). 즉, Discriminator는 모든 단어에 대해 바꿔치기 여부를 판단하면서 판단 정확도가 높아지는 방향으로 학습을 합니다. 따라서 모든 단어를 학습하기 때문에 15%만 사용하는 BERT보다 훨씬 sample-efficient하며 결과적으로 동일한 성능을 더 적은 데이터만으로 달성합니다. 동시에 ELECTRA Discriminator 모델이 진짜/가짜 판단 정확도를 올리기 위해서는 데이터셋에 담긴 언어 맥락을 정확하게 이해해야 하므로(the accurate representation of the data distribution) 인간 언어 표현 측면에서도 훌륭한 성능을 갖게 됩니다.

출처: Google AI Blog

일부 토큰을 가짜로 바꿔치기 하려면 먼저 가짜 단어를 생성해야 합니다. 가짜 단어는 Generator라는 다른 신경망 모델이 생성합니다. 전체 토큰셋에 대하여 특정 확률분포를 따라 토큰을 생성하는 모델이라면 무엇이든 Generator가 될 수 있습니다. 구글 연구진은 Generator로 작은 히든 사이즈를 가진 BERT를 사용했고 이 Generator는 Discriminator와 함께 학습되었습니다.

Generator가 데이터를 Discriminator에 보내는 Generator/Discriminator 구조는 GAN과 유사하지만, Generator는 마스킹된 단어를 예측하기 위해 최대우도(maximum likelihood) 학습을 한다는 차이가 있습니다.


역자 추가 해설: 위 내용을 추가로 조금 쉽게 풀어 설명하겠습니다. GAN은 학습과정에서 Discriminator가 Generator에게 피드백을 전달합니다. 즉, Discriminator가 진짜/가짜이미지를 더 잘 구별하기 위해 데이터를 학습하는 과정에서 얻은 지식(피드백)은 Generator에도 전달(Back-propagated)되어 Generator도 더 정교하게 Discriminator를 “속일 수” 있도록 진화합니다.

하지만, ELECTRA는 Generator 입장에서 자신이 샘플링한 가짜 단어들을 가지고 역전파 학습을 하기 어렵기 때문에(원 논문 13페이지 참조) Discriminator가 Generator에게 피드백을 주진 못합니다. ELECTRA에서 Generator는 어떤 문장이 주어지고 그 문장 내에서 일부 단어들이 “[MASK]”로 가려졌을 때, “[MASK]”를 대체하는 다른 단어를 특정한 확률분포를 따라 생성하는 모델입니다. 그리고 이 특정한 확률분포의 모수(Parameter)를 수정하여 “[MASK]”를 대체하는 단어로 원래 단어(=”진짜” 단어)를 생성하도록 하는 것이 목적입니다. 따라서 경사하강법을 사용해 “진짜” 단어를 생성할 확률(우도값)을 최대로 하는 최대우도 학습을 수행합니다.


Generator와 Discriminator는 같은 데이터를 입력으로 받습니다(동일한 워드 임베딩).사전 학습이 끝난 후 Generator는 버려지며 Discriminator가 ELECTRA 본 모델로써 다른 NLP 작업(감성분석, Q&A)의 베이스 모델로 사용됩니다. Generator와 Discriminator 모두 Transformer 신경망 아키텍처를 사용합니다.

출처: Google AI Blog

ELECTRA Results

ELECTRA를 사용한 결과 동일한 컴퓨팅 리소스가 주어졌을 때 다른 NLU 모델을 압도하는 성능을 보였습니다. 특히 RoBERTa, XLNet과 비교 시 25%의 리소스로 비슷한 성능을 보였습니다.

출처: Google AI Blog

GLUE 점수에서 최상위를 차지하는 T5 (11B) 모델은 위 그림에 있지 않는데, T5가 RoBERTa 비교 시 10배 이상의 리소스를 잡아먹는 등 X축 상에서 T5가 소모하는 컴퓨팅 자원을 표시할 수 없기 때문입니다.

ELECTRA는 4일 동안 단일 GPU로만 학습하는 스몰스케일(small-scale) 환경에서도 좋은 정확도를 보였습니다. 여러 TPU(Tensor Processing Unit)를 사용할 때 보다 정확도는 낮지만 작은 리소스로 꽤나 괜찮은 성능을 보였으며 심지어 GPT에 필요한 계산량의 1/30만으로도 GPT를 압도하는 성능을 확인했습니다.

마지막으로, 대규모 스케일(RoBERTa와 동일한 리소스/T5의 10% 리소스)로 ELECTRA 모델을 만든 결과 단일 모델로는 SQuAD 2.0 Q&A 데이터셋에서 최고 기록을 경신했고, GLUE 점수에서 RoBERTa, XLNet, ALBERT를 추월했습니다. 아직 T5-11b모델이 GLUE에서 최고 기록을 가지고 있지만, ELECTRA의 크기는 T5의 1/30에 지나지 않으며 T5 계산량의 10%만을 사용합니다.

출처: Google AI Blog

Releasing ELECTRA

구글은 ELECTRA 베이스 모델을 만드는 사전학습용 코드와 텍스트 분류, Q&A, 시퀀스태깅 등 세부작업을 위한 파인튜닝용 코드를 릴리즈합니다. 이 릴리즈는 단일 GPU에서 작은 스케일로 ELECTRA를 학습하기 위한 코드를 포함합니다. 또 ELECTRA-Large, ELECTRA-Base, ELECTRA-Small 각 모델에 대해 사전학습된 가중치 벡터도 공유합니다. ELECTRA는 현재 “영어” 모델만 지원하며 향후 다른 언어도 지원할 계획도 있습니다.

2020.1월 4주 – OpenAI, DeepRL Base로 PyTorch를 선택

OpenAI, DeepRL Base로 PyTorch를 선택

실습 프레임워크인 “Spinning Up in Deep RL”의 PyTorch 버전 공개

Block-Sparse GPU Kernel도 곧 PyTorch 버전으로 공개 예정


2020년 1월 4주, Tensorflow에서 PyTorch로

PyTorch 배워야겠다…

들어가며…

그 동안 여러가지로(주로 여행 다니며 번역하는 일로,,,) 바쁜 나머지 밀린 포스팅을 이제야 올리려 합니다. 기존 파이썬 뉴스레터를 포함해 Google AI, Faceboo AI, DeepMind, Open AI 등 여러 글로벌 메이저 AI 블로그 사이트를 돌아보니 그 동안 놓친 여러 재미있는 뉴스가 많았습니다. 그 중에서도 1월 4주차에서 꼭 다루어야(었어야) 했던 것은 단연 OpenAI가 DeepRL 표준으로 PyTorch를 선택한 것입니다.

OpenAI는 전 인류를 위한 AI 기술, 정확히는 AGI(Artificial General Intelligence)를 개발하고 발전시키는 것을 목표로 하는 비영리 공공 AI 연구 기관입니다. AGI, 다른 말로 “인공 일반 지능”이란 위키피디아에 따르면 인간이 할 수 있는 어떠한 지적인 업무도 성공적으로 해낼 수 있는 기계의 지능을 말합니다. 즉, 이 기관의 궁극적인 목표는 연구자들이 아닌 일반 사람들이 생각하는 인공지능(예를 들면 스타워즈의 C3PO)을 개발하는 것이며 특정 이익 집단이 아닌 전 인류에게 공헌하기 위함입니다.

OpenAI는 강화학습, 로보틱스, NLU 등 여러 분야에서 많은 성과를 거두었습니다. 특히 DeepRL 사용자에게 OpenAI Gym으로 만든 Environment Interface는 거의 표준으로 자리잡았다고 봐도 과언이 아닙니다. 저도 Gym과 Tensorflow로 제 주식투자 모델을 위한 Customized Environment를 만들어 사용했는데, 그냥 RL를 공부하고 혼자 만든 Environment Class보다 확실히 더 편하고 구성이 체계적이었습니다. Environment를 어떻게 구성해야 할지 골치가 아플 때 시간을 절약하는데 큰 도움이 되는 프레임워크입니다. 또 일단 프레임워크를 도입하면 A2C, ACKTR 등 DeepRL의 Baseline 알고리즘과 여러 Custom Model을 쉽고 빠르게 사용할 수 있는 것도 큰 장점입니다.

OpenAI, TensorFlow → PyTorch

OpenAI는 자사의 딥러닝 프레임워크 표준을 PyTorch로 제공하기로 결정했습니다. 지금까지는 OpenAI가 진행하는 프로젝트마다 요구되는 특징이 있으면, 해당 특징을 제공하는 딥러닝 프레임워크를 사용했었습니다. 이제는 모든 OpenAI 프로젝트 팀이 최적화된 코드를 구현하고 도입하기 위해 PyTorch로 프레임워크를 표준화하기로 결정했습니다.

먼저, OpenAI는 Spinning Up in Deep RL의 PyTorch 버전을 공개했습니다. Spinning Up in Deep RL이란 RL의 주요 개념과 알고리즘을 간단히 소개하고 여러 실습용 Environment로 실행해볼 수 있는 DeepRL 교육용 프레임워크입니다.

또한 Dense Network를 동일한 non-zero weights를 가지면서도 더 Sparse한 Network로 치환함으로서 GPU가 모델을 더 빠르게 학습할 수 있도록 하는 Block-Sparse GPU Kernels또한 PyTorch 버전으로 공개할 예정입니다.

PyTorch로 프레임워크를 표준화하는 주된 이유는 OpenAI가 보유한 GPU 가용성을 높여 연구를 더 효율적으로 진행하기 위함입니다. PyTorch를 사용하면 새로운 아이디어를 시도하고 테스트해보는 일이 매우 쉽습니다. 예를 들어 PyTorch로 생성 모델을 연구할 때 연구 주기(새로운 아이디어를 입안, 구현, 테스트, 결과 확인하는 연구 생애 주기)는 몇 주에서 몇 일로 단축되었습니다. OpenAI는 GPU 확장성과 성능을 한계까지 끌어올리는 일에 Facebook, Microsoft 및 여러 개발 커뮤니티와 함께하길 원합니다.

앞으로 특별한 기술적 이유가 있는 경우를 제외하고는 PyTorch가 OpenAI 딥러닝 프레임워크로 사용될 것입니다. 저희 팀원 중 여럿은 이미 PyTorch로 프로젝트 전환을 완료했고, 앞으로 PyTorch 커뮤니티에 기여할 것을 기쁘게 생각하며 고대합니다.

2020년 1월 3주 – 캐글 뽀개기 Titanic

특별히 전달할 소식이 없을 때에는 캐글(kaggle) 분석 과제 뽀개기를 해보려고 합니다. 먼저 캐글의 hello world에 해당하는 타이타닉 생존자 예측입니다. 모든 코드는 Google Colab Jupyter Notebook 환경에서 작성한 것입니다.


from google.colab import files
!pip install -q kaggle
uploaded = files.upload()
#kaggle.json 파일 업로드 (kaggle 로그인 후 MyPage에서 다운로드 받을 수 있음
!mkdir /root/.kaggle
!mv kaggle.json /root/.kaggle/
!kaggle competitions download -c titanic
!pip install --upgrade pandas_profiling

%matplotlib inline
import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt
from pandas_profiling import ProfileReport
train = pd.read_csv('./train.csv')
test = pd.read_csv('./test.csv')

print(train.shape)
print(test.shape)
#간단하게 EDA하기 위해 PandasProfiling 사용
rp_train = ProfileReport(train, title='Train', html={'style':{'full_width':True}})
rp_test = ProfileReport(test, title='Test', html={'style':{'full_width':True}})
rp_train.to_file('rp_train.html')
files.download('rp_train.html')
rp_test.to_file('rp_test.html')
files.download('rp_test.html')

워드프레스 가입형은 유료 플랜은 너무 비싸고 무료는 제한이 너무 많습니다. 대표적인 것이 바로 플러그인/사용자 CSS 레이아웃 사용 불가. 외부 HTML 보여주는 기능도 제한이 있어 Google Colab을 HTML로 보여줄 수 없습니다. 그래서 이미지 파일로…………….. 조만간 설치형으로 호스팅할 것입니다.

EDA 해석

  1. NA값은 Age(training), Cabin(training, test), Fare(test)에 존재
  2. Parch, SibSp는 값이 0에 skew되어 있음 (상당 수가 혼자 탄 승객임)
  3. 상관분석 결과를 보면 Survived와 가장 높은 상관관계를 지닌 변수는 Fare(Pclass), Parch, Sex임.
  4. Parch는 값이 0에 많이 쏠려있으므로 제외. -> Sex, Pclass, Fare
#추가 EDA - categorical variables
#Sex
sex_pivot = train.pivot_table(index='Sex', values='Survived')
print(sex_pivot)
sex_pivot.plot.bar(ylim=(0,1))
plt.plot()
#추가 EDA - continuous variable
survived = train[train['Survived']==1]
failed = train[train['Survived']==0]

#Fare
survived['Fare'].plot.hist(alpha=0.3, color='red', bins=100, figsize=(20,10))
failed['Fare'].plot.hist(alpha=0.3, color='blue', bins=100, figsize=(20,10))
plt.legend(['Survived', 'Failed'])
plt.show()

#Age
survived['Age'].plot.hist(alpha=0.3, color='red', bins=100, figsize=(20,10))
failed['Age'].plot.hist(alpha=0.3, color='blue', bins=100, figsize=(20,10))
plt.legend(['Survived', 'Failed'])
plt.show()
#Age를 구간별로 짜르면 class 구분이 가능한 bin이 존재함을 확인(예:0~5살 영유아 구간은 Survived 비율이 높음)

EDA 결론으로 Pclass(CAT), Fare(NUM), Sex(CAT), Age(NUM)의 4가지 변수를 모델에서 사용하기로 선택

#Cleansing & Mangling
#Processing with Age in training set

#Age는 임의의 구간이 아닌, 의미가 명확한 구간으로 나눌 수 있는 명목변수이다.
#따라서 CAT 변수로 바꿀 수 있다: Age_categories: CAT
#Age는 미싱값이 있다. 미싱값도 하나의 CATEGORY로 변환해서 사용한다

def process_age(df,cut_points,label_names):
    df["Age"] = df["Age"].fillna(-0.5)
    df["Age_categories"] = pd.cut(df["Age"],cut_points,labels=label_names)
    return df


cut_points = [-1,0, 5, 12, 18, 35, 60, 100]
label_names = ["Missing", 'Infant', "Child", 'Teenager', "Young Adult", 'Adult', 'Senior']

train = process_age(train,cut_points,label_names)
test = process_age(test,cut_points,label_names)

age_cat_pivot = train.pivot_table(index="Age_categories",values="Survived")
age_cat_pivot.plot.bar()
plt.show()
#Processing with Fare in test set
#Fare는 Age와 달리 의미 있는 구간으로 나누기 위한 정보가 부족하다. (물가, 소득수준 등)
print(test[test['Fare'].isna()==True])
#동일한 Pclass, Sex, Age_categories 값을 지닌 row들의 mean(Fare)를 사용하기로 하자.
#test셋에는 해당 데이터가 자기 자신밖에 없다. train 셋에서 찾아보자
subset0 = train[(train['Pclass']==3) & (train['Sex']=='male') & (train['Age_categories']=='Senior')]
print(subset0)
#위 subset Fare의 평균값을 na값을 대체하여 사용
test['Fare'].fillna(subset0.Fare.mean(), inplace=True)
print(test.iloc[152])
print()

#잘 replace 되었나 체크
print(test[test['Fare'].isna()==True])

#카테고리컬 variable을 One-hot 인코딩
def create_dummies(df, c_name):
    dummies = pd.get_dummies(df[c_name], prefix=c_name)
    df = pd.concat([df,dummies], axis=1)
    return df

train_dummies = create_dummies(train, "Pclass")
test_dummies = create_dummies(test, "Pclass")

train_dummies = create_dummies(train_dummies, "Sex")
test_dummies = create_dummies(test_dummies, "Sex")

train_dummies = create_dummies(train_dummies, "Age_categories")
test_dummies = create_dummies(test_dummies, "Age_categories")

train_dummies.sample(5)
test_dummies.sample(5)
#Fare 정규화
#트레이닝셋
from sklearn.preprocessing import MinMaxScaler
minmax_scaler = MinMaxScaler()

fitted = minmax_scaler.fit_transform(train_dummies[['Fare']])
train_dummies['Fare_norm']= fitted
print(train_dummies.sample(5))
print();print()

#테스트셋
fitted = minmax_scaler.fit_transform(test_dummies[['Fare']])
test_dummies['Fare_norm']= fitted
print(test_dummies.sample(5))

#Fare 정규화
#트레이닝셋
from sklearn.preprocessing import MinMaxScaler
minmax_scaler = MinMaxScaler()

fitted = minmax_scaler.fit_transform(train_dummies[['Fare']])
train_dummies['Fare_norm']= fitted
print(train_dummies.sample(5))
print();print()

#테스트셋
fitted = minmax_scaler.fit_transform(test_dummies[['Fare']])
test_dummies['Fare_norm']= fitted
print(test_dummies.sample(5))

#머신러닝 모델 만들기
#EDA 결과 독립-종속 간 선형성 확인 가능했다. 로지스틱 회귀 모델(glm)으로 테스트
columns = ['Fare_norm', 'Pclass_1', 'Pclass_2', 'Pclass_3', 'Sex_female', 'Sex_male',
       'Age_categories_Missing','Age_categories_Infant',
       'Age_categories_Child', 'Age_categories_Teenager',
       'Age_categories_Young Adult', 'Age_categories_Adult',
       'Age_categories_Senior']

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()

from sklearn.model_selection import train_test_split
all_X = train_dummies[columns]; all_y = train_dummies['Survived']
#training, validation셋 분할
train_X, valid_X, train_y, valid_y = train_test_split(all_X, all_y, test_size=0.2, random_state=0)

print(train_X.shape)
print(valid_X.shape)
print(train_y.shape)
print(valid_y.shape)
print()
print(train_X.info())

lr.fit(train_X, train_y)
pred = lr.predict(valid_X)
from sklearn.metrics import accuracy_score, confusion_matrix
acc = accuracy_score(valid_y, pred)
conf_mat = confusion_matrix(valid_y, pred)
print(acc)
print()
print(pd.DataFrame(conf_mat, columns=['Survived_Pred','Died_Pred'], index=[['Survived_True','Died_True']]))
print()

#10-fold validation learning
from sklearn.model_selection import cross_val_score
import numpy as np
scores = cross_val_score(lr, all_X, all_y, cv=10)
print(scores)
print()
print(np.mean(scores))

선형모델로 80%면 뭐 나쁘진 않습니다… 이대로 test셋을 예측해봅니다.

# test셋 예측
lr_test = LogisticRegression()
lr_test.fit(all_X, all_y)

test_X = test_dummies[columns]
test_pred = lr_test.predict(test_X)
print(test_pred)


비선형모델을 돌려보는 것과 추가로 Feature Engineering하는 과정(Name 변수 데이터를 Parsing하여 직업, 사회적 직위와 같은 변수를 생성)은 생략합니다. 다음에는 Housing Price Prediction을 올려보겠습니다.

2020.1월 2주 – 1. Python 2의 종말 카운트다운

Cpython 코어 개발팀, 파이썬 2 개발 종료를 공지

원래 2015년도에 지원 종료였으며 5년 유예기간이 지남

데드라인이 도래. 반드시 2to3 마이그레이션을 진행해야 할 때


2020년 1월 8일, 정말로 마지막이 된 Python 2

파이썬 2를 이제 보내줘야 할 시간이 왔다. 이미지 출처

Python 공식 커뮤니티, Python 2.7 지원 종료 공식 발표

2019년 12월 20일, CPython(=오리지날 Python) 코어 개발팀은 파이썬2 지원과 개발이 이제 종료된다고 발표했습니다. 코어 개발팀은 파이썬 2.7의 마지막 마이너버전을 2020년 4월에 공개하는 것을 끝으로 파이썬 2에 대한 모든 개발을 중단합니다. 모든 파이썬2 사용자들은 2020년 4월 이후로 파이썬2에서 파이썬3로 마이그레이션해야 합니다.

“2006년 파이썬3 개발을 시작한 이래로 파이썬3가 개발되는 목적은 한결같았습니다. 바로 파이썬2를 대체하는 것입니다. 파이썬 에코시스템에 참여하는 모든 사람들의 어마어마한 노력 덕분에 파이썬3는 이제 파이썬2가 담당해왔던 모든 작업을 대체할 수 있습니다. 파이썬2에서 3로 넘어가는 이 시점은 파이썬 커뮤니티에 있어 매우 중요한 마일스톤입니다. 오늘날 이 시간이 오기까지 함께하신 모든 도움의 손길과 파트너분들게 감사의 말을 전합니다.” – Nick Coghlan, Python Steering Council 초기 멤버이자 Python3 Q&A 제작자

파이썬2 개발 종료가 늦어진 이유

Python 2.7의 마지막 마이너 버전은 원래 2020년이 아닌 2015년에 공개될 계획이었습니다. 하지만 현실을 고려해 사용자들이 파이썬3로 옮길 수 있도록 5년의 유예기간을 주었고 이제 그 데드라인이 2020년 4월로 다가온 것입니다. 여기서 현실이란 파이썬2에서 벗어나고 싶어도 라이브러리 의존성 등 여러 이슈로 인해 벗어나지 못하는 개발자/시스템들의 사정을 말합니다. 따라서 파이썬 코어개발팀은 파이썬3 마이그레이션 계획을 세우고 이에 필요한 협업, 파트너쉽을 진행하라고 5년의 시간을 준 것입니다.

5년 동안 파이썬2 종료가 유예된 또 다른 이유는 파이썬3 자체의 문제입니다. 파이썬3는 파이썬2보다 더 까다로운 텍스트 모델을 사용합니다. 이 때문에 파이썬3 인터프리터, 파이썬3 표준 라이브러리는 물론이고 이들과 연동되는 외부 라이브러리와 애플리케이션에서 발생하는 복잡한 유니코드 이슈를 해결해야 했기 때문입니다.

파이썬2 사용자들은 이제 정말로! 마이그레이션해야 합니다

파이썬3는 유니코드, 인터널라이제이션을 기본적으로 지원하며 더 좋은 문법과 표현 패턴을 제공해 사용자들은 파이썬2보다 더 쉽게 코드를 읽고 이해할 수 있습니다. 또 향상된 동시성, 에러 처리, 테스팅, 디버깅 지원으로 개발자들은 더 견고하고 안전한 애플리케이션을 제작할 수 있습니다.

앞으로, 오직 파이썬3에 대한 버그 해결, 보안 지원이 있을 것이며 사용자들은 2020년 이후 파이썬3로 마이그레이션 할 것을 강력히 권장합니다.

파이썬3 현재 버전 정보

상용 파이썬 배포판 및 파이썬 런타임 모듈을 임베드한 상용 애플리케이션 사용자들은 반드시 그들의 벤더와 연락하여 파이썬3 마이그레이션 지원을 논의해야 합니다.

파이썬 포팅 가이드

간단 요약 FAQ, 상세 FAQ

귀찮아서 포팅 안하고 파이썬2 계속 쓰시던 분들, 이제라도 그만 쓰고 파이썬 3 씁시다.

[잡썰] 나는 콘텐츠 리크리에이터/큐레이터

잡썰을 풀며…

간간히 주간 연재물이 아닌 잡썰을 풀어보려고 합니다. 특히 오늘같이 비 오는 날에는 어쩌면 필 꽂히면 딱 쓰고 싶은 글 일지 모르겠습니다.

사실 이전 직장에서 컨텐츠 라이터로 일하면서 블로그를 운영해보라는 말을 여러 번 들었습니다만, 결국 직장 다닐 때는 귀찮아서 뒤로 하다가 직장을 그만두고 나서야 글을 쓰게 된 것 같습니다(많은 개발자 분들이 그러하듯이…).

이 블로그에 쓰는 글은 기본적으로 번역물입니다. 하지만 나름 발 한 쪽은 IT에 몸 담고 있었던 사람인 만큼 형편없는(=직역으로 틀리지 않게만 번역하는 데에만 집중하는) 논문, 블로그, 튜토리얼, 서적 번역에 고통받으며 몸서리치고 진저리났던 오랜 경험이 있습니다.

그래서 원문을 대조해보시면 알겠지만 원문의 기술적 내용 외에는 사실상 초월번역을 하거나 아예 원문 구조 자체를 재구성해서 편찬합니다. 일종의 원문을 바탕으로 한 콘텐츠 재창조이자 큐레이팅이라고 할 수 있습니다. 필요에 따라서는 순서는 물론이고 늬앙스도 바꾸며 콘텐츠 생략과 보강도 빈번하게 합니다.

물론, 이것이 가능한 이유는 제가 모든 내용을 다 이해하고 쓰고 있기 때문이며 개발하시거나 분석하시는 입장에서 쓰려고 하기 때문입니다. 이 블로그의 Target Audience는 매우 다양하므로 모두를 만족시킬 수는 없지만, 관심있는 주제가 나왔을 때에만 봐주셔서도 좋습니다. 누군가에게라도 유익이 된다면 그걸로 족하니까요.

저는 번역가이기도 하기 때문에 사실 맞춤법과 윤문에 굉장히 민감합니다. 하지만 이 블로그만큼은 맞춤법 검사를 하거나 다 작성한 초안을 2,3번 읽으면서 탈고하는 일은 일단 생략하려고 합니다. 눈에 뵈기 싫을 정도만 아니길 바랄 뿐…

어제의 Breakthrough가 오늘의 Baseline

이 블로그에서 IT와 프로그래밍 전반을 다룰 예정이지만 머신러닝으로 학위를 딴 만큼 저는 아무래도 A.I에 관심을 두게 될 수 밖에 없는 것 같습니다. 그래서 다음 주간 매거진 포스팅은 신경망이 들어가는 포스팅을 할까 합니다.

예전에 잠시 검색 엔진 회사에서 일하면서 NLP를 공부한 적이 있습니다. 그런데 그 당시 뜨던 기술이 어떻게 발전해 왔는지를 보면서 기술 발전 속도가 정말로 빠르다는 케케묵은 클리셰같은 생각이 마음에 와닿았습니다.

그 때 당시만 해도 Seq2Seq과 Attention 모델은 매우 핫한! 주제였습니다. 많이 쓰여서 핫하다기 보다는 나온지 얼마 안 된 (2014년 논문에 상용화 시기는 2015~2016 즘이었던 걸로 기억합니다) 따끈따끈한 주제였습니다.

그런데 저번 DeepFake 포스팅을 작성하면서 GPT-2라는 게 튀어나오길래 궁금한 나머지 우연히 NLP 분야의 동향을 보게되었습니다. 재미있게도 Seq2Seq+Attention 조합은 (물론 현업에서 아직 사용하고 있는 곳도 있겠지만) 최소한 학술적으로는 벤치마크용 Baseline 취급을 받는 것 같습니다.

Self-Attention, Transformer, ELMo, BERT, GPT-2 등 신기방기한 논문이 많이 나와있는데 최신 기술이 변화하는 속도가 참 빠르구나라는 생각이 들었습니다.

내친김에 Transformer에 대한 블로그 포스팅을 잠시 읽어보았는데 아키텍처가 참 희한해 보였습니다. 마치 LSTM에서 Gate Unit을 처음 접하고 이해하려고 할 때처럼 마냥 복잡해보였습니다. 속으로 그냥 RNN 계열을 쓰지 “뭐 이딴 식으로 복잡한 아키텍처를 쓸 필요가 있나?”라는 생각도 들기도 했구요!ㅎ

Transformer는 겁나게
복잡한 구조를
지녔다

nlpinkorean이라는 사이트 운영자께서 이미 Seq2Seq+Attention과 Transformer에 대해 좋은 번역을 해주셨길래(보충/수정 제안하고 싶은 부분도 있지만) 그래서 다음 포스팅은 BERT 또는 GPT-2를 소개하는 원문 포스팅 내용을 제가 번역해서 올릴까 합니다. 사이트 운영자님께 함께 번역하자고 메일을 드렸는데 답장이 빨리 왔으면 좋겠습니다!

2020.1월 1주 – Rust in Python

Rust는 C++,C를 대체하는 로우레벨,고성능,고복잡 언어

작성한 코드에 메모리 오류 가능성이 있으면 컴파일조차 불가

Rust를 Python에서 동작시켜 성능 Advantage 확보 가능


2020년 1월 3일, 대세는 이미 Rust?

궤도에 오른 Rust

Rust에 대한 자세한 내용은 구글링을 부탁드리며 여기서는 간단히 소개하겠습니다. Rust는 2015년부터 유명세를 타게 된 시스템 프로그래밍 언어(Low-Level Language)이며 따라서 C/C++ 언어와 같은 레이어에 속해 있습니다.

Rust의 큰 특징 중 하나는 C언어에서 코딩 실력의 영역에 있던 메모리 관리 문제를 프로그래밍 언어가 해결해준다는 것입니다. 바로 메모리 문제가 생길 것 같으면 소스코드 컴파일 조차! 안 되는 Rust의 특징에 있습니다(C언어도 스마트 포인터로 이러한 메모리 관리 편의를 제공합니다).

이미지 출처: Unsplash

Rust가 주목받는 이유 중 하나는 바로 웹어샘블리(WASM)입니다. WASM은 한 마디로 웹 브라우저에서 JavaScript 대신 C/C++/Rust를 사용하게 하는 표준입니다(C/C++/Rust 등 저수준 언어로 작성된 코드를 웹으로 컴파일해 실행). 웹어샘블리를 사용하면 웹에서 기존에 돌리기 힘들었던 엄청나게 무거운 애플리케이션도 실행할 수 있게 됩니다.

제가 알기로 Rust VS C는 그냥 메모리 관리를 프로그래머에 맡긴다vs아니다 수준으로 단순하게 논의할 수 있는 내용이 아닙니다. 시스템 레벨의 복잡한 이슈가 얽혀 있으며 두 언어는 구조는 서로 다른데, 배우기는 똑같이 어렵습니다(Rust는 입문자용 언어가 절대 아닙니다).

하지만 Rust를 지원/사용하는 곳은 점점 늘어나고 있습니다. 현재 Rust를 일부라도 지원/사용하는 곳으로 알려진 곳은 Dropbox, Facebook, Amazon AWS Lambda, Mozilla Firefox, Reddit입니다.

Rust를 Python에서 실행하기: Overview

그러면 이제 이번 포스트에서 본래 다루려고 했던 주제인 Rust in Python에 관해 소개합니다. Python 사용자들이 대부분 공감하는 것은 (최적화가 되어있지 않은 or 더 최적화하기 어려운) Python 코드는 매우 느리다는 것입니다.

사람들은 그래서 NumPy, SciPy같은 C언어 기반 수치 연산 라이브러리를 사용하거나,아예 파이썬 인터프리터를 들어내고 C언어 컴파일러(Cython)를 쓰거나 JIT 컴파일 기능을 제공하는 라이브러리(Numba)를 사용해 성능을 높입니다.

그런데 이제 Python 코드 성능을 높이기 위해 Rust를 사용할 수 있습니다. Python에서 Rust를 실행하려면 아래 단계를 따릅니다.

  1. Rust로 Worker(=고성능 연산이 필요한 코드)를 작성
  2. Worker를 Rust로 컴파일
  3. Python에서 Rust를 Import
  4. Python에서 Rust Worker코드를 실행

Rust를 Python에서 실행하기: Rust 코드 작성

먼저 Rust를 설치한 후 아래와 같이 새로운 Rust 프로젝트인 rust_in_python을 생성합니다. rustup을 쓰면 윈도우, 맥, 리눅스 등 대부분의 환경에서 손쉽게 Rust를 설치할 수 있습니다. 이후 설명은 리눅스 기준입니다.

cargo new rust_in_python

Cargo(카고)는 Rust의 빌드 시스템 및 패키지 매니저로 프로젝트 관리, 코드 빌드, 의존성 라이브러리 다운로드, 라이브러리 빌드 등 Python 에코시스템의 pip이나 Anaconda와 비슷한 역할을 합니다.

그리고 rust_in_python 프로젝트의 소스코드 파일인 src/main.rssrc/lib.rs로 바꿔줍니다. 이는 우리가 작성할 Rust 프로그램이 스탠드얼론으로 작동하는 프로그램이 아니라, 다른 프로그램에서 Import해서 사용할 라이브러리이기 때문입니다.

mv src/main.rs src/lib.rs

이제 이 src/lib.rs에 헬로월드 함수를 작성해보겠습니다.

#[no_mangle]
extern fn hello() {
    println!("Hello from rust");
}

컴파일러는 컴파일을 할 때 함수명을 일정한 규칙에 따라 바꿔주는 작업을 하며 이를 Name Mangling이라고 합니다. 그런데 우리는 이 함수를 Rust가 아닌 Python에서 실행해야 하므로 Rust 스타일의 함수명이 아닌 C 스타일의 함수명이 필요합니다. 왜냐하면 우리가 나중에 Python에서 Rust 라이브러리를 로드하기 위해 사용할 도구(FFI)는 C로 작성된 라이브러리를 로드할 때 사용하는 도구이기 때문입니다. 따라서 #[no_mangle]이라는 flag를 붙이면 일단 우리가 작성한 함수를 Rust 스타일의 함수명으로 바꾸는 작업을 생략합니다.

extern 키워드는 Rust에서 이 함수가 외부 언어에서 호출될 수 있음을 알려주는 키워드입니다.

Rust를 Python에서 실행하기: Rust에서 컴파일

그리고 이제 우리가 작성한 헬로월드 함수가 담긴 lib.rs를 컴파일하면 됩니다. 그런데 이 코드를 그냥 컴파일하면 확장자가 *.rlib이라는 파일이 나타납니다. *.rlib은 Rust Static 라이브러리 파일을 뜻하는 확장자이며 이 라이브러리는 Rust에서만! 사용할 수 있음을 말합니다. 따라서 이 라이브러리를 다른 언어에서 사용할 수 있도록 Dynamic 라이브러리 파일로 컴파일해야 합니다. 그래야 이 라이브러리를 Python의 외부 함수 인터페이스(FFI)가 인식하도록 하여 Python에서 Import할 수 있습니다.

lib.rs를 Dynamic 라이브러리로 컴파일하려면 Cargo.toml 파일에 다음을 추가하면 됩니다.

[lib]
crate-type = ["dylib"]

이제, cargo를 통해 이 코드를 컴파일합니다.

cargo build --release

Rust를 Python에서 실행하기: Python에서 Rust를 Import 및 실행

이제 Python 메인(main.py)을 만들고 Rust로 작성한 라이브러리 파일을 Import해 실행해봅니다. main.py에 다음을 작성합니다.

from ctypes import CDLL

lib = CDLL("target/release/librust_in_python.dylib")
lib.hello()

파이썬의 ctypes 라이브러리가 바로 파이썬의 외부 함수 인터페이스(FFI) 중 하나입니다. 이 라이브러리는 CDLL이라는 클래스를 통해 사용자가 C로 작성된 라이브러리를 파이썬에 로드할 수 있게 합니다. 아까 Rust에서 컴파일해 만든 *.dylib 경로를 사용해 라이브러리를 로드하고 헬로월드 함수를 실행합니다.

python main.py

Rust를 Python에서 실행하기: Parameter 입력, Return값 받기

이제 Rust에서 만든 함수에 입력(=파라미터)을 주고 그 결과(=리턴)을 받아보겠습니다. 그런데 Rust는 Typed, Python은 Untyped 언어이므로 Rust 함수를 Python에서 실행하려면 함수에 넘겨줄 파라미터가 어떤 타입이고 함수 실행 결과는 어떤 타입으로 반환할 것인지를 명시해야만 합니다.

#[no_mangle]
extern fn add(a: f64, b: f64) -> f64 {
    return a + b;
}

Rust로 작성한 이 함수는 Float64타입 2개를 입력으로 받아서 이들을 서로 더하여 결과값도 Float64타입으로 반환하는 덧셈함수입니다.

cargo build --release

이제 파이썬 파트를 보겠습니다.

from ctypes import CDLL, c_double

lib = CDLL("target/release/librust_in_python.dylib")

lib.add.argtypes = (c_double, c_double)
lib.add.restype = c_double

result = lib.add(1.5, 2.5)
print(result)  # 4.0

lib.add.argtypes은 직관적으로 알 수 있듯이 Rust 덧셈함수에 넘겨줄 2개의 입력 argument 타입을 명시한 튜플입니다: ctypes 라이브러리에서 사용하는 타입 목록

마찬가지로 lib.add.restype으로 return값의 타입을 정의합니다.

Python C Rust
c_bool
c_bytechari8
c_ubyteunsigned charu8
c_shortshorti16
c_ushortunsigned shortu16
c_intinti32
c_uintunsigned intu32
c_longlongi64
c_ulongunsigned longu64
c_floatfloatf32
c_doubledoublef64
※ Python, C, Rust 데이터 타입

※ Array & List

Rust
#[no_mangle]
extern fn sum(arr: [i32; 5]) -> i32 {
    let mut total: i32 = 0;
    for number in arr.iter() {
        total += number;
    }
    return total;
}
Python
from ctypes import CDLL, c_int

lib = CDLL("target/release/librust_in_python.dylib")

lst = [1, 2, 3, 4, 5]
# Create the memory of the list size
seq = c_int * len(lst)
arr = seq(*lst)

result = lib.sum(arr)
print(result)

※ Class & Complex Data Type

Rust
#[repr(C)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

#[no_mangle]
fn greet_point(p: Point) {
    println!("x: {}, y: {}", p.x, p.y);
}
Python
from ctypes import CDLL, Structure, c_double

lib = CDLL("target/release/librust_in_python.dylib")

class Point(Structure):
    _fields_ = [
        ('x', c_double),
        ('y', c_double)
    ]


p = Point(x=1.2, y=3.4)
lib.greet_point(p)

이번 포스팅을 작성하면서 Rust에 대해 알 수 있어서 좋았습니다. 그런데 과연 Python에서 굳이 Rust로 작성한 라이브러리를 써야할 일이 얼마나 있을까라는 생각이 들었고(파이썬이 여러 방법으로 C/C++ 기반 라이브러리를 지원하는데 굳이…) 성능 벤치마크가 있다면 보고 싶어졌습니다.

제가 중간중간에 수정도 하고 더 찾아서 보충도 하긴 했지만, 지금까지는 너무 남이 써 놓은 포스팅이나 뉴스만 옮겨오는 것 같습니다. 조만간 기회가 된다면 제가 했던 강화학습 프로젝트에 대해 잠시 다뤄보겠습니다. 읽어주셔서 감사합니다~!

2019.12월 4주 – [논문요약] DeepFake, 공공정책 결정에 개입

OpenAI NLP 프레임워크 GPT-2를 사용한 텍스트 생성 (DeepFake Text)

생성한 텍스트와 코멘팅 봇으로 시민 의견 수렴 과정에 개입

로봇이 작성한 코멘트인지 아닌지 구별할 수 없음 (정확도 50% 미만)

캡차(CAPTCHA) 등 인증수단 강화 필요


2019년 12월 31일, 여론 형성에 개입하는 DeepFake

DeepFake는 위와 같은 웹 여론 수렴에 위험요소가 되고 있다.

행복한 2019년 마지막 주 되셨기를 바라며, 모두 새해에는 더 행복하고 기쁜 삶이 되시기를 바랍니다.

새해 첫날인 오늘, 12월 마지막 주 포스팅으로 인공지능이 우리 사회에 어떤 영향을 미칠 수 있는지와 관련해 논문 한 편을 요약, 번역해 소개합니다.


논문 제목:  Deepfake Bot Submissions to Federal Public Comment Websites Cannot Be Distinguished from Human Submissions
게시일: 2019-12-18
게시처: Technology Science
저자: Max Weiss (A Senior at Harvard College)
링크: https://techscience.org/a/2019121801/ 

Abstract/Results 요약

미 연방 정부는 어떤 공공 정책을 시행할 때 댓글 업로드를 통해 여론을 수렴하는 시기를 둡니다. 그런데 이러한 여론 수렴 제도는 잘못된 의도를 가진 사람들에게 악용당하기 쉬운 구조를 갖고 있습니다.

2017년, 미 연방 통신위원회(이하 FCC)가 망 중립성(net neutrality) 폐지를 추진하는 과정에서 전체 댓글의 약 96%인 2,100만개의 댓글(총 댓글 수 약 2,200만개)가 비교적 단순한 방법인 치환법(Search-and-Replace)으로 생성된 문장임이 밝혀진 바 있습니다. 치환법이란, 기계로 문장을 생성하는 전통적인 방법 중 하나입니다. 사람이 만든 샘플 문장을 가지고 그 문장의 문맥은 그대로 유지되면서 문장을 구성하는 단어만 바꿔치는 방법으로 생성하는 방법입니다.

문제는, 치환법으로 만든 문장이 사람이 만든 문장인지 기계가 만든 문장인지 비교적 쉽게 구별이 가능했다면, 이제는 구분이 불가능한 방법으로 기계가 문장을 만들어내고 이를 댓글에 올릴 수 있다는 점입니다.

DeepFake 텍스트는 TensorFlow, OpenAI와 같이 누구나 사용할 수 있는 오픈소스 AI 도구로 만들 수 있으며, 이 연구에서는 사람이 과연 이러한 “가짜 여론”을 과연 기계가 쓴 것인지 아니면 사람이 쓴 것인지 구분할 수 있는가를 테스트했습니다.

이 연구에서는 OpenAI의 DeepFake NLP 프레임워크인 GPT-2를 사용해 기존의 연방 공공 정책 결정을 위한 여론 수렴에 사용되었던 댓글을 학습한 모델을 만들었습니다. 그리고 이 DeepFake 텍스트 생성 모델로 만든 가짜 댓글을 Proxy 서버, 웹봇을 사용해 2019년 10/26~30 기간 동안 진행된 연방 정책(Section 1115 Idaho Medicaid Reform Waiver: 미국 아이다호 주의 저소득층 의료급여 제공 조건을 완화하는 변경안)에 관한 여론 수렴 사이트에 업로드했습니다.

해당 기간에 여론 수렴 사이트에 업로드된 총 댓글 수는 1,810개였고, 그 중 DeepFake 모델로 만든 가짜 댓글 수는 1,001개로 전체의 약 55.3%을 차지했습니다.

그리고 108명으로 구성된 설문조사 대상을 구성해 이 가짜 댓글들과 사람이 실제로 쓴 댓글들을 섞어서 누가 쓴 글인지 구별할 수 있는가를 확인했습니다. 이들 108명은 전통적인 방식으로 작성한 기계 댓글을 잘 분별해낼 수 있는 능력을 가진 사람으로 구성했고, 적극적 참여를 위해 실험 참여와 더불어 글쓴이를 정확히 분별했을 경우 보상이 주어졌습니다.

실험 결과, 이들은 약 49.63%의 분별 정확도를 보여주었고 이는 임의로 선택한 정확도(50%)보다 낮은 수치이므로 사람은 DeepFake 모델로 만든 가짜 여론이 가짜인지 아닌지 정확히 분별하기 어렵다는 결론을 얻었습니다.

Data & Methodology & Else

인트로, 백그라운드 등은 생략하고 사용된 데이터, 도구, 방법론은 아래와 같습니다.

  1. 연방 공공 댓글 사이트(Federal public comment website, 여론 수렴 사이트)
  2. OpenAI GPT-2 NLP 프레임워크
  3. 트레이닝셋용(Training Data) 기존 공공 댓글
    • 다른 주(아칸소, 알라바마, 애리조나, 인디아나 등)에서 마찬가지로 저소득층 의료급여 수급조건 완화 정책을 위해 만든 여론 수렴 사이트의 댓글을 Medicaid.gov에서 다운로드하여 사용
  4. 웹봇 및 프록시 서버
  5. 설문조사 도구(Qualtrics and Amazon Mechanical Turk Survey)

※ 웹봇 루틴(Web-bot Execution Routine)

  • 가짜 댓글이 모두 다 입력될 때까지 다음을 실행:
    1. 데이터셋에서 가짜 댓글 선택
    2. 크롬 드라이버 창 열기
    3. 여론 수렴 사이트 URL 로딩
    4. 댓글 입력 박스에 댓글 입력
    5. “Finish” 버튼 클릭
    6. 크롬 드라이버 창 종료
    7. 잠시 대기(Sleep). 대기 시간은 일정 범위 내(수초~수분)에서 랜덤
    8. 데이터셋의 다음 가짜 댓글로 넘어간 후, 2~8번 과정 반복

※ GPT-2 모델

총 파라미터 수는 1억 2천 4백만개이며 가장 낮은 수준(기본적인 수준)의 모델을 사용했고, Temperature 파라미터는 기본값인 0.7로 고정되었습니다.

(역자 주: GPT-2에서 Temperature 파라미터는 템퍼레쳐라는 영단어 해석에서 직관적으로 알 수 있듯이 “문장의 성격, 분위기”를 의미합니다. Temperature는 볼츠만 확률 분포를 사용해 텍스트 내용의 랜덤한 정도를 결정하는 실수 파라미터이며 0에 가까울 수록 반복적이고 예측이 쉬운, 즉 뻔한 문장 패턴을 띄게 됩니다. 반대로 1에 가까울수록 더 불규칙적이고 예측불허의 문장을 만들어냅니다)

학습에 사용한 댓글의 문장 길이는 75~100 단어이며, 이는 문장 길이가 쓸데없이 길어지는 것을 막아 테스트 시에 사람들이 문장 길이를 가지고 기계가 만들었는지 분간하지 못하도록 하기 위함입니다. GPT-2 모델은 총 179,034개의 서로 다른 가짜 댓글을 생성했으며 이중 적절하다고 판단된 1,001개 댓글을 수작업으로 뽑아 테스트에 사용했습니다. (역자 주: GPT-2를 사용했지만 사람 손을 결국 탔다는 데에서 공격받을 여지가 있는 부분으로 보입니다)

학습, 모델 수정(파인튜닝), 텍스트 생성에 사용한 코드는 Max Woolf 씨에 의해 무료로 제공되며 다음 구글 Colab 사이트에서 받으실 수 있습니다:

OpenAI GPT-2 모델 (GPU버전)

Comments Examples

  • GPT-2 모델로 생성해 여론 수렴 사이트에 올린 “가짜” 코멘트 예시
CommentResponse IDDate/TimeSentiment
I support Governor Little’s efforts to overhaul Idaho’s Medicaid program.45966910/27/2019  4:00:00 PM의료급여 수급조건 완화에 찬성
Medicaid is an important safety net program. It helps people who are losing their coverage to get back on their feet. We need to make health and wellness a priority for the Medicaid program in Idaho.45982510/27/2019  6:08:00 PM중립
I am writing to you today regarding Idaho’s Medicaid waiver proposal, I oppose the aspects of this program that create new burdens on people who are already struggling. The proposed changes to Medicaid could deny health insurance to sick individuals when they are most in need. I do not support this approach that creates barriers to access. I am hopeful that you change the proposed waiver.46012910/27/2019  10:36:00 PM의료급여 수급조건 완화에 반대
GPT-2 모델은 영문장을 학습했으므로, 영문 그대로 읽어보시고 기계가 썼다는 생각이 드는지 확인해보세요!
  • 기계 댓글인데 사람이 쓴 글로 오분류된 댓글 예시
“사람 댓글”로 잘못 분류된 “기계 댓글”설문조사 시 분류 성공율
As a physician and health educator, I feel this is absolutely crucial to care for the poor and vulnerable in Idaho. The proposed waiver would harm the most vulnerable members of our state. It would make our state so sickly and with so many children and adults who would be affected by this waiver, this will end up costing most of the proposed budget to administer.0.00%
The work requirements are based on a false premise that everyone who qualifies for Medicaid is “lazy” or “unemployed.” In fact, work requirements are a huge waste of time and resources. The proposal is based on a false premise that the state is offering people who are working nothing but the minimum wage an additional 20 hours a week. This is a ridiculous idea.0.00%
I am very concerned about the impact these new work requirements would have on people in Idaho who are already struggling. I am a social worker and had no idea that the work requirements were being challenged and ended up costing the state more money.4.76%
Taking away health care will not promote work. The evidence from other developed countries shows that work itself actually leads to better health6.67%
The goal of Medicaid is to give coverage to those who need it. This work requirement will not accomplish this goal.10.00%
In addition, it should be noted that the working families who are eligible for Medicaid will have to pay a premium and/or copay. These are families who make minimum wage and/or don’t have resources to pay for health insurance, even if they are eligible.10.00%
Bot 댓글이 Bot으로 정확히 분류된 비율: 평균 31.11%
  • 사람 댓글인데 기계가 쓴 글로 오분류된 댓글 예시
“기계 댓글”로 잘못 분류된 “사람 댓글” 설문조사 시 분류 성공율
[ Medicade health coverage helps Idaho’s most frail and vulnerable]25.00%
[Insert Comment Here]I request your action to prevent the State of Idaho from implementing illegal work reporting requirements.28.57%
[I think Idaho should respect the will of the voters and enact expanded Medicaid without any additional requirements.28.57%
[Thank you for the opportunity to comment. I am against the work requirements for Medicaid. This requirement came on the heels of expanding Medicaid to cover the working Americans in the gap. Working Americans. These requirement are onerous and should not be allowed to take effect.]31.82%
Please disallow this constriction to healthcare delivery, extra complicated bureaucracy and greatly added wasteful expense.36.00%
Reject Medicare restrictions.43.75%
WORK REQUIREMENTS ARE ILLEGAL.45.83
Please NO restrictions.47.37%
Human 댓글이 Human으로 정확히 분류된 비율: 평균 66.67%

위 2개 테이블 결과에서 사람들이 오히려 기계가 쓴 글을 더 “사람답게” 생각하는 것을 확인할 수 있습니다.

이 실험은 실제 아이다호 주 공공 정책과 관련된 여론 수렴 사이트에서 진행했기 때문에 실험이 실제 여론과 정책 결정에 미치는 영향을 최소화해야 했습니다. 따라서 연구진은 사전에 CMS( Centers for Medicare and Medicaid Services, 메디 케어 및 메디 케이드 서비스 센터: 의료 프로그램을 관리하고 주 정부와 협력하여 의료 분야를 관리하는 미국 보건 복지부 내 연방 기관)에 실험을 인가받고 고지했으며, 웹봇이 작성한 모든 댓글은 공식적으로 CMS에 의해 삭제되었습니다.

전 제가 데이터 분석을 하던 2016~2017년 경을 기준으로 한국어를 처리하고 한국어 문장을 생성하는 NLP 프레임워크는 그닥 성능이 만족스럽지 못했던 기억이 있습니다. 하지만 이 논문을 읽으면서 GPT-2가 영문장을 학습, 생성하는 수준으로 한국어 문장을 다룰 수 있는 도구가 있다면, 포털사이트와 정부 청원 사이트 등 여러 곳에서 특정한 의도를 가진 집단에 의해 여론이 “가짜”로 생성될 수도 있겠다는 생각이 들었습니다.

흔히 볼 수 있는 자동화 입력 방지 프로그램인 CAPTCHA 예시

논문 본문에는 이를 방지하기 위해 몇 가지 방법을 제시하고는 있지만, GPT-2와 웹봇과 프록시 서버를 사용할 줄 아는 개발자라면 CAPTCHA를 비롯한 인증수단도 우회하거나 인증과정도 기계화할 수 있지 않을까요?

2019.12월 3주 – Python의 __init__()과 __new__()

클래스 인스턴스를 메모리에 할당해 “생성” = Allocator, Instantiator

“생성”된 인스턴스를 “사용 준비 시킴” = Constructor, Initializer

__init__은 Constructor, __new__는 Allocator

__new__는 방어적 프로그래밍에 사용 가능


2019년 12월 18일, __init__()은 생성자?

class Foo:
  def __init__(self, value_a, value_b):
    self.a = value_a
    self.b = value_b

foo = Foo(7, 9) # __init__ is called
print(foo.a , foo.b) # 7 , 9

__new__와 __init__의 차이

먼저, 이 포스팅에서는 클래스 객체(object)와 클래스 인스턴스(instance)를 구분없이 사용함을 알려드립니다. 또 글 처음부터 끝까지 원문에 달린 코멘트와 제가 이해한 내용을 바탕으로 종합해 정리한 내용이 주를 이룹니다.

오늘 포스팅은 C++, Java 등 여러 객체지향 언어를 다루다가 파이썬에 입문한 분들과 파이썬만 사용하시는 분들 모두를 위한 글입니다. 오늘은 생성(Construct)과 초기화(Initialize)의 개념을 우리가 어떻게 바라보아야 할 지에 관해 논하려고 합니다.

이는 그리 복잡하지는 않지만 다소 혼란스러울 수 있는 주제입니다. 객체 지향 프로그래밍 언어(java, python, C++, …) 사용자라면 생성자(Constructor)에 관해 들어본 적이 있을 것입니다. 그리고 파이썬 사용자로서 클래스를 다뤄본 사람이라면 __init__ 메소드는 클래스 “생성자 메소드”라는 것도 알고 있을 것입니다.

그런데 한 가지 재미있는 점은 바로 파이썬 클래스에서 __init__ 메소드는 클래스 오브젝트에 메모리를 할당하지 않는다라는 것입니다. 따라서 __init__은 클래스 인스턴스를 생성하지 않습니다. 즉, __init__은 생성자 메소드로 불리기에 애매하다는 점입니다.

즉, __init__ 메소드는 클래스 인스터스 형태인 객체(Object)가 생성(Created/Instantiated)되어 초기화(Initialized)되는 즉시 호출(Called)되기는 합니다만, 객체에 메모리를 할당하지 않는특수한 메소드라는 것입니다.

그러면 객체에 메모리를 할당(Allocate)하는 주인공은 누구냐? 바로 __new__ 메소드입니다. 파이썬에서 객체를 생성해보면 __init__이 실행되기 전에 항상 __new__가 먼저 실행되며, 이 때 객체에 메모리가 할당됩니다.

class Point():
    def __new__(cls,*args,**kwargs):
        print("From new")
        print(cls)
        print(args)
        print(kwargs)
        # create our object and return it
        obj = super().__new__(cls)
        return obj
    def __init__(self, x = 0, y = 0):
        print("From init")
        self.x = x
        self.y = y
>>> p2 = Point(3,4)
From new
<class '__main__.Point'>
(3, 4)
{}
From init

위 코드를 보시면 아시겠지만, __new__메소드는

  • __init__보다 먼저 실행되며
  • 클래스 자기 자신(cls)을 숨겨진 파라미터로 받으며
  • 반드시 object를 return함

을 알 수 있습니다. 예시 코드에서는 Point 클래스를 포함해 모든 클래스의 부모 클래스인 Object 클래스의 객체(object)를 반환합니다. 즉, object를 “생성”해 반환한다는 점에서 __new__ 메소드가 오히려 더 생성자 메소드에 가까워 보일 수도 있습니다.

객체의 Allocator/Instantiator는 __new__

객체의 Constructor/Initializer는 __init__

하지만 여기서 중요한 개념을 하나 소개합니다. 우리가 아는 “생성자”는 사실 객체를 “생성”하지 않습니다. 다른 객체 지향 언어들에서는 메모리에 주소를 할당하는 방식으로 클래스 인스턴스를 생성하는 함수를 “생성자”라고 부르지 않습니다. C++에서는 이를 배분자(Allocator), Java에서는 클래스 인스턴스를 메모리에 할당하는 Static Factory Method라고 부릅니다.

즉, __new__ 메소드는 객체를 생성해 반환하는데 이를 생성자로 부르기에는 적절치 않다는 것입니다. 개인적으로 __new__ 메소드는 차라리 인스턴시에이터(Instantiator)라고 부르는 것이 더 적절해 보입니다.

그렇다면, “생성자”는 어떤 역할을 하는 함수를 말하는 것일까요? 네, 우리가 알고 있는 __init__의 기능이 바로 생성자 역할입니다.

즉, __init__은 결국 생성자(Constructor)가 맞지만, 이 “생성(Construct)”은 우리가 알고 있는 “객체 생성” 기능과는 다르다는 것을 말하는 것입니다. “생성”이란 __new__ 메소드로 만든(Instantiate) 인스턴스를 사용자가 원하는 대로 사용하도록 커스토마이징(Customizing/Initiating)함을 말합니다. 예를 들어 self.x=x, self.y=y 와 같이 클래스 인스턴스에 프로퍼티(Property)를 부여하는 등 인스턴스 사용을 위한 초기 세팅을 주는 것이 바로 생성(Construct 또는 Initiate)인 것입니다.

(역자 주 – 사실 생성자를 다른 객체 지향 언어에서처럼 매우 엄밀하게 정의한다면 파이썬의 __init__과 __new__ 둘 다 생성자에 해당되지 않습니다. 파이썬에는 C++와 Java에서 정의하는 Constructor가 존재하지 않으며, 다만 유사한 작업을 수행한다는 차원에서 Constructor Expression이라고 표현할 수도 있습니다.)

물론, 여기까지 읽으셨다면 느끼셨겠지만 여러분들은 __init__과 __new__ 메소드의 본래 목적에는 별 관심이 없습니다. 따라서 보통 __init__으로 열심히 인스턴스를 수정하기만 하면 되고, __new__는 그냥 자동으로 실행되도록 냅둡니다.

__new__의 응용:

__new__를 사용해 생성할 객체 수를 제한하기

__new__ 메소드는 그럼 어디에 쓸모가 있을까? “점”을 의미하는 위 Point 클래스의 인스턴스 4개를 사용해 “사각형”을 의미하는 RectPoint 클래스를 만들어 보겠습니다. Point 클래스 인스턴스 4개는 이 사각형의 4개 꼭지점을 의미합니다.

당연하게도, 사각형은 꼭지점을 4개만 가질 수 있으므로 RectPoint 클래스는 Point 클래스 인스턴스를 “4개까지만” 가질 수 있어야 하며, 5번째 Point 인스턴스를 가질 수 없도록 제약을 걸어야 합니다(역자 주 – 이는 방어적 프로그래밍에 해당합니다).

class RectPoint(Point):
    MAX_Inst = 4
    Inst_created = 0
    def __new__(cls,*args,**kwargs):
        if (cls.Inst_created >= cls.MAX_Inst):
            raise ValueError("Cannot create more objects")
        cls.Inst_created += 1
        return super().__new__(cls)

이를 위해 먼저 RectPoint 클래스는 Point 클래스를 상속하도록 만듭니다. 그 다음, 가질 수 있는 클래스 인스턴스 숫자를 최대 4개로 제한합니다. 여기서 RectPoint는 Point 클래스를 상속했으므로 RectPoint의 클래스 인스턴스는 Point 클래스의 인스턴스이기도 합니다.

RectPoint가 가지는 Point 개수를 4개로 제한하기 위해 클래스 변수인 MAX_Inst와 Inst_created를 사용하며, 인스턴스를 생성하는 __new__메소드가 호출될 때 마다 클래스 변수 Inst_created의 값을 증가시켜 나갑니다.

>>> p1 = RectPoint(0,0)
>>> p2 = RectPoint(1,0)
>>> p3 = RectPoint(1,1)
>>> p4 = RectPoint(0,1)
>>> 
>>> p5 = RectPoint(2,2)
Traceback (most recent call last):
...
ValueError: Cannot create more objects

코드 결과를 보시면 5번째 점을 RectPoint 클래스로 만드려고 하면 밸류에러를 뱉게함으로써, 인스턴스를 잘못 생성하는 것을 미리 차단함을 볼 수 있습니다.

2019.12월 2주 – Python으로 PDF 테이블 추출하기

크롬/익스플로러 창으로 연 온라인 PDF 문서

PDF 문서 안에 있는 테이블을 Pandas DataFrame으로 읽어오기

tabula-py를 pip 설치 후 간단한 코드로 읽어오기 가능


2019년 12월 10일, 온라인 PDF 테이블 추출하기

Python Pandas DataFrame로 추출할 온라인 PDF 문서 속 테이블

2019년 12월 둘째 주 소식을 전합니다.

이미 잘 알고 사용하시는 사용자분들도 계시겠지만, 이번 주는 유투브에 올라온 영상 하나를 번역, 요약하여 소개하려고 합니다. 비록 짧고 쉬운 내용이지만 많은 연구자분들의 수고를 덜어줄 내용이라고 생각합니다.

이 영상은 구글 크롬이나 MS 익스플로러 창으로 연 온라인 PDF 문서에 있는 테이블을 Pandas의 DataFrame 오브젝트로 뽑아내는 방법을 소개한 영상입니다. Pandas는 파이썬 사용자라면 모르는 사람이 없을 정도로 잘 알려진 데이터 분석과 핸들링용 패키지입니다.

구글 스칼라에서 논문 검색을 하다보면 필수적으로 맞닥뜨리는 것이 바로 PDF 문서의 테이블입니다. 그리고 대학원생이나 논문을 준비중인 직장인이라면 한번 쯤, 참고하고 있는 논문 데이터를 그냥 Pandas 데이터프레임으로 한번에 뽑아내고 싶다는 생각을 해보셨을 것입니다. 저도 그랬었구요.

일단 원본 영상 링크입니다. 유투브에 그냥 공개된 영상이라 퍼와도 될 거라고 생각하는데, 혹시 문제가 된다면 댓글이나 메일로 제작자에게 허락을 구할까 합니다.

  1. 패키지 Dependencies 설치

준비물은 파이썬, 파이썬에 설치된 Pandas 패키지, 테이블이 있는 PDF 문서입니다. 먼저 추출을 위해 필요한 패키지를 설치합니다.

pip으로 tabula-py 설치

시간이 없으신 개발자/분석가 분들은 주황색으로 작성한 부분은 스킵하세요.

제가 이 블로그를 운영하면서 스스로 다짐한 것 중 하나는 “친절하게 쓰자”입니다. “모든 것을 다” 설명하자는 것은 아니지만, 최소한 개발자들의 귀차니즘으로 인해 파이썬에 막 입문한 비개발자 입장에서 보면 2계단, 3계단 생략된 설명은 지양하자는 것이 개인적인 소망입니다.

파이썬 개발자분들은 알아서 이 부분을 스킵하시리라고 믿고 설명을 계속합니다. 파이썬이 PDF문서의 테이블을 추출하려면 파이썬에 tabula-py라는 라이브러리가 설치되어 있어야 하며 이를 pip이라는 파이썬 라이브러리 인스톨 도우미(=다른 패키지를 설치하기 위해 존재하는 패키지)를 사용합니다.

관리자 권한을 획득한 채 cmd창을 연 다음, cmd창에서 파이썬이 설치되어 있는 디렉토리(python.exe 또는 python3.exe가 존재하는 디렉토리)로 이동합니다. 커스톰 설치 환경과 리눅스 환경에서 사용하시는 분들을 위한 설명은 과감히 생략합니다(이미 지식이 풍부한 개발자라고 믿고 생략합니다).

그 다음 다음 명령어를 사용해 tabula-py를 설치합니다:

  • pip install tabula-py

혹시 설치 과정에서 에러가 난다면 위 “pip으로 tabula-py 설치” 스크린샷을 참고하여 numpy, distro, pytz, python-dateutil, six 등 Dependencies 패키지 설치 여부와 버전을 확인하세요.

  1. 스크립트 작성

이제 파이썬 IDE(또는 파이썬 코드를 작성할 수 있는 창)을 열어 아래와 같이 작성합니다.

온라인 리퀘스트를 위해 SSL 패키지도 같이 import하는 것으로 보입니다. URL 변수에 테이블을 추출하고 싶은 PDF 문서가 열려있는 웹 주소를 줍니다. 코드를 작성한 후 이를 터미널에서 실행합니다(cmd창에서 python 스크립트 파일명.py 실행).

  1. 실행 및 결과 확인

코드를 보시면 아시겠지만 별 문제 없다면, PDF 파일에서 읽어온 테이블을 Pandas DataFrame 오브젝트인 df 변수로 읽어올 수 있을 것입니다.

별 문제 없다면 ” 이라고 강조한 이유는, 그 동안 수 많은 파이썬 튜토리얼과 기술 문서를 따라하면서 온갖 이유(Python2 vs Python3 설정, Environment 설정, Dependencies 설정, 패키지 변경 등)로 인해 매우 쉽고 간단한 코드가 실행되지 않은 것을 보아왔기 떄문입니다. 또 이를 해결하기 위해 이보다 훨씬 더 기술적으로 어려운 것을 공부하며 온갖 노가다와 고생을 해본 경험은 비단 저만의 것이 아니리라고 믿습니다. 참고로 저는 옛날 일이긴 하지만, 하나의 텐서플로우 튜토리얼을 따라해 결과를 그대로 재현하기 위해 GPU 드라이버를 재설치하고 2시간 넘게 깃허브 댓글을 뒤진 경험이 있습니다.

다시 한번 별 문제 없다면, 코드의 print(df) 명령이 실행되어 PDF 문서에 있는 테이블이 터미널에 잘 출력된 것을 보실 수 있습니다. 만약 PDF 문서에 테이블이 여러 개 있다면 여러 개의 테이블을 읽어온다고 하며, 그 여러 DataFrame들을 어떤 식으로 불러오는지는 최근 포맷한 제 개인 PC에 파이썬을 다시 설치하기 귀찮은 관계로? 확인하지 않았습니다.

더 알아보기: 파이썬으로 PDF Data Extractor 제작하기

워드프레스닷컴으로 이처럼 사이트 디자인
시작하기