로지스틱 회귀 비용함수로부터 Cross-entropy 도출하기


원래 지난 번 포스팅에서 cross-entropy까지 언급하기로 했었는데 정신없이 정리하다보니 이 부분이 누락되어
로지스틱회귀의 비용함수만 언급하고 지나가버렸다. 그래서 이번 포스팅에서는 간단하게 로지스틱회귀 비용함수 식이cross-entropy 식으로 변형되는 과정을 간단하게 알아보도록 하겠다.


로지스틱회귀 비용함수 복습


수차례 반복되었지만 로지스틱회귀 비용함수 식은 아래와 같다.


김성훈 교수님의 강좌에도 나오지만 이 식은 결국 다항로지스틱회귀의 비용함수인 cross-entropy와 동일한 식이다.
뭐 수학이나 공학을 전공한 사람들은 금방 알 수 있겠지만 우리 문돌이들은 두드러기가 생길 문제이므로 간단하게
풀어보도록 하겠다.


cross-entropy 도출


우선 로지스틱회귀 비용함수를 조금 변형해보자.


맞는가? 그렇다면 이번에는 우리 문돌이를 종종 미궁에 빠뜨리는 치환이다. y를 p1, H(x)를 q1, y-1을 p2, 1-H(x)를 q2로
치환해보자 그러면 식은 다음과 같이 표현할 수 있다.


이 식은 다시 다음과 같이 변형이 가능하고…


위 식을 일반화 하면 최종적으로 다음과 같은 식이 나오는데 이 식이 바로 Cross-entropy 식이다.


아마도 제대로 된 cross-entropy의 개념은 이보다 더 깊은 의미가 있고 식의 도출도 더 복잡하겠지만 문돌이가
이해하기에는 이정도가 딱인 듯싶다.


정리


이번 글은 개인적으로 수학적인 내용에 대한 최종 정리의 의미를 가지고 있다. 더이상 수학적인 공부를 하지 않겠다는
의미가 아니라 수학적인 공부는 계속 하되 가급적이면 필요 이상의 상세한 내용은 피하겠다는 의미이다. 지난 몇주간
회사 일도 바쁘고 집에도 좀 복잡한 문제가 있어 공부를 제대로 못했다. 이제 본격적으로 텐서플로우에 대한 내용에
집중해서 공부를 좀 해보자~!







저작자 표시
신고

Docker 이미지로 설치한 Jupyter에 커널 추가하기


텐서플로우를 시작하면서 나의 Mac mini에 Docker 이미지로 텐서플로우를 설치하였고 이 이미지를 실행하면
텐서플로우 예제 노트북이 포함된 jupyter가 실행된다. 설치 이후 별다른 문제가 없이 잘 사용을 하고 있었는데
이번에 로지스틱회귀 비용함수와 관련된 내용을 공부하다가 이 비용함수의 그래프를 그려주는 python
소스(jupyter notebook)가 있길래 가져다 사용을 해보려 했더니 이 소스가 python 3.X 기반이었다. 그런데
Docker 이미지에 있는 jupyter는 python2 커널만 있어서 python3 커널을 추가하는데 조금 삽질을 했다.


삽질 과정은 생략하고 간단하게 Docker 이미지로 텐서플로우를 설치한 경우 jupyter에 커널을 추가하는 방법을
정리한다.


jupyter Document에서 안내하는 커널 추가 방법


일단 jupyter 공식 홈페이지에 가면 다음과 같은 코드로 커널을 추가하도록 안내하고 있다.

python2 -m pip install ipykernel
python2 -m ipykernel install —user 


python3 커널을 추가할 경우에는 숫자 2만 3으로 바꾸면 된다. 다음과 같이…

python3 -m pip install ipykernel
python3 -m ipykernel install —user


나는 python3 커널을 추가해야 하므로 당연히 두 번째 코드를 사용하였다. 그러나…
첫 번째 라인을 입력하고 엔터를 치니 다음과 같은 오류가 발생을 하였다.

/usr/bin/python3: No module named pip


확인을 해보니 /usr/local/lib/python3.4/dist-packages/ 아래에 아무런 패키지들이 없었다. 그래서 우선
python3용 pip를 먼저 설치했다.

apt-get update
apt-get -y install python3-pip


그런데 이렇게 pip를 설치한 후 python3 커널을 설치하는 과정에서 이번에는 잘 진행되는 듯 싶다가 마지막에
다음과 같은 오류가 발생을 하였다.

ImportError: No module named 'packaging'


pip 패치가 필요하다고 하여 패치를 진행하였다. 이 과정에서 wget 모듈이 필요하여 wget 설치를 먼저 하였다.

#wget 설치
apt-get install wget

#pip 패치
$ wget https://bootstrap.pypa.io/get-pip.py
$ sudo python2 get-pip.py
$ sudo pip2 install -U setuptools


여기까지 하고 나니 python3 커널이 정상 설치되었다. 아래는 pyhon3 커널 설치 후 New 메뉴의 모습이다.


요약

  1. python3용 pip 설치
  2. wget 설치
  3. 설치한 pip 패치
  4. 커널 추가

후기


python3 커널을 설치한 후 서두에 언급한 비용함수 그래프를 그려주는 노트북을 실행하니 python2 커널에서 
발생하던 오류는 사라졌다. 그런데 마지막에 그래프를 그리는 과정에서 자꾸 커널이 죽어버리는 문제가 발생을
하였다. python과 jupyter를 잘 모르니 원인이 무엇인지를 모르겠다. 정상적으로 실행이 된다면 다음과 같은
그래프가 그려져야 하는 것 같다.



그리고 혹시나 해서 노트북 소스와 출처 링크를 첨부한다.

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

cost_function.ipynb










저작자 표시
신고


Logistic 회귀의 cost function


지금까지 해온 것 처럼 로지스틱회귀의 비용함수 역시 어설프나마 문돌이식 수학으로 이해해 보려고 했다. 그래서 
일주일이 넘는 시간을 투자했으나 이번 시도는 앞선 시도들 보다 더 이해하기가 어려운 내용이었다. 때문에 이번
포스팅에서는 로지스틱회귀의 비용함수 이해에 필요한 몇가지 개념에 대한 설명과 지난 일주일간의 검색 과정에서
발견한 몇가지 팁을 설명하는 것 정도로 마무리 해야 할 것 같다.


몇 가지 개념


이미 많은 사람들이 알고 있다시피 기존 선형회귀에서 사용하던 비용함수를 로지스틱회귀에서 그대로 사용할 경우
그래프의 형태가 아래와 같이 표현된다고 한다(함수를 통해 아래 형태의 그래프를 그려보려고 무진 애를 썼으나
결국 실패했다).


선형회귀분석에서 이미 언급했지만 비용 함수라는 것은 예측값과 실제 값의 오차에 대한 제곱 평균을 구하는 것이고
이를 표현하는 함수에 대한 미분을 통해 기울기가 최소가 되는 지점을 찾는 과정이라고 하였다. 그리고 선형회귀분석의
경우 이 비용함수의 그래프가 U자 형으로 하나의 최소 기울기만이 존재하는 형태였다. 그러나 로지스틱회귀의 가설 
함수를 선형회귀의 비용함수에 바로 대입하는 경우에는 위의 그래프와 같이 기울기가 0이되는 지점이 다수 발생한다.
하지만 위의 그래프에서 실제로 최솟값을 갖는 위치는 빨간 점으로 표시된 위치이고 이 지점을 global minimum
이라 말하며 파란색 선으로 표시된 위치들은 비록 기울기는 0이지만 진정한 의미에서의 최솟값은 아니며 이 지점을
local minimum으로 부른다. 문제는 컴퓨터는 계산을 하면서 ‘기울기가 0인 지점’만을 찾게 되기에 local minimum
에서 계산을 멈추는 문제가 발생하게 되는 것이다.


이렇게 하나의 최솟값만을 구할 수 있는 형태의 함수를 convex 함수라고 하고 다수의 local minimum이 존재하는
함수를 non-convex 함수라 한다.


이와같은 이유로 해서 로지스틱회귀에서 사용할 비용함수는 convex 함수가 될 수 있도록 다시 만들어져야 한다.


그래서…로지스틱회귀의 비용함수는?


이미 많은 자료들에서 그 내용을 설명하고 있으니 굳이 내가 재차 설명할 필요는 없을 것 같다. 가장 많이 언급되는
홍콩과기대 김성훈 교수님의 슬라이드를 링크하는 것으로 대신한다.


http://hunkim.github.io/ml/lec5.pdf


다만 이 자료에서 주의할 점이 New cost function for logistic섹션의 함수 표기 중 H(𝑥)는 H(X)로 표기되어야
하는 것이 아닌가 싶은데 바로 다음 장에서 H(𝑥)가 로지스틱회귀 가설함수임을 표시하고 있으므로 그렇게 이해하면
될 것 같다.


결국 실제의 값이 1인 경우와 0인 경우 각각에 대해 로그를 취함으로써 convex 함수 형태를 만들어 비용을 최소화 할
수 있도록 만든 것이다. 간단하게 최종 결과만 보자.


위 식은 다음과 같이 해석된다. 우선 H(𝑥)는 로지스틱회귀의 가설함수임을 주지하자. 그리고 𝑦는 실제의 값이다.
이 실제의 값이 1일 경우 비용함수는 다음과 같이 변한다.



그리고 이 식의 그래프는 다음과 같다.



이 그래프의 x축은 가설함수의 결과 값이고 y축은 비용함수의 결과값이다. 그래프를 보면 직관적으로 알 수 있듯이 
가설 함수의 결과 값이 실제 𝑦의 값인 1인 경우 비용 함수는 최솟값 0을 얻을 수 있다. 하지만 가설함수의 결과 값이
0이 될 경우 비용함수의 결과는 ∞에 수렴하게 되어 엄청난 패널티를 받게 되는 것이다.


반대로 실제 𝑦값이 0인 경우는 이와는 반대로 진행된다. 먼저 𝑦가 0인 경우 비용함수는 다음과 같이 변하며



그래프는 다음과 같다.



이 경우에는 𝑦가 1일 때와 그래프 방향이 반대가 되어 가설함수의 결과 0일 경우 0이 1일 경우 ∞가 되어 역시나 최소
비용을 찾아낼 수 있게 되는 것이다.


desmos 살펴보기


사실 앞서 정리한 내용은 이미 많은 곳에서 더 잘 설명을 하고 있는내용이다보니 굳이 또다시 정리하기가 조금 민망할
정도이다. 그래서 더더욱 non-convex 함수를 그래프로 구현해보고자 했는데 수학적 지식이 일천하다보니 실패하고
말았다. 대신에 이 과정에서 좋은 사이트 하나를 발견하여 소개한다. 함수를 그래프로 표현해주는 웹 기반 서비스이며
이름은 desmos이다. 김성훈 교수님의 자료 중 시그모이드 함수와 그래프를 표시한 부분이 바로 desmos 화면이다.


링크 : https://www.desmos.com


웹 서비스 뿐만 아니라 모바일 앱으로도 같은 서비스를 제공하고 있으며 별다른 비용은 없는 것으로 보인다.
사용법은 비교적 단순한데 아래 동영상을 참고해서 설명을 이해하시도록…



먼저 사이트 접속 후 상단의 ‘Start Graphing’ 버튼을 클릭하면 시작할 수 있다. 회원 가입을 하면 작업했던 그래프를
저장하고 다음에 또 사용할 수도 있다.


우선 시작하게 되면 좌측 영역을 클릭하여 그래프를 그리고자 하는 함수 식을 입력한다. 미지수가 1개인 함수는 바로 
그래프가 그려지지만 미지수가 2개 이상인 경우 기본 미지수를 제외한 나머지에 대해서는 직접 값을 조작할 수 있도록
슬라이드를 생성할 수 있도록 해준다. 처음 식인 W = wx + b에 대해 w와 b는 슬라이드를 생성하였다.


