no image
[Python] 연관분석 실습 _ 네이버 지식인 크롤링
1. 데이터 가져오기 # 고양이 질문 데이터 from bs4 import BeautifulSoup #HTML 파싱 import urllib #검색어 인코딩 import time #슬립 사용 import requests #HTML 가져오기 from tqdm import tqdm_notebook #크롤링 결과 저장할 변수 present_candi_text = [] html = 'https://search.naver.com/search.naver?where=kin&sm=tab_jum&ie=utf8&query=' + urllib.parse.quote('고양이') + '&start=' for n in range(1, 1000, 10): response = requests.get(html + str(n)) soup..
2024.03.15
no image
[Python] 감성 분석 실습
1. IMDB 영화평을 이용한 지도학습 기반 감성분석 - https://www.kaggle.com/c/word2vec-nlp-tutorial/data - 데이터 구조 id : 유저 아이디 sentiment : 감성. 긍정 1, 부정 0 review : 리뷰 레이블이 있는 데이터를 가지고 범주를 예측하는 것과 동일하지만, 자연어는 피처가 문장으로 주어지기 때문에 문장을 피처 벡터화 작업해줘야 하는 것이 다르다. 모든 단어를 각각의 피처로 만들고 각 문장은 피처의 존재 여부를 데이터로 소유한다. 1. 데이터 읽어오기 import pandas as pd review_df = pd.read_csv('./data/IMDB/labeledTrainData.tsv/labeledTrainData.tsv', header=..
2024.03.14
no image
[Python] 차원 축소
1. 차원 축소 머신 러닝의 많은 훈련 샘플들은 여러 개의 특성을 가지고 있다. 특성의 개수가 많으면 훈련이 느려지고 좋은 솔루션을 찾기 어려워진다. = 차원의 저주 실전에서는 특성 수를 크게 줄여 불가능한 문제를 가능한 범위로 변경해야 하는 경우가 많다. 1) 차원의 저주 - 고차원에서는 많은 것이 상당히 다르게 동작한다. 사각형 안에서 점을 무작위로 선택하면 경계선에서 0.001 정도 거리에 존재할 확률이 0.4% 정도 되는데 1만 차원이 되면 경계선이 늘어나서, 경계선 근처에 몰려 존재할 확률이 99.999999% 보다 커짐 대부분의 훈련 데이터가 서로 멀리 떨어져 있게 되고, 새로운 샘플도 멀리 떨어져있을 가능성이 높아진다. 예측을 위해서는 훨씬 더 많은 외삽을 수행해야 한다. 외삽: 변수의 값을..
2024.03.12
no image
[Python] 지도학습 연습 _ 범주형 데이터 이진분류
https://www.kaggle.com/competitions/cat-in-the-datCategorical Feature Encoding Challenge | Kagglewww.kaggle.com 1. 범주형 데이터 이진분류문제 유형: 이진분류평가지표: ROC AUC미션은 Target에 속할 확률데이터 인위적으로 만든 데이터 피처와 타겟에 대한 의미를 알 수 없음 제공되는 데이터는 전부 범주형 bin_: 이진 범주 nom_: 명목형 범주 ord_: 순서형 범주 day와 month는 날짜 피처 데이터 읽어보기데이터를 읽어봤더니 각 행을 구분하는 id 속성이 존재한다.DataFrame을 만들 때 각 행을 구분하는 컬럼이 있는 경우는 index로 설정하는 것이 좋다.import pandas as pd tr..
2024.03.07
no image
[Python] 머신러닝_앙상블
1. 개요- 무작위로 선택된 수천명의 사람에게 복잡한 질문을 하고 대답을 모은다고 가정하면 이렇게 모은 답이 전문가의 답보다 나을 가능성이 높은데, 이를 대중의 지혜 혹은 집단지성이라고 한다. - 하나의 좋은 예측기를 이용하는 것보다 일반적인 여러 예측기를 이용해서 예측을 하면 더 좋은 결과를 만들 수 있다는 것을 앙상블 기법이라고 한다. - Decision Tree는 전체 데이터를 이용해서 하나의 트리를 생성해서 결과를 예측하지만, Random Forest는 훈련 세트로부터 무작위로 각기 다른 서브 세트를 이용해서 여러개의 트리 분류기를 만들고 예측할 때 가장 많은 선택을 받은 클래스나 평균 이용 - 머신러닝에서 가장 좋은 모델은 앙상블을 이용하는 모델 2. 투표기반 분류기- 분류기 여러개를 가지고 훈..
2024.03.07
no image
[Python] 회귀 - 비선형 회귀
1) 개요- 회귀 계수의 결합이 비선형인 경우다. - 회귀 함수를 기반으로 하지 않는다. 2) KNN (K-Nearest Neighbors) 회귀- 새로운 데이터가 들어왔을 때 가장 가까운 이웃 몇 개를 찾아서 그 이웃의 데이터로분류는 투표를 해서 다수결로 타겟 결정회귀는 평균을 이용해서 타겟 결정- 가중 회귀 일반적인 경우 현재 위치에서 거리가 가장 가까운 3개를 추출했을 때 A는 거리가 3.2인데 5 B는 거리가 11.5인데 6.8 C는 거리가 1.1인데 값은 9.0 (5 + 6.8 + 9.0) / 3 = 약 6.9 거리에 따라서 가중치를 부여하는 방식 (5/3.2 + 6.8/11.5 + 9.0/1.1) / (1/3.2 + 1/11.5 + 1/1.1) KNN을 생성할 때 weights 매개변수에 di..
2024.03.07
no image
[Python] 분류
1. 분류- 데이터를 가지고 어떤 결정을 해야 하는 문제를 접하는 경우, 결정해야 하는 Target이 이미 알려진 범주형일 때 이미 알려져있으므로 지도학습 - 분류의 유형은 이진분류(2가지 중 하나)와 다중분류(3가지 이상 중 하나)로 분류하기도 하고, 선형분류와 비선형분류와 나누기도 함 - sklearn의 분류기들은 예측하기 위한 함수로 2가지 사용predict: 분류 결과predict_proba: 각 클래스에 대한 확률(확률이 가장 높은 결과가 predict의 결과 1-1) 분류 알고리즘- 판별 분석 - 랜덤 분류 - KNN - Support Vector Machine - 나이브 베이즈 - 로지스틱 회귀 - 결정 트리 - 최소 근접 - 신경망 - 앙상블 2. MNIST 데이터- 0부터 9까지의 숫자 ..
2024.03.07
no image
[Python] 머신러닝
1. 인공지능- 지능 : 문제를 해결할 수 있는 능력환자를 보고 병을 진단- 인공지능: 지능 작업을 수행할 수 있는 기계의 능력환자에 대한 정보를 입력하면 컴퓨터가 병을 진단- 구현 방법: 지식 공학(전문가 시스템): 문제 해결을 위한 알고리즘을 사람이 작성전문가의 도움을 받아서 개발자가 알고리즘을 작성해서 컴퓨터에 저장하고 이 알고리즘을 따라서 문제 해결컴퓨터의 역할은 결과를 만들어내는 것- Machine LearningData와 Output을 주면 컴퓨터가 알고리즘을 만들어내는 방식 인공지능과 머신러닝의 관계- 인공지능 > 기계학습(머신러닝) > 딥러닝, 강화학습 - 전문가 시스템 -> 머신 러닝 -> 딥러닝, 강화학습딥러닝은 이겼다, 졌다로 판정해주지만 강화학습은 순간순간 확률을 보여준다.=> 게임..
2024.03.07

1. 데이터 가져오기

# 고양이 질문 데이터

from bs4 import BeautifulSoup  #HTML 파싱
import urllib  #검색어 인코딩
import time  #슬립 사용
import requests  #HTML 가져오기
from tqdm import tqdm_notebook

#크롤링 결과 저장할 변수
present_candi_text = []

html = 'https://search.naver.com/search.naver?where=kin&sm=tab_jum&ie=utf8&query=' + urllib.parse.quote('고양이') + '&start='
for n in range(1, 1000, 10):
    response = requests.get(html + str(n))
    soup = BeautifulSoup(response.text, "html.parser")
    tmp = soup.select('div.question_area > div.question_group > a') #선택자
    for line in tmp:
        #print(line.text)
        present_candi_text.append(line.getText())
    time.sleep(0.5)
print(present_candi_text)


...

 

# 강아지 질문 데이터

 

2. 형태소 분석

#고양이

import nltk
from konlpy.tag import Okt
okt = Okt()
present_text = ''

for each_line in present_candi_text[:10000]:
    present_text = present_text + each_line + '\n'
tokens_ko = okt.morphs(present_text)
print(tokens_ko)

 

#강아지

 

 

3. 단어별 등장횟수 확인

ko = nltk.Text(tokens_ko, name='고양이')
print(ko.vocab().most_common(100))
[('고양이', 4860), ('가', 1800), ('에', 1260), ('를', 1170), ('\n', 900), ('이', 900), ('...', 900), ('는', 720), ('?', 540), ('강아지', 540), ('을', 540), ('집', 540), ('..', 450), ('제', 450), ('새끼', 450), ('한', 450), ('스트레스', 450), ('애기', 360), ('상', 360), ('요', 360), ('사료', 360), ('안', 360), ('배', 360), ('??', 270), ('에서', 270), ('으로', 270), ('근데', 270), ('한테', 270), ('꿈', 270), ('키우는', 270), ('들', 270), ('마리', 270), ('알레르기', 270), ('많이', 270), ('친구', 270), ('도', 270), ('이사', 270), ('그냥', 180), ('했는데', 180), ('랑', 180), ('고', 180), ('생각', 180), ('각', 180), ('먹고', 180), ('.', 180), ('많아서', 180), ('해', 180), ('닫고', 180), ('저희', 180), ('친척', 180), ('이번', 180), ('때', 180), ('문', 180), ('아기', 180), ('부터', 180), ('음식', 180), ('하는', 180), ('가서', 180), ('계속', 180), ('…', 180), ('말', 180), ('지금', 180), ('원래', 180), ('구조', 180), ('사람', 180), ('좋은', 90), ('건가', 90), ('인데', 90), ('귀여운', 90), (',', 90), ('이라고', 90), ('들어', 90), ('봤는데', 90), ('상도', 90), ('아니고', 90), ('고양', 90), ('이상은', 90), ('뭔', 90), ('뜻', 90), ('이에요', 90), ('간식', 90), ('급여', 90), ('거', 90), ('줘도', 90), ('될꺼요', 90), ('이마트', 90), ('구매', 90), ('코너', 90), ('사이', 90), ('있었어요', 90), ('꺼인줄', 90), ('알', 90), ('구', 90), ('먀', 90), ('아닐수도', 90), ('있다는', 90), ('들어서요', 90), ('같이', 90), ('키우고있는데', 90), ('자율', 90)]
ko_pup = nltk.Text(tokens_ko_pup, name='강아지')
print(ko_pup.vocab().most_common(100))
[('강아지', 5394), ('가', 2523), ('를', 1479), ('...', 1044), ('.', 1044), ('이', 870), ('는', 870), ('\n', 870), ('요', 783), ('?', 783), ('에서', 696), ('..', 609), ('에', 609), ('저', 522), ('도', 522), ('은', 522), ('ㅠㅠ', 435), ('집', 435), ('너무', 435), ('꿈', 435), ('키우고', 435), ('많이', 435), ('하는', 435), ('제', 348), ('을', 348), ('엄마', 348), ('들', 348), ('때', 348), ('입니다', 348), ('뭔가', 348), ('알르레기', 261), ('법', 261), ('키울', 261), ('몇', 261), ('낑낑', 261), ('거리', 261), ('적응', 174), ('제일', 174), ('물론', 174), ('하지만', 174), ('좋아해서', 174), ('마음', 174), ('못', 174), ('털', 174), ('있거든요', 174), (',', 174), ('진짜', 174), ('싶어요', 174), ('정도', 174), ('거', 174), ('잘', 174), ('시간', 174), ('안녕하세요', 174), ('돈', 174), ('입양', 174), ('1', 174), ('저희', 174), ('2', 174), ('나이', 174), ('키우는', 174), ('있는', 174), ('세상', 174), ('의', 174), ('다른', 174), ('처음', 174), ('것', 174), ('애기', 174), ('한테', 174), ('면', 174), ('데려오는', 174), ('(', 174), ('소리', 174), ('전', 174), ('달', 174), ('아빠', 174), ('절', 174), ('장염', 174), ('약', 174), ('밥', 174), ('않', 174), ('먹고', 174), ('물', 174), ('무엇', 87), ('보다', 87), ('걸리는게', 87), ('네', 87), ('이직', 87), ('하게', 87), ('되어도', 87), ('어머니', 87), ('케어', 87), ('해', 87), ('주시긴', 87), ('워낙', 87), ('좋아하고', 87), ('두고', 87), ('떠나려니', 87), ('안좋네요', 87), ('없어도', 87), ('금방', 87)]

 

 

 

4. 불용어 제거

stop_words = ['.','가','요','답변','...','을','수','에','질문','제','를','이','도',
                      '좋','1','는','로','으로','2','것','은','다',',','니다','대','들',
                      '2017','들','데','..','의','때','겠','고','게','네요','한','일','할',
                      '10','?','하는','06','주','려고','인데','거','좀','는데','~','ㅎㅎ',
                      '하나','이상','20','뭐','까','있는','잘','습니다','다면','했','주려',
                      '지','있','못','후','중','줄','6','과','어떤','기본','!!',
                      '단어','라고','중요한','합','가요','....','보이','네','무지',
                         '\n', '??', '안', '에서', '근데', '한테', '랑', '입니다']

tokens_ko_pup = [each_word for each_word in tokens_ko_pup
    if each_word not in stop_words]

ko_pup = nltk.Text(tokens_ko_pup, name='강아지')
ko.vocab().most_common(50)
[ ('강아지', 540),
 ('집', 540),
 ('새끼', 450),
 ('스트레스', 450),
 ('애기', 360),
 ('상', 360),
 ('사료', 360),
 ('배', 360),
 ('꿈', 270),
 ('키우는', 270),
 ('마리', 270),
 ('알레르기', 270),
 ('많이', 270),
 ('친구', 270),
 ('이사', 270),
 ('그냥', 180),
 ('했는데', 180),
 ('생각', 180),
 ('각', 180),
 ('먹고', 180),
 ('많아서', 180),
 ('해', 180),
 ('닫고', 180),
 ('저희', 180),
 ('친척', 180),
 ('이번', 180),
 ('문', 180),
 ('아기', 180),
 ('부터', 180),
 ('음식', 180),
 ('가서', 180),
 ('계속', 180),
 ('말', 180),
 ('지금', 180),
 ('원래', 180),
 ('구조', 180),
 ('사람', 180),
 ('좋은', 90),
 ('건가', 90),
 ('귀여운', 90),
 ('이라고', 90),
 ('들어', 90),
 ('봤는데', 90),
 ('상도', 90),
 ('아니고', 90),
 ('고양', 90),
 ('이상은', 90),
 ('뭔', 90),
 ('뜻', 90)]

 

#강아지

stop_words = ['.','가','요','답변','...','을','수','에','질문','제','를','이','도',
                      '좋','1','는','로','으로','2','것','은','다',',','니다','대','들',
                      '2017','들','데','..','의','때','겠','고','게','네요','한','일','할',
                      '10','?','하는','06','주','려고','인데','거','좀','는데','~','ㅎㅎ',
                      '하나','이상','20','뭐','까','있는','잘','습니다','다면','했','주려',
                      '지','있','못','후','중','줄','6','과','어떤','기본','!!',
                      '단어','라고','중요한','합','가요','....','보이','네','무지',
                         '\n', '??', '안', '에서', '근데', '한테', '랑', '입니다']

tokens_ko_pup = [each_word for each_word in tokens_ko_pup
    if each_word not in stop_words]

ko = nltk.Text(tokens_ko_pup, name='강아지')
ko.vocab().most_common(50)
[('강아지', 5394),
 ('저', 522),
 ('ㅠㅠ', 435),
 ('집', 435),
 ('너무', 435),
 ('꿈', 435),
 ('키우고', 435),
 ('많이', 435),
 ('엄마', 348),
 ('뭔가', 348),
 ('알르레기', 261),
 ('법', 261),
 ('키울', 261),
 ('몇', 261),
 ('낑낑', 261),
 ('거리', 261),
 ('적응', 174),
 ('제일', 174),
 ('물론', 174),
 ('하지만', 174),
 ('좋아해서', 174),
 ('마음', 174),
 ('털', 174),
 ('있거든요', 174),
 ('진짜', 174),
 ('싶어요', 174),
 ('정도', 174),
 ('시간', 174),
 ('안녕하세요', 174),
 ('돈', 174),
 ('입양', 174),
 ('저희', 174),
 ('나이', 174),
 ('키우는', 174),
 ('세상', 174),
 ('다른', 174),
 ('처음', 174),
 ('애기', 174),
 ('면', 174),
 ('데려오는', 174),
 ('(', 174),
 ('소리', 174),
 ('전', 174),
 ('달', 174),
 ('아빠', 174),
 ('절', 174),
 ('장염', 174),
 ('약', 174),
 ('밥', 174),
 ('않', 174)]

 

 

 

 

5. 단어 시각화 - 선 그래프

plt.figure(figsize=(20,10))
ko.plot(50) 
plt.show()

 

#강아지

 

 

 

 

6. 워드 클라우드

import pytagcloud

data = ko.vocab().most_common(101)
taglist = pytagcloud.make_tags(data, maxsize=200)
for i in taglist:
    if i["tag"] == '고양이':
        taglist.remove(i)

#태그 클라우드 생성
pytagcloud.create_tag_image(taglist, 'wordcloud.png', size=(2000, 2000), fontname='Korean', rectangular=False)

import matplotlib.pyplot
import matplotlib.image

img = matplotlib.image.imread('wordcloud.png')
imgplot = matplotlib.pyplot.imshow(img)
matplotlib.pyplot.show()

 

# 강아지

 

 

 

 

 

7. 단어 추천

!pip install gensim
from gensim.models import word2vec
okt = Okt()
results = []
lines = present_candi_text

for line in lines:
    malist = okt.pos(line, norm=True, stem=True)
    r = []
	
    #조사, 어미, 구두점을 제거하고 r에 추가
    for word in malist:
        if not word[1] in ["Josa", "Eomi", "Punctuation"]: #품사가 아닌 경우
            r.append(word[0])

	#앞뒤에 있는 좌우 공백 제거
    r1 = (" ".join(r)).strip()
    results.append(r1)
    print(r1)
