페이지

2019년 12월 22일 일요일

MATLAB Rutime 설치하기

MATLAB Rutime 설치하기

미설치시 에러

MATLAB Runtime을 설치하지 않은 환경에서 MATLAB 응용프로그램이나 공유 라이브러리를 사용하려고 하면 아래와 같은 에러 메시지가 표시될 것입니다.

처리되지 않은 예외: System.TypeInitializationException: 'MathWorks.MATLAB.NET.Utility.MWMCR'의 형식 이니셜라이저에서 예 외를 Throw했습니다. ---> System.TypeInitializationException: 'MathWorks.MATLAB.NET.Arrays.MWArray'의 형식 이니셜라이저에서 예외를 Throw했습니다. ---> System.DllNotFoundException: DLL 'mclmcrrt9_3.dll'을(를) 로드할 수 없습니다. 지정된 모듈을 찾을 수 없습니다. (예외가 발생한 HRESULT: 0x8007007E)
   위치: MathWorks.MATLAB.NET.Arrays.MWArray.mclmcrInitialize2(Int32 primaryMode)
   위치: MathWorks.MATLAB.NET.Arrays.MWArray..cctor()
   --- 내부 예외 스택 추적의 끝 ---
   위치: MathWorks.MATLAB.NET.Utility.MWMCR..cctor()
   --- 내부 예외 스택 추적의 끝 ---
   위치: MathWorks.MATLAB.NET.Utility.MWMCR.processExiting(Exception exception)

해결 방법

이 문제를 해결하기 위해서는 MATLAB Runtime을 설치해야 합니다. 여러 가지 방법으로 MATLAB Runtime을 설치할 수 있습니다.

  1. MATLAB이 설치되어 있는 경우에는 MATLAB 설치 폴더 아래에 있는 MATLAB Runtime 설치 프로그램을 실행하여 설치합니다.

    예) C:\Program Files\MATLAB\R2017b\toolbox\compiler\deploy\win64\MCRInstaller.exe

  2. MATLAB이 설치되어 있지 않는 경우에는 아래 링크에서 설치 프로그램을 직접 다운로드하여 MATLAB Runtime을 설치할 수 있습니다.

    MATLAB Runtime

  3. 또는 응용프로그램을 패키징하고 그것의 설치 프로그램을 통해서 MATLAB Runtime을 웹으로부터 다운로드하여 설치할 수 있습니다.

MATLAB 응용프로그램 개발자가 아닌 경우에는 위의 두 번째 방법을 추천합니다.

설치 과정

MATLAB Runtime은 무료로 설치할 수 있는 프로그램입니다. 이 문서에서는 웹에서 직접 설치 프로그램을 다운로드하여 설치하는 방법을 소개합니다.

  1. MATLAB Runtime 사이트에서 원하는 설치 파일을 다운로드합니다. 여기서는 R2017b (9.3) 버전을 다운로드하여 설치를 진행합니다.

    MCR_R2017b_win64_installer.exe

  2. 설치 파일을 실행합니다.

  3. MATLAB Runtime 인스톨러 대화상자에서 다음 버튼을 클릭합니다.

  4. 라이선스 계약 대화상자에서 를 선택하고 다음 버튼을 클릭합니다.

  5. 폴더 선택 대화상자에서 설치 폴더를 선택하고 다음 버튼을 클릭합니다.

    C:\Program Files\MATLAB\MATLAB Runtime

  6. 확인 대화상자에서 설치 정보를 확인하고 설치 버튼을 클릭합니다.

    • 설치 폴더: C:\Program Files\MATLAB\MATLAB Runtime
    • 설치 크기: 2,701MB
    • 제품: MATLAB Runtime 9.3
  7. 설치가 끝나면 마침 버튼을 클릭합니다.

참고 문서

  1. MATLAB Runtime
  2. Install and Configure the MATLAB Runtime
  3. Deploy Components to End Users

Written with StackEdit.

Intel MKL 예제를 Microsoft Visual C++로 빌드하기

Intel MKL 예제를 Microsoft Visual C++로 빌드하기

인텔 기반 시스템에서 아래의 영역에 해당하는 수학 계산을 빠르게 수행하고자 한다면 Intel MKL 라이브러리를 사용할 수 있습니다.

  • Linear Algebra
  • Fast Fourier Transforms (FFT)
  • Vector Statistics & Data Fitting
  • Vector Math & Miscellaneous Solvers

이 문서는 Intel MKL이 제공하는 예제 파일을 Microsoft Visual C++ 로 컴파일하고 링크하여 실행 파일을 만드는 과정을 소개합니다.

빌드 환경

다음은 이 문서를 작성하는 과정에서 Intel MKL 예제를 빌드하기 위하여 사용한 환경입니다.

시스템

항목 제품
운영체제 Windows 10 (64비트)
프로세서 Intel Core i7

설치 제품

항목 제품
IDE Microsoft Visual Studio Community 2019 (version 16)
라이브러리 Intel Math Kernel Library 2019 Update 5

