본문 바로가기

STUDY

파이썬 스터디 ver3. 10주차

2022.08.22~2022.08.26

구름 X 전주 ict 이노베이션 스퀘어의 온라인 코딩교육 내용을 정리하였습니다.

이번에도 사실 저 기간은 아니지만 9주차(저번 글)에 이미 저번 프로젝트 관련 내용을 정리했었던 바, 되는대로 하였습니다

 

천방지축 어리둥절 빙글빙글 돌아가는, 부트캠프 들으면서 프로젝트 진행하면서 학교 다니면서 빅분기 공부하는 메타몽의 파이썬 공부 정리입니다. 사실 저 중 뭐 하나 제대로 한 것이 없어 이렇게 정리하면.. 양심이 없는 거 아닌가.. 싶긴 하지만..하긴 했으니까..^^

조금이나마 발화되지 않게 정리하는 데에 의의가 있죠. 있겠죠????


 

원핫 인코딩: 범주형 표현을 0이나 1을 가진 특성으로 바꾸는 것.

from sklearn.preprocessing import OneHotEncoder
import numpy as np; from sklearn.preprocessing import LabelEncoder

items = ['TV','냉장고','전자렌지','컴퓨터','선풍기','선풍기','믹서','믹서']

#숫자로 먼저 바꿔줌
le = LabelEncoder()
le.fit(items)
labels = le.transform(items)
labels = labels.reshape(-1, 1) #2차원으로 바꿈

ohe = OneHotEncoder() #원핫인코딩 만듦
ohe.fit(labels) #적용해주기
labels_ohe = ohe.transform(labels)

print(labels_ohe.shape) #(8, 6)
display(labels_ohe.toarray()) #이렇게 생김

items가 TV, 냉장고, 전자렌지, 컴퓨터, 선풍기, 선풍기, 믹서, 믹서였다. 이를 토대로 추측컨대 첫번째 컬럼이 TV, 두번째 컬럼이 냉장고, 세번째 컬럼이 믹서, 네번째 컬럼이 선풍기, 다섯번째 컬럼이 전자렌지, 여섯번째 컬럼이 컴퓨터인 것 같다.

import pandas as pd #pandas 원핫인코딩 시도할 예정

df = pd.DataFrame({'item':items}) #items리스트를 item이라는 컬럼을 가진 df로 만듦
pd.get_dummies(df) #get_dummies()로 나열(?)해주기

예상하던 순서가 맞았다! 그리고 확실히 ndarray로 볼때마다 pandas df로 보는 게 가독성이 훨씬 좋은 것 같다.

- 훈련세트와 테스트세트에 범주형 값이 같은 반식으로 표현되어야 함: 원본 df에 원핫인코딩 적용 후 train-test-split 수행

- 범주형이 처음부터 숫자로 표현되어 있는 경우, 연속형으로 다룰 수도 있고 범주형으로 다룰 수도 있다: 선택가능 (다만 숫자(int)로 표시된 범주형변수는 원핫인코딩이 적용되지 않는다. 가변수(dummies)를 만들고 싶은 경우 str로 타입을 변경해주어야 한다)

 

피처 스케일링: 서로 다른 변수 값 범위를 일정 수준으로 맞추는 것. svm, 신경망 등에 적용해주기 위함

- 표준화Standardization: 데이터 특성을 평균 0, 분산 1인 가우시안 정규분포로 변환

- 정규화Normalization: 서로 다른 특성들의 크기를 통일해주기 위해 변환

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

cancer = load_breast_cancer() #유방암 데이터셋으로 표준화/정규화 시행 예정
X = cancer.data; y = cancer.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

from sklearn.svm import SVC
model = SVC(C=100).fit(X_train, y_train) #서포트벡터머신~ 100개로~
print(model.score(X_test, y_test)) #표준화 전 #0.9440
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler() #표준화 스케일러 만듦
scaler.fit(X_train) #train데이터 기준으로 정보 설정!