print(results)
['애기 고양이 상 좋다 건가 고양이 상 귀엽다 고양이 상 애기 고양이 상 들다 보다 그냥 고양이 상도 아니다 애기 고양 이상은 뭔 뜻', '고양이 간식 급여 이 거 고양이 주다 되다 이마트 구매 하다 고양이 강아지 코너 사이 있다 고양이 끄다 알 구 먀 하다 아니다 있다 생각 들어서다', '강아지 고양이 사료 강아지 고양이 같이 키우다 자율 급식 고양이 강아지 사료 각 각 퍼 놓다 근데 강아지 고양이 사료 먹다 고양이 강아지 사료 먹다 괜찮다', '고양이 방 집착 하다 많다 고양이 해 되다 방뮨 닫다 지내다 저희 고양이 친척 안 좋아하다 근데 친척 이번 설 고양이 되게 쌔 혼내다 많다 고양이 해 되다 방문 닫다 사용 싶다 고양이 화장실 밥 먹다 때 문 열다 닫다 하다 돼다 걱정 많다 고양이', '고양이 꿈 해몽 부탁드리다 제 방금 일어나다 고양이 꿈 생생하다 궁금하다 일단 일어나다 제 집 키우다 고양이 마당 앉다 집 들어오다 문 열다 모르다 고양이 들 ....
['강아지 적응 ㅠㅠ 무엇 제일 걸리다 강아지 네 물론 제 이직 하다 되어다 집 어머니 케어 해 주다 하지만 강아지 워낙 저 좋아하다 저 강아지 너무 좋아하다 두다 떠나다 마음 안좋다 강아지 제 없다 금방 적응 하다 요 물론 집 강아지 엄마 간식 맛있다', '강아지 꿈 해석 해주다 강아지 들 엘베 타다 강아지 못 타다 줄이다 올라가다 강아지 매달리다 사고 있다 그걸 꾸다 사람 들 이 줄 빨리 끊다 하니 끊다 안되다 하다 강아지 높이 올라가다 수록 더 아프다 20 층 멈추다 의미 있다', '강아지 털 알르레기 없애다 법 제 강아지 털 알르레기 있다 근데 강아지 너무 키우다 강아지 너무 좋아하다 ㅠㅠ 어리다 때 강아지 너무 좋아하다 강아지 훈련사 꾸다 ㅠㅠ 진짜 강아지 넘다 키우다 싶다 ㅠㅠ 엄마 키우다 되다 강아지 알르레기 땜 ㅠㅠㅠ 내공 40', '강아지 너무 키우다 싶다 저 5-6년 정도 강아지 키우다 싶다 마음 크다 엄마 강아지 침 알레르기 좀 있다 안되다 거 같다 저 엄마 신경 쓰다 진짜 강아지 자다 키우다 자신 있다 ㅜㅠ 강아지 키우다 정말로 힘들다 그렇다 거 .....

 

# Word2Vec 적용

data_file = 'cat.data'
with open(data_file, 'w', encoding='utf-8') as fp:
    fp.write("\n".join(results))
    data = word2vec.LineSentence(data_file)
model = word2vec.Word2Vec(data, vector_size=200, window=10, hs=1,min_count=2, sg=1)
model.save('cat.model')
model = word2vec.Word2Vec.load("cat.model")
model.wv.most_similar(positive=['고양이'])
[('근데', 0.3730681836605072),
 ('좋아하다', 0.33693957328796387),
 ('한', 0.31936752796173096),
 ('기도', 0.31049662828445435),
 ('부다', 0.285176157951355),
 ('제', 0.2773987352848053),
 ('강아지', 0.2767084240913391),
 ('애기', 0.2762206494808197),
 ('이번', 0.2740619480609894),
 ('많이', 0.2703922390937805)]

 

#강아지

data_file_pup = 'pup.data'
with open(data_file_pup, 'w', encoding='utf-8') as fp:
    fp.write("\n".join(results_pup))
    data_pup = word2vec.LineSentence(data_file_pup)
    
modelp = word2vec.Word2Vec(data_pup, vector_size=200, window=10, hs=1,min_count=2, sg=1)
modelp.save('pup.model')
modelp = word2vec.Word2Vec.load("pup.model")
modelp.wv.most_similar(positive=['고양이'])
[('스트레스', 0.9240872263908386),
 ('하다', 0.8693583607673645),
 ('이사', 0.8684884309768677),
 ('많이', 0.850583016872406),
 ('받다', 0.8146383166313171),
 ('번', 0.790684163570404),
 ('새끼', 0.787282407283783),
 ('1년', 0.7280821800231934),
 ('ㅍ', 0.7129629850387573),
 ('가게', 0.7129014134407043)]
  • 강아지 데이터에서 '고양이'를 기점으로 window=10, 앞뒤로 10개까지 연관된 단어를 찾는다.

# 고양이에서

model.wv.most_similar(positive=['강아지'])
[('각', 0.8238824605941772),
 ('사료', 0.821792721748352),
 ('자율', 0.7821705937385559),
 ('퍼', 0.7787206172943115),
 ('놓다', 0.7757903337478638),
 ('급식', 0.7747383713722229),
 ('괜찮다', 0.7555736303329468),
 ('같이', 0.7227309346199036),
 ('급여', 0.5995111465454102),
 ('이', 0.5719886422157288)]

 

 

'Python' 카테고리의 다른 글

[Python] 딥러닝 _ Tensorflow  (0) 2024.03.20
[Python] 연관 분석  (0) 2024.03.18
[Python] 감성 분석 실습  (3) 2024.03.14
[Python] 차원 축소  (0) 2024.03.12
[Python] 지도학습 연습 _ 범주형 데이터 이진분류  (0) 2024.03.07

[Python] 감성 분석 실습

0ㅑ채
|2024. 3. 14. 14:48

1. IMDB 영화평을 이용한 지도학습 기반 감성분석

 

- https://www.kaggle.com/c/word2vec-nlp-tutorial/data

- 데이터 구조

  • id : 유저 아이디
  • sentiment : 감성. 긍정 1, 부정 0
  • review : 리뷰

  • 레이블이 있는 데이터를 가지고 범주를 예측하는 것과 동일하지만, 자연어는 피처가 문장으로 주어지기 때문에 문장을 피처 벡터화 작업해줘야 하는 것이 다르다.
  • 모든 단어를 각각의 피처로 만들고 각 문장은 피처의 존재 여부를 데이터로 소유한다. 

 

1. 데이터 읽어오기

 

import pandas as pd

review_df = pd.read_csv('./data/IMDB/labeledTrainData.tsv/labeledTrainData.tsv', header=0, sep="\t", quoting=3)
review_df.head(3)

 

 

2. 데이터 전처리

- <br />를 제거: replace

- 영문만 남겨두기: 정규식 모듈의 sub 메소드 활용

#정규식 모듈
import re

# <br> html 태그 -> 공백으로 변환
review_df['review'] = review_df['review'].str.replace('<br />',' ')

# 파이썬의 정규 표현식 모듈인 re를 이용하여 영어 문자열이 아닌 문자는 모두 공백으로 변환 
review_df['review'] = review_df['review'].apply( lambda x : re.sub("[^a-zA-Z]", " ", x) )

print(review_df['review'].head())
0     With all this stuff going down at the moment ...
1       The Classic War of the Worlds   by Timothy ...
2     The film starts with a manager  Nicholas Bell...
3     It must be assumed that those who praised thi...
4     Superbly trashy and wondrously unpretentious ...
Name: review, dtype: object
  • a-zA-Z가 아닌 글자를 공백으로 치환
  • 한글을 제외한 글자 제거: [^가-힣]

 

3. 훈련/테스트 데이터 분리

- 지도학습 기반의 분류이므로 훈련 데이터를 이용해서 훈련하고, 테스트 데이터로 확인하는 것을 권장

from sklearn.model_selection import train_test_split

class_df = review_df['sentiment']
feature_df = review_df.drop(['id','sentiment'], axis=1, inplace=False)

X_train, X_test, y_train, y_test= train_test_split(feature_df, class_df, test_size=0.3, random_state=156)

X_train.shape, X_test.shape
((17500, 1), (7500, 1))

 

4. 피처벡터화

- CountVectorizer로 피처벡터화 수행 후 분류 모델 훈련

- TdidfVectorizer로 비처벡터화 수행 후 분류 모델 훈련

from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, roc_auc_score
# 스톱 워드는 English, filtering, ngram은 (1,2)로 설정해 CountVectorization수행. 
# LogisticRegression의 C는 10으로 설정. 
pipeline = Pipeline([
    ('cnt_vect', CountVectorizer(stop_words='english', ngram_range=(1,2) )),
    ('lr_clf', LogisticRegression(C=10))])

# Pipeline 객체를 이용하여 fit(), predict()로 학습/예측 수행. predict_proba()는 roc_auc때문에 수행.  
pipeline.fit(X_train['review'], y_train)
pred = pipeline.predict(X_test['review'])
pred_probs = pipeline.predict_proba(X_test['review'])[:,1]

print('예측 정확도는 {0:.4f}, ROC-AUC는 {1:.4f}'.format(accuracy_score(y_test, pred),
                                         roc_auc_score(y_test, pred_probs)))
예측 정확도는 0.8860      , ROC-AUC는 0.9503
# 스톱 워드는 english, filtering, ngram은 (1,2)로 설정해 TF-IDF 벡터화 수행. 
# LogisticRegression의 C는 10으로 설정. 
pipeline = Pipeline([
    ('tfidf_vect', TfidfVectorizer(stop_words='english', ngram_range=(1,2) )),
    ('lr_clf', LogisticRegression(C=10))])

pipeline.fit(X_train['review'], y_train)
pred = pipeline.predict(X_test['review'])
pred_probs = pipeline.predict_proba(X_test['review'])[:,1]

print('예측 정확도는 {0:.4f}, ROC-AUC는 {1:.4f}'.format(accuracy_score(y_test ,pred),
                                         roc_auc_score(y_test, pred_probs)))
예측 정확도는 0.8939,      ROC-AUC는 0.9596
ngram을 설정하면 하나의 단어를 하나로 인식하지 않고, n개의 단어까지 하나의 단어로 인지한다.
영어는 2나 3을 설정: 사람 이름 등 (I am a boy  -> I am, am a, a boy)
정확하게 일치하는 것이라면 예측도 가능하다. (I am 다음은 a/an 이구나)
그래서 생성형 AI는 ngram을 설정하는게 좋다. 

 

 

 

 

2. 비지도학습 기반 감성분석

- 레이블이 없는 경우 사용

- Lexicon이라는 감성 분석에 관련된 어휘집을 이용하는 방식

- 한글 버전의 Lexicon이 현재는 제공되지 않음

 

 

 

 

3. 네이버 식당 리뷰 데이터를 이용한 감성분석

  • 이진분류는 LogisticRegression이면 충분
  • 크롤링한 데이터 
  • score: 별점
  • y: 감성, score가 4이상이면 1, 아니면 0

 

1. 데이터 읽어오기

df = pd.read_csv("./python_machine_learning-main/data/review_data.csv")
print(df.head())
   score                      review  y
0      5            친절하시고 깔끔하고 좋았습니다  1
1      5                  조용하고 고기도 굿  1
2      4      갈비탕과 냉면, 육회비빔밥이 맛있습니다.  1
3      4  대체적으로 만족하나\n와인의 구성이 살짝 아쉬움  1
4      5       고기도 맛있고 서비스는 더 최고입니다~  1

 

 

 

2. 데이터 전처리

한글 추출: (가-힣)

  • 모음과 자음만으로 구성된 텍스트도 추출하려면 (ㄱ-ㅣ, 가-힣)

 

#한글을 제외한 글자 전부 제거

import re
# 텍스트 정제 함수 : 한글 이외의 문자는 전부 제거
def text_cleaning(text):
    # 한글의 정규표현식으로 한글만 추출합니다.
    hangul = re.compile('[^ ㄱ-ㅣ가-힣]+')
    result = hangul.sub('', text)
    return result
df['ko_text'] = df['review'].apply(lambda x: text_cleaning(x))
del df['review'] 
df.head()
  • 데이터 용량을 줄이기 위해 바로바로 del

 

 

 

3. 형태소분석

from konlpy.tag import Okt

# konlpy라이브러리로 텍스트 데이터에서 형태소를 '단어/품사'로 추출
def get_pos(x):
    tagger = Okt()
    pos = tagger.pos(x) # PartOfSpeech
    pos = ['{}/{}'.format(word,tag) for word, tag in pos]
    return pos
# 형태소 추출 동작을 테스트합니다.
result = get_pos(df['ko_text'][0])
print(result)
['친절하시고/Adjective', '깔끔하고/Adjective', '좋았습니다/Adjective']

 

 

 

4. 피처 벡터화

  • 등장하는 모든 단어를 수치화

# 등장 횟수만을 고려한 피처 벡터화를 위한 사전 생성하고 데이터를 피처 벡터화

from sklearn.feature_extraction.text import CountVectorizer

# 형태소를 벡터 형태의 학습 데이터셋(X 데이터)으로 변환
index_vectorizer = CountVectorizer(tokenizer = lambda x: get_pos(x))
X = index_vectorizer.fit_transform(df['ko_text'].tolist())

X.shape
(545, 3030)
  • 3030: 단어의 개수
  • 545: 행의 개수
print(df['ko_text'][0])
print(X[0])
친절하시고 깔끔하고 좋았습니다
  (0, 2647) 1
  (0, 428) 1
  (0, 2403) 1
  • 다 한번씩만 등장!

 

#CounterVectorizer를 이용해서 만든 피처 벡터를 Tfidf 기반으로 변경

from sklearn.feature_extraction.text import TfidfTransformer

# TF-IDF 방법으로, 형태소를 벡터 형태의 학습 데이터셋(X 데이터)으로 변환합니다.
tfidf_vectorizer = TfidfTransformer()
X = tfidf_vectorizer.fit_transform(X)

print(X.shape)
print(X[0])
(545, 3030)
  (0, 2647) 0.5548708693511647
  (0, 2403) 0.48955631270748484
  (0, 428) 0.6726462183300624
  • 가중치로 나타남

 

-  필요하다면 데이터를 제거하거나 추가해도 된다.

  • 제거는 많이 하고 추가는 예측할 데이터가 들어오면 그 데이터를 추가하는 형태로 작업

 

 

5. 훈련 데이터와 검증 데이터 생성

from sklearn.model_selection import train_test_split

y = df['y']
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.30)
print(x_train.shape)
print(x_test.shape)
(381, 3030)
(164, 3030)

 

 

6. 모델 생성 및 훈련과 평가

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score

# 로지스틱 회귀모델을 학습합니다.
lr = LogisticRegression(random_state=0)
lr.fit(x_train, y_train)
y_pred = lr.predict(x_test)
y_pred_probability = lr.predict_proba(x_test)[:,1]

# 로지스틱 회귀모델의 성능을 평가합니다.
print("accuracy: %.2f" % accuracy_score(y_test, y_pred))
print("Precision : %.3f" % precision_score(y_test, y_pred))
print("Recall : %.3f" % recall_score(y_test, y_pred))
print("F1 : %.3f" % f1_score(y_test, y_pred))
accuracy: 0.90
Precision : 0.896
Recall : 1.000
F1 : 0.945
  • 1이 나오는 경우는 굉장히 드물다. 의심을 해봐야 한다.
from sklearn.metrics import confusion_matrix

# Confusion Matrix를 출력합니다.
confmat = confusion_matrix(y_true=y_test, y_pred=y_pred)
print(confmat)
[[  0  17]  #0을 0으로, 0을 1로 분류
 [  0 147]]  #0을 1로, 1을 1로 분류
  • 실제 1인 데이터는 전부 1로 분류했으므로 정확도가 1이 된 것
  • 감성 분석이나 신용 카드 부정 거래 탐지 등을 수행할 때 주의할 점은 샘플의 비율이 비슷하지 않은 것
  • 일정한 비율로 샘플링하는 것이 중요

이런상황

df['y'].value_counts()

 

 

#타겟의 분포 확인

y
1    492
0     53
Name: count, dtype: int64
  • 1과 0의 비율이 10배 정도
  • 1을 언더샘플링 하든지, 0을 오버샘플링 해야 함

 

# 언더샘플링

# 1:1 비율로 랜덤 샘플링을 수행합니다.
positive_random_idx = df[df['y']==1].sample(50, random_state=30).index.tolist()
negative_random_idx = df[df['y']==0].sample(50, random_state=30).index.tolist()

print(positive_random_idx)
print(negative_random_idx)
[274, 315, 544, 31, 297, 153, 396, 444, 491, 446, 90, 0, 524, 516, 26, 172, 344, 27, 189, 406, 364, 307, 216, 334, 38, 147, 290, 121, 448, 218, 358, 215, 14, 72, 116, 400, 309, 137, 424, 223, 148, 288, 470, 108, 277, 357, 29, 273, 474, 131]

[151, 265, 425, 231, 488, 232, 321, 317, 54, 163, 353, 254, 333, 195, 238, 50, 389, 170, 36, 540, 249, 275, 45, 92, 523, 261, 403, 340, 89, 20, 497, 105, 55, 13, 248, 37, 112, 508, 349, 123, 325, 16, 40, 361, 19, 114, 17, 227, 79, 537]
  • 비율을 맞춘 데이터 생성
# 랜덤 데이터로 데이터셋을 나눔
random_idx = positive_random_idx + negative_random_idx
sample_X = X[random_idx, :]

y = df['y'][random_idx]
x_train, x_test, y_train, y_test = train_test_split(sample_X, y, test_size=0.30)
print(x_train.shape)
print(x_test.shape)
(70, 3030)
(30, 3030)
  • 데이터의 개수는 줄어들었지만 비율은 맞음
# 로지스틱 회귀모델을 다시 학습합니다.
lr = LogisticRegression(random_state=42)
lr.fit(x_train, y_train)
y_pred = lr.predict(x_test)
y_pred_probability = lr.predict_proba(x_test)[:,1]

# 학습한 모델을 테스트 데이터로 평가합니다.
print("accuracy: %.2f" % accuracy_score(y_test, y_pred))
print("Precision : %.3f" % precision_score(y_test, y_pred))
print("Recall : %.3f" % recall_score(y_test, y_pred))
print("F1 : %.3f" % f1_score(y_test, y_pred))
accuracy: 0.70
Precision : 0.818
Recall : 0.562
F1 : 0.667
  • 샘플링 비율을 맞추면서 데이터의 개수가 줄어드는 바람에 모든 평가지표가 내려갔다.
  • 분류 모델은 데이터를 많이 모으는게 중요하고 특히 비율이 안맞는 경우 비율이 낮은 쪽의 데이터를 많이 모아야 한다.

 

 

 

7. 피처의 중요도 확인

  • 트리 게열의 모델들은 feature_importance_라는 속성에 각 피처의 중요도를 가지고 잇음
  • 트리 계열이 아닌 모델들은 회귀 계수를 가지고 판단
  • 모델의 회귀계수는 coef_라는 속성에 저장됨
print(lr.coef_[0])
[ 0.          0.          0.18171898       ...          0.         -0.12358162            0.        ]
  • 각 피처에 대한 회귀 계수 = 단어
#회귀 계수 내림차순 정렬
coef_pos_index = sorted(((value, index) for index, value in enumerate(lr.coef_[0])),
                       reverse = True)

#단어와 매핑
invert_index_vectorizer = {v:k for k, v in index_vectorizer.vocabulary_.items()}
print(invert_index_vectorizer)
{2647: '친절하시고/Adjective', 428: '깔끔하고/Adjective', 2403: '좋았습니다/Adjective', 2356: '조용하고/Adjective', 233: '고기/Noun', 721: '도/Josa', 330: '굿/Noun', 120: '갈비탕/Noun', 260: '과/Josa', 528: '냉면/Noun', 2065: '육회/Noun', 1419: '비빔밥/Noun', 2082: '이/Josa', 1013: '맛있습니다/Adjective', 671: '대/Modifier', 2604: '체적/Noun', 2067: '으로/Josa', 956: '만족하나/Adjective', 1996: '와인/Noun', 2077: '의/Josa', 293: '구성/Noun', 1476: '살짝/Noun', 1705: '아쉬움/Noun', 1001: '맛있고/Adjective', 1508: '서비스/Noun', 589: '는/Josa', 701: '더/Noun', 2613: '최고/Noun', 2182: '입니다/Adjective', 24: '가/Josa', 2177: '입/Noun', 1897: '에서/Josa', 553: '녹아요/Verb',  ...

 

 

[Python] 차원 축소

0ㅑ채
|2024. 3. 12. 13:25

1. 차원 축소

  • 머신 러닝의 많은 훈련 샘플들은 여러 개의 특성을 가지고 있다.
  • 특성의 개수가 많으면 훈련이 느려지고 좋은 솔루션을 찾기 어려워진다. = 차원의 저주
  • 실전에서는 특성 수를 크게 줄여 불가능한 문제를 가능한 범위로 변경해야 하는 경우가 많다.

 

 

1) 차원의 저주

- 고차원에서는 많은 것이 상당히 다르게 동작한다.

  • 사각형 안에서 점을 무작위로 선택하면 경계선에서 0.001 정도 거리에 존재할 확률이 0.4% 정도 되는데
  • 1만 차원이 되면 경계선이 늘어나서, 경계선 근처에 몰려 존재할 확률이 99.999999% 보다 커짐 

  • 대부분의 훈련 데이터가 서로 멀리 떨어져 있게 되고, 새로운 샘플도 멀리 떨어져있을 가능성이 높아진다.
    예측을 위해서는 훨씬 더 많은 외삽을 수행해야 한다. 
    • 외삽: 변수의 값을 추정하는 과정 중의 하나 변수들 간의 관계를 이용해서 값을 추정한다.
    • 보간법: 하나의 속성에서 값과 값 사이의 데이터를 추정하는 것
      10    20    30    ?   50  => 40
      외삽: 여러 변수 사이에서 값을 추정하는 것
      A       B    Target
      10    23    15
      22    32    29
      10    11     ?
    • 외삽을 정확하게 하기 위해서는 샘플의 개수를 늘리는 방법이 있지만
      일반적으로 비용이 많이 소모되고 피처가 많을 때는 아주 많은 샘플을 추가해야 한다. => 불가능

 

 

2) 차원 축소의 종류 

feature selection 특성 선택

  • 특정 피처에 종속성이 강한 불필요한 피처는 제거하고 데이터의 특성을 잘 나타내는 주요 피처만 선택
  • 통계적인 방법을 이용해서 feature들의 중요도에 순위를 정해 결정
  • but 정보 손실이 발생
  • 동일한 문제를 해결하는 다른 데이터 세트에서는 중요도의 순위가 달라질 수 있음

- 구현 방법

  • 필터 방식
    • 전처리 단계에서 통계 기법을 사용해서 변수 채택
    • 높은 상관계수의 feature를 사용
    • 변수 중요도나 상관도에 관련 알고리즘
      • 상관관계
      • 불순도(트리 모델에서 사용하는 지니계수, 엔트로피)
      • 카이 제곱 검정
  • 래퍼 방식
    • 예측 정확도 측면에서 가장 좋은 성능을 보이는 피처 집합을 뽑아내는 방법
    • 변수 선택 평가 모델
      • AIC (Akaike Information Criteria)
      • BIC (Bayesian Information Criteria)
      • 수정 R2 (결정계수)
    • 여러 번 머신러닝 알고리즘을 수행해야 하기 때문에 시간과 비용이 높게 발생하지만
      가장 좋은 피처의 집합을 찾는 원리이기 때문에 모델의 정확도를 위해서는 바람직한 방법
    • 알고리즘
      • Forward Selection(전진 선택법): 피처가 없는 상태에서부터 반복할 때마다 피처를 추가하면서 성능이 가장 좋아지는 지점을 찾는 방식
      • Backward Selection(후진 소거법): 모든 피처를 가지고 시작해서 덜 중요한 피처를 제거하면서 성능의 향상이 없을 때까지 수행하는 방식
      • Stepwise Selection(단계별 선택법): 피처가 없는 상태에서 출발해서 전진 선택법과 후진 소거법을 번갈아가면서 수행하는 방식인데 가장 시간이 오래 걸리는 방식이지만 가장 우수한 예측 성능을 나타내는 변수 집합을 찾아낼 가능성이 높다. 
  • 임베디드 방식
    • Filtering과 Wrapper의 조합
    • 각각의 피처를 직접 학습하며 모델의 정확도에 기여하는 피처를 선택
    • 계수가 0이 아닌 피처를 선택해서 더 낮은 복잡성으로 모델을 훈련하면서 학습 절차 최적화
    • 라쏘, 릿지, 엘라스틱넷 등을 이용해서 규제를 추가할 수 있다. 

 

피처 추출(feature extraction)

  • 기존 피처를 저차원의 중요 피처로 압축해서 추출
  • 새롭게 만들어진 피처는 기존의 피처와는 다른 값
    • 새로 만들어지는 피처는 기존 피처를 단순하게 압축하는 것이 아니라 함축적으로 더 잘 설명할 수 있는 다른 공간으로 매핑해서 추출하는 것
    • 함축적인 특성 추출은 기본 피처가 전혀 인지하기 어려웠던 잠재적인 요소를 추출
  • 이 방식은 전체 피처를 사용

- 구현 방법

  • Linea Methods: PCA, FA, LDA, SVD
  • Non-Linear Methods: Kernel PCA, t-SNE, Isomap, Auto-Encoder(-> 생성형 AI)

 

 

3) 차원 축소의 목적

- 비용, 시간, 자원, 용량의 문제

  • 불필요한 변수 저장: 용량 문제 발생
  • 차원이 많아지면 비례해서 분석 시간 증가

- Overfitting 문제 해결

  • 변수가 많으면 모델의 복잡도 증가: 일반화 가능성 낮음
  • 민감도가 증가해서 오차가 커질 수 있는 가능성 높아짐

- 군집화 분석 결과가 좋지 않음

  • 차원이 많아지면 경계선에 있을 가능성이 높아지고, 그러면 벡터 간의 거리가 유사해짐
  • 군집 성능 저하

- 차원이 높으면 설명력이 떨어짐

  • 2~3차원은 시각화가 가능하지만 3차원보다 큰 차원은 시각화가 어렵다.
  • 잠재적인 요소를 추출할 수 있다. 

- 차원을 축소하면 일부 정보가 유실되기 때문에 훈련 속도가 빨라지기는 하지만 성능은 나빠질 가능성이 높다. 

 

 

 

 

2. 투영 (Projection)

  • 물체의 그림자를 어떤 물체 위에 비추는 일 또는 그 비친 그림자를 의미
  • 어떤 물체든 그림자는 2차원으로 표현될 수 있다는 원리
  • 투영을 이용해서 구현된 알고리즘이 PCA, LDA 등

 

1) 특징

  • MNIST 같은 이미지에는 잘 적용이 되지만 스위스 롤 같은 데이터에는 적용할 수 없다.
  • 특성들의 값이 거의 비슷하고 다른 특성의 값이 많이 다르면 적용하기가 좋은데 그렇지 않은 경우는 적용하기 어렵다. 
높이가 별 차이 안나니까 눌러서 높이를 없애버려도 차이가 없으니 두개만으로도 설명이 된다.

MNIST 이미지 - 바깥 쪽은 거의 다 똑같다. (테두리는 항상 흰색)

스위스롤: 
누르면 안됨. 부딪혀버린다. 


실제 값                                          원했던 것

 

 

 

 

 

3. Manifold 방식

  • 차원을 축소하는데 특정 차원을 없애지 않고 새로운 차원을 만들어 내는 방식
  • 알고리즘: LLE, t-SNE,  Isomap, Autoencoer

  • 직선으로 구분하는 건 불가해졌지만, 곡선으로 나누면 됨 => PCA

 

 

 

4. PCA (Principal Component Analysis - 주성분 분석)

  • 여러 변수 간에 존재하는 상관관계를 이용하고 이를 대표하는 주성분을 추출해서 차원을 축소하는 기법
  • 차원을 축소할 때 데이터의 정보는 최대한 유지하면서 고차원에서 저차원으로 축소를 하는 방식
  • 데이터의 다른 부분을 보존하는 방식으로 분산을 최대한 유지하는 축을 생성
  • 이 방식은 데이터의 분포가 표준정규분포가 아닌 경우에는 적용이 어려움

 

- 동작 원리

  • 학습 데이터 셋에서 분산(변동성)이 최대인 축 (PC1)을 찾음
  • PC1과 수직이면서 분산이 최대인 축 (PC2)를 찾음
  • 첫 번째 축과 두 번째 축에 직교하고 분산을 최대한 보존하는 세 번째 축을 찾음
  • 1~3의 방법으로 차원 수만큼 찾음

데이터 구분: 차이
구분되는 성질을 가장 적게 잃어야하기 때문에, 가장 많이 차이가 나는 것을 찾아야 한다.
애-어른을 구분하는 가장 큰 차이는 나이, 그게 첫번째 주성분이 된다. 
그러면 반대편은 설명하지 못하기 때문에, 두번째 주성분은 첫번째 주성분과 직교한다.
2-3가지 정도로 판단하자!

 

 

4-1) sklearn

  • 변환기를 데이터 세트에 학습시키고 나면 결과 확인 가능
  • n_components 매개변수에 축소하려는 차원 개수 설정
#데이터 생성
#피처가 3개인 데이터 생성
np.random.seed(42)
m = 60
w1, w2 = 0.1, 0.3
noise = 0.1

angles = np.random.rand(m) * 3 * np.pi / 3 -0.5
X = np.empty((m, 3))
X[:, 0] = np.cos(angles) + np.sin(angles) / 2 + noise * np.random.randn(m) / 2
X[:, 1] = np.sin(angles) * 0.7 + noise * np.random.randn(m) / 2
X[:, 2] = X[:, 0] * w1 + X[:, 1] * w2 + noise + np.random.randn(m)

...
from sklearn.decomposition import PCA
#2개의 피처로 주성분 분석을 수행
pca = PCA(n_components=2)
X2D = pca.fit_transform(X)

print(X2D[:5])
[[ 0.88527067 -0.33376821]
 [ 0.09028918  1.11319151]
 [ 0.73610082  0.50819326]
 [ 1.86082527  0.0634811 ]
 [-0.46712242 -0.51380375]]

 

# 분산 비율 확인: explained_variance_ratio_

#분산 비율 확인
print(pca.explained_variance_ratio_)
[0.73833891 0.20993687]
  • Explained Variance Ratio: 분산을 얼마나 보존하는가 나타내는 지표 (70% 이상 채택)
  • 첫번째 주성분이 74% 정도 데이터를 설명할 수 있고 두번째 주성분은 21% 정도 설명 가능

# 데이터 복원: inverse_transform

X3D_inv = pca.inverse_transform(X2D)
print(X3D_inv[:5])
[[ 0.97487955  0.28357613  1.30383978]
 [-0.4587498   0.66634022  0.5800397 ]
 [ 0.16053264  0.52058405  1.19513314]
 [ 0.65692889  0.44306826  2.29529498]
 [ 1.06130267  0.16980808 -0.05298289]]
#원본 데이터와 복원된 데이터를 비교
print(np.allclose(X3D_inv, X))
#차이의 평균
print(np.mean(np.sum(np.square(X3D_inv - X), axis=1)))
False
0.07836731698475538
  • 원본과 복원결과는 차이가 있음, 일부 데이터는 소실됨

 

 

4-2) 적절한 차원 수 선택

  • 시각화를 위해서 주성분 분석을 하는 경우 2~3개로 수행
  • 시각화 이외의 목적인 경우는 일반적으로 주성분의 개수를 지정하기 보다 분산의 비율을 이용해서 개수 설정
    n_components에 성분의 개수가 아닌 0.0 ~ 1.0 사이의 비율을 설정하면 비율이 되는 주성분의 개수로 분석 수행
  • 시각화를 수행해서 엘보가 만들어지는 지점이 있다면 엘보를 이용해서 주성분 개수 설정
#데이터 가져오기
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split

#이미지 데이터 전부 가져오기
mnist = fetch_openml('mnist_784', version=1, as_frame=False)

mnist.target = mnist.target.astype(np.uint8)

X = mnist.data
y = mnist.target

X_train, X_test, y_train, y_test = train_test_split(X, y)
#X_train 은 784개의 피처를 가진 데이터
print(X_train.shape)
(52500, 784)
#차원수를 지정해서 PCA 수행: 154개의 피처로 주성분분석 수행
pca = PCA(n_components=154)
pca.fit_transform(X_train)
#분산의 비율 확인
print(pca.explained_variance_ratio_)

...
#분산 비율의 합계 - 95%
print(pca.explained_variance_ratio_.sum())
0.9500532025918321

#차원수를 지정해서 PCA 수행: 95% 이상되는 주성분의 개수로 주성분 분석
pca = PCA(n_components=0.95)
pca.fit_transform(X_train)
print(pca.explained_variance_ratio_.sum())
0.9504506731634907
#주성분의 개수
print(pca.n_components_)
154

 

 

 

4-3) Random PCA

  • sklean의 기본 PCA는 모든 데이터를 메모리에 적재시킨 후 훈련을 시켜야 한다. 
  • 시간이 많이 소모된다. O(m * n의 제곱) + O(n의 3제곱)   m은 행의 개수이고 n은 차원의 개수

- PCA에서 svd_solver 매개변수를 randomized 로 지정하면 Random PCA를 수행

- 확률적 알고리즘을 이용해서 주성분에 대한 근사값을 찾는 방식

  • 이 방식은 원본 데이터의 차원은 속도에 아무런 영향을 주지 못하고 주성분의 개수만 훈련 속도에 영향을 줍니다.
  • 시간 복잡도가 O(m * d의 제곱) + O(d의 3제곱)  d는 줄이고자 하는 차원의 개수
  • sklearn은 차원의 개수가 500개 이상이거나 줄이려는 차원의 개수가 원본 차원의 80% 보다 작으면 Random PCA를 수행
#차원수를 지정해서 랜덤 PCA 수행
pca = PCA(n_components=154, svd_solver='randomized')
pca.fit_transform(X_train)
print(pca.explained_variance_ratio_.sum())
0.9500820155151543

 

 

 

4-4) 점진적 PCA 

  • PCA 구현의 문제점 중 하나는 전체 훈련 세트를 메모리에 올려야 한다는 것
  • 이를 해결하기 위한 방법 중 하나가 IncrementalPCA
  • 메모리가 부족한 경우 와 실시간으로 데이터가 생성되는 경우 점진적 PCA를 사용

- 훈련 세트를 미니배치로 나눈 뒤 PCA 알고리즘에 한번에 하나씩 주입한다.

  • 훈련 세트가 클 때 유용
  • 나누어서 학습하는 것이 가능: 온라인 학습이 가능

- fit 대신 partial_fit 이라는 메서드를 이용해서 미니배치마다 호출

- 일반 PCA 보다 훈련 시간은 조금 더 걸릴 수 있다.

  • 배치 처리가 미니 배치 방식보다 좋은 점은 자원을 효율적으로 사용할 수 있고 작업 시간은 단축 시킬 수 있다는 것
  • 미니 배치 방식이 좋은 점은 훈련을 실시간으로 수행하기 때문에 훈련이 끝나는 시점만 따져보면 더 빠르다.

* 파이썬에서 메모리 정리 방법: (1) 가리키는 데이터가 없을 때 자동으로 정리.  (2) 기존 변수가 라키는 데이터가 없도록  del 변수명 을 사용

 

 

 

4-5) 3가지 PCA 훈련 시간 비교

# 일반 PCA

import time
start = time.time()

pca = PCA(n_components=154, svd_solver="full")
pca.fit_transform(X_train)

end = time.time()
print("PCA 수행 시간:", (end-start))
print(pca.explained_variance_ratio_.sum())
PCA 수행 시간: 4.382986545562744
0.9504506731634907

 

# 랜덤 PCA

import time
start = time.time()

pca = PCA(n_components=154, svd_solver="randomized")
pca.fit_transform(X_train)

end = time.time()
print("랜덤 PCA 수행 시간:", (end-start))
print(pca.explained_variance_ratio_.sum())
랜덤 PCA 수행 시간: 4.955554962158203
0.9500787743598854
  • svd_solver="randomized"를 생략해도 Random PCA - 피처의 500개가 넘으면 자동

# 점진적 PCA

from sklearn.decomposition import IncrementalPCA

#배치 사이즈: 데이터를 몇 개 씩 훈련
n_batches = 100
start = time.time()

inc_pca = IncrementalPCA(n_components=154)
for X_batch in np.array_split(X_train, n_batches):
    inc_pca.partial_fit(X_batch)

X_reduced = inc_pca.transform(X_train)
end = time.time()
print("점진적 PCA 수행 시간:", (end-start))
print(inc_pca.explained_variance_ratio_.sum())
점진적 PCA 수행 시간: 29.47389006614685
0.9497567304638014

 

 

 

 

 

4-6) Kernel PCA

- 커널 트릭 PCA: 비선형 데이터에 다항을 추가하는 것 처럼 해서 비선형 결정 경계를 만드는 PCA

  • 일반 PCA를 수행해서 분산의 비율이 제대로 표현되지 않는 경우 사용