슬라이드는 하단의 슬라이드 컨트롤을 클릭하면 값의 범위와 변화 단게를 설정할 수 있다. 최소 -10 ~ 최대 10, 0.1씩
변화와 같은 식으로 설정한다. 그리고 슬라이드 왼편의 플레이 버튼을 클릭하면 범위 내에서 변화 단계만큼씩 자동으로
변화하면서 그래프를 보여준다.


그리고 이미 생성한 함수를 다시 사용할 수 있다. 두 번째 식인 c = (W-r)에서 W는 앞서 생성한 함수이다. 만일
W 함수의 미지수 슬라이드를 변경하면 W를 사용한 c 함수의 그래프도 같이 변화하는 것이 보일 것이다.


이상이 기본적인 사용법이다.


사실 이 서비스를 이용하여 non-convex 그래프를 그려보려 했지만 뭔가 잘 안되었다. 아래 화면은 내가 시도한
흔적이다.



두 번째 이미지가 non-convex 그래프를 시도한 것인데…뭔가 잘못했나보다…ㅠ.ㅠ


정리


이미 이전에도 겪은 바이지만 무리하게 수학적인 이해를 추구하다보니 불필요하게 많은 시간들을 소모하는 경향이
있다. 그렇다고 완벽한 결론을 얻는 것도 아니고. 아무래도 앞으로는 적당히 수위를 조절해 가면서 버릴 것은 버리고
취할 것은 취해야겠다. 뭐 그래도 궁금한 것은 풀고 넘어가야 직성이 풀리니…


그래도 회귀 분석을 진행하면서 많은 수학적 개념에 대해 이해하게 된 것은 나름 성과라면 성과이다. 특히나 로지스틱
회귀의 경우 이후 신경망에 대한 학습에 매우 중요한 단서를 제공하므로 가능하면 꼼꼼히 알아 둘 필요도 있다고 판단
된다. 동영상 강의도 이제 곧 신경망쪽으로 넘어갈테니 기존 내용을 조금 더 훑어보며 복습을 좀 해야겠다.








저작자 표시
신고


다항 로지스틱 회귀의 이해


처음 텐서플로우 공부를 시작하면서 단순선형회귀에서 다중선형회귀로 넘어가는 과정에서 상당히 혼란스러웠었다.
단순한 1차 방정식의 형태에서 독립 변수가 늘어난 것만으로도 엄청나게 골머리를 싸매야 했다. 그리고 이제 이항
로지스틱회귀에서 다항로지스틱회귀로 넘어가려고 한다. 이번에도 역시 기대를 저버리지 않고 뇌이랑이 메워질만큼
알 듯 모를 듯한 수식들을 붙들고 씨름을 하게 만들었다.


설명은 단순한데 그 단순한 것을 제대로 이해하기 위해서는 상당한 공을 들여야 하는 것이 이 바닥인가보다…ㅠ.ㅠ


준비운동 - sigmoid 복습


먼저 이전 포스팅인 이항로지스틱회귀에서 언급되었던 식 몇개를 되살려보자. 거기에는 두 가지 개념이 등장했는데
바로 오즈 비(ODDS Ratio)와 이 오즈 비에 자연로그를 씌운 로짓(logit) 변환이었다. 


오즈 비는 어떤 일이 발생하지 않을 확률에 대한 발생 확률의 비로 발생 확률을 p로 본다면 전체 확률 1에서 발생
확률 p를 뺀 것이 바로 발생하지 않을 확률 1-pr가 되므로 오즈 비는 아래와 같은 식으로 표현된다.




그리고 여기에 자연로그를 취해 아래와 같이 로짓 변환을 마무리 한다. (왜 이런 짓을 하는지는 이전 글인 ‘로지스틱
(Logistic) 회귀 함수 살펴보기’
를 참조하라~)




이런 과정을 통해 다음과 같은 등식을 얻을 수 있었다.




이 등식을 확률 p에 대해 정리하게 되면 우리가 익히 들어왔던 시그모이드 함수의 식이 나타난다.


메인이벤트 - 다항로지스틱회귀


이제 본격적으로 다항로지스틱회귀에 대해 살펴보도록 하자. 앞서 살펴본 로지스틱회귀는 결과가 1 또는 0이 나오는
이항로지스틱회귀였다. 이항로지스틱회귀에 대해 조금 더 부연을 하자면 실제로 이항로지스틱회귀를 통해 나오는
결과 값은 0 ~ 1 사이의 실수이다. 그렇기 때문에 기준점을 하나 두어 그 기준점보다 크면 1로 기준점보다 작으면
0으로 처리하는 것이다. 이 때 이 기준점을 cutoff value라고 하며 0 ~ 1 사이의 값에 대한 기준점이브로 보통
cutoff value는 0.5를 사용한다고 한다.


다항로지스틱회귀의 경우 결과 항이 3개 이상인 경우이다. 현실적인 예를 들자면 최근 대선을 앞두고 쉴새없이 여론
조사 자료들이 뉴스에 보도된다. 이 때 문-안 2자 대결에 대한 여론조사가 이항로지스틱회귀라면 5명의 후보자들에
대해 모두 조사를 하게 되면 다항로지스틱회귀가 되는 것이다. 이 다항로지스틱회귀 역시 결과는 확률 값이며 따라서
각각의 항에 대한 모든 결과으의 합은 1( = 100%)이 된다.


다항로지스틱을 유도하는 식은 이항로지스틱의 경우와 동일하다. 각각의 항에 대해 이항로지스틱을 수행한 후 이 
결과를 일반화 하여 식을 만들게 된다.


대통령 선거를 예로 조금 더 상세하게 들어가보자. 다항로지스틱회귀를 통해 분류를 해야 하는 지지자들은 문, 안, 홍,
유, 심의 5 부류이다(다행이 후보자들 성이 다 다르다…^^). 이 경우 각각에 대해 이항로지스틱을 수행한다 함은 바로
다음과 같다(편의상 이하 순서대로 A,B,C,D,E로 부르도록 하자).


  1. A를 지지하는 사람과 지지하지 않는 사람의 이항
  2. B를 지지하는 사람과 지지하지 않는 사람의 이항
  3. C를 지지하는 사람과 지지하지 않는 사람의 이항
  4. D를 지지하는 사람과 지지하지 않는 사람의 이항
  5. E를 지지하는 사람과 지지하지 않는 사람의 이항


그런데 여기서 약간의 문제가 있다. 이항일 경우에는 오즈 비가 쉽게 나왔다. 경우의 수가 p 또는 1-p로 쉽게 식을
만들 수 있었는데 경우의 수가 많다보니 우리같은 수포자는 여지없이 멘붕에 빠지고 만다…ㅠ.ㅠ


엄밀하게 말하자면 이런 경우에는 각각의 결과를 서로간에 비교를 해야 한다. A와 B를, A와 C를… B와 C를…C와 D…
하지만 이렇게 하자니 결과 항이 많아질수록 복잡해지고 일이 커진다. 누누히 강조하거니와 수학은 곧 단축이 
미덕이다…^^ 현명하신 우리의 수학자들께서는 여기서도 지혜를 발휘하셨다. 그 방법인 즉슨…기준이 되는 하나의
항을 정하고 그 항에 대한 나머지 항들 각각의 비율을 계산하도록 한 것이다. 즉, 편의상 맨 마지막의 E를 기준으로
했다고 가정하면,


  1. A에 대해 p(A) / p(E)
  2. B에 대해 p(B) / p(E)
  3. C에 대해 p(C) / p(E)
  4. D에 대해 p(D) / p(E)


E에 대한 E의 확률은 의미가 없으니 생략하게 되어 총 4개, 다시 말해 전체 항의 갯수를 K개라고 하면 K-1개의 식이
만들어진다. 그리고 이항로지스틱에서와 마찬가지로 자연로그를 취하면 다음과 같이 표현할 수 있다.


이와 같은 식을 항의 갯수가 K일 경우로 일반화 시켜보면 다음과 같이 표현할 수 있다.




잠깐 되돌아가서 보면 특정 항에 대한 각 항의 비만을 계산했는데(A와 E, B와 E, C와 E, D와 E) 그럼 다른 항들
간의 비는 어떻게 확인해야 할까? 매우 간단하다. 이미 E에 대한 각 항목의 비를 구해놓았기 때문에 간단한 로그
법칙으로 구할 수 있다. 만일 B에 대한 A의 비를 구한다면 아래 식과 같이 하면 된다.


파이널 라운드 - sigmoid에서 softmax로


늘상 수학적인 문제에서 길을 잃는다. 이 포스팅을 쓰기 시작한 것이 4월 14일 경이었는데 바로 전 섹션까지가 그 때
쓴 글이다. 이렇게 써내려가면서 나는 내가 sigmoid 함수와 softmax 함수의 상관 관계를 이해하고 있다고 오해했다.
다항로지스틱회귀를 통해 나온 식으로 자연스럽게 softmax 함수가 유도될 것으로 생각했는데 나의 착각이었다.
가장 근접하게 접근한 것이 위키피디아의 로지스틱 회귀 항에 나오는 로그-선형 모델 항목인데, 이 로그-선형 모델
자체를 모르니 알듯 모를듯한 혼란만 계속되었다. 그렇다고 다시 로그-선형모델로 거슬러 올라갈 수도 없고…


결국 그렇게 매우 유사하게 생긴 두 함수 사이의 관계를 밝혀본답시고 또 일주일이 그렇게 흘러가버리고 말았다.
그리고 찾은 답은 sigmoid에서 softmax를 유도할 수 없다면 거꾸로 softmax로부터 sigmoid를 이끌어내보자는
것이다. 이미 sigmoid가 softmax의 특수한 형태 (결과 항이 2개인 경우의 특수한 형태)라는 것은 검색 과정에서
알 수 있었다. 다만 그 과정을 유도할 수 없었을 뿐. 그리고 앞서 말했듯이 이제 거꾸로 한 번 거슬러 올라가보고자 한다.


우선 주의할 것은 이전 포스팅에서도 언급한 바가 있듯이 치환에 주의하라는 것이다. 이 치환의 마법으로 동일한
식이 다양한 형태로 등장하게 된다. 내가 검색을 하며 찾았던 softmax 함수에 대한 표현만 해도 아래 이미지와 같이
다양했다.




하지만 이 것들이 모두 동일한 의미의 식이라는 것… 이쯤 되면 이 건 수학보다는 문학에 가깝다…-.-
일단 변수와 지수, 밑, 첨자까지 너무 많은 알파벳과 그리스 문자가 뒤섞여 있으면 혼란만 더할 것이니 가장 단순한
식을 기준으로 진행해 가보자. 바로 김성훈 교수님의 유튜브 강좌에 나온 식이다.




군더더기 없이 깔끔해서 좋다^^. 일단 이 식 자체에 대해 간단하게 설명을 해보자. 위의 식에서는 생략이 되었지만
softmax 함수에서 각 항을 의미하는 아래 첨자 i의 범위는 우변의 분모에 있는 시그마의 밑이 j의 범위와 동일하다.
즉 i 항에 대한 값 / 모든 결과항에 대한 값의 합이라고 간단하게 말할 수 있을 것이다. 


다시 말해 전체에 대한 부분의 비율이므로 각 항에 대한 결과로써의 좌변의 값은 각 항에 대해 1보다 작은 값이
출력되며 모든 항에 대한 결과값의 총 합은 1(=100%)가 된다.


