Programming

LSTM을 활용한 주식가격 예측

강의 홍보

LSTM과 RNN의 개요

  • RNN은 자연어처리에서 사용되는 대표적인 알고리즘
    • 순환신경망으로 표현됨
    • 활용범위: 음성 인식, 언어 모델링, 번역, 이미지 주석 생성

기업 요청 샘플 (수강생) - Python Dash를 활용한 대시보드

강의 홍보

개요

  • 보안 로그 파일을 업로드한 뒤, 점검 결과를 자동으로 출력해주도록 한다.
  • (수강생의 도전) 보안 로그 파일을 업로드 한 뒤, CPU 사용률이 70%가 넘으면 경고 메시지를 뛰우도록 한다.

Chapter 1. 로그데이터 분석 및 확인

  • 먼저 CPU가 들어있는 로그데이터를 확인한다.
    • 골든시스에서 제공한 Sample 데이터를 근거로 랜덤하게 데이터를 생성했다. 파일명: (cpu_test.txt)
Dorm_E_116.95#
Dorm_E_116.95#sh logg
Dorm_E_116.95#sh logging 
Syslog logging: enabled (0 messages dropped, 1 messages rate-limited, 0 flushes, 0 overruns, xml disabled, filtering disabled)

No Active Message Discriminator.

No Inactive Message Discriminator.

    Console logging: level debugging, 2178 messages logged, xml disabled,
                     filtering disabled
    Monitor logging: level debugging, 244 messages logged, xml disabled,
                     filtering disabled
    Buffer logging:  level debugging, 2178 messages logged, xml disabled,
                    filtering disabled
    Exception Logging: size (4096 bytes)
    Count and timestamp logging messages: disabled
    File logging: disabled
    Persistent logging: disabled

No active filter modules.

    Trap logging: level informational, 2176 message lines logged
        Logging Source-Interface:       VRF Name:
          
Log Buffer (100000 bytes):
17:19:23: %PM-4-ERR_DISABLE: storm-control error detected on Gi1/0/36, putting Gi1/0/36 in err-disable state
Jul  7 17:19:23: %STORM_CONTROL-3-SHUTDOWN: A packet storm was detected on Gi1/0/36. The interface has been disabled.
Jul  7 17:19:25: %LINK-3-UPDOWN: Interface GigabitEthernet1/0/36, changed state to down
Jul  7 17:19:53: %PM-4-ERR_RECOVER: Attempting to recover from storm-control err-disable state on Gi1/0/36
Switch#show processor cpu
CPU utilization for five seconds: 4%/1%; one minute: 3%; five minutes: 3%
.
.
.
  • 업무 수행 시, 위와 같은 데이터를 생성한다. 그 후에, 점검일지 파일을 작성한다.
    • 점검일지 파일 Sample /img/programming/2021/08/dash_cpu_logdata/log_01.png log_01.png

(1) 문제점 진단

  • 통상적으로 위와 같이 명령어를 입력한 뒤 결괏값을 찾아 수기로 입력하여 작성하도록 함
  • 일일이 확인해야 하는 번거로움이 있다보니, 이를 자동으로 처리할 수 있는 툴 제작 의뢰를 받음

(2) 해결방안

  • 명령어 및 결괏값이 일정한 패턴이 있기 때문에, 문자열 매칭을 통해 전처리가 가능한 것을 확인
  • 또한, Dash 프레임워크를 통해 비교적 간단하게 대시보드를 만들 수 있음에 착안하였다.

(3) 배경지식

Chapter 2. 문자열 전처리 함수 만들기

  • 우선 Jupyter Lab에서 간단한 테스트 함수를 만들어 보았다.
  • 전체 코드는 아래와 같다.
    • 파일을 불러온 뒤 우선 각 코드 라인 중에서 CPU utilization 가 있는 텍스트만 남기고 그 외에는 모두 삭제한다.
    • 여기에서 다른 명령어의 결괏값을 확인하다면, if else 구문으로 계속적으로 확장할 수 있을 것이다.
    • 그 후에, 각 re.sub 함수를 활용하여 수치만 뽑아낸다.
    • 이를 각 데이터 프레임에 추가한 것이다.
import pandas as pd
import re

def text_cleanser(FILE_PATH):
    with open(FILE_PATH, 'r') as f:
        logLines = f.read().splitlines()
    cleaned_lines = [x for x in logLines if "CPU utilization" in x]
    clean_text = re.sub('/[0-9]%', '', cleaned_lines[0])
    print("temp:", clean_text)
    result_list = re.findall("\d+", clean_text)
    return result_list
        
FILE_PATH = "data/cpu_test.txt"
result_list = text_cleanser(FILE_PATH)
result_list
  • 위 파일을 Jupyter lab에서 실행하면 아래와 같은 결괏값이 출력될 것이다.
temp: CPU utilization for five seconds: 4%; one minute: 3%; five minutes: 3%
['4', '3', '3']
  • 출력한 결과물을 pandas 데이터 프레임에 적용한 결과는 아래와 같다.
data = pd.DataFrame({"분류":["cpu"],
                     "점검내용": ["CPU 사용률 점검 및 불필요한 프로세스 확인"],
                     "점검기준": ["MEM 임계치: 70% (MAX)"],
                     "점검방법": ["show processes cpu | in five"],
                     "5초 동안 CPU 사용률": [result_list[0]], 
                     "1분 동안 CPU 사용률": [result_list[1]], 
                     "5분 동안 CPU 사용률": [result_list[2]], 
                    })
data

log_02.png

네이버 뉴스 댓글 크롤링 대시보드 만들기 with Heroku

강의 홍보

