반응형

나무위키 워드벡터(word2vec) 만들기


1. 나무위키 데이터베이스 다운로드

https://mu-star.net/wikidb

미러링 사이트에서 찾았다. 다운로드 안되는 곳이 많음. 

7z압축되어 있는 최신 파일 다운로드하니 1.2기가 정도 됨.


+ 압축해제 

우분투에 7z 압축해제 프로그램이 없어서 p7zip을 설치하고 다운받은 압축 파일을 풀었더니  무려 8.3기가였다.

설치

#apt-get install p7zip

파일압축해제

#7zr x [파일명].7z


+파일이 너무 커서, 조금만 읽어보고 형태를 보자.

압축을 풀면 확장자가 JSON 포맷임.

# wc -l  namuwiki_20180326.json

0

줄 구분없이 한 줄로 구성된 텍스트 파일이다. more로 한 페이지만 보자.


# head namuwiki_20180326.json | more

다음과 같이 나온다.

[{"namespace":"0","title":"!","text":"#redirect \ub290\ub08c\ud45c\n","contributors":["namubot","R:hoon12560"]},{"nam

espace":"0","title":"!!\uc544\uc557!!","text":"[[\ud30c\uc77c:3444050440.jpg]]\n([[\uc2e0 \uc138\uacc4\uc218\uc758 \u

bbf8\uad81 2]]\uc5d0\uc11c \ub72c !!\uc544\uc557!!)\n{{{+1 \uff01\uff01\u3042\u3042\u3063\u3068\uff01\uff01 }}}\n\n[[

\uc138\uacc4\uc218\uc758 \ubbf8\uad81 \uc2dc\ub9ac\uc988]]\uc5d0 \uc804\ud1b5\uc73c\ub85c \ub4f1\uc7a5\ud558\ub294 \u

b300\uc0ac. [[\uc138\uacc4\uc218\uc758 \ubbf8\uad81 2 \uc81c\uc655\uc758 \uc131\ubc30|2\ud3b8 \uc81c\uc655\uc758 \uc1

31\ubc30]]\ubd80\ud130 \ub4f1\uc7a5\ud588\uc73c\uba70, \ud6cc\ub96d\ud55c [[\uc0ac\ub9dd \ud50c\ub798\uadf8]]\uc758 \

uc608\uc2dc\uc774\ub2e4.\n\n\uc138\uacc4\uc218\uc758 \ubaa8\ud5d8\uac00\ub4e4\uc774 \ud0d0\ud5d8\ud558\ub294 \ub358\u

c804\uc778 \uc218\ud574\uc758 \uad6c\uc11d\uad6c\uc11d\uc5d0\ub294 \ucc44\ucde8\/\ubc8c\ucc44\/\ucc44\uad74 \ud3ec\uc

778\ud2b8\uac00 \uc788\uc73c\uba70, \uc774\ub97c \uc704\ud55c \ucc44\uc9d1 \uc2a4\ud0ac\uc5d0 \ud22c\uc790\ud558\uba7

4 \uc81c\ud55c\ub41c \ucc44\uc9d1 \uae30\ud68c\uc5d0 \ubcf4\ub2e4 \ud070 \uc774\ub4dd\uc744 \ucc59\uae38 \uc218 \uc78

8\ub2e4. \uadf8\ub7ec\ub098 \ubd84\ubc30\ud560 \uc218 \uc788\ub294 \uc2a4\ud0ac \ud3ec\uc778\ud2b8\ub294 \ud55c\uc815

\ub41c \ub9cc\ud07c \ucc44\uc9d1 \uc2a4\ud0ac\uc5d0 \ud22c\uc790\ud558\ub294 \ub9cc\ud07c \uc804\ud22c \uc2a4\ud0ac \

ub808\ubca8\uc740 \ub0ae\uc544\uc9c0\uac8c \ub41c\ub2e4.\n\n 1. \ucc44\uc9d1\uc6a9 \uce90\ub9ad\ud130\ub4e4\ub85c \uc

774\ub8e8\uc5b4\uc9c4 \uc57d\ud55c \ud30c\ud2f0(ex: [[\ub808\uc778\uc800(\uc138\uacc4\uc218\uc758 \ubbf8\uad81 2)|\ub

808\uc778\uc800]] 5\uba85)\uac00 \uc218\ud574\uc5d0 \uc785\uc7a5\ud55c\ub2e4.\n 1. \ud544\ub4dc \uc804\ud22c\ub97c \u

d68c\ud53c\ud558\uba74\uc11c \ucc44\uc9d1 \ud3ec\uc778\ud2b8\uc5d0 \ub3c4\ucc29\ud574 \uc5f4\uc2ec\ud788 \uc544\uc774

\ud15c\uc744 \uce90\ub294 \uc911\uc5d0...\n ..............................

유니코드 인코딩인 것으로 보인다. title, text 필드가 제목과 내용인 것 같다.

JSON 파일이 너무 커서 한 번에 읽을 수도 없다. python은 ijson 패키지에서 처리한다고 하니 살펴본다.

https://pypi.org/project/ijson/


+시험삼아 데이터 몇개만 꺼내 확인해 본다.

import ijson


def load_json(filename):
count=0
with open(filename, 'r') as fd:
parser = ijson.parse(fd)
for prefix, event, value in parser:
if prefix.endswith('.title'):
print('index=', count+1)
print("\nTITLE: %s" % value)
elif prefix.endswith('.text'):
print("\nCONTENT: %s" % value)
count += 1
# debug
if count==10 :
break
if __name__ == "__main__":
print('load_json start')
load_json('/home/psychic/download/namuwiki_20180326.json')
print('load_json end')

글 제목과 내용이 번갈아가면서 출력된다. 알아서 디코딩되어 한글이 바로 잘 출력되었다.

데이터가 너무 많기 때문에 10개만 출력되게 디버깅코드를 추가하였다. 

얼마나 많은 데이터가 있는지 카운트를 해 보았다. (출력문이 있으면 상당히 느려지기 때문에 카운트만 계산한다. ) 카운트만 해도 오래 걸린다. 565985 개가 있다. (약 56만건)


+용이한 개발 및 디버깅 테스트를 위해 일부만 자르자. 원본 갖고 바로 작업하는 것은 상당히 비효율적이다. 개발시에는 디버깅용으로 라이트한 데이터만 다루고, 이후 개발이 다 되고 난 후에 전체데이터로 학습을 시키면 된다. 물론, DB가 크다는 가정하에 개발해야 될 것이다. (메모리 관리)


+디버깅용 DB 만들기 / 미니나무위키DB

일부만 자른다. 10메가로 하자.

# head -c 10000000 > mini_namu.json

파일을 열고 마지막 부분을 편집하여 JSON 형식을 맞춘다.

vi로 편집하려했더니 느려서 안되겠다. 그냥 커맨드로 append해야 겠다.

tail -c 800 mini_namu.json  | grep -E "title|text"

:"#redirect \uc81c18\ub300 \uad6d\ud68c\uc758\uc6d0 \uc120\uac70\n","contributors":["namubot","R:ttgrgt24","211.187.44.56"]},{"namespace":"0","title":"18\ub85c \uc81c\ud6c4","text":"[include(\ud2c0:18\ub85c \uc81c\ud6c4)]\n\n\u5341\u516b\u8def\u8af8\u4faf\n\n[\ubaa9\ucc28]\n== \uc124\uba85 ==\n[[\ud6c4\ud55c]]\ub9d0 [[190\ub144]], [[\uc5ed\uc801]] [[\ub3d9\ud0c1#s-1|\ub3d9\ud0c1]]\uc744 \ud1a0\ubc8c\ud558\uace0 [[\ud55c\ub098\ub77c#s-1.2|\ud55c\ub098\ub77c]] \ud669\uc2e4\uc744 \uad73\uac74\ud788 \ubc1b\ub4e0\ub2e4\ub294 \uac83\uc744 \ub300\uc758 \uba85\ubd84\uc73c\ub85c \ud558\uc5ec \uc77c\uc5b4\uc120 [[\uc81c\ud6c4]]\ub4e4\uc758 [[\uc5f0\ud569]]. \uc774\ub978\ubc14 '[[\ubc18\ub3d9\ud0c1 \uc5f0\ud569]]'\uc774\ub77c\uace0 \ubd80\ub974\uba70, \ud568\uace1\uad00\uc758 \ub3d9\ucabd \uc9c0\ubc2

#echo -n "0\"}]" >> mini_namu.json

#tail -c 100 mini_namu.json 

ud569]]'\uc774\ub77c\uace0 \ubd80\ub974\uba70, \ud568\uace1\uad00\uc758 \ub3d9\ucabd \uc9c0\ubc20"}] 