사실 이정도만 이해하면 그냥 다음 진도 뽑아도 된다. 그래도 궁금하다. 단지 결과 항이 늘어는 것 뿐이고 또 웬만한
검색 결과에도 sigmoid 함수가 softmax 함수의 특수한 형태라고 하니 두 함수의 관계를 한 번 짚고 넘어가고 싶은
것이다. 그래서 일주일도 넘는 시간을 투자했고…ㅠ.ㅠ 완변하진 않지만 지금부터 한 번 살펴보자.


위에 적은 식은 i(또는 j)가 임의의 갯수인 경우의 식이다. 그런데 만일 i가 0과 1 두가지의 경우밖에 없는 경우를
가정해보자 그렇다면 각각의 항에 대해 다음과 같이 표현할 수 있을 것이다.




이 때 y0이 0인 경우를 생각해보면 식은 다음과 같이 바뀐다.




첫 번째 줄의 가장 오른쪽 식을 보면 바로 sigmoid 함수가 나온다. 이렇게 뭔가 어설프지만 softmax와 sigmoid
함수가 모종의 은밀한 관계(?)가 있다는 것을 알 수는 있었다…-.-


정리


겨우 풀어놓긴 했지만 여전이 뭔가 덜 풀린듯한 느낌이다. 하지만 이 것만 붙잡고 시간을 보낼 수 없으니 이정도에서
마무리 하고자 한다. 사실 이 과정에서 중요한 것은 식이 어떻게 도출되느냐가 아니라 딥 러닝으로 가는 과정에서
그 단초를 살짝 엿볼 수 있었다는 점이다.


텐서플로우에서의 hello world라고 하는 MNIST 분석이라든지 이미지 분석 등이 따지고 보면 결국 확률의 문제다.
어떤 이미지가 개인지 고양이인지를 판단한다는 것은 곧 학습 데이터를 기반으로 개일 확률과 고양이일 확률을
구하는 것에 다름 아닌 것이다.



갈길은 멀고 마음은 급하지만 그래도 차근차근 황소 걸음으로 가보련다. 다음 포스팅에서는 다항로지스틱회귀의
cost 함수인 cross-entropy에 대해 알아보도록 하겠다.






저작자 표시
신고




텐서플로우를 이용한 로지스틱(Logistic) 회귀

로지스틱 회귀에 들어오면서 시그모이드 함수에 난데없이 등장한 자연상수 e에 대한 정체를 밝히느라 무려 2주를
보냈다. 물론 그 과정이 쓸모없는 일은 아니었지만 꽤나 고민스럽고 답답한 시간들이었다. 이제 어느정도 그 정체를
밝혔으니 본격적으로 로지스틱 회귀 실습을 진행해보도록 하자.


샘플 데이터는 http://www.dodomira.com/2016/02/12/logistic-regression-in-r/ 블로그를 참조하였고
자료 원본 경로는 http://www.ats.ucla.edu/stat/data/binary.csv 이다.


자료 불러오기


주제에서 조금 벗어나는 이야기지만 현재 나는 Docker for MAC에서 TensorFlow 이미지를 설치한 상태이고
따라서 Docker를 통해 Jupyter 환경에서 작업을 진행 중이다. 이런 환경에서 작업의 불편을 조금이라도 줄이기
위해 Docker 컨테이너에서 MAC OSX의 디렉토리에 접근하여 데이터 파일을 읽어들이도록 사전 작업이 필요하였다. 


Docker for MAC은 기본적으로 File Sharing이라는 기능을 제공하는데 작업표시줄에 떠있는 아이콘을 클릭하면
표시되는 메뉴에서 'Preferences...'를 선택하면 설정 창이 표시되고 설정 창의 두 번째 탭에 'File Sharing'이 있다.
기본적으로 /Users,/Volumes, /private, /tmp가 등록이 되어있다. 추가로 등록도 가능하지만 기본적으로 등록된
경로의 하위 경로는 등록하지 못한다.


이렇게 등록한 후 docker run 명령어를 통해 컨테이너를 실행할 때 '-v [MAC 경로]:[docker 경로]' 형식으로
옵션을 추가하면 된다.


예)
docker run -it -v /Volumes/Data1/Tesorflow:/data -p 8888:8888 -p 6006:6006 \
gcr.io/tensorflow/tensorflow


위과 같이 한 경우 Docker 컨테이너 쉘로 진입하면 cd /data로 MAC 디렉토리에 접근이 가능하다. 이번 예제에서는
이와 같이 데이터 파일을 /data로부터 읽어 사용할 것이다.


파일을 읽어 출력한 결과는 아래와 같다.

import tensorflow as tf
import numpy as np

xy = np.loadtxt('/data/sample.txt', unpack=True, dtype='float32')

x_data = xy[0:-1]
y_data = xy[-1]

print (x_data)
print (y_data)

출력 결과 ---------------------------------------------------------------

[[ 1.          1.          1.         ...,  1.          1.          1.        ]
 [ 3.79999995  6.5999999   8.         ...,  4.5999999   7.          6.        ]
 [ 3.6099999   3.67000008  4.         ...,  2.63000011  3.6500001
   3.8900001 ]
 [ 3.          3.          1.         ...,  2.          2.          3.        ]]
[ 0.  1.  1.  1.  0.  1.  1.  0.  1.  0.  0.  0.  1.  0.  1.  0.  0.  0.
  0.  1.  0.  1.  0.  0.  1.  1.  1.  1.  1.  0.  0.  0.  0.  1.  0.  0.
  0.  0.  1.  1.  0.  1.  1.  0.  0.  1.  1.  0.  0.  0.  0.  0.  0.  1.
  0.  1.  0.  0.  0.  0.  1.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.
  0.  0.  0.  0.  0.  1.  0.  1.  0.  0.  0.  0.  1.  0.  0.  0.  0.  1.
  0.  1.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  1.  1.  0.
  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  1.  1.  0.  0.  0.  0.
  1.  0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  0.
  0.  0.  0.  0.  1.  0.  1.  0.  1.  0.  0.  1.  0.  1.  0.  0.  0.  0.
  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  1.  0.  0.
  0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  1.  0.  0.  0.  1.  0.  0.  1.
  0.  0.  0.  1.  1.  0.  1.  1.  0.  1.  0.  0.  0.  0.  0.  0.  1.  1.
  0.  1.  0.  1.  0.  0.  1.  0.  0.  1.  0.  0.  0.  1.  0.  0.  0.  0.
  1.  0.  1.  0.  0.  0.  0.  1.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.
  1.  1.  1.  0.  1.  1.  0.  0.  0.  0.  1.  1.  1.  0.  0.  1.  1.  0.
  1.  0.  1.  0.  0.  1.  0.  1.  1.  1.  0.  0.  0.  0.  1.  0.  1.  1.
  0.  0.  1.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  1.  1.  1.  0.  0.
  1.  0.  0.  0.  0.  0.  0.  1.  0.  1.  1.  1.  1.  0.  0.  0.  0.  0.
  0.  0.  0.  1.  0.  0.  0.  0.  0.  0.  1.  1.  0.  0.  0.  1.  0.  1.
  0.  0.  0.  0.  0.  0.  0.  0.  1.  0.  1.  0.  1.  1.  0.  0.  1.  0.
  1.  1.  0.  0.  1.  0.  0.  0.  0.  0.  1.  1.  1.  1.  0.  0.  0.  1.
  0.  0.  0.  1.  0.  0.  1.  0.  1.  0.  0.  0.  1.  1.  1.  1.  1.  0.
  0.  0.  0.  0.]

로지스틱 회귀 시작하기

우선 기본적인 흐름은 김성훈 교수님의 동영상 강좌를 기준으로 따라가도록 하겠다.
위에서 이미 데이터를 준비해 놓았으므로 이후 진행을 살펴보자.


먼저 학습 이후에 테스트를 위한 임의의 값을 넣을 수 있도록 x와 y에 대한 placeholder를 생성한다.

X = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)


그리고 각 독립변수 (X)에 대한 기울기 변수 W를 만들자. 위 출력 결과를 보면 알 수 있듯이 x_data은 1행 3열의
벡터이다. 즉 len(x_data)의 값은 3이 될 것이다.

W = tf.Variable(tf.random_uniform([1, len(x_data)], -1.0, 1.0))


다음으로 선형 회귀에서 사용했던 가설함수를 만들고...

h = tf.matmul(W, X)


이 식은 선형 회귀에 대한 가설 함수이므로 이 것을 이용하여 이전 포스팅에서 열심히 공부한 로지스틱 회귀에 적합한
가설 함수를 새롭게 만든다.

hypothesis = tf.div(1., 1. + tf.exp(-h))


로지스틱 회귀의 가설 함수 공식은 이미 알아본 바가 있으므로 tensorflow의 API만 간단히 살펴보자.


  1. tf.div : 굳이 설명할 것도 없이 첫 번째 인자를 두 번째 인자로 나누는 함수이다.
  2. tf.exp : exp라는 함수는 tensorflow 뿐만 아니라 수학 계산을 위해 만들어진 대부분의 프로그램 및 언어에서 대체로 동일한 이름으로 사용되며 자연상수 e의 거듭제곱을 의미한다. 괄호 안의 파라미터만큼 거듭제곱을 하는 것이다. 따라서 이 식은 e^(-W * X)로 우리가 알고있는 로지스틱 회귀 함수의 분모에 보여지는 식이 된다.


이렇게 가설 함수를 만들었으니 이번에는 cost 함수를 만들 차례이다. 로지스틱 회귀에서의 cost 함수에 대해서는
별도로 정리한 적은 없으나 다른 사이트의 훌륭한 자료들을 참고로 식만 살펴보자(이 블로그에서는 다음에 자세히
다뤄보도록 하겠다).

with tf.name_scope("cost") as scope:
cost = -tf.reduce_mean(Y*tf.log(hypothesis) + (1 - Y)*tf.log(1 - hypothesis))
cost_summ = tf.scalar_summary("cost", cost)
cost_summ = tf.summary.scalar("cost", cost)


앞서 말했듯이 자세한 내용은 뒤로 미루고 간단하게 몇가지만 짚고 넘어가자.


우리는 로지스틱 회귀가 결과로써 0 또는 1 사이의 값을 갖도록 한다고 했다. 그 기준에 따라 Y가 0일 경우와
Y가 1일 경우 각각 다른 cost 함수가 적용되는데 이 것을 두 개의 식으로 사용하기 복잡하므로 위와 같은 식을
만들었다. Y가 1이라면 tf.reduce_mean의 파라미터로 들어가있는 덧셈식의 오른쪽 항이 0이되고 Y가 0이라면
반대로 왼쪽항이 사라져 둘 중 한 식만 적용이 되는 것이다.


그밖에 제곱 평균(tf.reduce_mean)을 하는 것은 선형 회귀와 동일하다.
그리고 아래와 같이 학습 계획을 세우는 부분 역시 이전의 선형 회귀외 동일하다.

a = tf.Variable(0.1)
optimizer = tf.train.GradientDescentOptimizer(a)
train = optimizer.minimize(cost)


그리고 이미 알고 있겠지만 tensorflow의 Variable들은 초기화를 하기 전까지는 아무것도 아니다~
변수를 초기화 하고 Session을 생성하고 학습을 진행하는 것까지 한번에 가보자!


학습 과정에서는 결과에 대해 프린트도 하고 TesorBoard용 로그도 찍도록 내용을 추가하였다.
참고로 TensorBoard를 이용하기 위한 API가 변경되었다. 관련 내용은 글 말미에 간단하게 정리하겠다.

init = tf.global_variables_initializer()

with tf.Session() as sess:
merged = tf.merge_all_summaries()
merged = tf.summary.merge_all()
writer = tf.train.SummaryWriter("/logs", sess.graph_def)
writer = tf.summary.FileWriter("/logs", graph=sess.graph)