1. 개요

  • 기존 웹크롤링은 주로 코드에 기반한 소개가 주를 이루었음
  • 본 장에서는 가급적 사용자 기준에 맞춰서 뉴스 URL만 입력하면 댓글 수집할 수 있는 기능 소개함

2. 라이브러리

  • 크롤링 및 대시보드 작업을 위한 필수 라이브러리는 다음과 같음 (requirements.txt)
colorama==0.4.4
dash==1.21.0
gunicorn==20.1.0
numpy==1.19.4
pandas==1.2.0
beautifulsoup4==4.9.3
openpyxl==3.0.7
requests==2.26.0
  • 위 파일을 프로젝트의 가장 최상단에 위치시켜 놓는다.
  • 설치 진행 시에는 pip install -r requirements.txt 해도 좋고, 아니면 개별적으로 설치를 해도 좋다.

3. 코드 설명

  • 본장에서는 디테일한 코드 설명은 넘어가도록 한다.

(1) 크롤링 코드

  • 먼저 크롤링 코드는 다음과 같다.
# 크롤링 라이브러리
from bs4 import BeautifulSoup
import requests
import re

# 데이터프레임
import pandas as pd

# 샘플 URL을 적용한다. 
url = "https://news.naver.com/main/read.naver?mode=LSD&mid=shm&sid1=100&oid=022&aid=0003609357" 

def get_df(url):
    # 댓글을 달 빈 리스트를 생성합니다.
    List = []
    url = url
    oid = url.split("oid=")[1].split("&")[0]
    aid = url.split("aid=")[1]
    page = 1
    header = {
        "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
        "referer": url,

    }
    while True:
        c_url = "https://apis.naver.com/commentBox/cbox/web_neo_list_jsonp.json?ticket=news&templateId=default_society&pool=cbox5&_callback=jQuery1707138182064460843_1523512042464&lang=ko&country=&objectId=news" + oid + "%2C" + aid + "&categoryId=&pageSize=20&indexSize=10&groupId=&listType=OBJECT&pageType=more&page=" + str(
            page) + "&refresh=false&sort=FAVORITE"
    # 파싱하는 단계입니다.
        r = requests.get(c_url, headers=header)
        cont = BeautifulSoup(r.content, "html.parser")
        total_comm = str(cont).split('comment":')[1].split(",")[0]

        match = re.findall('"contents":([^\*]*),"userIdNo"', str(cont))
        # 댓글을 리스트에 중첩합니다.
        List.append(match)

        # 한번에 댓글이 20개씩 보이기 때문에 한 페이지씩 몽땅 댓글을 긁어 옵니다.
        if int(total_comm) <= ((page) * 20):
            break
        else:
            page += 1

		# 
    def flatten(l):
        flatList = []
        for elem in l:
        # if an element of a list is a list
        # iterate over this list and add elements to flatList
            if type(elem) == list:
                for e in elem:
                    flatList.append(e)
            else:
               flatList.append(elem)
        return flatList

    # 리스트 결과입니다.
    # print(flatten(List))

    # convert dataframe
    data = pd.DataFrame(flatten(List), columns=["기사댓글"])
    data = data.rename_axis("index").reset_index()

    # write_excel
    # data.to_excel("news_comments.xlsx", sheet_name="Sheet1")
    return data

# data = get_df(url) # URL 테스트 시, 실행 
data = pd.DataFrame({"index": [0], "기사댓글": ["댓글"]}) # 앱 배포시 실행
# print(data.head())
  • 중간에 주석처리 한 것을 풀면 된다.
  • 해당 코드는 app.py 또는 일반적인 주피터 노트북, 구글 코랩에서 실행해도 된다.
  • 수집된 댓글을 확인해보니, 중간에 삭제된 글은 댓글 수집 시, 제외되는 것을 확인할 수 있다.

dash-crawling-04.png

Python Dash를 활용한 대시보드에서 엑셀 데이터로 다운로드 받기

강의 홍보

[대시보드] Dash Project - Excel 다운로드

개요

  • 각 레벨에 따라 달라지는 데이터를 시각화로 표현하고 결과치를 엑셀로 다운로드 받는 기능을 구현한다.

데이터 다운로드

import pandas as pd
train_df = pd.read_csv("train.csv")