환경 변수

  1. 명령 프롬프트 창을 엽니다.

  2. 아래 스크립트를 실행하여 환경 변수INCLUDE, LIB, 그리고 PATH를 설정합니다.

    @echo off
    
    set CPRO_PATH=C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows
    set MKLROOT=%CPRO_PATH%\mkl
    set REDIST=%CPRO_PATH%\redist
    
    set INCLUDE=%MKLROOT%\include;%INCLUDE%
    
    set LIB=%MKLROOT%\lib\intel64;%LIB%
    set PATH=%REDIST%\intel64\mkl;%PATH%
    
    REM for OpenMP intel thread
    set LIB=%CPRO_PATH%\compiler\lib\intel64;%LIB%
    set PATH=%REDIST%\intel64\compiler;%PATH%
    
    REM for TBB thread
    set LIB=%CPRO_PATH%\tbb\lib\intel64\vc_mt;%LIB%
    set PATH=%REDIST%\intel64\tbb\vc_mt;%PATH%
    
    call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
    
  3. 아래 스크립트를 실행하여 Visual C++ 사용 환경을 만듭니다. 64-bit로 빌드하기 위하여 vcvarsall.bat의 인자로 amd64를 지정합니다. 인자를 지정하지 않으면 32-bit로 빌드할 것입니다.

    @echo off
    
    call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
    

빌드 과정

예제-1. ex_nslqp_c

  1. 아래 위치에 있는 예제 압축 파일을 작업하고자 하는 폴더 <working folder>에 풉니다.

    C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl\examples\examples_core_c.zip

  2. 명령 프롬프트 창에서 아래의 폴더로 이동합니다.

    <_working folder_>\solverc

  3. 아래 스크립트를 실행하여 ex_nlsqp_c 예제를 빌드합니다.

    @echo off
    
    nmake dllintel64 threading=parallel compiler=msvs function="ex_nlsqp_c"
    nmake libintel64 threading=parallel compiler=msvs function="ex_nlsqp_c"
    nmake dllintel64 threading=sequential compiler=msvs function="ex_nlsqp_c"
    nmake libintel64 threading=sequential compiler=msvs function="ex_nlsqp_c"
    
  4. 아래 폴더에서 빌드 결과로 ex_nlsqp_c.exe 파일이 생성되었는지 확인합니다.

    _results\msvs_lp64_parallel_intel64
    _results\msvs_lp64_parallel_intel64_dll
    _results\msvs_lp64_sequential_intel64
    _results\msvs_lp64_sequential_intel64_dll
    
  5. ex_nlsqp_c.exe 파일을 실행하면 아래와 같은 메시지가 표시됩니다.

    >_results\msvs_lp64_sequential_intel64_dll\ex_nlsqp_c.exe
    |         dtrnlsp Powell............PASS
    

예제-2. matrix_multiplication

  1. 아래 사이트를 방문하여 예제 압축 파일을 다운로드하고 작업하고자 하는 폴더 <working folder>에 풉니다.

  1. 명령 프롬프트 창에서 아래의 폴더로 이동합니다.

    <_working folder_>\mkl\mkl_c_samples\matrix_multiplication\src

FOR 루프 계산

  1. 아래 스크립트를 실행하여 matrix_multiplication 예제를 빌드합니다.

    @echo off
    
    set THREADING=sequential
    set DLL_SUFF=
    set EXAMPLE=matrix_multiplication
    set OMP_LIB=
    cl.exe /c %EXAMPLE%.c
    link.exe mkl_intel_lp64%DLL_SUFF%.lib mkl_core%DLL_SUFF%.lib mkl_%THREADING%%DLL_SUFF%.lib %OMP_LIB% %EXAMPLE%.obj
    
  2. 현재 폴더에 matrix_multiplication.exe 파일이 생성되었는지 확인합니다.

  3. matrix_multiplication.exe 파일을 실행하면 아래와 같은 메시지가 표시됩니다.

    >matrix_multiplication.exe
     
     This example measures performance of rcomputing the real matrix product
     C=alpha*A*B+beta*C using a triple nested loop, where A, B, and C are
     matrices and alpha and beta are double precision scalars
    
     Initializing data for matrix multiplication C=A*B for matrix
     A(2000x200) and matrix B(200x1000)
    
     Allocating memory for matrices aligned on 64-byte boundary for better
     performance
    
     Intializing matrix data
    
     Making the first run of matrix product using triple nested loop
     to get stable run time measurements
    
     Measuring performance of matrix product using triple nested loop
    
     == Matrix multiplication using triple nested loop completed ==
     == at 1219.72656 milliseconds ==
    
     Deallocating memory
    
     Example completed.
    

    계산 소요 시간:

    • 1219.72656 milliseconds