- KernelPCA 클래스 (kernel, gamma)

  • kernel = rbf: 가우시안 정규 분포 형태의 커널을 적용
  • kernel = sigmoid: 로그 함수 형태
  • gamma: 복잡도를 설정. 0.0 ~ 1.0 사이의 값

- 하이퍼 파라미터 튜닝을 거의 하지 않지만 주성분 분석을 분류나 회귀의 전처리 과정으로 사용하는 경우에 가장 좋은 성능을 발휘하는 kernel이나 gamma 값을 찾기 위해서 튜닝 하는 경우도 있다.

#데이터 생성
from sklearn.datasets import make_swiss_roll

# X는 3차원 피처, t 는 범주
X, t = make_swiss_roll(n_samples=1000, noise=0.2, random_state=42)
print(X)
[[-3.29677117  4.26198454  7.69787682]
 [ 5.95972743 11.45784273 12.72625276]
 [ 6.66051523 18.15820401 -9.84713337]
 ...
 [ 6.18364276  1.44095323 -1.71242696]
 [ 5.86076169  1.09185823 12.47091112]
 [-8.16213703  5.61235668  4.51171684]]
  • 스위스 롤 같은 데이터를 차원 축소를 수행하거나 분류를 수행하는 경우 선형으로 차원 축소를 수행하거나 분류를 수행할 수 없다.
axes = [-11.5, 14, -2, 23, -12, 15]
fig = plt.figure(figsize=(6, 5))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=t, cmap=plt.cm.hot)
plt.show()
from sklearn.decomposition import KernelPCA

pca = PCA(n_components=2)
result = pca.fit_transform(X)
print(pca.explained_variance_ratio_.sum())
0.7098258574944849
kernel_pca = KernelPCA(n_components=2, kernel="sigmoid", fit_inverse_transform=True,
                      coef0=1, gamma=0.0001)
result = kernel_pca.fit_transform(X)

#KernelPCA는 분산의 비율을 직접 계산
explained_variance = np.var(result, axis=0)
explained_variance_ratio = explained_variance / np.sum(explained_variance)
print(explained_variance_ratio)
[0.57463469 0.42536531]

 

 

 

4-7) PCA 수행 이유

  • 시각화를 하거나 단순한 모델을 만들기 위해서 PCA 수행
  • PCA를 수행해서 분류나 회귀 작업을 수행하게 되면 성능은 떨어질 가능성이 높지만 일반화 가능성은 높아짐
  • 실세계에 존재하는 데이터는 잡음이 섞인 경우가 많아서 잡음이 섞인 데이터를 가지고 훈련을 하게되면 overfitting 될 가능성이 높다.

# 잡음 제거: 이미지에 잡음 추가하고 PCA 수행

from sklearn.datasets import load_digits

#8*8 의 MNIST 데이터 가져오기
digits = load_digits()
digits.data.shape
(1797, 64)
#이미지 출력 함수
def plot_digits(data):
    fig, axes = plt.subplots(4, 10, figsize=(10, 4),
                            subplot_kw={'xticks':[], 'yticks':[]},
                            gridspec_kw=dict(hspace=0.1, wspace=0.1))
    for i, ax in enumerate(axes.flat):
        ax.imshow(data[i].reshape(8, 8), cmap='binary',
        interpolation='nearest', clim=(0, 16))
#원본 이미지 출력
plot_digits(digits.data)
#주성분 분석
from sklearn.decomposition import PCA

pca = PCA(n_components = 0.6).fit(noisy)
print(pca.n_components_)
18
  • 60%의 분산을 나타내는 데 피처 18개 필요
  • 70%의의 분산을 나타내는데는 26개의 피처가 필요
#PCA를 수행한 데이터를 복원
components = pca.transform(noisy)
filtered = pca.inverse_transform(components)
plot_digits(filtered)
  • 잡음 제거 완료

 

 

5. 지역 선형 임베딩 LLE (Locally Linear Embedding)

- 비선형 차원 축소 방법

- 피처 내의 모든 데이터를 이용하지 않고 가장 가까운 이웃에 얼마나 선형적으로 연관되어 있는 측정한  후 국부적인 관계가 가장 잘 보존되는 저차원을 찾는 방식

- LocallyLinearEmbedding 클래스

  • n_components: 차원의 개수를 설정
  • n_neighbors: 이웃의 개수를 설정

#스위스롤은 롤 케익처럼 클래스가 구분된 데이터를 생성

  • 첫번째 리턴되는 값은 피처 3개로 구성된 피처의 배열
  • 두번째 리턴되는 값은 범주에 해당하는 타겟
X, t = make_swiss_roll(n_samples=1000, noise=0.2, random_state=42)

from sklearn.manifold import LocallyLinearEmbedding
#10개의 이웃을 이용해서 2개의 성분으로 축소
lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10, random_state=42)
X_reduced = lle.fit_transform(X)

plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=t, cmap=plt.cm.hot)
plt.show()
lle = PCA(n_components=2, random_state=42)
X_reduced = lle.fit_transform(X)

plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=t, cmap=plt.cm.hot)
plt.show()

 

 

 

 

 

6. 선형 판별 분석 LDA (Linear Discriminant Analysys)

- 분류 알고리즘이지만 훈련 과정에서 개별 클래스를 분별할 수 있는 축을 학습

- 각 클래스 간의 분산과 클래스 내부에서의 분산을 이용

  • 클래스 간의 분산은 최대한 크게하고 내부 분산은 최대한 작게 가져가는 방식

- LDA 나 LLE를 이용해서 차원을 축소할 때는 스케일링 필요

- sklearn.discriminant_analysis.LinearDiscriminantAnalysis 클래스

  • 하이퍼파라미터는 n_components 

 

 

 

7. 기타 차원 축소 알고리즘

1) NMF: 행렬 분해를 이용해서 차원 축소

2) Random Projection: 랜덤 투영

3) Multi Dimensional Scailing: 샘플 간의 거리 유지하면서 차원 축소

4) Isomap

5) t-SNE

  • 비슷한 샘플은 가까이,  비슷하지 않은 샘플은 멀리 떨어지도록 하면서 차원을 축소
  • 고차원 공간에 있는 샘플의 군집을 시각화 할 때 사용
  • 고도로 군집된 데이터의 경우 가.장 잘 작동하지만 속도가 느림

- 일반 PCA가 많이 사용되었던 이유는 설명 분산을 기반으로 하기 때문에 차원의 개수를 설정하는 것이 쉽고 빠름

- PCA의 단점은 비선형 관계를 보존할 수 없다는 것

- 고차원의 데이터를 축소할 때는 t-SNE 나 Isomap을 주로 이용

 

 

https://www.kaggle.com/competitions/cat-in-the-dat

Categorical Feature Encoding Challenge | Kaggle

www.kaggle.com

 
 
 

1. 범주형 데이터 이진분류

  • 문제 유형: 이진분류
  • 평가지표: ROC AUC
  • 미션은 Target에 속할 확률

데이터

  • 인위적으로 만든 데이터
  • 피처와 타겟에 대한 의미를 알 수 없음
  • 제공되는 데이터는 전부 범주형
    • bin_: 이진 범주
    • nom_: 명목형 범주
    • ord_: 순서형 범주
    • day와 month는 날짜 피처

 

데이터 읽어보기

  • 데이터를 읽어봤더니 각 행을 구분하는 id 속성이 존재한다.
  • DataFrame을 만들 때 각 행을 구분하는 컬럼이 있는 경우는 index로 설정하는 것이 좋다.
import pandas as pd 
train = pd.read_csv('./cat/train.csv', index_col='id')
test = pd.read_csv('./cat/test.csv', index_col='id')
submission = pd.read_csv('./cat/sample_submission.csv', index_col='id')

  • train 데이터의 열이 test 데이터의 열보다 1개 많다. (target)

 
 
 

2. 데이터 탑색

1) 타겟 분포 확인

- 로그 분포 변환을 수행할지 여부
- 층화 추출 같은 샘플링 적용 여부

print(train['target'].value_counts())
target
0    208236
1     91764
Name: count, dtype: int64
  • 아주 큰 차이는 아니어서 층화 추출을 고려하지 않아도 된다. 
    • 한 쪽에 4-5베 쏠린 정도일 때 층화 추출
  • 데이터수가 매우 크면 층화추출을 할 필요가 없다. 
더보기

Decision Tree
----------------sklearn
- RandomF: 랜덤추출 
- Ada
- GB : 경사하강법
- HistGB : 피처(연속형)의 구간(정수)화하여 경사하강법
---------외부라이브러리 
- XGBoost
- LGBM
- CAT
무작위성 추가, 속도 향상 등등 

딥러닝

- 데이터가 많을 때 성능 날뜀


트리 모델을 쓸 때 조심할 점
1. 트리를 만들 때는 균형을 맞춰야 한다.
- 깊이가 깊어지면 찾기가 더 힘들어지기 때문이다. 트리가 한쪽으로 치우치면 평균 조회횟수가 높아진다.
- 그래서 Balaned Tree를 만들려고 애쓰는데, LGBM은 이걸 신경쓰지 않기 때문에 훈련속도가 빠르다. 

2. 이상치나 과대적합 가능성
- 하나하나는 조건인데 만약 샘플이 한개밖에 없다면 이상치이거나 과대적합의 가능성이 높다. 
- 10만개 중 1개의 데이터를 맞추기 위해 알고리즘을 생성했다면 (상황에 따라 필요한 경우도 있지만) 과대적합의 가능성이 높다.
- 샘플이 적은데 비율 차이가 많이 나면 그냥 하면 안됨. 
  추출을 할 때 반드시 100:1로 추출해! 그럼 선택이 안되는 상황은 줄어든다. => 층화추출
   max_depth 트리의 깊이가 줄어들면 조건이 세분화되지 않으니까 배치되는 샘플이 많아진다. 얕게 여러번 하자!
   mean_leaf_nodes / samples같은게 있으면 배치할 수 있는 샘플 수가 있다. 최소 10개는 주자~

3. 시각화 한번 해서 확인하기!

 

2) 피처 확인

# 피처의 정보를 출력해주는 함수

#범주형에서 중요한 정보는 결측값 개수, 고유한 값의 개수 등
def resumetable(df):
    print("데이터 프레임 구조:", df.shape)
    
    #각 피처의 자료형 출력
    summary = pd.DataFrame(df.dtypes, columns=['데이터 타입'])
    summary = summary.reset_index()
    summary = summary.rename(columns={'index': '피처'})
    summary['결측값 개수'] = df.isnull().sum().values
    summary['고유값 개수'] = df.nunique().values
    summary['첫번째 값'] = df.loc[0].values
    summary['두번째 값'] = df.loc[1].values
    summary['세번째 값'] = df.loc[2].values
    
    return summary

 
 
# 순서형, 범주형 데이터 고유값 확인

for i in range(3): 
    feature = "ord_" + str(i)
    print(f'{feature} 고유값: {train[feature].unique()}')
ord_0 고유값: [2 1 3]
ord_1 고유값: ['Grandmaster' 'Expert' 'Novice' 'Contributor' 'Master']
ord_2 고유값: ['Cold' 'Hot' 'Lava Hot' 'Boiling Hot' 'Freezing' 'Warm']
for i in range(3, 6): 
    feature = "ord_" + str(i)
    print(f'{feature} 고유값: {train[feature].unique()}')

 
# day와 month의 고유값

print('day의 고유값:', train['day'].unique())
print('month의 고유값:', train['month'].unique())
day의 고유값: [2 7 5 4 3 1 6]
month의 고유값: [ 2  8  1  4 10  3  7  9 12 11  5  6]
  • day는 요일로 판단하는구나

 

3) 시각화

import seaborn as sns
import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.rc('font', size=15)
plt.figure(figsize=(7, 6))

 
# 타겟값(0, 1)의 분포 시각화

ax = sns.countplot (x='target', data=train)
ax.set(title='Target Distribution')

 
# 이진 피처(bin_0 ~ bin_4)와 타겟 분포 확인

sns.countplot (x='bin_0', hue='target', data=train)
sns.countplot (x='bin_1', hue='target', data=train)
sns.countplot (x='bin_2', hue='target', data=train)
sns.countplot (x='bin_3', hue='target', data=train)
  • 범주 값에 따라 타겟의 분포가 유의미하게 차이가 나므로 이진 피처는 분류에 전부 사용

 
# 명목형 피처(nom_)와 타겟의 분포 확인

sns.countplot (x='nom_0', hue='target', data=train)
sns.countplot (x='nom_1', hue='target', data=train)
sns.countplot (x='nom_2', hue='target', data=train)
sns.countplot (x='nom_3', hue='target', data=train)
  • 각 피처들의 값에 따라 유의미한 변화가 감지됨

 
# 순서형 피처(ord_)와 타겟 분포 확인

sns.countplot (x='ord_0', hue='target', data=train)
sns.countplot (x='ord_1', hue='target', data=train)
sns.countplot (x='ord_2', hue='target', data=train)
sns.countplot (x='ord_3', hue='target', data=train)

 
 
# 순서가 의미를 갖는 범주형 데이터의 순서 설정

from pandas.api.types import CategoricalDtype

#범주형 데이터의 순서 설정
ord_1_value = ['Novie', 'Contributer', 'Expert', 'Master', 'Grandmaster']
ord_2_value = ['Freezing', 'Cold', 'Warm', 'Hot', 'BoillingHot', 'Lava Hot']

#범주형 데이터를 하나의 데이터 타입으로 생성
ord_1_dtype = CategoricalDtype(categories=ord_1_value, ordered=True)
ord_2_dtype = CategoricalDtype(categories=ord_2_value, ordered=True)
_
#반영
train['ord_1'] = train['ord_1'].astype(ord_1_dtype)
train['ord_2'] = train['ord_2'].astype(ord_2_dtype)

 
 
- 탐색 결과

  • 결측값 없음
  • 모든 피처가 target과 유의미한 차이를 가지므로 제거할 피처를 찾지 못함

 

4) 기본 모델로 분류

#데이터 가져오기

#훈련 데이터와 테스트 데이터를 합쳐서 동일한 구조 만들기
all_data = pd.concat([train, test])
all_data = all_data.drop('target', axis=1)

 
 
- 데이터가 전부 범주형이고 문자열 등이 혼합되어 있어서 원-핫 인코딩을 수행해서 숫자로 변환

  • sklearn.preprocessing.OneHotEncoder나 pandas.get_dummies 사용
#피처 원핫 인코딩
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder()
all_data_encoded = encoder.fit_transform(all_data)
all_data_encoded
<500000x16552 sparse matrix of type '<class 'numpy.float64'>'
with 11500000 stored elements in Compressed Sparse Row format>

 
 

5) 훈련 데이터와 테스트 데이터 생성

num_train = len(train)

X_train = all_data_encoded[:num_train]
#답안 생성을 위한 데이터 - 새로운 데이터
X_test = all_data_encoded[num_train:]

y = train['target']

#타겟의 비율에 따라 층화추출
from sklearn.model_selection import train_test_split
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y, test_size =0.3, 
                                                     stratify=y, random_state=42)

 
 
# 회귀 모델을 만들어서 훈련하고 ROC, AUC 점수 출력

from sklearn.linear_model import LogisticRegression

#로지스틱 회귀를 이용해서 훈련
logistic_model = LogisticRegression(max_iter=1000, random_state=42)
logistic_model.fit(X_train, y_train)

#ROC AUC 점수 출력
y_valid_preds = logistic_model.predict_proba(X_valid)[:, 1]

from sklearn.metrics import roc_auc_score
roc_auc = roc_auc_score(y_valid, y_valid_preds)
print('ROC AUC:', roc_auc)
ROC AUC: 0.7938612279220084

 
 

6) 답안 생성

y_preds = logistic_model.predict_proba(X_test)[:, 1]
submission['target'] = y_preds
submission.to_csv('submission.csv')

 

 

 

7) 인코딩 방식을 변경하고 스케일링 수행 

 

피처 엔지니어링

# 이진 피처 중 값이 0,1이 아닌 피처의 값을 0,1로 수정

all_data['bin_3'] = all_data['bin_3'].map({'F':0, 'T':1})
all_data['bin_4'] = all_data['bin_4'].map({'N':0, 'Y':1})

 
#순서형 피처 데이터 변경

#순서형 피처 데이터 변경
ord1dict = {'Novice':0, 'Contributor':1, 'Expert':2, 'Master':3, 'Grandmaster':4}
ord2dict = {'Freezing':0, 'Cold':1, 'Warm':2, 'Hot':3, 'Boiling Hot':4, 
           'Lava Hot':5}

all_data['ord_1'] = all_data['ord_1'].map(ord1dict)
all_data['ord_2'] = all_data['ord_2'].map(ord2dict)

 
#순서형 피처 라벨 인코딩

from sklearn.preprocessing import OrdinalEncoder

ord_345 = ['ord_3', 'ord_4', 'ord_5']

ord_encoder = OrdinalEncoder()
all_data[ord_345] = ord_encoder.fit_transform(all_data[ord_345])

 
# 명목형 목록은 이전처럼 원핫인코딩

nom_features = ['nom_' + str(i) for i in range(10)]

onehot_encoder = OneHotEncoder()
encoded_nom_matrix = onehot_encoder.fit_transform(all_data[nom_features])

#명목형 피처는 제거
all_data = all_data.drop(nom_features, axis=1)

 
#day와 month를 원핫 인코딩하고 피처 삭제

date_features = ['day', 'month']

onehot_encoder = OneHotEncoder()
encoded_date_matrix = onehot_encoder.fit_transform(all_data[date_features])
all_data = all_data.drop(date_features, axis=1)

 
#순서형 피처의 값이 0 ~ 1이 아니므로 스케일링 수행

from sklearn.preprocessing import MinMaxScaler

ord_features = ['ord_' + str(i) for i in range(6)]
all_data[ord_features] = MinMaxScaler().fit_transform(all_data[ord_features])

 
#명목형 피처와 날짜 피처 합치기

from scipy import sparse

#원핫 인코딩 한 결과가 sparse matrix라서 희소 행렬을 합치는 API 사용
all_data_sprs = sparse.hstack([sparse.csr_matrix(all_data), 
                              encoded_nom_matrix, 
                              encoded_date_matrix], format='csr')
all_data_sprs
<500000x16306 sparse matrix of type '<class 'numpy.float64'>'
with 9163718 stored elements in Compressed Sparse Row format>

 
 
- 현재 작업

  • 이진 피처는 숫자 0과 1로 생성
  • 명목 피처는 원핫인코딩
  • 순서형 피처는 순서를 만들어서 번호를 부여하거나 일련번호 형태로 인코딩한 후 스케일링 작업 수행

 

데이터 분할

#훈련 데이터의 개수
num_train = len(train)

#훈련용
X_train = all_data_sprs[:num_train]

#답안 제출용
X_test = all_data_sprs[num_train:]

#훈련용
y = train['target']

#ROC AUC 점수를 확인하라고 했으므로
#훈련 데이터를 다시 모델 훈련 데이터와 평가 훈련 데이터로 분할
X_train, X_valid, y_train, y_test = train_test_split(X_train, y, 
                                                    test_size=0.3, 
                                                    stratify=y, 
                                                    random_state=42)

 
 

하이퍼 파라미터 튜닝

from sklearn.model_selection import GridSearchCV

logistic_model = LogisticRegression()

Ir_params = {'C':[0.1, 0.125, 0.2], 'max_iter':[800, 900, 1000], 
            'random_state':[42]}
gridsearch_logistic_model = GridSearchCV(estimator=logistic_model,
                                        param_grid=Ir_params,
                                        scoring='roc_auc', 
                                        cv=5)
gridsearch_logistic_model.fit(X_train, y_train)
print(gridsearch_logistic_model.best_params_)
{'C': 0.125, 'max_iter': 800, 'random_state': 42}

 
#ROC_AUC 점수 출력

y_valid_preds = gridsearch_logistic_model.predict_proba(X_valid)[:, 1]
roc_auc = roc_auc_score(y_valid, y_valid_preds)
print(roc_auc)
0.8013521765689966

 
 

답안 생성

y_preds = gridsearch_logistic_model.best_estimator_.predict_proba(X_test)[:, 1]
submission['target'] = y_preds
submission.to_csv('submission.csv')

 
 

모든 데이터를 가지고 훈련

#훈련 데이터 개수
num_train = len(train)

#훈련용
X_train = all_data_sprs[:num_train]

#답안 제출용
X_test = all_data_sprs[num_train:]

#훈련용
y = train['target']

logistic_model = LogisticRegression()

Ir_params = {'C':[0.1, 0.125, 0.2], 'max_iter':[700, 800, 900, 1000], 
            'random_state':[42]}
gridsearch_logistic_model = GridSearchCV(estimator=logistic_model,
                                        param_grid=Ir_params,
                                        scoring='roc_auc', 
                                        cv=5)
gridsearch_logistic_model.fit(X_train, y)
y_valid_preds = gridsearch_logistic_model.predict_proba(X_valid)[:, 1]
roc_auc = roc_auc_score(y_valid, y_valid_preds)
print(roc_auc)

print(gridsearch_logistic_model.best_params_)
0.826168104832565
{'C': 0.125, 'max_iter': 700, 'random_state': 42}
  • 점수를 확인해서 이전보다 좋아지면 샘플의 개수가 모델의 성능에 영향을 준다는 것 확인
  • 피처를 수정하면 성능이 이전보다 좋아지는 경향이 있음

 

'Python' 카테고리의 다른 글

[Python] 감성 분석 실습  (3) 2024.03.14
[Python] 차원 축소  (0) 2024.03.12
[Python] 머신러닝_앙상블  (0) 2024.03.07
[Python] 회귀 - 비선형 회귀  (0) 2024.03.07
[Python] 분류  (0) 2024.03.07

1. 개요

- 무작위로 선택된 수천명의 사람에게 복잡한 질문을 하고 대답을 모은다고 가정하면 이렇게 모은 답이 전문가의 답보다 나을 가능성이 높은데, 이를 대중의 지혜 혹은 집단지성이라고 한다.
- 하나의 좋은 예측기를 이용하는 것보다 일반적인 여러 예측기를 이용해서 예측을 하면 더 좋은 결과를 만들 수 있다는 것을 앙상블 기법이라고 한다.
- Decision Tree는 전체 데이터를 이용해서 하나의 트리를 생성해서 결과를 예측하지만, Random Forest는 훈련 세트로부터 무작위로 각기 다른 서브 세트를 이용해서 여러개의 트리 분류기를 만들고 예측할 때 가장 많은 선택을 받은 클래스나 평균 이용
- 머신러닝에서 가장 좋은 모델은 앙상블을 이용하는 모델
 
 

2. 투표기반 분류기

- 분류기 여러개를 가지고 훈련을 한 후 투표를 해서 다수결의 원칙으로 분류하는 방식
- law of large numbers(큰 수의 법칙)

  • 동전을 던졌을 때 앞면이 나올 확률이 51%이고 뒷면이 나올 확률이 49%인 경우 일반적으로 1000번을 던진다면 앞면이 510번 뒷면이 490번 나올 것이다.
  • 이런 경우 앞면이 다수가 될 가능성은 확률적으로 75% 정도 된다.
  • 이를 10000번으로 확장하면 확률은 97%가 된다.

- 앙상블 기법을 이용할 때 동일한 알고리즘을 사용하는 분류기를 여러 개 만들어도 되고 서로 다른 알고리즘의 분류기를 여러개 만들어도 됨

  • 동일한 알고리즘을 사용하는 분류기를 여러개 만들어서 사용할 때는 훈련 데이터가 달라야 한다.

 

2-1) 직접 투표 방식

- 분류를 할 때 실제 분류된 클래스를 가지고 선정
 

2-2) 간접 투표 방식

- 분류를 할 때 클래스 별 확률 가지고 선정
- 이 방식을 사용할 때는 모든 분류기가 predict_proba 함수 사용 가능
- 이 방식의 성능이 직접 투표 방식보다 높음
 

2-3) API

- sklearn.ensemble.VotingClassifier 클래스
- 매개변수로는 estimators 가 있는데 여기에 list로 이름과 분류기를 튜플의 형태로 묶어서 전달하고 voting 매개변수에 hard와 soft를 설정해서 직접 투표 방식인지 간접 투표 방식인지 설정
 
 

2-4) 투표 기반 분류기 (클래스를 가지고 선정)

# 직접 투표 방식

# 데이터 생성
from sklearn.model_selection import train_test_split #훈련 데이터와 테스트 데이터 분할
from sklearn.datasets import make_moons #샘플 데이터 생성 

X, y = make_moons(n_samples=500, noise=0.30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
#개별 분류기 
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

#모델 생성 및 훈련
log_clf = LogisticRegression(solver="lbfgs", random_state=42)
rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)
svm_clf = SVC(gamma="scale", random_state=42)

#직접 투표 기반 분류기
from sklearn.ensemble import VotingClassifier
voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='hard')

#평가 지표 확인
from sklearn.metrics import accuracy_score

for clf in (log_clf, rnd_clf, svm_clf, voting_clf):
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)
    print(clf.__class__.__name__, accuracy_score(y_test, y_pred))
LogisticRegression 0.864
RandomForestClassifier 0.896
SVC 0.896
VotingClassifier 0.912

 
# 간접 투표 방식

  • 확률을 가지고 분류할 때는 모든 예측기가 predict_proba()를 호출할 수 있어야 한다.
    • SVM은 기본적으로 predict_proba()를 가지고는 있지만 사용을 못한다. 
    • 인스턴스를 만들 때 probability=True를 추가해 주어야 확률을 계산한다. 
# log_clf = LogisticRegression(solver="lbfgs", random_state=42)
# rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)
svm_clf = SVC(gamma="scale", probability=True, random_state=42)

voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)],
    voting='soft')

from sklearn.metrics import accuracy_score

for clf in (log_clf, rnd_clf, svm_clf, voting_clf):
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)
    print(clf.__class__.__name__, accuracy_score(y_test, y_pred))
LogisticRegression 0.864
RandomForestClassifier 0.896
SVC 0.896
VotingClassifier 0.92
  • 성능 조금 더 향상
            직접       간접
log_clf     2    (0.49, 0.51)
svm_clf   2    (0.49, 0.51)
rnd_clf    1    (0.60, 0.40)

간접 투표 기반이 더 효율이 좋다.

100%는 아닐테니까 오류가 발생하고, 다른 모델에서 공통으로 발생할 가능성은 크지 않으니까 여러개를 돌리면 효율이 좋아질 것이라는게 투표기반 모델의 가정.

 
 

2-5) 배깅과 페이스팅

- 동일한 알고리즘을 사용하고 훈련 세트에 서브 세트를 무작위로 구성해서 예측기를 각기 다르게 학습시키는 것
- bagging(bootstrap aggregating): 훈련 세트에서 중복을 허용하고 샘플링하는 방식
- pasting: 훈련 세트에서 중복을 허용하지 않고 샘플링하는 방식

  • 하나의 샘플링 데이터는 여러개의 예측기에 사용 가능한데 
    bagging은 하나의 예측기에 동일한 샘플이 포함될 수 있다.
    pasting은 하나의 예측기 안에는 동일한 샘플이 포함될 수 없다.
  • 모든 예측기가 훈련을 마치면 모든 예측을 모아서 새로운 샘플에 대한 예측을 생성하는데, 이때 수집 함수는 분류일 때 최빈값이고 회귀일 때 평균을 계산
  • 개별 예측기는 원본 데이터 전체로 훈련한 것보다 편향이 심하지만 수집 함수를 통과하면 편향과 분산이 모두 감소
  • 예측기들은 동시에 다른 CPU 코어나 컴퓨터에서 병렬로 학습 가능