+미니 DB를 사용하여 워드 벡터 생성.

import ijson
import codecs
import sys, os
import time

import nltk

from gensim.models import word2vec
from konlpy.tag import Okt

import pickle


'''
JSON 나무위키 파싱

'''


def load_json(filename):
count=0
with open(filename, 'r') as fd:
# parser = ijson.parse(fd)
# for prefix, event, value in parser:
# print ( 'prefix=', prefix, 'event=', event )

# if prefix.endswith('.title'):
# # print('index=', count+1)
# # print("\nTITLE: %s" % value)
# elif prefix.endswith('.text'):
# # print("\nCONTENT: %s" % value)
# count += 1
# print ('total cnt=', count)
for item in ijson.items(fd, 'item'):
print(item['title'])
# print(item['text'])

def load_and_write_content(filename, filename2):
count=0
file = codecs.open(filename2, 'w', encoding='utf-8')
with open(filename, 'r') as fd:
for item in ijson.items(fd, 'item'):
count+=1
file.write('[[제목]]: ')
file.write(item['title'])
file.write('\n')
file.write('[[내용]]: \n')
file.write(item['text'])
file.write("\n")
file.close()
print('contents count=', count)


def make_wakati(f1, f2, f3):
file = codecs.open(f1, 'r', encoding='utf-8')
text = file.read()

