2D Gaussian Convolution filter

2D space에서 image smoothing, noise 제거 등을 위해서 Gaussian filter를 적용할 때가 있다.

Matlab에는 워낙 다양한 toolbox들이 있어서 간혹 유사한 기능을 하는 함수들이 존재하기도 하는데, 때문에 같은 기능을 다양한 방식으로 구현을 할 수 있다.

오늘 imgaussfilt 함수와 mvnpdf 함수를 사용해서 Gaussian filter를 적용을 하다가 몇가지 차이점과 유의점을 발견하여 이곳에 적는다.

 

Original Image

코드 설명에 사용할 Original Image를 만드는 코드이다.

100 x 100 이미지에 (50, 50) 과 (20, 50) 의 pixel만 1의 값이 들어가있다.

originalImage = zeros(100,100);
originalImage(50, 50) = 1;
originalImage(20, 50) = 1;

Method 1: imgaussfilt

함수 개발 목적 자체가 2D 이미지에 Gaussian filter를 적용하기 위한 것이기 때문에 가장 간단하게 원하는 목적을 달성할 수 있다.

figure(1);
clf;
sigma = 1;
fimage1 = imgaussfilt(originalImage, sigma, 'FilterSize', 101);
imagesc(fimage1);

여기서 중요한 것은, 'FilterSize' Name-Value pair인데, 이 값을 입력하지 않거나, 작은 값으로 설정하는 경우 convolve 하는 kernel의 크기가 작아서 빠르지만 조금은 부정확한 결과가 나올 수 있다.

참고로 fimage1의 합은 2가 된다.

 

Method 2: mvnpdf로 kernel을 만든 뒤 conv2

직접 2D Gaussian kernel을 만든 뒤에 원본이미지와 conv2 함수를 사용해서 적용하는 방식이다.

[X,Y] = meshgrid(1:101, 1:101);
X = X(:);
Y = Y(:);
mu = 51;
sigma = [1, 0; 0, 1];
kernel = reshape(mvnpdf([X,Y],mu,sigma),101, 101);

fimage2 = conv2(originalImage, kernel, 'same');

figure(2);
imagesc(fimage2);

51의 값은 101 크기의 kernel의 중간값이다.

 

위 결과는 imgaussfilt의 결과와 정확히 일치한다.

 

 

 

함수 개발 목적 자체가 2D 이미지에 Gaussian filter를 적용하기 위한 것이기 때문에 가장 간단하게 원하는 목적을 달성할 수 있다.

Posted by Knowblesse

들어가며

  안전한 보안을 위한 좋은 방법 중 하나는 Two Factor Authentication (2FA)를 계정에 설정해두는 것이다. 사이드마다 다른 비빌먼호 생성, 주기적인 비밀번호 변경보다 훨씬 쉽게 보안 수준을 높게 유지할 수 있다. 그런데 github에 2FA 인증을 설정해두면 서드파티 앱이나 shell에 있는 git 에서 remote repository (remote)에 접근을 하기 어려워진다. 또한 지난 2021년 8월 13일부로 github 계정을 사용한 remote 접근이 완전히 차단되어서 더이상 단순히 비밀번호를 입력해서 접근하기가 어려워졌다. 때문에 아래와 같은 방법을 통해 remote에 접근 할 수 있다.

 

1. Token 발급 : sub-password의 역할을 하는 token을 발급받아 비밀번호 대용으로 쓰는 방식으로, 각 token 마다 유효기간, 권한등을 설정할 수 있다. 기존의 github 계정 비빌번호를 사용하는 방법을 완전히 대체 가능하다.

2. OAuth : Open Authentication. 로컬 git, 서트파티앱에 github 로그인 정보를 제공하지 않고 github에 연결하게 해주는 인증방식이다.

3. SSH : 이 글에서 다룰 내용으로 한번 설정만 제대로 한다면 편리하고 보안성도 좋다.

 

설정 난이도만 생각하면 Token을 발급받는 방법이 가장 쉽다. Token을 발급받아서 필요한 권한을 준 뒤, 어딘가에 적어두고 remote를 연결할 때 사용하면 된다. 그러나 더 높은 보안과 편리성을 위해서는 SSH를 사용하는 것을 추천한다.


SSH 연결 특징

  SSH는 Secure SHell의 약자이며 구체적인 SSH 통신 방법은 여기에서 다루지 않겠다. 대신, SSH 를 사용하면 어떤 것들이 필요하고, HTTPS를 사용하는 방식과는 어떻게 다른지 비교를 하겠다.

 

사용자 이름을 knowblesse, repository 이름을 myrepo라고 했을 때 HTTPS와 SSH에서 필요한 것은 아래와 같다.

 

  HTTPS SSH