id  level                                           full_log
0   0      0  Sep 24 10:02:22 localhost kibana: {"type":"err...
1   1      0  Feb  8 16:21:00 localhost logstash: [2021-02-0...
2   2      0  Jan 13 01:50:40 localhost kibana: {"type":"err...
3   3      0  Jan  4 10:18:31 localhost kibana: {"type":"err...
4   4      1  type=SYSCALL msg=audit(1603094402.016:52981): ...

함수 작성

  • 각 레벨에 따른 첫번째 글자수의 빈도를 구하는 데이터를 구하도록 한다.
  • 예를 들면, level 1에 대한 첫번째 글자 수를 집계한 결과는 아래와 같다.
    • 아래와 같은 결과값이 나오는 함수를 작성할 것이다.
index     cnt
0  type=SYSCALL  116496
1           Jan    3019
2           Oct    2904
3           Nov    2646
4           Feb    2381
  • 먼저 함수를 작성해본다.
def get_df(data, level=0):

    # pandas dataframe
    lvl_df = data.loc[data.level == level].full_log.apply(lambda x: x.split(' ')[0]).value_counts().rename(
        'cnt').to_frame().reset_index()

    return lvl_df

data = get_df(train_df, level = 1)
print(data.head())
index     cnt
0  type=SYSCALL  116496
1           Jan    3019
2           Oct    2904
3           Nov    2646
4           Feb    2381
  • 위 데이터를 토대로 시각화를 작성하고, 그 후에는 엑셀로 된 데이터로 변환하는 것이 목표이다.
  • 이 때, 각 레벨의 값에 따라 데이터 결괏값과 시각화의 그림도 달라지게 된다.

프로젝트 폴더 정리

  • 최종적인 프로젝트 폴더 및 파일은 아래와 같다.
C:.
  .gitignore
  app.py
  README.md


├─assets
      style.css

├─data
      train.csv

(1) 데이터 불러오기

  • 이제 app.py를 작성하도록 한다.
  • 먼저 데이터를 불러오는 코드 사용자 정의 함수는 다음과 같다.
import dash
import dash_table
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
from dash.dependencies import Input, Output

# 함수 정의
def get_df(data, level=0):
    # pandas dataframe
    lvl_df = data.loc[data.level == level].full_log.apply(lambda x: x.split(' ')[0]).value_counts().rename(
        'cnt').to_frame().reset_index()

    return lvl_df

DATA_PATH = "data/"
train_df = pd.read_csv(DATA_PATH + "train.csv")
print(train_df.head())

data = get_df(train_df, level=1)
print(data.head()) # 결과치가 잘 나오는지 확인하다. 
  • level=1 에서, 숫자를 변경하여 결괏값이 서로 다르게 나타나는지 반드시 확인하도록 한다.

(2) Dash 객체 생성

  • dash 클래스를 호출화여 app 인스턴스화를 진행한다.
  • 또한, app.titleapp.layout 작성한다.
.
.
external_stylesheets = [
    {
        "href": "https://fonts.googleapis.com/css2?"
                "family=Lato:wght@400;700&display=swap",
        "rel": "stylesheet",
    },
]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = "Temp Analytics: Understand Your Data!"

app.layout = html.Div(
    html.P("Hello World")
)

if __name__ == "__main__":
	app.run_server(debug=True)
  • 앱을 실행하면 아래와 같이 결괏값이 나타날 것이다.
id  level                                           full_log
0   0      0  Sep 24 10:02:22 localhost kibana: {"type":"err...
1   1      0  Feb  8 16:21:00 localhost logstash: [2021-02-0...
2   2      0  Jan 13 01:50:40 localhost kibana: {"type":"err...
3   3      0  Jan  4 10:18:31 localhost kibana: {"type":"err...
4   4      1  type=SYSCALL msg=audit(1603094402.016:52981): ...
          index     cnt
0  type=SYSCALL  116496
1           Jan    3019
2           Oct    2904
3           Nov    2646
4           Feb    2381
Dash is running on http://127.0.0.1:8050/
  • [http://127.0.0.1:8050/](http://127.0.0.1:8050/) 을 클릭하면 Hello World 만 나타날 것이다.

(3) HTML 상단 머리글 입력

  • 이제 HTML 코드를 추가로 입력한다.
  • 이제 app.layout 을 입력 하는 코드를 작성해보도록 한다.
  • HTML 코드는 DIV 태그가 기준이 된다. 따라서, DIV 태그가 완성될 때마다 화면을 공유하도록 했다.
  • 먼저, 화면의 상단 DIV 태그를 작업한다.
.
.
app.layout = html.Div(
	children=[
        html.Div(
            children=[
                html.P(children="📈", className="header_emoji"),
                html.H1(children="Temp Analytics", className="header_title", ),
                html.P(children="Temp", className="header_description", ),
            ],
            className='header',
        ),
	]
)
.
.
  • 아직 style.css을 입히기 전의 화면을 보면 다음과 같이 출력될 것이다.

dash_01.png

Python Dash를 활용한 대시보드 만들기 with Heroku

강의 소개

  • 필자의 강의를 소개합니다.

개요

  • 대시보드 프로젝트를 진행한다.
  • Heroku에 배포까지 진행하는 것을 목적으로 한다.
  • 참조: https://realpython.com/python-dash/
    • 여기에 있는 내용을 최대한 간결하게 한글로 재 작성하였다. 중간에 없는 코드들도 있으니, 가급적 본 소스코드를 활용한다.

1. 데이터 수집

C:\Users\1\Desktop\dashboard-project21>tree /f
폴더 PATH의 목록입니다.
볼륨 일련 번호는 E657-CFA3입니다.
C:.
  README.md

└─data
        avocado.csv
  • 파일 경로를 주의해서 보도록 합니다.

2. 가상환경 및 라이브러리 설치

  • conda를 활용하여 가상환경 설정을 합니다.
  • (dashboard-project21) 형태로 터미널 명령어가 바뀌어 있어야 합니다.
$ conda create --name dashboard-project21 python=3.8
.
.
$ conda activate dashboard-project21
(dashboard-project21) C:\Users\1\Desktop\dashboard-project21>
  • 이번에는 dash 라이브러리를 설치한다.
$ conda install dash
$ conda install pandas
$ conda install colorama

3. 대시 보드 코드 작성

(1) 데이터 수집 및 Dash 클래스 정의

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd

# step 1. Data Import
data = pd.read_csv("avocado.csv", index_col=0)
data = data.query("type == 'conventional' and region == 'Albany'")
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True)

# step 2. Dash Class
app = dash.Dash(__name__)
  • dash 라이브러리는 대시보드 어플리케이션 초기화를 담당한다.
  • dash_core_components 동적 구성요소들(예: 그래프, 드롭다운 메뉴, 날짜 기간 등) 작성할 수 있도록 도와주는 기능을 제공한다.
  • dash_html_components 은 html 태그에 접근하도록 한다.
  • pandas 데이터 수집 및 가공을 제공할 수 있는 함수들을 지원한다.
  • 코드 설명
    • step 1
      • 데이터를 avocado.csv 데이터를 수집한 후, type = conventional 과, region = Albany 만 추출하는 행을 추출한다.
      • 그 이후 날짜의 오름차순으로 정렬하는 코드를 작성한다.
    • step 2
      • Dash 클래스를 정의하여 app이라는 객체를 별도 생성한 것을 의미한다.

(2) 대시보드 HTML Layout 정의

  • 이전 코드에 이어서 작성을 하도록 한다.
# step 3. HTML
app.layout = html.Div(
	  # Header Message
    children=[
        html.H1(children="Temp Analytics",),
        html.P(
            children="Temp",
        ),
        # 그래프		
        dcc.Graph(
            figure={
                "data": [
                    {
                        "x": data["Date"],
                        "y": data["AveragePrice"],
                        "type": "lines",
                    },
                ],
                "layout": {"title": "Title_1"},
            },
        ),
        dcc.Graph(
            figure={
                "data": [
                    {
                        "x": data["Date"],
                        "y": data["Total Volume"],
                        "type": "lines",
                    },
                ],
                "layout": {"title": "Title_2"},
            },
        ),
    ]
)
  • Dash는 크게 2가지로 구성이 된다.

Python과 Oracle 연동

개요

  • 파이참에서 가상환경을 만들어 오라클 연동 예제를 작성한다.
  • 아나콘다, 파이참, 그리고 오라클 설치는 생략한다.

1. 가상환경 활성화

  • cmd 창에서 가상 환경을 세팅 하도록 한다. (권장: 관리자 실행)
  • 바탕화면에 필자는 python_oracle 폴더를 생성했다.
  • 현재 경로는 아래와 같다.
C:\Users\1\Desktop\python_oracle> 
  • 먼저 가상환경을 만든다.
conda create --name your_env_name python=3.8
.
.
done
#
# To activate this environment, use
#
#     $ conda activate python_oracle
#
# To deactivate an active environment, use
#
#     $ conda deactivate
  • your_env_name 대신 다른 이름으로 설정해도 된다.
  • 가상 환경에 접속한다.

C:\Users\1\Desktop\python_oracle>conda activate python_oracle
(python_oracle) C:\Users\1\Desktop\python_oracle>

2. 필수 라이브러리 설치

  • ML을 위한 필수 라이브러리를 설치한다.
  • pycaret & oracle
(python_oracle) C:\Users\1\Desktop\python_oracle>pip install pycaret
.
.
.
... wordcloud-1.8.1 yellowbrick-1.3.post1
(python_oracle) C:\Users\1\Desktop\python_oracle>pip install cx_Oracle
Collecting cx_Oracle
  Downloading cx_Oracle-8.2.1-cp38-cp38-win_amd64.whl (219 kB)
     |████████████████████████████████| 219 kB 2.2 MB/s
Installing collected packages: cx-Oracle
Successfully installed cx-Oracle-8.2.1
(python_oracle) C:\Users\1\Desktop\python_oracle> deactivate
  • deactivate 한 뒤, lsnrctl status 명령어를 통해 확인한다.
C:\Users\1\Desktop\python_oracle>lsnrctl status
LSNRCTL for 64-bit Windows: Version 19.0.0.0.0 - Production on 16-7월 -2021 10:28:24

Copyright (c) 1991, 2019, Oracle.  All rights reserved.

(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))에 연결되었습니다
리스너의 상태
------------------------
별칭                     LISTENER
버전                     TNSLSNR for 64-bit Windows: Version 19.0.0.0.0 - Production
시작 날짜                 13-7월 -2021 17:21:03
업타임                   217 시간. 7 분. 25트레이스 수준            off
보안                     ON: Local OS Authentication
SNMP                     OFF리스너 매개변수 파일   C:\ORACLE\WINDOWS.X64_193000_db_home\network\admin\listener.ora
리스너 로그 파일         C:\ORACLE\diag\tnslsnr\DESKTOP-F7LRGM5\listener\alert\log.xml
끝점 요약 청취 중...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT=1521)))
  (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(PIPENAME=\\.\pipe\EXTPROC1521ipc)))
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=DESKTOP-F7LRGM5)(PORT=5500))(Security=(my_wallet_directory=C:\ORACLE\admin\orcl\xdb_wallet))(Presentation=HTTP)(Session=RAW))
서비스 요약...
"CLRExtProc" 서비스는 1개의 인스턴스를 가집니다.
  "CLRExtProc" 인스턴스(UNKNOWN 상태)는 이 서비스에 대해 1 처리기를 가집니다.
