심심해서 하는 블로그 :: 'Data Mining' 카테고리의 글 목록 (2 Page)

1. 의사 결정 트리(Decison Tree)


아키네이터라는 게임해보셨나요?? 이상한 요술 램프 지니같이 생긴 캐릭터가 여러 가지를 물으면서 사용자가 생각하는 내용을 맞추는 게임인데요. 짜증 날 정도로 잘 맞춰서 나중에는 엄청 말도 안 되는 걸 생각해서 정신 승리를 추구하려는 주변인+나 자신을 많이 봤습니다. (저도 그러면서 컴퓨터 따위가 감히 내 생각을 맞출 수가 없지 하고 만족했지만...)


저의 조심스러운 추측이지만 아키네이터는 의사 결정 트리를 사용한 게 아닐까 생각이 듭니다. 의사 결정 트리는 일종의 분류기인데 트리를 내려가면서 다양한 질문을 받게 됩니다. 그리고 그 질문의 응답에 따라 어떤 분류에 속하는지를 결론을 내려줍니다. 의사 결정 트리의 장점은 분류 결과를 사람이 쉽게 이해할 수 있다는 겁니다. 왜냐하면 의사 결정 트리는 사람이 생각하는 과정과 매우 유사하기 때문이죠. 예를 하나 봅시다.


 

만약 여러분이 소개팅 주선자이고 소개팅 상대를 물어보는 친구가 있다고 생각합시다. 소개팅을 받는 친구는 아주 설렌 마음에 자기의 이상형에 맞는지 여러분께 계속 물어볼 겁니다. 키는 큰지 돈은 많은지 차는 있는지 잘 생겼는지 성격이 어떤지 말이죠. 질문과 답이 오가는 과정에서 친구는 내 이상형이다 또는 내 이상형이 아니라고 결정을 내리게 됩니다. 그 결정과정을 보여주는 것이 위의 그림입니다. 그림을 보았을 때 어떠한 과정으로 분류를 해나가는지를 쉽게 알 수 있습니다.


의사 결정 트리의 단점은 과적합(Overfitting)이 되기 쉽다는 점입니다. 과적합은 훈련 데이터 내에서는 완전 딱 들어맞는 형태를 보여주지만 새로운 데이터에 대해서 예측 결과가 현저히 떨어지는 현상을 의미합니다.



2. 정보 이득(Infomation Gain)


정보 이득은 어떤 순서로 트리를 구성하는 것이 효과적인지를 선정하는 지표로 사용합니다. 위에 소개팅의 예를 들어서 정보 이득의 간단한 개요를 설명해 보겠습니다.



다시 여러분의 친구에게 주변에 좋은 사람을 소개팅하려고 합니다. 위에서 예를 들었던 거와 다른 순서로 질문을 합니다. 키가 큰지 노래를 잘 하는지 말이죠. 전부 다 마음에 드는 상황에서 마지막으로 남자냐고 물었더니 아니라고 대답해 버린 주선자.. 제 친구였으면 아마 반 죽여 버렸을 겁니다. 처음에 남자인지 물어봤으면 그 아래의 내용은 질문할 필요조차 없었는데 말입니다. 이처럼 질문들이 어떤 것이 더욱 결정적이고 변별력 있는지를 수치적으로 계산하는 것이 정보 이득입니다.


정보 이득을 계산하기 위해서는 섀넌 엔트로피라는 개념이 필요합니다. 일반적으로 물리나 수학을 공부하신 분이라면 엔트로피라는 개념이 무질서도를 의미한다는 것을 의미한다는 걸 알고 있을 겁니다. 만약 엔트로피가 크다면 무질서한 정도가 크다는 걸 의미합니다. 이해가 안 된다면 여러분이 지금 제멋대로 막 흩어 놓은 책상을 보시면 됩니다.(정말 지저분하죠?? 엔트로피가 크다는 겁니다.) 


섀넌 엔트로피는 비슷한 의미로 생각하시면 됩니다. 섀넌 엔트로피가 크다는 것은 데이터가 정렬이 아주 안 된 상태를 의미하고 반대로 작다면 데이터가 정렬이 잘 되어 있는 상태를 의미합니다. 살짝 멀미가 나시겠지만 수식으로 섀넌 엔트로피를 확인해 봅시다.



새넌 엔트로피는 위의 식으로 정의합니다. 그림에도 나와 있지만 p(x) 값이 크면 클수록 로그 함수의 결과가 작아짐을 알 수 있습니다. l(x) 값은 항상 음수이기 때문에 H(x)를 구할 때에 부호를 변경하기 위해 음수를 곱해주는 모습도 볼 수 있습니다. 이어서 정보 이득을 구하는 수식도 한꺼번에 계산하는 식을 살펴봅시다.


간단한 예제도 같이 만들어 보았습니다. 한 사람을 대상으로 이상형의 조건에 따라 질문을 하고 그에 대한 답을 얻는 과정입니다. 우선 전체 엔트로피 H(S)를 구하는 과정입니다. 최종 결과인 이상형인 확률과 이상률이 아닐 확률을 구합니다. 각각 4개 중에 2개씩 차지하니까 확률 p(x)는 1/2, 위의 섀넌 엔트로피 공식에 집어넣으니 1이 됩니다. 


이제는 정보 이득을 한번 구해 보겠습니다. 어떤 질문을 먼저 할까 고민하다가 "잘 생겼어?"라는 질문을 먼저 했을 경우에 얼마나 정보이득을 보는지 볼까요?



간단히 설명하면 잘생겼다는 속성을 가진 데이터는 총 3개이며 그중에 이상형인 확률과 아닐 확률을 통해 섀넌 엔트로피를 구합니다. 못생겼다도 마친가지로 진행하면 되겠죠?? 그럼 이번엔 노래를 잘하는가에 대한 질문을 먼저 했을 때 정보 이득을 구해 봅시다.