CBLAS API + 멀티쓰레드 계산

  1. 아래 스크립트를 실행하여 dgemm_threading_effect_example 예제를 빌드합니다.

    @echo off
    
    set THREADING=intel_thread
    set DLL_SUFF=
    set EXAMPLE=dgemm_threading_effect_example
    set OMP_LIB=libiomp5md.lib
    cl.exe /c %EXAMPLE%.c
    link.exe mkl_intel_lp64%DLL_SUFF%.lib mkl_core%DLL_SUFF%.lib mkl_%THREADING%%DLL_SUFF%.lib %OMP_LIB% %EXAMPLE%.obj
    
  2. 현재 폴더에 dgemm_threading_effect_example.exe 파일이 생성되었는지 확인합니다.

  3. dgemm_threading_effect_example.exe 파일을 실행하면 아래와 같은 메시지가 표시됩니다.

    >dgemm_threading_effect_example.exe
    
     This example demonstrates threading impact on computing real matrix product
     C=alpha*A*B+beta*C using Intel(R) MKL function dgemm, where A, B, and C are
     matrices and alpha and beta are double precision scalars
    
     Initializing data for matrix multiplication C=A*B for matrix
     A(2000x200) and matrix B(200x1000)
    
     Allocating memory for matrices aligned on 64-byte boundary for better
     performance
    
     Intializing matrix data
    
     Finding max number of threads Intel(R) MKL can use for parallel runs
    
     Running Intel(R) MKL from 1 to 4 threads
    
     Requesting Intel(R) MKL to use 1 thread(s)
    
     Making the first run of matrix product using Intel(R) MKL dgemm function
     via CBLAS interface to get stable run time measurements
    
     Measuring performance of matrix product using Intel(R) MKL dgemm function
     via CBLAS interface on 1 thread(s)
    
     == Matrix multiplication using Intel(R) MKL dgemm completed ==
     == at 17.27277 milliseconds using 1 thread(s) ==
    
     Requesting Intel(R) MKL to use 2 thread(s)
    
     Making the first run of matrix product using Intel(R) MKL dgemm function
     via CBLAS interface to get stable run time measurements
    
     Measuring performance of matrix product using Intel(R) MKL dgemm function
     via CBLAS interface on 2 thread(s)
    
     == Matrix multiplication using Intel(R) MKL dgemm completed ==
     == at 9.67240 milliseconds using 2 thread(s) ==
    
     Requesting Intel(R) MKL to use 3 thread(s)
    
     Making the first run of matrix product using Intel(R) MKL dgemm function
     via CBLAS interface to get stable run time measurements
    
     Measuring performance of matrix product using Intel(R) MKL dgemm function
     via CBLAS interface on 3 thread(s)
    
     == Matrix multiplication using Intel(R) MKL dgemm completed ==
     == at 8.70466 milliseconds using 3 thread(s) ==
    
     Requesting Intel(R) MKL to use 4 thread(s)
    
     Making the first run of matrix product using Intel(R) MKL dgemm function
     via CBLAS interface to get stable run time measurements
    
     Measuring performance of matrix product using Intel(R) MKL dgemm function
     via CBLAS interface on 4 thread(s)
    
     == Matrix multiplication using Intel(R) MKL dgemm completed ==
     == at 7.69962 milliseconds using 4 thread(s) ==
    
     Deallocating memory
    
     It is highly recommended to define LOOP_COUNT for this example on your
     computer as 130 to have total execution time about 1 second for reliability
     of measurements
    
     Example completed.
    

    계산 소요 시간:

    • 17.27277 milliseconds using 1 thread(s)
    • 9.67240 milliseconds using 2 thread(s)
    • 8.70466 milliseconds using 3 thread(s)
    • 7.69962 milliseconds using 4 thread(s)

정리

계산 소요 시간 비교

행렬곱을 계산하기 위하여 Intel MKL이 제공하는 CBLAS API와 멀티쓰레드를 사용하면 FOR 루프를 사용할 때에 비하여 소요 시간이 1/158 수준으로 단축됨을 알 수 있었습니다.

  • FOR 루프 계산: 1219.72656 milliseconds
  • CBLAS API + 멀티쓰레드 계산: 7.69962 milliseconds using 4 thread(s)

스크립트 및 소스 파일

본문에서 소개한 환경 설정 스크립트, 빌드 스크립트, 그리고 행결곱 계산 소스 파일을 GitHub에 올려 놓았습니다.

참고 문서

  1. Developer Guide for Intel® Math Kernel Library for Windows
  2. Using Intel® Math Kernel Library for Matrix Multiplication - C
  3. Intel® Math Kernel Library Link Line Advisor
  4. Compiling and Linking Intel® Math Kernel Library with Microsoft* Visual C++*

Written with StackEdit.

2019년 12월 16일 월요일

Electron 기반 데스크톱 앱 개발

Electron 기반 데스크톱 앱 개발

Electron은 HTML, CSS, 그리고 JavaScript를 사용하여 크로스 플랫폼 데스크톱 앱을 개발할 수 있도록 합니다. 지원하는 플랫폼은 다음과 같습니다.

  • Windows
  • macOS
  • Linux