"orcl" 서비스는 1개의 인스턴스를 가집니다.
  "orcl" 인스턴스(READY 상태)는 이 서비스에 대해 1 처리기를 가집니다.
"orclXDB" 서비스는 1개의 인스턴스를 가집니다.
  "orcl" 인스턴스(READY 상태)는 이 서비스에 대해 1 처리기를 가집니다.
명령이 성공적으로 수행되었습니다
  • (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))에 연결되었습니다

3. 연동코드 작성

  • 필수 정보를 확인한다.
    • IP주소: localhost
    • Port번호: 1521
    • 서비스이름: orcl
  • JupyterLab을 연 후 코드를 작성한다. (관리자 살행)
    • 가상환경 python_oracle 으로 연결이 되어 있는지 확인한다.

In ML, Data Leakage - 2

머신러닝 전처리 자주하는 안 좋은 습관들 모음

Sample 데이터

  • 먼저 가상의 데이터를 하나 생성합니다.
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split

random_state = 42
X, y = make_regression(random_state = random_state, n_features = 1, noise = 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.4, random_state = random_state)

Inconsistent preprocessing

  • 모델을 학습시킬 때 이러한 데이터 변환을 사용하는 경우 테스트 데이터든 프로덕션 시스템의 데이터든 후속 데이터셋에도 사용해야 합니다. 그렇지 않으면 피쳐 공간이 변경되고 모델이 효과적으로 수행되지 않습니다.