X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test) #데이터 변형

model = SVC(C=100).fit(X_train_scaled, y_train) #다시 SVM 학습
print(model.score(X_test_scaled, y_test)) #표준화 이후 0.9580


from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler() #minmax(정규화 일종) 스케일러 만듦
scaler.fit(X_train) #train데이터 기준 정보 설정

X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

model = SVC(C=100).fit(X_train_scaled, y_train) #다시 SVM 학습
print(model.score(X_test_scaled, y_test)) #정규화 이후 0.9650

 

특성공학feature engineering: 기존 특성(피처, 컬럼)에서 새 특성을 만드는 것. 

- 범주형의 경우 원-핫 인코딩 적용, 시계열의 경우 새로운 시간 특성 추가, 결측치는 어떻게 처리해 줄 지, 구간을 분할할 것인지, 상호작용이나 다항식 특성을 넣어줄지 등등.

 

특성 선택: 특성이 너무 많아도 모델 복잡도와 과적합 가능성이 높아지므로 필요한 특성만 택하는 게 좋당

- 일반량 통계: 특성과 타겟 사이의 통계적 관계를 계산하고 관련되어 있다 판단되는 것만 선택.

- 모델기반 선택: 트리모델의 feature_importances_처럼, 특성 중요도를 평가해 중요한 것만 선택.

- 반복적 특성 선택: 각 다른 모델에서 특성 수를 만들어 조건에 도달하면 특성을 추가/제거.

 

비지도학습: 데이터의 label이 주어지지 않은 상태에서 학습시키는 것. 비슷한 특성을 가진 것끼리 묶는 군집화clustering와 차원축소dimension reduction으로 나뉨.

- 지도학습의 전처리나 데이터에 대한 더 높은 이해를 위해 사용됨

- 레이블이 없어 학습이 잘 되었는지에 대한 파악이 어려움

- 차원축소는 차원의 저주(데이터가 고차원일수록 데이터 밀도가 희소해져 과적합이 일어남)를 막아줌

- 주성분분석PCA: 여러 변수 간의 상관관계를 이용해 principal component 추출, 가장 높은 분산을 갖는 축으로 차원을 축소하는 것. 고차원 데이터의 시각화에서 주로 이용됨.

- 선형판별분석LDA: pca와 유사함. 데이터를 저차원으로 투영해 축소하는 것. 클래스 간 분산과 클래스 내 분산의 비율을 최대화하며 차원을 축소

- 특이값분해SVD: m*n크기의 행렬 A를 분해하는 것 (pca는 정방행렬만 고유벡터 분해가 가능하나 svd는 정방행렬이든 아니든 고유벡터 분해 가능)

- PCA는 밀집행렬dense matrix에 대한 변환만 가능하나 SVD는 희소행렬sparse matrix에 대한 변환도 가능. 둘 다 컴퓨터 비전 영역에서의 이미지 압축을 통한 패턴인식, 신호처리에 사용.

t-SNE: t-distributed Stochastic Neighbor Embedding. 데이터 포인트 사이의 거리를 잘 보존하는 2차원 표현을 찾는 것. 가까운 것에 더 비중을 두기 때문에 가까운 데이터는 더 가깝게, 먼 데이터는 더 멀어지게 만듦. EDA에서 유용

- 매니폴드Manifold 학습: 매니폴드란 다양체, 국소적으로 유클리드 공간과 닮은 위상공간. 고차원 데이터셋이 저차원 매니폴드에 가깝게 놓여있다고 가정하는 것. 

#digit 데이터셋으로 t-SNE 실습
from sklearn.datasets import load_digits

digits = load_digits()

fig, axes = plt.subplots(2, 5, figsize=(10, 5))
ax = axes.ravel() #2*5를 납짝하게! 1차원으로
for i in range(10): #digit 이미지들을 ax순서대로 그림
  ax[i].imshow(digits.images[i])