Windows Forms를 사용하여 GUI 데스크톱 앱을 개발하는 것과 비교하여 많은 장점을 가지고 있습니다. 그 중에서 몇 가지만 나열하자면 아래와 같습니다.

  • 크로스 플랫폼 - 이제 맥북 사용자에게 "당신은 이 앱을 사용할 수 없습니다"라고 말하지 않아도 됩니다.
  • 데이터 시각화 - 다양한 차트 라이브러리를 사용하여 뛰어난 시각화 기능을 빠르고 쉽게 구현할 수 있습니다.
  • 현대적 느낌의 테마 - 큰 글자, 대담한 여백, 고급스러워 보이는 색상 등 웹에서 경험할 수 있는 테마를 데스크톱 앱에서도 제공할 수 있습니다.
  • 글자 확대/축소 - 노안이 찾아 오는 40대 중후반 이후 연령대의 사용자에게는 매우 고마운 기능입니다.
  • 반응형 UI 디자인 - 작은 화면의 컴퓨터를 사용하는 사용자에게 유용할 것입니다.
  • 편리한 디버깅 - 구글 크롬 브라우져의 개발자 도구와 같은 방식으로 디버깅을 할 수 있습니다.

이 문서에서는 아래의 내용을 다루며 설명은 Windows 시스템을 기준으로 진행합니다.

  1. 개발 환경 준비
  2. 간단한 앱 작성 및 실행
  3. 패키징
  4. 디버깅

Node.js 설치

  1. Node.js 다운로드 페이지에서 LTS 버전의 Windows Installer를 선택합니다. 이 문서를 작성하는 시점에서 최신 버전은 12.13.0(npm 6.12.0 포함)입니다.
  2. 다운로드한 파일을 더블클릭하여 설치를 시작합니다. 설치되는 항목은 아래와 같습니다.
    • Node.js runtime
    • npm package manager
    • Online documentation shortcuts
    • Add to PATH
  3. Tools for Native Modules 화면에서 아래 옵션을 선택합니다.
    • Automatically install the necessary tools. Note that this will also install Chocolatey. The script will pop-up in a new window after the installation completes.
  4. Node.js 설치가 끝나면 아래 스크립트 창이 뜹니다. 엔터키를 눌러서 Tools for Node.js Native Modules 설치를 진행합니다.
    ====================================================
    Tools for Node.js Native Modules Installation Script
    ====================================================
    
    This script will install Python and the Visual Studio Build Tools, necessary
    to compile Node.js native modules. Note that Chocolatey and required Windows
    updates will also be installed.
    
    This will require about 3 Gb of free disk space, plus any space necessary to
    install Windows updates. This will take a while to run.
    
    Please close all open programs for the duration of the installation. If the
    installation fails, please ensure Windows is fully updated, reboot your
    computer and try to run this again. This script can be found in the
    Start menu under Node.js.
    
    You can close this window to stop now. Detailed instructions to install these
    tools manually are available at https://github.com/nodejs/node-gyp#on-windows
    
    계속하려면 아무 키나 누르십시오 . . .
    
  5. Tools for Node.js Native Modules 설치가 끝나면 아래와 같은 결과가 출력됩니다. 엔터키를 눌러서 설치 스크립트 화면을 닫습니다. 일부 패키지는 시스템 재시작을 필요로 합니다.
    Upgraded:
     - visualstudio2017buildtools v15.9.17.0
     - kb2919355 v1.0.20160915
     - kb3033929 v1.0.5
     - python2 v2.7.17
     - kb2999226 v1.0.20181019
     - chocolatey-core.extension v1.3.4
     - dotnetfx v4.8.0.20190930
     - chocolatey-visualstudio.extension v1.8.1
     - visualstudio2017-workload-vctools v1.3.2
     - kb2919442 v1.0.20160915
     - visualstudio-installer v2.0.1
     - vcredist140 v14.23.27820
     - chocolatey-dotnetfx.extension v1.0.1
     - kb3035131 v1.0.3
     - chocolatey-windowsupdate.extension v1.0.4
    
    Packages requiring reboot:
     - vcredist140 (exit code 3010)
    
    The recent package changes indicate a reboot is necessary.
     Please reboot at your earliest convenience.
    Type ENTER to exit:
    

첫번째 Electron 앱 만들기

  1. 앱을 만들고자 하는 폴더에서 명령 프롬프트 창을 열고 아래 명령을 실행합니다.

    >npm init
    

    선택 옵션에 대하여 모두 엔터키를 치면 기본값을 사용하여 package.json 파일을 생성할 것입니다.

  2. package.json 파일을 열어서 scripts 항목에 아래와 같이 start 항목을 추가하고 저장합니다. 이것은 앱을 실행할 때 Electron 런타임을 사용하도록 지정하는 것입니다.

    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "electron ."
    }
    
  3. 앱 폴더에서 아래 명령을 실행하여 Electron을 설치합니다.

    >npm install electron -g
    

    앱별로 Electron을 설치하여 사용하고자 한다면 아래의 명령으로 설치합니다.

    >npm install electron --save-dev
    
  4. 앱 폴더에서 아래와 같은 내용으로 index.js 파일을 작성합니다.

    const { app, BrowserWindow } = require('electron')
    
    function createWindow () {
      // Create the browser window.
      let win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
          nodeIntegration: true
        }
      })
    
      // and load the index.html of the app.
      win.loadFile('index.html')
    }
    
    app.on('ready', createWindow)
    
  5. 앱 폴더에서 아래와 같은 내용으로 index.html 파일을 작성합니다.

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>Hello World!</title>
        <!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
        <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
      </head>
      <body>
        <h1>Hello World!</h1>
        We are using node <script>document.write(process.versions.node)</script>,
        Chrome <script>document.write(process.versions.chrome)</script>,
        and Electron <script>document.write(process.versions.electron)</script>.
      </body>
    </html>
    
  6. 앱 폴더에서 아래 명령으로 Electron 앱을 실행합니다.

    >npm start
    

    앱이 정상적으로 실행되면 아래와 같은 화면이 표시될 것입니다.