Wrong

  • 먼저, 잘못된 방식을 소개합니다.
  • train 데이터에는 scaler가 적용되었지만, 테스트 데이터에는 적용되지 않았습니다. 따라서, 이러한 경우 모델 성능이 예상보다 떨어질 수 있습니다.
from sklearn.metrics import mean_squared_error
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_train_transformed = scaler.fit_transform(X_train)
model = LinearRegression().fit(X_train_transformed, y_train)
mean_squared_error(y_test, model.predict(X_test))
62.80867119249524
  • Non-Transformed X_test 데이터를 예측값으로 넣으려면 X_train과 마찬가지로 동일하게 transform을 적용해야 한다.
scaler = StandardScaler()
X_train_transformed = scaler.fit_transform(X_train)
X_test_transformed = scaler.transform(X_test)
model = LinearRegression().fit(X_train_transformed, y_train)
mean_squared_error(y_test, model.predict(X_test_transformed))
0.9027975466369481
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

random_state = 42
X, y = make_regression(random_state = random_state, n_features = 1, noise = 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.4, random_state = random_state)

model = make_pipeline(StandardScaler(), LinearRegression())
model.fit(X_train, y_train)
mean_squared_error(y_test, model.predict(X_test))
0.9027975466369481

Data Leakage

  • 모델을 구축할 때 예측 시점에 사용할 수 없는 정보가 사용될 때 데이터 누출이 발생함.
  • 이로 인해 교차 검증 시, 매우 낙관적인 성능 추정치가 발생하기도 하며, 실제 새로운 데이터와 만날 때는 성능이 매우 크게 저하 되기도 함.
  • 훈련 및 테스트 데이터 하위 집합 모두 이전 섹션에서 설명한 것과 동일한 전처리 변환을 받아야 하지만,
  • 이러한 변환은 훈련 데이터에서만 학습되는 것이 중요하다. 예를 들어 평균값으로 나누는 정규화 단계가 있는 경우 평균은 모든 데이터의 평균이 아니라 훈련 데이터 하위 집합의 평균이어야 합니다. 테스트 하위 집합이 평균 계산에 포함되는 경우 테스트 하위 집합의 정보가 모델에 영향을 줍니다.
  • Data Leakage가 발생하는 몇가지 상황을 살펴본다.

Data Leakage During Pre-Processing

  • 우선 가상의 데이터를 만듭니다.
import numpy as np 
n_samples, n_features, n_classes = 200, 100000, 2
rng = np.random.RandomState(42)
X = rng.standard_normal((n_samples, n_features))
y = rng.choice(n_classes, n_samples)

print(X.shape, y.shape)
(200, 100000) (200,)

Wrong

  • 이제 transformation부터 머신러닝 학습, 그리고 평가까지 진행한다.
from sklearn.model_selection import train_test_split
from sklearn.feature_selection import SelectKBest
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import accuracy_score

# 여기 부분이 잘못 되었다. 
X_selected = SelectKBest(k=25).fit_transform(X, y)

X_train, X_test, y_train, y_test = train_test_split(X_selected, y, random_state=42)
gbc = GradientBoostingClassifier(random_state=1)
gbc.fit(X_train, y_train)

y_pred = gbc.predict(X_test)
accuracy_score(y_test, y_pred)
0.72

Right

  • Data Leakage를 예방하기 위해 먼저 train 데이터와 test 데이터를 분리한다.
  • fit이나 fit_transform은 train 데이터에만 적용한다.
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 42)
select = SelectKBest(k=25)

X_train_selected = select.fit_transform(X_train, y_train)
gbc = GradientBoostingClassifier(random_state = 1)
gbc.fit(X_train_selected, y_train)
GradientBoostingClassifier(ccp_alpha=0.0, criterion='friedman_mse', init=None,
                           learning_rate=0.1, loss='deviance', max_depth=3,
                           max_features=None, max_leaf_nodes=None,
                           min_impurity_decrease=0.0, min_impurity_split=None,
                           min_samples_leaf=1, min_samples_split=2,
                           min_weight_fraction_leaf=0.0, n_estimators=100,
                           n_iter_no_change=None, presort='deprecated',
                           random_state=1, subsample=1.0, tol=0.0001,
                           validation_fraction=0.1, verbose=0,
                           warm_start=False)
X_test_selected = select.transform(X_test)
y_pred = gbc.predict(X_test_selected)
accuracy_score(y_test, y_pred)
0.5
  • 이번에는 Pipeline을 통해 구성하도록 한다.
from sklearn.pipeline import make_pipeline

X_train, X_test, y_train, y_test = train_test_split(
    X, y, random_state=42)

pipeline = make_pipeline(SelectKBest(k=25), 
                         GradientBoostingClassifier(random_state=1))

pipeline.fit(X_train, y_train)
Pipeline(memory=None,
         steps=[('selectkbest',
                 SelectKBest(k=25,
                             score_func=<function f_classif at 0x7f56a7c7f5f0>)),
                ('gradientboostingclassifier',
                 GradientBoostingClassifier(ccp_alpha=0.0,
                                            criterion='friedman_mse', init=None,
                                            learning_rate=0.1, loss='deviance',
                                            max_depth=3, max_features=None,
                                            max_leaf_nodes=None,
                                            min_impurity_decrease=0.0,
                                            min_impurity_split=None,
                                            min_samples_leaf=1,
                                            min_samples_split=2,
                                            min_weight_fraction_leaf=0.0,
                                            n_estimators=100,
                                            n_iter_no_change=None,
                                            presort='deprecated',
                                            random_state=1, subsample=1.0,
                                            tol=0.0001, validation_fraction=0.1,
                                            verbose=0, warm_start=False))],
         verbose=False)
  • 테스트 데이터는 예측을 할 때만 사용하도록 한다.
