티스토리 뷰

Unsupervised Learning

비지도 학습(Supervised Learning)이란, labeling이 되어있지 않은 자료들을 가지고 학습을 하는 것을 말한다.

보통 classification(clustering) 같은 곳에 쓰인다.

 

내 데이터들은 분류가 제대로 되지 않았다.

데이터 중 한 가지 주제(택시 동승)에 대해서만 정규표현식으로 분류가 됐다.

그 마저도 100% 정확도는 아니고 98%정도이다.

 

데이터가 4000개 글 정도 되는데, 하나하나 직접 분류하자니 시간이 많이 걸린다.

꽤 오래 한 것 같은데 이제 1000개 했다.

처음에 7개 정도로 분류할랬는데, 그것도 하다보니 애매하다.

경계도 모호하고 두 가지 이상의 종류에 속한 글도 있다.

사람이 해도 다 다를 분류이다.

 

여튼 인공지능을 학습시키기 전에 Supervised Learning을 해보기로 했다.

 

개요

1. 자연어처리(토큰화)

2. Word2Vec

3. 하나의 글 벡터화

4. K Means

 

이전에 쓴 글과 공통된 부분이 있다.

특히 자연어처리는 똑같다.

 

Word2vec을 사용하지 않아보려 했지만, 글이 길이가 다 제각각이다.

딱히 좋은 방법이 생각나지 않아서 그냥 똑같이 Word2vec을 사용했다.

자세한 건 저번 글에.

 

하나의 글 벡터화는, 각 글을 일정한 input size를 가진 벡터로 표현하는 것이다.

나중에 보면 알겠지만, 나는 평균을 사용했다.

 

K Means는 비지도 학습 중 클러스터링에 많이 쓰는 알고리즘이다. (그 외에 HCA, Expectation Maximization 등이 있음)

단순하다는 장점이 있다.

Means라는 뜻처럼 평균을 이용한다.

잘 나온 블로그를 소개한다.

이 클러스터링의 단점은, K 값(군집의 갯수)를 사전에 정해줘야 한다는 것이다.

 

0. Data 추출

Django에 있는 data를 추출하는 단계가 사실 먼저이다.

Django의 dumpdata를 이용했다.

# 데이터가 있는 서버
python manage.py dumpdata AppName.ModelName > Filename.json
# AppName, ModelName Filename은 사용자 설정

# 인공지능 학습 서버
import json
def read_django_json(filename):
    json_data = json.load(open(filename + '.json'))
    result = []
    for data in json_data:
        result.append([
        	data['pk'],
        	data['fields']['..field..']    # 필요한 field를 알맞게 넣음
        ])
    return result   

# 아래는 json을 csv로 변경할 필요가 있을 때
import csv
def json2csv(filename):
    json_data = json.load(open(filename + '.json'))
    csv_writer = csv.writer(open(filename + '.csv', 'w', encoding='utf-8', newline=''))
    for row in json_data:
        mes = row['fields']['..field..']
        mes = mes.replace('\n', ' ')    # \r, \n을 없애줘야 엑셀에서 보기 쉬움
        mes = mes.replace('\r', ' ')
        mes = mes.replace(',', ' ')     # 셀을 구분하는 ,과 구분할 필요가 있을 때
        csv_writer.writerow([row['pk'], mes])

 

1. 자연어처리

KoNLPy의 Mecab(은전 한 닢)을 이용했다.

from konlpy.tag import Mecab
def tokenize_sentense(text):
    mecab=Mecab()
    return mecab.morphs(text)

 

2. Word2Vec

간단하게 gensim을 이용했다.

from gensim.models import Word2Vec as w2v
model = w2v(tokenized_data, size=100, window=2, min_count=50, iter=20, sg=1)
    # 포스태깅된 컨텐츠를 100차원의 벡터로 바꿔라.
    # 주변 단어(window)는 앞뒤로 두개까지 보되, 코퍼스 내 출현 빈도가 50번 미만인 단어는 분석에서 제외해라.
    # CPU는 쿼드코어를 쓰고 100번 반복 학습해라. 분석방법론은 CBOW와 Skip-Gram 중 후자를 선택해라.
# tokenized_data는 2D list로 저장된 값입니다.
# 위의 함수(tokenized_sentence)의 output을 넣어주면 됩니다.

 

3. 벡터화

현재 내가 가진 값을 정리해보면,

data : [ ['이', '건', '하나', '의', '글', '입니다',], ['이', '건', '다른', '글', '이죠'], ... ]

model : data의 각각 하나의 token('이','건','하나','다른' 등)이 word2vec으로 학습된 모델

 

그런데 K Means에는 하나의 표본당 하나의 벡터값이 표현되어야 한다.

data와 model을 그대로 쓰면, data에서 하나의 글(['이', '건', '하나', '의', '글', '입니다',])에 100차원의 벡터값('이'도 100차원, '건'도 100차원, '하나'도 100차원 ... )이 여러 개있을 것이다.

글마다 길이도 제각각이다.

input을 일정하게 정해줘야 한다.

 

나는 고민하다 저번처럼 평균을 사용해보겠다고 결정했다.

미리 10개의 단어('택시', '공지', '분실' 등)를 주고, 각 token들('이', '건', ...)이 10개의 단어와 similarity를 어느 정도 이루는지의 평균이다.

말이 어려우니 표를 써보자.

1번 글 하나 ... 평균(쓸 값)
택시 0.3 0.3 0.4 0.2   0.3
공지 0.01 0.01 0.1 0.1   0.06
분실 0 0.3 0.6 0.9   0.45
...(총 10가지)            

