본문 바로가기
카테고리 없음

파도!(14) - 릿지 리그레션으로 정확도가 높아진다구?

※ '파도'는 파이스크립트 도전기의 준말입니다.

지난 게시글에 연재되는 글입니다 : https://itadventure.tistory.com/554

 

파도!(13) - 음? 인공지능 적중율이?! - 평균가격 추가

'파도'는 파이스크립트 도전기의 줄임말입니다. 지난 게시글에서 이어지는 내용입니다 : https://itadventure.tistory.com/553 파도!(12) - 무신 러닝? 머신러닝! - 리니어 리그레션 ( LinearRegression ) '파도'..

itadventure.tistory.com

 

과소적합이라구?

 

지난 시간에는 평균가격 도입으로 머신러닝의 매출량 적중율이 높아진 것을 볼수가 았었는데요.
훈련데이터 점수는 58%인 반면에 테스트데이터 점수는 73%이었지요.

훈련용모델 정확도
0.5872217352485853
테스트모델 정확도
0.730782898221902

이렇게 훈련데이터 적중율이 테스트데이터 적중율보다 낮은 경우를 '과소적합'이라고 하는데요.
보통은 훈련세트와 테스트 데이터의 적중율이 비슷해야 좋은 알고리즘이라고 합니다.

 

차수가 뭐여?

 

훈련세트가 적중율이 낮기 때문에 우선 훈련세트의 적중율을 높여야 하는데요.
적중율을 높이는 방법으로는 단서가 될만한 추가 정보를 넣는 것 외에도,
'차수를 이용한 방법'이 있습니다.

차수란 무엇일까요?
약간의 중학교 수준의 수학적 지식이 요구되지만 이해하지 않으셔도 머신러닝을 하는데 큰 지장은 없습니다.
중학교 수학시간에 1차 방정식2차 방정식에 관해 들어보신 적이 있으실 겁니다.

1차 방정식은 아래와 같은 그래프입니다.

본 그래프의 공식은 아래와 같은데요.

y = 0.5x

코랩 소스를 참고용으로 싣습니다. ( 파이스크립트에서는 바로 실행되지 않습니다 )

import matplotlib.pyplot as plt
import numpy as np
x = np.array(range(-5, 5))
y = 0.5 * x
plt.xlabel('x axis')
plt.ylabel('y axis')
plt.hlines(0, -5, 5, linestyle='--')
plt.vlines(0, -2.5, 2.5, linestyle='--')
plt.plot(x, y)

 

그리고 2차 방정식은 X의 제곱과 같은 수치를 입력하기 때문에 공식과 그래프의 모양이 아래와 같지요

y = x²

역시 간단한 구글 코랩용 소스를 싣습니다.

import matplotlib.pyplot as plt
import numpy as np
x = np.array(range(-5, 6))
y = x ** 2
plt.xlabel('x axis')
plt.ylabel('y axis')
plt.hlines(0, -5, 5, linestyle='--')
plt.vlines(0, 0, 25, linestyle='--')
plt.plot(x, y)

 


지금까지 게시글에서 다룬 선형회귀들은 사실 1차 방정식이었습니다.
다만 x값이 한개가 아니라, 아래와 같이 여러개의 x값이 존재하며
머신러닝이 훈련과정에 그 값들을 절묘하게 조정해 매출량(y)값을 산출하는 것으로 파악됩니다.

y = cx₁ + cx₂ + cx₃ + cx₄ + ...

c₁, c₂, ... 는 상수, x₁, x₂, x₃, x₄ 는 연도, 월, 일, 주 등의 값

이러한 계산 방법으로는 훈련과정에 한계가 있게 마련인데요.
보다 정확도를 높이기 위해 공식을 아래와 같이 바꿔보면 어떨까요?

y = cx₁ + cx₂ + cx₃ + cx₄ + ...
      cx₁² cx₂²cx₃²cx₄² + ...

 

2차 방정식 자체가 곡선을 표현할 수 있다보니 보다
디테일한 굴곡을 표현할 수 있도록 머신 러닝이 훈련을 시킬 수 있습니다.