y_pred = pipeline.predict(X_test)
accuracy_score(y_test, y_pred)
0.5
  • Pipeline은 cross_val_score에 직접 사용할 수도 있다.
from sklearn.model_selection import cross_val_score
scores = cross_val_score(pipeline, X, y)
print(f"Mean accuracy: {scores.mean():.2f}+/-{scores.std():.2f}")
Mean accuracy: 0.52+/-0.04

In ML, Data Leakage - 1

Data Leakage

  • 모형 평가를 하기 전에 전체 데이터셋을 가공 및 변환함.
  • 이를 평가에 반영하면 새로운 데이터를 예측할 때 부정확한 결과를 도출 할 수 있음.
  • 이를 방지 하기 위해서는 training 데이터만 데이터 전처리를 수행하는 것이 바람직함.
  • Data Leakage를 피하기 위해서는 scikit-learn modeling pipeline을 설계해햐 함.

데이터 준비

  • 가상의 데이터를 준비한다.
  • 데이터는 모두 수치형 데이터로 준비했다.
from sklearn.datasets import make_classification
X, y = make_classification(n_samples = 1000, n_features = 20, n_informative = 15, n_redundant = 5, random_state = 7)

# summarize the dataset
print(X.shape, y.shape)
(1000, 20) (1000,)

일반적인 방법의 데이터 전처리

  • 수치형 데이터이기 때문에, MinMaxScaler 클래스를 활용한다.
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
X = scaler.fit_transform(X)
  • 이번에는 데이터셋을 분리하도록 한다.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state = 7)
  • 이번에는 로지스틱 회귀분석을 시행한다.
from sklearn.linear_model import LogisticRegression

model = LogisticRegression()
model.fit(X_train, y_train)
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=100,
                   multi_class='auto', n_jobs=None, penalty='l2',
                   random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False)
  • 이제 모형을 평가하도록 합니다.
from sklearn.metrics import accuracy_score

yhat = model.predict(X_test)
accuracy = accuracy_score(y_test, yhat)

print('Accuracy: %.3f' % (accuracy * 100))
Accuracy: 84.848
  • 이것이 일반적인 방법론이다. 그러나 엄밀히 말하면 Data Leakage 현상이 나타났다고 볼 수 있다.

Data Leakage 피하는 방법

  • 이번에는 먼저 데이터 셋을 분리하도록 합니다.
  • Train 데이터에만 scaler를 적용하도록 합니다.
from sklearn.utils.validation import check_random_state

X, y = make_classification(n_samples = 2000, n_features = 20, n_informative = 15, n_redundant = 5, random_state = 7)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state = 1)
  • 그 후, X_train과 X_test에만 scaler를 적용한다.
scaler = MinMaxScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
  • 이제 모형을 만든 후, 결괏값을 적용합니다.
model = LogisticRegression()
model.fit(X_train, y_train)
yhat = model.predict(X_test)
accuracy = accuracy_score(y_test, yhat)

print('Accuracy: %.3f' % (accuracy * 100))
Accuracy: 88.333
  • 결과를 보면 알겠지만, 모형의 성능에도 좋지 않음을 올 수 있다.

Reference

Jason Brownlee. 2020. How to Avoid Data Leakage When Performing Data Preparation. Retrieved from https://machinelearningmastery.com/data-preparation-without-data-leakage/

엑셀 데이터 가공하기 변환

강의 홍보

개요

  • 정리되지 못한 엑셀 파일을 불러와서 하나의 테이블을 만드는 과정을 진행해본다.

  • 위 데이터를 원본 그대로 받아서 pandas 데이터 프레임에 추가한다.
  • A3 셀에 있는 [시·도지사선거][서울특별시][강남구] 분리하여 각 column에 추가한다.

라이브러리 불러오기

  • 3개의 라이브러리를 불러온다.
import pandas as pd
import openpyxl
import os

파일 확인

  • data 폴더 내 데이터를 확인한다.
  • 추후, 엑셀 데이터만 추려서 반복문을 활용하여 동일하게 처리할 수 있도록 상상을 한다.
print(os.listdir('data'))
['1 강남구-[2021년_재·보궐선거]_개표단위별_개표결과.xlsx', '.DS_Store', '~$1 강남구-[2021년_재·보궐선거]_개표단위별_개표결과.xlsx']

openpyxl 라이브러리 할용

  • openpyxl 라이브러리는 A Python library to read/write Excel 2010 xlsx/xlsm files을 가지고 있다.
  • 먼저 A3 셀에 있는 [시·도지사선거][서울특별시][강남구] 데이터를 가져오도록 한다.
  • 이 때, openpyxl 라이브리를 활용하면 각 셀에 접근해서 개별적으로 데이터를 가져올 수 있다.
DATA_PATH = "data"
FILE_PATH = os.listdir(DATA_PATH)[0]
wb_obt= openpyxl.load_workbook("data/" + FILE_PATH) 
sheet = wb_obt.active
values = sheet["A3"].value
values
'[시·도지사선거][서울특별시][강남구]'

문자열 전처리

  • 먼저 하나의 셀로 연결되어 있는 것을 각각 분리하도록 하는 코드를 작성한다.
  • strip(‘pattern’)는 특정 문자를 제거하는 것이고, split(‘pattern’)는 문자열을 특수문자로 분리하는 것이다.