패키징

  1. 아래 명령을 실행하여 Electron Packager를 설치합니다.
    >npm install electron-packager -g
    
  2. 앱 폴더에서 아래 명령을 실행하여 현재 시스템을 대상으로 하는 배포 번들을 만듭니다.
    >electron-packager ./ hello-world
    
    첫번째 실행인자 ./는 소스 폴더 위치이고 두번째 실행인자 hello-world는 앱 이름입니다. 배포 번들은 <appname>-<platform>-<architecture> 규칙으로 생성된 폴더 아래에 저장됩니다. 위 명령을 Windows (64 비트) 시스템에서 실행하고 있기 때문에 배포 번들은 hello-world-win32-x64 폴더에 생성됩니다.
  3. 다른 플랫폼을 대상으로 배포 번들을 만들고자 한다면 platformarch 옵션을 사용하여 대상을 지정할 수 있습니다.
    >electron-packager ./ hello-world --platform=linux --arch=x64
    >electron-packager ./ hello-world --platform=darwin --arch=x64
    
    관리자 권한으로 연 명령 프롬프트 창이 아닐 경우 darwin을 대상으로 지정한 명령을 수행하면 아래와 같은 에러가 출력됩니다.
    EPERM: operation not permitted, symlink 'C:\Users\usera\AppData\Local\Temp\electron-packager\symlink-test\test' -> 'C:\Users\usera\AppData\Local\Temp\electron-packager\symlink-test\testlink'
    

실행 및 디버깅

  1. hello-world-win32-x64 폴더 아래에 있는 hello-world.exe 파일을 실행합니다.
  2. 앱 화면에서 View > Toggle Developer Tools 메뉴 항목을 클릭합니다. 그러면 HTML/CSS/JavaScript 를 디버깅할 수 있는 창이 화면 오른쪽에 표시됩니다.

참고 자료

  1. Electron Documentation - Developer Environment
  2. Electron Documentation - Writing Your First Electron App
  3. Electron Packager

Written with StackEdit.

2019년 10월 9일 수요일

붓꽃 분류 - Gaussian Naive Bayes 모델

iris-classification-by-gaussian-nb

붓꽃 분류 - Gaussian Naive Bayes 모델

이 글에서는 붓꽃의 꼳받침과 꽃잎의 특징을 사용하여 어떻게 꽃의 종류를 예측할 수 있는지 베이지안 추론 방식으로 보여줍니다. 이 글의 전개 과정은 아래와 같습니다.

  1. 붓꽃 데이터 세트를 준비합니다.
  2. 꽃 종류별로 측정값의 히스토그램을 그려서 분포를 파악합니다.
  3. 꽃 종류에 따라서 측정값이 어떻게 분포할 수 있는지 설명하는 모델을 정의합니다.
  4. 측정값이 주어질 때 꽃의 종류를 예측하는 분류기를 구현합니다.
  5. 데이터 세트를 훈련 데이터와 검증 데이터로 나누어 분류기를 훈련시키고 예측 성능을 구합니다.

라이브러리 준비

사용할 파이썬 라이브러리들을 임포트합니다.

In [1]:
from collections import defaultdict
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from scipy.stats import norm
import matplotlib.pyplot as plt

%matplotlib inline

np.set_printoptions(precision=6)
np.random.seed(7)

데이터 준비

scikit-learn에서 제공하는 API load_iris()를 사용하여 Iris 데이터 세트를 준비합니다.

  • 붓꽃 세 종류에 대하여 종류별로 50개의 측정 데이터를 가짐
    • 꽃 종류
      • setosa
      • versicolor
      • virginica
    • 측정 항목
      • sepal length (cm): 꽃받침 길이
      • sepal width (cm): 꽃받침 넓이
      • petal length (cm): 꽃잎 길이
      • petal width (cm): 꽃잎 넓이

데이터 읽기

In [2]:
ds_iris = load_iris()

print(f'target names: {ds_iris.target_names}')
print(f'feature names: {ds_iris.feature_names}')
print(f'data shape: {ds_iris.data.shape}')
print(f'target shape: {ds_iris.target.shape}')
target names: ['setosa' 'versicolor' 'virginica']
feature names: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
data shape: (150, 4)
target shape: (150,)

데이터 탐색

데이터 통계

측정 데이터 앞부분의 일부 내용은 다음과 같습니다.

In [3]:
df_data = pd.DataFrame(ds_iris.data, columns=ds_iris.feature_names)
df_data.head()
Out[3]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2