주저리 주저리 공식을 설명드렸지만 복잡하시다면 다 잊어버리시고,
사용 방법만 생각하시면 되는데요.

 

차수 데이터 쉽게 넣기

 

우리가 지금까지 머신러닝에 제공한 훈련데이터는 연도, , , , 평균가격 이었습니다.
그리고 이 훈련데이터에 따른 매출량을 학습시켰지요.

이제는 추가 데이터로 연도의 제곱, 월의 제곱, 일의 제곱, 주의 제곱, 평균가격의 제곱 수치도 함께 제공하는 것입니다. 대신 그 데이터가 2배로 늘어나게 되는데요.
이렇게 제곱 수치 데이터를 제공하게 되면 그래프가 좀 더 정밀한 표현을 할 수 있게 됩니다.

어디 한번 진행해볼까요? 사실 지난 소스에서 바뀌는 곳은 아주 일부분입니다. 아래와 같은데요. ( 빨간색 )


주간매출데이터목표_넘파이 = 주간매출데이터['매출량(만)'].to_numpy()

주간매출데이터훈련_넘파이 = 주간매출데이터훈련_넘파이.astype(np.float)
주간매출데이터훈련_넘파이 = np.column_stack(( 
  주간매출데이터훈련_넘파이 ,
  주간매출데이터훈련_넘파이[:,0] ** 2,
  주간매출데이터훈련_넘파이[:,1] ** 2,
  주간매출데이터훈련_넘파이[:,2] ** 2,
  주간매출데이터훈련_넘파이[:,3] ** 2,
  주간매출데이터훈련_넘파이[:,4] ** 2,
  주간매출데이터훈련_넘파이[:,5] ** 2
))


여기서 astype(np.float) 는 넘파이 데이터들을 문자에서 숫자로 바꿔주는 기능입니다.
숫자로 바꿔주어야 제곱과 같은 연산을 처리할 수 있거든요.

"1972" => 1972 ( 숫자로 변신! )


그리고 np.column_stack 은 넘파이 원본 데이터에 하나씩 열을 추가해주는 기능인데요.
원본데이터 주간매출데이터훈련_넘파이의 우측에 열을 추가해줍니다.

원본

[
  [1420297200.00 2015.00 1.00 4.00 1.00 1.30] 
  [1420902000.00 2015.00 1.00 11.00 2.00 1.37] 
         :  
]


앞의 6칸의 제곱수를 추가한 부분 ( 빨간색 )


[
  [1420297200.00 2015.00 1.00 4.00 1.00 1.30 2017244136327840000.00 4060225.00 1.00 16.00 1.00 1.69]
  [1420902000.00 2015.00 1.00 11.00 2.00 1.37 2018962493604000000.00 4060225.00 1.00 121.00 4.00 1.88]
    :
]


 

차수 데이터 넣은 결과는?

 

이렇게만 수정하고 훈련시킨 결과 어떻게 되었을까요?
우선 훈련그래프는 아주 좋어졌습니다.
위쪽이 적용전이고 아래쪽이 적용후인데요. 파란색 선이 빨간색 선에 보다 근접한 것을 볼 수가 있습니다.

훈련데이터의 점수도 58%애서 75%로 올라갔는데요.

한가지 문제가 생겼습니다. 테스트데이터 적중율이 아주 형편없어진 것이지요.
적중율이 73%에서 1%대로 낮아진 것입니다. 우째 이런일이...

보통 이런 경우를 과적합이라고 하는데요.
훈련용 데이터에 너무 충실하다 보면 그런 일이 발생할 수 있다는군요.
참고로 위 예제는 아래 URL 에서 보실 수 있습니다.
http://dreamplan7.cafe24.com/pyscript/py10-4.html

그래서 머신러닝에서는 과적합을 방지하는 기법들이 있는데요.
지금까지 본 리니어 리그레션(Linear Regression)에서 이런 개념을 더한 것으로
릿지 리그레션(Rigde Regression)과 라쏘 리그레션(Lasso Regression)이 있습니다.