sess.run(init)
for step in range(20000):
sess.run(train, feed_dict = {X:x_data, Y:y_data})
if step % 200 == 0 :
summary = sess.run(merged, feed_dict = {X:x_data, Y:y_data})
writer.add_summary(summary, step)
print(step, sess.run(cost, feed_dict = {X:x_data, Y:y_data}), sess.run(W))

correct_prediction = tf.equal(tf.floor(hypothesis + 0.5), Y)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print("Accuracy: ", accuracy.eval({X:x_data, Y:y_data}))


(0, 3.9219272, array([[ 0.56839818,  0.54351538,  0.33454522,  0.35521972]], dtype=float32))
(200, 0.58839369, array([[ 0.26840821,  0.14631633, -0.08747143, -0.65875155]], dtype=float32))
(400, 0.58729297, array([[ 0.12495378,  0.15614627, -0.05624871, -0.66897833]], dtype=float32))
(600, 0.58630282, array([[-0.01207304,  0.15915234, -0.02467063, -0.66446435]], dtype=float32))
(800, 0.58538932, array([[-0.14362514,  0.16177623,  0.00590925, -0.65985072]], dtype=float32))
(1000, 0.58454657, array([[-0.26996911,  0.16426219,  0.03536379, -0.65547818]], dtype=float32))
...
중간 생략
...
(17400, 0.57432276, array([[-3.30464649,  0.22630024,  0.74326813, -0.56382245]], dtype=float32))
(17600, 0.57432127, array([[-3.30992198,  0.22641255,  0.74449652, -0.56368262]], dtype=float32))
(17800, 0.57432002, array([[-3.31500411,  0.2265207 ,  0.74568129, -0.56354982]], dtype=float32))
(18000, 0.57431865, array([[-3.31990051,  0.22662495,  0.74682134, -0.56342   ]], dtype=float32))
(18200, 0.57431746, array([[-3.32461858,  0.22672606,  0.74791992, -0.56329668]], dtype=float32))
(18400, 0.57431638, array([[-3.32916546,  0.22682308,  0.74897873, -0.56317711]], dtype=float32))
(18600, 0.57431531, array([[-3.33354449,  0.22691627,  0.74999893, -0.56306177]], dtype=float32))
(18800, 0.57431442, array([[-3.33776498,  0.22700654,  0.75098211, -0.56295192]], dtype=float32))
(19000, 0.57431358, array([[-3.34183216,  0.22709353,  0.75192851, -0.5628444 ]], dtype=float32))
(19200, 0.57431275, array([[-3.34575057,  0.22717719,  0.75284076, -0.56274116]], dtype=float32))
(19400, 0.57431203, array([[-3.34952545,  0.22725755,  0.753721  , -0.56264317]], dtype=float32))
(19600, 0.57431132, array([[-3.35316253,  0.22733469,  0.75456882, -0.5625475 ]], dtype=float32))
(19800, 0.57431066, array([[-3.356668  ,  0.22740975,  0.75538445, -0.56245506]], dtype=float32))
('Accuracy: ', 0.70749998)


유튜브 강좌에 있던 짧은 샘플로는 강좌에서와 거의 유사한 결과를 얻었다. 그런데 이렇게 별도로 가져온 데이터를
사용하나 cost가 영 줄어들지를 않는다...ㅠ.ㅠ 게다가 정확도도 0.7 수준...
참고로 TensorBoard로도 확인 가능하도록 했으니 확인이나 한 번 해보자. 출력한 cost 값의 챠트는 다음과 같다.


그리고 그래프는 아래와 같다.


아직 나의 수준에서는 뭔가 제대로 된 결과가 나오기는 힘든가보다. 조금 더 열심히 공부를 해봐야겠다...ㅠ.ㅠ

앞서 말했듯이 전체적인 진행을 김성훈 교수님의 유튜브 강의를 기반으로 이번 실습을 진행을 했다. 그런데 tensorflow를 1.0으로
업그레이드한 탓인지 TensorBoard 설정을 위한 API 일부가 모두 바뀌었다. 일단 코드 상에 강의에 등장하는 이전 버전의 API는
모두 주석 처리하여 남겨두었다. 간단하게 정리하면 변화는 다음과 같다. 

  • tf.scalar_summary("cost", cost) -> tf.summary.scalar("cost", cost)
  • tf.merge_all_summaries() -> tf.summary.merge_all()
  • tf.train.SummaryWriter("/logs", sess.graph_def) -> tf.summary.FileWriter("/logs", graph=sess.graph)



마지막으로 위 실습을 진행한 Jupyter 노트북과 샘플 데이터 파일을 첨부한다.


sample.txt

chapter2_logistic_regression.py.ipynb


저작자 표시
신고


자연로그! 너 죽고 나 죽자! - 고등 수학을 초등학생처럼 배워보기~


지난 글에서 내가 문돌이라는 것을 무기 삼아 겨우 자연상수 설명 조금하고 자연로그는 그저 그런게 있는갑다 하고
그냥 넘어가버렸다. 하지만 역시…뒤처리가 안된 것 마냥 여전히 찜찜하기 그지없다. 그래서 한 번 더 자연로그를
이해하는데 도전해보기로 하였다. 과연 잘 설명이 될 지는 모르겠으나 이미 언급한 바와 같이 이러한 과정 하나하나가
분명 앞으로 나아가는 길이라 믿는다.


문돌이 혹은 수포자의 한계


어떤 수학 공식을 풀이 한다는 것, 왜 그런 공식이 도출 되었나 하는 것은 곧 수학의 입장에서는 검증을 하는 것, 다시
말해 ‘증명’을 하는 것이다. 그런데 아이러니컬 하게도 증명을 하게 되면 처음의 단순했던 식이 매우 복잡해진다.
이것은 문돌이나 수포자(이하 우리들…-.-)의 수와 연산에 대한 알레르기에 불을 붙이는 격이다. 하지만 이 과정을 
거치지 않고는 ‘이해’라는 한 단어에 접근할 수 없다.


그렇다면 어떻게 해야 이 알레르기를 극복할 수 있을까…최근 여러가지 수학 공식을 정리하면서 개인적으로 터득한
것을 정리해보고자 한다. ‘개인적’인, 지극히 ‘개인적인’ 방법이며 이 방법이 누구에게나 통한다고 장담할 수는 없다.


  1. 너무나 당연한 이야기부터 해볼까? 적어도 각 연산자의 또는 수학 기호의 의미는 알아야 한다. 굳이 그 성질이나 활용까지 이해하지는 못하더라도 적어도 개념은 알아야 한다. 예를 들어 우리들의 가장 큰 적 중 하나인 적분을 생각해보자. ∫(인테그랄, integral)이라는 기호가 정확히 뭔진 모르겠지만 어떤 범위 내에서 어떤 값들을 무한히 더해가는 것이라는 것 정도는 알아야 한다.
  2. ‘치환’에 유의해야 한다. 다른 말로 하면 가장 처음에 주어지는 ‘등식(=)’을 유심히 봐야 한다. 나는 최근까지 그 의미를 몰랐다. f(𝒙) = a𝒙 + b라는 것이 증명이 진행되어감에 따라 a𝒙 + b 대신 f(𝒙)를 사용해도 된다는 의미인지를 까맣게 몰랐다. 그래서 도대체 f(𝒙)라는 것은 어떤 의미일까을 계속 궁금해 했다. 이밖에도 길어질 수 있는 공식을 간단하게 표현하기 위해 치환을 자주 한다. 간단하게는 한 개의 변수에서부터 복잡하게는 어떤 수식을 하나의 문자로 바꾸어 쓰는 것이다. 이 것을 놓치게 되면 증명에 마지막에 가서 우리는 ‘음…도대체 이 문자는 언제부터 여기에 있었지?’하는 지극히 근혜스러운 고민을 하게 되고 만다.
  3. 2번과 같은 맥락에서 수학 공식을 풀이할 때는 단지 길어서 보기 힘든 공식을 ‘단순화’하기 위한 장치들을 많이 사용한다. 우리들은 이 부분을 이해하지 못한다. 심지어는 이렇게 축약된 부분을 다시 길게 늘여놔야 더 잘 이해하는 경우도 있다. 대체로 이런 장치들은 치환에 의해 이루어지므로 2번과 잘 연결해서 생각해야 한다.
  4. 마지막 방법도 매우 당연한 이야기다. 바로 끈기있게 보고 또 봐야 한다는 것이다. 길고 긴 증명의 과정을 따라가다보면 돌연 어느 단계에서 ‘왜 이 공식의 좌변과 우변이 같은 것이지?’라는 의문이 드는 그런 단계가 있다. 마치 몇만 광년은 워프한 듯한 지극히 아득한 느낌으로 양 변의 공식이 동일하다는 것을 이해하지 못하는 순간…아는 사람만 알 것이다. 인터스텔라급 괴리감이다…-.- 이 과정에서 1번과 2번의 자세가 필요하다.


이러한 마음가짐으로 자연로그에 대해 다시 한 번 알아보고자 한다.


다시 미분으로


처음 회귀분석과 관련된 수학 내용을 정리할 때 미분을 한 번 정리했었고 그 때 주로 참고했던 자료가 바로 위키백과의
미분 항목이었다. 그런데 앞서 설명한 그 알레르기 때문에 그냥 내가 필요한 내용만을 보고 나머지는 현기증을 느끼며
피해갔던 것이 실수였다. 바로 그 미분을 설명하는 내용 중에 자연스럽게 자연로그가 도출되고 있었던 것이다.


지금부터 우리들의 입장에서 아주 지겨울 정도로 자세하게 하나 하나 되짚어볼 것이다.


단서가 된 항목은 바로 ‘지수 함수의 미분’이었다. 우선 미분에 대해 다시 한 번 짚어보자. 미분이란 어떤 연속적인 곡선
(엄밀하게는 맞는 표현이 아니지만 우선은 이렇게 표현하자)에 대해 특정 위치에서의 기울기를 구하는 것이라고 했다.
위키의 표현으로 말하자면 평균변화율을 구할 수 있을 때 이 평균 변화율의 극한이 바로 특정 위치의 기울기(를 갖는
접선)라고 할 수 있는 것이다. 최초 출발은 여기다. 아래 공식을 이해하도록 해보자.



사실 우리네 입장에서 공식만 가지고 이해하긴 어렵다. 이해할 수 있다면 이고생도 안한다…-.- 그러니 그래프를
보면서 차근차근 알아보자. 



일단 ∆는 라틴어 델타의 대문자로 주로 변화량을 의미한다. 즉 ∆ 𝒙라는 것은 얼마인지는 모르겠지만 𝒙의 변화량을 
의미한다. 즉 아래 그래프에서 보면 𝒙는 𝒙에서부터 𝒙 + ∆ 𝒙까지 변한 것이다. 이에 대해 𝒙에 대한 함수인 𝑓(𝒙)는 
𝑓(𝒙)에서부터 𝑓(𝒙 + ∆ 𝒙)까지 변했다. 이 내용을 𝒙를 기준으로 설명하면 바로 위의 공식이 나오는 것이다. 𝒙의 변화량
∆ 𝒙에 대한 𝑓(𝒙)의 변화량 ∆ 𝑓(𝒙)의 비율이란 바로 이런 의미이다.


그리고 이 평균 변화율에 대해 극한을 적용한 것이 바로 순간변화율(미분계수)이며 우리가 미분을 통해서 얻고자 하는
값인 것이다(극한에 대한 이해는 위키에 있는 아래 이미지를 참고하자).


By Brnbrnz - 자작, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=43249235


일단 이 순간 변화율 공식을 기억해두자.


지수 함수 미분하기


우선 아래 링크(위키피디아의 미분에 대한 문서)의 ‘지수 함수의 미분’을 같이 보면서 살펴보자


https://ko.wikipedia.org/wiki/미분


