Machine Learning – Basic 1

(** 인터넷 익스플로러에서 보면 파이썬 코드 줄이 제대로 적용되어 보이지 않을 수 있습니다.**)

기계 학습 (Machine Learning)은 수집한 데이터의 특성을 분석하고 새로운 것을 예측하는 통계적 방법이다. 특성 벡터 (feature vector)는 수집한 데이터를 표현하는 속성들을 나열한 것이고, 레이블 (label)은 특성 벡터를 분류하기 위해 부여하는 속성이다.

지도 학습 (supervised learning)은 특성 벡터에 레이블을 직접 붙여 특성 벡터들을 분류하는 방식이고, 비지도 학습 (unsupervised learning)은 그러한 레이블 없이 특성 벡터만 가지고 분류하는 방식이다.

두 개의 특성 벡터가 얼마나 가까운지를 판단하는 방법으로 민코스키 거리가 있다. 민코스키 거리가 가까우면 두 특성 벡터가 동일한 대상일 가능성이 높고 반대로 거리가 멀면 다른 대상의 특성 벡터들일 것으로 판단할 수 있다. 예를 통해 민코스키 거리에 대해 알아보자.

먼저 특성 벡터의 예를 들어보자. 동물들을 나열하고 각각 알을 낳는지, 비늘이 있는지, 독성이 있는지, 냉혈 동물인지, 다리가 몇 개인지 나열한 속성들로 이루어진 벡터를 만든다. 각 특성 벡터를 갖는 동물이 파충류인지 판단하는 것에 관심이 있다.

지도 학습 방법에 따라 각 특성 벡터에 파충류인지 여부를 표시하는 레이블을 붙일 수 있지만, 아래는 특성 벡터들사이의 민코스키 거리를 구하는 예제 프로그램에 더 집중한다.

#동물들의 특성 벡터 (feature vector)
# 알을 낳음 1, 아니면 0.
# 비늘이 있으면 1, 아니면 0.
# 독성이 있으면 1, 아니면 0
# 냉혈 동물이면 1, 아니면 0
# 다리 갯수 N
cobra = Animal('cobra', [1,1,1,1,0])
rattlesnake = Animal ('rattlesnake', [1,1,1,1,0])
boa = Animal ('boa constrictor', [0,1,0,1,0])
alligator = Animal ('alligator', [1,1,0,1,4])
dartFrog = Animal ('dart frog', [1,0,1,0,4])
salmon = Animal ('salmon', [1,1,0,1,0])
python = Animal ('python', [1,1,0,1,0])


## Animal 클래스
class Animal(object):
    def __init__(self, name, features):
        self.name = name
        self.features = pylab.array(features)

    def getName(self):
        return self.name

    def getFeatures(self):
        return self.features

위 예제에서Animal 클래스를 새로 정의하여 동물 이름과 특성 벡터(리스트)를 멤버 변수로 갖는 객체를 만든다. cobra, rattlesnake, ... , python 동물의 특성 벡터를 Animal 클래스 객체로 만들었다.

이제 두 개의 특성 벡터 사이의 거리를 측정하는 한가지 척도인 민코스키 거리를 알아보자. 두 벡터 v1과 v2의 민코스키 거리는 다음과 같이 계산한다.

  • 두 벡터의 i번째 원소들의 차이를 구하고 그 차이의 절대값에 p승을 한 값을 모든 i에 대해 더한다. 이렇게 구한 최종 합의 1/p승이 민코스키 거리다.
  • 예를 들어, p=2인 경우 특성 벡터의 원소가 두 개이다. 예를 들어, [a1, a2]와 [b1, b2]라 가정하자. 이 특성벡터들을 2차원 좌표로 (a1을 x1, a2를 y1, b1을 x1, b2를 y2로) 해석하면 (p=2인) 민코스키 거리는 두 2차원 좌표의 거리 (유클리드 거리)와 일치한다.
def minkowskiDist(v1,v2,p):
    """Assumes v1 and v2 are equal-length arrays of numbers
        Returns Minkowski distance of order p between v1 and v2"""
    dist = 0.0
    for i in range(len(v1)):
        dist += abs(v1[i]-v2[i])**p
    return dist**(1.0/p)