똑같은 방법을 적용해서 계산을 했더니 잘 생겼다에 대한 질문의 결과보다 정보 이득이 크다는 것을 알 수 있어요. 그 말은 "노래 잘해?"라는 질문이 "잘 생겼어?"라는 질문보다 변별력이 크다는 것을 의미해요. 따라서 트리를 작성할 때 제일 먼저 "노래 잘해?"를 먼저 물어보는 것이 좋겠네요.. 이처럼 정보 이득을 사용하면 질문의 순서를 정하는데 근거가 되어 줄 수 있어요.

3. 구현(python)

블로그를 방문하신 분들은 위의 이론들보다 그래서 어떻게 구현하는데? 가 더욱 중요할 겁니다.

구현 순서는 섀넌 엔트로피와 정보 이득을 계산하여 트리를 구성하는 것까지 진행해보겠습니다.


섀넌 엔트로피 계산

1

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from math import log
 
def shannonEntropy(dataSet):
    # 확률 계산할 때 분모로 들어가는 부분
    dataSize = len(dataSet)
    
    # 라벨별로 몇 개씩 있는 지 저장
    labelCount = {}
    entropy = 0.0
    
    # 데이터 집합 중 라벨 별로 몇 개씩 있는지 계산
    for dataLine in dataSet:
        curLabel = dataLine[-1]
        if curLabel not in labelCount.keys():
            labelCount[curLabel] = 0
        labelCount[curLabel] += 1
 
    # 확률 계산 및 섀넌 엔트로피 계산
    for key in labelCount:
        prop = float(labelCount[key]) / dataSize
        entropy -= prop * log(prop, 2)
    
    return entropy    
        
cs


정보이득 계산

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"""
    잘 생김, 노래 잘부름 등등 속성 중에서 하나를 선택합니다.
    그 속성 값을 제외한 나머지 값들을 결과로 반환하는 함수입니다.
    (정보이득을 계산하기 위함)
"""
def pickResult(dataSet, index, value):
    result = []
    for dataLine in dataSet:
        if dataLine[index] == value:
            tempVec = dataLine[:index]
            tempVec.extend(dataLine[index+1:])
            result.append(tempVec)
 
    return result
 
# 어떤 질문을 먼저 해야 효율적일지 선별하는 과정
def getBestInfoGainFeat(dataSet):
    baseEntropy = shannonEntropy(dataSet)
    bestInfoGain = 0.0
    index = 0
    numOfFeat = len(dataSet[0]) - 1
    for i in range(numOfFeat):
        featValueSet = set([value[i] for value in dataSet])
        newEntropy = 0.0
        
        # 정보이득을 구하는 과정
        for element in featValueSet:
            subDataSet = pickResult(dataSet, i, element)
            prop = len(subDataSet) / float(len(dataSet))
            newEntropy += prop * shannonEntropy(subDataSet)
        
        infoGain = baseEntropy - newEntropy
        
        if bestInfoGain < infoGain:
            bestInfoGain = infoGain
            index = i
    
    return index      
cs


트리 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 다수결 
def majority(dataSet):
    classCount = {}
    for data in dataSet:
        if data not in classCount.keys():
            classCount[data] = 0
        classCount[data] += 1
    sortedClassCount = sorted(classCount,key=operator.itemgetter(1), reversed=True)
    return sortedClassCount[0][0]
 
 
 