- API: BaggingClassifier

  • 기본적으로 bagging을 사용. bootstrap 매개변수를 False로 설정하면 페이스팅 수행
  • 샘플이 많으면 페이스팅 사용, 샘플의 개수가 적으면 bagging 사용
  • n_jobs 매개변수: sklearn이 훈련과 예측에 사용할 CPU 코어 수를 설정 (-1을 설정하면 모든 코어 사용)
  • n_estimators 매개변수: 예측기의 개수 설정
  • max_sample 매개변수: 샘플의 최대 개수를 지정
  • predict_proba 함수를 가진 경우에는 분류에서 간접 투표 방식 사용 가능

# DecisionTree와 DecisionTree의 Bagging 정확도 확인

  • 하나가 나은가 여러개가 나은가
  • 전체 데이터를 하나의 훈련데이터로
  • 전체 데이터를 쪼개서 트리를 여러개로 
from sklearn.metrics import accuracy_score
from sklearn.tree import DecisionTreeClassifier
tree_clf = DecisionTreeClassifier(random_state=42)
tree_clf.fit(X_train, y_train)
y_pred_tree = tree_clf.predict(X_test)
print(accuracy_score(y_test, y_pred_tree))
0.856     #정확도
from sklearn.ensemble import BaggingClassifier
bag_clf = BaggingClassifier(
    DecisionTreeClassifier(), n_estimators=500, #결정 트리 500개 생성
    max_samples=100, bootstrap=True, random_state=42) #각 트리의 샘플 데이터 수 최대 100개, 복원 추출
bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)

from sklearn.metrics import accuracy_score
print(accuracy_score(y_test, y_pred))
0.904     #정확도
  • 앙상블의 예측이 결정 트리 하나의 예측보다 일반화가 잘 됨
  • 앙상블은 비슷한 편향을 가지지만 더 작은 분산을 만듦

 
- 부트스트래핑은 각 예측기가 학습하는 서브 세트에 다양성을 증가시키므로 배깅이 페이스팅보다 편향이 조금 더 높지만 다양성을 추가하게 되면 예측기들의 상관관계를 줄이므로 앙상블의 분산을 감소

  • 일반적으로는 배깅이 페이스팅보다 더 좋은 성능을 나타내지만 되도록이면 교차검증으로 모두 평가하는게 좋다.

- oob 평가

  • 배깅에서 어떤 샘플은 한 예측기를 위해서 여러번 샘플링 되고, 어떤 데이터는 전혀 선택되지 않을 수 있다. 
  • 전체 데이터에서 63% 정도만 사용된다.
  • 선택되지 않은 37%를 oob(out-of-bag) 샘플이라고 한다. 
    • 예측기마다 데이터가 다르다. 
  • 별도로 테스트 데이터 생성 없이 훈련에 사용하지 않은 37%의 데이터를 이용해서 검증 작업을 수행할 수 있다. 
  • 앙상블의 평가는 각 예측기의 oob 평가를 평균해서 얻는다.
  • BaggingClassifier를 만들 때 oob_score = True로 설정하면 훈련이 끝난 후 자동으로 oob 평가 수행
    • 그 결과를 oob_score_ 에 저장
bag_clf = BaggingClassifier(
    DecisionTreeClassifier(), n_estimators=500, max_samples=100,
    bootstrap=True, oob_score=True, random_state=42)
bag_clf.fit(X_train, y_train)
bag_clf.oob_score_
0.9253333333333333

 
- BaggingClassifier는 특성 샘플링도 지원

  • max_features, bootstrap_features라는 매개변수 이용해서 특성 샘플링 지원
    • 최대 사용하는 피처의 개수와 피처의 중복 허용 여부를 설정
  • 각 예측기는 무작위로 선택한 입력 특성의 일부분으로 훈련된다. 
    • 이미지와 같은 고차원의 데이터 세트에서 이용
  • 훈련 특성과 샘플을 모두 샘플링하는 것을 Random Patches Method라고 한다. 
  • 샘플을 모두 사용하고 특성만 샘플링하는 것은 Random Subspaces Method라고 한다. 
  • 특성 샘플링을 하게 되면 더 다양한 예측기가 만들어지므로 편향을 늘리는 대신 분산을 낮춘다.

 
 
 

3. RandomForest

- 같은 알고리즘으로 여러 개의 분류기를 만들어서 보팅으로 최종 결정하는 배깅의 대표적인 알고리즘
- 알고리즘은 DecisionTree를 이용
- bootstrap 샘플을 생성해서 샘플 데이터 각각에 결정 트리를 적용한 뒤 학습 결과를 취합하는 방식으로 작동
- 각각의 DecisionTree들은 전체 특성 중 일부만 학습
- random_state 값에 따라 예측이 서로 다른 모델이 만들어지는 경우가 있는데 트리의 개수를 늘리면 변동이 적어진다. 
- 각 트리가 별개로 학습되므로 n_jobs를 이용해서 동시에 학습하도록 할 수 있다.
 
 

3-1) 장점

- 단일 트리의 단점 보완
- 매개변수 튜닝을 하지 않아도 잘 작동하고 데이터의 스케일을 맞출 필요가 없다. 
- 매우 큰 데이터 세트에도 잘 작동
- 여러 개의 CPU 코어를 사용하는 것이 가능
 
 

3-2) 단점

- 대량의 데이터에서 수행하면 시간은 많이 걸린다.
- 차원이 매우 높고 (특성의 개수가 많음) 희소한 데이터(유사한 데이터가 별로 없는) 일수록 잘 작동하지 않는다. 

  • 이런 경우 선형 모델을 사용하는 것이 적합
  • 희소한 데이터는 리프 노드에 있는 샘플의 개수가 적은 경우

 

3-3) 배깅과 랜덤 포레스트 비교

- 랜덤 포레스트는 Decision Tree의 배깅

  • 배깅에서 부트 스트래핑을 사용하고 특성 샘플링을 이용하는 것이 랜덤 포레스트 
  • 부트 스트래핑은 하나의 훈련 세트에서 일부분의 데이터를 추출하는데 복원 추출 이용해서 수행
  • 특성 샘플링은 비율을 설정해서 특성을 전부 이용하지 않고 일부분만을 이용해서 학습하는 방식
  • 랜덤 포레스트에서는 특성의 비율을 기본적으로 제곱근만큼 사용
#배깅을 이용한 random forest 만들기
bag_clf = BaggingClassifier(
    DecisionTreeClassifier(max_features="sqrt", max_leaf_nodes=16),
    n_estimators=500, random_state=42)

bag_clf.fit(X_train, y_train)
y_pred = bag_clf.predict(X_test)
#random forest 분류기
from sklearn.ensemble import RandomForestClassifier

rnd_clf = RandomForestClassifier(n_estimators=500, max_leaf_nodes=16, random_state=42)
rnd_clf.fit(X_train, y_train)

y_pred_rf = rnd_clf.predict(X_test)
print(np.sum(y_pred == y_pred_rf) / len(y_pred))
1.0
  • 2개 모두 일치하면 1.0

 
- RandomForest 알고리즘은 트리의 노드를 분할할 때 전체 특성 중에서 최선의 특성을 찾는 것이 아니라 무작위로 선택한 특성 후보 중에서 최적의 특성을 찾는 식으로 무작위성 주입

  • 이러한 방식은 트리의 다양성을 높여서 편향을 손해보는 대신에 분산을 낮추는 방식으로 더 좋은 모델을 만들어간다.

 

3-4) 특성 중요도

- 트리 모델은 특성의 상대적 중요도를 측정하기 쉬움
- sklearn은 어떤 특성을 사용한 노드가 평균적으로 불순도를 얼마나 감소시켰는지 확인해서 특성의 중요도 판단

  • 가중치의 평균이며 노드의 가중치는 연관된 훈련 샘플의 수와 같음
  • 훈련 샘플의 수를 전체 합이 1이 되도록 결과값을 정규화한 후 feature_importances_에 저장
from sklearn.datasets import load_iris
iris = load_iris()

#data라는 속성에 피처 4가지 있음
#target이라는 속성의 꽃의 종류에 해당하는 범주형 데이터 3가지
#featre_names 속성에 피처 이름이 저장되어 있고 class_names에 클래스 이름 저장

rnd_clf = RandomForestClassifier(n_estimators=500, random_state=42)
rnd_clf.fit(iris["data"], iris["target"])
for name, score in zip(iris["feature_names"], rnd_clf.feature_importances_):
    print(name, score)
sepal length (cm) 0.11249225099876378
sepal width (cm) 0.02311928828251033
petal length (cm) 0.4410304643639577
petal width (cm) 0.42335799635476823

 
 
- 이미지에서 특성의 중요도 확인해보면 이미지의 어느 부분이 다르게 보이는지 확인 가능

  • MNIST 데이터에서 분류할 때 가장 크게 다른 부분은 가운데 부분
  • 특성의 중요도를 파악한 후 전체 피처를 전부 사용하지 않고 일부분만 사용해도 거의 동일한 정확도를 만들어낼 수 있다. 
#특성 중요도 시각화 - MNIST
from sklearn.datasets import fetch_openml

#28*28 이미지 가져오기
mnist = fetch_openml('mnist_784', version=1) # 28*28=784, 피처의 개수
mnist.target = mnist.target.astype(np.uint8) #정수변환

#랜덤포레스트 분류기
rnd_clf = RandomForestClassifier(n_estimators=100, random_state=42)
rnd_clf.fit(mnist["data"], mnist["target"])

#중요도를 시각화 하기 위한 함수
def plot_digit(data):
    image = data.reshape(28, 28)
    plt.imshow(image, cmap = mpl.cm.hot,
               interpolation="nearest")
    plt.axis("off")
    
plot_digit(rnd_clf.feature_importances_)

cbar = plt.colorbar(ticks=[rnd_clf.feature_importances_.min(), rnd_clf.feature_importances_.max()])
cbar.ax.set_yticklabels(['Not important', 'Very important'])

save_fig("mnist_feature_importance_plot")
plt.show()
  • 이미지의 외곽 부분은 분류에 미치는 효과가 거의 없고 중앙 부분의 데이터가 이미지 분류에 영향을 많이 주므로 이 이미지는 가운데 부분만 잘라서 학습을 해도 무방
  • 이런 경우 이미지를 자르면 피처의 개수가 줄어들기 때문에 나중에 학습할 때 학습 시간이 줄어들고 예측할 때 일반화 가능성이 높아진다.

 
 

3-5) 하이퍼 파라미터 튜닝

- RandomForest는 DecisionTree처럼 하이퍼 파라미터가 다양하다.
- GridSearchCV나 RandomSearchCV를 이용해서 최적의 파라미터를 찾는 작업이 중요
- 샘플의 개수가 많거나 피처의 개수가 많으면 시간이 오래 걸린다.

  • 따라서 n_jobs를 -1로 설정해주는 것이 좋다. 

# 하이퍼 파라미터 튜닝을 위한 파라미터 생성

  • 기본은 dictionary(문자열 - 하이퍼 파라미터 이름과 리스트 - 값들로 구성)
  • dictionary 내부의 list들은 전부 곱한 만큼의 조합
  • 이러한 dictionary들을 list로 묶으면 별개로 동작
# GridSearchCV를 이용해 랜덤 포레스트의 하이퍼 파라미터를 튜닝
from sklearn.model_selection import GridSearchCV

params = {
    'n_estimators':[100, 200, 300],
    'max_depth' : [4, 6, 8, 10], 
    'min_samples_leaf' : [4, 6, 10],
    'min_samples_split' : [8, 16, 20]
}
# RandomForestClassifier 모델 생성
rf_clf = RandomForestClassifier(random_state=42, n_jobs=-1)
#GridSearchCV 인스턴스 생성
grid_cv = GridSearchCV(rf_clf , param_grid=params , cv=3, n_jobs=-1 )
grid_cv.fit(X_train , y_train)

print('최적 하이퍼 파라미터:\n', grid_cv.best_params_)
rn_clf = grid_cv.best_estimator_
print('최적 분류기:\n', rn_clf)
최적 하이퍼 파라미터:
 {'max_depth': 6, 'min_samples_leaf': 4, 'min_samples_split': 8, 'n_estimators': 100}

최적 분류기:
 RandomForestClassifier(max_depth=6, min_samples_leaf=4, min_samples_split=8,
                       n_jobs=-1, random_state=42)

 

 

3-6) Extra Tree

- RandomForest에서는 트리를 만들 때 무작위로 특성의 서브 세트를 만들어서 분할에 이용하는데
  분할을 하기 위해서 어떤 특성을 이용해서 분할하는 것이 효과적인지 판단하는 작업을 거치게 된다.

  • 오래걸림
  • 최적의 특성을 찾는 것이 아니고 일단 아무 특성이나 선택해서 분할하고 그 중에서 최상의 분할을 선택하는 방식의 트리
  • 무작위성을 추가하는 방식

- RandomForest에 비해서 훈련 속도가 빠름

  • 성능은 어떤 것이 좋을지 알 수 없기 때문에 모델을 만들어서 비교해야 한다.

- API: sklearn.ExtraTreesClaassifier
- 사용법은 RandomForest와 동일

from sklearn.ensemble import ExtraTreesClassifier
iris = load_iris()
rnd_clf = RandomForestClassifier(n_estimators=500, random_state=42)
rnd_clf.fit(iris["data"], iris["target"])
for name, score in zip(iris["feature_names"], rnd_clf.feature_importances_):
    print(name, score)
sepal length (cm) 0.11249225099876375
sepal width (cm) 0.02311928828251033
petal length (cm) 0.4410304643639577
petal width (cm) 0.4233579963547682

 
 
 
 

4. Boosting

  • 원래 이름은 hypothesis boosting
  • 약한 학습기를 여러개 연결해서 강한 학습기를 만드는 앙상블 방법
  • Bagging이나 Pasting은 여러 개의 학습기를 가지고 예측한 후 그 결과를 합산해서 예측
  • 앞의 모델을 보완해 나가면서 예측기를 학습
  • 종류는 여러가지

 
 

4-1) Ada Boost

  • 이전 모델이 과소 적합했던 훈련 샘플의 가중치를 더 높이는 것으로 이 방식을 이용하면 새로 만들어진 예측기는 학습하기 어려운 샘플에 점점 더 맞춰지게 된다.
  • 먼저 알고리즘의 기반이 되는 첫번째 분류기를 훈련 세트로 훈련시키고 예측을 만들고 그 다음에 알고리즘이 잘못 분류한 훈련 샘플의 가중치를 상대적으로 높인다.두번째 분류기는 업데이트된 가중치를 사용해서 훈련 세트에서 다시 훈련하고 다시 예측을 만들고 그 다음에 가중치를 업데이트 하는 방식
    • 투표 방식이 아닌 것임

- API: sklearn. AdaBoostClassifier

  • 기반 알고리즘이 predict_proba 함수를 제공한다면 클래스 확률을 기반으로 분류가 가능
  • algorithm 매개변수에 SAMME.R 값 설정
  • 확률을 이용하는 방식이 예측 값을 이용하는 방식보다 성능이 우수할 가능성이 높다. 
# moons 데이터에 AdaBoost 적용
X, y = make_moons(n_samples=500, noise=0.30, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

from sklearn.ensemble import AdaBoostClassifier

ada_clf = AdaBoostClassifier(
    DecisionTreeClassifier(max_depth=1), n_estimators=200,
    algorithm="SAMME.R", learning_rate=0.5, random_state=42)
ada_clf.fit(X_train, y_train)

plot_decision_boundary(ada_clf, X, y)
  • learning_rate(학습률)는 훈련을 할 때 스텝의 크기로 이 값이 크면 최적화에 실패할 가능성이 높아지고, 낮으면 최적화에는 성공할 가능성이 높지만 훈련 속도가 느려지고 과대적합 가능성이 높아진다. 
  • 따라서 학습률은 그리드서치로 최적값을 찾을 필요가 있다.

 
 

4-2) Gradient Boosting

  • 여러 개의 결정 트리를 묶어서 강력한 모델을 만드는 앙상블 기법
  • 회귀와 분류 모두 가능 
  • RandomForest도 여러 개의 결정 트리를 이용하는 것은 같지만, Gradient Boosting은 이전 트리의 오차를 보완하는 방식으로 순차적으로 트리 생성
  • 무작위성이 없다. 이전에 오분류한 샘플에 가중치를 부여한다. 대신 강력한 사전 가지 치기를 사용한다.
  • 보통 1~5 정도 깊이의 트리를 사용하기 때문에 메모리를 적게 사용하고 에측도 빠르다.
  • RandomForest 보다 매개변수 설정에 조금 더 민감하지만  잘 조정하면 더 높은 정확도를 제공한다.
  • AdaBoost 가중치 업데이트를 경사 하강법을 이용해서 조정한다.
  • 예측 속도는 빠르지만 훈련 속도(수행 시간)는 느리고 하이퍼 파라미터 튜닝도 좀더 어렵다.
랜덤 포레스트는 각각 훈련하기 때문에 병렬이 된다. n_jobs여럿 지정 가능. 
그런데 부스팅은 순차적으로 가야하기 때문에 병렬이 불가하다. 
GB의 장점은 메모리가 적고 예측도 빠르고 성능이 좋지만 훈련 시간이 문제임. 

 
- API: sklean. GradientBoostingClassifier 

from sklearn.ensemble import GradientBoostingClassifier
import time

# GBM 수행 시간 측정을 위함. 시작 시간 설정.
start_time = time.time()

gb_clf = GradientBoostingClassifier(random_state=0)
gb_clf.fit(X_train , y_train)

gb_pred = gb_clf.predict(X_test)
gb_accuracy = accuracy_score(y_test, gb_pred)

print('GBM 정확도: {0:.4f}'.format(gb_accuracy))
print("GBM 수행 시간: {0:.1f} 초 ".format(time.time() - start_time))
GBM 정확도: 0.8880
GBM 수행 시간: 0.2 초 

 
- 하이퍼 파라미터

  • max_depth: 트리의 깊이
  • max_features: 사용하는 피처의 비율
  • n_estimators: 학습기의 개수로 기본은 100
  • learning_rate: 학습률. 기본은 0.1이며 0~1사이로 설정 가능
  • subsample: 샘플링 비율. 기본 값은 1인데 과대적합 가능성이 보이면 숫자를 낮추면 된다.기본은 모든 데이터로 학습
from sklearn.model_selection import GridSearchCV

params = {
    'n_estimators':[100, 500],
    'learning_rate' : [ 0.05, 0.1]
}

grid_cv = GridSearchCV(gb_clf , param_grid=params , cv=2 ,verbose=1)
grid_cv.fit(X_train , y_train)
print('최적 하이퍼 파라미터:\n', grid_cv.best_params_)
print('최고 예측 정확도: {0:.4f}'.format(grid_cv.best_score_))
  • cv의 숫자는 교차검증에 사용할 fold의 개수로 실제 사용할 때는 조금 더 큰값 사용
  • verbose는 훈련 가정에서 로그 출력 여부
Fitting 2 folds for each of 4 candidates, totalling 8 fits

최적 하이퍼 파라미터:
 {'learning_rate': 0.05, 'n_estimators': 100}

최고 예측 정확도: 0.8960

 
- 타이타닉

  • deck 열에는 결측치가 많다. 
    embarked와 embark_town은 동일한 의미를 가진 컬럼

# 전처리

# load_dataset 함수를 사용하여 데이터프레임으로 변환
df = sns.load_dataset('titanic')

# IPython 디스플레이 설정 - 출력할 열의 개수 한도 늘리기
pd.set_option('display.max_columns', 15)

# NaN값이 많은 deck 열을 삭제, embarked와 내용이 겹치는 embark_town 열을 삭제
rdf = df.drop(['deck', 'embark_town'], axis=1)  

# age 열에 나이 데이터가 없는 모든 행을 삭제 - age 열(891개 중 177개의 NaN 값)
rdf = rdf.dropna(subset=['age'], how='any', axis=0)  

# embarked 열의 NaN값을 승선도시 중에서 최빈값으로 치환하기
# 각 범주의 개수를 구한 후 가장 큰 값의 인덱스를 가져오기
most_freq = rdf['embarked'].value_counts(dropna=True).idxmax()   
# 치환
rdf['embarked'].fillna(most_freq, inplace=True)

 
#피처 선택

# 타겟은 survived
ndf = rdf[['survived', 'pclass', 'sex', 'age', 'sibsp', 'parch', 'embarked']]

# 원핫인코딩 - 범주형 데이터를 모형이 인식할 수 있도록 숫자형으로 변환
onehot_sex = pd.get_dummies(ndf['sex'])
ndf = pd.concat([ndf, onehot_sex], axis=1)

onehot_embarked = pd.get_dummies(ndf['embarked'], prefix='town')
ndf = pd.concat([ndf, onehot_embarked], axis=1)

ndf.drop(['sex', 'embarked'], axis=1, inplace=True)
# 속성(변수) 선택
X=ndf[['pclass', 'age', 'sibsp', 'parch', 'female', 'male', 
       'town_C', 'town_Q', 'town_S']]  #독립 변수 X
# X=ndf.drop(['survived'], axis=1)       

y=ndf['survived']                      #종속 변수 Y

#피처의 스케일 확인
X.describe() #숫자 컬럼의 기술 통계량 확인
  • 숫자 컬럼의 값들의 범위가 차이가 많이 나면 scaling을 수행하는 것이 좋다.

# 스케일링

# 설명 변수 데이터를 정규화(normalization)
from sklearn import preprocessing
X = preprocessing.StandardScaler().fit(X).transform(X)

 
# 훈련

# train data 와 test data로 구분(7:3 비율)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=10)

 
# 평가지표

gbrt = GradientBoostingClassifier(random_state=0)
gbrt.fit(X_train, y_train)

print("훈련 세트 정확도: {:.3f}".format(gbrt.score(X_train, y_train)))
print("테스트 세트 정확도: {:.3f}".format(gbrt.score(X_test, y_test)))
훈련 세트 정확도: 0.894
테스트 세트 정확도: 0.795
  • 과대적합 발생: 훈련 데이터는 높고 테스트는 오히려 떨어졌다. 
  • n_estimators를 조정한다. 100개 정도가 적당
  • max_depth도 높아지면 과대적합 가능
# 하이퍼 파라미터 변경
gbrt = GradientBoostingClassifier(n_estimators=100, max_depth=1, random_state=42)
gbrt.fit(X_train, y_train)

print("훈련 세트 정확도: {:.3f}".format(gbrt.score(X_train, y_train)))
print("테스트 세트 정확도: {:.3f}".format(gbrt.score(X_test, y_test)))
훈련 세트 정확도: 0.810
테스트 세트 정확도: 0.805

 
# 피처의 중요도 파악

# 특성 중요도 확인
print('pclass', 'age', 'sibsp', 'parch', 'female', 'male', 
       'town_C', 'town_Q', 'town_S')
print(gbrt.feature_importances_)
pclass age sibsp parch female male town_C town_Q town_S
[0.22911763 0.11503979 0.02325801 0.01771038 0.2245367  0.38466583
 0.00567166 0.         0.        ]
#별 연관이 없는 애들은 빼고 다시 훈련
X = ndf.drop(['survived', 'town_C', 'town_Q', 'town_S'], axis=1)
X = preprocessing.StandardScaler().fit(X).transform(X)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=10) 

gbrt = GradientBoostingClassifier(n_estimators=100, max_depth=1, random_state=42)
gbrt.fit(X_train, y_train)

print("훈련 세트 정확도: {:.3f}".format(gbrt.score(X_train, y_train)))
print("테스트 세트 정확도: {:.3f}".format(gbrt.score(X_test, y_test)))
훈련 세트 정확도: 0.816
테스트 세트 정확도: 0.805

 

- 특징

  • 일반적으로 성능이 우수하지만 처음부터 이 모델을 사용하는 것을 권장하지는 않는데 이유는 훈련 시간이 길기 때문
  • 랜덤 포레스트나 선형 모델을 이용해서 훈련하고 평가한 후 성능이 나쁘거나 경진대회에서 마지막 성능까지 뽑아내고자 할 때 이용
  • 트리 모델이므로 희소한 고차원 데이터에는 잘 작동하지 않음
  • 가장 중요한 파라미터는 n_estimators, learning_rate, max_depth
gbrt = GradientBoostingClassifier(n_estimators=100, learning_rate=0.2, max_depth=2, random_state=42)
gbrt.fit(X_train, y_train)

print("훈련 세트 정확도: {:.3f}".format(gbrt.score(X_train, y_train)))
print("테스트 세트 정확도: {:.3f}".format(gbrt.score(X_test, y_test)))
훈련 세트 정확도: 0.880
테스트 세트 정확도: 0.823

 

- 회귀에 적용

- 분류에서는 잘못 분류된 데이터를 다음 학습기에서 다시 예측하는 형태로 구현
- 회귀에서는 첫번째 결정 트리 모델을 가지고 학습한 후 잔차를 구하여 다음 결정 트리 모델이 잔차를 타겟으로 해서 다시 학습을 수행하고 잔차를 구한 후 이 결과를 가지고 다음 결정 트리 모델이 훈련을 하는 방식으로 구현

  • 예측값은 모든 결정 트리 모델의 예측을 더하면 됨

- GradientBoostingRegressor 제공

#샘플 데이터 생성
np.random.seed(42)
X = np.random.rand(100, 1) - 0.5
y = 3*X[:, 0]**2 + 0.05 * np.random.randn(100)

print(X[0])
print(y[0])
[-0.12545988]
0.05157289874841034
#첫번째 트리로 훈련
from sklearn.tree import DecisionTreeRegressor

#의사 결정 나무를 이용한 회귀
tree_reg1 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg1.fit(X, y)

print(y[0])
print(tree_reg1.predict(X[0].reshape(1, 1)))
0.05157289874841034
[0.12356613]
#첫번째 예측기에서 생긴 잔여 오차에 두번째 DecisionTreeRegresssor를 훈련
y2 = y - tree_reg1.predict(X)

#잔차를 타겟으로 훈련
tree_reg2 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg2.fit(X, y2)

print(tree_reg2.predict(X[0].reshape(1, 1)))
[-0.09039794]
#두번째 예측기에서 생긴 잔여 오차에 세번째 DecisionTreeRegresssor를 훈련
y3 = y2 - tree_reg2.predict(X)
tree_reg3 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg3.fit(X, y3)

print(tree_reg3.predict(X[0].reshape(1, 1)))
[0.00704347]
#이제 세 개의 트리를 포함하는 앙상블 모델이 생겼습니다. 
#새로운 샘플에 대한 예측을 만들려면 모든 트리의 예측을 더하면 됩니다.
X_new = np.array([[0.8]])

y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))

print(y_pred)
[0.75026781]
#이제 세 개의 트리를 포함하는 앙상블 모델이 생겼습니다. 
#새로운 샘플에 대한 예측을 만들려면 모든 트리의 예측을 더하면 됩니다.
X_new = np.array([[0.05]])

y_pred = sum(tree.predict(X_new) for tree in (tree_reg1, tree_reg2, tree_reg3))

print(y_pred)
[0.04021166]

 

- API 활용

from sklearn.ensemble import GradientBoostingRegressor

gbrt = GradientBoostingRegressor(max_depth=2, n_estimators=100, learning_rate=0.05, random_state=42)
gbrt.fit(X, y)
print(gbrt.predict(X_new))
[0.00710809]

 

- 확률적 그라디언트 부스팅

- 훈련 샘플의 비율을 설정하는 것
- subsample 매개변수에 훈련 샘플의 비율 설정
- 편향이 높아지는 대신 분산이 낮아지고 훈련 속도가 빨라진다.
 
 

4-3) Histogram-based Gradient Boosting

  • sklearn에서 제공하는 다른형태의 Gradient Boosting
  • 입력 특성을 구간으로 나누어서 정수로 대체하는 형식
  • 구간의 개수는 max_bins 속성으로 설정 가능한데 기본값은 255이고 그 이상의 값은 안됨
  • 구간 분할을 사용하면 학습 알고리즘이 평가해야 하는 임계값의 개수가 줄어들게 되고 정수로 작업을 수행하기 때문에 속도가 빠르다.
  • 대규모 데이터 셋에서 빠르게 훈련할 때 사용

 