측정 데이터별 통계 자료는 아래와 같습니다.

In [4]:
df_data.describe()
Out[4]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
count 150.000000 150.000000 150.000000 150.000000
mean 5.843333 3.057333 3.758000 1.199333
std 0.828066 0.435866 1.765298 0.762238
min 4.300000 2.000000 1.000000 0.100000
25% 5.100000 2.800000 1.600000 0.300000
50% 5.800000 3.000000 4.350000 1.300000
75% 6.400000 3.300000 5.100000 1.800000
max 7.900000 4.400000 6.900000 2.500000

꽃 종류별 측정값의 히스토그램

꽃 종류별로 측정값의 히스토그램을 그립니다. 이것을 참고하여 꽃 종류에 따라 측정값이 어떤 분포를 따르는지 추정할 수 있습니다.

In [5]:
def separate_by_targets(X, y):
    separated = defaultdict(lambda: [])
    
    row_count = X.shape[0]
    for row in np.arange(row_count):
        measured = X[row, :]
        target = y[row]
        separated[target].append(measured)
    
    for target in separated.keys():
        separated[target] = np.array(separated[target])
        
    return separated

def plot_feature_histograms_for_a_target(separated, target, feature_names, target_name):
    ds_measured = separated[target]
    
    fig = plt.figure(figsize = (16,4))
    fig.suptitle(f'feature histograms for {target_name}')
    for col in np.arange(len(feature_names)):
        plt.subplot(141 + col)
        plt.hist(ds_measured[:, col], bins=24, range=(0, 8))
        plt.ylim(0, 45)
        plt.grid(True)
        plt.xlabel('measured')
        plt.ylabel('frequency')
        plt.title(feature_names[col])
    plt.show()
    
separated = separate_by_targets(ds_iris.data, ds_iris.target)
for target in np.arange(len(separated.keys())):
    feature_names = ds_iris.feature_names
    target_name = ds_iris.target_names[target]
    plot_feature_histograms_for_a_target(separated, target, feature_names, target_name)

위의 히스토그램을 토대로 꽃 종류별 측정값의 분포를 정규분포로 간주하는 것이 크게 무리는 아니라고 볼 수 있습니다.

꽃 종류별 측정값의 상자그림

꽃 종류에 따라 측정값의 통계가 어떻게 달라지는지 더 명확하게 파악하기 위하여 꽃 종류별로 측정값의 상자그림을 그려봅니다.

In [6]:
plt.figure(figsize = (16,4))
plt.suptitle('box plots of features')
for col in np.arange(len(ds_iris.feature_names)):
    plt.subplot(141 + col)
    data_for_boxplot = []
    labels = []
    for target in np.arange(len(separated.keys())):
        data_for_boxplot.append(separated[target][:, col])
        labels.append(ds_iris.target_names[target])
    plt.boxplot(data_for_boxplot, labels = labels)
    plt.ylim(0.0, 8.0)
    plt.grid(True)
    plt.xlabel('target')
    plt.ylabel('measured')
    plt.title(f'{ds_iris.feature_names[col]}')
plt.show()

위의 상자그림들을 살펴 보면 setosa의 경우 꽃잎 길이나 꽃잎 넓이 측정값만으로도 나머지 두 가지 꽃 종류와 완전하게 구분됨을 알 수 있습니다.

모델 정의

꽃 종류에 따른 측정값의 분포를 설명하기 위하여 모델을 정의합니다. 여기에서는 꽃 종류가 주어질 때 측정을 수행하여 얻는 값들의 발생 가능성이 아래의 분포를 따른다고 가정합니다.

  • 조사대상군으로부터 수집한 데이터에서 구한 꽃 종류별 측정값 평균과 표준편차를 사용하는 정규분포

예를 들어 꽃 종류가 setosa일 때 꽃받침 길이에 해당하는 값들의 발생 가능성이 어떻게 분포하는지는 아래의 방법으로 구합니다.

  1. 데이터 세트에서 setosa에 해당하는 것들만 따로 모읍니다.
  2. 꽃받침 길이 값들의 평균과 표준편차를 구합니다.
  3. 위에서 구한 평균과 표준편차를 사용하는 정규분포를 그립니다.

이제 꽃 종류별 측정값 평균과 표준편차를 구하여 테이블 형태로 저장합니다.

In [7]:
def get_norm_params(separated):
    targets = separated.keys()
    target_count = len(targets)
    feature_count = separated[0].shape[1]

    thetas = np.zeros((target_count, feature_count))
    sigmas = np.zeros((target_count, feature_count))
    
    for target in targets:
        ds_measured = separated[target]
        thetas[target,:] = np.mean(ds_measured, axis=0)
        sigmas[target,:] = np.std(ds_measured, axis=0)
        
    return thetas, sigmas

thetas, sigmas = get_norm_params(separated)

위에서 구한 평균과 표준편차 값들을 사용하여 정규분포 곡선을 그려 봅니다.