이에 대한 수학적 공식은 알 필요는 없습니다.
우리는 다만 이 툴을 사용하는 사용자이니까요 :)
우리가 해결할 일은 릿지 리그레션을 사용하여 과적합을 해결하는 일뿐이기 때문에
여기서는 릿지 리그레션의 사용법을 알아보겠습니다.

 

리니어 대신 릿지!

 

릿지 리그레션은 사실 리니어 리그레션과 사용법면에서 별반 차이가 없습니다.

리니어 리그레션이 아래와 같은 모양이라면,

from sklearn.linear_model import LinearRegression
선형회귀모델 = LinearRegression()
선형회귀모델.fit(훈련용데이터, 훈련용목표)
print("훈련용모델 정확도")
print(선형회귀모델.score(훈련용데이터, 훈련용목표))
print("테스트모델 정확도")
print(선형회귀모델.score(테스트데이터, 테스트목표))
훈련용목표예측 = 선형회귀모델.predict(훈련용데이터)
테스트목표예측 = 선형회귀모델.predict(테스트데이터)


릿지 리그레션은 아래와 같은 모양이거든요. 

from sklearn.linear_model import Ridge
릿지모델 = Ridge(alpha=알파값)
릿지모델.fit(훈련용데이터, 훈련용목표)
print("훈련용데이터 정확도")
print(릿지모델.score(훈련용데이터, 훈련용목표))
print("테스트데이터 정확도")
print(릿지모델.score(테스트데이터, 테스트목표))
훈련용목표예측 = 릿지모델.predict(훈련용데이터)
테스트목표예측 = 릿지모델.predict(테스트데이터)


모양이 상당히 비스무리한데요.
상이한 부분은 사이킷 런에서 릿지 모듈를 불러오는 부분이 다르고


from sklearn.linear_model import Ridge



릿지모델을 정의할 때, 알파값이란 변수를 파라미터로 주는 점입니다.
이 알파값이 과적합되지 않도록 방지해주는 옵션입니다.


릿지모델 = Ridge(alpha=0.1)


그 외 나머지 부분은 붕어빵처럼 똑같지요.

 

알파값은 과적합 방지 옵션

 

보통 알파값으로 과적합을 방지하는데요.
0.01, 0.1, 1, 10 등으로 1의 배수 단위를 사용하는 것이 일반적이고,
사람이 이 값으로 정도를 조절해야 한다고 해서 하이퍼 파라미터(Hyper parameter)라고 부릅니다.

크레이가 몇번 시험해본 결과 아래 2개의 알파값이 점수가 높은 편이었는데요.

알파값 : 0.01

훈련용데이터 정확도
0.7263856825522386
테스트데이터 정확도
0.696256651630534

알파값 : 0.1
훈련용데이터 정확도
0.7183159560818655
테스트데이터 정확도
0.7504082410181369


알파값 0.1이 그래도 점수가 높은 편이어서, 이 값으로 최종 소스를 작성해보았습니다.

<html> 
    <head> 
      <link rel="stylesheet" 
        href="https://pyscript.net/alpha/pyscript.css" /> 
      <script defer 
        src="https://pyscript.net/alpha/pyscript.js"></script> 
<py-env>
  - pandas
  - matplotlib
  - seaborn
  - scikit-learn
  - paths:
    - ./NANUMMYEONGJO.TTF
    - ./NANUMMYEONGJOBOLD.TTF
    - ./common.py
</py-env>
    </head>
  <body> 
    <link rel="stylesheet" href="pytable.css"/>
<py-script>
import pandas as pd
from pyodide.http import open_url
from common import *
import numpy as np

# 넘파이 배열 출력시 소숫점 자릴수 지정
np.set_printoptions(formatter={'float_kind': lambda x: "{0:0.2f}".format(x)})

from datetime import datetime

# 경고 문구 제거
import warnings
warnings.filterwarnings( 'ignore' )

# 판다스에서 csv 를 데이터 프레임으로 읽어옴
매출데이터 = pd.read_csv(open_url(
  "http://dreamplan7.cafe24.com/pyscript/csv/avocado.csv"
))      