- API: HistGradientBoostingRegressor, HistGradientBoostingClassifier

  • 사용 방법 거의 동일

- 일반 그라디언트 부스팅과의 차이점

  • 샘플의 개수가 1000개가 넘으면 자동 조기종료 기능 활성화
    • 조기종료 기능은 훈련을 하다가 현재 점수보다 그다지 좋아지지 않으면 훈련 종료
  • n_estimators 매개변수 대신에 max_iter로 변경
  • 조정할 수 있는 파라미터도 줄어듬
from sklearn.ensemble import HistGradientBoostingClassifier

hgbrt = HistGradientBoostingClassifier(max_iter=100, max_depth=3, random_state=42)
hgbrt.fit(X_train, y_train)

print("훈련 세트 정확도: {:.3f}".format(hgbrt.score(X_train, y_train)))
print("테스트 세트 정확도: {:.3f}".format(hgbrt.score(X_test, y_test)))
훈련 세트 정확도: 0.957
테스트 세트 정확도: 0.888

 
 
 

5. XG Boosting (eXtra Gradient Boosting)

  • https://github.com/dmlc/xgboost
  • 트리 기반의 앙상블 학습
  • 케글 경연 대회에서 상위를 차지한 경우에 이 모델 많이 사용
  • 분류에 있어서 다른머신러닝 알고리즘보다 뛰어난 예측 성능
  • Gradient Boosting에 기반하지만 느린 훈련속도와 과적합 규제에 대한 부분 보완
  • 병렬 학습 가능
  • 자체 내장된 교차 검증 수행
  • 결측값도 자체 처리

 

5-1) 하이퍼 파라미터

- 일반 파라미터: 일반적으로 실행 시 스레드의 개수 silent 모드 (로그 출력x) 등의 선택을 위한 파라미터로 기본 값을 거의 변경하지 않음
- 부스터 파라미터: 트리 최적화, 규제 등의 파라미터
- 학습 태스크 파라미터: 학습 수행 시의 객체 함수나 평가를 위한 지표 등을 설정
 
#설치

pip install xgboost

 
 

5-2) 훈련과 검증에 사용하는 데이터 

- API: DMatrix

  • data 매개변수에 피처, label 매개변수에 타겟 설정

- 위스콘신 유방암 데이터

  • X-ray 촬영한 사진을 수치화한 데이터
    • 사진을 직접 이용하는게 아니라 사진에서 필요한 데이터를 추출해서 수치화해서 사용하는 경우가 많다.
    • 이러한 라벨링 작업에 Open CV를 많이 활용한다. 
  • 타겟이 0이면 악성(malignant), 1이면 양성(benign)
# 데이터 가져오기
import xgboost as xgb
from xgboost import plot_importance
from sklearn.datasets import load_breast_cancer

dataset = load_breast_cancer()
X_features= dataset.data
y_label = dataset.target

cancer_df = pd.DataFrame(data=X_features, columns=dataset.feature_names)
cancer_df['target']= y_label
cancer_df.head(3)

 
#레이블 분포 확인 - 층화 추출이나 오버나 언더샘플링 여부를 판단하기 위해서 수행함

print(dataset.target_names)
print(cancer_df['target'].value_counts())
['malignant' 'benign']

target
1    357
0    212
Name: count, dtype: int64

 
#훈련 데이터와 테스트 데이터 분리

# 전체 데이터 중 80%는 학습용 데이터, 20%는 테스트용 데이터 추출
X_train, X_test, y_train, y_test=train_test_split(X_features, y_label,
                                         test_size=0.2, random_state=156 )
print(X_train.shape , X_test.shape)
(455, 30) (114, 30)

 
#xgboost가 사용할 수 있는 형태로 데이터 변경

dtrain = xgb.DMatrix(data=X_train , label=y_train)
dtest = xgb.DMatrix(data=X_test , label=y_test)

 
#하이퍼 파라미터 생성

params = { 'max_depth':3,
           'eta': 0.1,   #학습률
           'objective':'binary:logistic',  #0하고 1이니까 이진분류
           'eval_metric':'logloss'   #평가지표
        }
num_rounds = 500   #예측횟수

 
#모델 생성 후 훈련

# train 데이터 셋은 ‘train’ , evaluation(test) 데이터 셋은 ‘eval’ 로 명기합니다. 
wlist = [(dtrain,'train'),(dtest,'eval') ]
# 하이퍼 파라미터와 early stopping 파라미터를 train( ) 함수의 파라미터로 전달
xgb_model = xgb.train(params = params , dtrain=dtrain , num_boost_round=num_rounds ,early_stopping_rounds=100, evals=wlist )
  • early_stopping_rounds - 조기 종료 기능 설정
  • 일반 그라디언트 부스팅은 예측기의 개수를 설정하면 무조건 예측기의 개수만큼 훈련 (무조건 500번)
    중간에 조기종료를 하려면 매 학습시마다 점수를 가져와서 점수가 더 이상 좋아지지 않으면 종료되도록 알고리즘 구현
  • 더이상 해봐야 이거보다 좋아지지 않는다.

# 예측을 수행하면 1로 판정할 확률 리턴

  • 예측 값을 출력할 때는 클래스로 변환해서 출력해야 함
# predict( ) 수행 결과값을 10개만 표시, 예측 확률 값으로 표시됨
print(np.round(pred_probs[:10],3)) #소수 셋째짜리까지
[0.904 0.004 0.908 0.267 0.992 1.    1.    0.999 0.994 0.   ]
# 예측 확률이 0.5 보다 크면 1 , 그렇지 않으면 0 으로 예측값 결정하여 List 객체인 preds에 저장 
preds = [ 1 if x > 0.5 else 0 for x in pred_probs ]
print('예측값 10개만 표시:',preds[:10])
예측값 10개만 표시: [1, 0, 1, 0, 1, 1, 1, 1, 1, 0]

 
#범주형 평가 지표 출력

  • 정확도, 정밀도, 재현율, 정밀도와 재현율의 조화 평균(f1-score), roc_auc 
from sklearn.metrics import confusion_matrix, accuracy_score
from sklearn.metrics import precision_score, recall_score
from sklearn.metrics import f1_score, roc_auc_score

#오차 행렬 출력
confusion = confusion_matrix( y_test, preds)
print('오차행렬:\n', confusion)

#정확도: 전체 데이터에서 맞게 분류한 것의 비율
accuracy = accuracy_score(y_test , preds)
print('정확도:', accuracy)

#정밀도: 검색된 문서들 중 관련있는 문서들의 비율 (True로 판정한 것 중 실제 True인 것의 비율)
precision = precision_score(y_test , preds)
print('정밀도:', precision)

#재현율: 관련된 문서들 중 검색된 비율 (실제 True인 것 중 True로 판정한 것의 비율)
recall = recall_score(y_test , preds)
print('재현율:', recall)

#recall과 precision의 조화 평균 (f1_score)
f1 = f1_score(y_test,preds)
print('f1_score:', f1)

#roc_auc score - area under curve. 그래프 곡선 아래 면적
#1에 가까울수록 좋은 성능
roc_auc = roc_auc_score(y_test, pred_probs)
print('roc_auc:', roc_auc)
오차행렬:
 [[59  2]
 [12 52]]

정확도: 0.888
정밀도: 0.9629629629629629
재현율: 0.8125
f1_score: 0.8813559322033898
roc_auc: 0.9723360655737704

 
# 피처의 중요도 출력

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(10, 10))
plot_importance(xgb_model, ax=ax)

 
 
 

5-3) Light GBM

  • XGBoost는 매우 뛰어난 부스팅이지만 여전히 학습 시간이 길다.
    • GridSearchCV를 이용해서 하이퍼 파라미터 튜닝을 하다보면 수행 시간이 너무 오래 결러셔 많은 파라미터를 튜닝하기에 어려움을 겪게 된다.
    • XGBoost보다 학습에 걸리는 시간이 훨씬 적고 메모리 사용량도 상대적으로 적으면서도 비슷한 성능 발휘
    • 10000건 이하의 데이터 세트에 적용하면 과대적합이 발생할 가능성이 높음
    • 다른 부스팅 알고리즘은 균형 트리를 만들려고 하는 경향이 있는데 이유는 과대적합을 방지하기 위해서인데 이것 때문에 훈련 시간이 오래 걸림
  • Light GBM은 균형 잡히 트리를 만들려고 하지 않고 리프 중심 트리 분할을 수행
    • 불균형 트리를 만들어서 사용한다. 
    • 데이터가 아주 많다면 리프에 배치되는 샘플의 개수도 많아질 가능성이 높기 때문에 과대 적합이 잘 발생하지 않는다.

- 설치

pip install lightgbm 

  • 4.x 버전이 설치가 된다.
  • 조기종료가 콜백의 형태
  • 로그 출력 옵션 없어짐

pip install lightgbm ==3.3.2

  • 조기 종료가 파라미터 형태
  • 로그 출력 옵션 있음

 
- 하이퍼 파라미터

  • xbboost와 거의 유사
  • xgboost와 다른 점은 파이썬 래퍼를 사용하면 데이터를 이전처럼 numpy의 ndarray로 지정

 

- 위스콘신 유방암

from lightgbm import LGBMClassifier
import lightgbm

dataset = load_breast_cancer()
ftr = dataset.data
target = dataset.target

# 전체 데이터 중 80%는 학습용 데이터, 20%는 테스트용 데이터 추출
X_train, X_test, y_train, y_test=train_test_split(ftr, target, test_size=0.2, random_state=156 )

print(X_train.shape, X_test.shape)

# 검증에 사용할 데이터 생성
evals = [(X_test, y_test)]
(455, 30) (114, 30)
# 모델 생성 훈련
lgbm_wrapper = LGBMClassifier(n_estimators=1000)
lgbm_wrapper.fit(X_train, y_train, early_stopping_rounds=100, eval_metric="logloss", 
                 eval_set=evals)
                 
preds = lgbm_wrapper.predict(X_test)

#검증 이하생략
오차행렬:
 [[33  4]
 [ 1 76]]
정확도: 0.956140350877193
정밀도: 0.95
재현율: 0.987012987012987
f1_score: 0.9681528662420381
roc_auc: 0.995085995085995

 
#피처의 중요도 시각화

# plot_importance( )를 이용하여 feature 중요도 시각화
from lightgbm import plot_importance

fig, ax = plt.subplots(figsize=(10, 12))
plot_importance(lgbm_wrapper, ax=ax)

 
 
 

6. Stacking

- 부스팅이나 랜덤 포레스트를 예측기의 예측을 취합해서 무언가 동작(투표, 확률, 평균 등)을 수행하는데 예측을 취합하는 모델을 훈련시키려고 하는 방식

  • 개별 알고리즘의 예측 결과 데이터 세트를 최종적인 메타 데이터를 만들고 이 데이터를 가지고 별도의 ML 알고리즘으로 최종 학습을 수행하고 테스트 데이터를 기반으로 다시 최종 예측을 수행하는 방식
  • 개별 알고리즘의 예측 결과를 가지고 훈련해서 최종 결과를 만들어내는 예측기 Blender 생성
    • 블랜더는 홀드 아웃 세트를 이용해서 학습
  • 스태킹에서는 두 종류의 모델이 필요한데 하나는 개별적인 기반 모델이고 다른 하나는 기반 모델의 예측 데이터를 학습 데이터로 만들어서 학습하는 최종 메타 모델

- API:  sklearn 스태킹 지원 안함. 직접 구현하거나 오픈소스 시용
 

6-1) 위스콘신 유방암 데이터를 이용해서 스태킹 구현

- 개별 학습기: KNN, RandomForest, AdaBoost, DecisionTree
- 최종 학습기: LogisticRegression
 
# 데이터 가져오기

import numpy as np

from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier

from sklearn.linear_model import LogisticRegression

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

cancer_data = load_breast_cancer()

X_data = cancer_data.data
y_label = cancer_data.target

X_train , X_test , y_train , y_test = train_test_split(X_data , y_label , test_size=0.2 , random_state=0)

 
# 예측기 생성

# 개별 ML 모델을 위한 Classifier 생성.
knn_clf  = KNeighborsClassifier(n_neighbors=4)
rf_clf = RandomForestClassifier(n_estimators=100, random_state=0)
dt_clf = DecisionTreeClassifier()
ada_clf = AdaBoostClassifier(n_estimators=100)

# 최종 Stacking 모델을 위한 Classifier생성. 
lr_final = LogisticRegression(C=10)

 
# 개별 모델 학습

# 개별 모델들을 학습. 
knn_clf.fit(X_train, y_train)
rf_clf.fit(X_train , y_train)
dt_clf.fit(X_train , y_train)
ada_clf.fit(X_train, y_train)

 
# 개별 학습기 가지고 예측한 후 정확도 확인

# 학습된 개별 모델들이 각자 반환하는 예측 데이터 셋을 생성하고 개별 모델의 정확도 측정. 
knn_pred = knn_clf.predict(X_test)
rf_pred = rf_clf.predict(X_test)
dt_pred = dt_clf.predict(X_test)
ada_pred = ada_clf.predict(X_test)

print('KNN 정확도: {0:.4f}'.format(accuracy_score(y_test, knn_pred)))
print('Random Forest 정확도: {0:.4f}'.format(accuracy_score(y_test, rf_pred)))
print('Decision Tree 정확도: {0:.4f}'.format(accuracy_score(y_test, dt_pred)))
print('AdaBoost 정확도: {0:.4f}'.format(accuracy_score(y_test, ada_pred)))
KNN 정확도: 0.9211
랜덤 포레스트 정확도: 0.9649
결정 트리 정확도: 0.9123
에이다부스트 정확도: 0.9561
pred = np.array([knn_pred, rf_pred, dt_pred, ada_pred])
print(pred.shape)

# transpose를 이용해 행과 열의 위치 교환. 컬럼 레벨로 각 알고리즘의 예측 결과를 피처로 만듦. 
pred = np.transpose(pred)
print(pred.shape)
(4, 114)
(114, 4)

 
#최종 모델의 정확도 확인

lr_final.fit(pred, y_test)
final = lr_final.predict(pred)

print('최종 메타 모델의 예측 정확도: {0:.4f}'.format(accuracy_score(y_test , final)))
최종 메타 모델의 예측 정확도: 0.9737

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

'Python' 카테고리의 다른 글

[Python] 차원 축소  (0) 2024.03.12
[Python] 지도학습 연습 _ 범주형 데이터 이진분류  (0) 2024.03.07
[Python] 회귀 - 비선형 회귀  (0) 2024.03.07
[Python] 분류  (0) 2024.03.07
[Python] 머신러닝  (0) 2024.03.07

1) 개요

- 회귀 계수의 결합이 비선형인 경우다. 
- 회귀 함수를 기반으로 하지 않는다.
 
 

2) KNN (K-Nearest Neighbors) 회귀

- 새로운 데이터가 들어왔을 때 가장 가까운 이웃 몇 개를 찾아서 그 이웃의 데이터로

  • 분류는 투표를 해서 다수결로 타겟 결정
  • 회귀는 평균을 이용해서 타겟 결정

- 가중 회귀

  • 일반적인 경우
    • 현재 위치에서 거리가 가장 가까운 3개를 추출했을 때
      A는 거리가 3.2인데 5
      B는 거리가 11.5인데 6.8
      C는 거리가 1.1인데 값은 9.0
    • (5 + 6.8 + 9.0) / 3 = 약 6.9
  • 거리에 따라서 가중치를 부여하는 방식
    • (5/3.2 + 6.8/11.5 + 9.0/1.1) / (1/3.2 + 1/11.5 + 1/1.1)  
  • KNN을 생성할 때 weights 매개변수에 distance를 설정하면 거리에 따른 가중치 적용

- KNN은 게으른 알고리즘

  • 데이터를 예측하기 위해서 모든 데이터가 메모리에 상주해야 하는 알고리즘

- 이해하기 쉬운 화이트 박스 알고리즘이고 별도의 훈련을 거치지 않는 간단한 알고리즘이라서 데이터 전처리(결측치 대체나 새로운 피처 추가)에 많이 이용 

  • 결측치나 새로운 피처 추가: 지역적 정보를 이용하기 때문에 다중공선성 문제는 고려할 필요가 없음. 전체 데이터를 사용하는게 아니라서 실제 예측하는 데는 사용하지 않음.
  • 화이트 박스 알고리즘: 내가 보고 이해하기 쉬운 알고리즘, 대표적으로 KNN과 트리 / 블랙박스 알고리즘: 딥러닝

- API는 sklearn.neighbors.KNeighborsRegressor

.
 

 
 
- KNN을 사용할 때는 스케일링 반드시 수행
 
 
 
 
 

3) Decision Tree

- 트리 기반
- 불순도 지표

  • 지니계수 (오분류 오차)
    • 1 - (예측한 개수 / 전체 개수 - (예측한 개수 / 전체 개수) ...
      한쪽으로만 분류가 되면 0: 가장 좋은 형태
      가장 나쁜 값이 0.5
  • 엔트로피

- API: DecisionTreeRegressor
- RandomForest나 GBM, XGBoost, LightGBM의 기반이 되는 모델
- 다양한 파라미터가 존재하기 때문에 하이퍼 파라미터 튜닝 필수
- 스케일링 과정은 필요 없음

m = 200
X = np.random.rand(m, 1)
y = 4 * (X - 0.5) ** 2 + np.random.randn(m, 1)/10

from sklearn.tree import DecisionTreeRegressor

tree_reg = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg.fit(X, y)
#시각화
from graphviz import Source
from sklearn.tree import export_graphviz

#decision_tree_dot 이라는 파일로 저장
export_graphviz(tree_reg, out_file='decision_tree.dot', 
               feature_names=['X'], class_names=['y'], 
               rounded=True, filled=True)

#파일을 출력
with open('decision_tree.dot') as f:
    dot_graph = f.read()
    
src = Source(dot_graph)
src

 

tree_reg = DecisionTreeRegressor(max_depth=12, random_state=42)
tree_reg.fit(X_data, y_target)

#피처의 중요도
print(tree_reg.feature_importances_)
[0.07125588 0.00142092 0.00421452 0.00076625 0.0256092  0.57625877
 0.01265629 0.07238728 0.00095654 0.01204435 0.02101767 0.00643705
 0.1949753 ]

 
 
 

4) SVM  (Support Vector Machine)

- 분류에서는 가장 가까운 데이터와의 거리가 가장 먼 결정 경계를 만들어가는 방식
- 회귀에서는 제한된 마진 오류 안에서 가능한 많은 샘플이 들어가도록 학습

  • 마진 오류의 폭은 epsilion 파라미터로 설정
#시드 고정 후 가우시안 분포를 따르는 데이터셋을 만듭니다.
np.random.seed(42)
m = 50
X = 2 * np.random.rand(m,1)
y = (4 + 3 * X + np.random.randn(m,1)).ravel()

X_train = X[:40]
X_test = X[40:]

y_train = y[:40]
y_test = y[40:]

 
 
 

# 모델 생성 과 훈련
from sklearn.svm import LinearSVR

epsilons = [0.1, 0.5, 1.0, 1.5, 2.0]
for epsilon in epsilons: 
    svm_reg = LinearSVR(epsilon=1.5, random_state=42)
    svm_reg.fit(X_train,y_train)

# svm_reg = 마진이 큰 모형(epsilon=1.5)

    y_pred = svm_reg.predict(X_test)
    mse = mean_squared_error(y_test, y_pred)
    rmse = np.sqrt(mse)
    print(epsilon, ":", rmse)
  • 별로 좋아지지는 않는다.

 
 
 
 

[Python] 분류

0ㅑ채
|2024. 3. 7. 14:00

1. 분류

- 데이터를 가지고 어떤 결정을 해야 하는 문제를 접하는 경우, 결정해야 하는 Target이 이미 알려진 범주형일 때
이미 알려져있으므로 지도학습
- 분류의 유형은 이진분류(2가지 중 하나)와 다중분류(3가지 이상 중 하나)로 분류하기도 하고, 선형분류와 비선형분류와 나누기도 함
- sklearn의 분류기들은 예측하기 위한 함수로 2가지 사용

  • predict: 분류 결과
  • predict_proba: 각 클래스에 대한 확률(확률이 가장 높은 결과가 predict의 결과

 

1-1) 분류 알고리즘

- 판별 분석
- 랜덤 분류
- KNN
- Support Vector Machine
- 나이브 베이즈
- 로지스틱 회귀
- 결정 트리
- 최소 근접
- 신경망
- 앙상블
 
 

2. MNIST 데이터

- 0부터 9까지의 숫자 이미지 70,000개로 이루어진 데이터, 레이블이 존재
- sklearn을 이용해서 다운로드 받으면 data 속성에 피처가 존재하고 target 속성으로 레이블을 제공
 

1-1) MNIST 데이터 가져오기

from sklearn.datasets import fetch_openml

mnist = fetch_openml('mnist_784', version=1, as_frame=False)

X, y = mnist['data'], mnist['target']

print(X.shape)
print(y.shape)
(70000, 784)
(70000,)
  • 이미지가 7만개 존재, 각 이미지에는 784개의 특성 존재
  • 특성은 픽셀의 개수 (28x28)

#이미지 출력

#이미지 데이터 1개 가져오기
some_digit = X[0]

#이미지를 다시 2차원으로 변경
some_digit_image = some_digit.reshape(28, 28)

#출력
plt.imshow(some_digit_image, cmap=mpl.cm.binary)
plt.axis('off')
plt.show()

 
 

1-2) 훈련 데이터와 테스트 데이터 분리

print(y.dtype)
object
  • 정수로 형변환
#레이블 자료형을 정수로 변경
y = y.astype(np.uint8)
uint8

 
 

3. 이진 분류

3-1) 이진 분류를 위한 데이터 준비

- 타겟이 True 또는 False
- 이진 분류는 맞다 틀리다를 구분하는 것
- 레이블의 값을 True와 False로 생성

  • 5가 맞는지 여부로 레이블 수정

# 이진 분류를 위한 레이블 생성

y_train_5 = (y_train == 5)
y_test_5 = (y_test == 5)
print(y_train_5[:10])
print(y_test_5[:10])
[ True False False False False False False False False False]
[False False False False False False False False  True False]

 
 

3-2) sklearn.linear_model 의 SGDClassifier

- 확률적 경사 하강법 (Stochastic Gradient Descent -  SGD) 사용하는 분류 모델 클래스

  • 타겟을 찾아갈 때 한번에 찾아가지 않고 학습률 값을 이용해서 작게 분할해서 타겟을 찾아가는 방식

- 매우 큰 데이터 세트를 효율적으로 처리
- 한번에 하나씩 훈련 샘플을 독립적으로 처리하기 때문에 온라인 학습에 적합

  • 온라인 학습: 실시간으로 데이터가 주어지는 형태의 학습 ↔ 배치 학습

- 하이퍼 파라미터

  • max_iter: 최대 수행 횟수
  • tol: 중지기준. 이 값보다 손실의 값이 작으면 훈련 중지 (어느정도까지 틀릴 것인지?)
  • random_state

# SGDClassifier를 이용한 훈련

#모델 생성 및 학습
from sklearn.linear_model import SGDClassifier

#max_iter는 작업 횟수이고 tol은 중지 기준으로 loss가 tol 보다 작으면 훈련 중지  random_state는 seed 값
sgd_clf = SGDClassifier(max_iter=1000, tol=1e-3, random_state=42)
#훈련
sgd_clf.fit(X_train, y_train_5)
#첫 번째 데이터를 이용해 이미지 감지\
sgd_clf.predict([some_digit])
array([ True])

 
 

3-3) 분류의 평가 지표

- 오차 행렬: 정답이 True와 False로 나누어져 있고 분류 결과도 True와 False로 나누어서 표로 만든 형태

  • True Positive(TP) : 실제 True인 정답을 True라고 예측 (정답)
  • False Positive(FP) : 실제 False인 정답을 True라고 예측 (오답)
  • False Negative(FN) : 실제 True인 정답을 False라고 예측 (오답)
  • True Negative(TN) : 실제 False인 정답을 False라고 예측 (정답)

- confusion_matrix 함수를 이용해서 오차행렬 생성

  • 타겟 클래스의 레이블과 예측 클래스의 레이블을 대입
  • 예측 클래스의 레이블은 cross_val_predict(분류기, 피처, 레이블, cv=교차검증횟수) 호출

#이전 데이터와 분류기 이용해서 오차행렬 생성

from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix

#예측값 생성
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
#오차행렬 생성
confusion_matrix(y_train_5, y_train_pred)
array([[53892,   687],
       [ 1891,  3530]], dtype=int64)
  • 53892는 5가 아닌 것으로 제대로 분류
  • 687는 5라고 잘못 분류한 것
  • 1891은 5 이미지가 맞는데 5가 아니라고 분류한 개수
  • 3550은 5라고 맞게 분류한 데이터

- Accuracy (정확도)

  • True를 True로, False를 False로 예측한 비율
  • 옳은 경우를 고려하는 지표
  • 정확도는 가장 직관적으로 모델의 성능을 평가할 수 있는 지표
  • 정확도를 사용할 때는 타겟의 비율을 확인해야 함
    • 타겟의 비율이 어느정도 고르게 분포되어 있다면 고려할 수 있는 평가지표이지만,
      타겟의 비율이 고르지 않다면 다른지표를 사용해야할 수 있다.
    • 어느 곳의 날씨가 99일은 맑고 1일은 비가 온다면, 무조건 맑다고 예측할 때 정확도가 99%가 된다.

- Recall (재현율)

  • True를 True라고 예측한 비율
    • 실제 날씨가 맑은데 맑다고 예측한 비율
  • sensitivity(민감도) 또는 hit rate 라고도 한다.

- Precision (정밀도)

  • True라고 예측한 것 중에서 실제 True인 것의 비율
    • 날씨가 맑다고 예측했는데 실제로 맑은 비율

 

  • 정밀도와 재현율은 같이 사용하는 경우가 많다.

- F1 Score

  • Precision과 Recall의 조화 평균
  • Target의 비율이 불균형 구조일 때 모델의 성능 지표로 많이 이용 

- sklearn.metrics에서 지표계산 API 제공

  • 대입해야 하는 데이터: 실제 값과 예측한 값의 배열

- 보통의 경우 F1 Score가 좋으면 성능이 좋은 분류기라고 하지만, 상황에 따라서는 다른 지표 사용 고려

  • 감시 카메라를 이용해서 도둑을 잡아내는 분류기를 훈련시킨다고 하면 정확도가 낮더라도 재현율이 좋은 것이 좋은 분류기가 될 수 있다.
  • 어린 아이에게 동영상을 추천하는 시스템의 경우는 어린 아이에게 성인이 볼 영상을 추천하면 안됨
    이런 경우에는 안전한 것들만 추천해줘야 하므로 정밀도가 높은 것이 좋은 분류기가 될 수 있다.
  • 일반적으로 정밀도를 올리면 재현율이 떨어지고, 재현율을 높이면 정밀도가 떨어진다.
    이를 정밀도와 재현율의 트레이드 오프라고 한다. 
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score

print("accuracy: %.2f" %accuracy_score(y_train_5, y_train_pred))
print("Precision : %.3f" % precision_score(y_train_5, y_train_pred))
print("Recall : %.3f" % recall_score(y_train_5, y_train_pred))
print("F1 : %.3f" % f1_score(y_train_5, y_train_pred))
accuracy: 0.96
Precision : 0.837
Recall : 0.651
F1 : 0.733

 

- ROC 곡선

  • Receiver Operation Characteristic(ROC -수신기 조작 특성) 곡선도 이빈 분류에서 널리 사용되는 도구
  • 거짓 양성 비율(False Positive Rate -FPR)에 대한 진짜 양성 비율(재현율)의 곡선
    • 거짓 양성 비율은 양성으로 잘못 분류된 음성 샘플의 비율.
    • 1 - 진짜 음성 비율 (음성으로 정확하게 분류한 음성 샘플의 비율)  \
  • 음성으로 정확하게 분류한 비율을 특이도(Specificity)라고 하는데 ROC 곡선은 재현율에 대한 1-특이도 그래프
  • 이 값이 1에 가까우면 좋은 모델, 0.5에 가까우면 나쁜(랜덤) 모델