In [8]:
def plot_feature_norm_for_a_target(thetas, sigmas, target, feature_names, target_name):
    x_arr = np.linspace(0, 10, 100)
    
    fig = plt.figure(figsize = (16,4))
    fig.suptitle(f'feature probability distribution for {target_name}')
    for col in np.arange(len(feature_names)):
        y_arr = norm.pdf(x_arr, thetas[target, col], sigmas[target, col])
        plt.subplot(141 + col)
        plt.plot(x_arr, y_arr)
        plt.ylim(0.0, 2.5)
        plt.grid(True)
        plt.xlabel('measured')
        plt.ylabel('probability')
        plt.title(feature_names[col])
    plt.show()
    
for target in np.arange(len(separated.keys())):
    feature_names = ds_iris.feature_names
    target_name = ds_iris.target_names[target]
    plot_feature_norm_for_a_target(thetas, sigmas, target, feature_names, target_name)

베이지안 추론

베이즈 정리에 기반하여 다음과 같이 세 단계를 거쳐 추론하는 것을 베이지안 추론이라고 합니다.

  1. 기존의 믿음 (prior belief)
  2. 새로운 증거 (new evidence)
  3. 믿음의 수정 (update belief -> posterior belief)

베이즈 정리

베이즈 정리는 아래의 식으로 표현됩니다.

  • $P(H|E) = \frac{P(E|H)\times P(H)}{P(E)}$

위 식에서 각 항목의 의미는 다음과 같습니다.

  • $E$ : 사건 (event)
  • $H$ : 추론하고자 하는 값 (hypothesis)
  • $P(H)$ : E가 발생하기 전의 H에 대한 확률분포 (prior probability distribution)
  • $P(E|H)$ : H를 알고 있을 때 E의 발생 가능도 (likelihood)
  • $P(E)$ : H에 관계없이 E의 발생 가능도 (marginal likelihood)
  • $P(H|E)$ : E가 발생한 후의 H에 대한 확률분포 (posterior probability distribution)

위의 식을 붓꽃 종류 분류 문제에 적용하기 위하여 E와 H를 다음과 같이 정의합니다.

  • E: 측정값 ($v_{measured}$)
  • H: 측정값으로부터 추정하는 실제값 ($v_{actual}$)

측정값이 $v_{measured}$일때 추정하는 실제값 $v_{actual}$의 확률분포를 아래와 같이 조건부확률로 표현할 수 있습니다.

  • $P(v_{actual}|v_{measured})$

이를 베이즈 정리에 따라 표현하면 아래와 같습니다.

  • $P(v_{actual}|v_{measured})=\frac { P(v_{measured}|v_{actual})\times P(v_{actual}) }{ P(v_{measured}) }$

위 식의 각 항목에 대한 의미는 다음과 같습니다.

  • $P(v_{actual})$ : 측정값을 알기 전의 실제값 $v_{actual}$에 대한 확률분포
  • $P(v_{measured}|v_{actual})$ : 실제값이 $v_{actual}$일때 측정값 $v_{measured}$을 얻을 가능도
  • $P(v_{measured})$ : 실제값이 무엇이냐에 관계없이 측정값 $v_{measured}$을 얻을 가능도
  • $P(v_{actual}|v_{measured})$ : 측정값이 $v_{measured}$일때 추정하는 실제값 $v_{actual}$에 대한 확률분포

기존의 믿음

붓꽃 데이터 세트로부터 얻은 꽃 종류의 분포를 기존의 믿음으로 간주합니다. 즉 꽃 종류가 알려지지 않은 새로운 붓꽃이 주어질 때 꽃받침과 꽃잎의 길이와 넓이를 측정하기 전에는 그 꽃의 종류는 데이터 세트로부터 얻은 분포를 따른다고 믿는 것입니다. 이를 사전확률(prior probability)이라고 합니다.

In [9]:
def get_priors(separated):
    targets = separated.keys()

    priors = np.zeros(len(targets))
    
    total_count = 0
    for target in targets:
        count = separated[target].shape[0]
        total_count += count
        priors[target] = count
    
    priors /= total_count
    
    return priors

priors = get_priors(separated)
print(priors)
[0.333333 0.333333 0.333333]

위의 결과는 주어진 붓꽃에 대하여 측정을 하기 전까지는 아래의 확률로 꽃 종류를 추정할 수 있음을 의미합니다.

  • $P(target=setosa) = \frac {1}{3}$
  • $P(target=versicolor) = \frac {1}{3}$
  • $P(target=verginica) = \frac {1}{3}$

새로운 증거

붓꽃 한 개가 있고 꽃받침의 길이와 넓이, 그리고 꽃잎의 길이와 넓이를 측정하여 얻은 값은 다음과 같다고 가정합니다.

꽃받침 길이(sl) 꽃받침 넓이(sw) 꽃잎 길이(pl) 꽃잎 넓이(pw)
6.1 3.3 5.1 1.4
In [10]:
measured = np.array([[6.1, 3.3, 5.1, 1.4]])