# 3개 필드만 추려서 데이터 프레임을 다시 만듬
매출데이터 = 매출데이터[[
  'Date', 
  'Total Volume',
  'AveragePrice'
]]   

# 영문 제목을 한글로 변경
매출데이터.columns = [
  '날짜', 
  '매출량',
  '평균가격'
]

주간매출_매출량=매출데이터.fillna(0) \
  .groupby('날짜', as_index=False)[['매출량']].sum() \
  .sort_values(by='날짜', ascending=True)
  
주간매출_평균가=매출데이터.fillna(0) \
  .groupby('날짜', as_index=False)[['평균가격']].mean() \
  .sort_values(by='날짜', ascending=True)

주간매출데이터=pd.merge(주간매출_매출량, 주간매출_평균가, on='날짜')

# 날짜(시간값) 추가
주간매출데이터.insert(1, '날짜(시간값)',
  '',
  True)
  
for i in 주간매출데이터['날짜'].index:
  주간매출데이터['날짜(시간값)'].loc[i]=time.mktime(
    datetime.strptime(
      주간매출데이터['날짜'].loc[i], 
      '%Y-%m-%d'
    ).timetuple()
  )

# 10000으로 나눈 매출량 필드 추가
주간매출데이터.insert(3, '매출량(만)', 
  주간매출데이터['매출량']/10000, 
  True)

# 훈련학습용으로 날짜를 연도, 월, 일로 나눈다
주간매출데이터.insert(4, '연도', '', True)
주간매출데이터.insert(5, '월', '', True)
주간매출데이터.insert(6, '일', '', True)
주간매출데이터.insert(7, '주', '', True)

for i in 주간매출데이터['날짜'].index:
  임시=str(주간매출데이터['날짜'].loc[i]).split('-')
  연도=int(임시[0])
  월=int(임시[1])
  일=int(임시[2])
  주간매출데이터['연도'].loc[i]=연도
  주간매출데이터['월'].loc[i]=월
  주간매출데이터['일'].loc[i]=일
  주간매출데이터['주'].loc[i]=str(
    datetime(연도, 월, 일).isocalendar()[1]
  )

createElementDiv(
  document, 
  Element, 
  'output2'
).write(주간매출데이터)

주간매출데이터훈련_넘파이 = 주간매출데이터[['날짜(시간값)', '연도', '월', '일', '주', '평균가격']].to_numpy()
주간매출데이터목표_넘파이 = 주간매출데이터['매출량(만)'].to_numpy()

주간매출데이터훈련_넘파이 = 주간매출데이터훈련_넘파이.astype(np.float)

print(주간매출데이터훈련_넘파이[:5])

주간매출데이터훈련_넘파이 = np.column_stack(( 
  주간매출데이터훈련_넘파이 ,
  주간매출데이터훈련_넘파이[:,0] ** 2,
  주간매출데이터훈련_넘파이[:,1] ** 2,
  주간매출데이터훈련_넘파이[:,2] ** 2,
  주간매출데이터훈련_넘파이[:,3] ** 2,
  주간매출데이터훈련_넘파이[:,4] ** 2,
  주간매출데이터훈련_넘파이[:,5] ** 2
))

print(주간매출데이터훈련_넘파이[:5])

from sklearn.model_selection import train_test_split

훈련용데이터, 테스트데이터, 훈련용목표, 테스트목표 = \
  train_test_split(
    주간매출데이터훈련_넘파이, 
    주간매출데이터목표_넘파이,
    random_state=100,
    shuffle=False)

#print(훈련용데이터[0:5])
#print(훈련용목표[0:5])

#print(테스트데이터[0:5])
#print(테스트목표[0:5])

from sklearn.preprocessing import StandardScaler
스케일러 = StandardScaler()
스케일러.fit(훈련용데이터)
훈련용데이터_스케일 = 스케일러.transform(훈련용데이터)
테스트데이터_스케일 = 스케일러.transform(테스트데이터)