twitter = Okt()
lines = text.split('\r\n')
results = []
for line in lines:
r = []
malist = twitter.pos(line) # part of speech (POS)
# pumsa : Noun, Josa, Verb, Eomi, Punctuation, Number, KoreanParticle,
for word, pumsa in malist:
if not pumsa in ['Josa', 'Eomi', 'Punctuation']:
r.append(word)
results.append((" ".join(r)).strip())
output = (" ".join(results)).strip()

with open(f2, "w", encoding="utf-8") as fp:
fp.write(output)

print('make word2vec')
data = word2vec.LineSentence(f2)
model = word2vec.Word2Vec(data, size=200, window=10, hs=1, min_count=5, sg=1, workers=3)
model.save(f3)
print('end word2vec')


if __name__ == "__main__":
print('load_json start')
print('curdir=', os.getcwd())
filedir = os.path.dirname( os.path.realpath(__file__))
os.chdir(filedir)
print('filedir=', filedir)

filename ='mini_namu.json'
filename2='mini_namu.txt'
filename3='mini_namu.wakati'
filename4='mini_namu.model'
filename5='mini_namu.pkl'

# load_json(filename)
# 나무위키 JSON DB에서 제목과 컨텐트를 스트링으로 기록한 파일 생성. (txt)
if True:
print('Create WordTxT')
t1=time.time()
load_and_write_content(filename, filename2)
t2=time.time()
print('End WordTxt ', 'time=',t2-t1)

if True:
print('Create Wakati')
t1=time.time()
make_wakati(filename2, filename3, filename4)
t2=time.time()
print('End Wakati', 'time=',t2-t1)

if True:
print('Model test')
t1=time.time()
model = word2vec.Word2Vec.load(filename4)
t2=time.time()
print(model.most_similar(positive=["조선", "일본"]))
print(model.most_similar(positive=["고려"]))
print(model.most_similar(positive=["고려"], negative=["왕"]))
print(model.most_similar(positive=["고려", "경제"]))
print(model.most_similar(positive=["RPG", "게임"]))
print(model.most_similar(positive=["임진왜란"]))
print(model.most_similar(positive=["왕", "여자"], negative=["남자"]))

print(model.similarity('고려','공민왕'))
print(model.doesnt_match("아침 점심 저녁 조선 밥".split() ) )
print(model.doesnt_match("총 무기 칼 게임 하늘".split() ) )

print('time=',t2-t1)

gensim.model.word2vec을 사용하여 간단하게 만들어서 테스트해서 아래의 결과가 나왔다.

[('1536년', 0.7473468780517578), ('음력', 0.7256854772567749), ('이이', 0.7070702910423279), ('도요토미', 0.7009462118148804), ('1552년', 0.6966617703437805), ('정조', 0.6919983625411987), ('문인', 0.6826009750366211), ('메이지', 0.6784789562225342), ('덴노', 0.6729492545127869), ('1337년', 0.6693580150604248)]

[('폐위', 0.7308624982833862), ('공민왕', 0.7183995246887207), ('정종', 0.7026641368865967), ('충숙왕', 0.7024295926094055), ('인종', 0.6962566375732422), ('즉위', 0.6933885216712952), ('충혜왕', 0.6890701651573181), ('명종', 0.6795019507408142), ('무신정권', 0.6763734817504883), ('원나라', 0.6668024659156799)]

[('마진', 0.2932665944099426), ('국호', 0.28764602541923523), ('발전', 0.27793973684310913), ('인종', 0.26443612575531006), ('말기', 0.2635525166988373), ('부족하다는', 0.2609822154045105), ('경향', 0.2581954002380371), ('1.6', 0.2573755383491516), ('줄어든', 0.2572077512741089), ('시기', 0.25421619415283203)]