from sklearn.metrics import roc_auc_score
print('ROC_AUC:', roc_auc_score(y_train_5, y_train_pred))
ROC_AUC: 0.8192920558800075

 
 

3-4) RandomForestClassifier를 활용한 분류

- 예측하는 방법이 다른다. 이 클래스는 예측한 범주를 리턴하는게 아니라 predit_proba 함수를 이용해서 각 범주에 대한 확률값을 리턴

from sklearn.ensemble import RandomForestClassifier

forest_clf = RandomForestClassifier(random_state=42)
y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_5, 
                                    cv=3, method='predict_proba')
print(y_probas_forest([1]))
[0.99 0.01]
  • 훈련하고 예측하면 결과가 확률로 나온다.
#예측 - 양성 클래스의 확률
y_scores_forest = y_probas_forest[:, 1]
print(y_scores_forest[1])
0.01
  • y_scores_forest를 가지고 평가 지표를 구해야 함
print(roc_auc_score(y_train_5, y_scores_forest))
0.9983436731328145
  • RandomForest를 이용한 모델의 ROC 값이 이전 모델보다 높게 나옴

 

3-5) 교차 검증

- 여러 개의 fold로 나누어서 모델을 생성하고 훈련한 후 평가지표를 얻어서 평균을 구하기도 함

#교차 검증
from sklearn.model_selection import cross_val_score

#3번 수행해서 각 검증의 정확도 확인
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([0.95, 0.96, 0.96])
#교차 검증
from sklearn.model_selection import cross_val_score

#3번 수행해서 각 검증의 정확도 확인
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([0.95, 0.96, 0.96])

 
 
 
 

4. 다중 분류

- 둘 이상의 클래스를 구별하는 작업
- SGD, RandomForest, Naive Bayes 알고리즘은 여러개의 범주를 직접 처리할 수 있지만
- Logistic Regression, Support Vector Machine 같은 알고리즘은 이진분류만 가능

  • 이진 분류기를 여러개 결합하면 다중 분류가 가능

 

4-1) 이진 분류 알고리즘을 이용한 다중 분류

- OvR(One-versus-the-Rest)

  • 범주의 개수만큼 이진 분류기를 만들어서 가장 높은 결정 점수를 가진 분류기가 분류한 것을 선택
    • 숫자분류기라면 숫자는 0부터 9까지 총 10개가 있으므로 분류기를 10개 만든다. 
    • 각 숫자에 해당하는 확률을 구해서 그 확률 중에서 가장 높은 것을 선택한다.

- OvO(One-versus-One) 

  • 두가지로 분류하는 이진분류기를 모두  (범주개수 * 범주개수-1) / 2  생성한 후 가장 많이 양성으로 분류된 범주를 선택
    • 0/1, 0/2, 0/3, ... 8/9 구분까지 생성
    • 그 중에서 가장 많이 분류된 클래스 선택
  • sklearn에서는 다중 클래스 분류에 이진 분류 알고리즘을 사용하는 분류기를 선택하면 알아서 자동으로 알고리즘 선택
    • 강제로 알고리즘을 선택할 수도 있다.

- SVM(Support Vector Machine)을 이용한 다중 분류

  • SVM은 이진 분류기: 기본적으로 다중 분류를 하지 못함
  • 이 분류기를 이용해서 다중 분류를 위한 데이터를 학습시키면 스스로 알고리즘을 선택해서 다중분류 결과 예측 가능
### 이진 분류기를 이용한 다중 분류
from sklearn.svm import SVC

#모델 생성
svm_clf = SVC(random_state=42)

#훈련
svm_clf.fit(X_train[:1000], y_train[:1000])

#예측
svm_clf.predict([some_digit])
array([5], dtype=uint8)
some_digit = X[1]
svm_clf.predict([some_digit])
array([0], dtype=uint8)
  • 여러개의 분류기를 생성한 후 각각의 분류기를 가지고 예측을 해서 점수 배정
  • 이 점수가 가장 높은 클래스 선택
some_digit = X[0]
some_digit_scores = svm_clf.decision_function([some_digit])
print(some_digit_scores)

some_digit = X[1]
some_digit_scores = svm_clf.decision_function([some_digit])
print(some_digit_scores)
[[ 1.758  2.75   6.138  8.285 -0.287  9.301  0.742  3.793  7.208  4.858]]
[[ 9.305 -0.287  7.214  5.881  0.725  8.272  2.779  1.775  3.792  4.984]]
  • 이 점수나 확률은 확인할 필요가 있다.
  • 5하고 제일 점수가 가까운 것은 8.285, [3]이다. 그러므로 5를 3으로 오분류할 가능성이 크다. 
    • 그래서 나중에는 오분류된 데이터만 가지고 다시 학습시키기도 한다.

- 강제로 알고리즘을 설정

#강제로 알고리즘 선택
from sklearn.multiclass import OneVsRestClassifier
ovr_clf = OneVsRestClassifier(SVC(random_state=42))
ovr_clf.fit(X_train[:1000], y_train[:1000])

some_digit = X[2]
print(ovr_clf.predict([some_digit]))
[4]

 
 

4-2) 스케일링 적용 후 분류

#현재 데이터의 스케일링 상태 확인
print(X_train[0])
from sklearn.linear_model import SGDClassifier

#SGD 분류기 생성
sgd_clf = SGDClassifier(max_iter=1000, tol=1e-3, random_state=42)

#훈련
sgd_clf.fit(X_train, y_train) 

#교차 검증 수행
cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")
array([0.874, 0.858, 0.869])
from sklearn.preprocessing import StandardScaler
#스케일을 해서 교차검증
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train.astype(np.float64))
cross_val_score(sgd_clf, X_train_scaled, y_train, cv=3, scoring="accuracy")
array([0.898, 0.891, 0.902])

 
-오차 행렬을 확인해서 어떤 부분에서 오류가 많이 발생하는지 확인

  • 오류가 많이 발생하는 부분이 있다면 그에 해당하는 데이터를 더 수집해서 좀더 정확한 모델을 만드는 것을 고려하거나 이미지 데이터의 경우는 전처리를 수행해서 보정하기도 함
#오차 행렬
y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
conf_mx = confusion_matrix(y_train, y_train_pred)
conf_mx
array([[5577,    0,   22,    5,    8,   43,   36,    6,  225,    1],
       [   0, 6400,   37,   24,    4,   44,    4,    7,  212,   10],
       [  27,   27, 5220,   92,   73,   27,   67,   36,  378,   11],
       [  22,   17,  117, 5227,    2,  203,   27,   40,  403,   73],
       [  12,   14,   41,    9, 5182,   12,   34,   27,  347,  164],
       [  27,   15,   30,  168,   53, 4444,   75,   14,  535,   60],
       [  30,   15,   42,    3,   44,   97, 5552,    3,  131,    1],
       [  21,   10,   51,   30,   49,   12,    3, 5684,  195,  210],
       [  17,   63,   48,   86,    3,  126,   25,   10, 5429,   44],
       [  25,   18,   30,   64,  118,   36,    1,  179,  371, 5107]],
      dtype=int64)

 
 
 

5. 다중 레이블 분류

- 분류기가 샘플 별로 여러개의 결과를 출력하는 경우
- 이미지에서 객체를 탐지하는 경우 하나의 객체만 탐지하는 것이 아니고 여러개의 객체를 탐지해야 하는 경우나 얼굴에서 얼굴의 각 부위를 탐지하는 경우 등

#다중 레이블 생성
y_train_large = (y_train >= 7)
y_train_odd = (y_train % 2 == 1)
y_multilabel = np.c_[y_train_large, y_train_odd]
print(y_multilabel[0])
[False  True]

 
 
 

6. 판별 분석 (Discriminant Analysis)

- 2개 이상의 모집단에서 추출된 표본들이 지니고 있는 정보를 이용해서 이 표본들이 어느 모집단에서 추출된 것인지를 결정해줄 수 있는 기준을 찾는 분석

  • 은행에서 부동산 담보 대출할 때 이 고객이 대출을 상환할 것인가 아니면 상환하지 않을 것인가를 판별하는 경우 은행에서는 기존의 대출을 상환하지 않은 고객들의 특성과 대출을 상환한 고객들의 특성을 별도로 만들어두고 이 고객의 특성이 어느쪽에 더 가까운건지 파악할 수 있다.

- 초창기에는 LDA(Linear Discriminant Analysis - 선형 판별 분석)를 많이 사용

  • 최근에는 신경망 등이 등장하면서 사용빈도가 낮아졌다. 
  • 아직도 다른 머신러닝 알고리즘의 기반 알고리즘으로 사용됨

- 전체 표본의 크기가 독립 변수의 개수보다 3배 이상 많아야 함
 
 

6-1) LDA

- 내부 제곱합에 대한 사이 제곱합의 비율을 최대화하는 것이 목적

  • 그룹 내부의 제곱합은 적게, 그룹과 그룹 사이의 제곱합은 크게 만드는 알고리즘
  • 클러스터링의 기반이 되는 방법

- sklearn.discriminant.LinearDicriminantAnalysis 클래스 이용

from sklearn.neighbors import KNeighborsClassifier

knn_clf = KNeighborsClassifier()
#훈련할 때 여러개의 label인 묶인 타겟을 설정하면 다중 레이블 분류
knn_clf.fit(X_train, y_multilabel)
knn_clf.predict([some_digit])
array([[False, False]])
some_digit = X[0]
knn_clf.predict([some_digit])
array([[False,  True]])

 
 

7. DummyClassifier

- 랜덤하게 분류
- 타겟의 비율을 보고 그대로 랜덤하게 예측하는 방식
 
 

8. KNN(K-Nearest Neighbor) 최근접 이웃

- 특징들이 가장 유사한 K개의 데이터를 찾아서 K개의 데이터를 가지고 다수결로 클래스를 선택해서 할당
- 회귀에 사용할 때는 그 값의 평균을 구해서 예측
 

8-1) 특징

- 간단: 전처리 과정에서 결측치를 대체하는 데 사용하기도 함
- 모델을 피팅하는 과정이 없음
- 모든 예측 변수들은 수치형이어야 함

  • 거리를 계산해야 하기 때문
  • 이 경우 범주형 데이터는 특별한 경우가 아니면 원핫 인코딩을 수행해야 함

- 게으른 알고리즘이라고 하는데 훈련 데이터 세트를 메모리에 전부 저장하고 거리 계산을 수행

  • 온라인 처리가 안됨

 

8-2) API

- sklearn.neighbors.KNeighborsClassifier 클래스

  • 인스턴스를 생성할 때 n_neighbors를 이용해서 이웃의 개수 설정
  • metric을 이용해서 거리 계산 알고리즘을 설정하는데
    1이면 맨하튼 거리,  2이면 유클리드 거리, 설정하지 않으면 mincowski 거리

- 데이터: loan_200.csv

  • payment_inc_ratio: 소득에 따른 대출 상환 비율
  • dti: 소득에 대한 부채 비율
  • outcome: 상환 여부

# KNeighborsClassifier를 이용해서 payment_inc_ratio와 dit에 따른 outcome 분류

#데이터 가져오기
loan200 = pd.read_csv('./python_machine_learning-main/data/loan200.csv')
#데이터 분리
#테스트를 위한 데이터 1개 추출
newloan = loan200.loc[0:0, ['payment_inc_ratio', 'dti']]

#피처 추출
X = loan200.loc[1:, ['payment_inc_ratio', 'dti']]
#타겟 추출
y = loan200.loc[1:, 'outcome']

from sklearn.neighbors import KNeighborsClassifier
# 인스턴스 생성 - 필수적인 파라미터는 n_neighbors
# 모델 생성
knn = KNeighborsClassifier(n_neighbors=21)

#훈련
knn.fit(X, y)
#예측
knn.predict(newloan)
array(['paid off'], dtype=object)
#클래스별 예측 확률
print(knn.predict_proba(newloan))
[[0.476   0.524]]
  • predict 함수는 거의 모든 분류 모델이 소유하고 있지만
  • predict_proba 함수는 없을 수 있다.

 

8-3) 거리 지표

- 유클리드 거리

  • 서로의 차이에 대한 제곱합을 구한 뒤 그 값의 제곱근을 취하는 방식
  • 유클리드 거리를 사용할 때는 수치형 데이터의 범위를 확인

- 맨하튼 거리

  • 서로의 차이에 대한 절대값을 구한 뒤 모두 더한 거리

- 마할라노비스 거리

  • 두 변수 간의 상관관계를 사용
  • 유클리드 거리나 맨하튼 거리는 상관성을 고려하지 않기 때문에 상관관계가 있는 피처들의 거리를 크게 반영
  • 주성분간의 유클리드 거리를 의미
  • 많은 계산이 필요하고 복잡성이 증가
  • 피처들의 상관관계가 높을 때 사용

- 민코프스키 거리

  • 1차원 공간에서는 맨하튼 거리를 사용하고 2차원 공간에서는 유클리드 거리를 사용

 

8-4) 표준화

- 거리의 개념을 이용하므로 스케일링이나 표준화를 수행을 해주어야 함
- 표준화를 했을 때와 그렇지 않을 때 이웃이 달라지게 됨

  • 10000 1 1 1 1
    20000 1 1 1 1
    11000 100 100 100 100
    제곱을 하니까 단위 자체가 다름

 
 

8-5) 피처 엔지니어링

- KNN은 구현이 간단하고 직관적
- 성능은 다른 분류 알고리즘에 비해서 그렇게 우수한 편은 아니다.
- 다른분류 방법들의 특정 단계에 사용할 수 있게 모델에 지역적 정보를 추가하기 위해서 사용하는 경우가 많음

  • 새로운 피처를 만드는 데 많이 사용
  • 기존의 피처를 이용해서 새로운 피처를 만드는 것이라서 다중 공선성 문제를 야기할 것 같은데 KNN으로 만들어진 피처는 다중 공선성 문제가 거의 발생하지 않는다.
  • KNN은 피처 전체를 이용하는 것이 주위 데이터 몇개만 이용하기 때문에 매우 지엽적인 정보 이용

- loan_data.csv.gz 데이터에서 새로운 피처 추가

  • csv 파일의 크기가 너무 커지는 경우 파일을 gz 타입으로 압축해서 용량 줄일 수 있음
    • pandas는 gz로 압축된 csv 파일의 내용을 읽을 수 있다. 
    • python은 zip이나 tar로 압축된 파일을 압축을 해제할 수 있고 여러 파일을 압축할 수 있는 API를 제공

# outcome 컬럼을 타겟으로 하고 나머지를 피처 특성으로 만들고, 피처 특성으로 타겟을 예측하도록 KNN으로 학습한 후 그 때의 예측 확률을 새로운 피처로 추가

loan_data = pd.read_csv('./python_machine_learning-main/data/loan_data.csv.gz')
print(loan_data.info())
print(loan_data['outcome'].value_counts())
15  outcome            45342 non-null  object 

outcome
default     22671
paid off    22671
Name: count, dtype: int64
  • outcome은 카테고리인데, 자료형이 Object(문자열)

# 문자열을 범주형으로 전환

loan_data['outcome'] = pd.Categorical(loan_data['outcome'], 
                                     categories=['paid off', 'default'], 
                                     ordered=True)

loan_data.info()
15  outcome            45342 non-null  category

 
#특성 행렬과 타겟 생성

X = loan_data[['dti', 'revol_bal', 'open_acc']]
y = loan_data['outcome']

from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=20)
knn.fit(X, y)

 
# knn으로 만들어진 예측 확률을 새로운 피처로 추가

# 예측 결과는 이차원 배열이므로 첫번째 열만 추가
loan_data['borrow_score'] = knn.predict_proba(X)[:, 0]
print(loan_data.info())
loan_data['borrow_score'].head()
21  borrow_score       45342 non-null  float64 

0    0.50
1    0.40
2    0.40
3    0.55
4    0.55
Name: borrow_score, dtype: float64
  • default, 빚을 갚지 않을 확률들!
예측 결과가 0이거나 1인 경우에는 1이 1일 확률만 내보낸다. 두번째 열은 없긴 함
그렇지만 머신러닝은 이차원 배열만 리턴한다. 하나의 데이터로만 예측하려 하진 않을테니까!

 
 

8-6) 특징

- 장점

  • 이해하기 쉬운 모델
  • 많이 조정하지 않아도 좋은 성능을 내는 경우가 있음

- 단점

  • 예측이 느림
    • 어떤 알고리즘이 특별히 있는게 아니라 예측할 데이터가 오면 그때 가서 이웃을 선정하고 예측함 
  • 많은 특성을 처리하는 능력이 부족
    • 데이터를 전부 메모리에 로드해야하기 때문에 데이터가 많으면 사용하기 어려움 - 게으른 알고리즘

 
 

9. 나이브 베이즈

- 나이브 베이즈 알고리즘은 주어진 결과에 대해 예측 변수 값을 확률을 사용해서 예측 변수가 주어졌을 때 결과를 확률로 추정
- 완전한 또는 정확한 베이지언 분류 - 나이브 하지 않은 방식 (현실성이 없음)

  • 예측 변수 프로파일이 동일한 모든 레코드를 찾음
  • 해당 레코드가 가장 많이 속한 클래스(타겟값) 결정
  • 새로운 레코드에 해당 클래스 지정
  • 모든 예측 변수들이 동일하다면 같은 클래스에 할당될 가능성이 높기 때문에 예측 변수들이 같은 레코드를 찾는 데 무게를 두는 방식

- 나이브 베이즈

  • 확률을 계산하기 위해 정확히 일치하는 레코드로만 제한하지 않고 전체 데이터 활용
    • overcast일 때 경기를 한 확률만이 아니라, 전체 날씨 중에 overcast인 확률도 넣었어야 한다.
    • 단일 항목을 가지고 분류 - 날씨가 overcast일 때 경기를 할 확률은?
      P(Yes|Overcast) = P(Overcast|Yes) P(Yes) / P(Overcast)
  • 사전 확률
    P(Overcast) = 4/14 = 0.29
    P(Yes) = 9/14 = 0.64
  • 사후 확률

    P(Overcast|Yes) = 4/9 = 0.44

 

  • 베이즈 정리 공식에 대입
    P(Yes|Overcast) = P(Overcast|Yes) P(Yes) / P(Overcast) = 0.44 * 0.64 / 0.29 = 0.98
  • 결론: 날짜가  Overcast일 때 경기를 할 확률이 0.98이라는 의미

 

9-1) 나이브 베이즈 특징

- 일반적인 선형 분류기보다 훈련 속도가 빠른 편
- 일반화 성능은 일반적인 선형 분류기보다 떨어짐
- 효과적인 이유는 각 특성을 개별로 취급해서 파라미터를 학습하고 각 특성에서 클래스 별 통계를 단순하게 취합

  • 텍스트 처리에서 주로 이용

 

9-2) API

- GaussianNB : 연속적인 데이터에 사용
- BernouliNB: 이진 데이터에 사용
- MultinomialNB: 카운트 데이터 (특성의 개수를 헤아린 정수값) 

  • BernouliNB와 MultinomialNB을 자연어 처리에 이용
loan_data = pd.read_csv('./python_machine_learning-main/data/loan_data.csv.gz')

loan_data['outcome'] = pd.Categorical(loan_data['outcome'], 
                                     categories=['paid off', 'default'], 
                                     ordered=True)

#purpose_, home_, emp_len은 범주형
loan_data['purpose_'] = loan_data['purpose_'].astype('category')
loan_data['home_'] = loan_data['home_'].astype('category')
loan_data['emp_len_'] = loan_data['emp_len_'].astype('category')
  • 타겟은 순서가 중요하다. 분류 할 때 각 타겟의 순서대로 확률을 제시하기 때문.
  • 피처는 순서가 중요하지 않다. 바로 카테고리로 변환해도 된다. 나중에 원핫인코딩을 하기 때문.
#타겟과 피처 분리
predictors = ['purpose_', 'home_', 'emp_len_']
outcome = 'outcome'

#원핫 인코딩을 수행해서 특성 배열(피처 배열)을 생성
X = pd.get_dummies(loan_data[predictors], prefix='', prefix_sep='')
y = loan_data[outcome]

X.head()
from sklearn.naive_bayes import MultinomialNB

naive_model = MultinomialNB()
naive_model.fit(X, y)

#146번 데이터 예측
new_loan=X.loc[146:146, :]
print("146번-", naive_model.predict(new_loan))
print(naive_model.classes_)
print("146번-", naive_model.predict_proba(new_loan))
146번- ['default']
['default' 'paid off']
146번- [[0.65344472 0.34655528]]
  • default일 확률이 0.65, paid off일 확률이 0.34
print("146번-", naive_model.predict(new_loan)[0])
146번- default

 
 
 
 

10. Logistic Regression

- 타겟(종속변수)이 범주형인 경우 사용하는 회귀모형
- 분류만 가능
- 타겟이 2개 값을 갖는 경우에만 사용
- 피처의 자료형이 연속형에도 사용할 수 있고 이산형에도 사용 가능
 
 

10-1) Odds Ratio(특정 이벤트가 발생할 확률)

- Odds = 성공율 / 실패율 =>  성공율 / (1-성공율)
- Odds Ratio = 한 그룹의 Odds / 다른 그룹의 Odds

  • 비만일 확률/(1-비만일 확률) =(402/4016)/(1-402/4016)) = 0.1001/0.8889 = 0.111
  • 혈중 콜레스테롤이 정상인 그룹에서 비만이 아닌 경우의 오즈  0.899/0.1001 = 8.99

- Logit(P) = log(P/1-P)

  • 값의 범위를 0- 1 범위로 입력받아서 자연 로그를 취한 함수가 Logit 함수
  • P는 특성 x가 주어졌을 때 이 샘플이 클래스 1에 속할 조건부 확률
  • 샘플이 특정 클래스에 속할 확률을 예측하는 것이 관심 대상이므로 logit 함수를 거꾸로 뒤집는데 이러한 함수가 로지스틱 시그모이드(logistic sigmoid function) 함수

- 약물 치료에 대한 환자의 반응(종속 변수)을 예측하고자 할 때 약물 치료 적용 후 환자가 살아남은 경우 1 살아남지 못한 경우를 0으로 표현할 수 있음

단순선형회귀Logistic Regression 분석

 
 
 

10-2) Logit 함수

- Odds Ratio에 로그를 취한 것
- 자연 로그를 취하면 부드러운 곡선의 형태
- 직선의 형태로 구분하는 단순 회귀보다는 조금 더 정확한 모델을 만들 가능성이 높다. 

  • 경계선 부분에 데이터가 많은 경우

 

10-3) API

- sklearn.linear_model LogisticRegression

  • penalty : 페널티를 부여할 때 사용할 기준을 결정('l1', 'l2')
  • dual : Dual Formulation인지 Primal Formulation인지를 결정(True, False)
  • tol : 중지 기준에 대한 허용 오차 값
  • C : 규칙의 강도의 역수 값
  • fit_intercept : 의사 결정 기능에 상수를 추가할 지 여부 결정(True, False)
  • class_weight : 클래스에 대한 가중치들의 값
    • 타겟 값 비율이 다를 때
  • random_state : 데이터를 섞을 때 사용하는 랜덤 번호 생성기의 시드 값
  • solver : 최적화에 사용할 알고리즘 결정('newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga')
  • max_iter : solver가 수렴하게 만드는 최대 반복 횟수 값
  • multi_class : ('ovr', 'multinomial')
  • warm_start : 이전 호출에 사용했던 solution을 재사용 할지 여부 결정(True, False)
  • _jobs : 병렬 처리 시 사용 할 CPU 코어의 수
    • n_jobs: n개만큼 동시에 작업, -1을 쓰면 최대만큼 묶어서 작업
from sklearn import datasets
iris = datasets.load_iris()
X = iris.data
y = iris.target

print(X[:5])
print(iris.feature_names)
print(y[:5])
print(iris.target_names)
[[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 [4.7 3.2 1.3 0.2]
 [4.6 3.1 1.5 0.2]
 [5.  3.6 1.4 0.2]]

['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

[0 0 0 0 0]

['setosa' 'versicolor' 'virginica']
from sklearn.linear_model import LogisticRegression

log_reg = LogisticRegression()
log_reg.fit(X, y)

print(log_reg.predict([X[0]])) #X[0]하나만 보더라도 [X[0]]
[0]
print(log_reg.predict(X[0].reshape(-1, 4))) 
print(y[0])
[0]
0
  • 맞춤
print(X[0].reshape(-4, 1))
[[5.1]
 [3.5]
 [1.4]
 [0.2]]

 
 

10-4) 소프트맥스 회귀

 - 이진 분류 알고리즘을 가지고 다중 클래스 분류를 할 때는 여러 개의 분류를 만들어서 처리

  • 클래스 개수만큼 이진 분류기를 만들어서 분류기를 통과한 결과 중 가장 높은 확률을 가진 클래스로 판정
  • 2개씩 쌍으로 분류할 수 있는 모든 분류기를 만들어서 판정하는 방식

- 여러 개의 이진 분류기를 훈련시켜 연결하지 않고 직접 다중 클래스를 지원하도록 일반화한 방식 : 소프트맥스 회귀

  • 소프트맥스 함수는 정규화된 지수함수라고도 하는데, 각 피처들을 가지고 클래스에 대한 점수를 계산하는 선형 회귀식을 만들어서 사용
  • 각 클래스에 대한 선형 회귀식을 별도로 만들어 회귀식에 의해서 샘플에 대한 점수가 계산이 되면 소프트맥스 함수를 통과시켜서 확률 추정

 

10-5) 크로스 앤트로피

* 앤트로피: 불확실성. 값이 작을 수록 데이터가 잘 분리되어있다는 뜻.

- 모델에서 예측한 확률값이 실제값과 비교했을 때 틀릴 수 있는 정보량
-  값 이 적을 수록 모델이 데이터를 더 잘 예측하는 모델
- 정보 이론에서 유래
- 여러개의 상태가 있을 때 이 상태를 표현하는 방법을 비트 수를 다르게 해서 표현하는 방식

  • 자주 등장하는 상태는 비트 수를 적게 해서 만들고 자주 등장하지 않는 상태는 비트 수를 크게 만드는 것

 
현재 상태가 4가지 (첫번째 상태가 90%이고, 나머지 상태가 7% , 2% , 1% 인 경우
- 일련번호 방식

  • 00
  • 01
  • 10
  • 11

- 원핫인코딩

  • 1000
  • 0100
  • 0010
  • 0001

- 크로스 앤트로피

  • 0 - 첫번째 상태
  • 10 - 두번째 상태
  • 110 - 세번째 상태
  • 1111 - 네번째 상태

 

10-6) 로지스틱 회귀에서 소프트맥스 사용법

- multi_class 옵션에 multinomial 적용
- solver 옵션에 lbfgs 같은 소프트맥스 알고리즘을 적용할 수 있는 알고리즘 설정

log_reg = LogisticRegression(multi_class = 'multinomial', 
                            solver='lbfgs', random_state=42)
log_reg.fit(X, y)

print(log_reg.predict(X[0].reshape(-1, 4)))
print(y[0])
print(log_reg.predict(X[51].reshape(-1, 4)))
print(y[51])
[0]
0
[1]
1

 
 
 
 

11. SVM(Support Vector Machine)

- 일반적인 머신러닝 기법 중에서 매우 강력하고 선형 또는 비선형, 회귀, 이상치 탐색에도 사용하는 머신러닝 모델
- 초창기에는 가장 인기있는 모델에 속함, 현재는 아님
- 복잡한 분류 문제에 적합했고, 작거나 중간 크기의 데이터 세트에 적합
- 모든 속성을 활용하는 전역적 분류 모형
- 군집별로 초평면을 만드는데 이 초평면은 다른 초평면의 가장 가까운 자료까지의 거리가 가장 크게 만드는 것
 