X = digits.data; y = digits.target
pca = PCA(n_components=2).fit(X) #2차원으로 pca 차원축소
X_pca = pca.transform(X) #데이터 변환

colors = ["#476A2A", "#7851B8", "#BD3430", "#4A2D4E", "#875525",
          "#A83683", "#4E655E", "#853541", "#3A3120","#535D8E"]
          #각 레이블을 구분해 줄 색깔 설정

plt.figure(figsize=(10, 10)) #도화지 크기, x범위, y범위 설정
plt.xlim(X_pca[:, 0].min(), X_pca[:, 0].max() + 1)
plt.ylim(X_pca[:, 1].min(), X_pca[:, 1].max() + 1)

for i in range(len(X)):
  plt.text(X_pca[i, 0], X_pca[i, 1], str(y[i]), color=colors[y[i]], 
           fontdict={'weight':'bold', 'size':9} ) #plt.text? 써보는듯
plt.show() #scatter기본이 점인데 반해 text는 말그대로 숫자인듯하네

#위에건 PCA였으니 이제 t-SNE 사용해보자!
from sklearn.manifold import TSNE

tsne = TSNE(n_components=2, random_state=0)
X_tsne = tsne.fit_transform(X) #똑같이 n_components 2로 만듦
print(X_tsne.shape) #잘 바뀌었는지 확인 #(1797, 2)

colors = ["#476A2A", "#7851B8", "#BD3430", "#4A2D4E", "#875525",
          "#A83683", "#4E655E", "#853541", "#3A3120","#535D8E"]

plt.figure(figsize=(10, 10))
plt.xlim(X_tsne[:, 0].min(), X_tsne[:, 0].max() + 1)
plt.ylim(X_tsne[:, 1].min(), X_tsne[:, 1].max() + 1)

for i in range(len(X)):
  plt.text(X_tsne[i, 0], X_tsne[i, 1], str(y[i]), color=colors[y[i]], 
           fontdict={'weight':'bold', 'size':9}) #마찬가지로 scatter아닌 text  
plt.show() #다 잘 구분되어있음을 확인할 수 이따

- 분류, 회귀 전에 매니폴드 학습을 해놓으면 문제가 더 간단히 해결될 수 있다

확실히 pca보다 t-SNE에서 더 잘 구분되어 있음을 확인할 수 있었다.! 


 

개인 코드 공유. 위의 대회 웹 사이트 또는 대회 규정에 달리 명시되어 있지 않는 한, 대회 기간 동안 대회 자료 또는 기타 출처 또는 대회 관련 실행 코드와 관련하여 개발된 소스 또는 실행 코드를 개인적으로 공유 할 수 없습니다.

라는 데이콘 내의 동의사항 규정에 따라 대회 일정인 9월 19일까지 기다리느라(?) 업로드가 늦었습니다. 

https://dacon.io/competitions/official/235961/overview/description

 

청경채 성장 예측 AI 경진대회 - DACON

분석시각화 대회 코드 공유 게시물은 내용 확인 후 좋아요(투표) 가능합니다.

dacon.io

우선 데이터를 받아보시면 train의 data파일과 label파일이 58개씩 있고, test의 data파일과 label파일이 8개씩 있습니다.

무작정 병합하기 전에 데이터를 어떻게 볼 지부터 알아야 하기에 train data하나를 읽었습니다. 컬럼도 많고 그만큼 NaN도 많아 전처리가 명확하지 않은 점 미리 양해 바랍니다.

시간 컬럼 값이 2022-04-26 00:01:00, 2022-04-26 00:02:00 이런식으로 분단위로 되어있음을 알 수 있었습니다. 다만 train label파일에는 2022-04-27, 2022-04-28처럼 일단위로 되어있어 x와 y로 주었을 때 수가 맞지 않을 거라 보고, 일 단위로 resample해야겠다 생각했습니다.