[('천도', 0.6886128783226013), ('무신정권', 0.6836416721343994), ('인종', 0.6704342365264893), ('요나라', 0.6599131226539612), ('공민왕', 0.6575653553009033), ('권력자', 0.6571816205978394), ('도모', 0.6510674357414246), ('신변', 0.6495651006698608), ('폐위', 0.646056056022644), ('몽골', 0.6421409845352173)]

[('동봉', 0.6660709381103516), ('FPS', 0.6493582129478455), ('플래시', 0.6356097459793091), ('슈팅', 0.6346113681793213), ('로더', 0.629481852054596), ('플레이스테이션', 0.6227577924728394), ('TECHNIKA', 0.6164207458496094), ('DJMAX', 0.6132534742355347), ('온라인게임', 0.6100178360939026), ('오어', 0.606633186340332)]

[('1598년', 0.9288400411605835), ('이순신', 0.9017009735107422), ('1618년', 0.7677931189537048), ('1821년', 0.7676668167114258), ('도요토미', 0.7628185749053955), ('1865년', 0.7540280222892761), ('1839년', 0.7511029243469238), ('1868년', 0.7489482760429382), ('문인', 0.7481677532196045), ('1863년', 0.7473239898681641)]

[('속특', 0.4879986345767975), ('왕비', 0.48508623242378235), ('성종', 0.4771915674209595), ('선조', 0.4723415970802307), ('샤', 0.46159034967422485), ('세조', 0.4432491660118103), ('충혜왕', 0.43805891275405884), ('후궁', 0.4356191158294678), ('철종', 0.4354616701602936), ('문종', 0.43034279346466064)]

0.7184

조선

게임

데이터가 적기 때문에 오차가 좀 있지만 그런대로 결과는 나온다.




+실제 나무위키DB 텍스트 변환 (대용량 변환)

파일명들을 실제 나무위키 원본으로 바꾸고 실행해보자.


1단계. create wordtxt에서 encoding 에러 발생함.

open에 옵션으로 errors='ignore'로 무시하고 진행하도록 설정.

참고로 작업 서버 사양은... 

CPU : intel i7-7800X CPU 3.5GHz 12core

메모리 : 64기가

GPU: GeForce GTX 1080


시간이 오래걸릴 것으로 예상.  원본json이 8기가가 넘어서, 생성되는 것도 그 쯤 될 것이다.

7분정도 되니까 1기가까지 만들어짐. 

25분 걸려서 txt 파일 5기가 정도로 생성 완료하였다.


2단계.wakati 생성. 텍스트 파일을 읽어서 필요한 단어만 모아놓음.  (명사, 동사, 형용사, 부사..)  word2vec 클래스에서 에러가 발생할 것으로 생각됨. 메모리 부족??

VM 에러 발생. Memory.

# ulimit -c unlimited     ; 파일 사이즈 제한 해제도 미리 해 둠.

품사 정보 생성을 위해 Okt (구 Twitter)를 쓰는데, 거기서 발생한 것으로 보임. 라인 길이가 너무 크다. 2424660146 이정도된다.

메모리가 되는 선에서 적당한 크기로 잘라서 부분씩 처리하기로 함. 여기서는 20메가씩 스트링을 처리. 좀 더 크게 잡았더니 계속 실패해서 줄이고 줄임.  스트링을 자르는 방식도 여러가지인데, 그냥 구현하기로... 메모리를 상당히 내부적으로 많이 쓰는 듯 함. 

make_wakati 수정


# make wakati file
twitter = Okt(max_heap_size=1024*8) # heap size.. 8G
lines = text.split('\r\n')
t1=time.time()

print('making wakati start')
print('lines count=', len(lines))
fp = open(f2, "w", encoding="utf-8")
for line in lines:
linelen = len(line)
print("line length=", linelen)

# split.
# lineparts = map(''.join, zip(*[iter(line)] * 1000*1000*20)) # 20MB / 1line
blocksize = 1000*1000*20
if linelen % blocksize == 0:
blockcnt = int(linelen/blocksize)
else:
blockcnt = int(linelen / blocksize) + 1

for li in range(blockcnt):
if li==blockcnt-1:
linepart = line[li*blocksize:]
else:
linepart = line[li*blocksize:(li+1)*blocksize]
print('progress=', li, '/', blockcnt, len(linepart) )
malist = twitter.pos(linepart) # part of speech (POS)
# pumsa : Noun, Josa, Verb, Eomi, Punctuation, Number, KoreanParticle,
for word, pumsa in malist:
if not pumsa in ['Josa', 'Eomi', 'Punctuation']:
fp.write(word.strip())
fp.write(" ")
fp.close()
t2=time.time()
print('making wakati end time=', t1-t1)