11-1) 특징

- 장점

  • 에러율이 낮음
  • 결과를 해석하기 용이

- 단점

  • 파라미터 및 커널 선택에 민감
  • 이진분류만 가능
  • 특성의 스케일에 굉장히 민감. 반드시 스케일링 수행해야 함

 

11-2) 하드마진과 소프트마진

- 하드마진

  • 모든 데이터가 정확하게 올바르게 분류된 것
  • 하드마진이 성립되려면 데이터가 선형적으로 구분될 수 있어야 하고, 이상치에 민감

- 소프트마진

  • 이상치로 인해 발생하는 문제를 피하기 위해서 좀더 유연한 모델을 생성 
  • 어느정도의 오류를 감안하고 결정경계를 만들어내는 방식
  • SVM에서 매개변수 C로 마진 오류 설정
    • 마진 오류는 적은 것이 좋지만 너무 적게 설정하면 일반화가 잘 안됨
      *일반화: 지금껏 보지 못한 데이터에 올바르게 적용되는 성질

 

11-3) 선형 SVM 모델을 이용한 붓꽃 분류

- Pipeline을 사용

  • 피처 스케일링과 모델 훈련을 하나의 파이프라인으로 묶어서 수행
#피처 스케일링
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

#모델 생성해서 훈련
from sklearn.svm import LinearSVC
svc_clf = LinearSVC(C=1, random_state=42)
svc_clf.fit(X, y)

#예측
svc_clf.predict([[5.5, 1.7]])
array([1.])

 
- 모든 컬럼에 스케일링하는 것이 동일하다면 파이프라인으로 묶는 것이 가능

#피처 스케일링
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

#스케일러와 분류기를 하나로 묶어서 사용
svm_clf = Pipeline([
    ('scaler', StandardScaler()), 
    ('linear_SVC', LinearSVC(C=1, random_state=42))
])

svm_clf.fit(X, y)
svc_clf.predict([[5.5, 1.7]])
array([1.])

 
 

11-4) 비선형SVM

- 선형 SVM 분류기가 효율적이지만 선형적으로 분류할 수 없는 데이터가 많음 
- 선형 SVM(커널)의 문제점중 하나가 XOR을 구분할 수 없는 점  => 다항식을 이용하여 해결
- 다항식을 이용하는 SVM을 생성하기 위해서는 PolynomialFeatures 변환기를 추가하면 됨

#데이터 생성
from sklearn.datasets import make_moons

X, y = make_moons(n_samples=100, noise=0.14, random_state=42)

#데이터 시각화
def plot_dataset(X, y, axes):
    plt.plot(X[:, 0][y==0], X[:, 1][y==0], "bs")
    plt.plot(X[:, 0][y==1], X[:, 1][y==1], "g^")
    plt.axis(axes)
    plt.grid(True, which='both')
    plt.xlabel("x_1", fontsize=20)
    plt.ylabel("x_2", fontsize=20, rotation=0)

plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])

 
# 다항식을 추가한 선형 분류기

from sklearn.preprocessing import PolynomialFeatures

polynomial_svm_clf = Pipeline([
        ("poly_features", PolynomialFeatures(degree=3)),
        ("scaler", StandardScaler()),
        ("svm_clf", LinearSVC(C=1,random_state=42))
    ])

polynomial_svm_clf.fit(X, y)

 

다항식 없으면degree = 10degree = 30


 

  • degree는 추가되는 다항의 지수승을 의미한다.
    • 숫자가 높아지면 잘 분류할 가능성은 높아지지만 학습할 피처의 개수가 늘어나고, 이로인해 훈련 속도도 느려지고 학습 속도가 느려진다.
    • OVerfitting 될 가능성이 높아진다.

 
 

11-5) 다항식 커널

- 다항식 특성을 추가하면 성능이 좋아졌는데 낮은 차수의 다항식은 매우 복잡한 데이터 세트에는 잘 맞지 않을 것이고 높은 차수의 다항식은 굉장히 많은 특성을 추가하므로 모델을 느리게 함
- Kernel, Trick이라는 수학적 기교를 이용해서 실제로는 특성을 추가하지 않으면서 다항식 특성을 추가한 것과 같은 효과를 얻는 방법 사용
- 이 기법을 사용할 때는 SVC 클래스에서 매개변수 kernel에 poly 설정, 매개변수 degree에 적용하고자 하는 차수를 설정, 매개변수 coef()에 정수값을 설정. 낮은 차수와 높은 차수 중에서 어떤 다항식에 영향을 받을지 설정

#다항식 커널은 실제로 다항식을 추가하는 것이 아니라 추가하는 것과 같은 효과를 냄
from sklearn.svm import SVC

#스케일러와 분류기를 하나로 묶어서 사용
poly_kernel_svm_clf = Pipeline([
    ('scaler', StandardScaler()), 
    ('svm_SVC', SVC(kernel='poly', degree=10, coef0=1, C=1, random_state=42))
])

poly_kernel_svm_clf.fit(X, y)

plot_predictions(poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()

 
 

11-6) 유사도 특성을 추가하는 가우시안 RBF 커널

- 몇 개의 랜드마크를 추가한 후 이 랜드마크와의 유사도를 측정해서 그 값을 특성으로 추가
- SVC로 생성, 매개변수 kernel에 RBF, gamma 설정

  • gamma를 너무 적은 수로 설정하면 선형 분류에 가까워지고 높게 설정하면 다항식을 이용하는 분류에 가까워짐
#가우시안 커널 이용
from sklearn.svm import SVC

rbf_kernel_svm_clf = Pipeline([
    ('scaler', StandardScaler()), 
    ('svm_SVC', SVC(kernel='rbf', gamma=0.01, C=1, random_state=42))
])

rbf_kernel_svm_clf.fit(X, y)

plot_predictions(rbf_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()

 
 

11-7) 커널의 종류로 sigmoid도 존재

 

11-8) 시간복잡도

* 문자를 이용해서 행열을 표현할 때 앞의 숫자가 행의 수, 뒤의 숫자가 열의 수

- SGDClassifier(선형 회귀 분류):  O(m*n)
- LinealSVC: O(m*n)
- SVC: O(m^ * n) ~ O(m^^ * n)
 
 
 

12. Decision Tree(결정 트리)

- 데이터 사이에 존재하는 패턴을 예측 가능한 트리 모양의 조건문 형태로 만드는 방식
- 복잡한 데이터세트도 학습하는 강력한 알고리즘
- RandomForest 모델의 기본 구성 요소
- 분류와 회귀에 모두 사용할 수 있고 다중 출력도 가능

  • 분류에서는 마지막 터미널 값을 그대로 리턴하고 회귀의 경우는 평균을 리턴

- 트리는 한번 분기할 때마다 변수 영역을 2개로 구분

  • 한번 구분한 뒤 순도가 증가하거나 불순도가 감소하는 방향으로 학습을 수행
  • 순도가 증가하거나 불순도가 감소하는 것을 정보획득이라고 함

- API: DecisionTreeClassifer, DecisionTreeRegression

  • max_depth: 트리의 깊이 
iris = datasets.load_iris()

X = iris.data[:, 2:]
y = iris.target

from sklearn.tree import DecisionTreeClassifier
tree_clf = DecisionTreeClassifier(max_depth=2, random_state=42)
tree_clf.fit(X, y)

 
- leaf: (Terminal) 자식이 없는 노드
 
 

12-1) 트리 시각화를 위한 준비

- graphviz.org 다운로드, 환경변수에 path 추가

!pip install graphviz
!pip install pygraphviz
from sklearn.tree import export_graphviz
import graphviz

export_graphviz(
        tree_clf,
        out_file="iris_tree.dot",
        feature_names=iris.feature_names[2:],
        class_names=iris.target_names,
        rounded=True,
        filled=True
    )
    
with open('iris_tree.dot') as f:
    dot_graph = f.read()
src = graphviz.Source(dot_graph)

src

 
 
 
12-2) 트리 시각화
 
 

12-3) 불순도 지표 

- 트리 모델은 순도가 증가하거나 불순도가 감소하는 방향으로 학습
- 지니계수(Gini Index): 오분류 오차

  • 1에서 전체 데이터 개수에서 분류된 클래스의 데이터 개수를 제곱한 값을 전부 뺀 값
  • 작은 값이 좋은 값
  • 총 54개의 데이터를 분류했는데 클래스1이 0개, 클래스2가 49개, 클래스3이 5개로 분류된 경우
    1 - (0/54)^ - (49/54)^ - (5/54)^

- 앤트로피

  • 기본적으로는 지니 계수를 사용하지만 crieterion을 entropy로 설정하면 불순도 지표가 변경됨
  • 하나의 영역에 2가지 클래스가 배정된 경우
    하나의 영역에 16개의 데이터가 배정었고 1번이 10개, 2번이 6개가 배정되면
    10/16 * log2(10/16) - 6/16 * log2(6/16)
  • 앞뒤 비율이 비슷해질수록 오분류 가능성이 커진다.
  • 한쪽으로만 데이터가 배정되면 앞의 값이 0이 되고 뒤의 값도 0이 되어 결국 0이 됨.
    0에 가까운 값이 좋은 값이다.

 

12-4) 규제 매개변수

- 결정 트리는 훈련 데이터에 대한 제약사항이 거의 없음
- 규제를 하지 않으면 트리가 훈련 데이터에 아주 가깝게 맞추려고 해서 과대적합이 발생
- 결정 트리는 모델의 하이퍼 파라미터가 없는 것이 아니라 훈련되기 전에 파라미터 수가 결정되지 않기 때문에 모델 구조가 데이터에 맞춰져서 고정되지 않고 자유로움

  • 하이퍼 파라미터의 기본값이 대부분 Max 값이거나 None이다.
  • 이전의 다른 모델은 하이퍼 파라미터의 값이 설정되어 있음
  • 하이퍼 파라미터의 값이 기본값을 가지는 경우는 과대 적합될 가능성은 낮지만 과소 적합될 가능성이 있고 하이퍼 파라미터의 값이 기본값을 가지지 않고 MAX 값을 가지는 경우 과대 적합될 가능성은 높지만 과소 적합될 가능성은 낮다.
비선형 커널에서 degree라는 하이퍼 파라미터가 있다.
degree가 높으면 데이터를 정확하게 분류할 확률이 높다. 
degree를 -1로 설정할 수 있다고 하면 정확하게 분류할 가능성은 높아지지만

 
- 결정 트리의 형태를 제한하는 매개변수

  • max_depth: 최대 깊이, 특성의 개수를 확인해서 적절하게 설정
  • min_samples_split: 분할을 할 때 가져야 하는 데이터의 최소 개수
  • min_samples_leaf: 하나의 터미널이 가져야 하는 최소 개수
  • min_weight_fraction_leaf: 개수가 아니라 비율로 설정
  • max_leaf_nodes: 터미널의 개수 
  • max_features: 분할에 사용할 특성의 개수

min으로 시작하는 매개변수를 증가시키거나 max로 시작하는 매개변수를 감소시키면 규제가 커짐

  • 규제가 커지면 오차가 발생할 확률은 높아진다.

 

12-5) 트리 모델의 단점

- 훈련 데이터의 작은 변화에도 매우 민감

  • 조건문을 사용하는 방식이기 때문에 하나의 데이터가 추가되었는데 이 데이터가 조건에 맞지 않으면 트리를 처음부터 다시 만들어야 할 수도 있다.
  • 트리 모델을 훈련시켜서 다른트리 모델과 비교하고자 할 때는 반드시 random_state를 고정시켜야 한다.

 

12-6) 피처의 중요도 확인

- 트리 모델에는 feature_importance 

#피처의 중요도 확인
print(tree_clf.feature_importances_)
print(iris.feature_names[2:])
[0.56199095 0.43800905]
['petal length (cm)', 'petal width (cm)']
iris_importances_ = pd.Series(tree_clf.feature_importances_,
                             index=iris.feature_names[2:])
sns.barplot(x=iris_importances_, y= iris_importances_.index)
plt.show()

 
 

12-7) 모델 생성 및 훈련

from sklearn.datasets import load_iris
#붓꽃 데이터 가져오기
iris = load_iris()
#피처 선택
#iris.data - 열의 개수가 4개인 2차원 배열
#iris.feature_names - 피처 이름 확인 
#print(iris.feature_names)
 

 

#피처 선택
X = iris.data
#타켓 선택
y = iris.target
#결정 트리를 생성해서 훈련
from sklearn.tree import DecisionTreeClassifier
#max_depth 가 깊으면 정확한 분류를 해낼 가능성은 높지만 시간이 오래 걸리고
#과대 적합 문제 발생 가능성이 있음
#트리 모델은 트리를 한 개 생성하는 것이 아니고 여러 개 생성하므로
#각 트리가 동일한 데이터를 가지고 훈련해야 하므로 random_state를 고정시켜야 합니다.
tree_clf = DecisionTreeClassifier(max_depth=2, random_state=42)
tree_clf.fit(X, y)
 

 
 

12-8) 예측

#예측
result = tree_clf.predict([[5.1, 3.5, 1.4, 0.2]])
print(result)

 
 
 

12-9) 트리 모델 시각화

1) 트리 모델의 강점

- 화이트박스, 이해가 쉽다. 

  • 트리 구조 시각화

- 피처의 중요도 파악이 쉽다
- 별도의 파라미터 튜닝을 하지 않아도 좋은 성능
- 데이터 전처리가 거의 필요 없음
 

2) 시각화 준비

- graphviz 설치: 화면에 트리를 출력하는 것이 목적
 - Windows
   https://graphviz.org/download/#windows 에서 graphviz를 다운로드 받아서 설치
   graphviz 가 설치된 디렉토리의 bin 디렉토리 와 bin 안에 있는 dot.exe 경로를 path에 추가

 # 패키지 설치

   pip install graphviz

 
- pygraphviz 설치: 출력된 트리를 이미지 파일로 만드는 것이 목적

  •  python -m pip install --use-pep517 --config-settings="--global-option=build_ext" --config-settings="--global-option=-IC:\Program Files\Graphviz\include"  --config-settings="--global-option=-LC:\Program Files\Graphviz\lib" pygraphviz
    \

 

3) 이미지로 출력

from sklearn.tree import export_graphviz
import graphviz
export_graphviz(
    tree_clf,
    out_file="iris_tree.dot",
    feature_names = iris.feature_names,
    class_names = iris.target_names,
    rounded=True,
    filled=True
)
#강제로 path 에 경로를 추가
import os
os.environ["PATH"] += os.pathsep + 'C:\Program Files\Graphviz\bin'
with open("iris_tree.dot") as f:
    dot_graph = f.read()
src = graphviz.Source(dot_graph)
src

 
 

4) 이미지 파일로 저장

import pygraphviz as pga
from IPython.display import Image
graph = pga.AGraph("./iris_tree.dot")
graph.draw("iris_tree.png", prog='dot')
Image("iris_tree.png")

 
 

5) 최적의 파라미터 찾기 - 하이퍼 파라미터 튜닝( max_depth, min_sample_split)

from sklearn.model_selection import GridSearchCV

#파라미터 모음 만들기
params = {
    'max_depth': [2, 4, 6, 8, 12, 16],
    'min_samples_split': [2, 4, 8, 10]
}

grid_cv = GridSearchCV(tree_clf, param_grid=params, scoring='accuracy',
                      cv = 5, verbose=1)
grid_cv.fit(X, y)

print("가장 좋은 파라미터:", grid_cv.best_params_)
print("가장 좋은 정확도:", grid_cv.best_score_)


6) 피처의 중요도 확인

best_iris_clf = grid_cv.best_estimator_
iris_importances = pd.Series(best_iris_clf.feature_importances_, 
                             index=iris.feature_names)
#2개의 피처는 영향력이 거의 없음
print(iris_importances)

iris_top3 = iris_importances.sort_values(ascending=False)[:3]
print(iris_top3)

sns.barplot(x=iris_top3, y=iris_top3.index)
plt.show()

 

[Python] 머신러닝

0ㅑ채
|2024. 3. 7. 14:00

1. 인공지능

- 지능 : 문제를 해결할 수 있는 능력

  • 환자를 보고 병을 진단

- 인공지능: 지능 작업을 수행할 수 있는 기계의 능력

  • 환자에 대한 정보를 입력하면 컴퓨터가 병을 진단

- 구현 방법: 지식 공학(전문가 시스템): 문제 해결을 위한 알고리즘을 사람이 작성

  • 전문가의 도움을 받아서 개발자가 알고리즘을 작성해서 컴퓨터에 저장하고 이 알고리즘을 따라서 문제 해결
  • 컴퓨터의 역할은 결과를 만들어내는 것

- Machine Learning

  • Data와 Output을 주면 컴퓨터가 알고리즘을 만들어내는 방식

 

인공지능과 머신러닝의 관계

- 인공지능 > 기계학습(머신러닝) > 딥러닝, 강화학습
- 전문가 시스템 -> 머신 러닝 -> 딥러닝, 강화학습

  • 딥러닝은 이겼다, 졌다로 판정해주지만 강화학습은 순간순간 확률을 보여준다.=> 게임에 적합
  • 이미지 데이터가 존재하는 경우 머신 러닝은 이미지 데이터 1개를 데이터 1개로 바라보지만 딥러닝은 이를 작게 쪼개서 그 안에서 알고리즘을 찾을 수 있음

 
 
 

2. Machine Learning

- 데이터를 가지고 학습하도록 컴퓨터를 프로그래밍하는 과학
- 명시적으로 프로그램 되는 것이 아니라 훈련되며 작업과 관련있는 샘플을 제공하면 이 데이터에서 통계적 구조를 찾아 그 작업을 자동화하기 위한 규칙을 만들어내는 것
- 필요 요소

  • 입력 데이터 포인트: 최근에는 여러 입력 데이터 포인트로 얻어진 데이터를 한 곳에 모아서 처리하는 부분에 대해 중점
    • 데이터 발생지가 여러 곳인 경우 별도로 처리하는 것이 어렵기 때문에 한 곳에 잘 정리를 해서 모으는 것이 중요
  • 기대 출력: 어떤 결과를 원하는지
  • 알고리즘 성능 측정 방법: 평가 지표

 

1) 사용하는 이유

- 전통적인 방식으로는 너무 복잡하거나 알려진 알고리즘이 없는 문제

  • 음성인식은 직접 알고리즘을 만들기에는 너무 복잡해서 머신러닝을 이용

- 머신 러닝을 통해 학습을 할 수 있기 때문
대용량의 데이터를 분석하다보면 기존에 알고 있지 않은 또는 겉으로는 보이지 않는 패턴 발견 가능 - 데이터 마이닝
 
 

2) 역사

- 확률적 모델링

  • 나이브 베이즈
  • 로지스틱 회귀

- 신경망

  • 등장은 1950년대, 이때는 컴퓨터 성능이 좋지 못해서 효과적인 훈련 방법을 찾지 못했다.
  • 1989년 얀 르쿤에 의해 초창기 합성곱 신경망이 등장하면서 다시 각광

- 커널 방법

  • 분류에 사용되었는데 선형이 아닌 비선형으로 결정 경계 만들기 시작

- Decision Tree, Random Forest, Gradient Boosting: 앙상블 모형

  • 컴퓨터 성능이 좋아지면서 하나의 알고리즘을 부트스트랩을 이용하거나 여러 개의 알고리즘을 한꺼번에 학습하는 방법

- 신경망
- 생성형 AI
 
 

3) 분류

- 레이블(정답)의 존재 여부에 따른 분류

  • 지도학습: 레이블 존재 (회귀 or 분류)
  • 비지도학습: 레이블 없음 (주성분분석, 군집, 연관분석 등)
  • 준지도학습: 레이블을 만드는 작업 (레이블이 일부분밖에 없어서)
  • 강화학습: 보상이 주어지는 방식

- 실시간 점진적으로 학습을 할 수 있는지 여부

  • 온라인학습: 점진적 학습 가능
    • 실시간으로 데이터가 들어오는 경우도 학습이 가능하다.
  • 배치학습: 점진적 학습 불가능
    • 데이터가 확정이 되어있어야 한다.

- 사례 기반 학습과 모델 기반 학습

  • 사례 기반 학습: 알고 있는 데이터와 새 데이터를 비교하는게 목적
  • 모델 기반 학습: 패턴을 발견해서 예측 모델을 만드는 방식

 
 

4) 지도학습 (Supervised Learning)

- 레이블이 존재하는 학습
- 입력-출력 쌍들을 매핑해주는 함수 학습
- 종류

  • 출력이 이산적일 때: 분류
  • 출력이 연속적일 때: 회귀
  • 출력이 확률인 경우: 추정 (Deep Learning)

- 단점

  • 사용할 수 있는 데이터에 한계 
  • 데이터를 생성하는 데 많은 비용

- 선형회귀
- 로지스틱 회귀
- k-최근접 이웃
- 서포트 벡터 머신
- 결정 트리
- 랜덤 포레스트
- 신경망
 
- 로지스틱 회귀를 제외하고는 분류와 회귀에 모두 사용 가능

  • 로지스틱 회귀는 분류만 가능

 
 

5) 비지도 학습(Unsupervised Learning)

- 레이블이 존재하지 않는 학습
- 데이터에 내재된 고유의 특징을 탐색하기 위해서 사용
- 지도 학습에 비해서 학습하기 어려움
 
- 군집

  • k means 
  • DBSCAN
  • 계층군집
  • 이상치 탐지와 특이치 탐지
  • 원클래스
  • 아이솔레이션 포레스트

- 시각화와 차원 축소

  • PCA (주성분 분석)
  • LLE (지역적 선형 임베딩)
  • t-SNE

- 연관 규칙 학습

  • Apriori
  • eclat

 

6) 준지도 학습

- 라벨링이 일부분만 되어있어서 그 데이터를 이용해서 라벨이 없는 데이터에 라벨을 붙이기 위해서 사용
 
 

7) 강화 학습

- 결과가 바로 주어지지 않고 시간이 지나서 주어지는 방식
- 최근에 로봇 같은 분야에서 많이 이용
 
 

8) 애플리케이션 사례

- Netflix의 영화 추천 시스템: 고객의 평점작성 내역과 구매 내역을 이용해서 추천
- 미국 국가 안보국의 SKYNET: 파키스탄의 테러리스트를 식별해서 사살하기 위한 프로그램

  • 휴대 전화 기록을 이용하여 휴대 전화를 자주 끄거나 USIM을 변경하는 사람을 테러리스트로 식별해서 사살했는데 잘못된 알고리즘으로 무고한 사람이 희생됨

 
 

3. scikit learn

- 파이썬 머신러닝 패키지 중 가장 많이 사용되는 라이브러리
- 가장 Python스러운 API
 - 패키지: sklearn

  • 아나콘다는 내장

 
 

4. 데이터 표현 방식

1) 테이블로서의 데이터

- 기본 테이블은 2차원 데이터 그리드 형태
- 행: 데이터 세트의 개별 요소, sample이라 부르고 행의 개수는 n_sample이라 표현
- 열:각 요소와 관련된 수량,  feature 또는 target이라고 부르는 경우가 많고 열의 개수를 n_features라고 표현

  • feature: 독립적인 데이터 
  • Target : feature로 인해서 만들어지는 데이터로 label이라고도 함

 

2) feature

- 보통은 X라는 변수에 저장
- 특징 행렬이라는 표현을 사용. [n_sampels, n_features]의 모양을 가진 2차원 행렬이라 가정하며 실제 자료형은 numpy의 ndarray나 pandas의 DataFrame으로 되어 있는 경우가 많은데 가끔 sklearn의 희소 행렬인 경우도 있음
- 정량적인 데이터여야 하기 때문에 대부분의 경우는 실수지만 이산적인 데이터아 부울도 가ㅡㄴㅇ
 
 

3) target

- 대상 행렬이라고 하는데 y로 표시
- numpy의 1차원 ndarray나 pandas의 Series인 경우가 많음
- 연속적인 수치가 이산 클래스를 가질 수 있음

import seaborn as sns
iris = sns.load_dataset('iris')
#특징 행렬과 타겟 분리
X_iris = iris.drop('species', axis=1)
Y_iris = iris['species']
#species를 분류하기 위한 데이터 (타겟)
#나머지 데이터는 특징행렬

 
 
 

5. Estimator API(머신러닝 모델 API)

1) 기본 원칙

- 일관성

  • 모든 객체는 일관된 문서를 갖춘 제한된 함수 집합에서 비롯된 공통 인터페이스 공유
  • 거의 모든 객체는 동일한 작업을 수행하는 메소드를 공통으로 소유
  • 템플릿 메소드 패턴: 공통으로 사용될 것 같은 메소드를 인터페이스에 등록하고 이를 클래스에서 구현해서 사용

- 검사 (inspection)

  • parameter: 함수나 기능을 수행하기 위해서 내부적으로 사용하는 데이터. argument, 인수, 인자, 매개변수라고 하기도 함
  • hyper parameter: 개발자가 직접 설정하는 파라미터. 
    • hyper parameter 튜닝은 값을 변경해서 더 좋은 모델을 만들거나 최적의 값을 찾아가는 작업이다.
  • 모든 hyper parameter를 public 속성으로 노출해서 확인이 가능하도록 함

- 제한된 객체 계층 구조

  • 알고리즘만 python 클래스에 의해 표현, 데이터세트는 표준 포맷으로 표현, 매개변수 이름은 문자열
    • 표준 포맷(numpy의 ndarray, pandas의 DataFrame, scipy의 Sparse Matrix)

- 구성

  • 대부분의 머신러닝 작업은 기본 알고리즘의 시퀀스로 나타낼 수 있음 (순서대로)

- 합리적인 기본값

  • 대다수의 하이퍼 파라미터는 라이브러리가 적절한 기본값을 가지도록 정의

 
 

2) 사용 방법

- 적절한 모델 클래스 mport
- 모델 클래스를 인스턴스화 할 때 적절한 하이퍼 파라미터를 설정
- 데이터를 특징 배열과 타겟 배열로 생성
- 모델 클래스의 인스턴스의 fit 메소드를 호출해서 모델을 데이터에 적합하도록 훈련
- 모델을 새 데이터에 적용

  • 지도 학습의 경우: predict 함수에 새로운 데이터 사용해서 예측
  • 비지도 학습의 경우: transform이나 predict를 이용해서 데이터의 속성을 변환하거나 예측

 
 

3) 선형 회귀 수행

 - sklearn.linear_model.LinearRegression

#샘플 데이터 생성
rng = np.random.RandomState(42)

#데이터 50개 생성
x = 10 * rng.rand(50)
#데이터를 이용해서 타겟 데이터 생성 - rng.randn(50)은 잡음
y = 2 * x -1 + rng.randn(50)

#x 데이터를 특징 행렬로 변환
print(x.shape) #1차원 배열 - 특성 배열은 2차원 배열, DataFrame, 희소 행렬
X = x.reshape(x.shape[0], -1) #x[:, np.newaxis]도 가능
print(X.shape) # X는 특성 배열이 되었다.
(50,)
(50, 1)
#추정기 인스턴스 생성
from sklearn.linear_model import LinearRegression
model = LinearRegression(fit_intercept=True)

#기존 데이터로 훈련
model.fit(X, y)

#훈련 결과 확인
print("회귀 계수:", model.coef_)
print("절편:", model.intercept_)
회귀 계수: [1.9776566]
절편: -0.9033107255311164
  • 훈련 결과를 확인 - 스스로 만든 파라미터나 결과에는 _가 붙음
  • 하이퍼 파라미터에는 _가 붙지 않음
#회귀는 지도학습: predict 함수로 예측
xfit = np.linspace(-1, 11)
Xfit = xfit[:, np.newaxis]
yfit = model.predict(Xfit)

