전자세금계산서 엑셀 양식 작성 프로그램

ERP 시스템(전사적 자원 관리 시스템)을 통해 다운로드한 엑셀 파일을 가지고 전자세금계산서 발급을 위한 엑셀 양식 파일을 채우는 프로그램을 만들기로 했다. 기존에 사람이 직접 전자세금계산서 발급을 위해서 엑셀 양식을 채우는 과정에서 빈번한 실수가 발생하여 이를 방지하기 위해 프로그램을 개발할 필요성이 있었다.

따라서, 기존에 회사에서 발급 양식을 채우던 방식 그대로 꼭 필수 항목이 아니더라도 양식을 채우기로 했다.

 

고려사항
  • 내가 받은 ERP 시스템에서 다운로드한 엑셀 파일에는 이메일 주소가 나와있지 않음
  • 최대 1000건의 엑셀 데이터를 다루어야함
  • 프로그래밍을 할 줄 모르는 사용자 고려 (프로그램을 실행하는 과정을 최대한 단순하게)
  • openpyxl 라이브러리는 xls 확장자를 지원하지 않음
  • 전자세금계산서 발급 양식은 어떤 경우에도 변경하지 않음
해결방안
  • 별도의 이메일 주소가 담긴 엑셀 파일을 준비
  • 파이썬의 openpyxl 라이브러리 사용
  • 쉽게 프로그램을 사용할 수 있도록 별도의 웹서버 운영 (진행 중)
  • ERP 시스템으로 xlsx 파일을 받을 수 있는지 확인, xlsx 파일로 전자세금계산서 발행이 가능한지 확인 (진행 중)
  • 전자세금계산서 발급 양식은 불변하므로 별도의 머릿글 행(열 이름)을 처리하지 않음
사용 언어
  • Python
데이터

프로그램 테스트를 위해 사용한 데이터는 다음과 같다.

erp.xlsx
email.xlsx
origin.xlsx
origin.xlsx
0.08MB
email.xlsx
0.01MB
erp.xlsx
0.03MB

 

코드
#!/usr/bin/env python
# coding: utf-8
import openpyxl
import os
import datetime

erpWB = openpyxl.load_workbook('data/erp.xlsx')
emailWB =  openpyxl.load_workbook('data/email.xlsx')
taxWB = openpyxl.load_workbook('data/origin.xlsx')

erpWS = erpWB[erpWB.sheetnames[0]]
emailWS = emailWB.active
taxWS = taxWB[taxWB.sheetnames[0]]

# 이메일 사업자 번호를 키로 해쉬테이블
emailAddress = dict()
for i in range(2,emailWS.max_row+1):
    emailAddress[emailWS[i][0].value] = emailWS[i][1].value
    print(emailWS[i][0].value,emailAddress[emailWS[i][0].value])

# erp 수금 기록 열 이름
# 사업자번호, 법인명, 대표자, 업태, 종목, 사업장주소, 공급가액, 세액 사용
erpDic = {'사업자번호':0,'법인명':1,'대표자':2,'업태':3,'종목':4,'사업장 주소':5,'공급가액':6,'세액':7}
erpHash = dict() #열 이름을 키값으로 갖는 셀 위치
erpName = []
for c in range (0,erpWS.max_column):
    erpHash[erpWS[2][c].value] = erpWS[2][c].coordinate.strip('0123456789')
    print(erpWS[2][c].value,erpHash[erpWS[2][c].value])

erpVal = []
for i in range (3,erpWS.max_row+1):
    temp = []
    if erpWS[erpHash['사업자번호']+str(i)].value!=None and erpWS[erpHash['수금액']+str(i)].value!=None: #사업자명이나 수금액이 없다면 저장하지 않음
        for j in erpDic:
            if j=='대표자': #대표자 이름 전처리
                temp.append(erpWS[erpHash[j]+str(i)].value.strip(' '))
            else:
                temp.append(erpWS[erpHash[j]+str(i)].value)
        if temp[0] in emailAddress.keys(): #저장된 이메일 주소가 없을 경우 None
            temp.append(emailAddress[temp[0]])
        else:
            temp.append(None)
        erpVal.append(temp)
        print(temp)

# 전자세금계산서 저장을 위한 이차원 배열
rowVal = []
dt = datetime.datetime.now();
for i in range (len(erpVal)):
    temp = []
    for j in range (taxWS.max_column):
        if j==0:
            temp.append('01')
        elif j==1:
            temp.append(dt.strftime("%Y%m%d"))
        elif j==2:
            temp.append(erpVal[i][erpDic['사업자번호']])
        elif j==4:
            temp.append(erpVal[i][erpDic['법인명']])
        elif j==5:
            temp.append(erpVal[i][erpDic['대표자']])
        elif j==6:
            temp.append(erpVal[i][erpDic['사업장 주소']])
        elif j==7:
            temp.append(erpVal[i][erpDic['업태']])
        elif j==8:
            temp.append(erpVal[i][erpDic['종목']])
        elif j==9:
            temp.append(erpVal[i][8]) #이메일
        elif j==11:
            temp.append(erpVal[i][erpDic['공급가액']])
        elif j==12:
            temp.append(erpVal[i][erpDic['세액']])
        elif j==14:
            temp.append(dt.strftime("%d"))
        elif j==15:
            temp.append(dt.strftime("%m")+'월 CCTV용역료')
        elif j==19:
            temp.append(erpVal[i][erpDic['공급가액']])
        elif j==20:
            temp.append(erpVal[i][erpDic['세액']])
        elif j==50:
            temp.append('01') #영수01 청구02
        else:
            temp.append('')
    rowVal.append(temp)
    print(temp)

for i in range (len(rowVal)):
    for j in range (taxWS.max_column):
        taxWS.cell(i+7,j+1,rowVal[i][j])
        print(rowVal[i][j])

taxWB.save('data/test.xlsx')

erpWB.close()
emailWB.close()
taxWB.close()

 

주피터 노트북으로 실행
taxInvoice-Copy1
In [1]:
from IPython.core.display import display, HTML
display(HTML("<style>.container {width:90% !important;}</style>"))
In [2]:
import openpyxl
import os
import datetime
In [3]:
erpWB = openpyxl.load_workbook('data/erp.xlsx')
emailWB =  openpyxl.load_workbook('data/email.xlsx')
taxWB = openpyxl.load_workbook('data/origin.xlsx')
In [4]:
erpWS = erpWB[erpWB.sheetnames[0]]
emailWS = emailWB.active
taxWS = taxWB[taxWB.sheetnames[0]]
In [5]:
emailWS[8][0].value
In [6]:
# 이메일 사업자 번호를 키로 해쉬테이블
emailAddress = dict()
for i in range(2,emailWS.max_row+1):
    emailAddress[emailWS[i][0].value] = emailWS[i][1].value
    print(emailWS[i][0].value,emailAddress[emailWS[i][0].value])
1111111111 java@java.com
2222222222 ccc@ccc.com
3333333333 data@base.com
4444444444 python@python.com
5555555555 ruby@ruby.com
6666666666 perl@cperl.com
None None
In [7]:
# erp 수금 기록 열 이름
# 사업자번호, 법인명, 대표자, 업태, 종목, 사업장주소, 공급가액, 세액 사용
erpDic = {'사업자번호':0,'법인명':1,'대표자':2,'업태':3,'종목':4,'사업장 주소':5,'공급가액':6,'세액':7}
erpHash = dict() #열 이름을 키값으로 갖는 셀 위치
erpName = []
for c in range (0,erpWS.max_column):
    erpHash[erpWS[2][c].value] = erpWS[2][c].coordinate.strip('0123456789')
    print(erpWS[2][c].value,erpHash[erpWS[2][c].value])