특정 조건만 만족하면 많은 함수들을 미분할 수 있다. 그리고 그 많은 예가 위키에 잘 설명이 되어있다. 여기서는 그 중
지수 함수에 대한 미분을 살펴볼 것이다. 이전에는 그냥 자연로그가 있어서 그걸 사용하기만 했는데 이 지수 함수의
미분 과정에서 자연로그가 톡 튀어나온다. 신기하게도…


먼저 수학 알레르기를 극복할 수 있는 방법을 상기하자. 그리고 위키에 설명된 지수 함수의 미분 내용의 첫 줄을 살펴
보자. 𝑓(𝒙) = b^ 𝒙라는 등식이 제일 처음 보인다. 그리고 이 함수의 도함수(순간변화율을 얻을 수 있는 함수)가
다음과 같이 표시되어있다.



설명에 ∆ 𝒙를 ℎ로 표기한다고 했으니 다시 ℎ를 ∆ 𝒙로 돌려놓고 보면 다음과 같이 된다(편의상 순간변화율에서 극한을
제거하고 평균변화율만 보자).



그런데 제일 처음 등식에서 𝑓(𝒙) = b^ 𝒙라고 하였으니 𝑓(𝒙 + ∆ 𝒙) = b ^ (𝒙 + ∆ 𝒙)가 성립하고 따라서 위 식에서
b^ 𝒙를 𝑓(𝒙)로 b ^ (𝒙 + ∆ 𝒙)를 𝑓(𝒙 + ∆ 𝒙)로 바꾸어 놓으면 우리가 최초에 확인했던 평균변화율과 동일한 공식이
나온다. 이렇게 해서 지수 함수 𝑓(𝒙) = b^ 𝒙에 대한 도함수를 확인했다.


이후 내용은 이 도함수에 대한 풀이 과정이다. 비교적 어렵지 않은 내용이므로 조금만 검색을 하면서 보면 이해할 수 
있다. 그런데…'이때 상수 𝑘를'이라는 문장 바로 다음에 난데없이 자연로그가 튀어나왔다. 우변의 공식은 앞서 
진행된 풀이에 등장했던 공식이므로 당연한 등장인데 이 공식이 ln 𝑏와 같다고 하니 이 우변의 공식을 지지고 볶으면 
뭔가 자연로그에 대한 단서가 나온다는 것이렷다. 매번 이해도 못하는 자연로그를 가져다 쓰다가 처음으로 자연로그가
없는 상태에서 자연로그를 도출하게 되었다. 이제 본격적으로 어떻게 아래 공식이 자연로그가 되는지 확인해보자.



위의 등식에서 우측의 식이 어떻게 해서 자연로그로 이어지는 지를 분석해볼 것이다. 역시나 가장 먼저 할 일은
바로 ‘치환’이다. 우측 식에서 가장 복잡한 분자 𝑏^ℎ - 1을 𝑡라고 하자. 즉 𝑡 = 𝑏^ℎ - 1가 되는 것이다. 그런데 우리는
지금 지수에 대해서 알고자 하는 것이니 𝑏^ℎ를 기준으로 식을 바꾸어보면 𝑏^ℎ = 𝑡 + 1로 놓을 수 있다. 여기서 한 번
더! 분모가 ℎ이니 마지막에 바꾼 식을 ℎ에 대해 정리한다. 그러면 우리는 최초의 식에서 2개의 항을 다음과 같이
바꿔놓을 수 있다.



이 때 lim의 밑이 ℎ →0에서 𝑡 →0으로 바뀌었는데 이 것은 극한의 성질을 조금 이해해야 한다. 극한이란 
엄밀하게 말하면 정확한 어떤 수가 아닌 ‘어떤 수에 가장 근접하는 상태’라고 봐야 한다. 하지만 계산의 편의를 
위해 화살표 우측의 수라고 생각해도 무관하다. 그렇게 봤을 때 ℎ →0으로 무한히 가는 경우 

𝑏^ℎ - 1 = 𝑏^0 - 1 = 1 - 1 = 0

위 등식이 성립하므로 𝑏^ℎ - 1 = 𝑡라는 조건에 의해 결국 𝑡또한 0이 된다. 이 조건으로 인해 𝑏^ℎ - 1이 𝑡로 
치환된 식에서는 lim의 밑을 𝑡 →0로 놓을 수 있는 것이다.


원래의 식을 치환한 항들로 대체하면 다음과 같이 변경된 식이 나온다.



여기까지는 이해가 되었는가? 여기서 한가지만 더하자. 미지수가 분자와 분모에 모두 존재하니 아무래도 뭔가 더
복잡해 보인다. 그러니 미지수를 분모쪽으로 모두 몰아버리자. 이러한 방법도 식을 간단하기 위해 매우 자주 쓰이는
방법이다. 자~원래의 식의 결과를 변화시키지 않고 처리하려면? 1을 곱하면 된다. 다만 1을 어떻게 표현하는가가
관건이다. 결과부터 보자.



절묘하지 않은가? 나만 그런가? 분자의 미지수를 없애기 위해 1이라는 수를 (1/𝑡) / (1/𝑡) 로 바꾸었다. 분모와
분자가 같기만 하면 1이니까. 그래서 분모는 조금 복잡해졌지만 분자는 𝑡 * (1/𝑡) = 1로 미지수를 없애고 
1이라는 아주 쉬운 수로 바뀌었다. 이런 너무도 당연해보이는 기법들이 우리들에게는 그토록 어려운 것이다…ㅠ.ㅠ
이렇게 정리된 식은 다음과 같다.



분모를 한 번 더 정리해보자. 로그의 성질에 의해 다음과 같이 바꿀 수 있다.



드디어 나왔다! 기억하시는가? 자연상수 e의 식을! 다시 한 번 보자.



극한이 변수 t에 대해 적용되므로 극한을 t에 대해서만 놓고보면 결국 자연상수 e가 튀어나온다. 
그리고 이제 정말 마지막 정리이다.



이후 진행은 위에 링크한 위키피디아의 ‘지수 함수의 미분’항목을 참고하면 되겠다. 특이한 것은 그 내용의 마지막에
보면 지금까지 진행된 식의 𝑏라는 상수가 자연상수 𝑒일 경우, 즉 자연상수 e가 밑이 되는 지수 함수는 미분을 해도 그
도함수가 처음 형태와 동일하다는 것이며 아마도 이 성질이 매우 유용한 것 같다


정리


자연상수 e가 처음 발견된 것은 수학자 네이피어가 로그를 계산하는 과정에서라고 한다. 결국 어찌보면 자연상수보다
자연로그가 더 먼저 발견되었다고 할 수 있겠다. 어쨌든 지수함수를 미분하다보니 자연로그가 자연스럽게 도출이 되는
과정을 보았다. 적어도 자연로그와 자연상수 𝑒가 밑도 끝도 없이 뿅! 하고 태어난 것은 아니라는 것은 알 수 있게 
되었다.


이렇게 까지 해도 여전히 이해가 안가는 부분은 남아있지만 그 것은 아마도 실제로 자연로그와 자연상수 𝑒가 얼마나
유용하게 사용되는지 그 실례를 많이 접하지 못해서이리라. 하지만 그 건 어디까지나 이과충과 공돌이의 영역이므로
우리는 이정도 선에서 만족해도 될 것 같다.


도대체 텐서플로우 공부해보자고 시작해서 이게 웬 난리인지 모르겠다…ㅠ.ㅠ
이제부터는 다시 본연의 텐서플로우로 돌아가야겠다.
남들은 다 얼굴 이미지 분석하고 막 그러고 있던데…ㅠ.ㅠ

저작자 표시
신고


문돌이를 위한 자연상수 e와 자연로그의 이해

음…제목을 ‘이해’라고 달았지만…본인 스스로도 전혀 이해하고 있지 못한 이 슬픈 현실…ㅠ.ㅠ
일단 염두에 두어야 할 것은 자연상수 e의 존재 가치는 경험으로 얻을 수 밖에 없다는 것이다. 수많은 수학과 물리학 등
자연과학은 물론 공학에 이르기 까지 그 사용은 너무나 많다고들 한다. 하지만 문돌이의 처지에서 도대체 그 ‘많은’ 
것들이 대체 뭔가 하는데 이르면 그야말로 노답인 것이다. 그래서 오늘은 그저 딱 문돌이가 알면 도움이 될…까? 하는
선에서 자연상수 e와 그 자연상수 e를 밑으로 하는 자연로그에 대해 짚고 넘어가겠다.


자연상수 e에 대해

일단 네이피어에서 시작해 오트리드, 베르누이, 라이프니츠 그리고 오일러에 이르기까지 많은 수학자들이 자연상수 
e를 발견하고 사용을 했다. 하지만 마지막에 이 자연상수에 e라는 이름을 붙이고 체계적으로 정리한 사람은 바로
오일러(Euler)이다. 자연상수 e는 최초 발견자인 네이피어를 기리는 의미로 ‘네이피어 상수’라고도 하고 또 그냥
‘오일러의 수’라고도 하지만 가장 대표적인 이름은 오일러의 첫 이니셜을 딴 ‘자연상수 e’이다.


그런데 오늘 설명에서 가장 주목해야 할 사람은 네이피어도, 오일러도 아닌 바로 베르누이다.
베르누이는 복리식을 계산을 연구하다가 그 이자가 무한대의 횟수만큼 지급되는 경우를 계산해보았더니 그 수가
특별한 어떤 수에 가까워지는 것을 발견하였는데 이 수가 바로 자연상수 e였던 것이다.


원금 1원에 연 이율이 100%인 경우를 가정해보자. 1원을 저축하면 1년 후에는 원금 1원과 1원에 대한 이자(100%)
1원을 더해 2원을 받을 수 있다. 그렇다면 이자를 1년 꽉 채운 후 받지 말고 6개월 후에 한 번 그리고 1년 후에 한 번,
이렇게 나누어 받으면 어떨까? 그러면 연 이율이 100%니까 6개월후에 50% 이자를 받고 1년 후에 나머지 50%를
추가로 받게 되는 것이다. 그러면 6개월이 된 시점에서 원금 1월과 50%의 이자 0.5원이 생긴다. 그리고 또 6개월이
지나면 1원에 대한 이자 0.6원과 처음 6개월이 지난 시점에서 받은 이자 0.5원에 대한 이자 0.25원이 추가로 생긴다.
그러면 1년 후의 총 금액은 1 + 0.5 + 0.5 + 0.25 = 2.25로 1년에 1번 받을 때보다 0.25원을 더 많이 받게 된다.
바로 이런 식으로 이자를 받는 기간을 점점 짧게 자주 받을 경우를 계산해본 것이다. 아마도 주기가 짧아질수록 금액이
점점 더 커질 것을 예상했으리라


수식으로 보면 이렇다.


1년에 1번 이자 받기
1 + 1 * 1


1년에 2번 이자 받기
(1 + 1/2 * 1) + (1/2 * 1 + 1/2 * 1/2)
식을 간단하게 해보면
1 + 1/2 + 1/2 + 1/4


1년에 3번 이자 받기
(1 + (1/3 * 1)) + 
(1 + (1/3 * 1)) + (1/3 * 1 + 1/3 * 1/3) + 
(1 + (1/3 * 1)) + (1/3 * 1 + 1/3 * 1/3) + (1/3 * 1/3 + 1/3 * 1/3 * 1/3)
역시 식을 간단히 해보면
1 + 1/3 + 1/3 + 1/9 + 1/9 + 1/27


위 3개의 식을 다르게 표현해보면 다음과 같다(아무리 문돌이지만 아래 식을 풀어보면 위의 식과 같다는 정도는
알겠지…-.-)
1번 이자 받기 : 1 + 1 = (1 + 1/1)^1
2번 이자 받기 : (1 + 1/2) * (1 + 1/2) 
3번 이자 받기 : (1 + 1/3) * (1 + 1/3) * (1 + 1/3)