city_list = values.strip("[]").split("][")
city_list
['시·도지사선거', '서울특별시', '강남구']

데이터 수집 및 전처리

  • 이 때 중요한 parameters는 skiprows, header이다.
  • 먼저 skiprows는 특정 행은 건너 뛴다는 의미를 가지고 있다. 즉, 데이터프레임에 접근하기 전까지의 행은 건너 뛴다는 의미다.
  • header는 엑셀의 열에 해당하는데, 본 데이터에서는 multiple headers가 있다. 따라서, 이를 리스트 처리하면 해당 열은 모두 가져올 수 있다.
df = pd.read_excel("data/1 강남구-[2021년_재·보궐선거]_개표단위별_개표결과.xlsx", skiprows=3,  header=[0, 1], )
df.drop(columns=df.columns[-1], axis=1, inplace=True) # 마지막 column은 NAN라 삭제.  
print(df.head())
                읍면동명               투표구명               선거인수                투표수  \
  Unnamed: 0_level_1 Unnamed: 1_level_1 Unnamed: 2_level_1 Unnamed: 3_level_1   
0                 합계                              452344.0           276485.0   
1               거소투표                                3062.0             2877.0   
2             관외사전투표                               12957.0            12955.0   
3                신사동                 소계            13922.0             8672.0   
4                                관내사전투표             2667.0             2667.0   

     후보자별 득표수                                                               \
  더불어민주당\n박영선 국민의힘\n오세훈 기본소득당\n신지혜 국가혁명당\n허경영 미래당\n오태양 민생당\n이수봉 민생당\n이수봉.1   
0     66907.0  202320.0      909.0     2005.0    229.0    424.0        NaN   
1       595.0    1995.0       14.0       97.0     11.0     24.0        NaN   
2      4393.0    8171.0       48.0      100.0     18.0     18.0        NaN   
3      1581.0    6910.0       20.0       45.0      6.0     15.0        NaN   
4       632.0    1977.0        6.0       12.0      3.0      6.0        NaN   

                                                                        \
  신자유민주연합\n배영규 여성의당\n김진아 진보당\n송명숙 무소속\n정동희 무소속\n이도엽 무소속\n신지예         계   
0         20.0    1212.0    274.0     82.0     47.0    655.0  275084.0   
1          3.0       2.0      6.0      8.0      2.0      8.0    2765.0   
2          0.0      67.0     23.0      6.0      2.0     39.0   12885.0   
3          1.0      36.0      5.0      3.0      1.0     20.0    8643.0   
4          0.0       8.0      3.0      1.0      1.0      5.0    2654.0   

              무효\n투표수                 기권수  
  Unnamed: 18_level_1 Unnamed: 19_level_1  
0              1401.0            175859.0  
1               112.0               185.0  
2                70.0                 2.0  
3                29.0              5250.0  
4                13.0                 0.0  
  • header[0]값은 읍면동명, 투표구명, 선거인수, 투표수 등으로 정리가 되어 있다.
  • header[1]값은 각 후보들의 값이 나타난 것을 확인할 수 있다.
  • 여기에서 후보자별 득표수만 지우기만 하면 된다. 다만, 각각 가져와야 하는 값이 서로 다르다.
    • 이 때, MultiIndex에 대응하기 위해 get_level_values() 함수를 사용한다.
df.columns
MultiIndex([(    '읍면동명',  'Unnamed: 0_level_1'),
            (    '투표구명',  'Unnamed: 1_level_1'),
            (    '선거인수',  'Unnamed: 2_level_1'),
            (     '투표수',  'Unnamed: 3_level_1'),
            ('후보자별 득표수',         '더불어민주당\n박영선'),
            ('후보자별 득표수',           '국민의힘\n오세훈'),
            ('후보자별 득표수',          '기본소득당\n신지혜'),
            ('후보자별 득표수',          '국가혁명당\n허경영'),
            ('후보자별 득표수',            '미래당\n오태양'),
            ('후보자별 득표수',            '민생당\n이수봉'),
            ('후보자별 득표수',          '민생당\n이수봉.1'),
            ('후보자별 득표수',        '신자유민주연합\n배영규'),
            ('후보자별 득표수',           '여성의당\n김진아'),
            ('후보자별 득표수',            '진보당\n송명숙'),
            ('후보자별 득표수',            '무소속\n정동희'),
            ('후보자별 득표수',            '무소속\n이도엽'),
            ('후보자별 득표수',            '무소속\n신지예'),
            ('후보자별 득표수',                   '계'),
            ( '무효\n투표수', 'Unnamed: 18_level_1'),
            (     '기권수', 'Unnamed: 19_level_1')],
           )
df.columns.get_level_values(0)
Index(['읍면동명', '투표구명', '선거인수', '투표수', '후보자별 득표수', '후보자별 득표수', '후보자별 득표수',
       '후보자별 득표수', '후보자별 득표수', '후보자별 득표수', '후보자별 득표수', '후보자별 득표수', '후보자별 득표수',
       '후보자별 득표수', '후보자별 득표수', '후보자별 득표수', '후보자별 득표수', '후보자별 득표수', '무효\n투표수',
       '기권수'],
      dtype='object')