상호 A
미수 B
수금액 C
미수금 D
수금일자 E
입금수방 F
미수금사유 G
메모 H
사업자번호 I
법인명 J
대표자 K
주민번호 L
업태 M
종목 N
사업장 주소 O
실발행일자 P
입력자사번 Q
우편물(우) R
우편물 주소 S
(전)발급코드 T
(전)발급일자 U
(전)신고일자 V
(전)사유 W
관리담당 X
지역명 Y
선후납 Z
비고 AA
가입자번호 AB
지출증빙 AC
핸드폰(기초정보) AD
공급가액 AE
세액 AF
In [8]:
erpVal = []
for i in range (3,erpWS.max_row+1):
    temp = []
    if erpWS[erpHash['사업자번호']+str(i)].value!=None and erpWS[erpHash['수금액']+str(i)].value!=None: #사업자명이나 수금액이 없다면 저장하지 않음
        for j in erpDic:
            if j=='대표자': #대표자 이름 전처리
                temp.append(erpWS[erpHash[j]+str(i)].value.strip(' '))
            else:
                temp.append(erpWS[erpHash[j]+str(i)].value)
        if temp[0] in emailAddress.keys(): #저장된 이메일 주소가 없을 경우 None
            temp.append(emailAddress[temp[0]])
        else:
            temp.append(None)
        erpVal.append(temp)
        print(temp)
[1111111111, '자바', '제임스', '음식점업', '카페', '서울특별시 서초구 자바빌딩', 29000, 2900, 'java@java.com']
[2222222222, '씨', '벨연구소', '도.소매', '컴퓨터판매', '인천광역시 남구 씨대로 99', 35000, 3500, 'ccc@ccc.com']
[3333333333, '데이터', '디비', '도.소매', '공장', '서울특별시 강남구 데이터대로 베이스빌딩', 30000, 3000, 'data@base.com']
[4444444444, '파이썬', '귀도', '도.소매', '꽃집', '서울특별시 강남구 파이썬로 29', 60000, 6000, 'python@python.com']
[5555555555, '루비', '마츠모토', '도.소매', '보석상', '경기도 시흥시 루비상가', 20000, 2000, 'ruby@ruby.com']
[6666666666, '펄', '래리', '음식점업', '횟집', '서울특별시 종로구 펄길', 35000, 3500, 'perl@cperl.com']
In [9]:
# 전자세금계산서 저장을 위한 이차원 배열
rowVal = []
dt = datetime.datetime.now();
for i in range (len(erpVal)):
    temp = []
    for j in range (taxWS.max_column):
        if j==0:
            temp.append('01')
        elif j==1:
            temp.append(dt.strftime("%Y%m%d"))
        elif j==2:
            temp.append(erpVal[i][erpDic['사업자번호']])
        elif j==4:
            temp.append(erpVal[i][erpDic['법인명']])
        elif j==5:
            temp.append(erpVal[i][erpDic['대표자']])
        elif j==6:
            temp.append(erpVal[i][erpDic['사업장 주소']])
        elif j==7:
            temp.append(erpVal[i][erpDic['업태']])
        elif j==8:
            temp.append(erpVal[i][erpDic['종목']])
        elif j==9:
            temp.append(erpVal[i][8]) #이메일
        elif j==11:
            temp.append(erpVal[i][erpDic['공급가액']])
        elif j==12:
            temp.append(erpVal[i][erpDic['세액']])
        elif j==14:
            temp.append(dt.strftime("%d"))
        elif j==15:
            temp.append(dt.strftime("%m")+'월 CCTV용역료')
        elif j==19:
            temp.append(erpVal[i][erpDic['공급가액']])
        elif j==20:
            temp.append(erpVal[i][erpDic['세액']])
        elif j==50:
            temp.append('01') #영수01 청구02
        else:
            temp.append('')
    rowVal.append(temp)
    print(temp)