이 것을 일반화 시켜보면 (1 + 1/x)^x의 형태가 나온다. 그리고 이자 받는 횟수를 무한히 증가시켜보면 결국 
최종 식은 다음과 같다.


그리고 이 식의 값은 대략 2.71828…정도라고 한다.


사실 이런 과정은 다 필요 없고 다음의 성질만 기억해두면 될 것 같다.


  1. 자연상수 e는 증가에 대한 개념으로부터 출발한다(그래서 일부러 위의 식을 적었다).
  2. 어떤 수(1보다는 크지만 1에 극히 가까운 수)가 무한히 증가하더라도 이 식의 결과는 무한히 증가하지 않고 특정 값 (2.71828…)에 수렴한다.
  3. 마치 원주율의 𝜋와 같이 e 또한 상수이며 어떤 계산의 편의를 위해 사용된다(다만 𝜋의 경우 ‘원’이라는 구체적 도형으로부터 도출된 것이지만 그에 비해 e는 매우 추상적이어서 문돌이들의 접근이 쉽지 않은 것 같다).

물론 미적분과 관련하여 더 중요한 의미가 있는 것 같지만 우린 문돌이니까 적절한 선에서 멈추자…-.-


자연로그에 대해

사실 로그까지 오면 더 난감해진다. 역시나 그저 문돌이 수준에서 정리를 하고 넘어가자.
일단 로그는 지수(거듭제곱)와 관련된다. 간단하게 예를들면 1초에 2마리씩 분열하는 박테리아가 있다. 이 박테리아는
10초 후에 몇마리가 되었을까? 1초에 2마리, 2초에 4마리 3초에 8마리 4초에 16마리…10초 후에는 1024마리가 
된다. 이 것을 식으로 나타내면 다음과 같을 것이다.

2^10 = x

이 것을 역으로 물어볼 수도 있다. 이 박테리아가 1024마리가 되기 위해서는 몇초가 필요한가? 이 것은 식으로 
나타내면 다음과 같다.

2^x = 1024

이 식을 x를 기준으로 표현한 것이 바로 로그(log, logarithm)이며 다음과 같이 표현할 수 있다.


이 때 2를 밑, 1024를 진수라고 한다.


이러한 로그에는 특수한 케이스가 존재하는데 그 중 하나가 바로 밑이 10인 상용로그이다. 우리가 10진법을 주로
사용하기 때문에 매우 쉽고 유용하게 접근할 수 있다. 이러한 상용로그는 밑을 표시하지 않고 간단히 log100처럼
사용한다. 즉, 다음 두 식은 같은 것이다.


다른 하나가 바로 자연상수 e를 밑으로 하는 자연로그인데 아직 완전히 이해하지 못한 e를 밑으로 하는 만큼
자연로그 역시 아직은 그 가치를 잘 모르겠다…ㅠ.ㅠ 역시나 미적분과 밀접한 관련이 있다는 것만 어렴풋이 알 수
있을 뿐…


이 자연로그는 역시 밑을 생략하고 쓰기도 하는데 이미 사용 로그에서 log100과 같이 표현을 했기 때문에 같이
쓰지는 못하고 자연(nature)의 n을 따와서 ln으로 표시한다. 즉 아래의 두 식은 동일한 식이다.


그런데 고등 수학으로 가면 상용로그를 거의 안쓰기 대문에 log7.398…이 자연로그를 의미하는 경우가 더 많다고
한다.


정리

앞서 로지스틱 회귀에 대한 내용을 정리할 때도 말했지만 굳이 이런 부분까지 알아야 할까 하는 의문이 없진 않다.
이미 모든 것이 API화 되어있어 그 API만 호출하면 원하는 결과를 쉽게 출력할 수 있는데…하지만 비록 완벽하지는
못하다 할지라도 이러한 내용을 찾아가고 정리하는 과정에서 전체적인 흐름을 이해하는데 도움이 된 것 또한 분명하다.
텐서플로우를, 인공지능을 공부하는 내내 아마도 이런 어려운 수학들과 씨름을 하게 되겠지만 그 힘든 과정이 왠지
즐거울 것만 같은 묘한 느낌이 든다…^^ 


스스로도 이해하지 못하는 내용을 정리하다보니 정확하지 않은 지식을 남들에게 보여주어 피해를 입해는 것은 아닐까 
하는 두려움도 있으나 일단 이렇게라도 정리를 해두어야 나중에 더 깊이 공부할 때 도움이 될 것 같아 우선은 남겨둔다.
언젠가는 보다 정확한 지식을 기반으로 더 쉽게 정리해보겠다!!!

저작자 표시
신고


로지스틱 회귀 분석 이해를 위한 수학 지식


들어가는 말


앞서 단순 선형 회귀에서 다중 선형 회귀로 넘어갈 때도 단지 변수 하나가 더 추가되었다는 이유만으로 한참을
헤맸다. 그런데 로지스틱 회귀는… 나를 지옥으로 몰고갔다…ㅠ.ㅠ


이놈에 로지스틱 회귀 모형을 이해하기 위해 장장 2주간의 기간에 걸쳐 로지스틱 회귀, 자연상수 e, 자연로그,
로그의 일반 성질 등을 공부해야 했고 대략 열 서너 편의 유튜브 강좌와 구글과 네이버를 통해 수많은 자료를 
검색하여 대략적으로는 감을 잡았다. 하지만 여전히 답을 찾지 못한 내용들이 조금 있다.


사실 우리가 중고등학교 때 수학을 배운 것과 같은 방법으로 그저 공식 하나 외우고 그 공식을 사용하면 그뿐이다.
그리고 애초에 이 공부를 시작하면서 깊숙한 원리를 깨우친다기 보다는 텐서플로우라는 툴을 잘 사용할 수 있을
정도만 공부를 하자는 목표가 있었다(단지 그 목표만을 따르자면 sigmoid라는 API만 알면 된다).


하지만 예전에 공부를 할 때도 그랬거니와 지금 이 시점에도 왜 그리 그 ‘공식’이란 것이 어떤 의미인지가 궁금한지…
그걸 그냥 지나치자니 화장실에서 뒷처리 않하고 나온 듯 찜찜하기가 이를데 없다. 그래서 비록 수학과는 거리가 먼
천상 문돌이 이지만 한 번 도전해보기로 했다.


지금부터는 문돌이 식으로 해석한 로지스틱 회귀에 대한 설명이다. 위키피디아의 로지스틱 회귀 항목을 기준으로
문돌이의 관점에서 설명을 한 번 해보도록 하겠다.


로지스틱 회귀 모델 개요

우선 위키피디아에서 정의한 로지스틱 함수는 다음과 같다.


이 함수는 다른 말로 시그모이드(sigmoid) 함수라고 하며 그 모양이 S자와 유사한 성질이 있다.
지금부터 이 함수가 어떻게 도출되는지에 대해 알아볼 것이다.


이전까지 알아본 선형 회귀 분석의 경우 그 모델을 위해 만들어지 함수들은 모두 다음과 같은1차 함수들이었다. 

특히 다중 선형 회귀식은 벡터를 이용하여 다음과 같이 표현하기도 했다.



이 1차 함수가 표현하는 것은 독립 변수들(보통은 x로 표현)이 변화해 갈 때 종속 변수( 보통 y로 표현)가 어떻게 변화해
가는지를 알아보는 것이 목적이었다. 그리고 이 때 독립 변수 x와 종속 변수 y는 모두 그 범위가 음의 무한대(이하 -∞)에서 

양의 무한대(이하 ∞)까지로 제약이 없었다.


그런데…
때에 따라서는 연속적으로 변화하는 어떤 값들에 대해 두 개의 결과만을 확인할 필요가 있었다.
예를 들면 흡연량과 폐암 발병에 대한 관계를 보면 연속적으로 변화하는 흡연량에 대해 그 결과로 ‘폐암에 걸렸다’와
‘폐암에 걸리지 않았다’라는 두 개의 결과만이 필요한 경우 또는 소득 수준을 독립 변수로 했을 때 어느 정도 이상의
소득이 있으면 ‘투표를 했고’ 그 이하인 경우에는 ‘투표를 안했다’더라라는 식으로 말이다.


이러한 경우 수학적 처리를 위해 흔히 구하고자 하는 종속 변수의 값을 1로 놓고 그 반대의 경우를 0으로 놓아 y가
1또는 0의 값을 갖도록 모델을 만든다. 그리고 이러한 독립 변수를 한 건 한 건 검사를 하게 되면 종속 변수 y가 0 
또는 1의 값만을 표현하게 되지만 무수히 많은 독립 변수를 검사하게 되면 그 결과는 확률로 표현할 수 있게 된다.


즉, 하루에 담배 10개피를 피운 사람 a를 조사했더니 폐암에 걸렸다(1). 그런데 하루에 담배 10개피를 피운 사람을
10명 조사했더니 6명은 폐암에 걸렸으나 4명은 걸리지 않았다. 결국 10개피의 담배를 피운 사람은 60%의 확률로
폐암에 걸렸다고 말할 수 있는 것이다.


이처럼 조사 횟수가 많아지게 되면 종속 변수 y는 확률로 표현할 수 있게 되는 것이며 이럴 경우에도 종속 변수의
값은 0 ~ 1의 범위(확률상 0% ~ 100%)를 넘지 않는다. 


위키피디아의 로지스틱 회귀 항목 첫 줄에 설명되어있는 내용이 바로 이러한 의미이다.

“독립 변수의 선형 결합을 이용하여 사건의 발생 가능성을 예측하는데 사용되는 통계 기법이다.”


로지스틱 함수 도출

그런데 이 과정에서 문제가 있다. 독립 변수의 선형 식은 그 결과가 -∞에서 ∞까지 제약이 없는데 우리는 0과 1사이의
값만을 얻기를 원한다. 


다시 한 번 담배 이야기로 돌아가보자. 하루에 담배 5개피를 피는 사람을 기준으로 1의 값을 얻기 위해 기울기(W)는 
편의상 1로 놓고 b는 -4로 초기 설정을 해보자. 그러면 y = 1 X 5 -4로 y = 1의 결과를 얻을 수 있다. 하지만 담배
개비 수가 10개비로 늘어나면 y = 1 X 10 - 4가되어 y = 6으로 1을 훌쩍 넘어버린다. 기존의 선형 식으로는 이러한
조건을 절대 만족할 수 없는 것이다.


일단 선형 식이 0 ~ 1 사이의 값을 가질 수 없음이 분명해졌으니 그렇다면 종속 변수쪽을 -∞에서 ∞의 범위로 확장을
할 수는 없을까? 바로 이러한 발상에서 로지스틱 함수가 시작된다.


앞서 설명한 내용 중 종속 변수 y는 x에 대한 조사의 수가 증가하게 되면 확률값을 갖게 된다고 하였다. 사례를 1건만
조사하는 경우는 없으니 결국 y는 확률의 값을 갖게 될 것이다. 그렇다면 우리가 어떤 확률 p에 대해 -∞에서 ∞값을 
출력하는 새로운 함수를 만들면 이 함수는 곧 기존의 선형 함수와 같다고 할 수 있어 새로운 등식을 만들 수 있을 
것이다. 이 때 굳이 p에 대한 함수를 만들어야 하는 것은 우리가 최종적으로 만들어진 함수를 통해 확률 값(p)를 
얻어야 하기 때문이다.


이렇게 하기 위해 2가지 과정이 필요한데 그 것이 바로 odds 비라는 것과 log이다.


odds비는 쉽게 말해 실패 확률에 대한 성공 확률의 비율이다. 성공 확률을 p라고 한다면 실패 확률은 1-p가 된다.
이렇게 보았을 때 odds 비는 아래 식과 같이 표현할 수 있다.