train_1 = pd.read_csv('/content/train_data (1).csv')
train_1 = train_1.dropna(how='all') #(로우)값이 모두 nan인 것만 드롭

train_1['date'] = pd.to_datetime(train_1['시간']) #시간값을 datetime 형태로
train_1 = train_1.drop('시간', axis=1) #기존의 시간컬럼 드랍
train_1 = train_1.set_index('date') #datetime형태의 컬럼을 인덱스로

train_1_tp = pd.DataFrame() #빈 df만듦
for i in train_1.columns: #컬럼 하나하나(들)에 대해서
    tp = train_1[i].resample(rule='1D').mean() #1일 단위로 resample. 함수는 mean
    train_1_tp = pd.concat([train_1_tp, tp], axis=1) #빈 df에 붙여주기

train_1 = train_1_tp #위의 for문에서 만든 df를 train_1에 덮어씀
train_1.head()

train data를 어느정도 정리해주었으니 이제 train label도 보아야 할 차례입니다.

train_1_tg = pd.read_csv('/content/train_target (1).csv')
train_1_tg = train_1_tg.dropna(how='all')

train_1_tg['date'] = pd.to_datetime(train_1_tg['시간'])
train_1_tg = train_1_tg.drop('시간', axis=1)
train_1_tg = train_1_tg.set_index('date')

train_1_tg.head()

train data와 마찬가지로 모든 (로우)값이 nan인 것들을 드랍해주고, 기존의 시간 컬럼을 datetime으로 변경해 date컬럼에 넣어준 뒤 시간 컬럼을 삭제하고 date컬럼을 인덱스로 설정해주었습니다.

보시면 아시겠지만, target의 date는 4월 29일부터 시작합니다. data의 date는 4월 26일부터였는데 말이죠..ㅎ...ㅎㅎ...

이것에 대해서 같이 프로젝트 하는 친구들과 상의해 본 결과, target에는 data의 성장 정보('일별 잎면적 증감률')가 기록되어 있는 만큼 아마도 "4월 28일의 데이터를 바탕으로 4월 29일의 증감률"을, "4월 29일의 데이터를 바탕으로 (4월 29일 대비)4월 30일의 증감률"을 알 수 있는 게 아닐까 싶었습니다. 위에서 기존의 train data의 1분 간격 데이터들을 1일 간격으로 바꿔주었었는데, (저희의 추론이 맞다는 가정하에) 4월 29일의 target rate를 알고싶다면 4월 29일의 데이터들을 매칭시켜주어야 할 것 같습니다.

import datetime

train_1_tp = pd.to_datetime(train_1.index) + datetime.timedelta(days=1) #1일씩 더함
train_1.index = train_1_tp #더해준 datetime 날짜를 인덱스로 설정

train_1_tg_tp = pd.to_datetime(train_1_tg.index) #label data는 datetime 그대로 사용
train_1_tg.index = train_1_tg_tp

train_1 = pd.merge(train_1, train_1_tg, left_index=True, right_index=True, how='right')
#(train의)label데이터의 인덱스(날짜)를 기준으로 data와 merge!
train_1_X = train_1.iloc[:,:-1] #마지막 컬럼(target값)까지 X
train_1_y = train_1.iloc[:,-1] #마지막 컬럼(target값)만 y

display(train_1_X.head(3), train_1_y.head(3))

(아직 datetime을 사용해 본 경험이 많지 않아서.. datetime으로 바꿨던 걸 set_index를 썼더니 type(train_1.index) 찍었을 때 `pandas.core.indexes.base.Index`이길래 또 datetime으로 바꾸는.. 짓을 하였습니다^-^)

train_1_X(위의 긴 df) 자체는 엄청나게 긴데, 캡쳐하다보니 생략된 부분을 제하고도 컬럼이 중간에 짤려버렸습니다... 그래도 덕분에 날짜 인덱스(date컬럼)는 맞춰진 듯 합니다! 이런 식으로 다른 파일에도 똑같이 적용해주면 될 것 같아 함수를 만들기로 했습니다.