remote repository 주소 https://github.com/knowblesse/myrepo git@github.com:knowblesse/myrepo.git
ID knowblesse@gmail.com X
PSW (Token) ghp_********* X
Public Key / Private Key X SHA256:e/***************
(옵션) credential 저장 git config credential.helper store
git config --global user.name Knowblesse
git config --global user.email knowblesse@gmail.com
eval "$(ssh-agent -s)"
ssh-add

 

차이점 1 : remote 주소

SSH는 인증방식이 아니라 (보안 인증 프로토콜을 포함하고 있는) 통신 방식이다. 때문에 일반적으로 알고 있는 https로 시작하는 주소가 아닌 git@github.com 으로 시작하는 주소를 입력해야한다.

차이점 2 : 인증

SSH에서는 public keyprivate key, 두 가지 파일을 사용하는 공개키 암호화 방식을 기본으로 택하고 있다. 처음에 key 생성기를 사용해서 서로 매치가 되는 public key와 private key를 생성하고, 인증을 원하는 곳에 public key를 제공하면 이후 접속시 private key를 이용해서 인증받을 수 있다.

차이점 3 : Credential 저장

token을 사용하는 HTTPS 방식에서는 credential.helper를 사용해서 token을 저장해둘 수 있다. SSH에서는 ssh-agent를 사용해 credential을 저장한다.


SSH 연결 설정

SSH 연결 설정 순서는 다음과 같다.

 

1. SSH 키가 없는 경우 : SSH 키 생성

2. Github에 SSH 등록

3. local repository에 SSH 기반 remote 주소 등록

4. ssh-agent 셋업 및 연결 방법


1. SSH 키 생성

    SSH 키 생성은 다양한 프로그램이 지원하고 있는데, Window와 Linux 모두 기본으로 설치되어있는 ssh-keygen을 사용하는 방법이 가장 보편적이다.

 

먼저 기존에 생성해둔 ssh키가 없는지 간단히 확인한다.

Window ("C:\Users\Knowblesse") 와 Linux ("\home\Knowblesse") 의 유저폴더 안에 ".ssh" 라는 명칭의 폴더 안에 .pub로 끝나는 파일이 있는지 확인해보면 된다. 키 생성을 한번도 안한경우 폴더 자체가 없을 것이다.

 

Window cmd나 Linux shell에서 다음과 같이 ssh-keygen을 실행하자.

> ssh-agent -t ed25519

ssh-agent는 기본 옵션으로 rsa key pair를 만드는데 최신 방식은 ed25519라고 한다. 기본 옵션으로 키를 생성하려면 "ssh-agent"만 실행하면 된다.

 

이후 파일을 저장할 위치를 묻는데 default 위치를 그대로 사용해야지, 다른 곳에 저장을 했다가는 추가로 key 위치를 입력해주는 작업을 해야한다. 가능하면 ~/.ssh 폴더 안에 생성하도록 그대로 두자.

> ssh-keygen -t ed25519
Generating public/private ed25519 key pair.
Enter file in which to save the key (C:\Users\Knowblesse/.ssh/id_ed25519):

다음으로는 password를 묻는다.

> ssh-keygen -t ed25519
Generating public/private ed25519 key pair.
Enter file in which to save the key (C:\Users\Knowblesse/.ssh/id_ed25519):
Enter passphrase (empty for nopassphrase):

이 passphrase는 private key를 한번 더 암호화 하는데에 사용된다. private key에도 암호를 걸어두는 것을 적극 권장한다.

Q. 왜 passphrase를 설정하나요?
A. 행여나 private key가 노출되었을때를 방지하기 위함입니다. 실질적인 보안은 사실 public/private key 쌍이 담당하고 있기에 passphrase를 복잡하게 설정할 필요는 없습니다. 때문에 명칭도 password가 아니라 phrase 입니다.

 

private key에 암호설정 과정을 마치면, 아래와 같은 출력 메시지 후에 public key와 private key가 생성된다.

예시 화면

.pub 확장자를 가진 파일이 public key, 아무런 확장자가 없는 파일이 private key 이다.

생성된 두 파일은 텍스트 편집기로 열 수 있는 plain text 파일이며 private key는 절대 인터넷 상에 노출이 되면 안된다.

 

Q. 어차피 private key는 public key와 인증을 거치는 과정 중에서 인터넷 상에 노출되지 않나요?
A. SSH 인증에 사용하는 비대칭 공개키 암호화 방식은 1) 서버가 랜덤한 메시지를 호스트로 보내고, 2) 이를 받은 호스트는 private key로 암호화를 해서 다시 서버에게 보내고, 3) 암호화된 메시지를 받은 서버가 자신이 가지고 있는 public key를 사용해 복호화 한 뒤, 원래 보냈던 메시지와 비교하는 방식입니다. 때문에 private key는 인터넷 상에 절대 노출되지 않습니다.

2. Github에 SSH 등록

    생성한 public키는 Github에 등록할 수 있다.

 

 

새로운 SSH 키 등록 버튼을 누른 뒤, 텍스트 편집기로 .pub 파일을 열어서 내용을 붙여넣으면 된다.

public key 입력

3. local repository에 SSH 기반 remote 주소 등록

    여기서부터 살짝 까다로워 진다.

3-1. 처음 repository를 생성하는 경우 (clone 하는 경우)

https 기반 주소가 아니라 아래처럼 SSH 기반 주소를 입력하면 끝난다.

> git clone git@github.com/knowblesse/myrepo

 

3-2. 기존에 생성된 repository를 변경하는 경우

remote에 연결된 url 을 SSH 형식으로 수정하는 작업이 필요하다.

> git remote
origin

먼저 git remote 커맨드로 어떤 remote 와 연결되어있는지 확인한다. 위의 예시에서는 origin만 연결되어 있다.

 

추가로 아래 커맨드를 사용하면 origin의 url을 볼 수 있다.

> git remote get-url origin
https://github.com/knowblesse/myrepo.git

url의 변경은 set-url을 사용하면 된다.

> git remote set-url origin git@github.com:knowblesse/myrepo.git

형식에 주의하자

git@github.com:knowblesse/myrepo.git

Q. 여러 개의 SSH key를 사용할 경우에는 어떻게 하죠? 어떤 key를 쓰라고 지정할 수는 없나요?
A. ~/.ssh 폴더 안에 config 파일을 만들어서 어느 사이트에 연결할 때 어떤 key 파일을 사용할지 정해줄 수 있습니다. 이 방법 외의 다른 방법도 있지만, git command 안에서 "어떤 key 파일을 써라" 라고 명시적으로 알려주는 방식은 없는 것으로 알고 있습니다. config 파일을 생성하는 방법은 아래 접은 글을 참고해 주세요.
더보기

config 파일 만드는 법

open SSH 에서 사용하는 config 파일은 여러 요소로 구성되어 있으나, 복수의 key를 사용하기 위한 목적이면 Host, HostName, IdentityFile 3개의 키워드만 사용하면 된다. 구체적인 작성법은 다음 사이트를 참고하자.

https://www.ssh.com/academy/ssh/config

 

SSH config file syntax and how-tos for configuring the OpenSSH client

SSH config file syntax and how-tos for configuring the OpenSSH client

www.ssh.com

config 파일을 사용해서 복수의 key를 사용하는 원리는 Host의 alias 생성이 가능하다는 점을 이용하는 것이다.

git@github.com:knowblesse/myrepo.git

 

위의 주소에서 git 은 사용자명(knowblesse가 아니다!), github.com 은 Host의 위치, knowblesse/myrepo.git은 호스트 서버 내의 내 remote의 위치이다(정확히는 remote 정보를 담고 있는 git 파일).

 

중요한 점은 github.com 이 실제 주소가 아니라 Host의 이름 이라는 것인데 open SSH는 config 파일에서 해당 이름을 가진 Host가 없으면 이를 실제 Host 주소로 사용해서 실제 github.com과 통신을 시작한다.

 

반면, Host를 지정해주고 실제 Host의 주소를 config 파일에 명시하면 다른 형태로 접속이 가능하다.

Host myhub
	HostName github.com

예를들어, 위와 같이 config 파일을 설정해주면 이후 github.com을 입력하지 않고 단순히 아래처럼 입력할 수 있다.

git@myhub:knowblesse/myrepo.git

주의점은 HostName이 alias가 아니라 Host가 alias이다. HostName은 실제 주소다!

 

또한 config 파일에서는 각 host 마다 사용할 IdentityFile의 위치를 지정할 수 있다.

 

여기까지 설명을 하면 이미 눈치를 챘을 것이다. IdentityFile만 다르게 지정한 alias를 여러개 만들어 두면 remote의 set-url을 진행할 때 사용하는 alias에 따라 다른 key 파일을 사용하도록 할 수 있다.

 

다음은 account1_private 과 account2_private key 파일을 사용하는 config 파일의 예시이다.

#keyfile 1
Host account1
         HostName github.com
         IdentityFile ~/.ssh/account1_private
#keyfile 2
Host account2
         HostName github.com
         IdentityFile ~/.ssh/account2_private

이후 git remote set-url 함수를 사용해서 repo 마다 remote의 주소를 설정해주어야 한다.

예를 들어 account1_private key 파일을 repo1에 사용하려면 아래와 같이 url을 변경한다.

> git remote set-url origin git@account1:knowblesse/repo1.git

4. ssh-agent 셋업 및 연결 방법

    여기까지 설정을 완료했고, key 파일이 기본 위치인 ~/.ssh 안에 들어있다면 정상적으로 remote와 연결이 될 것이다. 그러나 매번 passphrase를 물어본다는 단점이 있다. 이를 해결하는 방법은 ssh-agent를 사용해서 잠시동안 passphrase를 저장해두는 것이다. 이 경우 한동안 passphrase 없이 SSH 통신을 할 수 있다. 마치 credential.helper 를 사용해서 token을 저장해두는 것과 동일하다.

 

순서는 두 과정을 거치는데, 1)ssh-agent를 실행하고, 2) ssh-add를 통한 key 등록이다.

Linux

> eval `ssh-agent -s`
Agent pid 12345
> ssh-add

Windows (PowerShell)

> Set-Service ssh-agent -StartupType Manual
> Start-Service ssh-agent
> ssh-add
Q. 왜 그냥 ssh-gent를 실행하면 안되고 꼭 eval 함수 사용해서 실행해야하죠?
A. eval을 쓰지 않고 ssh-agent 명령을 실행해도 ssh-agent 프로세스는 정상적으로 시작된다. 그런데 그 이후에 ssh-add가 정상작동하지 않는다. 그 이유는 ssh-add가 SSH 소켓에 대한 정보를 필요로 하기 때문이다.

ssh-agent 명령을 실행하면 다음과 같은 출력이 나온다.

> ssh-agent -s
SSH_AUTH_SOCK=/tmp/ssh-XXXXXXmj93qJ/agent.36991;
export SSH_AUTH_SOCK;
SSH_AGENT_PID=36992;
export SSH_AGENT_PID;
pid 36992;

눈치 챘겠지만, ssh-agent 명령은 ssh-add 에게 매번 긴 SSH_AUTH_SOCK에 해당하는 값을 전달해주지 않도록 SSH 소켓과 ssh-agent 관련 정보를 bash 코드로 출력한다. 때문에 출력 텍스트를 그대로 코드처럼 실행시키는 eval 함수를 사용하여 ssh-agent를 실행하면, ssh-agent의 출력결과인 소켓과 pid 설정을 자동으로 할 수 있다!

때문에 굳이 eval을 쓰고 싶지 않다면, ssh-agent를 돌려서 나온 소켓과 pid 정보를 아래와 같이 변수로 입력해주면 된다.

> SSH_AUTH_SOCK=/tmp/ssh-XXXXXXmj93qJ/agent.36991
> SSH_AGENT_PID=36992
> ssh-add
Q. 왜 윈도우에서는 서비스 시작 방법을 바꾸나요?
A. Startup Type 이 Manual이 아니면 수동 시작이 안되는 것 같다. 윈도우는 UI가 잘 되어 있으니 수동시작을 하지말고 시작시 자동 시작을 하게 하면 더 편할 것 같다.

마치며

SSH 설정이 단순하지는 않지만, 한번 설정만 해두면 높은 보안수준을 유지하며 쉽게 데이터 공유가 가능하다.

Posted by Knowblesse

 

심리학과 답게 진행한 Negative Punishment. 효과는 없었다.

사용 후 꼭 레버를 올려주세요

 연구실에 사비로 캡슐 커피머신을 비치해두었는데, 공유지의 비극으로 관리의 문제가 심각해진 적이 있었다. 캡슐을 사용하고 레버를 올리지 않는 경우 캡슐이 그대로 기기안에 남는데, 기온이 높은 여름에는 따뜻하고 눅눅한, 곰팡이가 번식하기에 최적인 조건이 만들어진다. 특히 금요일 오후즈음 이런 상황이 발생하면 주말내내 커피머신은 곰팡이 배양기로써 그 역할을 충실히 수행한다. 월요일 아침에 커피를 마시기 위해 레버를 올리는 순간, 떨어지는 캡슐에 핀 곰팡이를 보게되면 결국 기기 분해 세척은 주인인 내 몫이 되었다.

 

 다들 범행(?)을 부인하는 통에 전체 공지도 돌리고 사진처럼 Negative Punishment도 해봤으나 인간은 쉽사리 바뀌지 않는법. 결국 Raspberry Pi를 사용해 커피 머신 사용 중 동영상을 촬영하고, 2분내로 레버를 올린 경우 동영상은 삭제, 레버를 올리지 않은 경우는 동영상을 저장한 뒤, 내게 이메일로 노티를 주는 장치를 만들었다. (코드)

 

GitHub - knowblesse/BlueberryPi

Contribute to knowblesse/BlueberryPi development by creating an account on GitHub.

github.com

 

 원래 계획은 범인의 커피 내리는 영상을 (이게 생각보다 우스꽝스럽다) 반복 재생시켜두려고 했으나, 매우 안타깝게도 높으신 분의 반복범행임이 밝혀져서 깔끔히 포기했다. 10개의 범행장면을 모은 뒤 '두근두근 범인 공개 상영회'를 연 것으로 만족했으며, 그냥 스스로 자주 확인을 하기로 했다.

 

코드 다 돌아가면 알려줘!

 앞서 언급한 프로젝트를 진행하던 중 처음 겪은 불편함은 범인이 걸렸는지 확인하려면 직접 기기에 가서 파일 생성 여부를 체크해야 했다는 점이다. 터치스크린을 연결해두기는 했지만 매일 기기 체크하기가 귀찮아서 범인이 잡히면 이메일을 보내는 스크립트를 만들었는데, 이는 생각보다 단순하다. (코드) 구글, 네이버 등의 이메일 SMTP 서버를 사용해서 내 계정에서 내 이메일로 메일을 보내도록 설정하면 된다. 단점으로는 이메일 자체가 느리고, 내 핸드폰의 이메일 동기화 주기가 그렇게 짧지 않아서 즉각적인 메일 확인이 어렵다는 점이다. 물론 커피 머신 범인을 확인하는게 급한 문제는 아니었기에 만족하면서 사용했다.

 

그러나 다른 컴퓨터에서 코드를 돌리고 실행이 완료되거나, 문제가 발생하면 알려주는 기능은 커피 머신보다는 급한일이다. 더욱이 이메일로 보내는 경우 다른 중요치 않은 이메일에 섞여서 묻힐 수 있다. 때문에 기존 메신저 앱들 중 편리한 API 구성을 제공하는 것이 없나 검색하다가 Telegram의 bot을 활용하는 방법이 군더더기 없이 제일 깔끔해서 이를 소개하려고 한다.

 

Why Telegram?

 사실 텔레그램하면 지난 몇 년간 언론을 달군 사건이 생각나서 설치 자체가 조금 꺼려지기는 했으나 군더더기 없이 깔끔하다는 말에 이를 택했다. 카카오톡이 추가앱 설치도 필요없어서 가장 유력한 후보였으나, 플러스친구, 로그인 등 각종 기능 등과 같이 묶여있어서 그런지 너무 복잡해 반쯤 만들다가 포기했다. 또한 정확히 "코드 다 돌아가면 알려줘!"의 목적에 부합하는 bot 기능을 가지고 있기에 Telegram을 택했다.

 

How to

먼저 핸드폰에 Telegram 앱을 설치하고 계정을 만든다. 연락처 동기화를 자꾸 요구하는데 그닥 메인 메신저로 쓸 생각이 없어서 차단했다.

1. 먼저 BotFather를 사용자에서 찾아서 새로운 bot을 만든다.

계정명을 꼭 확인하자

2. BotFather 와 대화를 시작하고 /newbot 을 보내면 이름과 id를 물어보는 작업을 거쳐 bot을 만들어준다.

이름은 그렇다쳐도 id는 중복이 있으면 안되는 점이 조금 귀찮다.

해당 token은 이미 비활성화 했으므로 괜찮다.

해당 과정을 완료하면 HTTP access token을 발급해준다. 이 정보가 있어야 해당 봇을 통해 본인의 핸드폰으로 메시지를 보낼 수 있다. 함께 알려주는 api 사이트에 들어가면 생각보다 많은 기능을 지원한다는 것을 확인가능하다. 하지만 우리는 단 세 가지 함수만 사용할 예정임으로 굳이 들어가볼 필요는 없다.

 

3. HTTP 호출을 통해서 bot 생성여부를 체크한다.

HTTP GET, POST 등의 method 테스트를 위해서 예전에 Postman이라는 프로그램을 배웠고, 이런 프로젝트마다 디버깅 목적으로 사용하는데 간단한 튜토리얼임으로 기본 웹브라우저를 사용해서 설명하려한다. 별개로 Postman 프로그램이 꽤 유용하니 시간되면 꼭 살펴보시길.

https://www.postman.com/

 

Postman API Platform | Sign Up for Free

Postman is an API platform for building and using APIs. Postman simplifies each step of the API lifecycle and streamlines collaboration so you can create better APIs—faster.

www.postman.com

웹 브라우저(본인은 파이어폭스) 주소창에 아래와 같이 입력을 하면 생성한 bot과 연결이 잘 되는지 확인할 수 있다.

https://api.telegram.org/bot아까발급받은토큰/getMe
# 예시 : https://api.telegram.org/bot5413916344:AAE88PzAed9FCOxygDEeSsEQaggKd8-F81o/getMe

문제가 없다면 아래와 같은 ok 사인을 받을 수 있다.

이름과 id를 얻을 수 있다

4. bot에게 말을 걸자.

핸드폰으로 돌아가서 bot 생성시 두번째로 입력했던 id로 사용자를 검색하면 해당 bot을 찾을 수 있다. 이 bot과 대화를 시작하고, 메시지 하나를 보내두자. bot은 자체적으로 사용자에게 최초로 메시지를 보낼 수 없다. (스팸방지)

 

5. 생성된 대화방의 id를 불러온다.

https://api.telegram.org/bot아까발급받은토큰/getUpdates
# 예시 : https://api.telegram.org/bot5413916344:AAE88PzAed9FCOxygDEeSsEQaggKd8-F81o/getUpdates

다시 브라우저로 돌아가서 위 주소로 간다. 성공적으로 호출이 되면 아래와 같이 대화내용을 불러올 수 있을 것이다.

 

 

getMe를 호출하기 이전에 먼저 대화부터 보내고 나중에 getUpdates를 호출했더니 앞선 대화가 누락되는 문제를 확인했다. (첨부한 이미지에서도 보낸 메시지는 hi there부터 시작하는데  불러온 대화내용은 그뒤에 보냈던 "ㅁㅁ"만 보인다.) 다시 메시지를 보내면 getUpdates에서 잘 보이는 것 같으니 큰 문제는 아닌 것 같다.

 

이렇게 성공적으로 대화를 받으면, id 값을 확인한다. 이 경우 55201이다.

 

6. http request를 통해 메시지를 보내자

모든 과정이 끝났다. 메시지를 보내는 함수는 sendMessage이며 아래와 같이 사용하면 된다.

https://api.telegram.org/bot아까발급받은토큰/sendMessage?chat_id=아이디&text=보낼메시지
# 예시 : https://api.telegram.org/bot5413916344:AAE88PzAed9FCOxygDEeSsEQaggKd8-F81o/sendMessage?chat_id=55201&text=Code Finished

 

python의 경우는 request 패키지를 설치한 후, 아래의 함수를 넣어주면 된다.

import requests
requests.get('https://api.telegram.org/bot5413916344:AAE88PzAed9FCOxygDEeSsEQaggKd8-F81o/sendMessage?chat_id=55201&text=Code Finished')

text 뒤에 코드가 돌아갔다는 정보 외에 시간, 에러가 발생한 경우 그 에러 내용등의 첨부가 가능하다.

 

무엇보다 모든 과정이 핸드폰으로 진행이 가능하며, access token, 본인 telegram 대화방의 id, 단 두 개 정보만 확인되면 메시지를 바로 보낼 수 있다.

 

주의

github가 금광산이라는 말을 들은 적이 있다. 특히 요 얼마전에는 아마존 AWS에 스타트업에서 사용하는 기업용 계정 access token을 실수로 github에 그대로 올렸다가 수 십억에 달하는 사용료가 부과되었다는 뉴스가 나온적이 있다. 모르는 사람은 바보 같다고 생각하겠지만 평상시 버전 관리 프로그램을 쓰는 습관 때문에 나도 충분히 할법한 실수라고 생각한다. Telegram bot token으로는 그러한 짓을 할 수 없겠지만 access token이 어딘가에 공개가 되지 않도록 꼭 주의하자. 본 예시에 사용된 계정은 전부 비활성화 처리를 완료했다.

Posted by Knowblesse
취미/Technology | 2022. 2. 23. 11:11 | /42
 

Notion 입문 1일차

#스타트업 #힙스터 #팀워크 #생산성 스타트업'스러움'이 하나의 문화컨텐츠로 떠오르면서 온갖 제품과 서비스들이 이러한 분위기에 맞추어지고 있다. 마치 Helvetica 폰트가 디자이너들에게 사랑

blog.knowblesse.com

이전에 Notion을 하루 써보고 소감을 간략히 적어봤었는데 이제 사용을 시작한지 한달이 되었다. 지금쯤이면 평이 달라지지 않았을까.

 

장점

1. Markup

이 기능이 생각보다 편리하다. [ / ]버튼 하나로 각종 형식들을 만들 수 있고 evernote에 비하면 적은 노력을 들이고도 읽기 편한 문서를 만들 수 있다는 점이 가장 큰 강점으로 작용한다. 사실 낙서를 포함하는 자유도가 높은 메모는 실제 종이에서 하는 것이 가장 좋다. 디지털화가 필요한 문서의 경우 어느정도 구조화된 아이디어인 경우가 많은데 evernote는 이를 표현하기에는 '동기화가 가능한 메모장'에 지나지 않았다. Notion은 이 '구조화된 아이디어'를 정리하기에 정말 적합한 서비스이다.

구조화된 아이디어 정리에 적합한 Notion

2. Page 구조

이것은 Notion의 장점이라기 보다는 OneNote가 구현해내지 못한 단점에 가까울까. OneNote도 hierarchical 구조를 가지고 있기는 하다만 제한적인 depth를 가지고 있고 최상위 객체인 전자 필기장이 로딩이 느리다는 인상을 자주 받는다. 그에 비해서 Notion은 무한히 Page를 확장할 수 있고, 가볍다는 것이 확실히 느껴진다.

 

Notion과 OneNote의 구조

단점

1. Spell check

아니 Spell check가 제대로 안된다는게 있을 수 있는 일인가. 어째서인지 내 환경에서는 spell check 기능이 작동하지 않고 turn on/off 메뉴도 나타나지 않는다. 그리고 공식 설명에 따르면 setting page에 따로 spell check 기능 관련 메뉴가 없다던데 이 부분은 정말 아쉽다. 아쉬운대로 Grammarly 의 윈도우 add-on을 사용하고 있는데 이 부분은 시급히 개선해야할 것 같다. 

 

2. Keyboard Shortcut

한컴오피스에 익숙한 한국인이라 그런지 키보드 단축키의 부족함을 느낀다. 다른 앱들도 별반 다를 것이 없겠다만 이러한 기능도 구현이 되었으면 하는 아쉬움이 남는다.

 

 

총평

사용 1일차에 느꼈던 감상과 다르지 않다.

'이렇게 참신하고 대단한게 나오다니!' 보다는 '이런게 왜 이제야?'에 가깝다.

사실 노트 작성 프로그램이 '참신하다'라는 평을 받을 가능성이 거의 없음을 감안하면 최고의 평가가 아니었나 싶다. Vim 정도 되면 악랄함을 포함한 참신성을 느낄 수 있겠다만 글쓰고 동기화 시켜주는 에디터에 더 바랄게 뭐가 있으랴. 전반적으로 크게 불편함을 느끼는 부분이 존재하지 않고, 깔끔하게 완성되는 output이 흡족함을 준다. 무엇보다 notion을 쓰면서 evernote가 괘씸해졌다. 초반의 무료정책에 반하게 점점 가격이 올라가고 기능은 변하지 않는 모습에 결국 Notion 사용 한달차에 올 8월까지 결제가 되어있는 유료 플랜을 해지했다. Notion도 이제 시장에 진입한 초기 서비스라 나중에 결제 플랜이 어떻게 바뀔지는 모르겠으나 지금 수준의 가격만 유지된다면 사용료 때문이라도 이쪽으로 넘어올 가치가 충분히 있는 것 같다. 

Posted by Knowblesse

Intro

협업하는 팀의 사정으로 원래 쓰던 tensorflow를 놔두고 pytorch로 갈아타게 되었다. 때문에 포스트의 제목이 이런 식이다.

사실 tf v1 때는 변수 만드는 방법부터 배워야 할 정도로 사용성이 아주 부족했는데 keras의 등장 및 v2에서의 편입 이후로 레토르트 식품 데우듯이 쉽게 ML 모델 개발이 가능해졌다. 이러나 저러나 복잡한 모델을 만들지 않는 이상tensorflow와 pytorch 모두 주인장에게는 cuda를 편하게 쓰게 해주는 도구에 지나지 않고 이쯤 시간이 흘렀으면 사용성이 엇비슷하지 않을까 하는 생각에 1시간 정도 걸리리라 생각했다.

 

당장 튜토리얼부터 마음에 안들었다.

 

pytorch tutorials

동영상 자료는 재끼고, Basic에는 대뜸 Tensor 설명부터, Example에는 간단한 Iris, MNIST는 있지도 않다. 그나마 있는 example은 Image Classification. numpy랑 연동이 잘 된다고 하기엔 받아먹는 data형 제한이 너무 귀찮았고 (Linear function은 float32가 아니면 안받더라) 구조도 와닿지 않는다. keras에 익숙한 사람은 누구나 pytorch로 넘어가며 고구마 100개 순간을 경험하리라 생각해 간단한 Iris classification tutorial을 여기에 올려둔다. 구조적 특성이나 보는 관점은 전부 tensorflow에 익숙한 사람의 의견이라 처음부터 pytorch를 쓴 경우 '이 사람은 왜 이런 소리를 하나?' 생각을 할 수 있겠으나 주인장과 같은 전처를 밟은 사람이라면 이해하리라 기대한다.

 

Example

import torch
from torch import nn as nn
import torch.nn.functional as F
from sklearn import datasets
import numpy as np

# Define Model
class Clf(torch.nn.Module):
    def __init__(self):
        super(Clf, self).__init__()
        self.fc1 = nn.Linear(4,10)
        self.fc2 = nn.Linear(10,3)

    def forward(self,x):
        return self.fc2(F.relu(self.fc1(x)))

# Load Data
iris = datasets.load_iris()

X = torch.tensor(iris.data, dtype=torch.float32)
y = torch.tensor(iris.target)

# Setup Model, Optimizer, Loss function
clf = Clf()
optimizer = torch.optim.Adam(clf.parameters(), lr = 0.01)
loss_fn = nn.CrossEntropyLoss()

# Train
for _ in range(1000):
    y_pred = clf(X)
    loss = loss_fn(y_pred, y)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

print(np.argmax(y_pred.detach().numpy(),1))

dataset은 scikit-learn에서 들고오는 방식을 택했다. train test split, metric 등은 모두 무시하고 150개 데이터 전체를 한번에 학습시키는 단순무식한 코드임을 감안해서 보면 좋겠다.

 

Model

tensorflow 의 keras와 동일하게 모델을 정의하고 모델 구조를 짜서 넣어준다. 하나 차이점은 one-line으로 모델을 만들지는 못하고, nn.Module을 상속받는 class를 만들고, forward 함수를 구현해서 모델을 짜야한다는 점이다. forward 함수를 한줄로 짠 것을 보면 주인장의 귀찮음을 볼 수 있다. 모델은 별 문제없이 넘어갈 수 있다.

 

Data Loading

data를 넣을 때부터 슬슬 짜증이 나기 시작한다. scikit-learn에서 제공하는 iris 데이터는 numpy.ndarray 형태이다. X데이터는 float64, y데이터는 int64의 형태를 가지고 있다. 문제는 모델에서 사용한 nn.Linear가 망할 float32만 받는다는 것이다.

 

# Load Data
iris = datasets.load_iris()

X = torch.tensor(iris.data, dtype=torch.float32)
y = torch.tensor(iris.target)

때문에 tensor로 바꿀 때 dtype 을 명시하거나 처음부터 np.astype 함수를 사용해서 float32로 바꾸어서 들고와야한다.

 

Model setup

loss function을 정하고 optimizer를 정하는 것은 tensorflow와 유사하다. 원하는 것을 가져다가 쓰면 된다.

한가지 유의점은 keras의 경우 model 안에 loss function과 optimizer가 같이 포함되는데 pytorch에서는 이들이 다 따로 논다.

clf = Clf()
optimizer = torch.optim.Adam(clf.parameters(), lr = 0.01)
loss_fn = nn.CrossEntropyLoss()

 

Training

# Train
for _ in range(1000):
    y_pred = clf(X)
    loss = loss_fn(y_pred, y)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

tutorial의 training 파트의 코드를 볼 때는 문제가 없었는데 막상 짜다보니 멘붕이 왔다.

 

잠깐, 나 optimizer에 아무것도 안 넣었는데..?

 

더보기

clf(X) 호출을 통해서 y_pred를 구한다. foward 함수가 아마 호출이 될 것이다.

loss_fn도 이해가 된다. y_pred와 y_true(=y)를 넣어서 loss를 구한다.

optimizer.zero_grad()는 아마도 무언가 initialize를 하는 부분일 것이고,

loss.backward()는 함수명으로 보아 error를 통한 미분값을 구할 것 같은데(backward propagation) weight을 어떻게 알아서 미분값을 구하지?

심지어 loss 값은 optimizer에게 전달되지도 않았는데 혼자서 step() 함수를 호출하고 있다.

 

keras에서는 하나의 모델 안에 loss 함수와 optimizer가 같이 존재한다.

model = keras.Sequential(
    [
        keras.Input(shape=(4,)),
        layers.Dense(200, activation="relu", kernel_initializer=keras.initializers.random_normal(mean=1)),
        layers.Dense(200, activation="relu", kernel_initializer=keras.initializers.random_normal(mean=1)),
        layers.Dense(2,name='output')
    ]
)

model.compile(
    optimizer=keras.optimizers.SGD(learning_rate = 0.001),
    loss=keras.losses.MeanSquaredError(),
    metrics=[keras.metrics.MeanSquaredError()]
    )

심지어 metric 함수까지 포함되어 compile 과정을 거치면 model.fit()을 호출하는 것으로 loss와 optimizer가 작동하고 metric을 알려주는 기능까지 겸하고 있다. keras는 model만 들고다니면 그 안에 모든 함수와 weight들이 묶여서 돌아다니는 구조인데 pytorch는 쉽게 납득하기 어려운 형태를 취하고 있다.

 

pytorch 내부를 뜯어보고 document를 뒤적이고, pytorch를 많이 쓰는 아는 동생에게 물어본 결과 겨우 작동 방식을 이해했다.

왜 이렇게 만든 것인지는 전혀 와닿지는 않지만 말이다.

 

먼저 gradient descent를 위해서는 세 step이 필요하다. 1) error, 혹은 loss를 계산하고, 2)이를 통해 gradient 값을 구한 뒤, 3) 일정한 learning rate을 유지하든 momentum을 더하든 실제로 weight을 수정하는 것이다.

 

keras의 경우 loss function은 loss를 계산하는 부분(1)만 담당하고, gradient를 구하고, 적절한 learnint rate에 맞게 수정하는 부분(2,3)은 optimizer가 담당하고 있다.

 

pytorch는 다르다. loss function에서 1과 2를 담당하고, optimizer가 gradient만 사용해서 실제 parameter를 수정한다.(3)

 

코드 실행 순서를 거꾸로 올라가면 다음과 같다.

1. optimizer가 주어진 gradient 값을 사용해서 parameter를 수정한다.

optimizer.step()

optimizer가 처음 만들어졌을 때 clf.parameters()를 넣었는데, 이는 모델의 parameter를 참조하는 값이다. 때문에 optimizer는 clf 의 parameter를 수정할 수 있다. 수정에 사용하는 gradient 값은 parameter 안에 들어있다.

params = [i for i in clf.parameters()]
print(type(params[0]))
print(params[0].grad)

위 코드를 실행하면 torch.nn.parameter.Parameter 형태로 clf 내부가 표현되는 것을 알 수 있으며, 이는 Tensor의 일종으로, backward 함수를 실행하면 grad 값이 생긴다. loss.backward()를 실행하지 않으면 params[0].grad는 None으로 설정되어 있을 것이다.

 

2. loss 값을 사용해서 gradient를 구한다.

loss.backward()

그렇다면 어떻게 loss를 통해 구한 gradient가 clf의 weight까지 가는가?

loss는 Tensor type이다. 중요한 점은 이 Tensor가 단순히 data만을 담고 있지 않고, 어느 과정을 통해서 이 값들이 구해졌는지에 대한 정보를 가지고 있다는 것이다. 설마설마 하긴 했는데 이 변수 안에 어떻게 보면 이 값이 나오게 된 역사(?)가 담겨있다. (이러면 memory issue는 괜찮나...?)

tutorial에서도 제대로 알려주지 않는데 이를 확인하는 방법은 다음과 같다.

(Deep Learning with PyTorch: A 60 Minute Blitz 글 안에는 있다!)

loss.grad_fn.next_functions

Tensor object인 loss에는 grad_fn이라는 필드가 있는데, 이 function 안의 next_functions field에는 이 뒤에 호출해야할 function들의 참조가 달려있다. 물론 이 순서는 해당 Tensor가 만들어진 순서의 역순이다. 이렇게 next_functions 를 따라가면 아래의 순서로 loss가 구해졌음을 알 수 있다.

 

grad_fn 호출 stack

때문에  loss.backward()만 호출하면 각 parameter Tensor에 grad 값이 전파된다.

각 grad_fn에 대한 자세한 설명은 공식 document에도 나와있지 않지만, AccumulateGrad가 있는 부분마다 실제 error에 의한 grad 값들이 존재하는 것으로 보인다 (각 linear layer마다 weight에 해당하는 Tensor와, bias에 해당하는 Tensor가 있을 것이다.)

3. clf를 구성하는 Tensor들의 grad 값을 초기화 한다.

optimizer.zero_grad()

optimizer와 loss function이 따로 놀기 때문에 현재 parameter가 가지고 있는 gradient를 초기화 시켜주는 함수가 존재한다. 때문에 이 함수를 먼저 호출하고, 이 뒤에 loss를 backpropagation 시키는 방식으로 코드를 작성해야한다.

 

이 이후는 keras를 쓰던 유저도 쉽게 이해하리라 생각한다.

 

마치며

pytorch를 사용한 ML 논문이 이미 tensorflow를 사용한 논문의 수를 넘었다고 한다. 익숙하지 않은 구조 때문에 하루를 날리며 대체 왜 pytorch를 쓰는가에 대한 글을 찾다가 pytorch의 장점으로 동적인 computation graph를 언급하는 글을 보았다. 어쩌면 keras처럼 Model 종속적인 형태가 아니라 연산 값에 연산과정이 들어있는 구조 때문에 동적 computation graph가 가능한 것인가 하는 생각이 든다. 하나의 작은 산은 넘은 것 같다.

 

 

Posted by Knowblesse
취미/Technology | 2022. 1. 11. 11:58 | /40

#스타트업 #힙스터 #팀워크 #생산성

스타트업'스러움'이 하나의 문화컨텐츠로 떠오르면서 온갖 제품과 서비스들이 이러한 분위기에 맞추어지고 있다. 마치 Helvetica 폰트가 디자이너들에게 사랑받기 시작하면서 세계를 장악하는 현상을 보는 것만 같다. 실제 생산성을 높이기 위함이든, 본인의 '힙함'을 보여주기 위함이든 여러 제품을 구입하고, 서비스를 구독하기 시작하면 월 구독료로 나가는 돈은 적지 않을 것이다. 또한 개중에는 이러한 문화적 흐름에 편승해서 그럴듯하게 포장한 예쁜 쓰레기를 제공해주는 곳도 많아서 사용자로 하여금 더 많은 판단을 요구한다. 자칫 생각없이 모든 것을 수용하다가는 생산성 앱으로 도배된 생산적이지 않은 본인의 기기를 보게 될 것이다.

 

변화에 슬슬 더디게 반응하기 시작하는듯한 기분을 떨쳐내기 위해 오래전부터 이야기를 듣고 추천을 받아온 Notion을 쓰기 시작했다. 당장에 연구실 지식베이스와 소통을 위한 목적으로 어렵게 도입했던 Slack부터 스타트업에 잠시 참여했을 때 접했던 트렐로, 2011년 1월부터 사용중인 에버노트와 iOS 한글 지원문제로 갈아탔던 OneNote까지 온갖 유사 프로그램들 접했는데 각자 지향하는 바가 분명하게 다른 것 같다. 다른 앱과 함께 하루 Notion을 써본 소감을 간략히 적는다.

 

* 트렐로 : 협업용 TODO 리스트

* Keep : 동기화 되는 포스트잇 게시판

* Evernote : 동기화 되는 간단한 메모장

* OneNote : 화이트보드

* Slack : 조금은 DB의 형태를 갖춘 카카오톡

* Todoist / TickTick : 혼자쓰는 TODO 리스트

Notion : 잘 정리된 Blog 글 같은 Evernote

현재 각자 사용목적성이 뚜렷하고 열심히 쓰고 있는 두 앱이 OneNote와 Evernote여서 그런지 자꾸 이 둘과 비교를 하게 된다. Evernote의 경우 기사를 대충 스크랩해서 처박아두거나, 짧은 메모, 일기 등의 줄글 위주의 게시물, 혼자 보는 용도의 설명서를 담아둔다. '동기화가 되는 간단한 메모장'이 가장 확실한 설명인 것 같다. 노트를 예쁘고 깔끔하게 만들기 위해서는 추가적으로 서식을 변경하거나 하는 등의 노력이 많이 들어간다. 그런데 그럴 일은 없다. 대충 넣어두고 나중에 필요하면 찾으면 되고, 잘 정리된 문서가 필요하면 워드로 작성하면 된다. 첫 번째 책상서랍 같은 존재다. 때문인지 유료플랜을 결제할 때는 매번 망설인다. 해주는 기능도 얼마 없으면서 이 가격을 받아야 하나 싶다.

 

OneNote의 경우는 아이디어 상자이다. 화면 아무 곳이나 클릭하면 바로 글이 써지고 Indent 조절이 쉽게 되고 이리저리 컨텐츠 박스를 끌고 다닐 수 있다는 점이 매력이다. 화이트보드 같은 매력이 있다. 하지만 이러한 점은 화면이 작은 모바일 기기로 볼 때 확실히 단점으로 작용한다. 화면안을 이리저리 돌아다니면서 글을 읽는 것은 정말 불편하다. 그래도 Evernote에 비해서는 깔끔한 문서정리가 가능하기 때문에 자주 꺼내보아야 하는 자료의 경우는 OneNote에 정리해서 넣어둔다.

 

짧은 메모, 텍스트 위주의 정보, '대충 넣어두어도 나중에 필요하면 뒤지겠지...' 하는 수준의 정보는 Evernote.
빠른 아이디어 작성, 위계관계가 확실한 정보, 조금은 정리해서 넣어두어야 하는 정보는 OneNote.

 

 

Notion은 OneNote와 Evernote 어딘가에 어중간하게 있다. 많은 사람들이 마크업 기능을 장점 중 하나로 꼽는데 확실히 여타 다른 앱들에 비해서 월등히 편리하다. 그 외에 sorting이 지원되는 깔끔한 표 삽입 기능과 댓글기능, 페이지 간 링크 등은 앞서 언급한 많은 앱들의 필수 기능들을 하나로 통합해서 왜 이런 앱이 이제야 나왔는지 싶다. 하지만 '이렇게 참신하고 대단한게 나오다니!' 보다는 '이런게 왜 이제야?'에 가깝다. 다양한 템플릿을 지원한다는 점에서는 OneNote와 가깝고 심플하다는 것에 Evernote와 비슷한 느낌을 준다. 하지만 전반적인 감상은 OneNote보다는 Evernote에 가깝다고 할까. OneNote의 '화이트보드'스러움이 있었다면 Evernote와 OneNote를 통합해서 사용할 수 있었겠으나 아이디어를 이리저리 끌고다니는 기능은 없는듯 하다. 하긴 앞서 언급한 모바일환경에서의 불편함을 생각하면 좋지 않은 방법일 수도 있겠다. Notion에서는 확실히 '협업용'의 분위기가 물씬 풍긴다. 내 난잡한 Evernote 글을 동료에게 준다면 한소리 들을 것이 분명하고, 마크업이 제대로 되지 않고 외부자료를 넣기 힘든 OneNote는 장기적인 데이터 유지 관점에서는 분명 좋지 않은 선택이다. Notion은 원활한 정보전달에 필요한 만큼의 미적 요소를 가미해서 글 작성이 가능하다. 왜 이렇게 붐을 일으켰는지 알법하다.

 

하지만 Notion이 얼마나 내가 쓰는 기존 프로그램들을 대체할 수 있을지는 의문이 든다. '협업'의 필요성이 크지 않기에 기존 두 프로그램들로도 충분한 목적을 달성했다. 또한 마크업 기능도 미를 위해 할애하는 시간을 해치지 않을 만큼 쉽게 되어있기는 하지만 미가 필요할 수준의 내용이면 아에 문서로 작성을 하고, 그런 것이 아니라면 짤막한 메모로도 충분하다. 그나마 이번에 진행하는 프로젝트의 일원이 Notion을 쓰는 것 같던데 이 프로젝트를 위해서 사용하는 것이라면 Notion을 충분히 쓰겠으나 OneNote나 Evernote를 내려놓기에는 조금 힘들 것 같다. 다만 메모장 프로그램에 매달 돈을 지불해야하는 점이 괘씸하기에 Evernote는 Notion로 대체를 시도하려고 한다.

Posted by Knowblesse

3rd-generation Antihistamine

항히스타민제만큼 구하기 쉬우면서 부작용을 신경써야하는 약은 없을 것이다. 비염이나 알레르기 증상을 완화하려고 먹었다가 쏟아지는 졸음을 겪거나 나른해진 경험은 누구나 한번쯤 해봤을 것 같다. 아스피린의 최대의 부작용인 속쓰림을 개선해서 다양한 진통제가 나왔듯이 항히스타민제도 '세대'라는 말이 붙을 만큼 다양한 약들이 나오고 있다. 그렇게까지 알레르기 반응이 심하게 나와 본적이 없고, 가능하면 알러젠을 차단하거나 보호장구를 사용해서 대처를 하기에 항히스타민제에 관심이 없었으나 우연한 사건으로 관련 정보를 찾아보게 되었다.

 

나는 몸 상태에 따라 적절한 약을 먹을 수 있도록 단일제제의 약을 구비해두는 것을 좋아하는데, 3세대 항히스타민제 중 일반의약품으로 분류되어 판매가 되는 약은 펙소페나딘염산염인 '알레그라'가 거의 유일했다. 일부 커뮤니티에서는 카피약도 나와있다고 하고 실제 한미약품에서 펙소나딘정으로 제네릭 약품이 나와있기는 하다만 구하기 쉽지 않아 보였다.

 

왜 120mg만 일반일까? 약학계도 상품의 유통구조와 제약회사의 이익관계, 안정성 등이 복잡하게 엮긴 것 같다.

 

이전에 우루사를 구입하려고 했는데 200mg는 전문, 100mg는 일반 이라는 설명을 듣고 의야해 했는데 알레그라는 더욱 웃겼다. 30mg과 180mg는 전문, 120mg은 일반이라고 한다. 병원을 방문할 시간이 없었기에 일단 근처 약국 6개를 돌며 120mg 알레그라를 사기 위해 돌아다녔는데 전부 전문의약품 형태만 있다며 구입을 하지 못했다.

 

비염에 고통받는 사람들이 많은지 관련 커뮤니티에서도 약을 구하기 힘들다는 반응을 털어놓았는데, 한독약품에 문의를 한 결과 120mg은 일반의약품이 맞지만 판매형태가 조제용이라 처방을 받고 사야한다는 답변을 받았다. 조제용 포장을 소분해서 판매하는 행위는 불법이라는 점(처방 제외)은 아는데 조제용 포장을 그대로 파는 것도 불법인지는 모르겠다. 몇몇 사람들이 120mg 일반의약품을 30정이 든 조제용 포장으로 샀다는 글을 올렸는데 조제용 포장으로 판매하는지 여부는 약사 재량인가보다. 아니면 얼렁뚱땅 판매를 하셨거나.

 

한독약품 홈페이지에 관련 문의글은 많았으나 전부 비밀글로 올라와 있었는데 본인처럼 약을 구하시는 분들이 헛걸음하지 않도록 아래 답변 내용을 적어둔다.

 

한독 의약품은 (주)쥴릭 도매상을 통해 전국 유통되고 있으므로
쥴릭으로(전화: 080-2006-080) 연락하시어 거주지역에서 구입 가능한 약국을 문의해 주시기 바랍니다.

참고로, 현재 일반의약품으로 분류되는 알레그라정120mg은 처방조제용 포장(30정,100정)으로만
유통중인 관계로 일반 판매가 어려운 상황입니다.
번거로우시겠지만, 가까운 병원을 통해 처방 후 조제 받으시길 안내 드립니다.

22년 01월 10일 기준 답변

 

 

 

 

 

[1] Olasińska-Wiśniewska, Anna & Olasiński, Jerzy & Md, Phd. (2014). Cardiovascular safety of antihistamines. Postepy Dermatologii I Alergologii. 31. 182. 10.5114/pdia.2014.43191. 

Posted by Knowblesse

1. SimpleBlobDetector

parameter로 넣어준 조건에 따라 blob을 찾아줌.

알고리즘

  1. source image에 threshold를 적용.
    - parameter에 넣은 minThreshold 값(포함)과 maxThreshold 값(불포함) 사이를 thresholdStep으로 나누어 각각의 threshold를 사용해서 binary image로 바꾼다. 처음부터 binary를 넣어주려는 경우 기본 파라미터에서는 minThreshold=50, maxThreshold=220, thresholdStep=10 으로 설정이 되어있기에 이를 만져주어야 한다.
    binary image를 0과 255로 구성되도록 설정하고, minThreshold=253, maxThreshold=255, thresholdStep=1로 설정하면 최소한의 threshold 연산만 사용할 수 있다.
  2. 각각의 binary image에 대해서 findContours 함수를 적용해서 center를 계산.
  3. 각각의 binary image에서 찾은 contour들을 하나로 묶어줌.
    이때 minDistBetweenBlobs parameter를 사용한다.
  4. 찾아낸 Contour를 사용해서 blob의 최종 center와 radius를 확정. 이를 keypoint로 return

예시코드

import cv2 as cv

parameter = cv.SimpleBlobDetector_Params()
parameter.filterByArea = True
parameter.filterByConvexity = True
parameter.filterByCircularity = True
parameter.filterByInertia = False
parameter.filterByColor = False
parameter.minArea = 500  # this value defines the minimum size of the blob
parameter.maxArea = 10000  # this value defines the maximum size of the blob
parameter.minDistBetweenBlobs = 1 # not used in binary image
parameter.minConvexity = 0.3
parameter.minCircularity = 0.3

detector = cv.SimpleBlobDetector_create(parameter)
detected_blob = detector.detect(denoised_mask)
print(len(detected_blob))

2. keypoint size

keypoint는 pt 값과 size 값을 가지고 있는데 (다른 값들도 있지만 이 연산에서는 무의미한 것으로 추정) pt는 blob center의 x, y 좌표를 의미하지만 도무지 size에 대한 이야기가 없다.

내가 원하는 정보는 찾아낸 blob의 크기 (이를 둘러쌀 수 있는 최소한의 원의 반지름 이라던가 실제 blob의 픽셀 수라던가...)인데 나오는 size 값은 blob의 모양에 따라서 제각각이다.

blob detection 을 한뒤 pt를 기준으로 size/2 만큼의 radius를 가지는 원을 그린 결과물

4번째 스텝인 "찾아낸 Contour를 사용해서 blob의 최종 center와 radius를 확정." 에서 대체 무슨일이 일어나는 것이길래 이런 값이 출력되는지 모르겠다. 원 같은 경우는 그나마 비슷하긴 하지만 실제 위의 그림에서 볼 수 있듯이 지름 100px짜리 원의 blob 크기는 99.0151로 나온다. 여러 contour 들을 합치는 알고리즘을 들여다봐야 알겠지만 SimpleBlobDetector가 return하는 keypoint의 값만 사용해서 실제 blob의 크기를 알아낼 방법은 없는 것으로 보인다.

3. parameter의 minArea / maxArea

한 가지 주목할 점은 parameter로 사용하는 minArea / maxArea의 경우는 실제 blob으로 잡힌 object의 크기를 비교한다는 것이다. 한 변의 길이가 100px인 정사각형에 대한 keypoint의 size 값은 111.8034이지만 filterByArea 옵션을 켜고 minArea를 10000(100 * 100)으로 잡으면 정사각형을 찾지만, 10001을 사용하는 경우 정사각형을 찾지 못한다. circle의 경우는 사용한 radius를 사용해 수학적으로 계산한 넓이와 10 이하로 차이가 나는 것으로 보아 실제 pixel 수를 기준으로 filter 하는 것으로 보인다.

 

4. findContours 함수를 사용한 정확한 blob detection

때문에 내부에서 찾아낸 blob의 정확한 사이즈를 뽑아낼 수 있을 것이라고 기대했으나 실패했다.

사실 실제 4단계 step 중에서 사용하는 부분이 두 번째 step 뿐이기에 이쯤되면 그냥 findContous 함수를 써서 raw level에서 처리하는 것이 더 나을 것으로 보인다.

 

cnt = cv.findContours(binary_image, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)[0]

# Some basic features of the contour object
(x,y), radius = cv.minEnclosingCircle(k)
rect = cv.minAreaRect(k) # ((center x, center y), (width, height), rotation)
size = cv.contourArea(k)

뽑아내야 하는 blob의 수를 알고 있다면 area, 둘러싸는 직사각형의 가로 세로 사이즈 등등의 값을 얻은 뒤 단계적으로 filter를 적용하는 방법도 사용할 수 있다.

 

 

Posted by Knowblesse

Google아 TF2.6이 나온지 언젠데 아직도 install 페이지에 TF2.4 설치방법이 있더라.

일 안하니?

 

 

망할 파이썬을 쓰다보면 인생의 교훈을 한가지 얻을 수 있다.

새로운 것 나왔다고 바로 쓰지말고 나온지 좀 된 것을 쓰자.

 

그런데 기왕 셋업하는 김에 좀 최신 것을 쓰려고 했다가 시간을 엄청 날렸다. TensorFlow 자체 documentation 은 금방금방 업데이트를 해주는데 설치방법은 왜 여전히 그대로인지... 내가 설치할 당시에는 2.5 기준으로 documentation이 나와있었는데 2.5는 CUDA 11.2 버전이 필요하고, 홈페이지에 나와있는 설치방법은 11.0을 기준으로 나와있어서 그대로 설치했다가는 'tf 2.5 에서는 cuda 11.2 버전이 필요합니다' 에러를 낸다.

 

문제는 이 cuda 11.2가 그당시 완성이 덜 되어있어서 몇몇 라이브러리가 없었는데 이 때문에 수동으로 라이브러리를 다운받아 이름을 변경하여 넣어야 돌아갔다. 세상에.

 

화난 개발자들이 올린 글들의 도움을 얻어 어찌저찌 설치를 했는데 왜 이 글을 쓰냐... apt-get autoclean과 apt-get autoremove 를 썼다가 수동으로 다운받아둔 라이브러리가 다 날아갔다. 이왕 다시 셋업 하는거 기록으로 남겨두자는 마음으로 글을 작성한다.

 

docker를 쓰는 쉬운 방법도 있다는 것 같지만 docker에 대한 필요성을 딱히 못 느끼기에 사용하지 않았다. 그래도 가상환경을 사용하지 않고 새로운 패키지를 까는 것을 죄악이니 꼭 conda는 사용하길 바란다.

 

0. tensorflow 2.6 설치

혹시 설치하지 않은 경우...

pip install tensorflow==2.6

1. NVIDIA GPU driver 설치

자기 그래픽 카드에 맞게 최신으로 설치.

https://www.nvidia.com/download/index.aspx?lang=en-us 

 

Download Drivers | NVIDIA

 

www.nvidia.com

 

2. CUDA Toolkit 11.4 Update 1 (11.4.1) 설치

https://developer.nvidia.com/cuda-toolkit-archive

 

CUDA Toolkit Archive

Previous releases of the CUDA Toolkit, GPU Computing SDK, documentation and developer drivers can be found using the links below. Please select the release you want from the list below, and be sure to check www.nvidia.com/drivers for more recent production

developer.nvidia.com

콘솔에서 커맨드 실행전에 sudo 명령어를 사용하는 다른 명령을 사용해두는 것이 좋다.

안그러면 첫줄만 돌아가고 root password를 묻는 통에 둘째 라인부터 실행이 되지 않는다.

 

Ubuntu 18.04

wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-ubuntu1804.pin
sudo mv cuda-ubuntu1804.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/11.4.1/local_installers/cuda-repo-ubuntu1804-11-4-local_11.4.1-470.57.02-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu1804-11-4-local_11.4.1-470.57.02-1_amd64.deb
sudo apt-key add /var/cuda-repo-ubuntu1804-11-4-local/7fa2af80.pub
sudo apt-get update
sudo apt-get -y install cuda

Ubuntu 20.04

wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/11.4.1/local_installers/cuda-repo-ubuntu2004-11-4-local_11.4.1-470.57.02-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu2004-11-4-local_11.4.1-470.57.02-1_amd64.deb
sudo apt-key add /var/cuda-repo-ubuntu2004-11-4-local/7fa2af80.pub
sudo apt-get update
sudo apt-get -y install cuda

3-1. cuDNN v8.2.2 for CUDA 11.4 다운

아래 사이트에서 cuDNN Library for Linux (x86_64)를 다운받으면 된다.

 

원래 Debian Package (dpkg)를 사용해서 설치하는 것이 제일 간단한데 이게 왜인지 실행이 제대로 되지 않는다.

 

혹시 시도를 해보려면 아래 링크에서 cuDNN Runtime Library for Ubuntu18.04 x86_64 (Deb)를 받으면 된다.

 

Ubuntu 20.04인 경우 해당 버전에 맞는 것을 받자. Developer 버전을 받지 않아도 된다.

 

아래 설치는 dpkg가 아닌 library를 통째로 받은 것을 전제로 진행할 것이다.

 

https://developer.nvidia.com/rdp/cudnn-archive

 

cuDNN Archive

NVIDIA cuDNN is a GPU-accelerated library of primitives for deep neural networks.

developer.nvidia.com

3-2. cuDNN v8.2.2 for CUDA 11.4 설치

https://docs.nvidia.com/deeplearning/cudnn/install-guide/index.html

 

Installation Guide :: NVIDIA Deep Learning cuDNN Documentation

The NVIDIA® CUDA® Deep Neural Network library™ (cuDNN) is a GPU-accelerated library of primitives for deep neural networks. cuDNN provides highly tuned implementations for standard routines such as forward and backward convolution, pooling, normalizati

docs.nvidia.com

위 링크에서 자세한 설치 방법을 알 수 있으나, 아래에 간략히 요약하겠다.

 

받은 파일은  .tgz 확장자로 tar.gz 파일이다. 내장 파일탐색기 nautilus로도 압축을 풀 수 있고, 터미널에서 tar -xzvf 다운받은파일명 으로 풀 수 있다.

 

내장 파일탐색기로 푸는 것이 쉬우니 그 방법을 추천한다.

 

tar 명령어로 푸는 방법은 아래와 같다.

더보기
tar -xzvf cudnn-11.4-linux-x64-v8.2.2.26.tgz

참고로, xzvf는 extract, gzip, verbosely list files processed, file 의 약자다.

 

다음으로 라이브러리를 CUDA Toolkit이 찾을 수 있는 장소에 넣어주어야 한다.

 

일반적으로 이는 /usr/local/cuda 폴더이며, 앞서 CUDA Toolkit을 설치하면 자동으로 생성되었을 것이다.

 

일단 터미널을 열고 압축을 풀어서 나온 cuda 폴더가 보이는 위치까지 간 뒤 아래 명령을 실행한다. 두 번째 명령이 파일이 많아서 시간이 조금 걸린다.

 

sudo cp cuda/include/cudnn*.h /usr/local/cuda/include
sudo cp -P cuda/lib64/libcudnn* /usr/local/cuda/lib64 
sudo chmod a+r /usr/local/cuda/include/cudnn*.h /usr/local/cuda/lib64/libcudnn*

4. 설치 종합

tensorflow v2.6 : tensorflow

CUDA Toolkit v 11.4.1 : CUDA 그 자체이다.

cuDNN v8.2.2 : library로 tensorflow 사용시 dynamic library 문제가 발생했다고 하면 이 친구가 문제인 것.

 

꼭 CUDA Toolkit은 cuDNN보다 먼저 설치해야 한다.

5. 확인

python에서 아래 코드로 GPU 사용을 확인해보자.

 

import tensorflow as tf

if tf.config.list_physical_devices('GPU'):
    print('TF is using GPU')
else:
    print('TF is NOT using GPU')

 

실행시 Information, Warning, Error로 로그가 나오는데, E만 없어도 대체로 정상작동하더라.

 

꼭 GPU로 돌아가는지 확인을 해보고 실행을 하자!

 

 

 

감사는 댓글이나 커피로 표현해주시면 감사히 받겠습니다.

paypal.me/knowblesse

 

 

 

Posted by Knowblesse

어느 날부터 웹캠이 흑백으로 나온다면 아래의 방법을 시도해보자.

 

Microsoft 공식 홈페이지를 포함해서 대부분의 Solution들이 드라이버 업데이트/재설치, 윈도우 업데이트/재설치를 이야기 하는데 정말 간단한 문제일 수 있다.

 

1. 카메라 고급 옵션 접근

먼저 Skype를 실행한다. 언젠가부터 Microsoft가 Microsoft Teams랑 Skype를 쥐도 새도 모르게 깔아놓던데 아주 마음에 안든다. Skype가 깔려있지 않다면 찾아서 설치하자. 카메라 고급 옵션에 접근하는 다른 방법도 있을텐데 (video stream을 사용하는 다른 소프트웨어도 가능하지 않을까? OBS Studio 라던가) 이게 가장 간단하다.

 

추가 메뉴에서 설정.
오디오 및 비디오에서 웹캠 설정

 

2. 카메라 고급 옵션 기본값으로 초기화.

전부 기본값으로 변경

 

Skype 뿐만 아니라 Zoom, Kakao Talk 등 웹캠 스트리밍이 가능한 소프트웨어에서 사용자가 직접 옵션 변경을 했거나, 낮은 조도 때문에 임의로 채도 혹은 낮은 빛 보상 옵션이 변경이 되면 흑백으로 나타나는 현상이 발생한다.

 

문제는 대다수의 소프트웨어들이 이러한 옵션을 display 해주지 않으며, 심지어 기준이 되어 Microsoft 기본 카메라 앱에 이 옵션에 대한 접근이 불가능 하다. (이게 주 원인인것 같다. 마소 놈들....) 심지어 한번 설정한 옵션은 카메라 모듈 내부에 따로 저장이 되는지 OS 재설치를 진행해도 흑백으로 나오는 현상이 발생한다.

 

삼성 노트북 A/S 후기

기술 발전과 정체로 노트북 브랜드간 격차가 사라져서 큰 차이가 없으면 국산 브랜드를 애용하자는 마음으로 현 노트북으로 삼성을 골랐다. 16Gb RAM과 외장 그래픽이 같이 딸린 200만원이 넘는 노트북 9을 구입했었는데 이게 몇 시간 Unity 기반 2D 게임을 돌렸다가 블루스크린과 함께 GPU가 저세상으로 갔다. 적어도 CPU에는 온도가 너무 높아지면 성능을 임의로 저하시켜서 칩을 보호하는 기능이 있는 것으로 아는데 이 제품의 GPU에는 이것을 제대로 구현하지 않은 것인가. 강제로 오버클럭을 시키거나 Fan이 고장났거나, 온도관련 안전장치를 해제 했으면 모르겠는데 정상 사용중 GPU가 고온으로 손상되다니 이것은 명백한 설계 미스다. 심지어 사이즈를 줄이기 위해서 CPU, RAM, GPU를 한 보드 위에 몰아두어서 GPU가 나갔다고 GPU만 고칠 수 없다더라. 수리비 90만원.

 

그나마 삼성에서 리퍼 부품 교환 서비스를 운영하고 있어서 절반 가격인 40만원 정도에 수리를 받았으나 제품 블랙리스트를 고려할만큼 많은 실망을 안겨주었다. A/S 기사님도 되도록 새 제품을 사는 것을 권장하셨으나 구매가를 생각하면 나쁘지 않은 선택이었다고 생각한다.

 

센터에 간 김에 위의 웹캠이 흑백으로 나오는 문제를 같이 문의했었다. 나는 소프트웨어 문제라고 계속 주장을 했으나 A/S 기사님이 자체 카메라 검사 유틸리티를 실행해서 확인을 했는데 진단은 카메라 모듈 하드웨어 손상. 카메라가 아예 안나왔으면 안나왔지 이런 현상을 본적이 없다고 하시더라. 카메라 모듈 역시 디스플레이와 일체형이라 수리비 20만원. 얜 포기를 하고 GPU 만 수리를 받았는데 오늘에야 문제를 자체적으로 해결했다.

 

노트북 제품들간 차이가 줄어든 현재, 사용자 경험의 사소한 차이가 구매로 이어진다. 수리가 어려운 구조의 제품 디자인과 GPU 보조 장치/프로그램의 설계 미스, A/S 프로토콜이 짚어내지 못한 너무나도 단순한 문제 때문에 앞으로 다른 브랜드 노트북에 눈이 가게 될 것 같다.

Posted by Knowblesse