다음 날 보니 wakati 파일 생성완료. 4G 크기.


3단계로 word vector 생성  진행. 얼마나 진행되었는지 알 수 없다. 언제 끝나지?

메모리 상황을 보면 가상 메모리로 파이썬이 66기가를 쓰고있다. cpu는 3개 쓰기로 설정해서 300% 풀로 나온다. 

기나긴 기다림 끝에 드디어 끝났다. 워드 벡터 생성에 걸린 시간은 41564 초. 시간으로 바꾸니 11시간 걸렸다.

전체 과정(와카티 생성, 워드 벡터 생성) 시간은 96503초. 시간으로는 26시간 걸렸다. 반드시 학습한 모델을 저장하는 코드를 넣어라. 저장을 깜빡했다면 이제까지 했던게 허사. 처음 부터 다시 ^^;;

모델 저장이 됐으니 이제, 필요할때 마다 로딩하여 질의를 하면 된다.

워드벡터 모델 파일 사이즈는 236메가. 그리고 넘파일파일이 3개 더 생긴다. 모델파일만 있으니 안돌아가는 걸로 봐서 넘파이 파일도 쓰는것 같다. 다 합쳐서 2기가 정도 된다.

모델 로딩에만 8초 정도 걸린다.


+ 나무위키 모델 테스트

질의 내용
print(model.most_similar(positive=["조선", "일본"]))
print(model.most_similar(positive=["고려"]))
print(model.most_similar(positive=["고려"], negative=["왕"]))
print(model.most_similar(positive=["고려", "경제"]))
print(model.most_similar(positive=["RPG", "게임"]))
print(model.most_similar(positive=["임진왜란"]))
print(model.most_similar(positive=["왕", "여자"], negative=["남자"]))

print(model.similarity('고려','공민왕'))
print(model.doesnt_match("아침 점심 저녁 조선 밥".split() ) )
print(model.doesnt_match("총 무기 칼 게임 하늘".split() ) )


Model test

[('대한제국', 0.6866917610168457), ('중국', 0.6801478862762451), ('우리나라', 0.6566961407661438), ('청나라', 0.6511589288711548), ('일제강점기', 0.6480269432067871), ('한국', 0.6324470043182373), ('조선국', 0.6295247077941895), ('경술국치', 0.6277073621749878), ('임진왜란', 0.623056173324585), ('역사', 0.6140305995941162)]

[('조선', 0.6692752838134766), ('고려왕조', 0.6515953540802002), ('공민왕', 0.6487005949020386), ('문종', 0.6360877752304077), ('현종', 0.6323516368865967), ('무신정권', 0.6292731761932373), ('태조', 0.6256691217422485), ('감안', 0.6252619624137878), ('冊封', 0.6246188879013062), ('충렬왕', 0.6229778528213501)]

[('감안', 0.4361225962638855), ('염두', 0.3996908664703369), ('제승방략', 0.39954277873039246), ('유념', 0.3989650011062622), ('바람직하지', 0.39659619331359863), ('해보아야', 0.39381834864616394), ('갖추자는', 0.39022499322891235), ('추세였음', 0.38095635175704956), ('타당', 0.3788178563117981), ('받아들여지기에', 0.37214767932891846)]

[('정책', 0.7264151573181152), ('동북아', 0.702474057674408), ('사회', 0.7002162933349609), ('정치', 0.6759592294692993), ('불평등', 0.6736841797828674), ('노동시장', 0.6685125827789307), ('경제정책', 0.668291449546814), ('한반도', 0.6653428673744202), ('민생', 0.6610323190689087), ('발전', 0.6543104648590088)]

[('TPS', 0.757465124130249), ('롤플레잉', 0.7494930028915405), ('로그라이크', 0.7455687522888184), ('FPS', 0.7437580823898315), ('플랫포머', 0.7379695177078247), ('RTS', 0.7236455082893372), ('모바일', 0.7205138206481934), ('MOBA', 0.718673586845398), ('SRPG', 0.7134039402008057), ('MMORPG', 0.7068561911582947)]

[('정유재란', 0.8903812170028687), ('병자호란', 0.8541854619979858), ('임란', 0.8498165607452393), ('왜란', 0.8400564193725586), ('정묘호란', 0.7854044437408447), ('1592년', 0.7841730117797852), ('의병장', 0.781627893447876), ('류성룡', 0.7787343263626099), ('왜군', 0.7751662731170654), ('광해군', 0.7695599794387817)]