def createTree(dataSet, labels):
    classifyList = [data[-1for data in dataSet]
 
    # 정지조건 1. 모든 라벨 값이 동일한 경우
    if classifyList.count(classifyList[0]) == len(classifyList):
        return classifyList[0]
 
    # 정지조건 2. 더 이상 분류할 속성이 없는 경우 -> 다수결로 결정
    if len(dataSet[0]) == 1:
        return majority(classifyList)
 
    bestIndex = getBestInfoGainFeat(dataSet)
    bestIndexLabel = labels[bestIndex]
    Tree = {bestIndexLabel : {}}
    del(labels[bestIndex])
    featValue = set([example[bestIndex] for example in dataSet])
    for value in featValue:
        subLabels = labels[:]
        Tree[bestIndexLabel][value] = createTree(pickResult(dataSet, bestIndex, value), subLabels)
        
    return Tree 
cs




,

1. pyplot 시작하기

python에서 데이터 가시화를 위한 라이브러리로 matplotlib.pyplot을 사용한다. 파이썬이 데이터를 처리하는 데에는 일가견이 있는 언어인데 가시화 라이브러리도 있다니.. (R에만 있는 줄 알았는데)

일단 저는 윈도우에서 PyCharm을 설치해서 사용하기 때문에 윈도우에서 설치하는 과정만 소개하겠습니다.



위의 URL에서 사용중인 파이썬의 버전에 맞춰서 다운받은 다음 실행하시면 됩니다.


2. 그래프 그리기 : plot()

간단한 직선 그리기

그래프를 그릴 때 필요한 것은 X축과 Y축, 데이터겠죠?? 

일단 처음 그려보는거니까 간단한 걸로 한번 그려 봅시다.


1
2
3
4
5
6
7
8
9
import matplotlib.pyplot as plt
 
# y좌표 데이터만 있는 경우
plt.plot([123])
# (x,y) 좌표를 둘 다 사용할 경우
plt.plot([1,2,3],[2,4,6])
# axis([x축 시작, x축 끝, y축 시작, y축 끝])
plt.axis([0,4,0,7])
plt.show()
cs


첫 번째 그린 그래프가 파란색 선이에요. Y좌표만 입력받았을 경우에는 축의 시작점 부터 1씩 증가하는 꼴로

좌표가 자동으로 매칭되요. plot([1,2,3])의 경우에는, 따라서, (0,1), (1,2), (2,3)을 이은 선이 그려지는 거죠.


두 번째 주황색 그래프는 (X, Y) 좌표를 직접 입력한 경우에요. 앞의 리스트에는 X축의 좌표, 

뒤에는 Y축의 좌표로 사용해요. 따라서 plot([1,2,3],[2,4,6])은 (1,2), (2,4), (3,6)을 이은 을 그리게 되죠.


axis는 화면에 보여줄 축의 최대 값과 최소 값을 지정하는 함수로 주석에서 보셨듯이, 

이 그래프의 X축은 0~4 까지 Y축은 0 ~ 7까지로  설정된 것을 그래프를 통해서도 확인이 가능할 거에요.

그리고 그래프를 화면에 보여주는 show()함수를 사용하는 것으로 마무리!!!



이쁘게 그리고 싶어요..

plot에는 다양한 옵션들이 있어요. 그래프 색깔을 지정할 수 있는 옵션과 선의 스타일도 지정하는 옵션도 있어요. 저는 그 중에서 몇가지 종류만 사용해 보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
import matplotlib.pyplot as plt
import numpy as np
 
# x 좌표는 0부터 0.01씩 더해져 최대 5까지
= np.arange(0.05.00.01)
# y = x^2 그래프
plt.plot(x, x**2'r--')
# y = 2^x 그래프
plt.plot(x, 2**x, 'b')
plt.axis([0,6,0,40])
plt.show()
cs



 r

빨강 

 --

점선 

 b

파랑 

 o

동그라미 

g

초록 

^

삼각형 

y

노랑 

 *

별 


이 정도가 제가 알고 있는 스타일과 색상이에요. pyplot의 공식페이지에서 더욱 다양한 옵션들이 있어요.


하나의 창 여러 개 그래프

그래프를 이 때까지는 하나의 축상에서 두 개씩 그리는 것을 연습했어요. 그런데 여러 개의 그래프를 각각의 창으로 표현하는 방법도 있어요. 바로 subplot()을 이용하는 방법이죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt
import numpy as np
 
# x 좌표는 0부터 0.01씩 더해져 최대 5까지
= np.arange(0.05.00.01)
# y = x^2 그래프
plt.subplot(221)
plt.plot(x, x**2'r--')
# y = 2^x 그래프
plt.subplot(222)
plt.plot(x, 2**x, 'b-')
 
# y = x
plt.subplot(223)
plt.plot(x, x, 'g')
 
# y = 1/(x+1)
plt.subplot(224)
plt.plot(x, 1/(x+1))
 
plt.show()
cs



subplot()에 들어가는 숫자에 대하여 해설을 하자면 221은 전체창을 2 * 2로 나눈 뒤 1번째 그래프를 의미해요. 222는 2 * 2 중 2번째 이런 식으로 말이죠. 분할하고자 하는 화면의 갯수에 따라서 숫자를 다르게 쓰면 됩니다.


그래프를 상세하게 표기하기

여러 개의 그래프를 subplot으로 그리는 경우나 하나의 축에 그래프를 그리는 경우 제목이나 범례가 없으면 어떤 그래프인지 알기 힘들 겁니다. title() 함수와 legend() 함수를 이용해서 그래프의 제목과 범례를 지정할 수 있답니다. 그리고 X축과 Y축의 의미도 있으면 좋겠죠? X축과 Y축에 라벨을 붙여주는 xlabel(), ylabel()함수도 한 번 사용해보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt
import numpy as np
 
# x 좌표는 0부터 0.01씩 더해져 최대 5까지
= np.arange(0.05.00.01)
 
# 그래프의 타이틀 설정
plt.title('two graph')
plt.plot(x, x, 'g', label='y=x')
plt.plot(x, 2 * (x+1), 'r', label='y=2*(x+1)')
 
# X, Y축에 이름을 붙어주기
plt.xlabel('X Data')
plt.ylabel('Y Data')
 
# 범례 표시
plt.legend()
 
# 눈금 표시를 해주는 함수
plt.grid(True)
plt.show()
cs



3. Scatter 사용하기

산포도 그래프를 그리고 싶다!

분류기를 사용하여 데이터를 분류하는 경우 산포도 그래프로 데이터를 가시화하는 것을 많이 볼 수 있습니다. 데이터가 어느 쪽에 밀집해 있는지를 확인하기 쉽기 때문인데요. matplotlib.pyplot의 scatter() 함수를 이용하면 산포도 그래프를 손쉽게 그릴 수 있답니다. 예제 코드를 보시죠!



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import matplotlib.pyplot as plt
import numpy as np
 

x1 = [12112069]
y1 = [3710201630]
 
x2 = [571692]
y2 = [381023432]
 
# 그래프의 타이틀 설정
plt.title('two scatter')
plt.scatter(x1, y1, marker='s', c='r')
plt.scatter(x2, y2, marker='*', c='b')
# X, Y축에 이름을 붙어주기
plt.xlabel('X Data')
plt.ylabel('Y Data')
 
# 범례 표시
plt.legend()
 
# 눈금 표시를 해주는 함수
plt.grid(True)
plt.show()
cs


두 개의 데이터를 간단하게 준비하고 scatter() 함수를 사용해서 그래프를 그립니다. 속성 중에 marker 속성이 있는데 이 속성을 이용하면 네모, 세모, 별 모양 등의 모양을 지정할 수 있고, c는 색깔 속성을 의미합니다. 그 외 다양한 속성들은 pyplot.scatter() 페이지에서 확인할 수 있습니다!


4. annotation 이용하기

아니 이런 기능도 있어??

단순히 그래프 그리는 용도로 선 그래프나 산포도 그래프 등의 그래프를 그리는 함수만 있는 게 아니다. 그래프에 주석을 달거나 화살표를 그리는 등의 기능도 있다.  이 기능을 사용해서 트리형 그래프를 그릴 수도 있고 표시가 필요한 부분을 표시하는 등의 기능을 수행할 수 있습니다. 예제 코드를 한 번 볼까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import matplotlib.pyplot as plt
import numpy as np
 
x1 = [12112069]
y1 = [3710201630]
 
# box 속성
bboxType = dict(boxstyle="round4", alpha=0.8, fc='w')
# 화살표 속성
arrow = dict(arrowstyle='-|>', connectionstyle='arc3,rad=0.2', fc="w")
# 그래프의 타이틀 설정
plt.title('annotation')
plt.scatter(x1, y1, marker='*', c='r', s=40 )
 
# xy는 도착점, xytext는 text가 위치할 장소
plt.annotate('here!!', xy=(11,10), xytext=(1515), size=20,
             # 수평 정렬 / 수직 정렬
             ha='center', va='center',
             # 화살표와 박스의 속성을 설정
             arrowprops=arrow, bbox=bboxType, )
 
# 눈금 표시를 해주는 함수
plt.grid(True)
plt.show()
cs

우선 아까 scatter 함수를 연습할 때 사용한 데이터로 산포도 그래프를 그려줍니다. 저는 이 데이터 중에 (11, 10) 지점이 중요한 데이터라고 생각하고 표시를 해둘려고 합니다. 이럴 때 annotate() 함수를 사용합니다. 속성이 꽤 많이 필요한걸 볼 수 있는데요. 주석에 정리했지만 아래에 다시 정리 해보았습니다.


xy : 표시할 데이터가 있는 곳입니다. 쉽게 도착점이라고 생각합시다

xytext : 텍스트 박스를 둘 위치를 지정합니다. 이 때 박스의 중앙점의 좌표를 입력하면 됩니다.

ha / va : 각각 수평 / 수직 방향 정렬 속성입니다.

arrowprop : 화살표의 속성을 지정합니다. dict() 함수를 이용하여 지정합니다.

bbox : 텍스트 박스의 속성을 지정합니다. 마친가지로 dict()함수로 지정합니다.


박스와 화살표는 다양한 모양과 속성들이 있습니다. 모든 속성을 블로그에 옮겨 적는데에는 한계가 있어서 api 링크를 납깁니다. 긴 글 읽어주셔서 감사합니다!!


,

Python2.x를 사용하여 구현하였습니다. 3.x버전을 사용하시는 분들은 참고하시길 바랍니다.


1.  k-NN 알고리즘

가장 가까운 이웃을 찾아라

k-NN 알고리즘은 기존의 데이터들은 각각의 라벨이 붙어 있고, 라벨이 없는 임의의 데이터 X가 과연 어떤 종류에 들어가는지 분류하는 알고리즘이다. 이때 X와 기존의 데이터들의 거리를 모두 구한 후 그중 가장 가까운 k 개의 데이터의 라벨들 중 가장 많은 수의 라벨이 X 데이터의 라벨로 결정하게 하는 알고리즘이다.


일반적으로 k-NN 알고리즘을 적용하기 위해서는 수치형 값을 사용하는 편인데, 임의의 데이터와 기존 데이간의 거리를 계산하기 위해서이다. 특별히 훈련하는 과정도 없는 특징도 바로 모든 기존의 데이터와 거리를 계산하는 특징으로 나타나는 것이다.


2.  NumPy로 구현


알고리즘의 구현 순서

1) 파일로부터 데이터를 수집 

