no image
[Python] 연관분석 실습 _ 영화 콘텐츠 기반 필터링 추천 시스템
데이터 https://www.kaggle.com/tmdb/tmdb-movie-metadata 1. 데이터 읽어오기 movies =pd.read_csv('./python_machine_learning-main/data/tmdb_5000_movies.csv') print(movies.shape) movies.head() # 데이터 생성 movies_df = movies[['id','title', 'genres', 'vote_average', 'vote_count', 'popularity', 'keywords', 'overview']] # pd.set_option('max_colwidth', 100) movies_df.head(1) genres와 keywords는 하나의 문자열이 아니라 list다. csv 파..
2024.03.15
no image
[Python] Pandas 실습 _ 카카오 검색 API 데이터 가져오기
- 카카오 검색 API developers.kakao.com - 애플리케이션 생성 후 REST API 키 복사 a89507c93f15074167c0700239d4b1d0 블로그 검색 메서드URL인증 방식 GET https://dapi.kakao.com/v2/search/blog REST API 키 헤더 Authorization Authorization: KakaoAK ${REST_API_KEY} 인증 방식, 서비스 앱에서 REST API 키로 인증 요청 필수 파라미터 query String 검색을 원하는 질의어 특정 블로그 글만 검색하고 싶은 경우, 블로그 url과 검색어를 공백(' ') 구분자로 넣을 수 있음 필수 sort String 결과 문서 정렬 방식, accuracy(정확도순) 또는 recenc..
2024.03.15
no image
[Python] 연관분석 실습 _ 손흥민
설치 !pip install apyori 1. 데이터 읽어오기 df = pd.read_csv("./python_machine_learning-main/data/tweet_temp.csv") df.head() #한글 정제 함수 import re # 한글만 추출 def text_cleaning(text): hangul = re.compile('[^ ㄱ-ㅣ가-힣]+') # 한글의 정규표현식 result = hangul.sub('', text) return result #적용 # ‘tweet_text’ 피처에 적용 df['ko_text'] = df['tweet_text'].apply(lambda x: text_cleaning(x)) df.head() 2. 불용어 처리 - https://www.ranks.nl/s..
2024.03.15
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

데이터

https://www.kaggle.com/tmdb/tmdb-movie-metadata

 

1. 데이터 읽어오기

movies =pd.read_csv('./python_machine_learning-main/data/tmdb_5000_movies.csv')
print(movies.shape)
movies.head()


 

 

# 데이터 생성

movies_df = movies[['id','title', 'genres', 'vote_average', 'vote_count',
                 'popularity', 'keywords', 'overview']]

# pd.set_option('max_colwidth', 100)
movies_df.head(1)
  • genres와 keywords는 하나의 문자열이 아니라 list다.
    • csv 파일을 만드려고 list를 문자열로 만들기 위해 " "로 감싸는 형태로 만들었다. 
  • 파이썬이나 자바스크립트는 json 포맷의 문자열로 데이터를 표현한다. 
    + 문자열로 만들어진 데이터를 원래의 자료형으로 되돌리는 API가 제공된다. 
    • python에서는 ast 모듈의 literal_eval 함수 이용 (직접 파싱할 필요 X)
    • "[apple, pear, watermelon]"  ->  literal_eval(데이터)  ->  [apple, pear, watermelon]
print(movies_df[['genres', 'keywords']][:1])
                                              genres  \