def preprocessing_X(i): #위의 과정들을 반복할 함수 생성
    train_i = pd.read_csv(f'/content/train_data ({i}).csv')
    train_i = train_i.dropna(how='all') #불러와서 값이 모두 nan인 값 삭제

    train_i['date'] = pd.to_datetime(train_i['시간'])
    train_i = train_i.drop('시간', axis=1)
    train_i = train_i.set_index('date')

    train_i_tp = pd.DataFrame() #빈 df생성
    for i in train_i.columns: #불러온 파일의 컬럼 수만큼 반복하되
        tp = train_i[i].resample(rule='1D').mean() #1일단위로 리샘플
        train_i_tp = pd.concat([train_i_tp, tp], axis=1) #빈 df에 리샘플한 데이터 병합
    train_i = train_i_tp #반복문을 다 돈 df를 기존 변수명에 덮어씌움

    train_i.index = pd.to_datetime(train_i.index) + datetime.timedelta(days=1)
    #datetime으로 바꾼 인덱스 값들에 하루씩 날짜를 더해 기존 인덱스값에 덮어씌움 
    return train_i #처리(?)를 마친 df 반환

f_train = pd.DataFrame() #빈 df를 만들어서
for i in range(1, 59): #i는 1부터 58까지 반복
    tp = preprocessing_X(i) #함수에 i값을 넣어가며
    f_train = pd.concat([f_train, tp]) #빈 df에 병합
def preprocessing_y(i): #target값에 대해서도 똑같이 함수 생성
    train_i_tg = pd.read_csv(f'/content/train_target ({i}).csv')
    train_i_tg = train_i_tg.dropna(how='all')

    train_i_tg['date'] = pd.to_datetime(train_i_tg['시간'])
    train_i_tg = train_i_tg.drop('시간', axis=1)
    train_i_tg = train_i_tg.set_index('date')

    train_i_tg_tp = pd.DataFrame()
    for i in train_i_tg.columns:
        tp = train_i_tg[i].resample(rule='1D').mean()
        train_i_tg_tp = pd.concat([train_i_tg_tp, tp], axis=1)
    train_i_tg = train_i_tg_tp

    train_i_tg.index = pd.to_datetime(train_i_tg.index)
    return train_i_tg #내용은 data부분과 동일(다만, timedelta 사용x)
        
f_train_tg = pd.DataFrame()
for i in range(1, 59):
    tp = preprocessing_y(i)
    f_train_tg = pd.concat([f_train_tg, tp])
train = pd.merge(f_train, f_train_tg, how='right', left_index=True, right_index=True)
#함수를 적용해 만든 f_train과 f_train_tg를 f_train_tg기준으로 merge
train_X = train.iloc[:,:-1] #마지막 컬럼(target값)을 제하고 X #shape는 (14256, 42)
train_y = train.iloc[:,-1] #마지막 컬럼(target값)을 y #shape는 (14256, 1)

tr_lst = list(f_train.index) #len은 1814
te_lst = list(f_train_tg.index) #len은 1813

그런데 문제아닌 문제가 생겨버립니다.. 분명 train_X와 train_y의 로우 수는 동일한데, 이 둘의 index를 list로 만들어서 len을 보면 1814와 1813..이라는 값이 나와서, 맞지 않게 되어버려요. 일단 여기서 1차 멘붕이 왔지만 애써 무시했습니다.

어쨌든 train의 X와 y를 만들었으니 test의 X까지 만들면 어떻게든 test y값을 낼 수 있지 않을까요?

test_X = pd.read_csv('/content/test_data (1).csv', index_col='시간')
test_X.index = pd.to_datetime(test_X.index)