minkowskiDist() 함수의 인자 v1과 v2는 각각 특성 벡터이다. p가 1이면 맨해튼 거리, p가 2이면 유클리드 거리 (우리가 보통 생각하는 거리의 개념)이다. 바둑판처럼 배치된 블록들에서 두 지점은 가로질러 도달할 수 없고, 오직 수평과 수직으로만 이동해야 한다. 이 상황에서 두 지점의 거리를 맨해튼 거리라고 부른다.

Animal 클래스에 p가 2인 민코스키 거리를 구하는 distance() 함수를 추가해보자. animal1.distance(animal2)와 같이 호출하여 호출 객체 animal1과 인자 animal2의 특성 벡터 사이의 민코스키 거리를 구한다. 이때 p를 2로 설정한다.

## Animal 클래스
class Animal(object):
    def __init__(self, name, features):
        ...

    def getName(self):
        ...

    def getFeatures(self):
        ...

    def distance(self, other):
        return minkowskiDist(self.getFeatures(), other.getFeatures(), 2)

이제 주어진 특성 벡터 데이터를 가지고 각 두 가지 쌍을 민코스키 거리로 비교하여 얼마나 가까운지 계산해보자. 계산 결과는, pylab 모듈에서 제공하는 테이블로 표시한다. 먼저 테이블 테두리를 그린 다음 주어진 특성 벡터들의 동물 이름과 동일한 특성 벡터들의 동물 이름을 테이블 행과 줄에 각각 나열하며 서로 교차하는 테이블 셀에 민코스키 거리를 표시한다.

import pylab

def compareAnimals(animals, precision):
    columnLabels = []
    for a in animals:
        columnLabels.append(a.getName())
    rowLabels = columnLabels[:]
    tableVals = []

    for a1 in animals:
        row = []
        for a2 in animals:
            if a1 == a2:
                row.append('--')
            else:
                distance = a1.distance(a2)
                row.append(str(round(distance, precision)))
        tableVals.append(row)

    print(type(tableVals))
    print(tableVals)
    
    table = pylab.table(rowLabels = rowLabels,
                        colLabels = columnLabels,
                        cellText = tableVals,
                        cellLoc = 'center',
                        loc = 'center')
                     '''   표 왼쪽 레이블이 출력되지 않아 주석 처리함
                     '''   colWidths = [0.2]*len(animals))
    table.scale(1,2.5)
    pylab.axis('off')

compareAnimals 함수는 animals 인자로 Animal 클래스 객체들 리스트를 받고, precision으로 민코스키 거리를 표시할 때 소수점 몇 자리까지 출력할 지를 지정한다. 각 객체에 포함된 특성 벡터를 두 개씩 뽑아 민코스키 거리를 구하고 그 결과를 테이블로 출력한다. 이때 pylab.table 함수를 사용하는데, 리스트의 리스트 타입으로 주어진 tableVals 데이터를 테이블의 열과 행에 배치한다.

compareAnimals 함수를 호출한 다음 pylab.show()를 실행하면 결과 테이블을 볼 수 있다.

>>> compareAnimals([cobra, rattlesnake, boa, alligator, dartFrog, salmon, python], 3)
<class 'list'>
[['--', '0.0', '1.414', '4.123', '4.243', '1.0', '1.0'], ['0.0', '--', '1.414', '4.123', '4.243', '1.0', '1.0'], ['1.414', '1.414', '--', '4.123', '4.472', '1.0', '1.0'], ['4.123', '4.123', '4.123', '--', '1.732', '4.0', '4.0'], ['4.243', '4.243', '4.472', '1.732', '--', '4.359', '4.359'], ['1.0', '1.0', '1.0', '4.0', '4.359', '--', '0.0'], ['1.0', '1.0', '1.0', '4.0', '4.359', '0.0', '--']]
>>> pylab.show()

각 동물의 특성 벡터들로 부터 구한 민코스키 거리를 해석할 때 고려해야할 사항이 있다. 예를 들어, 동물 특성 벡터의 속성 5가지 중에서 다리 갯수는 0과 4이지만, 나머지 네가지 속성은 0과 1이다. 따라서 값이 큰 속성이 민코스키 거리에 더 많은 영향을 줄 수 있다. 만일 모든 속성이 고르게 민코스키 거리에 영향을 주려면 특성 벡터의 각 속성 값을 상대적으로 조절할 필요가 있다. 다음 강의에서 이러한 점을 고려하는 방법을 소개한다.