1번 글은 각 토큰이 10개의 단어와 이루는 유사도의 평균이 [0.3, 0.06, 0.45, ...]이다.

그럼 평균으로 10차원의 벡터 하나를 만들 수 있다.

이런 식으로 4000개의 글에 각각 10차원 벡터를 구해줬다.

def get_n_tokens(data, model):
    result = []
    word_list = ['택시', '분실', '나눔', '판매', '설문', '공지', '상품', '질문', '연락', '구해요']
    for i,j in enumerate(data):
        dist = [0] * len(word_list)
        if not data[i]:   # 내용이 없는 글
            pass
        elif len(data[i]) == 1:     # 한 토큰으로 된 글
            for l,m in enumerate(word_list):
                try:
                    dist[l] = model.similarity(m, data[0])
                except:
                    continue
        else:     # 한 토큰 이상으로 된 글
            for idx, k in enumerate(j):
                if idx >= 50:   # 토큰 50개만 사용한다.
                    break
                for l,m in enumerate(word_list):
                    try:
                        dist[l] += model.similarity(m, k)
                    except:
                        continue
        result.append([ x/(idx+1) for x in dist])
    return result

try를 많이 쓴 이유는 혹시 에러가 날까봐...

데이터가 많으면 글의 종류도 천차만별이라 어떤 글이 나올지 모른다.

idx >= 50인 이유는, 너무 글의 길이가 길면 similarity의 평균이 점점 낮아진다.

다양한 단어가 나오기 때문이다.

그래서 50으로 한 번 해봤다.

 

return 값으로 길이가 len(data)인 list가 나온다.

list의 각 값은 10차원 벡터이다.

 

4. K Means

이제 K Means에 넣기만 하면 된다.

시각화하고 싶은데, 10차원 input이라 시각화하기 굉장히 어렵다.

 

그 전에, K값은 도대체 뭘로 해야할까?

몇 개로 clustering 해야할까?

여기에 잘 나와있다.

내가 많이 참고한 사이트이다.

import sklearn.cluster import KMeans as km
inertia = []    # cluster 응집도
for k in range(1,11):    # 10개까지
	model = km(n_cluster = k)
    model.fit(data)
    inertia.append(model.inertia_)
print(inertia)

inertia가 응집도라는 뜻인가보다.

응집도가 낮으면 낮을 수록 각 cluster가 잘 뭉쳐있다는 뜻이다.

그렇다고 응집도를 0으로 만드는 것이 좋을까?

그럼 classification의 의미가 없다.

마치 사람을 남,여로 나누다가 더 정확히 나눈답시고 사람의 DNA 별로 나누는, 그래서 각 집단에 단 한 명만 속하게 되는 것과 같다.

값을 잘 관찰해보고 응집도가 어느 정도 되면 안정화하는지, 그리고 제대로 나뉘는지를 보면 된다.

내 경우,

k 1 (한 집단) 2 3 4 5 6 7 8 9
inertia 76.791 30.938 24.271 22.220 20.301 18.936 17.756 16.694 15.967

3 정도 되니까 변화량이 확 줄기 시작했다.

k 값에 따른 응집도 변화

그래서 3으로 잡아봤다.

(너무 적게 분류되는 것 같지만...)

(사실 여러 개로 분류해봤지만 5 넘어가니까 막 섞이고 왜, 뭘로 나뉘었는지도 모르겠음.)

 

KMeans의 input은 2차원 list이다.

아까 만든 10차원 벡터들을 쓰면 된다.

def unsupervised_learning(data, n=3):
    # cluster 개수 -> km_model.inertia_ 를 보면 기울기 변화가 적어지는 부분이 3
    km_model = km(n_clusters = n, algorithm = 'auto')
    km_model.fit(data)    # 학습
    predict_list = km_model.predict(data)    # clustering
    group_list = [ [] for _ in range(n)]    # 잘 됐는지 확인하려는 list
    for i in range(n):
        group_list[i] = [ init_data[idx] for idx,j in enumerate(predict_list) if j == i]
        # init_data는 ['이건 하나의 글입니다', '이건 다른 글이죠', ...] 같은 초기 data
        # print(i, 'group is', group_list[i][:10])     # 각 cluster 당 10개씩 출력
    return predict_list, group_list

제대로 됐는지 한 눈에 보려면 그래프로 시각화하는 것이 가장 좋다.

그런데 10차원을 어떻게 봐야할 지 감이 안 잡혀서 pass...

앞에 2개만 떼서 2차원으로 그래프 그려봐도 괜찮을 것 같긴 하다. (다른 분들은 꼭 그렇게 하세요. 안 그럼 모름)

 

일단 쭉 살펴보니 0,2 클러스터는 잘 모르겠는데 1 클러스터는 잘 나뉜 것 같다.

택시 관련 글이 쫙 모여있었다.

 

오옷?!

그래서 클러스터 1을 택시관련 글이라고 가정하고, 기존의 정규표현식과 비교해봤다.

96.98%

 

97%면 저번 글과 비슷하다.

비지도학습도 꽤 정확히 나뉜다는 것을 알게 됐다.

물론 클러스터 0과 2가 뭔지 모르고 쓸 데도 없다는 것이 함정.

 

그냥 4000개 데이터에 label 붙이고 다중 분류 학습을 시키는 것이 나을 것 같다.

꽤 걸리겠지만... ㅠ

댓글