from sklearn.linear_model import Ridge
릿지모델 = Ridge(alpha=0.1)
릿지모델.fit(훈련용데이터_스케일, 훈련용목표)

print("훈련용데이터 정확도")
print(릿지모델.score(훈련용데이터_스케일, 훈련용목표))
print("테스트데이터 정확도")
print(릿지모델.score(테스트데이터_스케일, 테스트목표))

훈련용목표예측 = 릿지모델.predict(훈련용데이터_스케일)
테스트목표예측 = 릿지모델.predict(테스트데이터_스케일)

import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import matplotlib as mat

# 기준 폰트 변경 : legend 의 한글을 적용하려면 필수
fm.fontManager.addfont('./NANUMMYEONGJO.TTF') #  폰트명 : NanumMyeongjo
mat.rc('font', family='NanumMyeongjo')

###################
# 폰트 목록 출력 ( 폰트 추가 후 정확한 이름을 확인하려면 필요
#font_list = [font.name for font in fm.fontManager.ttflist]
#for f in font_list:
#  print(f)      
###################        

# 개별 폰트 적용
NanumMyengjo = fm.FontProperties(
  fname='./NANUMMYEONGJO.TTF'
)
NanumMyengjoBold = fm.FontProperties(
  fname='./NANUMMYEONGJOBOLD.TTF'
)

# 그래프
fig = plt.figure(
  figsize=(15, 7)
)

plt.xticks(
  주간매출데이터['날짜(시간값)'].to_numpy(), 
  주간매출데이터[['날짜']].to_numpy()[:,0], 
  rotation=90, fontsize=8)

plt.title('주간 아보카도 매출량',  
  fontproperties=NanumMyengjoBold, 
  fontsize=32
);

plt.plot(        
  훈련용데이터[:,0],
  훈련용목표,
  marker='o',
  color='#c14549',
  label='원본'
)
plt.plot(        
  훈련용데이터[:,0],
  훈련용목표예측,
  marker='d',
  color='blue',
  label='훈련패턴'
)
plt.plot(        
  테스트데이터[:, 0],
  테스트목표,
  marker='o',
  color='#c14549'
)

plt.plot(        
  테스트데이터[:, 0],
  테스트목표예측,
  marker='d',
  color='green',
  label='예측패턴'
)

plt.xlabel('날짜', fontsize=16)
plt.ylabel('매출량(단위:만)', fontsize=12)

# loc : 표 하단 중앙
#plt.legend(
#  loc=8
#)

plt.legend(
  shadow=True
)

ax = plt.gca()
# 축만 그리드
ax.xaxis.grid(True)

# 배경색, 마진 조정
ax.set_facecolor('#e8e7d2')
ax.margins(x=0.01, y=0.02)

# 주위 이상한 여백 없애기
fig.tight_layout() 
fig

</py-script> 
  </body> 
</html>


결과는 아래와 같으며 이 예제는 크레이의 홈페이지에 업로드해놓아 아래 URL에서 확인하실 수 있습니다.
http://dreamplan7.cafe24.com/pyscript/py10-5.html

 

마무~리

 

오늘은 원본 훈련 데이터에 차수 데이터를 추가하는 방법,
그리고 훈련데이터에 너무 맞추어져서 테스트 데이터 적중율이 엄청 떨어지는 과적합 문제를
리니어 리그레션과 사촌격인 릿지 리그레션과 알파값으로 규제를 주어 해결하는 방법을 살펴보았습니다.

아무쪼록 필요하신 분께 도움이 되셨을지요 :)
방문해주시는 모든 분들께 늘 감사드립니다.

유익하셨다면 공감  한방, 댓글은 굿잡!
감사합니다~


다음게시글 : https://itadventure.tistory.com/557

 

파도!(15) - 라쏘회귀와 4차방정식까지

🍿 '파도'는 파이스크립트 도전기의 줄임말입니다. 지난 게시글에서 연재되는 글입니다. : https://itadventure.tistory.com/555 파도!(14) - 릿지 리그레션으로 정확도가 높아진다구? ※ '파도'는 파이스크

itadventure.tistory.com