test_1_tp = pd.DataFrame() #빈 df만들고
for i in test_X.columns: #각 컬럼들에 대해서
    tp = test_X[i].resample(rule='1D').mean() #1일 단위로 리샘플
    test_1_tp = pd.concat([test_1_tp, tp], axis=1) #빈 df에 리샘플한 데이터 병합

test_X = test_1_tp #리샘플한 데이터로 구성된 df를 불러왔던 파일이 담긴 변수에 덮어씌우고
test_X.index = pd.to_datetime(test_X.index) #인덱스를 datetime으로 바꿈

뒤늦게 발견한건데, 정신이 없었던 탓인지 무엇인지 모르겠지만 위의 코드박스 아래에 `test_X = test_X.dropna(how='all')`을 썼더라구요. 응..? 아니 리샘플 한 뒤에 drop하면 어쩌자는 거지.. 그럼 NaN을 포함하고 mean을 했던건가.. 싶고... ㅎ

하여튼 그렇습니다. 저 상태로 처음 가공했던 train_data_1, train_target_1, test_data_1까지 넘겨준 뒤 회귀를 돌려보게 했는데 score가 0.17이었나..? 엄청나게 처참한 결과가 나왔길래 scaler라도 써봐야겠다 싶었습니다.

tp = train_X.fillna(0) #nan들을 0으로 처리해 준 뒤
from sklearn.preprocessing import StandardScaler
sc = StandardScaler(); sc.fit(tp) #SC 학습
tp_scaled = sc.transform(tp) #sc적용
tp = round(pd.DataFrame(tp_scaled), 7) #df로 만듦
tp.set_index(train_X.index, inplace=True)
tp.columns = train_X.columns #인덱스, 컬럼명 설정

import matplotlib.pyplot as plt
from sklearn.decomposition import PCA #차원축소로 분포 확인

pca = PCA(n_components=2); pca.fit(tp) #2차원으로 축소, tp학습
X_pca = pca.transform(tp)
print(X_pca.shape) #(14256, 2) #2차원으로 잘 축소됨 확인
plt.scatter(X_pca[:,0], X_pca[:,1]); plt.show() #산점도 그리기

 

처음에 NaN들을 0으로 채워서 그런지 아무래도 0 주변에 값들이 모여있음을 확인할 수 있었습니다. sc를 적용하면서 0으로 채웠던 값들이 -1~-5쯤에 모이게 된 것 같아요.

ㅋ.. 어떡하죠

ㅋㅋㅋㅋㅋㅋㅋㅋ 여기서 2차 멘붕이 왔습니다.. 이게 맞는 것인가..?

어렵네요...

하여튼 저 상태에서 랜덤포레스트회귀 같은 걸 계속 돌려보았는데 오히려 sc사용 전보다 사용 후가 더 낮은 score를 갖더라구요. 여기서 3차 멘붕이 왔습니다. ^^ 그리고.. 팀 결성 기간이 끝났습니다.

많이 허무하고, 그냥.. 공부를 많이 해야겠다 생각했습니다.


어떡하죠, 학기 초라 그런가..? 각종 일정 때문에 사실상 부트캠프를 들을 여력이 되지 않습니다. 그렇지만 거의 다 왔는데.. 사실상 수업 진도는 한 두달, 프로젝트까지 합쳐 총 세 달 남짓밖에 남지 않았는데 포기하고 싶다는 생각만 스멀스멀 올라옵니다.

그렇다면 잠을 줄여서라도 해야할텐데 제 욕심에 비해 제 의지는 한없이 빈약하고 초라함을 깨닫고 있는 하루하루입니다.

'STUDY' 카테고리의 다른 글

파이썬 스터디 ver3. 12주차  (2) 2022.10.10
파이썬 스터디 ver3. 11주차  (2) 2022.10.03
파이썬 스터디 ver3. 9주차  (3) 2022.08.26
파이썬 스터디 ver3. 8주차  (2) 2022.08.22
파이썬 스터디 ver3. 7주차  (2) 2022.08.14