2) 정규화(Normalization)

3) 가시화(Visualization)

4) 테스트 데이터와 모든 데이터 간의 거리 계산 후 가장 근접한 k개 데이터 선정후 다수결로 결정

   (k-NN 알고리즘)


1) 파일로부터 데이터 수집

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from numpy import *
import operator
 
def file2matrix(filename):
    # 데이터에 저장된 Iris의 종류는 총 3개입니다.
    # 각각을 숫자로 매핑한 Dictionary를 구현합니다.
    Iris_dict = {"Iris-setosa" : 1"Iris-versicolor" : 2"Iris-virginica" : 3}
    
    # 파일을 열고 라인 단위로 파일을 읽어 드립니다.
    fr = open(filename)
    TextLine = fr.readline()
    
    # numOfData  : 데이터의 수를 의미
    # featMatrix : 파일로 읽어드린 데이터를 행렬로 저장하기 위해 영행렬로 초기화합니다.
    #              이 때 Feacture의 수가 총 3개이므로 Data의 수 X 3의 행렬을 만듭니다.  
    numOfData = len(TextLine)
    featMatrix = zeros((numOfData, 3))
 
    # labelVector : dataMatrix의 각 행에 대응하는 lebel을 저장합니다.
    labelVector = []
    index = 0
    
    for line in TextLine:
        # 각각의 데이터는 ','으로 구분되어 있습니다.
        # 따라서 ','을 기준으로 분리하여 줍니다.
        splitData = line.split(',')    
        # 파일에 데이터가 "종류,특징1,특징2,특징3"으로 저장되어 있으므로
        # 두번째 열부터 끝까지가 Feature입니다. 
        featMatrix[index, : ] = splitData[1:]
        labelVector.append(Iris_dictionary.get(splitData[0]))
        index += 1
    
    return featMatrix, labelVector   
cs


2) 정규화

정규화를 하는 이유는 각각의 특징(Feature)들이 서로 다른 단위를 가진다는 점에서 시작합니다. 
만약 달리기를 하는 사람의 데이터가 아래와 같이 있다고 가정하여 봅시다

Feature 1 : 지칠 때까지 최대한 달린 거리
Feature 2 : 100m 달리기 기록

Data 1 :  (10,300m, 13.40초, 장거리 선수)
Data 2 :  (5,000m, 11.30초, 단거리 선수)