p는 0에서 1사이의 값을 가지므로 위 식을 계산해보면 p가 가장 작은 0일 경우 0 / 1 - 0이 되어 0의 값을 갖게 되고
p가 가장 큰 1이 되는 경우 1 / 1-1 = 1 / 0이 되어 무한대가 된다(프로그래밍에서는 분모가 0인 경우 에러가 발생하나
수학에서는 극한의 개념을 도입하면 1/0(에 수렴)으로 생각할 수 있어 무한대로 정의한다).


이렇게 일단 odds 비를 적용하면 p에 대하여 0부터 양의 무한대까지를 범위로 갖는 새로운 함수를 생각할 수 있다.
하지만 최솟값이 0이므로 아직 음의 무한대를 만족하지 못한다. 이 음의 무한대를 범위에 포함시키기 위해 이번에는
log를 취하게 되는데 log 중에서도 자연상수 e를 밑으로 하는 자연로그를 취하게 된다(밑이 e인 로그를 특별히 ln
이라는 기호를 사용하여 표시한다).


오래 전에 배웠던 로그의 계산을 되새겨보자. 위 로그 식에서 밑은 e이고 진수는 앞서 보았던 odds비의 계산 식이다.
그리고 odds비의 계산 식은 0과 ∞의 범위를 갖는다. 이 점을 생각한다면 odds 비에 자연로그를 취한 값의 범위는 
다음과 같다고 할 수 있다.

계산을 해보면 일단 자연상수 e라는 값은 나중에 설명을 하겠지만 대략 2.71828…인 무리수이다. 이 무리수 e를
몇제곱 했을 때 0이 될까? 상식적으로 분수의 분모가 무한히 커질 때 그 분수는 0에 가까워진다는 것은 잘 알것이다.
즉 e를 x제곱했을 때 0이 나오는 x는 -∞가 되는 것이다(e^-∞란 1 / e^ ∞과 동일하므로). 한편 e를 x 제곱했을 때 
∞가 나오는 x는 바로 ∞란 것은 보다 쉽게 알 수 있을 것이다.


결국 odds 비를 구하고 거기에 자연로그를 취했더니 확률 p에 대해 -∞부터 ∞를 범위로 갖는 함수가 만들어졌다. 
그러면 이제 다음과 같이 표현할 수 있다.


하지만 우리의 최종 목적은 이 함수를 통해 확률 p를 찾는 것이므로 이 함수를 p에 대한 함수로 만들어야 한다.
다음과 같이 진행해보자.




정리

어찌어찌 로지스틱 회귀 함수 도출 과정을 살펴보았다. 위 내용을 간단하게 정리를 해보면 다음과 같다.

  1. 무한의 값을 갖는 선형 회귀 식을 통해 두 개의 값(0또는 1)만을 갖는 결과를 가져와야 한다.
  2. 두 개의 결과만을 도출하는 과정이 연속되면 확률이 되며 확률 또한 범위는 0부터 1까지로 제한된다.
  3. 0부터 1까지로 값이 제한된 확률 값의 범위를 0부터 ∞로 확장하기 위해 odds 비를 취한다.
  4. 0부터 ∞까지로 확장된 결과를 다시 -∞에서 ∞로 확장하기 위해 odds 비의 식에 자연로그를 취한다(이 과정을 logit 변환이라고도 한다).
  5. 이제 양쪽의 식이 모두 -∞에서 ∞까지의 값을 갖게 되므로 등식이 성립하고 이 등식을 변형하면 우리가 원하는 로지스틱 함수가 도출된다.


그런데 여전히 이해가 안가는 부분은 이 과정에서 등장하는 자연상수 e와 이 e를 밑으로 하는 자연로그의 역할이다.
일단 얼추 생각하기에는 자연상수 e가 성장에 관한 연구(베르누이의 복리 식)로부터 탄생했다는 것, 그리고 e가 갖는
미분상의 특징(이 부분은 아직 이해 불가…-.-) 때문에 자연상수를 사용한 것으로 보이는데…


암튼 자연상수 e는 문돌이와 이과충을 구분하는 가장 핵심적인 개념이라고 하니 사실 뭐 몰라도 흠될 것은 없지만
AI를 공부하고자 하는데 그러한 장벽을 두고 간다는 것도 내키지 않는 문제이다. 자연상수 e와 관련된 것은 앞으로도
계속 그 의미를 이해하도록 노력할 것이다. 그래서 다음 글에서는 불쌍한 문돌이들을 위해 간단하게나마 자연상수 e를
조금 정리해보도록 하고 오늘은 여기서 마치겠다.

저작자 표시
신고


TensorFlow 학습을 위한 간단한 Tip ~ #1

TensorFlow 공부를 시작하면서 몇가지 도구를 사용하게 되었다. 하지만 그 도구들에 대해 자세힌 알고있는
상태에서 시작을 한 것이 아니라 학습을 진행하면서 시행착오를 통해 알게되는 사실들이 꽤 많다.
이 그룹의 게시물들은 바로 이러한 팁을 정리하는 공간이 될 것이다.


첫 포스팅은 Docker와 Jupyter에 대한 몇가지 내용들을 다룰 것이다.


Docker에서 TensorFlow 관리하기

우선 처음 설치에 대한 내용과 1.0.0으로 업그레이드 하는 내용에서 Docker에 대한 약간의 내용들이 언급되었다.
오늘은 거기에 더해서 Container를 다루면서 실수했던 내용 몇가지를 정리한다.


처음 Docker를 통해 텐서플로우 이미지를 다운로드 받고 실행을 시키면 바로 Jupyter가 실행되면서 사용가능한
상태가 된다. 바로 그저께까지도 Docker의 container 사용 방법을 몰라 조금 미련한 짓을 하고 있엇다.


내가 분명 Jupyter상에서 ‘텐서플로우 첫걸음’의 예제를 업로드하여 학습을 하고 난 뒤 Jupyter를 종료시켰다가
다시 실행을 하면 이상하게도 내가 업로드한 내용들이 모두 사라지고 없었다. 처음 Jupyter를 실행했을 때의
상태로 초기화 되어있는 것이다.


이 것은 내가 Docker의 container에 대한 개념을 잘 몰랐기 때문에 발생한 문제였다.


일단 처음 이미지를 다운로드 받을 때 사용하는 명령어는 다음과 같다.

docker run -it -p 8888:8888 -p 6006:6006 gcr.io/tensorflow/tensorflow


이 명령어를 실행하면 이미지가 없는 경우에는 이미지를 다운로드 받은 후 container 하나를 생성하여 바로
Jupyter를 실행하고 이미지가 있는 경우에는 그 이미지를 이용하여 새로 container를 생성하여 Jupyter를 실행한다. 


나는 이 사실을 모르고 계속 Jupyter를 실행할 때마다 위의 명령어를 사용했던 것이다. 그러니 항상 새로운
container가 만들어진 것이고 이전에 작업했던 내용은 이전에 작업했던 container에 있을 뿐이나 당연히
초기화된 새로운 Notebook 목록이 열렸던 것이다.


따라서 이전에 했던 작업을 계속 진행하려고 한다면 당연히 이전에 작업했던 container를 실행시켜 진행해야
하는 것이다.


우선 콘솔에서 다음과 같이 입력하여 자기가 사용했던 컨테이너의 ID나 이름을 확인해두자

docker ps -a


그러면 다음과 같이 현재 생성된 container의 목록과 그 상태가 나온다. STATUS 항목에 Exited라고 표시된
container는 정지된 container이고 실행되고 있는 container들은 Up xx hours 처럼 표시된다.


이 container 목록 중 사용하고자 하는 것에 대해 start 또는 stop 명령을 통해 시작하거나 종료할 수 있게 되는
것이다. 그러니 run 명령은 최초 한 번만 실행하고 이후에는 start/stop을 사용하면 된다.

docker start trusting_booth
docker stop trusting_booth


이 때 주의할 것은 Jupyter가 실행되면 Jupyter에 로그인할 때 필요한 token이 생성되는데 이 token을
잘 기억하고 있어야 나중에 동일한 Jupyter 커널에 접속하여 사용할 수 있다. 만일 token이 올바르지 않다고
한다면 다음과 같이 진행하자


우선 아래와 같이 현재 실행 중인 container의 shell로 들어간다.

docker exec -it trusting_booth bash
root@cbec422fa6de:/notebooks#


여기서 다음과 같이 입력한다. 그러면 현재 실행 중인 Jupyter의 URL이 token과 함께 표시된다. 이 주소를 이용하여
접속하면 된다.

docker exec -it trusting_booth bash
root@cbec422fa6de:/notebooks# jupyter notebook list
Currently running servers:
http://localhost:8888/?token=2587d5ea8dd373c169e8042c4ab15dd956a2a0dc9a50bac9 :: /notebooks
root@cbec422fa6de:/notebooks# 


Jupyter에서 필요한 라이브러리 추가하기


사실 이 부분도 위에 설명한 Docker container의 사용 방법만 제대로 알고 있엇다면 실수를 하지 않았을 부분이다.
나는 다중 선형 회귀에서 사용하는 산점도 행렬을 그리기 위해 pandas라는 라이브러리를 추가하기 위해 작업을
진행했다.


우선 Jupyter에서 라이브러리를 추가하는 것은 매우 쉽다. 아무 Notebook이나 하나 실행해서 code cell에
다음과 같이 입력하고 실행하면 된다. 내가 pandas를 설치했으니 pandas를 설치하는 명려어를 쓴다.

!pip install pandas


그러면 다음과 같이 설치되는 모습이 보여진다.


이제는 현재 실행된 커널의 어떤 Notebook에서도 pandas를 import하여 사용할 수 있다.
처음에는 앞서 Docker에서의 실수로 매번 Jupyter를 실행할 때마서 라이브러리를 추가해주어야 하나 했는데
동일한 container를 사용한다면 그런 실수는 할 일이 없을 것이다.


Jupyter Notebook 활용하기

아직 사용한 지 얼마 되지 않지만 그 짧은 시간에 Jupyter가 얼마나 유용한 툴인지 깨닫고 있다. 바로 직전에
포스팅한 다중 선형 회귀에 대한 내용은 Jupyter Notebook에서 아래 메뉴를 실행하여 다운로드 받은 후
MacDown이라는 Markdown 편집기를 이용해 열어 그 preview를 그대로 복사해 붙여넣은 것이다.


File > Download as > Markdown (.md)


만일 바로 다른 Jupyter에서 실행 가능한 상태로 저장하고 싶다면 다음과 같이 선택하여 저장하면 된다.


File > Download as > Notebook (.ipynb)


어쩧게 저장하느냐에 따라 문서가 될 수도 있고 싫행 가능한 소스가 될 수도 있는 것이다.


참고로 나는 현재 다부분의 블로그를 작성할 때 Ulysses라는 편집기를 사용하고 있다. 이 글도 역시 Ulysses로
작성한 글이다. 그런데 이 Ulysses가 다수의 문서를 관리하고 publishing하는데는 매우 좋은데 일반적인
Markdown과 좀 다른 듯하다. 그래서 Jupyter에서 Markdown으로 저장한 문서를 열면 제대로 표현이 안되는
경우가 많다. 그래서 Jupyter에서 저장한 Markdown 문서는 무료 편집기인 MacDown을 사용한다.


꿩먹고 알먹고

사실 Docker도 그렇고 Python도 그렇고 앞으로 공부해야 할 목록에 들어있었는데 이렇게 TensorFlow 덕분에
함께 공부하게 되어 그야말로 일석이조다. 다만 간혹 오늘 적은 시행착오와 같은 문제로 불필요한 문제로 시간을
소모하게 되는 것은 불만스럽기도 하지만…앞으로도 새롭게 알게되는 내용들은 잘 정리를 해놓아야겠다.