0  [{"id": 28, "name": "Action"}, {"id": 12, "nam...   

                                            keywords  
0  [{"id": 1463, "name": "culture clash"}, {"id":...  

 

 

#데이터 변환

from ast import literal_eval

#문자열을 파이썬 객체로 변환
movies_df['genres'] = movies_df['genres'].apply(literal_eval)
movies_df['keywords'] = movies_df['keywords'].apply(literal_eval)

#디셔너리에서 데이터만 추출
movies_df['genres'] = movies_df['genres'].apply(lambda x : [ y['name'] for y in x])
movies_df['keywords'] = movies_df['keywords'].apply(lambda x : [ y['name'] for y in x])

#데이터 확인
movies_df[['genres', 'keywords']][:1]

 

 

 

 

 

2. 장르 유사도 측정

  • 문자열을 피처로 만드는 방법은 CountVectorizer,  TfidfVectorizer 
  • 장르 리스트 안에서 같은 단어가 나올 수 없으니 등장 횟수만을 기반으로 하는 CountVectorizer 사용

 

 

#피처 벡터화

from sklearn.feature_extraction.text import CountVectorizer

# CountVectorizer를 적용하기 위해 공백문자로 word 단위가 구분되는 문자열로 변환. 
movies_df['genres_literal'] = movies_df['genres'].apply(lambda x : (' ').join(x))
print(movies_df['genres_literal'])
0       [ { " i d " :   2 8 ,   " n a m e " :   " A c ...
1       [ { " i d " :   1 2 ,   " n a m e " :   " A d ...
2       [ { " i d " :   2 8 ,   " n a m e " :   " A c ...
3       [ { " i d " :   2 8 ,   " n a m e " :   " A c ...
4       [ { " i d " :   2 8 ,   " n a m e " :   " A c ...

...
count_vect = CountVectorizer(min_df=0.0, ngram_range=(1,2))
genre_mat = count_vect.fit_transform(movies_df['genres_literal'])
print(genre_mat.shape)
(4803, 276)

 

 

# 장르의 코사인 유사도 측정 

from sklearn.metrics.pairwise import cosine_similarity

genre_sim = cosine_similarity(genre_mat, genre_mat)
print(genre_sim.shape)
print(genre_sim[:2])
(4803, 4803)

[[1.         0.59628479 0.4472136  ... 0.         0.         0.        ]
 [0.59628479 1.         0.4        ... 0.         0.         0.        ]]

 

 

# 유사도 측정

genre_sim_sorted_ind = genre_sim.argsort()[:, ::-1]  #전부
print(genre_sim_sorted_ind[:1])
[[   0 3494  813 ... 3038 3037 2401]]

 

 

# 영화 정보 데이터프레임과 영화 정보 인덱스와 영화 제목과 추천할 영화 개수를 입력하면

  영화 제목과 평점을 리턴해주는 함수

def find_sim_movie(df, sorted_ind, title_name, top_n=10):
    
    # 인자로 입력된 movies_df DataFrame에서 'title' 컬럼이 입력된 title_name 값인 DataFrame추출
    title_movie = df[df['title'] == title_name]
    
    # title_named을 가진 DataFrame의 index 객체를 ndarray로 반환하고 
    # sorted_ind 인자로 입력된 genre_sim_sorted_ind 객체에서 유사도 순으로 top_n 개의 index 추출
    title_index = title_movie.index.values
    similar_indexes = sorted_ind[title_index, :(top_n)]   #자기 영화 제외하려면 1:(top_m)
    
    # 추출된 top_n index들 출력. top_n index는 2차원 데이터  
    #dataframe에서 index로 사용하기 위해서 1차원 array로 변경
    print(similar_indexes)
    
    similar_indexes = similar_indexes.reshape(-1)
    
    return df.iloc[similar_indexes]
similar_movies = find_sim_movie(movies_df, genre_sim_sorted_ind, 'The Dark Knight Rises',10)
similar_movies[['title', 'vote_average', 'vote_count']].sort_values('vote_average', ascending=False)

 

 

 

 

 

3. 가중 평점

  • 평점을 많이 매기지 않은 데이터가 평점이 높게 나오는 경향이 있을 수 있다.
  • 데이터의 개수가 작으면 극단치의 영향을 많이 받기 때문이다.
  • 데이터 개수에 따른 보정을 해줄 필요가 있다. 
  • IMDB에서 권장
가중 평점(Weighted Rating) =(v/(v+m)) * R+ (m/(v+m)) * C
  • v: 개별 영화에 평점을 투표한 횟수
  • m: 평점을 부여하기 위한 최소 투표 횟수
  • R: 개별 영화에 평균 평점
  • C: 전체 영화에 대한 평균 평점

 

# 가중 평점 부여

C = movies_df['vote_average'].mean()
m = movies_df['vote_count'].quantile(0.6)
print('C:',round(C,3), 'm:',round(m,3))
C: 6.092    m: 370.2
def weighted_vote_average(record):
    v = record['vote_count']
    R = record['vote_average']
    
    return ( (v/(v+m)) * R ) + ( (m/(m+v)) * C )   

movies_df['weighted_vote'] = movies_df.apply(weighted_vote_average, axis=1)
movies_df['weighted_vote'] = movies_df.apply(weighted_vote_average, axis=1)
movies_df[['title', 'vote_average', 'weighted_vote', 'vote_count']][:10]

 

# 가중치 조정

def find_sim_movie(df, sorted_ind, title_name, top_n=10):
    title_movie = df[df['title'] == title_name]
    title_index = title_movie.index.values
    
    # top_n의 2배에 해당하는 쟝르 유사성이 높은 index 추출 
    similar_indexes = sorted_ind[title_index, :(top_n*2)]
    similar_indexes = similar_indexes.reshape(-1)
# 기준 영화 index는 제외
    similar_indexes = similar_indexes[similar_indexes != title_index]
    
    # top_n의 2배에 해당하는 후보군에서 weighted_vote 높은 순으로 top_n 만큼 추출 
    return df.iloc[similar_indexes].sort_values('weighted_vote', ascending=False)[:top_n]

similar_movies = find_sim_movie(movies_df, genre_sim_sorted_ind, 'The Godfather',10)
similar_movies[['title', 'vote_average', 'weighted_vote']]

 

 

 

 

 

 

지금은 장르기반으로만 추천했지만, 가장 최근에 끝난 것에도 가중치 부여할 수 있다.

  • ex. 범죄도시 4 개봉한다고 하면, 범죄도시 2-3를 보여주는 것처럼!

- 카카오 검색 API

developers.kakao.com

 

- 애플리케이션 생성 후 REST API 키 복사

a89507c93f15074167c0700239d4b1d0

 

 

블로그 검색    메서드URL인증 방식

GET https://dapi.kakao.com/v2/search/blog REST API 키

 

 

헤더

Authorization Authorization: KakaoAK ${REST_API_KEY}
인증 방식, 서비스 앱에서 REST API 키로 인증 요청
필수

 

 

파라미터

query String 검색을 원하는 질의어
특정 블로그 글만 검색하고 싶은 경우, 블로그 url과 검색어를 공백(' ') 구분자로 넣을 수 있음
필수
sort String 결과 문서 정렬 방식, accuracy(정확도순) 또는 recency(최신순), 기본 값 accuracy 선택
page Integer 결과 페이지 번호, 1~50 사이의 값, 기본 값 1 선택
size Integer 한 페이지에 보여질 문서 수, 1~50 사이의 값, 기본 값 10 선택

query = "막대그래프"

 

import requests
import json

query = "막대그래프"
url = ("https://dapi.kakao.com/v2/search/blog?query=" + query)
headers = {'Authorization': 'KakaoAK {}'.format('a89507c93f15074167c0700239d4b1d0')}
data = requests.post(url, headers=headers)
print(data.text)

 

#JSON 문자열을 Python의 자료구조로 변경
result = json.loads(data.text)
print(type(result))
<class 'dict'>

 

 

documents = result['documents']

for doc in documents:
    print(doc['blogname'], doc['title'], doc['datetime'], )
야채's Data Tableau - <b>막대</b> <b>그래프</b> 2024-02-01T14:22:10.000+09:00
...

 

 

- MySQL 연동

!pip install pyMySQL

 

 

- 데이터베이스 접속 확인

import pymysql

con = pymysql.connect(host='데이터베이스 위치', 
                        port = 포트번호, 
                        user = '아이디', 
                        password = '비밀번호', 
                        db = '데이터베이스 이름', 
                        charset = '인코딩 방식')
con.close()

 

 

- SELECT를 제외한 구문 실행

#연결 객체를 가지고 cursor()를 호출해서 커서 객체 생성
cursor = con.cursor()
#sql 실행
cursor.execute("SQL 구문(%s를 대입할 데이터 개수만큼 생성)", (%s에 대입할 데이터를 나열))
#commint
con.commit()
  • SELECT문이 아니면 항상 커밋!

 

- SELECT 구문은 execute , 커서를 이용해서 fetch_one이나 fetchall()을 호출해서 튜플을 가져와 읽기

#SQL을 실행하기 위한 객체 생성
cursor = con.cursor()

#테이블 생성 구문 실행
cursor.execute("create table phamacy(placename varchar(30), addressname varchar(200)")

 

# 파싱한 데이터 순회

for doc in documents:
    cursor.execute("insert into blog(blogname, title) values(%s, %s)",
                   (doc["blogname"], doc["title"]))
con.commit()

 

 

설치

!pip install apyori

 

1. 데이터 읽어오기

df = pd.read_csv("./python_machine_learning-main/data/tweet_temp.csv")
df.head()

 

#한글 정제 함수

import re

# 한글만 추출
def text_cleaning(text):
    hangul = re.compile('[^ ㄱ-ㅣ가-힣]+') # 한글의 정규표현식
    result = hangul.sub('', text)
    return result

 

#적용

# ‘tweet_text’ 피처에 적용
df['ko_text'] = df['tweet_text'].apply(lambda x: text_cleaning(x))
df.head()

 

 

 

2. 불용어 처리

- https://www.ranks.nl/stopwords/korean

from collections import Counter

# 한국어 약식 불용어사전 예시 파일 - https://www.ranks.nl/stopwords/korean
korean_stopwords_path = "./python_machine_learning-main/korean_stopwords.txt"
with open(korean_stopwords_path, encoding='utf8') as f:
    stopwords = f.readlines()
#앞뒤 공백 제거
stopwords = [x.strip() for x in stopwords]
print(stopwords)
['아', '휴', '아이구', '아이쿠', '아이고', '어', '나', '우리', '저희', '따라', '의해', '을', '를', '에', '의', '가', '으로', '로', '에게', '뿐이다', '의거하여', '근거하여', '입각하여', '기준으로', '예하면', '예를 들면', '예를 들자면', '저', '소인', '소생', '저희', '지말고', '하지마', '하지마라', '다른', '물론', '또한', '그리고',  ...
# 문자열 대입받아서 명사만 추출, 1글자는 제거하고 불용어도 제거해서 리턴

from konlpy.tag import Okt

def get_nouns(x):
    nouns_tagger = Okt()
    nouns = nouns_tagger.nouns(x)
    
    # 한글자 키워드를 제거
    nouns = [noun for noun in nouns if len(noun) > 1]
    
    # 불용어를 제거
    nouns = [noun for noun in nouns if noun not in stopwords]
    
    return nouns

.

# ‘ko_text’ 피처에 적용
df['nouns'] = df['ko_text'].apply(lambda x: get_nouns(x))
print(df.shape)
df.head()
(525, 4)

  • 연관 규칙 분석을 할 떄는 데이터가 리스트 형태로 만들어지면 됨
  • 상품 거래 연관 규칙 분석을 할 때는 구매한 상품 목록을 list로 만들면 됨    ex. ['사과', '배', '한라봉']

 

 

 

3. 연관 규칙 분석

from apyori import apriori

# 장바구니 형태의 데이터(트랜잭션 데이터)를 생성
transactions = [
    ['손흥민', '시소코'],
    ['손흥민', '케인'],
    ['손흥민', '케인', '포체티노']
]

# 연관 분석을 수행
results = list(apriori(transactions))
for result in results:
    print(result)
RelationRecord(items=frozenset({'손흥민'}), support=1.0, ordered_statistics=[OrderedStatistic(items_base=frozenset(), items_add=frozenset({'손흥민'}), confidence=1.0, lift=1.0)])
RelationRecord(items=frozenset({'시소코'}), support=0.3333333333333333, ordered_statistics=[OrderedStatistic(items_base=frozenset(), items_add=frozenset({'시소코'}), confidence=0.3333333333333333, lift=1.0)])
...
RelationRecord(items=frozenset({'포체티노', '손흥민'}), support=0.3333333333333333, ordered_statistics=[OrderedStatistic(items_base=frozenset(), items_add=frozenset({'포체티노', '손흥민'}), confidence=0.3333333333333333, lift=1.0), OrderedStatistic(items_base=frozenset({'손흥민'}), items_add=frozenset({'포체티노'}), confidence=0.3333333333333333, lift=1.0), OrderedStatistic(items_base=frozenset({'포체티노'}), items_add=frozenset({'손흥민'}), confidence=1.0, lift=1.0)])
...
# 트랜잭션 데이터를 추출합니다.
transactions = df['nouns'].tolist()
transactions = [transaction for transaction in transactions if transaction] # 공백 문자열 방지
print(transactions)
[['트릴', '리온', '축구', '국가대표', '손흥민', '선수', '샴푸', '모델', '기용', '출처', '한국', '경제', '네이버', '뉴스'], ['손흥민', '말씀'], ['손흥민'], ['경남', '도민', '일보', '프로축구', '연맹', '경기장', '선거운동', '손흥민', '영국', '관중', '인종차별', '행위', '보고', '축구장', '선거운동', '규정', '위반', '이야기'], ['선택', '손흥민', '축구'], ['토트넘', '골수팬', '승부사', '제일', '선수', '손흥민', '입다'], ['계정', '지기', '실수', '삭제', '다시', '하리보', '손흥민', '홍보', '모델', '발탁', '기념', '해당', '추첨', '통해', '하리보', '골드바', '기간'], ['안녕하십니까', '손흥민', '트위터', '매우', '오늘', '경기도', '관심'], 
...

 

# 연관분석

# 연관 분석을 수행
results = list(apriori(transactions,
                       min_support=0.1,
                       min_confidence=0.2,
                       min_lift=5,
                       max_length=2))
for result in results:
    print(result)
[RelationRecord(items=frozenset({'국가대표팀', '게임'}), support=0.14285714285714285, ordered_statistics=[OrderedStatistic(items_base=frozenset({'게임'}), items_add=frozenset({'국가대표팀'}), confidence=1.0, lift=7.0), OrderedStatistic(items_base=frozenset({'국가대표팀'}), items_add=frozenset({'게임'}), confidence=1.0, lift=7.0)]), 

RelationRecord(items=frozenset({'게임', '금메달'}), support=0.14285714285714285, ordered_statistics=[OrderedStatistic(items_base=frozenset({'게임'}), items_add=frozenset({'금메달'}), confidence=1.0, lift=7.0), OrderedStatistic(items_base=frozenset({'금메달'}), items_add=frozenset({'게임'}), confidence=1.0, lift=7.0)]), 

RelationRecord(items=frozenset({'모습', '게임'}), support=0.14285714285714285, ordered_statistics=[OrderedStatistic(items_base=frozenset({'게임'}), items_add=frozenset({'모습'}), confidence=1.0, lift=7.0), OrderedStatistic(items_base=frozenset({'모습'}), items_add=frozenset({'게임'}), confidence=1.0, lift=7.0)]), 

RelationRecord(items=frozenset({'아시아', '게임'}), support=0.14285714285714285, ordered_statistics=[OrderedStatistic(items_base=frozenset({'게임'}), items_add=frozenset({'아시아'}), confidence=1.0, lift=7.0), OrderedStatistic(items_base=frozenset({'아시아'}), items_add=frozenset({'게임'}), confidence=1.0, lift=7.0)]), 

...
  • 하나의 항목은 제거

 

# 데이터 프레임 형태로 정리

# 데이터 프레임 형태로 정리
columns = ['source', 'target', 'support']
network_df = pd.DataFrame(columns=columns)

# 규칙의 조건절을 source, 결과절을 target, 지지도를 support 라는 데이터 프레임의 피처로 변환
for result in results:
        items = [x for x in result.items]
        row = [items[0], items[1], result.support]
        series = pd.Series(row, index=network_df.columns)
        network_df.loc[len(network_df)] = series

network_df.head()

 

 

 

 

4. 키워드 빈도 추출

from konlpy.tag import Okt
from collections import Counter

# 명사 키워드를 추출
nouns_tagger = Okt()
nouns = nouns_tagger.nouns(tweet_corpus)
count = Counter(nouns)

# 한글자 키워드를 제거
remove_char_counter = Counter({x : count[x] for x in count if len(x) > 1})
print(remove_char_counter)
Counter({'손흥민': 560, '하리보': 245, '축구': 140, '선수': 140, '조현우': 140, '황의조': 140, '금메달': 140, '모델': 105, '한국': 105, '신제품': 105, '선거운동': 70, '보고': 70, '계정': 70, '지기': 70, '실수': 70, '삭제': 70, '다시': 70, '홍보': 70, '발탁': 70, '기념': 70, '해당': 70, '추첨': 70, '통해': 70, '골드바': 70, '기간': 70, '출국': 70, '국가대표팀': 70, '아시아': 70, '게임': 70, '모습': 70, '의조': 70, '진짜': 70, '트릴': 35, '리온': 35, '국가대표': 35, '샴푸': 35, '기용': 35, '출처': 35, '경제': 35, '네이버': 35, '뉴스': 35, '말씀': 35, '경남': 35, '도민': 35, '일보': 35, '프로축구': 35, '연맹': 35, '경기장': 35, '영국': 35, '관중': 35, '인종차별': 35, '행위': 35, '축구장': 35, '규정': 35, '위반': 35, '이야기': 35, '선택': 35, '토트넘': 35, '골수팬': 35, '승부사': 35, '제일': 35, '입다': 35, '안녕하십니까': 35, '트위터': 35, '매우': 35, '오늘': 35, '경기도': 35, '관심': 35, '긴급': 35, '속보': 35, '개시': 35, '샤인': 35, '가장': 35, '먼저': 35, '팔로우': 35, '알림': 35, '설정': 35, '두기': 35})

 

# 단어와 등장횟수를 가지고 DataFrame 생성

# 키워드와 키워드 빈도 점수를 ‘node’, ‘nodesize’ 라는 데이터 프레임의 피처로 생성
node_df = pd.DataFrame(remove_char_counter.items(), columns=['node', 'nodesize'])
node_df = node_df[node_df['nodesize'] >= 50] # 시각화의 편의를 위해 ‘nodesize’ 50 이하는 제거합니다.
node_df.head()

 

# networkx

import networkx as nx
plt.figure(figsize=(12,12))

# networkx 그래프 객체
G = nx.Graph()

# node_df의 키워드 빈도수를 데이터로 하여, 네트워크 그래프의 ‘노드’ 역할을 하는 원을 생성
for index, row in node_df.iterrows():
    G.add_node(row['node'], nodesize=row['nodesize'])
    
# network_df의 연관 분석 데이터를 기반으로, 네트워크 그래프의 ‘관계’ 역할을 하는 선을 생성
for index, row in network_df.iterrows():
    G.add_weighted_edges_from([(row['source'], row['target'], row['support'])])
    
# 그래프 디자인과 관련된 파라미터를 설정
pos = nx.spring_layout(G, k=0.6, iterations=50)
sizes = [G.nodes[node]['nodesize']*25 for node in G]
nx.draw(G, pos=pos, node_size=sizes, node_color='yellow')

#레이블 출력 (한글폰트)
nx.draw_networkx_labels(G, pos=pos, font_family='Malgun Gothic', font_size=25)

# 그래프 출력
ax = plt.gca()
plt.show()



 

 

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