Data 1 ~ Data의 거리 :

     


kNN은 Feature 간의 거리를 계산하는 알고리즘입니다. Data 1 ~ Data 2간의 거리 값에서 가장 큰 비중을

차지하는 것은 Feature 1이 되어 버리겠죠. 왜냐면 Feature를 구성하는 단위의 크기가 어마하게 차이가

나기 때문입니다. 100m 달리기 기록을 아무리 단축하여도 거리에는 큰 영향을 못 준다.


데이터를 분석하는 사람의 판단으로 Feature 1이나 Feature 2나 동일하게 중요하다고 판단을 내리는 경우

위의 같은 사례는 썩 좋아하는 상황이 아닙니다. 따라서 데이터를 일정 범위로 줄여 버리는 과정인 정규화 과정을 거칩니다.


정규화


분모에는 각 Feature의 범위가 들어가고 분자에는 data Set에 저장되어있는 Feature 값과 최소값의 차이값이 들어갑니다. 이 식을 활용해서 파이썬으로 마찬가지로 구현하면 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 위의 함수에서 이어서 사용합니다.
def normarlization(dataSet):
    # dataSet 중의 최소 값과 최대 값을 구합니다
    # 그리고 두 값의 차이로 범위를 구해줍니다.
    minVal = dataSet.min(0)
    maxVal = dataSet.max(0)
    ranges = maxVal - minVal
 
    # 정규화 결과를 저장하는 행렬을 준비합니다.
    normalDataSet = zeros(shape(dataSet))
    
    # dataSet의 행의 갯수를 저장합니다.
    rowCnt = dataSet.shape[0]
 
    # 정규화 공식에 맞게 적용하였습니다.
    normalDataSet =(dataSet - tile(minVal, (rowCnt, 1))) / tile(ranges, (rowCnt, 1)) 
    
    return normalDataSet
cs


3) 가시화

보기에도 좋은 떡이 맛도 좋다는 말이 있죠? 데이터도 숫자로 되어 있으면 뭐가 뭔지 의미를 쉽게 알 수 없을
겁니다. 파이썬에는 matplotlib라는 좋은 그래프 그리는 라이브러리가 있으니까 그것을 적극적으로 활용해 봅시다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 위의 함수에서 이어서 사용합니다.
def visualizeData(dataSet, labels):
    import matplotlib
    matplotlib.use('Agg')
 
    import matplotlib.pyplot as plt
 
    fig, ax = plt.subplots(1,1)
    ax.scatter(dataSet[:,0], dataSet[ :, 1], 15.0*array(labels), 15.0 * array(labels))
    fig.savefig('plot1.svg')
    
    fig, ax = plt.subplots(1,1)
    ax.scatter(dataSet[:,0], dataSet[ :, 2], 15.0*array(labels), 15.0 * array(labels))
    fig.savefig('plot2.svg')
    
    fig, ax = plt.subplots(1,1)
    ax.scatter(dataSet[:,1], dataSet[ :, 2], 15.0*array(labels), 15.0 * array(labels))
    fig.savefig('plot3.svg')
 
cs


짜잔.. 숫자로 봤을 때 보다 훨씬 좋죠?


4) k-NN 알고리즘