저작자 표시
신고




텐서플로우를 이용한 다중 선형 회귀

단순 선형 회귀가 쉽기에 다중 선형 회귀도 쉬운 줄 알았다...하지만 결코 쉽지 않다...ㅠ.ㅠ 물론 독립변수만 늘어났을 뿐 대부분의 식을 그대로 사용해도 되므로 

그냥 그렇게만 알고 넘어가면 이보다 쉬운 것도 없다. 하지만 수학적 사고방식이 모자란 문돌이에게는 변수가 하나 늘어난다는 것은 천지가 개벽하는 변화다.


예를들어 단순 선형 회귀는 그 결과를 그래프를 통해서 시각적으로 쉽게 확인이 가능했다. 그런데 다중 선형 회귀는 도대체 시각적으로 어떻게 표현해야 할지를 모르겠다. 

그나마 독립변수가 2개인 경우는 3차원그래프로 설명을 해놓은 곳이 많아 그러려니 했는데 3개 이상부터는 도대체 어찌 할 수 있는지 할 수는 있는 것인지...ㅠ.ㅠ


일단은 cost 함수로 성공 여부를 가늠하면서 한 번 진행해보기로 했다.


일단 사용한 데이터는 아래 문서의 16쪽에 있는 예제의 데이터이다.

 https://ita.kaist.ac.kr/data/board/ITAMATLAB09_02.pdf


단순 선형 회귀에서 확장

# '텐서플로우 첫걸음'의 예제에서 구현된 변수. 여기서는 직접 회귀 분석에 사용되지는 않고 산점도 행렬을 그리는데만 사용됨
# num_points = 1000
vectors_set = [[2.0, 3.0, 3.2],[1.3, 1.1, 3.0],[2.4, 3.5, 3.6],[1.5, 2.5, 2.6],[0.6, 1.9, 0.6],[2.0, 2.8, 3.5],
               [1.0, 1.3, 2.1], [2.0, 3.3, 3.4], [1.3, 2.0, 2.8], [0.9, 1.0, 2.3]
               ]

#vectors_set2 = [[36.2, 206.2, 1.0, 32.0],[39.0, 218.6, 4.0, 39.0],[81.7, 264.6, 4.0, 41.0],[39.0, 330.5, 2.0, 33.0],
#               [68.3, 334.7, 3.0, 37.0],[106.3, 365.6, 4.0, 31.0],[123.9, 379.3, 5.0, 35.0],[114.8, 456.4, 6.0, 29.0], 
#               [97.0, 502.7, 3.0, 27.0], [100.2, 531.0, 7.0, 36.0]
#               ]

최초에 "텐서플로우 첫걸음"의 예제 노트북으로 시작을 했기에 코드역시 기본적으로는 예제와 동일한 구조다.

y_data = [2.0, 1.3, 2.4, 1.5, 0.6, 2.0, 1.0, 2.0, 1.3, 0.9]
x1_data = [3.0, 1.1, 3.5, 2.5, 1.9, 2.8, 1.3, 3.3, 2.0, 1.0]
x2_data = [3.2, 3.0, 3.6, 2.6, 0.6, 3.5, 2.1, 3.4, 2.8, 2.3]
#x3_data = [v[3] for v in vectors_set]

참고 : 위에서 기존에 사용하던 vectors_set이라는 변수를 그대로 살려놓았는데 그 이유는 다중 회귀 분석을 시각화 하는 방법 중 산점도 행렬을 그리기 위해서다. 

다중 회귀를 구성하는 각 변수들의 관계를 볼 수 있는 산점도 행렬을 만들 수 있도록 데이터를 구조화 해주는 pandas라는 라이브러리를 찾아서 사용해 보았는데. 

이 pandas에서 데이터를 만들 때 조금 더 편하게 사용할 수 있다. 아래 코드를 수행했을 때 처음 나오는 그래프가 바로 산점도 행렬이다.

import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame(vectors_set)

pd.tools.plotting.scatter_matrix(df)
plt.tight_layout()
plt.show()

plt.plot(x1_data, y_data, 'ro')
plt.show()
plt.plot(x2_data, y_data, 'b+')
plt.show()
#plt.plot(x3_data, y_data, 'cs')
#plt.show()


그리고 다음과 같이 가설 함수를 만든다. 다중 회귀 분석의 가설함수는 단순 회귀 분석의 가설함수에서 추가되는 독립 변수에 대해 W * X를 추가로 더해주면 된다. 

즉 기존 단순 선형 회귀의 가설 함수가 y = W * x + b였다면 독립변수가 2개인 다중 회귀의 가설함수는 y = W1 * x1 + W2 * x2 + b의 형식이 되는 것이다. 이런 식으로 

독립변수가 늘어남에 따라 가설함수는 다음과 같이 표현할 수 있다.


위 예제 데이터는 독립변수가 2개이기 때문에 아래와 같이 가설 함수를 만들 수 있다.

import tensorflow as tf

W1 = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
W2 = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
#W3 = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
#b = tf.Variable(tf.random_uniform([1], 88.0, 89.0))
b = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
y = W1 * x1_data + W2 * x2_data + b

가설 함수 단순화 하기

위와 같은 진행은 다중 선형 회귀 가설 함수의 일반 공식에서 보는 바와 같이 독립변수가 늘어나는 만큼 공식이 한도 끝도 없이 늘어날 수가 있다. 그래서 이 공식을 조금 더 

간단하게 표현하기 위해 행렬의 곱을 이용하여 표현할 수 있다. 이 행렬 곱의 성질을 이용하면 b까지도 간단하게 정리할 수 있다. 아래 공식과 같이 표현이 가능한 것이다.

독립변수가 2개인 경우


독립변수가 3개인 경우


b까지 포함시킨 경우


위 방법을 이용하여 가설함수를 다시 구성하면 다음과 같이 간단하게 정리할 수 있게된다. 먼저 두 개의 독립변수를 하나의 벡터로 만들어보자

x_data = [[3.0, 1.1, 3.5, 2.5, 1.9, 2.8, 1.3, 3.3, 2.0, 1.0],
          [3.2, 3.0, 3.6, 2.6, 0.6, 3.5, 2.1, 3.4, 2.8, 2.3]]
y_data = [2.0, 1.3, 2.4, 1.5, 0.6, 2.0, 1.0, 2.0, 1.3, 0.9]

x1_data, x2_data 두개의 변수가 x_data 하나의 변수에 모두 포함되었다. y_data는 그냥 사용하면 된다. 다음은 W1과 W2도 하나로 합쳐보자

W = tf.Variable(tf.random_uniform([1, 2], -1.0, 1.0))
b = tf.Variable(tf.random_uniform([1], -1.0, 1.0))

바뀐 부분은 tf.random_uniform의 첫번째 파라미터가  [1]에서 [1, 2]로 바뀐 것이다. 즉, 하나의 행만 가지고 있던 배열 형태에서 1행 2열 형태의 벡터로 바꾸었다. 

아직까지는 b는 그대로 사용을 하자.


이제 행렬 곱셈을 해보자. 텐서플로우에서 행렬 곱셈을 해주는 함수는 matmul이다.

y = tf.matmul(W, x_data) + b

y = W1 * x1_data + W2 * x2_data + b라는 긴 공식이 y = tf.matmul(W, x_data) + b로 짧아졌다. 새삼 수학적 사고의 대단함을 느끼는 순간이었다.

b까지 단순화 시키려면 다음과 같이 하면 된다.

import tensorflow as tf

x_data = [[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
          [3.0, 1.1, 3.5, 2.5, 1.9, 2.8, 1.3, 3.3, 2.0, 1.0],
          [3.2, 3.0, 3.6, 2.6, 0.6, 3.5, 2.1, 3.4, 2.8, 2.3]]
y_data = [2.0, 1.3, 2.4, 1.5, 0.6, 2.0, 1.0, 2.0, 1.3, 0.9]

W = tf.Variable(tf.random_uniform([1, 3], -1.0, 1.0))
y = tf.matmul(W, x_data)

정말 단순해졌다.

이후 진행은 단순회귀 분석과 동일하다. loss 함수를 정의하고 최적화 방법은 학습속도 0.01인 그래디언트 디센트를 사용한다.

loss = tf.reduce_mean(tf.square(y - y_data))
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)

그리고 모든 변수를 초기화하고 세션을 시작한다.

init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

일단 10000번 정도 반복을 하면서 1000번 째마다 현재 step과 W와 loss값을 출력하여 보았다. 행렬 곱을 이용하여 식을 단순화 하기 전에는 W1, W2, b의 값이 

각각 별개의 배열로 출력되었으나 식을 단순화 한 후에는 W가 하나의 벡터로 전체 값을 모두 표시해준다. 

for step in range(10000):
    sess.run(train)
    if step % 1000 == 0 :
        print(step, sess.run(W), sess.run(loss))
(0, array([[ 0.77657259,  0.304876  ,  0.05037546]], dtype=float32), 0.10678037)
(1000, array([[-0.0653717 ,  0.31436804,  0.32436851]], dtype=float32), 0.01154841)
(2000, array([[-0.24788393,  0.33180454,  0.3721334 ]], dtype=float32), 0.0072818799)
(3000, array([[-0.28713715,  0.33556959,  0.38239372]], dtype=float32), 0.0070845596)
(4000, array([[-0.29557922,  0.3363795 ,  0.38460028]], dtype=float32), 0.0070754252)
(5000, array([[-0.29739448,  0.33655375,  0.38507465]], dtype=float32), 0.0070749982)
(6000, array([[-0.2977854 ,  0.33659121,  0.38517687]], dtype=float32), 0.0070749833)
(7000, array([[-0.29786775,  0.33659837,  0.38519898]], dtype=float32), 0.007074981)
(8000, array([[-0.29788083,  0.33659986,  0.38520217]], dtype=float32), 0.0070749745)
(9000, array([[-0.29788083,  0.33659986,  0.38520217]], dtype=float32), 0.0070749745)

일단 결과는 위와 같이 나왔는데 이 결과는 비용함수의 결과가 상당히 낮은 값임에도 불구하고 예제 데이터가 있던 문서에서 정리한 결과 값과 상당한 차이를 보인다. 

왜 차이가 나는지 어떻게 이 차이를 줄이도록 조정할 수 있는지는 아직 잘 모르겠다.


문서 상의 결과값 : y(x1,x2) = -0.4503 + 0.3067 * x1 + 0.4589 * x2


정리

이번 포스팅 내용울 3줄 정리해보도록 하자.

  1. 다중 선형 회귀는 단순 선형 회귀에서 독립변수의 수만 늘어난 형태이다.
  2. 가설함수는 y = W1 * x1 + W2 * x2 + ... Wn * xn + b의 형태이다.
  3. 비용함수는 단순 회귀 분석과 동일하다.

적절한 값을 구한다는 목적만 생각한다면 달리 어려운 부분은 없다. 하지만 처음 단순 선형 회귀를 정리할 때 그래프를 보면서 변화를 비교했던 것이 이해를 돕는데 

도움이 되었기에 다중 선형 회귀에서도 그런 방법이 없을까 생각했는데 독립변수 2개까지는 3차원 그래프로 표현을 하는 내용들이 간혹 보였지만 독립변수가 3개 이상인 

경우에는 그래프로 표시 하는 내용을 전혀 찾을 수가 없었다. 일단 크게 중요한 부분은 아니기에 호기심을 누르고 그냥 넘어가도록 하자. 


전체 글은 Jupyter Notebook에서 작성되었으며 전체 Notebook 파일도 아래 첨부한다.

chapter2_multi_regression.py.ipynb


저작자 표시
신고

+ Recent posts

티스토리 툴바