[('왕인', 0.6398370265960693), ('준왕', 0.6377674341201782), ('왕중', 0.5966447591781616), ('극', 0.5917797088623047), ('최후', 0.5905873775482178), ('선왕', 0.5905437469482422), ('왕전', 0.588803231716156), ('中山王', 0.5885385274887085), ('楚穆王', 0.5835995674133301), ('南平王', 0.5762388706207275)]

0.648701

조선

게임

model.wv.get_vector('노트북') 을 해보니 200개의 수가 나온다. 200차원. 모델 생성시에 사이즈를 200으로 줘서 그렇다. 값이 클 수록 표현하는 정보가 큰데, 학습 시간이 더 오래 걸린다.

그런대로 재미가 있다.

다음엔 이걸로 무엇을 할 수 있을지 생각해 봐야겠다.
















반응형

+SVM (support vector machine)을 사용하여 간단하게 바이너리 분류하기


간단하게 말하면 가장 마진이 크도록 최적의 구분 라인 긋기. 약간의 판단 오류가 있더라면 데이터의 밀집 분포 정도에 따라 최적의 라인은 다를 수 있다. 판단오류가 하나도 없도록(과적합) 구분 라인을 만들면 오히려 임의의 값 예측시 정확도가 더 떨어질 수 있다. 


X 데이터 : 피쳐2개. 

Y데이터 : 라벨 (0, 1) binary 분류값.

SVM으로 최적 구분 라인 계산.

임의의 값에 대해 Y데이터 예측하기.


from sklearn.svm import SVC
import matplotlib.pyplot as plt
import numpy as np

# line split SVM