df.columns.get_level_values(1)
Index(['Unnamed: 0_level_1', 'Unnamed: 1_level_1', 'Unnamed: 2_level_1',
       'Unnamed: 3_level_1', '더불어민주당\n박영선', '국민의힘\n오세훈', '기본소득당\n신지혜',
       '국가혁명당\n허경영', '미래당\n오태양', '민생당\n이수봉', '민생당\n이수봉.1', '신자유민주연합\n배영규',
       '여성의당\n김진아', '진보당\n송명숙', '무소속\n정동희', '무소속\n이도엽', '무소속\n신지예', '계',
       'Unnamed: 18_level_1', 'Unnamed: 19_level_1'],
      dtype='object')
  • 이제, 각각의 index를 list로 변환후 하나의 column으로 합치는 과정을 진행한다.
  • 총 20개의 column이 정렬된 것을 확인할 수 있다.
col_1 = df.columns.get_level_values(0)[0:4].tolist()
col_2 = df.columns.get_level_values(1)[4:-2].tolist()
col_3 = df.columns.get_level_values(0)[-3:-1].tolist()
cols = col_1 + col_2 + col_3
cols
['읍면동명',
 '투표구명',
 '선거인수',
 '투표수',
 '더불어민주당\n박영선',
 '국민의힘\n오세훈',
 '기본소득당\n신지혜',
 '국가혁명당\n허경영',
 '미래당\n오태양',
 '민생당\n이수봉',
 '민생당\n이수봉.1',
 '신자유민주연합\n배영규',
 '여성의당\n김진아',
 '진보당\n송명숙',
 '무소속\n정동희',
 '무소속\n이도엽',
 '무소속\n신지예',
 '계',
 '후보자별 득표수',
 '무효\n투표수']
df.columns = cols
df.columns
Index(['읍면동명', '투표구명', '선거인수', '투표수', '더불어민주당\n박영선', '국민의힘\n오세훈', '기본소득당\n신지혜',
       '국가혁명당\n허경영', '미래당\n오태양', '민생당\n이수봉', '민생당\n이수봉.1', '신자유민주연합\n배영규',
       '여성의당\n김진아', '진보당\n송명숙', '무소속\n정동희', '무소속\n이도엽', '무소속\n신지예', '계',
       '후보자별 득표수', '무효\n투표수'],
      dtype='object')
  • 이제 시도와 시군구를 각각 추가한다.
df['시도'] = city_list[1]
df['시군구'] = city_list[2]
print(df.head(10))
     읍면동명    투표구명      선거인수       투표수  더불어민주당\n박영선  국민의힘\n오세훈  기본소득당\n신지혜  \
0      합계          452344.0  276485.0      66907.0   202320.0       909.0   
1    거소투표            3062.0    2877.0        595.0     1995.0        14.0   
2  관외사전투표           12957.0   12955.0       4393.0     8171.0        48.0   
3     신사동      소계   13922.0    8672.0       1581.0     6910.0        20.0   
4          관내사전투표    2667.0    2667.0        632.0     1977.0         6.0   
5          신사동제1투    2313.0    1052.0        205.0      827.0         1.0   
6          신사동제2투    1740.0     733.0        220.0      492.0         3.0   
7          신사동제3투    1896.0     813.0        261.0      508.0         6.0   
8          신사동제4투    2812.0    1736.0        112.0     1611.0         0.0   
9          신사동제5투    2494.0    1671.0        151.0     1495.0         4.0   

   국가혁명당\n허경영  미래당\n오태양  민생당\n이수봉  ...  여성의당\n김진아  진보당\n송명숙  무소속\n정동희  \
0      2005.0     229.0     424.0  ...     1212.0     274.0      82.0   
1        97.0      11.0      24.0  ...        2.0       6.0       8.0   
2       100.0      18.0      18.0  ...       67.0      23.0       6.0   
3        45.0       6.0      15.0  ...       36.0       5.0       3.0   
4        12.0       3.0       6.0  ...        8.0       3.0       1.0   
5         8.0       1.0       1.0  ...        4.0       0.0       1.0   
6        10.0       0.0       2.0  ...        5.0       0.0       0.0   
7         8.0       0.0       3.0  ...       14.0       2.0       0.0   
8         2.0       1.0       2.0  ...        2.0       0.0       1.0   
9         5.0       1.0       1.0  ...        3.0       0.0       0.0   

   무소속\n이도엽  무소속\n신지예         계  후보자별 득표수   무효\n투표수     시도  시군구  
0      47.0     655.0  275084.0    1401.0  175859.0  서울특별시  강남구  
1       2.0       8.0    2765.0     112.0     185.0  서울특별시  강남구  
2       2.0      39.0   12885.0      70.0       2.0  서울특별시  강남구  
3       1.0      20.0    8643.0      29.0    5250.0  서울특별시  강남구  
4       1.0       5.0    2654.0      13.0       0.0  서울특별시  강남구  
5       0.0       2.0    1050.0       2.0    1261.0  서울특별시  강남구  
6       0.0       1.0     733.0       0.0    1007.0  서울특별시  강남구  
7       0.0       6.0     809.0       4.0    1083.0  서울특별시  강남구  
8       0.0       1.0    1732.0       4.0    1076.0  서울특별시  강남구  
9       0.0       5.0    1665.0       6.0     823.0  서울특별시  강남구  

[10 rows x 22 columns]
  • 어느정도 정리가 된 것으로 보인다.

AirFlow ch01. 개요

인프런 강의

공지

  • Airflow 2.0 원서 나온 것을 공부용으로 활용합니다.

Airflow Project

Airflow%20Project%20ad0ddb927b43444a9837279ad7ea27fe/book_cover.png

  • 이 책에 나온 내용을 Chapter별로 요약하여 정리하려고 한다.
  • 원서 구매 페이지는 아래와 같다.
  • 구매 페이지: Data Pipelines with Apache Airflow

Chapter 1. Apache Airflow Introduction

Airflow%20Project%20ad0ddb927b43444a9837279ad7ea27fe/figure_1-1.png