['01', '20191204', 1111111111, '', '자바', '제임스', '서울특별시 서초구 자바빌딩', '음식점업', '카페', 'java@java.com', '', 29000, 2900, '', '04', '12월 CCTV용역료', '', '', '', 29000, 2900, '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '01']
['01', '20191204', 2222222222, '', '씨', '벨연구소', '인천광역시 남구 씨대로 99', '도.소매', '컴퓨터판매', 'ccc@ccc.com', '', 35000, 3500, '', '04', '12월 CCTV용역료', '', '', '', 35000, 3500, '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '01']
['01', '20191204', 3333333333, '', '데이터', '디비', '서울특별시 강남구 데이터대로 베이스빌딩', '도.소매', '공장', 'data@base.com', '', 30000, 3000, '', '04', '12월 CCTV용역료', '', '', '', 30000, 3000, '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '01']
['01', '20191204', 4444444444, '', '파이썬', '귀도', '서울특별시 강남구 파이썬로 29', '도.소매', '꽃집', 'python@python.com', '', 60000, 6000, '', '04', '12월 CCTV용역료', '', '', '', 60000, 6000, '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '01']
['01', '20191204', 5555555555, '', '루비', '마츠모토', '경기도 시흥시 루비상가', '도.소매', '보석상', 'ruby@ruby.com', '', 20000, 2000, '', '04', '12월 CCTV용역료', '', '', '', 20000, 2000, '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '01']
['01', '20191204', 6666666666, '', '펄', '래리', '서울특별시 종로구 펄길', '음식점업', '횟집', 'perl@cperl.com', '', 35000, 3500, '', '04', '12월 CCTV용역료', '', '', '', 35000, 3500, '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '01']
In [10]:
for i in range (len(rowVal)):
    for j in range (taxWS.max_column):
        taxWS.cell(i+7,j+1,rowVal[i][j])
        print(rowVal[i][j])
01
20191204
1111111111

자바
제임스
서울특별시 서초구 자바빌딩
음식점업
카페
java@java.com

29000
2900

04
12월 CCTV용역료



29000
2900





























01
01
20191204
2222222222

씨
벨연구소
인천광역시 남구 씨대로 99
도.소매
컴퓨터판매
ccc@ccc.com

35000
3500

04
12월 CCTV용역료



35000
3500





























01
01
20191204
3333333333

데이터
디비
서울특별시 강남구 데이터대로 베이스빌딩
도.소매
공장
data@base.com

30000
3000

04
12월 CCTV용역료



30000
3000





























01
01
20191204
4444444444

파이썬
귀도
서울특별시 강남구 파이썬로 29
도.소매
꽃집
python@python.com

60000
6000

04
12월 CCTV용역료



60000
6000





























01
01
20191204
5555555555

루비
마츠모토
경기도 시흥시 루비상가
도.소매
보석상
ruby@ruby.com

20000
2000

04
12월 CCTV용역료



20000
2000





























01
01
20191204
6666666666

펄
래리
서울특별시 종로구 펄길
음식점업
횟집
perl@cperl.com

35000
3500

04
12월 CCTV용역료



35000
3500





























01
In [11]:
taxWB.save('data/test.xlsx')
In [12]:
erpWB.close()
emailWB.close()
taxWB.close()

 

결과

프로그램 실행 결과는 다음과 같다.

test.xlsx
test.xlsx
0.08MB

개선사항
  • 최대한 확장성을 고려해 다른 회사에서도 사용할 수 있게 만들고 싶었으나, 머릿글 행(열 이름)의 처리 문제
  • 전자세금계산서 발급을 위해서는 최대 1000건의 데이터만 입력해야 하는데 이를 위한 처리를 따로 해주어야 함
  • 결과 파일의 배경 색상이 바뀌는 것의 원인을 확인하고 해결 해야함

행복한 프로그래밍

컴퓨터 분야에서 전문 지식에 대한 글이 아닌 개발자의 이야기를 다룬 글이 읽고 싶어 한동안 서점을 찾아다녔었다. 개발자와 기획자의 갈등을 다룬 글은 종종 찾아볼 수 있었는데 아쉽게도 그건 내가 원하던 주제가 아니었다.(하지만 갈등이라니 언제나 흥미로운 소재 아닌가 나중에 읽어봐야겠다.) 이 책을 읽기 전에 '나는 프로그래머다'라는 책을 먼저 접했다. '나는 프로그래머다'의 첫 번째 글을 쓴 작가가 바로 '행복한 프로그래밍'의 저자 임백준이다. (사실 읽다가 루슨트 테크놀로지스 사의 이야기가 겹쳐서 그제야 동일 인물임을 깨달았다.) '나는 프로그래머다' 속의 열정에 불타올라 도전하는 개발자의 이야기에 나와 비교하기 시작하고 위축되어 잠시 글 읽기를 멈추었다. 그에 반해 '행복한 프로그래밍'은 훨씬 가벼운 마음으로 글을 읽을 수 있었다.

 

본인의 개인적인 경험뿐만 아니라 컴퓨터 역사에서 있었던 일들까지 머리 아프지 않게 흥미롭게 풀어놓는 작가의 글솜씨에 즐겁게 책 한권을 읽을 수 있었다. 책 중간중간에는 알고리즘 문제들이 끼어있는데, 책장을 넘기던 손을 잠시 멈추고 생각하기에 좋았다. 책의 초반에 나온 통나무 건너기 문제에서 내가 생각한 답을 콕 짚으며, 개발자에게는 본인이 생각한 답이 정말 최선인지 생각하는 자세가 필요하다고 했을 땐 그동안 실행화면에 원하는 답을 출력해내는 것에 급급했던 내 자세에 대해 생각하게 되었다. 

 

사실 알고리즘 문제를 풀기 위해서 무식한 방법(Brutal Method)을 사용하는 것은 흔하다. 문제를 풀기 위한 규칙이 바로 눈에 들어오지 않을 때, 무식하게 먼저 풀다보면 거기서 규칙을 발견하는 일이 흔하기 때문이다. 마치 수학 지문에서 수열을 이용한 탑 쌓기 문제를 풀 때, 그림으로 탑을 쌓아가다가 규칙을 발견하는 것과 같은 이치다. 하지만 무식한 방법을 통해 찾은 규칙과 원리가 가장 좋은 해결 방법인가? 아닐 가능성이 적지 않다. 여태 알고리즘 문제를 풀면서 화면에 '맞았습니다.'라는 결과를 보고 나면 뒤도 돌아보지 않고 다음 문제로 넘어가던 태도를 고치고 '코드를 더 깔끔하게, 더 빠르게, 더 효율적으로 작동하는 방법이 있을 것이다.'라는 생각으로 내 코드를 한 번 더 들여다보는 습관을 가지게 되었다. 

 

너무도 유명한 백준 온라인 저지에서 알고리즘 문제를 풀면(이 책의 저자인 임백준과는 관계 없다. 이 사이트의 대표자는 최백준이다.) 같은 언어를 이용해서 문제를 푼 사람들의 코드 길이와 사용한 메모리와 소요 시간을 볼 수 있다. 내 코드보다 짧고 적은 메모리로 빠르게 푼 코드를 열어 보고 내 코드와 바로 비교할 수 있다면 좋겠지만, 코드를 공개해 놓은 사람은 그리 많지 않아 답은 모른 채로 고민만 하는 시간을 종종 갖는다. 이러한 시간을 갖는 것이 쉽지는 않다. '맞았습니다'보다 '틀렸습니다'를 훨씬 더 많이 보게 되는 풀이 과정에서 지쳐버려 더 이상 문제를 보고 싶지 않기 때문이다. 그렇지만 내 코드의 메모리와 속도를 남의 코드와 비교해 보는 시간은 꼭 갖는다. 남과 비교해 눈에 띄게 좋지 않은 코드일 경우 부끄럽다. 이러한 부끄러움은 다음에 문제를 풀 때, 한 번 더 생각하는 원동력이 된다.

 

이 책에서 가장 공감한 부분은 문제를 해결할 때 갖는 희열 때문에 개발자가 된다는 것이었다. 처음 c언어를 배우면서 "Hello World!"를 보고 시시했다. 하지만 피라미드 모양으로 별찍기를 완성해 검은 화면에 하얀 *이 원하는 모양으로 찍혔을 때, 리스트를 직접 구현하기 위해서 온갖 쓸데없는 예외 처리를 끼워 넣다가 더 많은 버그를 만들어 내다가 겨우 완성했을 때, 이미지를 화면에 띄워 마우스 클릭 포인트를 목적지로 이미지가 이동하게 만들었을 때의 희열은 아직도 생생하다. 이제 별찍기와 같은 문제는 시시해진 지 오래지만, 점점 더 어려운 문제를 접하고 문제를 해결했을 때의 희열에 이제는 벗어나기 힘들 정도로 빠져버렸다.

 

"현실로부터 도피하는 수단 중에서 프로그래밍이야말로 으뜸이다. 그것은 점점 더 중독성을 띠면서 환상의 세계가 된다" 혹은 "이 세계는 자신이 창조해낸 법칙에 따라 움직이도록 하는 힘을 갖고 있기 때문에, 이를 성취하면 프로그래머들은 승리감을 만끽한다"가 되는 식이다. 요컨데 프로그래머들은 자신이 속한 비트의 세계에서 승리를 쟁취하기 위해서 혼신의 힘으로 전진하는 가상 세계의 전사들인 것이다.
- 행복한 프로그래밍 P.35

 

가벼운 마음으로 읽기 시작한 책에서 많은 공감과 새로운 관점을 얻을 수 있었다. 내가 나아가고자 하는 길이 이렇게나 흥미진진한 세계임을 재미있게 풀어주어 저자의 다른 책이 기대된다.

 

밀리의 서재

지금 신청 시 첫달무료! 국내 최대 3만권 월정액 독서앱

www.millie.co.kr

아이패드를 사고 난 뒤 널찍한 화면이 독서에 딱 알맞겠다는 생각이 들던 차에 '밀리의 서재'라는 어플을 알게 되었다. 첫 달 무료 체험 이후 월 정액을 결제하였는데 무료 체험 당시에 제대로 알아보지 않아 아쉬운 몇 가지 정보를 공유하고자 한다.

 

독서 검색

구글 검색을 통해 홈페이지에 들어가 보니 어플 홍보 이미지만 보여서 그냥 바로 어플을 다운로드하였다. 어플에 둘러보기 기능이 있을 거라고 생각했기 때문인데, 어플에서는 바로 구독을 시작하지 않으면 안의 서비스를 확인할 수 없었다(가능했는데 내가 당황해서 못 찾았을 수도 있다). 어쩔 수 없이 첫 달 무료를 믿고 그냥 구독을 신청했다. 그런데 알고 보니 홈페이지에서도 밀리의 서재 아이콘을 한번 누르면 어플에서 보이는 것과 동일한 화면과 기능을 이용할 수 있었다. 거기다 윈도우 컴퓨터라면 아이디 생성 후 PC에서도 책을 볼 수 있다. 아쉽게도 나는 맥 유저라 PC로 책을 볼 수는 없다. 

 

다행히 머신러닝에 관한 서적을 밀리의 서재에서 충분히 찾을 수가 있어서 만족하고 구독 중이다. 하지만 당시에 내가 알고리즘 서적을 찾고 있었다면 한번 뿐인 첫 달 무료 기회를 날려버렸다고 후회했을지도 모르겠다.(현재 밀리의 서재에 알고리즘 관련 서적은 별로 유용해 보이는 게 없다.)

 

 

밀리의 서재

지금 신청 시 첫달무료! 국내 최대 5만권 월정액 독서앱

www.millie.co.kr

 

신중한 해지

보통 월정액을 결제하면 중간에 해지를 하여도 한 달이 끝나기 전에는 계속 서비스를 이용할 수 있는게 일반적이다. 그래서 밀리의 서재를 무료 이용하던 중 결제 카드를 분실했을 때, 나중에 자동 결제일이 되면 결제 오류 메시지가 뜨겠거니 싶어서 미리 다른 카드로 바꾸려고 했는데 방법을 못 찾아 첫 달 무료 기간이 끝나기 전에 가볍게 정액 구독을 정지했다.(결제 완료 후에 다른 카드로 결제 정보를 변경할 수 없다고 한다.) 그런데 정지한 순간 서비스 이용이 중지되었다... 당황해서 찾아보니 정액 결제 해지 페이지에 작게 첫 달 무료 이용 중에는 해지 시 곧바로 서비스 이용이 중지된다고 적혀있었다. 첫 달 무료 구독 중에 해지를 할 경우 조심히 해지해야 한다. 

 

리딩북과 챗북

나는 리딩북이나 오디오북 등을 이용해 본 적이 없다. 그러다 밀리의 서재에서 리딩북과 챗북이라는 형태로 책을 제공하고 있길래 궁금해서 사용해 보았다. 우선 책을 처음부터 끝까지 한 페이지도 빼놓지 않고 읽어야 직성이 풀리는 나에겐 챗북도 리딩북도 맞지 않았다.

 

챗북은 책의 주인공들이 대화를 나누는 형식으로 전개가 되었는데 많은 부분을 띄어 넘고 있다는 느낌이었다. 책을 가볍게 빨리 읽고 싶다면 좋은 형태의 책이 될 것이다.

 

리딩북은 배우나 가수, 작가 등 다양한 분야의 사람들이 책을 읽어주어 팬이라면 목소리를 들을 수 있어 좋을 것 같았다. 잠들기 전에 가볍게 들으면서 잠들 생각으로 리딩북을 들었는데 점점 책에 집중해버려 오히려 잠들지 못하기도 했다. 그러나 리딩북을 잘 듣고 있다가 재생 시간이 너무 짧다 싶어서 목차를 보니 책의 여러 챕터를 읽지 않고 그냥 넘어가고 있었다. 모든 글을 다 읽어준다면 좋을 텐데 그러지 않아 아쉽지만 이용하지 않을 생각이다. 

 

그 외 장점

물론 정기 구독을 결심한 만큼 밀리의 서재는 괜찮은 어플이라고 생각한다.

 

가격도 월 9,900원에 책 한 권에 조금 못 미치는 가격에 내가 보고 싶은 만큼 책을 볼 수 있다는 점이 가장 큰 메리트가 아닐까. (컴퓨터 서적이라면 1/3 정도 가격...) 사실 나는 책을 구매하는 것을 좋아하지 않는다. 도서관에 가면 무료로 볼 수 있으니까. 하지만 주말마다 도서관에 들리던 10대가 지나고 성인 되어서는 도서관에 갔던 적이 얼마나 있나 손에 꼽는다. 그러면서 중고 서점을 주로 이용했는데 최신 서적의 경우는 중고 서점에서도 그리 저렴하지 않다는 점에서 밀리의 서재는 괜찮은 금액이다. 책을 꾸준히 읽을 생각이 있다면 1년 정기 구독을 신청하면 1년에 99,000이다.(월 8,250원인 셈) 인앱 결제를 할 경우 월 정액만 가능하며 금액도 12,000원 정도로 비싸니 꼭 웹을 통해서 결제하길 바란다. 이번엔 종이책 구독 서비스도 오픈했는데 책 소장을 좋아하는 사람이라면 매우 좋은 서비스일 것이다.(월 15,900원으로 월 무제한 구독 + 종이책 한 권 배송)

 

뿐만 아니라 다양한 이벤트로 진행한다. 작가 공모전이나 맥주 제공, 전시회 등의 이벤트를 진행한다. 아래 링크를 통해 확인 가능하다.

 

밀리의 서재

지금 신청 시 첫달무료! 국내 최대 3만권 월정액 독서앱

www.millie.co.kr

 

'피드' 탭을 통해서는 내가 얼마나 많은 시간 동안 얼마나 되는 양의 책을 읽었는지  확인할 수도 있어 기록 쌓는 재미가 있다. 게다가 내가 읽은 책과 동일한 책을 읽는 사람의 수는 얼마나 되는지, 그 책을 읽은 사람들의 취향에 맞는 책은 무엇인지, 밀리의 서재에서 인기 있는 책은 무엇인지 소개해 주기 때문에 어떤 책을 읽으면 좋을까 고민을 덜어주기도 한다.

'내서재' 탭에서는 내가 읽은 책이나 읽고 싶은 책 등을 다양하게 분류하여 저장해 둘 수 있고 책에 대한 포스트를 올릴 수도 있어 내가 읽은 책과 감상 등을 정리해 둘 수 있어 좋다. 다른 사람의 서재를 구경할 수도 있으며 내서재는 비공개할 수도 있다.

 

e북 어플이니 만큼 당연히 서적 읽기 기능은 만족스럽다. 책의 목차를 보고 이동할 수도 있고 스크롤을 통해서 책장을 넘기면 어디에서 스크롤을 넘기기 시작했는지 표시가 되어 있어 다시 돌아가기도 용이하다. 북마크 기능과 인용문 저장 기능을 제공하며 인용문을 눌러서 그 페이지로 돌아가는 것 역시 가능하다. (PDF 파일로 된 서적의 경우 인용문 저장 기능을 제공하지 않는다.)

스크롤을 통해 페이지를 넘겼을 경우 흔적이 남는다. 아이콘은 차례로 오디오로 듣기(그냥 음성 변환이라 매끄럽지 않다), 목차, 화면 설정, 페이지 설정이다. 
북마크 한 페이지를 목차순/최신순으로 정렬해서 볼 수 있다.
기억해 두고 싶은 인용문은 저장해 두었다가 따로 모아 볼 수 있다.

 

이용하면서 읽기 기능에서 아쉬웠던 점은 스크롤로 이동하지 않고 목차를 통해 이동할 경우에는 내가 어디까지 읽었는지 알 수 없다는 점과 목차를 열면 무조건 제일 위에서부터 보여주는 것이 아쉬웠다. 목차를 열었을 때 내가 현재 읽고 있는 곳부터 보이면 훨씬 편할 것 같다. 

 

읽고 싶은 책이 넘쳐나는 한 앞으로도 밀리의 서재를 계속 이용할 의향이 있다. 이렇게 좋은 어플을 계기로 독서를 하는 사람들이 늘어났으면 좋겠다. 

경사 하강법(Gradient Descent)의 원리

선형 회귀 모델과 모델의 예측 평가 방법까지 알았으니 이제 어떻게 가장 최적의 모델을 찾을 것인지 알아보자. 이전 포스트에서 언급했듯이, 가장 최적의 모델은 가장 적은 비용(cost)을 갖는 모델이다. 모델이 최소 비용을 갖는 매개변수를 찾는 과정을 훈련한다고 하며 비용 최소화하기 위해서는 경사 하강법(Gradient Descent)을 이용한다. 이 방법의 원리는 산 정상에서 골짜기로 가장 빠르게 내려오는 방법인 가장 경사가 가파른 길을 골라 내려오는 것과 같다. 이 방법은 회귀뿐만 아니라 다양한 종류의 문제에서 최적의 해법을 찾을 수 있는 일반적인 최적화 알고리즘이다.

 

Gradient descent.png
경사 하강법을 실행하는 모습. x0에서 시작하여, 경사가 낮아지는 쪽으로 이동하여 차례대로 x1, x2, x3, x4를 얻는다.

 

이 그림에서 볼 수 있듯이, 등고선에서 고지를 찾는 방법은 임의의 시작점 x0에서 경사(기울기)가 낮아지는 쪽(등고선이 서로 가까울수록 경사가 가파르며 고지에 가까울수록 경사는 낮아진다)을 찾아 차례대로 이동하여 가장 경사가 낮은 곳(최적해)을 찾으면 된다. 이때 최종적으로 고지에 도달하면 경사가 0에 도달한다.

 

Gradient ascent (surface).png

그러나 위와 같은 형태의 곡면의 경우 임의의 시작점 ⍬0에서 출발하여 경사가 0에 도달해 찾은 고지가 다른 임의의 시작점 ⍬1에서 출발하여 찾은 고지와 다를 수 있다. 즉, 임의의 시작점 ⍬에서 찾은 지역적인 최적해(local minimum)가 전역적인 최적해(global minimum)라고 보장할 수 없다. 

 

다행히 비용 함수는 다음과 같은 모양의 이차 곡면을 가진다. 즉 하나의 전역 최솟값을 가지고 있다는 뜻이다.

Circular Paraboloid Quadric.png

학습률(Learning Rate)

경사 하강법에서 가장 중요한 파라미터는 스텝의 크기로 학습률 하이퍼파라미터로 결정된다. 여기서 하이퍼파라미터는 일반적인 파라미터와는 달리 데이터를 학습해서 얻을 수 없으며, 모델의 성능 향상을 위해 별도의 최적화 과정을 거쳐 찾아내야 하는 조정 파라미터(tuning parameter)를 말한다.

 

등고선에서 x0에서 x로 나아가기 위해서는 얼마나 멀리까지 이동을 할 것인가를 결정하는 것이 학습률 하이퍼파라미터를 결정하는 것이다.

Conjugate gradient illustration.svg

 

학습률이 너무 작으면 알고리즘이 수렴하기 위해 반복을 너무 많이 진행해야 하므로 시간이 오래 걸리며 전역 최솟값에 수렴하기 전에 지역 최솟값에서 모델이 학습을 중단할 수도 있다. 반면에 학습률이 너무 크면 골짜기를 가로질러 골짜기 아래로 내려가는 것이 아니라 반대로 올라갈 수도 있으며 알고리즘이 더 큰 값으로 발산하게 되어 적절한 해법을 찾을 수 없게 된다. 이를 overshooting이라고 한다. 따라서 적절한 해법을 찾기 위해서는 여러 가지 학습률을 적용해보고 비교하는 과정이 필요할 수도 있다.

학습률이 너무 작은 경우(왼)와 학습률이 너무 큰 경우(오)

경사 하강법에서의 계산 : 배치 경사 하강법(Batch Gradient Descent)

시작점 X0 출발하여 현제 Xi에 위치해 있을 때, 다음으로 이동할 점인 Xi+1은 다음과 같이 계산한다.

여기서 α는 이동할 거리를 조절하는 매개변수이다.

경사 하강법을 비용 함수에서 구현하려면 각 모델 파라미터 W에 대해 비용 함수의 경사(gradient)를 계산해야 한다. 이는 W가 조금 변경될 때 비용 함수가 얼마나 바뀌는지 계산한다는 의미이다. 이를 편미분(Partial Derivative)라고 한다. 이를 이용하여 비용 함수에서의 편미분을 구하는 방법은 다음과 같이 유도할 수 있다.

 

우선 간단한 계산을 위해 선형 회귀식 H(x)의 파라미터를 W만 남긴다.

이때 비용 함수는 다음과 같이 표현될 수 있다. 마찬가지로 간단한 미분을 위해 1/2을 한다. 1/m과 1/2m은 이 식에 큰 영향을 끼치지 않는다.

이렇게 정리한 비용 함수 cost(W)를 W에 대해서 편미분 하면 다음과 같은 식을 얻을 수 있다.

위의 경사 하강법 계산식의 ƒ(x)에 비용 함수를 대입하면 경사 하강법의 스텝 W에 대해 다음과 같은 정의를 할 수 있다. 여기서 α는 학습률이며 내려가는 스텝 W의 크기를 결정한다.

이러한 알고리즘을 배치 경사 하강법(Batch Gradient Descent)라고 한다. 이 알고리즘에서 매 스텝에서 훈련 데이터 전체를 사용하기 때문에 큰 훈련 데이터 세트에서 매우 느리다. 때문에 확률적 경사 하강법(Stochastic Gradient Descent)을 사용하기도 한다.

 

확률적 경사 하강법(Stochastic Gradient Descent)

매우 큰 훈련 데이터 세트에서의 배치 경사 하강법의 단점을 보완하기 위한 방법으로 확률적 경사 하강법이 있다. 이 방법은 매 스텝을 결정할 때마다 전체 훈련 데이터 세트를 이용하는 배치 경사 하강법과 달리 매 스텝에서 단 한 개의 데이터만 무작위로 선택하고 그에 대한 경사(Gradient)를 계산한다

 

다만 확률적(무작위)이기 때문에 배치 경사 하강법보다 불안정하다는 특성이 있다. 이 특성은 알고리즘이 지역 최솟값을 무시할 수 있도록 도와주지만 시간이 지나도 최솟값에 매우 근접할 뿐 최솟값을 찾기는 어렵다. 즉 배치 경사 하강법보다 전역 최솟값을 찾을 가능성이 높지만 전역 최솟값에 이르기 어렵다는 뜻이다.

 

 

이를 해결하기 위한 방법 중 하나는 학습률을 점진적으로 감소시키는 것이다. 이렇게 하면 알고리즘이 전역 최솟값에 도달하게 한다. 이렇게 매 스텝에서 학습률을 결정하는 함수를 학습 스케줄(learning schedule)이라고 한다.

 

 

미니 배치 경사 하강법(Mini-Batch Gradient Descent)

미니 배치 경사 하강법은 확률적 경사 하강법과 유사하다. 다만, 하나의 샘플이 아닌 미니 배치라 부르는 임의의 작은 샘플 세트에 대해서 경사를 계산한다는 점이 다르다. 

 

미니 배치 경사 하강법의 경우 확률적 경사 하강법에 비해 전역 최솟값에 더 빨리 가까이 도달하게 된다. 미니 배치 경사 하강법 역시 확률적 경사 하강법과 마찬가지로 전역 최솟값에서 멈추지 않고 그 주변을 반복적으로 맴돌 가능성이 있다. 이때 적절한 학습 스케줄을 통해 최솟값에 도달할 수 있다.

Stogra.png
미니 배치 경사 하강법에서 스텝의 변동

 

참고

오렐리앙 게론, "핸즈온 머신러닝", 한빛미디어, 2017년 3월

필드 케이디, "처음 배우는 데이터 과학", 한빛미디어, 2018년 2월

니킬 부두마, "딥러닝의 정석", 한빛미디어, 2018년 3월

Probst, Philipp, Anne-Laure Boulesteix, and Bernd Bischl. "Tunability: Importance of Hyperparameters of Machine Learning Algorithms." Journal of Machine Learning Research 20.53 (2019): 1-32.

Edwith, 머신러닝과 딥러닝 BASIC 중 'Linear Regression의 cost 최소화 알고리즘의 원리', https://youtu.be/TxIVr-nk1so

위키백과, 경사하강법, https://ko.wikipedia.org/wiki/%EA%B2%BD%EC%82%AC_%ED%95%98%EA%B0%95%EB%B2%95

위키백과, 이차 초곡면, https://ko.wikipedia.org/wiki/%EC%9D%B4%EC%B0%A8_%EC%B4%88%EA%B3%A1%EB%A9%B4

Wikipedia, Gradient Descent, https://en.wikipedia.org/wiki/Gradient_descent

Wikipedia, Stochastic Gradient Descent, https://en.wikipedia.org/wiki/Stochastic_gradient_descent

위키백과에 있는 이미지 파일 글쓰기

위키백과에 있는 이미지를 내 포스트에 올리고 싶을 때 캡처를 하는 방법도 있지만 위키백과에서 보이는 이미지는 작아 캡처를 할 경우 원하는 사이즈의 파일로 이용하는 데에 어려움이 있다. 이 포스트에서는 깔끔하게 이미지를 포스트에 업로드하는 방법을 알아보겠다.

 

업로드하고 싶은 이미지

위와 같은 위키백과에서 저 그래프를 내 포스트에 올리고 싶다면 우선 그래프를 클릭한다.

 

이미지 원본파일

그러면 위키백과에 첨부되어 있는 이미지의 원본 파일을 확인할 수 있다.

 

원본 파일 다운로드

오른쪽 하단에 보면 다운로드 버튼을 통해 직접 원본 파일을 다운로드하여 사용할 수 있다.

 

이미지 파일 공유하기

https://commons.wikimedia.org/wiki/File:Tangent_to_a_curve.svg#/media/파일:Tangent_to_a_curve.svg

 

File:Tangent to a curve.svg - Wikimedia Commons

 

commons.wikimedia.org

공유에 있는 링크를 복사 붙여 넣기 하면 이렇게 글에 이미지 파일 링크를 넣을 수도 있다. 하지만 이미지를 바로 포스트에 넣는 방법은 아니다.

 

이미지 파일 포함하기

포함하기 버튼을 누르면 위와 같이 HTML 코드를 볼 수 있다. 복사 버튼을 눌러 링크를 복사한 뒤에 내 블로그의 글쓰기에서 이미지를 포함시킬 수 있다. 

 

이미지를 포함한 글쓰기

이 HTML 코드를 이용하기 위해서는 HTML 모드로 들어가야 한다. 

 

글쓰기 HTML 모드

HTML 모드에서 원하는 곳에 코드를 넣은 후에 다시 기본 모드로 돌아오면 다음과 같이 이미지가 업로드되어 있다.

업로드된 이미지 확인

이렇게 이미지를 업로드할 경우에는 아쉽게도 이미지에 주석을 달 수 없다. 대신에 이미지를 누르면 바로 위키백과의 이미지를 다운로드한 원본 파일 페이지 링크로 넘어가는데 이 링크를 고치면 이미지를 눌러 위키백과의 '미분' 페이지로 넘어가게 만들 수 있다.

 

하이퍼링크 수정

제일 첫 번째에 나오는 <a href=""> 부분에서 ""안의 링크를 "https://ko.wikipedia.org/wiki/미분"으로 바꾸면 된다. 이 링크는 현재 페이지에서 링크로 연결되므로 새 탭에서 링크를 열고 싶다면 "https://ko.wikipedia.org/wiki/미분" 뒤에 target="_blank"를 넣어주면 된다.

<a href="https://ko.wikipedia.org/wiki/%EB%AF%B8%EB%B6%84" target="_blank">

 

Tangent to a curve.svg
By 영어 위키백과Jacj Later versions were uploaded by Oleg Alexandrov at en.wikipedia. - en.wikipedia에서 공용으로 옮겨왔습니다., 퍼블릭 도메인, 링크

↑기본 HTML 링크를 수정 없이 넣었을 경우

Tangent to a curve.svg
By 영어 위키백과Jacj Later versions were uploaded by Oleg Alexandrov at en.wikipedia. - en.wikipedia에서 공용으로 옮겨왔습니다., 퍼블릭 도메인, 링크

↑이미지의 연결 링크를 수정한 경우

 

이미지 아래에 따라오는 설명은 지워도 문제없다.

이미지 파일을 다운로드한 다음에 업로드하는 과정 없이 붙여 넣기만으로 이미지를 업로드할 수 있는 방법이다.

 

위키백과에 있는 자료는 비영리적인 목적뿐만 아니라 영리적인 목적으로도 자유롭게 사용할 수 있으며 자세한 내용은 다음에서 확인할 수 있다.

https://ko.wikipedia.org/wiki/%EC%9C%84%ED%82%A4%EB%B0%B1%EA%B3%BC:%EC%A0%80%EC%9E%91%EA%B6%8C 

 

위키백과:저작권 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 위키백과에 있는 모든 문서의 저작권은 각 저작자에게 있으며, 위키백과나 위키미디어 재단이 저작권을 소유하고 있지는 않습니다. 위키백과 내의 모든 문서는 크리에이티브 커먼즈 저작자표시-동일조건변경허락 3.0 Unported 라이선스(CC-BY-SA 3.0) 하에 배포되며, 이 라이선스 규정에 따라 문서를 이용할 수 있습니다. 또한, 문서 내에 크리에이티브 커먼즈 하에서만 배포된다는 표식이 없는 경우에는 그 문서는 GNU 자

ko.wikipedia.org

 

참고

위키백과, 미분, https://ko.wikipedia.org/wiki/%EB%AF%B8%EB%B6%84

위키백과, 저작권, https://ko.wikipedia.org/wiki/%EC%9C%84%ED%82%A4%EB%B0%B1%EA%B3%BC:%EC%A0%80%EC%9E%91%EA%B6%8C

 

'Record' 카테고리의 다른 글

[Git] 깃 저장소 연결: git remote  (0) 2020.11.02
[공유] 구형 맥북에서 사이드카 이용하기  (0) 2020.04.01
로고 이미지와 영상  (0) 2019.08.31
Linear Regression이란

Linear Regression(선형 회귀)는 이머신러닝 개념 포스트에서 간단히 다루었던 지도 학습(supervised learning)의 한 종류이다. 선형 회귀는 학습 데이터(training data set)를 이용한 학습(training) 과정을 거쳐 데이터에 가장 잘 맞는 선형 모델의 매개변수(parameter)를 찾아 예측하는데 여기서 예측의 범위는 연속적인 범위를 갖는다. 다시 말해, 종속변수 y와 한 개 이상의 독립변수 x와의 선형 상관관계를 모델링하는 회귀분석 기법이다. 한 개의 독립 변수(또는 설명 변수)에 기반한 경우에는 단순 선형 회귀, 둘 이상의 독립 변수에 기반한 경우에는 다중 선형 회귀라고 한다. 실제로 많은 데이터들이 아래의 그래프와 같이 선형(linear)으로 분포하는 경향이 있기 때문에 선형 회귀를 통해 예측이 가능하다. 

Normdist regression.png
독립변수 1개와 종속변수 1개를 가진 선형 회귀의 예

 

선형 회귀는 선형 예측 함수를 통해 회귀식을 모델링하며, 알려지지 않은 매개변수는 주어진 데이터로부터 추정한다. 이렇게 만들어진 회귀식을 선형 모델이라고 한다. 

 

선형 회귀의 대표적인 사용 사례는 다음과 같다.

  • 값을 예측하는 것이 목적일 경우: 선형 모델을 통해 독립변수 x에 따른 종속변수 y를 예측하기 위해 사용할 수 있다.
  • 종속변수 y와 독립변수 x 사이의 관계를 밝히는 경우: 선형 회귀 분석을 사용해 x와 y 사이의 관계를 정량화하기 위해 사용할 수 있다. 

왜 Regression이라 하는가?

Regression을 영어 사전에서 찾아보면, 후퇴, 복귀, 쇠퇴, 회귀라고 나온다. 그중에서도 통계학 용어인 회귀는 국어사전에서 '한 바퀴 돌아서 본디의 상태나 자리로 돌아오는 것'이라고 한다. 이러한 용어는 영국의 유전학자 프란시스 골턴(Francis Galton)이 부모의 키와 아이들의 키의 상관관계를 연구하면서 키가 큰 사람의 아이가 부모보다 작은 경향이 있다는 사실을 연구하면서 소개하였다. 다시 말해, 키가 큰 부모보다 작은 아이의 키가 전체 키 평균으로 회귀한다는 것이다. 프란시스는 두 변수 사이의 상관관계를 분석하기 위해 사용한 방법을 회귀라고 이름 붙였으며 이후 칼 피어슨(Karl Pearson)은 아버지와 아들의 키를 조사한 결과를 바탕으로 함수 관계를 도출하여 회귀 분석 이론을 수학적으로 정립하였다.

 

선형 회귀식

선형 회귀는 주어진 데이터 집합에서 종속변수 y와 연관된 독립변수 x가 d개 존개하는 경우에 회귀식은 다음과 같이 나타낼 수 있다.

d개의 독립변수에 따른 선형회귀식

d개의 독립변수에 따른 선형회귀식은 다음과 같은 형태로 나타낼 수 있다.

여기서 y는 종속 변수 혹은 응답 변수라고 하며 출력값이라고 볼 수 있다. x는 독립 변수, 예측 변수, 입력 변수라고 하며 입력값이라고 볼 수

있다. ⍵는 매개변수, 회귀 계수라고 불리기도 하는 상수이다. b는 오차항, 노이즈로 이는 종속변수 y에 대한 모든 오차 요인을 포함한다.

 

가장 간단한 선형 회귀식은 독립변수 x가 1개인 1차 방정식 형태이다. 쉬운 설명을 위해 다음과 같은 형태의 회귀식을 사용하도록 하자.

여기서 H(x)는 입력값 x에 따른 출력 값 y를 예측하는 가설(Hypothesis)이며, ⍵와 b는 모델 파라미터를 의미한다. 이 ⍵, b파라미터를 조정하여 선형 함수를 표현하는 모델을 얻을 수 있다. 이 모델을 사용하기 위해서는 ⍵와 b를 정의해야 하며 이를 훈련 데이터에 가장 잘 맞는 값을 찾는 과정을 훈련시킨다(training)라고 한다. 그렇다면 어떻게 ⍵와 b를 정의해야 할까? ⍵와 b의 정의에 따라 1차 함수의 기울기와 절편이 달라지기 때문에 데이터의 산포도와 가장 적합한 함수가 될 수 있도록 ⍵와 b를 정의해야 한다.

 

선형 회귀에서는 일반적으로 선형 모델의 예측과 훈련 데이터 사이의 거리를 재는 비용 함수(Cost Function)를 사용한다.

 

비용 함수(Cost Function)

선형 회귀에서는 일반적으로 선형 모델의 예측과 훈련 데이터 사이의 거리를 재는 비용 함수(Cost Function)를 사용한다. 간단히 말해 각 점과 선 사이의 거리를 계산하며 가설(선형 모델의 예측)과 실제 데이터(훈련 데이터)가 얼마나 다른지를 평가하는 것이다. 이때, 단순히 하나의 데이터(single data)와 모델의 예측의 차이를 계산하는 것을 손실 함수(Loss Function)라고 하며 모든 데이터에 대한 차이를 모두 합하여 모델을 평가하는 것이 비용 함수이다. 

 

 

선형 회귀식에서 ⍵와 b의 값에 따라 다양한 선형 회귀 모델을 만들 수 있다. 왼쪽의 그래프에서 볼 수 있듯이 세 가지의 선형 회귀 모델을 만들었다고 하자. 이 세 가지 모델 중에 가장 예측을 정확하게 하는 모델이 무엇인지 평가하기 위해서 비용 함수를 사용한다.

거리를 구하기 위해 위와 같은 식을 생각할 수 있으나 이 함수는 좋은 함수가 아니다. 단순하게 -y를 하기 때문에 y의 위치가 모델의 왼쪽과 오른쪽에 있을 때에 따라 같은 거리임에도 다르게 평가될 수 있기 때문이다. 따라서 비용 함수는 위의 함수를 제곱하여 사용한다. 다음 식에 m은 데이터의 개수를 의미하며 각 데이터에 대한 예측 결과를 실제 데이터와 비교하여 총 데이터에 대한 예측을 평가한다. 이 비용 함수의 결괏값이 적을수록 예측이 정확하다는 것을 의미한다. 가장 적은 비용을 갖는 ⍵와 b를 구하는 과정이 모델의 훈련 과정이다.

 

비용 함수를 이용한 예측 모델을 훈련시키는 방법은 다음 포스트에 이어진다.

 

참고

오렐리앙 게론, "핸즈온 머신러닝", 한빛미디어, 2017년 3월

필드 케이디, "처음 배우는 데이터 과학", 한빛미디어, 2018년 2월

Edwith, 머신러닝과 딥러닝 BASIC 중 'Linear Regression의 Hypothesis와 cost', https://youtu.be/Hax03rCn3UI

위키백과, 회귀분석, https://ko.wikipedia.org/wiki/%ED%9A%8C%EA%B7%80_%EB%B6%84%EC%84%9D

위키백과, 선형회귀, https://ko.wikipedia.org/wiki/%EC%84%A0%ED%98%95_%ED%9A%8C%EA%B7%80

Wikipedia, Linear Regression, https://en.wikipedia.org/wiki/Linear_regression

 

머신러닝이란

위키백과에서는 머신러닝을 "the scientific study of algorithms and statistical models that computer systems use to perform a specific task without using explicit instructions, relying on patterns and inference instead."라고 정의하고 있다. 즉, 컴퓨터 시스템이 패턴과 추론에 의지하는 대신에 명시적 지시를 사용하지 않고 특정 작업을 수행하는 데 사용하는 알고리즘 및 통계 모델에 대한 과학적 연구이다.

 

일반적으로 소프트웨어라고 생각하는 명시적인(explicit) 프로그램들은 한계를 가지고 있다. 스팸 메일 필터나 자율 주행 자동차를 떠올려보자. 계산기 프로그램을 만드는 것처럼 스팸 메일 필터를 만들면 어떻게 될까? 스팸 메일을 구분하기 위한 규칙들을 개발자가 직접 명시하기에는 너무나도 많은 경우의 수가 존재하며 이를 개발 단계에서 프로그래밍하기에는 명백한 한계를 가지고 있다. 그래서 나온 방법이 프로그램이 데이터로부터 스스로 학습하도록 프로그래밍하는 것이다. 스팸 메일의 경우 계속해서 진화하는 스팸 메일의 내용에 맞춰 프로그램이 스스로 학습하게 되면, 프로그램의 유지 보수가 용이해지며 정확도가 더 높아진다. 

 

머신러닝은 다음과 같은 경우에 적합하다.

  • 기존의 해결 방법으로는 많은 수동 조정과 규칙이 필요한 문제.
  • 전통적인 방법으로는 전혀 해결 방법이 없는 복잡한 문제.
  • 유동적인 환경에 적응해야 하는 문제.
  • 복잡한 문제와 대량의 데이터에서 통찰 얻기. (e.g. 데이터 마이닝)

 

"The field of study that gives computers the ability to learn without being explicitly programmed."
: "명시적인 프로그래밍 없이 컴퓨터가 학습하는 능력을 갖추게 하는 연구 분야다"
-아서 사무엘(Arthur Samuel), 1959

 

"A computer program is said to learn from experience E with respect to some class of tasks T and performance measure P, if its performance at tasks in T, as measured by p, improves with experience E."
"어떤 작업 T에 대한 컴퓨터 프로그램의 성능을 P로 측정했을 때 경험 E로 인해 성능이 향상됐다면, 이 컴퓨터 프로그램은 작업 T와 성능 측정 P에 대해 경험 E로 학습한 것이다."
-톰 미첼(Tom Mithchell), 1997

 

학습 방법에 의한 구분

머신러닝의 학습 방법은 접근 방식, 입력 및 출력하는 데이터 유형 및 해결하려는 작업 또는 문제의 유형에 따라 다르다. 보다 다양한 학습 방법의 구분은 다음에서 확인할 수 있다.

 

그중에서 학습하는 동안의 감독의 형태에 따라 지도 학습과 비지도 학습으로 구분할 수 있는데 다음과 같다.

 

지도 학습(Supervised Learning)

지도 학습은 입력과 원하는 출력을 모두 포함하는 예제(labeld examples)를 학습 데이터셋(training data set)으로 활용하여 수학적 모델을 구축한다. 각각의 훈련 예제는 하나 이상의 입력과 각 입력에 대응해 기대하는 결과를 함께 포함하고 있다. 분류(classfication)가 전형적인 지도 학습 작업이며 에측 변수(predictor variable)라 부르는 특성(e.g. 공부시간, 과제점수 등)을 사용해 타깃(e.g. 최종 학점)의 수치를 예측하는 것 역시 지도 학습을 이용한 전형적인 작업에 속한다.

예를 들면, 여러 동물 사진을 가지고 각 사진이 어떤 동물인지를 구분하는 프로그램을 학습시키고자 할 때, 지도 학습 방법은 프로그램을 학습시킬 때, 각 사진이 어떤 동물의 사진인지를 구분(labeled)을 먼저 한 뒤에 프로그램을 학습시키는 방법이다.

 

비지도 학습(또는 자율학습, Unsupervised Learning)

지도 학습과 달리 비지도 학습은 훈련 데이터셋에 구분이 되어 있지 않다. 따라서, 프로그램이 스스로 도움 없이 학습을 해야한다. 이 학습 방법에서는 군집화(Clustering)가 중요하다. 프로그램은 데이터 간의 공통점을 식별하고 새로운 데이터의 이러한 공통점의 존재 여부에 따라 반응한다. 

예를 들면, 뉴스 기사를 구분 하고자 할 때, 비지도 학습 방법은 여러 뉴스 기사를 통해 다른 기사와 유사한 점과 유사하지 않은 점을 구분해 나가며 군집(Cluster)을 만들어 나가며 그룹을 짓는다. 즉, 감독의 개입 없이 데이터셋만 가지고 스스로 학습하는 것이다.

 

학습 데이터셋(Training Data Set)

머신러닝 알고리즘은 간단한 학습 데이터(training data)를 기반으로 명시적인 프로그래밍 없이 예측 또는 결정을 하기 위한 수학적인 모델을 만든다. 학습 데이터셋은 쉽게 말해 학습에 사용하기 위한 데이터들의 모음이다.

 

참고

오렐리앙 게론, "핸즈온 머신러닝", 한빛미디어, 2017년 3월

Edwith, 머신러닝과 딥러닝 BASIC 중 '기본적인 Machine Learning의 용어와 개념 설명', http://www.edwith.org/others26/lecture/10729/

MIT, Introduction to Machine Learning, https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-0002-introduction-to-computational-thinking-and-data-science-fall-2016/lecture-videos/lecture-11-introduction-to-machine-learning/

Wikipedia, Machine Learning, https://en.wikipedia.org/wiki/Machine_learning

 

 

Big-O Notation (빅오 표기법)이란

알고리즘의 복잡도를 나타내는 지표 혹은 언어로 계산 복잡도 이론에서 사용되는 점근 표기법이다. 또한, 란다우 표기법이라고 부르기도 하는데 복잡도 이론, 컴퓨터 과학, 수학에서 함수의 점근적 동작을 설명하기 위해 사용하며, 기본적으로 함수가 얼마나 빠르게 증가 혹은 감소하는지 알려준다. 따라서 Big-O 표기법은 알고리즘의 속도를 파악하는데 큰 도움이 된다.

 

개념의 비유(시간 복잡도)

만약 우리가 누군가에게 파일을 전달하고자 할 때를 생각해보자. 메일이나 FTP 등 온라인을 통해 상대방에게 파일을 전송할 수도 있고 직접 파일이 담긴 USB를 전달할 수도 있을  것이다. 온라인으로 파일은 전송하는 방법은 간단하고 손쉽다. 하지만 파일 크기가 1TB가 넘는다면? 이러한 경우에는 파일을 전달하는데 하루가 부족할지도 모른다. 파일 크기에 따라 걸리는 시간이 증가하는 것이다. 반면에, 직접 USB에 파일을 담아 전달할 경우, 파일 크기가 얼마나 크던 작던 상관없이 걸리는 시간은 변하지 않는다. 즉, 파일 전달까지 걸리는 시간이 파일 크기에 무관하게 일정한 시간이 걸리는 것이다. 이를 Big-O 표기법으로 나타내면, 온라인 전송의 경우, O(n) (여기서 n은 파일의 크기를 의미한다), 직접 전달의 경우 O(1)로 나타낼 수 있다. 이것이 바로 Big-O 시간에 대한 개념이다. 

파일 크기에 따른 소요시간의 변화

그 외의 개념 Big-O, Big-Θ, Big-Ω

Big-O : 학계에서 O는 상한을 나타낸다. 어떤 알고리즘은 O(n)으로 표현할 수 있지만, 이 외에 N보다 큰 big-O 시간으로 표현할 수도 있다. 예를 들어 O(n²), O(n³)도 옳은 표현이다. 다시 말해 알고리즘의 수행 시간은 적어도 이들 중 하나보다 빠르기만 하면 된다. 따라서 big-O 시간은 알고리즘 수행 시간의 상한이 되고, 이는 '작거나 같은' 부등호와도 비슷한 관계가 있다. 만약 철수의 나이가 X라면 X ≤ 100이라고 말할 수 있지만, X ≤ 1,000 혹은 X ≤ 1,000,000으로 나타내도 옳은 표기법이다. 수학적으로 이야기해서 이들 모두 참인 표현법이다. 마찬가지로 배열의 모든 값을 출력하는 알고리즘은 O(n)이라고 표현할 수 있을 뿐만 아니라 O(n³) 혹은 O(n) 보다 큰 어떤 수행 시간으로도 표현 가능하다.

 

Big-Ω : 학계에서 Ω는 등가 개념 혹은 하한을 나타낸다. 배열의 모든 값을 출력하는 알고리즘은 Ω(n)뿐만 아니라 Ω(log(n)) 혹은 Ω(1)로도 표현할 수 있다. 결국, 해당 알고리즘은 Ω 수행 시간보다 빠를 수 없게 된다.

 

Big-Θ : 학계에서 ΘΩ와 O 둘 다를 의미한다. 즉, 어떤 알고리즘의 수행 시간이 O(n)이면서 Ω(n)이라면, 이 알고리즘의 수행 시간을 Θ(n)로 표현할 수 있다.

 

다양한 표기법과 그 의미

f(x) = o(g(x))는 f가 g보다 느리게 증가한다는 뜻이며 이는 비교에서 크게 중요하지 않다. Θ와 Ω는 컴퓨터 과학에서 종종 사용되며, 소문자 o는 수학에서는 흔하지만 컴퓨터 과학에서는 흔하지 않다. ω는 거의 사용하지 않는다. 

흔히 하는 실수는, Θ를 의미할 때 O를 사용하는 것이다. 예를 들어 힙 정렬은 Θ(n×log(n))라는 의미를 전달하고자 하는 의미로 O(n×log(n))이다.라고 말할 수 있다. 둘 다 맞는 말이지만 Θ(n×log(n))가 더 강한 주장이다. 업계에서 big-O의 의미는 학계에서 Θ의 의미와 가깝다. 이 글에서 아래에 설명하는 개념은 학계에서 Θ의 의미와 가까운 big-O 표기법을 사용하겠다.

 

공간 복잡도

위에서 살펴본 파일 크기에 따른 전송 시간은 시간에 대한 복잡도였다. 알고리즘에서는 시간뿐만 아니라 공간(메모리)에 대해서도 신경을 써야 한다. 예를 들어, 크기가 n인 배열을 만들고자 한다면, O(n)의 공간이 필요하다. 크기가 n×n인 이중 배열을 만들려면, O(n²)의 공간이 필요하다.

 

표기방법

big-O를 표기하는 방법은 다음과 같다. 

 

상수항은 무시한다. big-O는 단순히 증가하는 비율을 나타내는 개념이므로, n이 충분히 큰 숫자일 경우, O(n) 코드가 O(1) 코드보다 빠르게 동작하는 것이 가능하다. 이런 이유로 수행 시간에서 상수항을 무시해 버린다. 즉, O(2n)으로 표기되어야 할 알고리즘을 O(n)으로 표기한다. big-O 표기법은 수행 시간이 어떻게 변화하는지 표현해주는 것이므로 O(n)이 언제나 O(2n) 보다 나은 것은 아니다.

예를 들어, T(n) = 4 n²-2n+2의 경우, T(n)은 n²의 비율로 증가하며 이는 T(n) = O(n²)으로 표기할 수 있는 것이다. 

 

지배적이지 않은 항은 무시한다. O(n²+n)과 같은 수식이 있을 때는 어떻게 해야 할까? 우선 O(n²+n²)를 보자, 앞에서 상수항은 무시해도 된다고 언급했다. 따라서 O(n²+n²)은 O(2n²)이고 O(n²)이 된다. 이처럼 마지막 n²을 무시해도 된다면 그보다 작은 n항은 어떨까? 무시해도 된다. 즉, 수식에서 지배적이지 않은 항은 무시해도 된다.

O(n²+n)은 O(n²)이 된다.

O(n+log(n))은 O(n)이 된다.

예를 들어, f(n) = 10 log(n)+5(log(n))³+7n+3 n²+6 n³이라면, 이는 f(n) = O(n³)으로 표기할 수 있다.

하지만 언제나 수식에 합을 생략할 수 있는 것은 아니다. 예를 들어, O(B²+A)는 A와 B 사이에 존재하는 특별한 관계를 알고 있지 않은 이상 하나의 항으로 줄일 수 없다.

 

다음의 그래프는 흔히 사용되는 big-O 시간의 증가율을 나타낸다.

Comparison computational complexity.svg
입력 크기 n에 따른 함수의 연산 수 N에 대한 함수 그래프

 

알고리즘의 표기 방법

그렇다면 알고리즘에서 어떤 경우에 항을 덧셈으로 표기하고, 어떤 경우에 곱셈으로 표기하는지 알아보자.

for(int a : arrA)
{
	print(a);
}

for(int b : arrB)
{
	print(b);
}
//A의 일이 완전히 수행되고 난 뒤에 B의 일을 수행한다.

위의 알고리즘의 경우, 두 개의 for문이 각각 별개로 작업을 수행한다. 이러한 경우, 두 for문의 수행 시간을 더해야 한다. 즉, O(A+B)가 된다.

for(int a : arrA)
{
	for(int b : arrB)
    	{
    		print(a + "," +b);
	}
}
//A의 각 원소에 대해 B의 일을 수행한다.

위의 알고리즘의 경우, 두 번째 for문이 첫 번째 for문에 안에 위치해 있어, 첫 번째 for문이 한 번 수행될 때마다 두 번째 for문이 전체 수행된다. 이러한 경우, 두 for문의 수행 시간을 곱해야 한다. 즉, O(A×B)가 된다.

참고

+ Recent posts