이제 마지막으로 k-NN 분류기를 구현해보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 위의 함수에서 이어서 사용합니다.
def classify0(testDataSet, trainDataSet, labels, k):
    trainDataSetSize = trainDataSet.shape[0]
    
    # 테스트 데이터와 훈련 데이터간의 거리를 계산하는 과정입니다.
    diffMat = tile(testDataSet, (trainDataSetSize, 1)) - trainDataSet 
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5
    
    # 오름차순으로 argsort를 합니다.
    # 거리가 짧은 순서대로 정렬이 됩니다.
    sortedDistIndex = distances.argsort()     
    
    # 다수결의 결과를 저장하는 곳입니다.
    classCount={}
    
    for i in range(k):
        # 본격 개표 방송
        # 오름차순으로 정렬한 데이터 중 k번째 데이터까지 라벨을 확인한 후
        # 투표 결과를 저장합니다.
        votelabel = labels[sortedDistIndex[i]]
        classCount[votelabel] = classCount.get(votelabel,0+ 1
    
    # 내림차순으로 정렬합니다
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    
    return sortedClassCount[0][0]   
 
cs


3.  마치면서..

k-NN은 단순하지만 데이터를 분류하는데 효과적인 알고리즘입니다. 하지만 데이터를 전부다 순회하면서

거리를 측정해야 하기 때문에 대규모의 데이터에 적용할 때는 시간이 오래걸리는 단점이 있습니다.

앞으로 여러 가지 알고리즘을 공부해서 포스팅 하겠습니다.


정말 긴 글 읽어 주셔서 감사합니다. 밑에 하트 한 번만 눌러주시면 저에게 큰 힘이 됩니다 ㅠㅠ



,

Andrew Ng 교수님의 Machine Learning 강좌(http://www.coursera.org)

모두를 위한 머신러닝 강좌(http://hunkim.github.io/ml/)를 참고하여 포스팅하였습니다.


1. 선형 회귀

대표하는 직선!!  

왼쪽 그림과 같이 데이터가 분포하여 있을 때 X=9일 때 Y 값을 예측을 하고자 한다. 

단순히 왼쪽 그림만으로 Y 값을 예측하기에는 근거가 부족하다. 

우리가 4지 선다형 문제를 찍는 데에도 근거가 있으면 (예를 들면 답이 유독 4가 없다는 등)

묘하게 설득력이 생긴다. 통계학자들은 이런 근거를 만들고자 오른쪽과 같이 직선을 그어보았다.

이 직선 주변으로 데이터를 나타나는 점들이 분포하여 마치 데이터를 대표하는 모델로 역할을 수행한다.

따라서 이제 X=9에서 Y 값을 예측하는 것은 이 직선에서 X=9를 대입하면 끝!! 근거도 있어서 맘도 편하다.

이와 같은 방법으로 데이터를 예측하는 모델을 선형 회귀라고 하고, 이 직선의 방정식을 가설 함수라 한다.



직선 그리는 것도 각양각색

위의 그림에서 직선의 모양, 즉 가설 함수에 따라서 X=9일 때 예측값 Y의 값이 달라짐을 알 수 있다.

따라서 대표하는 직선을 어떤 기준으로 그릴 거냐는 문제가 발생한다.  



위의 그림에서 d는 원래 데이터의 값 y1과 예측값 H(x1)의 거리를 의미한다.

H(X)의 함수식 Θ1, Θ0에 따라서 d값이 달라질 것이며 어떤 점에 대해서는 너무 멀고 

어떤 점에서는 너무 가깝지 않는, 즉 분산이 최소가 되는 값을 구하여야 한다.

이 때 d값의 분산을 Cost Function이라 하고 Cost Function 최소가 되는 Θ1, Θ0를 구하는 것이 목표이다.



산을 타고 내려갑니다.

위의 그래프는 Θ에 따른 cost(Θ)의 변화이다. 앞서 말했듯이 우리가 원하는 것은 

cost(Θ)을 최솟값으로 만드는 Θ값을 구하는 것이다.

이때 사용하는 알고리즘이 Gradient descent이다. 쉽게 말해서 산타고 내려가는 것!! 

Θ를 일정한 양만큼 계속 줄여가면서 바닥으로 내려가는 것인데 만약 바닥이라면 기울기가 0이 되므로

위의 박스친 편미분 부분이 0이 되어 더 이상 감소하지 않는다. 

그리고 위의 식에서 α을 Learning rate라고 한다. α의 값은 개발자가 직접 설정해야 한다.

만약 α값이 너무 크면 바닥까지 내려오지 않을 수도 있고, α값이 너무 작으면 반복하는 횟수가 증가한다.

따라서 α값을 변형하면서 적절한 값을 찾는 것이 중요하다.


2. Tensorflow 구현

소스 코드 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import tensorflow as tf
import matplotlib.pyplot as plt
 
# 학습 데이터
X_data = [3.3,4.4,5.5,6.71,6.93,4.168,9.779,6.182,7.59,2.1677.042,10.791,5.313,7.997,5.654,9.27,3.1]
Y_data = [1.7,2.76,2.09,3.19,1.694,1.573,3.366,2.596,2.53,1.2212.827,3.465,1.65,2.904,2.42,2.94,1.3]
 
numOfData = len(X_data)     # 데이터의 갯수
alpha = 0.001               # Learning Rate alpha
itr = 3000                  # 반복 횟수 1000회
 
theta1 = tf.Variable(tf.random_uniform([1], 0.03.0))      # theta1
theta0 = tf.Variable(tf.random_uniform([1], 0.03.0))      # theta0
 
# H(x) = theta1 * x + theta0
hypothesis = tf.add(tf.mul(theta1, X_data), theta0)
 
# cost() = 1/2m * sum(pow(H(x) - y))
cost_function = tf.reduce_sum(tf.pow(hypothesis - Y_data, 2)) / (2 * numOfData)
 
# Gradient Descent 알고리즘
optimizer = tf.train.GradientDescentOptimizer(alpha).minimize(cost_function)
 
# 변수 초기화
init = tf.global_variables_initializer()
 
# Tensorflow session 시작
with tf.Session() as sess:
    sess.run(init)
 
    # 3000회 반복 수행
    for i in range(itr):
        sess.run(optimizer)
        if i % 20 == 0:
            print(i, sess.run(cost_function), sess.run(theta1), sess.run(theta0))
 
    # 데이터 시각화
    plt.plot(X_data, Y_data, 'ro', label='Training Data')
    plt.plot(X_data, sess.run(theta1) * X_data + sess.run(theta0), label='Linear Regression Result')
    plt.legend()
    plt.show()
 
 
cs

결 과


 

,

이클립스와 R의 연동은 http://ssoonidev.tistory.com/10 을 참고해주세요


     


1. 준비물 

  1) 카카오톡 대화내용 File

      컴퓨터용 카카오톡 메신저에서 분석하고자하는 대화방에 들어가서 [Ctrl + S]를 누르면 

      대화내용을 Text 파일로 저장할 수 있다. 파일명은 편의상 kakao_temp.txt라고 하였다.


   2) R 스크립트

      주의해야할 것은 아무리 함수가 길더라도 줄바꿈을 하면 안된다. 

      이거 때매 결과가 안 나와서 뻘짓 정말 많이 했다 ㅠㅠ 이름은 form.R로 저장했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
library(KoNLP)
library(wordcloud)
library(RColorBrewer)
library(stringr)
useSejongDic()
 
data1 <- readLines("kakao_temp.txt", encoding = "UTF-8")
data1 <- gsub("손찬호""", data1) # 대화명1
data1 <- gsub("여보♥""",data1)   # 대화명2
data1 <- str_replace_all(data1, "[^[:alpha:]]"""#특수문자 Bye~~ 
data1 <- str_replace_all(data1, "[A-Za-z0-9]""")  #영문, 숫자도 Bye~~
data1 <- gsub("ㅋ""", data1)
data1 <- gsub("ㅠ""", data1)
data1 <- gsub("ㅜ""", data1)
data1 <- gsub("ㅎ""", data1)
data1 <- gsub("오전""", data1)
data1 <- gsub("오후""", data1)
data1 <- gsub("이모티콘""", data1)
data1 <- gsub("월요일""", data1)
data1 <- gsub("화요일""", data1)
data1 <- gsub("수요일""", data1)
data1 <- gsub("목요일""", data1)
data1 <- gsub("금요일""", data1)
data1 <- gsub("토요일""", data1)
data1 <- gsub("일요일""", data1)
data1 <- gsub("년""", data1)
data1 <- gsub("월""", data1)
data1 <- gsub("일""", data1)
data1 <- gsub("음성메세지""", data1)
data1 <- gsub("사진""", data1)
data1 <- gsub("프렌즈팝""", data1)
write(data1,"kakao_1.txt"
 
data2 <- readLines("kakao_1.txt")
data2 <- sapply(data1, extractNoun, USE.NAMES = F)
 
data3 <- unlist(data2)
data3 <- Filter(function(x){nchar(x)>=2 && nchar(x) <= 4}, data3)
 
wordcount <- table(data3)
<- head(sort(wordcount, decreasing = T), 100
write(a,"kakao_count.txt")
 
palete <- brewer.pal(8"Set2")
jpeg(filename = "cloud.jpg", width = 1000, height = 1000)
wordcloud(names(a), freq = a, scale=c(20,2), rot.per = 0.25, min.freq = 1, random.order = F, random.color = T, colors = palete)
dev.off()
cs


   3) 이클립스로 프로젝트를 생성하고 해당 자바 프로젝트 폴더 안에  준비물을 넣어 둔다.

     



2. REnginManager.java

   R 스크립트를 읽어 실행하는 클래스이다. 

   Rengine의 멤버 함수 eval(String text)에서 R스크립트 내용을 수행한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import java.io.BufferedReader;
import java.io.FileReader;
 
import org.rosuda.JRI.Rengine;
 
public class REngineManager {
    
    /**
     * @class R스크립트 파일을 REngine으로 실행하는 클래스
     */
 
    private Rengine re;
    private boolean isRunning = false;
    private enum SCRIPT_PROCESSING {sucess, fail};
    private SCRIPT_PROCESSING state;
    
    public REngineManager(){
        
    }
 
    public boolean isSucessful(){
        if(state == SCRIPT_PROCESSING.sucess)
            return true;
        else
            return false;
    }
    
    public boolean isRunning(){
        return isRunning;
    }
    
    public void createREngine(){
        String[] Rargs = {"--no-save"};
        re = new Rengine(Rargs, falsenull);
        System.out.println("Create R Engine...");
        
        if(!re.waitForR()){
            System.out.println("Loading R engine was failed");
            isRunning = false;
            return;
        }
        isRunning = true;
        System.out.println("Create R Engine Sucess !!");
        init();
    }
    
    private void init(){
        System.out.println("Install Package....");
        re.eval("install.packages(\"KoNLP\")");
        re.eval("install.packages(\"wordcloud\")");
        re.eval("install.packages(\"RColorBrewer\")");
        re.eval("install.packages(\"httr\")");
        System.out.println("Install Package Complete!!!");
 
    }
    
    public void readScript(String filePath){
        try{
            BufferedReader reader = new BufferedReader(new FileReader(filePath));
            String textLine;
            while(true){
                textLine = reader.readLine();
                if(textLine == null)
                    break;
                re.eval(textLine);
            }
            state = SCRIPT_PROCESSING.sucess;
            reader.close();
        }
        catch(Exception e){
            state = SCRIPT_PROCESSING.fail;
            e.printStackTrace();
        }
    }
    public void close(){
        re.eval("q()");
    }
}
cs

 

3. REnginTest01.java
   이 소스코드는 REngineManager가 잘 운영하는지 보기위해 작성되었다

   form.R의 결과물을 위의 R스크립트에서 cloud.jpg로 저장되어진다.. 

   싱글 쓰레드에서 자바코드와 REngine가 실행되어져서 

   REngine이 실행되어지는 동안에는 자바 코드의 수행이 이루어 지지 않는다. 

   좀 더 효율적인 운영을 위해서 멀티쓰레드 방식을 사용하는 것을 고려해야겠다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.awt.BorderLayout;
import java.awt.Color;import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
 
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.Border;
 
import REngine.REngineManager;
 
public class REngineTest01 extends JFrame {
    
    JLabel Image = new JLabel();
    public REngineTest01(){
        init();
    }
    
    private void init(){
        setSize(10001000);
        setLayout(new BorderLayout());
        setLocationRelativeTo(null); // 화면 중앙으로
        setTitle("카카오 워드클라우드");
        setResizable(false);
    }
    
    public void setImg(ImageIcon img){
        Image.setIcon(img);
        add("Center", Image);
        setVisible(true);
    }
    
    public static void main(String[] args){
        
        REngineTest01 testFrame = new REngineTest01();
        
        REngineManager manager = new REngineManager();
        manager.createREngine();
        System.out.println("Read form.R");
        manager.readScript("form.R");
        
        if(manager.isSucessful()){
            System.out.println("Complete");
            System.out.println("Loading Wordcloud Image...");
 
            ImageIcon cloudImg = new ImageIcon("cloud.jpg");
            testFrame.setImg(cloudImg);
 
        }
        else{
            System.out.println("Fail");
        }
 
    }
}
cs



,

역시 삽질은 즐거워... 매번 당하면서도 왜 피하질 못하니ㅠㅠ 


1. R 또는 RStudio를 실행하여 install.packages("rJava")를 실행하여 rJava를 설치한다.

2. [R의 설치 경로]\R-3.3.1\library\rJava\jri로 가면 jar파일 3개가 보인다. 얘네들을 써먹을 거다.




3. 이클립스를 자바 프로젝트를 하나 생성한다.  그 후 해당 프로젝트 오른쪽 마우스 클릭 > Properties 

  Java Build Path > Libraries > Add Extermal JARs.. 클릭 2번의 3개의 파일을 찾아 등록한다. 


4. 환경변수를 추가한다.

    1) R_HOME 추가 : R의 설치경로

       


   2) 환경변수 편집(Window 10.. Window 버전에 따라서 조금 설정화면이 다를 수 있어욤)

       


5. Example Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package Test;
 
import org.rosuda.JRI.REXP;
import org.rosuda.JRI.Rengine;
 
public class REngineTest02 {
    public static void main(String[] args) {
        String[] Rargs = {"--vanilla"};
        Rengine re = new Rengine(Rargs, falsenull);
        System.out.println("Create R Engine...");
        
        if(!re.waitForR()){
            System.out.println("Loading R engine was failed");
            return;
        }
        
        REXP a = re.eval("a <- 10"true);
        System.out.println(a.asDouble());
    }
}
 
cs






,

학교에서 간단하게 배운 R로 일상생활에서 무엇을 해 볼 수 있을까? 고민하다가

"그 동안 여자친구랑 했던 카톡들 중에 어떤 말이 가장 많았을까?" 하는 궁금증에 야심차게(?) 수행해보았다. 


1. 데이터 수집

   컴퓨터 버전 카카오톡에서 [Ctrl + S]를 누르면 이 때까지 상대방과 대화하였던 내용을 .txt파일 형태로 

   저장시켜준다. 그 후 R의 Working Directory 위치에 이 txt파일을 옮겨준다.

   이제 파일을 열어보면....

   허.. 완전 추위에 오돌오돌 떨던 시절의 대화부터 저장되어있다.. txt파일이 12.6MB라니... 

   양이 방대한지라 분석하는데 오래 걸리길래 7월부터 현재까지 대화내용을 분석해 보기로 했다.


2. 데이터 정제

   우선 한글과 워드클라우드로 출력하기 위한 라이브러리를  설치한다.


1
2
3
4
5
6
7
8
9
10
install.packages("KoNLP")
install.packages("wordcloud")
install.packages("RColorBrewer")
install.packages('httr')
library(KoNLP)
library(wordcloud)
library(RColorBrewer)
library(stringr)
 
useSejongDic()
cs


   7월부터 현재까지의 대화내용은 kakao1.txt 파일에 저장되어 있다. 

   특수문자나 숫자, 분석에 필요없는 내용에 대하하여 삭제를 해주는 과정이 필요하다. 

   data1 <- str_replace_all(data1, "[^[:alpha:]]", "") 에서 이 과정을 처리한다.

   카카오톡 계정명인 내 이름과 여자친구 저장명을 삭제하고 

   각 종 게임 아이템 주고 받을 때마다 나오는 메세지 내용을 삭제하고 ㅋㅋ, ㅎㅎ, ㅠㅠ 등등을 

   gsub("대체당할 문자", "대체 하고자하는 문자", 변수)를 활용하여 제거해준다.

   (우리는 프랜즈팝을 즐겨해서 프랜즈팝이 온데간데 등장한다)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
data1 <- readLines("kakao1.txt")
data1
data1 <- gsub("손찬호""", data1)
data1 <- gsub("여보♥""",data1)
data1 <- str_replace_all(data1, "[^[:alpha:]]""")
data1 <- gsub("부끄럽지만.. 내 (하트)받아주면 안될까?(아잉)""", data1)
data1 <- gsub("어디서 났냐고 다들 물어봐~""", data1)
data1 <- gsub("뭘좋아할지몰""", data1)
data1 <- gsub("단준비한선물""", data1)
data1 <- gsub("님의 핫한 (선물)""", data1)
data1 <- gsub("ㅋ""", data1)
data1 <- gsub("ㅠ""", data1)
data1 <- gsub("ㅜ""", data1)
data1 <- gsub("ㅎ""", data1)
data1 <- gsub("오전""", data1)
data1 <- gsub("오후""", data1)
data1 <- gsub("이모티콘""", data1)
data1 <- gsub("월요일""", data1)
data1 <- gsub("화요일""", data1)
data1 <- gsub("수요일""", data1)
data1 <- gsub("목요일""", data1)
data1 <- gsub("금요일""", data1)
data1 <- gsub("토요일""", data1)
data1 <- gsub("일요일""", data1)
data1 <- gsub("년""", data1)
data1 <- gsub("월""", data1)
data1 <- gsub("일""", data1)
write(data1,"kakao_1.txt") # 정제한 결과 저장
cs

3. 데이터 분석

    어느 정도로 필터링이 완료되면 이제 이것들의 갯수를 새어주는 코드를 작성한다.

    이 때 2글자 이상의 단어만 카운트가 되도록 하였다.

   

1
2
3
4
5
6
data2 <- readLines("kakao_1.txt")
data2 <- sapply(data1, extractNoun, USE.NAMES = F)
data3 <- unlist(data2)
data3 <- Filter(function(x){nchar(x)>=2}, data3)
wordcount <- table(data3)
wordcount
cs

4. 워드클라우드로 표현하기

   이제 집계된 결과를 기반으로 워드클라우드를 만들어 본다.


1
2
3
4
5
<- head(sort(wordcount, decreasing = T), 30)
palete <- brewer.pal(9"Set2")
wordcloud(names(a), freq = wordcount, scale=c(150,0.5), 
        rot.per = 0.25, min.freq = 1, random.order = F, 
        random.color = T, colors = palete)
cs

5. 결 과

   


7월 달에 여자친구가 시험때문에 같이 한숨을 많이 쉬었던지라 ㅋㅋㅋ 에휴가 1등이다 ㅋㅋㅋ

그외에도 곡성을 보고나서 뭐시중헌디 뭐시중헌디 거렸던것이  임팩트가 있었나보다..ㅋㅋ

차후에 다른 그래프로도 표현을 해보고 매달마다 자동적으로 분석해주는 프로그램을 만들어보고 싶다.

,