실제값이 $target = t$일 때 측정값 $sl=m_{sl};sw=m_{sw};pl=m_{pl};pw=m_{pw}$을 얻을 가능성을 나타내는 가능도(likelihood)를 구합니다. 이때 각각의 특성은 서로 독립적이라고 가정하고 특성별로 확률을 계산합니다. 이러한 가정을 하기 때문에 Bayes 방식이라는 말 앞에 Naive를 덧붙여서 부릅니다.

  • $P(sl=m_{sl};sw=m_{sw};pl=m_{pl};pw=m_{pw}|target=t) = \\ \qquad P(sl=m_{sl}|target=t) \times \\ \qquad P(sw=m_{sw}|target=t) \times \\ \qquad P(pl=m_{pl}|target=t) \times \\ \qquad P(pw=m_{pw}|target=t)$
In [11]:
def get_likelihoods(thetas, sigmas, measured):
    target_count = thetas.shape[0]
    instance_count = measured.shape[0]

    likelihoods = np.zeros((instance_count, target_count))
    
    for target in np.arange(target_count):
        l = norm.pdf(measured, thetas[target, :], sigmas[target, :])
        likelihoods[:, target] = np.prod(l, axis=1)
        
    return likelihoods

likelihoods = get_likelihoods(thetas, sigmas, measured)
print(likelihoods)
[[1.165488e-125 7.080827e-002 1.870655e-002]]

실제값이 무엇인지에 관계없이 측정값으로 $sl=m_{sl};sw=m_{sw};pl=m_{pl};pw=m_{pw}$을 얻을 가능성을 나타내는 주변가능도(marginal likelihood)는 아래와 같이 구합니다.

  • $P(sl=m_{sl};sw=m_{sw};pl=m_{pl};pw=m_{pw}) = \\ \qquad P(sl=m_{sl};sw=m_{sw};pl=m_{pl};pw=m_{pw}|target=0) \times P(target=0) + \\ \qquad P(sl=m_{sl};sw=m_{sw};pl=m_{pl};pw=m_{pw}|target=1) \times P(target=1) + \\ \qquad P(sl=m_{sl};sw=m_{sw};pl=m_{pl};pw=m_{pw}|target=2) \times P(target=2)$
In [12]:
marginal_likelihoods = np.sum(likelihoods * priors, axis=1)
print(marginal_likelihoods)
[0.029838]

믿음의 수정

새로운 증거(new evidence)를 활용하여 기존의 믿음(prior)을 수정합니다. 이렇게 얻은 확률을 사후확률 (posterior)이라고 부릅니다. 측정값이 주어진 상태에서 꽃종류를 바꾸어 가면서 사후확률을 구합니다.

In [13]:
def get_posteriors(priors, thetas, sigmas, X):
    likelihoods = get_likelihoods(thetas, sigmas, X)
    marginal_likelihoods = np.sum(likelihoods * priors, axis=1)
    likelihood_ratios = likelihoods / marginal_likelihoods.reshape(len(marginal_likelihoods), -1)
    posteriors = likelihood_ratios * priors
    return posteriors

posteriors = get_posteriors(priors, thetas, sigmas, measured)
print(posteriors)
[[1.302006e-124 7.910229e-001 2.089771e-001]]
In [14]:
plt.figure(figsize = (6, 4))
plt.bar(ds_iris.target_names, posteriors[0,:], width=0.4)
plt.grid(True)
plt.title('posterior distribution')
plt.show()

꽃 종류별 사후확률 중에서 최대 사후확률에 해당하는 꽃 종류를 실제값으로 간주합니다. 이 과정을 Maximum A Posteriori(MAP) 추정이라고 부릅니다.

In [15]:
predicted = np.argmax(posteriors, axis=1)

for i in np.arange(measured.shape[0]):
    print(f'{measured[i,:]} => {ds_iris.target_names[predicted[i]]}')
[6.1 3.3 5.1 1.4] => versicolor

분류기 구현

위에서 정의한 함수들을 사용하여 분류기 클래스를 아래와 같이 구현할 수 있습니다.

In [16]:
class GaussianNB:
    def fit(self, X, y):
        # separate by targets
        separated = separate_by_targets(X, y)
        
        # get priors
        self.priors = get_priors(separated)
        
        # get normal distribution parameters
        self.thetas, self.sigmas = get_norm_params(separated)
        
    def predict(self, X):
        posteriors = get_posteriors(self.priors, self.thetas, self.sigmas, X)
        predicted = np.argmax(posteriors, axis=1)
        return predicted
        
    def score(self, X, y):
        return sum(self.predict(X) == y) / len(y)

예측 성능

In [17]:
X, y = ds_iris.data, ds_iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7)

nb = GaussianNB()
nb.fit(X_train, y_train)

score = nb.score(X_test, y_test)
print(f'score = {score:.4f}')
score = 0.8333

마무리

  • 이 글에서 구현한 분류기는 측정값이 정규분포를 따르는 다른 종류의 데이터 세트에 대해서도 동작합니다.

가설 검정 학습 내용 요약

출처: 위키백과 통계적 추론 추론 통계 또는 추론 통계학(inferential statistics)으로 불린다.  기술 통계학(descriptive statistics)과 구별되는 개념 도수 확률(frequency probability)과 사전 확률(...