#시각화
plt.scatter(x, y)
plt.plot(xfit, yfit)
  • 잡음을 섞어서 일정하게 나오지는 않는다.

 
 

4) 평가 지표를 이용한 성능 평가

- 앞의 방식은 모든 데이터를 가지고 훈련을 해서 모델을 만든 후 새로운 데이터에 적용한 방법

  • 여러 모델을 이용해서 만든 모델을 평가할 수 없다.

- 일반적으로 머신러닝에서는 기존 데이터를 분할해서 훈련에 사용하고 나머지 데이터를 이용해서 모델을 평가한다.

  • 여러 머신러닝 추정기를 적용하고 최적의 성능을 가진 모델을 선택하는 것이 일반적

 
 
 

6. Machine Learning 절차

1) 순서

- 작업 환경 설정: 파이썬 설치, 가상 환경 생성, 필요한 패키지 설치

  • 서비스를 만들고자 하는 경우는 가상 환경 생성 작업이 필수

- 문제 정의
- 데이터 수집 
- 데이터 탐색
- 데이터 전처리
- 모델을 선택하고 훈련
- 평가하고 전처리부터 다시 수행
- 최적의 모델을 선택하고 솔루션 제시
- 시스템을 런칭하고 모니터링 하면서 유지보수
 
 
2) 실행

- 환경 설정 
- 데이터 수집 : housing.csv

  • 캘리포니아 주택 데이터셋
  • 블록 별로 여러 컬럼들을 소유하고 있음

- 문제 정의: 중간 주택 가격 예측

  • 주택 가격이 이미 존재하므로 지도학습이다.
  • 주책 가격은 연속형 데이터이므로 회귀 분석

- 데이터를 불러와서 탐색

# 데이터 탐색
housing = pd.read_csv('./python_machine_learning-main/data/housing.csv')
housing
#각 열읱 특성 파악
housing.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 10 columns):
 #   Column              Non-Null Count  Dtype  
---  ------              --------------  -----  
 0   longitude           20640 non-null  float64
 1   latitude            20640 non-null  float64
 2   housing_median_age  20640 non-null  float64
 3   total_rooms         20640 non-null  float64
 4   total_bedrooms      20433 non-null  float64
 5   population          20640 non-null  float64
 6   households          20640 non-null  float64
 7   median_income       20640 non-null  float64
 8   median_house_value  20640 non-null  float64
 9   ocean_proximity     20640 non-null  object 
dtypes: float64(9), object(1)
memory usage: 1.6+ MB
  • total_berooms는 결측치 존재
  • ocean_proximity는 자료형이 객체 - 문자열
#문자열 컬럼은 범주형인지 확인
housing['ocean_proximity'].value_counts()
ocean_proximity
<1H OCEAN     9136
INLAND        6551
NEAR OCEAN    2658
NEAR BAY      2290
ISLAND           5
Name: count, dtype: int64
  • 5개 종류인 것으로 봐서 범주형이다.
#숫자데이터 범위 확인
housing.describe()
#숫자 데이터는 분포 확인 - 히스토그램
housing.hist(bins=50, figsize=(20, 15))
plt.show()
  • 데이터의 분포가 한쪽으로 몰린 경우에는 데이터의 분포가 조금 더 중앙에 많이 몰리도록 수정할 필요가 있다. (로그변환)
  • 좌우로 너무 넓게 펼쳐져있으면 극단치를 제거하는 것에 대해서 고려
  • 각 숫자 데이터의 범위를 비교해서 범위가 차이가 많이 나면 스케일링 고려
    • population이 너무 크다.
    • total_rooms의 max가 이상하다. 

 

- 데이터 분리

  • 훈련 데이터와 테스트 데이터 또는 검증용 데이터로 분리하는 것이 일반적
  • 데이터 전체를 파악하기 전에 분리하는 것이 좋다. 
  • 전체 데이터를 가지고 데이터 탐색을 수행하게 되면 과대적합 가능성이 발생한다.
  • 사람의 뇌가 패턴을 감지해버리기 때문이다.
  • 이러한 현상을 Data Snooping이라고 함

  • 데이터를 나눌 때 보통 7:3, 8:2를 많이 이용하지만 데이터 개수에 따라 다른 선택을 할 수 있다. 일반적으로 데이터가 많으면 훈련 데이터의 비율을 낮추고 데이터의 개수가 적으면 훈련 데이터의 비율을 높인다.
  • 데이터가 아주 많으면 검증용 데이터를 별도로 분할해도 된다.
  • 시계열 데이터는 데이터를 순차적으로 분할한다.

  • 훈련을 여러번 반복해서 수행하면 알고리즘의 비교 측면이나 모든 데이터를 샘플로 사용하게 되는 상황을 방지하기 위해서 일정한 데이터를 랜덤하게 추출하는 경우도 있다.

  • 분류 문제에서는 타겟의 비율이 다르다면 층화 추출도 고려
    • 100일 중 1번만 비가 온다면, 안온다고 할 떄 정확도가 99%가 된다.
      비가 오는 경우와 안오는 경우를 적절히 섞어야 한다. 

# sklearn 의 model_selection.train_test_split API를 이용한 데이터 분리

import sklearn
help(sklearn.model_selection.train_test_split)
  • arrys는 데이터 배열
  • test_size는 테스트 데이터 비율
  • train_size는 훈련 데이터의 비율로 test_size를 설정하면 설정하지 않음
  • 데이터가 아주 많으면 훈련하는 데 시간이 너무 많이 걸릴 수 있어서 설정하기도 함
  • random_state는 시드 번호로 설정하는 것을 권장
  • shuffle은 데이터를 섞을지 여부로 random_state를 설정하면 무의미
  • stratify는 측화추출을 하고자 할 때 데이터의 비율
  • 리턴되는 데이터는 훈련데이터와 테스트데이터의 튜플
#8:2로 분할 - 데이터를 하나의 데이터로 제공하면 2개로 리턴하고
#특성 배열과 타겟 배열 2개를 대입하면 4개로 리턴

train_set, test_set = sklearn.model_selection.train_test_split(housing, 
                                                              test_size=0.2, 
                                                              random_state=42)
#분할된 데이터의 차원 확인
print(train_set.shape)
print(test_set.shape)
(16512, 10)
(4128, 10)
X = housing.drop('median_house_value', axis=1)
y = housing['median_house_value']

result = sklearn.model_selection.train_test_split(X, y, test_size=0.2, random_state=42)
print(type(result))
<class 'list'>

 
 
- 층화 추출: 계층적 샘플링 (데이터를 일정한 비율로 샘플링)

  • 회귀나 분류를 할 때 타겟의 데이터 분포가 일정하지 않은 경우 왜곡된 결과를 만들 수 있음
    • 분류의 경우 1과 0의 비율을 10대 1 정도 되는 상황에서 훈련 데이터에 0으로 분류되는 데이터가 하나도 없다면 이 경우 모델은 테스트 데이터에 결과가 좋지 않을 것
    • 0으로 분류되는 모든 데이터가 훈련 데이터에 포함되어 버리면 잘못하면 테스트 데이터에 완전하게 맞는 결과가 나와버릴 수 있다.
  • 회귀의 경우 타겟이 연속형이라면 범주형으로 변환해서 수행해야 한다.
    • 이때 사용할 수 있는 함수는 pandas의 cut 함수
    • 데이터와 구간의 리스트, 레이블의 리스트 대입
  • API 함수는 StratifiedShuffleSplit
    • 이 함수의 리턴되는 데이터는 데이터가 아니고 데이터의 인덱스
    • 결과를 가지고 다시 데이터 추출을 해야 함
X = housing.drop('median_house_value', axis=1)
y = housing['median_house_value']

X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, test_size=0.2, random_state=42)
print(X_train.shape)
print(X_test.shape)
print(y_train.shape)
print(y_test.shape)
(16512, 9)
(4128, 9)
(16512,)
(4128,)

 
- median_income의 비율을 이용한 층화 추출 (계층적 샘플링)

# 연속형 데이터를 범주형으로 변환

#pd.cut(데이터, bins =[경계값 나열] ,labels =[ 레이블 나열 ])

housing['income_cat'] = pd.cut(housing['median_income'],
                               bins=[0, 1.5 ,3.0 ,4.5, 6, np.inf],
                               labels=[1,2,3,4,5])

housing['income_cat'].value_counts()
income_cat
3    7236
2    6581
4    3639
5    2362
1     822
Name: count, dtype: int64

 
# 층화 추출을 위한 객체 생성

#층화 추출을 위한 객체 생성
#n_splits은 조각의 개수
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)

result = split.split(housing, housing['income_cat'])

for train_index, test_index in result:
    start_train_set = housing.loc[train_index]
    start_test_set = housing.loc[test_index]
    
#비율확인
start_train_set['income_cat'].value_counts() / len(start_train_set)
  • k-ford validation: 데이터를 몇조각으로 등분해서, 5조각을 만들고 4개로 모델 만들고 1개로 테스트하고. 
  • 인덱스를 리턴해주어서 행단위 추출을 다시 해줘야 한다. (for문)
income_cat
3    0.350594
2    0.318859
4    0.176296
5    0.114462
1    0.039789
Name: count, dtype: float64

 
 
- 데이터 탐색을 완료하기 전에 데이터를 분리하는 것을 권장
 

- 데이터 탐색: 상관계수 출력

  • 데이터의 독립성을 살펴볼 때는 상관계수만 보면 안된다.
    여러개의 피쳐가 다른 피쳐를 설명할 수 있기 때문이다.
  • corr() 함수는 숫자가 아닌 컬럼을 제거하고 사용해야 한다. 
#숫자 이외의 컬럼 제거 - housing.info()
corr_matrix = housing.drop("ocean_proximity", axis=1).corr()
corr_matrix
  • 방 개수와 침대방 개수는 상관계수가 높다. 당연히 높겠지

# pandas로 해보기

from pandas.plotting import scatter_matrix

scatter_matrix(housing[['median_house_value', 'median_income', 'total_rooms']], figsize=(12, 8))
plt.show()

 
#출력된 이미지 저장

plt.savefig('산포도', format='png', dpi=300)

 
 

- 특성 조합을 이용한 탐색

  • 여러 특성을 조합해서 새로운 특성을 만들고, 그 특성과 다른 특성간의 관계 확인하는 것도 중요
  • 방의 개수보다 방의 개수 대비 침실의 개수와 같은 특성을 만들어서 확인해볼 수 있다.
housing['bedrooms_per_room'] = housing['total_bedrooms']/housing['total_rooms']
corr_matrix = housing.drop('ocean_proximity', axis=1).corr()
corr_matrix['median_house_value'].sort_values(ascending=False)
median_house_value    1.000000
median_income         0.688075
income_cat            0.643892
total_rooms           0.134153
housing_median_age    0.105623
households            0.065843
total_bedrooms        0.049686
population           -0.024650
longitude            -0.045967
latitude             -0.144160
bedrooms_per_room    -0.255880
Name: median_house_value, dtype: float64
  • median_house_value 를 타겟으로 회귀하려 한다. 
  • 방의 개수와 침실의 개수는 상관계수가 낮다. 그런데 bedrooms_per_room은 -0.25니까 회귀를 하려면 차라리 이걸 쓰는게 낫다.
  • 그래서 도메인 지식이 중요하다.

 

- 피처와 타겟 분리

#훈련 데이터 복제 - 레이블을 제외한 데이터 복제
housing_feature = start_train_set.drop('median_house_value', axis=1)

#훈련 세트를 위해 레이블 삭제
#레이블에 변형을 적용하지 않기 위해서 레이블값도 복제
housing_leabels = start_train_set['median_house_value'].copy()

 
 

- 전처리 - 누락된 데이터 처리
 

#결측치 확인

#NaN 결측치 확인
sample_incomplete_rows = housing[housing.isnull().any(axis=1)].head()
sample_incomplete_rows

 
#누락 행 제거

#total_bedrooms가 누락된 행 제거
housing_feature.dropna(subset=["total_bedrooms"]).info()
 #   Column              Non-Null Count  Dtype   
---  ------              --------------  -----   
 0   longitude           16354 non-null  float64 
 1   latitude            16354 non-null  float64 
 2   housing_median_age  16354 non-null  float64 
 3   total_rooms         16354 non-null  float64 
 4   total_bedrooms      16354 non-null  float64 
 5   population          16354 non-null  float64 
 6   households          16354 non-null  float64 
 7   median_income       16354 non-null  float64 
 8   ocean_proximity     16354 non-null  object  
 9   income_cat          16354 non-null  category
  • 모두 16354!

# 누락 열 제거

#열 제거
sample_incomplete_rows.drop('total_bedrooms', axis=1)

 
# 누락 값 중간값으로 대체

from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")

#숫자로된 컬럼만 추출해서 NaN->중간값 대체
housing_num = housing.select_dtypes(include=[np.number])
imputer.fit(housing_num)

X = imputer.transform(housing_num)
housing_tr = pd.DataFrame(X, columns=housing_num.columns,
                          index = list(housing.index.values))

housing_tr.head()

 
 

- 범주형 데이터 처리

  • 머신러닝은 숫자만 다룸
  • 범주형이나 문자열 데이터가 있다면 숫자로 변환해야 함
  • 범주형의 경우 일련번호 형태로 변경하거나 원핫인코딩 필요
#범주형 데이터 추출
housing_cat = housing_features['ocean_proximity']
#일련번호로 만들기
housing_cat_encoded, housing_categoris = housing_cat.factorize()
print(housing_cat_encoded[:10])
print(housing_categoris)
[0 1 0 1 2 3 2 2 2 2]
Index(['INLAND', 'NEAR OCEAN', '<1H OCEAN', 'NEAR BAY', 'ISLAND'], dtype='object')
  • 원핫 인코딩 - 범주형의 개수만큼 열을 만들어서 해당하는 열에만 1 표시
#sklearn 의 변환기들은 2차원 배열을 요구합니다.
from sklearn.preprocessing import OrdinalEncoder
oridinalEncoder = OrdinalEncoder()
result = oridinalEncoder.fit_transform(housing_features['ocean_proximity'])
print(result[:10])
ValueError: Expected 2D array, got 1D array instead:
array=['INLAND' 'NEAR OCEAN' 'INLAND' ... '<1H OCEAN' '<1H OCEAN' 'INLAND'].
Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.
#sklearn 의 변환기들은 2차원 배열을 요구합니다.
from sklearn.preprocessing import OrdinalEncoder
oridinalEncoder = OrdinalEncoder()
result = oridinalEncoder.fit_transform(housing_features[['ocean_proximity']])
print(result[:10])
[[1.]
 [4.]
 [1.]
 [4.]
 [0.]
 [3.]
 [0.]
 [0.]
 [0.]
 [0.]]
#원 핫 인코딩 - 범주형의 개수 만큼 열을 만들어서 해당하는 열에만 1을 표시
from sklearn.preprocessing import OneHotEncoder
oneHotEncoder = OneHotEncoder()

#기본적으로 희소 행렬(sparse matrix)
result = oneHotEncoder.fit_transform(housing_features[['ocean_proximity']])
print(result[:10])
#밀집 행렬로 변환
print(result.toarray()[:10])
  (0, 1) 1.0
  (1, 4) 1.0
  (2, 1) 1.0
  (3, 4) 1.0
  (4, 0) 1.0
  (5, 3) 1.0
  (6, 0) 1.0
  (7, 0) 1.0
  (8, 0) 1.0
  (9, 0) 1.0

[[0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1.]
 [0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1.]
 [1. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0.]
 [1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0.]]

 
 

- 특성 스케일링

  • 입력 숫자 특성들의 스케일이 많이 다르면 정확한 예측을 하지 못하는 경우가 발생
  • 입력 숫자 특성들만 스케일링을 수행하는데 타겟의 경우는 범위가 너무 큰 경우 로그 스케일링을 한다.
  • 훈련 데이터와 테스트 데이터로 분할된 경우 일반적으로 훈련 데이터에만 fit 수행하고
    훈련 데이터와 테스트 데이터에 transform 수행
  • min-max scaler와 standard scaler 주로 이용

- 정규화 : MinMaxScaler

  • (데이터 - 최소값) / (최대값 - 최소값)
  • 최대값이 이상치인 경우 영향을 많이 받게 됨

- 표준화: StandardScaler

  • 먼저 평균을 뺀 후 (평균=0) 표준편차로 나눠서 분포의 분산이 1이 되도록 함
  • 범위의 상한과 하한이 없어서 어느정도 값이 만들어지질지 예측하기 어려움
    • 딥러닝에서는 0 ~ 1의 값만을 요구하는 경우가 있어서 사용이 불가
  • 이상치의 영향을 덜 받는다. 로버스트함

- 전처리 작업을 위한 Pipeline

  • 연속해서 작업 수행 - 여러 작업을 하나로 묶어서 수행하는 것
  • 결측치 대체를 하고 스케일링을 수행한다고 했을 때 따로 작업을 해도 되지만 묶어서 한번에 수행 가능
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

#수행하고자 하는 작업을 이름 과 추정기를 튜플로 묶어서 list로 설정해주면 됩니다.
num_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy = 'median')),
    ('std_scaler', StandardScaler())
])

housing_num_tr = num_pipeline.fit_transform(housing_num)
print(housing_num_tr)
[[-0.94135046  1.34743822  0.02756357 ...  0.73260236  0.55628602    -0.8936472 ]
 [ 1.17178212 -1.19243966 -1.72201763 ...  0.53361152  0.72131799   1.292168  ]
 [ 0.26758118 -0.1259716   1.22045984 ... -0.67467519 -0.52440722   -0.52543365]
 ...
 [-1.5707942   1.31001828  1.53856552 ... -0.86201341 -0.86511838    -0.36547546]
 [-1.56080303  1.2492109  -1.1653327  ... -0.18974707  0.01061579    0.16826095]
 [-1.28105026  2.02567448 -0.13148926 ... -0.71232211 -0.79857323    -0.390569  ]]
  • 수행하고자 하는작업을이름과 추정기를 튜플로 묶어서 list로 설정

- ColumnTransformer

  • sklearn 0.20 버전에서 추가된 변환기로 여러 특성에 다른 변환 처리를 할 수 있도록 해주는 클래스
  • Pipeline과 유사하게 생성, 튜플을 만들 때 (변환기 이름, 변환기, 컬럼 이름리스트) 형태로 대입
from sklearn.compose import ColumnTransformer

#숫자 컬럼 이름 리스트
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
#print(num_attribs)

#열 별로 다른 변환기를 적용하기 위한 인스턴스 생성
full_pipeline = ColumnTransformer([
    ("num", num_pipeline, num_attribs),
    ("cat", OneHotEncoder(), cat_attribs)
])

#변환기 적용
hosing_prepared = full_pipeline.fit_transform(housing)
print(hosing_prepared)
[[-1.32783522  1.05254828  0.98214266 ...  0.          1.     0.        ]
 [-1.32284391  1.04318455 -0.60701891 ...  0.          1.     0.        ]
 [-1.33282653  1.03850269  1.85618152 ...  0.          1.     0.        ]
 ...
 [-0.8237132   1.77823747 -0.92485123 ...  0.          0.     0.        ]
 [-0.87362627  1.77823747 -0.84539315 ...  0.          0.     0.        ]
 [-0.83369581  1.75014627 -1.00430931 ...  0.          0.     0.        ]]

 
#피처 전처리

housing_prepared = full_pipeline.fit_transform(housing_features)

 
 

- 모델 적용 및 테스트

- 선형 회귀 모델

#선형 회귀 모델을 이용
from sklearn.linear_model import LinearRegression

#예측 모델 인스턴스 생성
lin_reg = LinearRegression()

#훈련
lin_reg.fit(housing_prepared, housing_labels)

#테스트
some_data = housing_features.iloc[:5]
some_labels = housing_labels.iloc[:5]

#샘플 피처 전처리
some_data_prepared = full_pipeline.transform(some_data)

print("예측한 값:", lin_reg.predict(some_data_prepared))
print("실제 값:", list(some_labels))
예측한 값: [ 88983.14806384 305351.35385026 153334.71183453 184302.55162102
 246840.18988841]
실제 값: [72100.0, 279600.0, 82700.0, 112500.0, 238300.0]

 
 

- 평가 지표

  • 모델을 생성하고 나면 이 모델을 다른 모델과 비교하기 위해서 평가지표 사용
  • 회귀의 평가지표로 사용 - RMSE, RMAE
    • RMSE: 예측한 값과 실제 값의 차이(residual-잔차)를 제곱해서 더한 후 제곱근을 해서 사용
    • RMAE: 잔차에 절대값을 적용한 후 합계를 구한 것
    • 이 값이 적은 쪽이 우수한 모델
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error

#모델을 만들 때 사용한 데이터를 가지고 예측
housing_predictions = lin_reg.predict(housing_prepared)

#잔차 제곱합
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)

#잔차의 절대값 합
lin_mae = mean_absolute_error(housing_labels, housing_predictions)

print("잔차 제곱합:", lin_mse)
print("잔차 제곱합의 제곱근:", lin_rmse)
print("잔차 절대값합:", lin_mae)
잔차 제곱합: 4767980139.451871
잔차 제곱합의 제곱근: 69050.56219504567
잔차 절대값합: 49905.329442715316
  • 제곱합은 단위가 너무 큼. 판정하기 어렵기 때문에 제곱근을 해서 스케일링한다.
  • 집값은 120000 ~ 260000 사이가 많은데, 실제로는 7만에서 5만 차이가 나니까 잔차가 많다.
  • 훈련에 사용한 데이터로 테스트 했는데 훈련에 사용한 데이터도 잘 맞지 않으면 과소 적합이라고 한다.
    • 데이터를 더 수집하기
    • 다른 모델(알고리즘)을 사용해보기
    • 하이퍼파라미터를 조정해보기
  • 훈련 데이터에는 잘 맞지만, 테스트 데이터나 새로운 데이터에 잘 맞지 않으면 과대 적합이라고 한다. 

 

- 새로운 모델 적용 

- Decision Tree

  • 트리 모델은 일반적인 형태로 검증하지 않고 데이터를 K개의 그룹으로 나눈 후 (k-1)개의 데이터 그룹을 사용해서 모델을 생성하고 나머지 그룹을 이용해서 테스트 수행하고 이 작업을 k번 반복해서 그 때의 평균을 리턴한다.
  • sklearn에서는 점수가 높은 쪽이 좋다고 생각하기 때문에 평가지표를 설정할 때 (-)를 곱해서 사용한다.
from sklearn.tree import DecisionTreeRegressor

tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(housing_prepared, housing_labels)

housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
print(tree_rmse)
0.0
  • 얜 안되겠네

- K-fold cross-validation (K 겹 교차 검증)

 

from sklearn.model_selection import cross_val_score

#10개의 K를 설정
scores = cross_val_score(tree_reg, housing_prepared, housing_labels, 
                        scoring="neg_mean_squared_error", cv=10)
tree_rmse_scores = np.sqrt(-scores)
print(tree_rmse_scores)
[71177.6601991  69770.07865373 64770.5639395  68536.60203993
 67057.08155801 68847.12456973 70977.38255647 69208.86346929
 67187.87131535 73280.38732407]
  • 그닥 별로. 오히려 나쁜듯

 

- 그리드 탐색

  • 가장 좋은 하이퍼 파라미터를 찾는 것
    • 파라미터: 함수 또는 메소드가 수행할 때 결정하는 데이터
    • 하이퍼 파라미터: 사용자가 직접 설정하는 데이터
      대부분의 하이퍼파라미터는 가장 알맞은 기본값을 소유하고 있다.
  • GridSearchCV를 이용하면 여러종류의 하이퍼 파라미터를 설정해서 훈련한 후 가장 좋은 하이퍼 파라미터를 추천
  • 파라미터를 설정할 때는 list의 dict로 설정하게 되는데 dict에 하이퍼 파라미터 이름과 값의 list를 설정하는데 하나의 dict 내에 있는 파라미터는 모든 조합을 가지고 수행하고 각 dict는 별개로 수행
  • 시간이 오래 걸릴 가능성이 높음
from sklearn.ensemble import RandomForestRegressor

forest_reg = RandomForestRegressor(random_state=42)

#파라미터 조합을 생성: 12 + 6 : 18번 수행
param_grid = [{'n_estimators':[3, 10, 30], 'max_features': [2, 4, 6, 8]}, 
              {'bootstrap':[False], 'n_estimators':[3, 10], 'max_features':[2, 3, 4]}]

from sklearn.model_selection import GridSearchCV
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
                          scoring='neg_mean_squared_error', 
                          return_train_score=True, n_jobs=2)

grid_search.fit(housing_prepared, housing_labels)
print(grid_search.best_params_)
#내장 파라미터는 뒤에 _ 붙여야 함
{'max_features': 8, 'n_estimators': 30}

 
# 평가 점수 확인

cvres = grid_search.cv_results_
for mean_score, params in zip(cvres['mean_test_score'], cvres['params']):
    print(np.sqrt(-mean_score), params)
63827.76261554265 {'max_features': 2, 'n_estimators': 3}
55056.82212305312 {'max_features': 2, 'n_estimators': 10}
52673.5498401615 {'max_features': 2, 'n_estimators': 30}
60299.48845134689 {'max_features': 4, 'n_estimators': 3}
53106.41271952157 {'max_features': 4, 'n_estimators': 10}
50370.55528306362 {'max_features': 4, 'n_estimators': 30}
58363.22748437211 {'max_features': 6, 'n_estimators': 3}
52446.057900340325 {'max_features': 6, 'n_estimators': 10}
50177.91173851986 {'max_features': 6, 'n_estimators': 30}
58058.12321723554 {'max_features': 8, 'n_estimators': 3}
51849.42681935635 {'max_features': 8, 'n_estimators': 10}
49941.11534754462 {'max_features': 8, 'n_estimators': 30}
62820.05402812565 {'bootstrap': False, 'max_features': 2, 'n_estimators': 3}
53846.18083156347 {'bootstrap': False, 'max_features': 2, 'n_estimators': 10}
59026.17902108823 {'bootstrap': False, 'max_features': 3, 'n_estimators': 3}
52996.55803561763 {'bootstrap': False, 'max_features': 3, 'n_estimators': 10}
58842.47703118809 {'bootstrap': False, 'max_features': 4, 'n_estimators': 3}
51891.75100173946 {'bootstrap': False, 'max_features': 4, 'n_estimators': 10}

 
#최적의 모델 가져오기

print(grid_search.best_estimator_)
RandomForestRegressor(max_features=8, n_estimators=30, random_state=42)

 

- 랜덤 서치

  • 그리드 서치는 파라미터 값을 직접 입력해서 선택하도록 하는데 파라미터가 적을 때는 유용하지만 많을 때는 어려움
  • RandomizedSearchCV는 파라미터의 상한과 하한을 
forest_reg = RandomForestRegressor(random_state=42)

from scipy.stats import randint
#파라미터 조합을 생성: 12 + 6 : 18번 수행
param_distribs = {'n_estimators':randint(low=1, high=200), 
                  'max_features': randint(low=1, high=8)}

from sklearn.model_selection import RandomizedSearchCV
random_search = RandomizedSearchCV(forest_reg, param_distributions = param_distribs, cv=5,
                          scoring='neg_mean_squared_error', n_jobs=-1,
                                  n_iter=10)

grid_search.fit(housing_prepared, housing_labels)

 
- 앙상블

  • 여러 개의 모델을 사용하는 방식
  • DecisionTree는 하나의 트리를 이용하지만 RandomForest는 여러개의 DecisionTree를 가지고 예측

 
- 테스트를 할 때는 모델을 만들 때 사용한 데이터가 아닌 테스트 데이터로 수행하기도 함

  • Overfitting(과대적합): 훈련 데이터에 잘 맞지만 테스트 데이터에 잘 맞지 않는 상황
  • Underfitting(과소적합): 훈련데이터에도 잘 맞지 않는 상황