X = np.array([[1,2],
[5,8],
[1.5,1.8],
[8,8],
[1,0.6],
[9,11]])
y = [0,1,0,1,0,1]
clf = SVC(kernel='linear'1.0# C is error penalty. if C is bigger then more strictly but margin will be narrow...
clf.fit(X,y)
print(clf.predict([[0.58,0.76]]))
print(clf.predict([[10.58,10.76]]))

w = clf.coef_[0]
print(w)
a = -w[0] / w[1]

xx = np.linspace(0,12)
yy = a * xx - clf.intercept_[0] / w[1]

h0 = plt.plot(xx, yy, 'k-'label="non weighted div")

plt.scatter(X[:, 0], X[:, 1], = y)
plt.legend()
plt.show()

[해설]
x데이터의 벡터는 2차원이고 구분 레이블은 y로 0, 1로 두 그룹으로 나눈다.
SVC의 리턴값으노 clf로 피팅을 하게 되면
clf.predict()로 입력값 x에 대한 구분 레이블 y를 예측할 수 있다.

x데이터의 벡터요소(피쳐)를 x0, x1라 하면
새로운 좌표계의 1차원 좌표축을 z라하면
z = f (x0, x1) 
z>=0 이면 레이블1, z<0이면 레이블 0가 될 것이다. (z=0이면 1일까? 맞다.)

clf의 coef_로 w0, w1를 얻고, intercept로 b를 얻는다.
w0, w1는 입력값의 가중치. b 는 bias

z = w0*x0+w1*x1 + b 가 된다.
따라서 위 라인 (z=0으로 놓는다.)을 기준으로 분류한다.

위 라인을 x1(Y축)에 대해 정리하면
기울기는 -w0/w1
y절편은 -b/w1가 된다.
이 라인이 구분선이 된다.





반응형



++ Linear Regression with multi-variables
X의 feature들이 여러 개 x1, x2, ...

import tensorflow as tf
# y = x1*2+x2+x3 training
# W => 2,1,1, b=0
x_train = [[1,2,3],[2,3,4],[3,4,5],[4,5,6], [1,3,5]]
y_train = [[7],[11],[15],[19], [10]]

#placeholder
x = tf.placeholder(tf.float32, shape=[None,3])
y = tf.placeholder(tf.float32, shape=[None,1])
# variables
W = tf.Variable(tf.random_normal(shape=[3,1]), name='W')
b = tf.Variable(tf.random_normal(shape=[1]), name='b')

# model
hypothesis = tf.matmul(x, W) + b
cost = tf.reduce_mean( tf.square(hypothesis-y) )
# train
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
train = optimizer.minimize(cost)
sess = tf.Session()
sess.run(tf.global_variables_initializer())
# test
# hres= sess.run(hypothesis, feed_dict={x:x_train, y:y_train})
for i in range(3001):
    _, pcost, _W, _b= sess.run([train, cost, W, b], feed_dict={x:x_train, y:y_train})
    if i%500==0:
        print (i, pcost, _W, _b)

# test
print( sess.run(hypothesis, feed_dict={x: [[4, 2, 3]]}))





반응형



+TensorFlow

machine learning 오픈소스 라이브러리. python 기반.
계산 그래프(computational graph) 를 생성하여 텐서가 흐르는 프로그램


+ 로딩
import tensorflow as tf

로딩이 안되면 tensorflow를 설치한다.



+버전 확인
tf.__version__
'1.1.0'


+텐서 (Tensor)
Rank ; 차원
여기서는 모든 자료형이 텐서다. 원래는 정적타입의 n차원 배열 or list ; 랭크값이 차원수. 1차원은 벡터, 2차원은 매트릭스, 3차원부터 텐서다.
rank 0 ; scalar 1
rank 1 ; vector [1,2,3]
rank 2 ; matrix [[1,2],[3,4]]
rank 3 ; 3-tensor [ [[1],[2]], [[3],[4]] ]
rank n ; n-tensor

Shape ; dimension number. 
rank 1 ;  shape [4]
rank 2 ;  shape [4,2]  ; 4x2 행렬
rank 3 ;  shape [3,4,5]    ; 3x4x5 행렬

DataTypes
tf.float32     ; 대부분 함수에서 디폴트 자료형임.
tf.float64
tf.int8, 16, 32, 64
tf.uint8, 16
tf.string
tf.bool
tf.complex64, 128



+ 기본 예제, 작동 확인:   hello world -> hello tensorflow

import tensorflow as tf
hello = tf.constant('Hello tensorflow!')
sess = tf.Session()
print ( sess.run(hello) )
-------------
b'Hello tensorflow!'
위와 같이 출력됨.
b는 bytes를 의미. 


------------------------------------------------------------------
+ 간단한 더하기 계산그래프 테스트

3+4를 계산하는 그래프.

import tensorflow as tf
# build graph(tensor)
node1=tf.constant(3.0, tf.float32)
node2=tf.constant(4.0)
node3 = tf.add(node1, node2)
# 위 값을 print("node1=",node1, "node2=",node2, "node3=",node3)로 출력해 봐야 주소와 타입만 찍힌다. 값을 모름.
# feed data and run graph, update vars and return.
sess = tf.Session()
print ( sess.run( [node1, node2] ) )          # node1, node2를 구하여 출력.
print ( sess.run( [node3] ) )     # node3인 계산 결과를 출력. (윗줄은 데이터 값을 확인하기 위한 디버깅용)
sess.close()



반응형


+ Tensorflow로 XOR  학습을 구현하기


XOR 학습을 수동으로 계산하지 않고, tensorflow의 api를 사용하면 심플해진다.

복잡한 네트웍도 쉽게 구현이 가능하다.


노드의 개수 및 레이어를 2-4-1 로 구성.

입력층에 노드 2개(feature 개수. x1, x2)

은닉층에는 4개 

출력층은 1개의 노드. (Y)

back-propagation을 위한 골치 아픈 작업(미분)들을 할 필요없이 api 하나로 학습 가능! activation function도 원하는대로 쉽게 변경하고, 학습 알고리즘은 쉽게 바꿀 수 있다.


#!/usr/bin/env python3

# -*- coding: utf-8 -*-

"""

Created on Tue May 23 14:56:53 2017


@author: crazyj

"""



import numpy as np

import tensorflow as tf


# trainint set

X_train = np.array( [[0,0], [0,1], [1,0], [1,1]])

T_train = np.array( [[0], [1], [1], [0]] )



# placeholder

X = tf.placeholder(tf.float32, [None, 2])

T = tf.placeholder(tf.float32, [None, 1])


# variable

W1 = tf.Variable(tf.truncated_normal([2,4]))

b1 = tf.Variable(tf.zeros([4]))

W2 = tf.Variable(tf.truncated_normal([4,1]))

b2 = tf.Variable(tf.zeros([1]), dtype=tf.float32)


# model

A1 = tf.matmul(X, W1)+b1

Z1 = tf.sigmoid(A1)

A2 = tf.matmul(Z1, W2)+b2

Z2 = tf.sigmoid(A2)


learn_rate = 0.1

Cost = tf.reduce_mean(tf.reduce_sum(tf.square(Z2-T), 1))

train = tf.train.GradientDescentOptimizer(learn_rate).minimize(Cost)


predict = Z2


sess = tf.Session()

sess.run(tf.global_variables_initializer())

for i in range(5000):

    _train, _Cost = sess.run([train, Cost], feed_dict={X:X_train, T:T_train})

    print( "cost=", _Cost)

    

_predict = sess.run([predict], feed_dict={X:X_train})

print("predict=", _predict)

print("result=", np.array(np.array(_predict)>=0.5, np.int))




결과

cost= 0.021748

cost= 0.0217359

cost= 0.0217239

predict= [array([[ 0.10571096],

       [ 0.86153752],

       [ 0.84178925],

       [ 0.17739831]], dtype=float32)]

result= [[[0]

  [1]

  [1]

  [0]]]


+ 코드 설명

# trainint set

X_train = np.array( [[0,0], [0,1], [1,0], [1,1]])

T_train = np.array( [[0], [1], [1], [0]] )

훈련 데이터는 당연히 xor의 입력 조합에 따른 결과를 훈련시킨다. (0,0) -> 0 , (0,1) -> 1 , (1,0) -> 1 , (1,1)->1


# placeholder

X = tf.placeholder(tf.float32, [None, 2])

T = tf.placeholder(tf.float32, [None, 1])

플레이스 홀더는 텐서플로우내에서 돌아갈 입출력 변수들이라고 보면 된다. 노드에서 변수가 되는 것을 X 입력층은 (?,2) 매트릭스 크기(로우는 임의의 개수, 컬럼은 2개(x1,x2)), 출력층 T는 (n,1) 매트릭스 형태로 선언한다.


# variable

W1 = tf.Variable(tf.truncated_normal([2,4]))

b1 = tf.Variable(tf.zeros([4]))

W2 = tf.Variable(tf.truncated_normal([4,1]))

b2 = tf.Variable(tf.zeros([1]), dtype=tf.float32)

텐서 플로우 변수들을 정의한다. 이것은 그래프에 구성될 노드들의 변수의 형태를 정의한다.

네트웍을 2-4-1로 구성할 것이므로 여기에 따른 weight를 담을 변수와 bias 변수의 형태는 다음과 같다.

2개 노드는 placeholder로 X이고, 2-4연결되는 W(웨이트)는 2행(입력노드개수) 4열(출력노드개수) 매트릭스이다. bias는 4개(출력노드개수)이다.

4-1로 연결되는 파트의 W는 4x1 이렇게 b는 1개 이렇게 구성한다. 초기값들은 랜덤하게 채워준다. bias는 0으로 초기화해준다.


# model

A1 = tf.matmul(X, W1)+b1

Z1 = tf.sigmoid(A1)

A2 = tf.matmul(Z1, W2)+b2

Z2 = tf.sigmoid(A2)

이제 빠진 히든 노드들과 출력층 노드들을 구성한다.

2-4-1네트웍에서 2개는 X, 4개는 A1으로 정하고,

A1 = X x W1 + b1 으로 정의한다.

Z1=은 A1에 활성화함수 sigmoid를 적용한다.


A2=Z1 x W2 + b2로 정의한다. 앞 노드의 출력 결과에 weighted sum이다.

Z2 = A2에 sigmoid를 적용한 것으로 최종 output이다.


learn_rate = 0.1

Cost = tf.reduce_mean(tf.reduce_sum(tf.square(Z2-T), 1))

train = tf.train.GradientDescentOptimizer(learn_rate).minimize(Cost)

predict = Z2

이제 학습방식을 정한다.
cost function은 오차 제곱법을 쓰고, 학습방식은 gradient descent를 사용하여 훈련 그래프를 만든다.
예측값은 최종 노드 출력인 Z2가 된다.


sess = tf.Session()

sess.run(tf.global_variables_initializer())

for i in range(5000):

    _train, _Cost = sess.run([train, Cost], feed_dict={X:X_train, T:T_train})

    print( "cost=", _Cost)

텐서플로우로 학습을 돌린다. 세션을 만들고, 초기화하여 5000번 학습한다. 그래프의 꼭지인 train을 집어 넣고, feed_dict로 플레이스 홀더 X, T에 훈련 데이터를 넣는다.  학습1회마다 cost값을 출력해 본다. 


_predict = sess.run([predict], feed_dict={X:X_train})
학습 완료후, 학습이 잘 되었는지  입력값 X_train을 넣고 출력값을 생성한다.

print("result=", np.array(np.array(_predict)>=0.5, np.int))

최종 결과로 시그모이드 함수에서 0.5이상이면 1로 미만이면 0으로 출력하여 binary 분류를 한다.





+ Recent posts