#include <opencv2/opencv.hpp>

int main() {
    // 픽셀 좌표 (예: 객체를 탐지한 픽셀 좌표)
    cv::Point2f pixelPoint(100, 150);

    // 카메라 매트릭스 및 왜곡 계수 (카메라 캘리브레이션에서 얻은 값 사용)
    cv::Mat cameraMatrix = (cv::Mat_<double>(3, 3) << fx, 0, cx, 0, fy, cy, 0, 0, 1);
    cv::Mat distortionCoeffs = (cv::Mat_<double>(1, 5) << k1, k2, p1, p2, k3);

    // 카메라 좌표를 월드 좌표로 변환하는 함수
    cv::Matx44d transformationMatrix; // 여기에 변환 행렬이 저장됨
    cv::solvePnP(objectPoints, imagePoints, cameraMatrix, distortionCoeffs, transformationMatrix);

    // 픽셀 좌표를 카메라 좌표로 변환
    cv::Matx31d pixelCoordinates(pixelPoint.x, pixelPoint.y, 1.0);
    cv::Matx31d cameraCoordinates = cameraMatrix.inv() * pixelCoordinates;

    // 카메라 좌표를 월드 좌표로 변환
    cv::Matx41d worldCoordinatesHomogeneous = transformationMatrix * cv::Matx41d(cameraCoordinates(0), cameraCoordinates(1), cameraCoordinates(2), 1.0);
    cv::Matx31d worldCoordinates(worldCoordinatesHomogeneous(0), worldCoordinatesHomogeneous(1), worldCoordinatesHomogeneous(2));

    // 결과 출력
    std::cout << "픽셀 좌표: " << pixelPoint << std::endl;
    std::cout << "월드 좌표: " << worldCoordinates << std::endl;

    return 0;
}

 

-chatgpt

import math
import numpy as np

def get_pivot_position(tableau):
    z = tableau[-1]
    column = next(i for i, x in enumerate(z[:-1]) if x > 0)

    restrictions = []
    for eq in tableau[:-1]:
        el = eq[column]
        restrictions.append(math.inf if el <= 0 else eq[-1] / el)

    row = restrictions.index(min(restrictions))
    return row, column

def pivot_step(tableau, pivot_position):
    new_tableau = [[] for eq in tableau]

    i, j = pivot_position
    pivot_value = tableau[i][j]
    new_tableau[i] = np.array(tableau[i]) / pivot_value

    for eq_i, eq in enumerate(tableau):
        if eq_i != i:
            multiplier = np.array(new_tableau[i]) * tableau[eq_i][j]
            new_tableau[eq_i] = np.array(tableau[eq_i]) - multiplier

    return new_tableau
    
def to_tableau(c, A, b):
    xb = [eq + [x] for eq, x in zip(A, b)]
    z = c + [0]
    return xb + [z]
    
def simplex(c, A, b):
    tableau = to_tableau(c, A, b)

    while can_be_improved(tableau):
        pivot_position = get_pivot_position(tableau)
        tableau = pivot_step(tableau, pivot_position)

    return get_solution(tableau)

def can_be_improved(tableau):
    z = tableau[-1]
    return any(x > 0 for x in z[:-1])

def is_basic(column):
    return sum(column) == 1 and len([c for c in column if c == 0]) == len(column) - 1

def get_solution(tableau):
    columns = np.array(tableau).T
    solutions = []
    for column in columns[:-1]:
        solution = 0
        if is_basic(column):
            one_index = column.tolist().index(1)
            solution = columns[-1][one_index]
        solutions.append(solution)

    return solutions
    

c = [1, 1, 0, 0, 0]
A = [
    [-1, 1, 1, 0, 0],
    [ 1, 0, 0, 1, 0],
    [ 0, 1, 0, 0, 1]
]
b = [2, 4, 4]

solution = simplex(c, A, b)
print('solution: ', solution)

 

출처 : https://radzion.com/blog/operations/simplex

스테이트 패턴 정의

객체 내부에 구성되어있는 상태에 따라 객체의 행동을 바꾸는 패턴이다.  

 

필요한 상황

스테이트 패턴

- 로봇의 상태에 따라 수행하는 동작을 바꾸고 싶을 때

- GUI 프로그램에서 상태에 따라 버튼의 Enable, Disable 등 여부를 바꾸고 싶을 때

- 상태 변수를 switch로 넣어서 case로 때려 넣은 코드가 있을 때 (리펙토링 대상!!)

 

Class 다이어그램

스테이트 패턴

코드

스테이트 패턴

public interface State {
    void insertQuarter();
    void ejectQuarter();
    void turnCrank();
    void dispense();
}

public class HasQuarterSate : State {
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine)
    {
        this.gumballMachine = gumballMachine;
    }
    public void insertQuarter()=> Console.WriteLine("손잡이를 돌렸습니다.");
        
    public void ejectQuarter(){
        Console.WriteLine("동전이 반환됩니다.");
        gumballMachine.setState(gumballMachine.noQuarterState);
    }
    public void turnCrank() => Console.WriteLine("손잡이를 돌렸습니다.");
    public void dispense()=> Console.WriteLine("알맹이가 나갈수 없습니다.");
}

public class SoldState : State {
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine)
    {
        this.gumballMachine = gumballMachine;
    }
    public void insertQuarter()=> Console.WriteLine("잠깐만 기다려 주세요. 알맹이가 나가고 있습니다.");
    public void ejectQuarter()=> Console.WriteLine("이미 알맹이를 뽑으셨습니다.");
    public void turnCrank(){}=> Console.WriteLine("손잡이는 한 번만 돌려주세요.");
    public void dispense(){
        gumballMachine.releaseBall();
        if ( gumballMachine.count > 0){
            gumballMachine.setState(gumballMachine.noQuarterState);
        }
        else{
            Console.WriteLine("더이상 동전이 없습니다");
            gumballMachine.setState(gumballMachine.soldOutState);
        }
    }
}

public class SoldOutState : State {
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine)
    {
        this.gumballMachine = gumballMachine;
    }
    //인터페이스 구현체..
}

public class NoQuarterState : State {
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine)
    {
        this.gumballMachine = gumballMachine;
    }
    //인터페이스 구현체..
}

인터페이스와 구현체

 

public class GumballMachine {
    State soldOutState {get;set;}
    State noQuarterState{get;set;}
    State hasQuarterSate{get;set;}
    State soldState{get;set;}
    
    State state = soldOutState;
    int count {get;set;};
    
    public GumballMachine(int numberGumballs){
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterSate = new HasQuarterSate(this);
        soldState = new SoldState(this);
        this.count = numberGumballs;
        if (numberGumballs > 0){
            state = noQuarterState;
        }
    }
    
    public void insertQuarter(){
        state.insertQuarter();
    }
    
    public void ejectQuarter(){
        state.ejectQuarter();
    }
    
    public void turnCrank(){
        state.turnCrank();
        state.dispense();
    }
    
    public void dispense(){
        state.dispense();
    }
    public void setState(State state){
        this.state = state;
    }
    
    public void releaseBall(){
        Console.WriteLine("A gumball comes rolling out the solt.....");
        if (count != 0){
            count = count - 1;
        }
    }
}

상태패턴을 이용하는 Context Class

코드 설명

스테이트 패턴

1. 인터페이스와 구현체

public interface State {
    void insertQuarter();
    void ejectQuarter();
    void turnCrank();
    void dispense();
}

Context Class에서 제공하는 기능을 인터페이스 함수로 대체했다.

 

public class HasQuarterSate : State {
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine)
    {
        this.gumballMachine = gumballMachine;
    }
    public void insertQuarter()=> Console.WriteLine("손잡이를 돌렸습니다.");
        
    public void ejectQuarter(){
        Console.WriteLine("동전이 반환됩니다.");
        gumballMachine.setState(gumballMachine.noQuarterState);
    }
    public void turnCrank() => Console.WriteLine("손잡이를 돌렸습니다.");
    public void dispense()=> Console.WriteLine("알맹이가 나갈수 없습니다.");
}

인터페이스의 구현체는 Context Class 인스턴스를 가지고 있으며, 인터페이스 구현체를 직접 실행하면서 상태 변경이 필요한 경우 Context Class 인스턴스의 상태를 직접 변경한다. 이부분에서 Context Class와의 종속성이 생긴다. (결합성이 올라감..)

 

2. 스테이트패턴을 이용하는 Context Class

Context Class는 스테이트 구현체들을 모두 가지고 있으며, 기능 실행이 필요할 때 인터페이스 구현체들의 함수만 호출한다.

설명 보충을 위한 그림 자료

스테이트 패턴

현재 상태 : HasQuarter, 상태별로 구현한 인터페이스 구현체들(슬롯 머신 기능 + 상태 천이 기능)

 

현재 상태 : Sold, 상태별로 구현한 인터페이스 구현체들(슬롯 머신 기능 + 상태 천이 기능)

필요에 따라 추가 설명

x

정리 및 결론

스테이트 패턴은 현재 프로그램의 상태에 따라 기능을 달리해야 할 때 적용할만한 패턴이다. 주요 특징은 아래와 같다.

1. 상태 인터페이스를 두고 그에 맞는 구현체들을 모든 상태 별로 구현한다.

2. 상태 인터페이스 구현체들은 온전히 자신의 기능만을 수행하며 상태 천이가 필요한 경우 Context Class의 상태 천이를 수행한다.

3. 전략 패턴과 유사하다. 

개인적으로 상태 패턴을 실제로 사용한 사례를 보고싶다. 전략 패턴, 옵저버 패턴, 팩토리 패턴, 데코 패턴은 많이 쓰인다고 들었는데 상태 패턴을 사용하는 사례를 보지 못했다. 

'Program > SW Design Patterns' 카테고리의 다른 글

Mediator 패턴 (C++)  (0) 2024.07.31
CH7 어댑터&퍼사드 패턴  (0) 2023.03.19
CH6 커맨드 패턴  (0) 2023.03.07
CH5 데코레이터 패턴  (1) 2023.03.05
CH4 팩토리 패턴  (0) 2023.03.04
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

// 히스토그램을 계산하고 정규화하는 함수
Mat calcHistNormalized(const Mat &src) {
    Mat hsv;
    cvtColor(src, hsv, COLOR_BGR2HSV);

    // H 채널에 대한 히스토그램 계산
    int h_bins = 256; // 히스토그램 빈의 수
    int histSize[] = { h_bins };
    float h_ranges[] = { 0, 256 };
    const float* ranges[] = { h_ranges };
    int channels[] = { 0 };
    Mat hist;
    calcHist(&hsv, 1, channels, Mat(), hist, 1, histSize, ranges, true, false);

    // 히스토그램 정규화
    normalize(hist, hist, 0, 1, NORM_MINMAX, -1, Mat());

    return hist;
}

// 코사인 유사도를 계산하는 함수
double cosineSimilarity(const Mat &hist1, const Mat &hist2) {
    return hist1.dot(hist2) / (norm(hist1) * norm(hist2));
}

int main() {
    // 이미지 파일을 로드
    Mat image1 = imread("image1.jpg");
    Mat image2 = imread("image2.jpg");

    if (image1.empty() || image2.empty()) {
        cout << "Could not open or find the image(s)." << endl;
        return -1;
    }

    // 히스토그램 계산 및 정규화
    Mat hist1 = calcHistNormalized(image1);
    Mat hist2 = calcHistNormalized(image2);

    // 코사인 유사도 계산
    double similarity = cosineSimilarity(hist1, hist2);

    cout << "Cosine Similarity: " << similarity << endl;

    return 0;
}

본 포스팅은 아래 선행 작업들이 있다.

- OpenCV 4.2 가 설치되 있고, 아래 명령어를 사용 가능한 상태여야한다.

     - opencv_createsamples

     - opencv_traincascade

- 학습 데이터가 수집 되어 있는 상태이다.

 

진행 순서

1. opencv_createsamples 실행 파일을 이용해 학습 데이터 생성하기 (out : .vec 파일)

2. opencv_traincascade 실행 파일을 이용해 검출기 생성하기 (out : .xml 파일)

3. .xml 파일을 이용해 객체 검출 해보기

 

1. opencv_createsamples 실행 파일을 이용해 학습 데이터 생성하기

파라미터 설명

opencv_createsamples -info <description_file_name> -vec <vec_file_name>

<description_file_name> : 검출을 원하는 이미지 데이터들이 기록되어있는 텍스트 file 이름 (positive 샘플이라한다.)

 

<vec> : 생성된 학습 데이터를 저장할 파일명 (확장자 : vec)

 

<background_file_name> : 검출할 이미지가 없는 이미지들이 기록되어있는 텍스트 file 이름 (negative 샘플이라한다.)

 

명령어 입력 예시

opencv_createsamples -info positive_list.txt -vec tr.vec -bg negative_list.txt

 

positive_list.txt 파일 구성은 아래와 같이 구성해야한다.

c:\positives\img1.jpg 1 140 100 45 45
c:\positives\img2.jpg 2 100 20 50 50 0 30 25 25
c:\positives\img3.jpg 1 0 0 20 20
....

첫번째 인자 : 이미지 파일 경로

두번째 인자 : 이미지에 구성되어있는 positive 샘플의 갯수

세번째 인자 : x, y, width, height  

positive 샘플의 갯수가 2개 이상이면 세번째 인자를 주르륵 이어서 쓰면됨

 

 

 

2. opencv_traincascade 실행 파일을 이용해 학습 모델 생성하기

opencv_traincascade -data <cascade_dir_name> -vec <vec_file_name> -bg <background_file_name> [-numStages <number_of_stages = 20>] [-featureType <HAAR(default), LBP, HOG] [-baseFormatSave]

파라미터 설명 

<cascade_dir_name> : 최종 검출기가 저장될 파일 이름 (최종 출력 확장자 .xml)

(일례 '-data result' 로 실행 시 result라는 폴더가 생성되고 result 밑에 cascade classifier들이 저장됨. 또한 현재 실행폴더에 result.xml이라는 이름으로 학습된 최종 검출기가 저장됨.) 

 

<vec_file_name> : 앞서 생성 했던 학습 데이터 파일명 (확장자 : vec)

 

<background_file_name> : 검출할 이미지가 없는 이미지들이 기록되어있는 텍스트 file 이름 (negative 샘플이라한다.)

 

[<HARR, LBP, HOG> (default : HARR)] : 검출 모드 설정(옵션)

 

[<number_of_stages> (default : 20)] : 학습할 cascade 단계의 수. Cascade 검출기는 일련의 기본 검출기들로 구성되는데, Cascade 검출 방식은 먼저 1단계 classifier로 false들을 걸러내고, 나머지에 대해서는 2단계 classifier로 false들을 걸러내고, ... 이런식으로 해서 최종 단계까지 살아남으면 물체 검출에 성공한 것으로 간주함. -nstages는 이 단계수 즉, 기본 검출기들의 개수를 조절하는 것임. 학습된 각 단계별 기본 검출기들은 result\ 밑에 0, 1, 2, ... 밑에 텍스트 파일 형태로 저장됨. (옵션)

 

[-baseFormatSave (default : false)] : -featureType이 HAAR일 경우에만 의미를 가지는 파라미터임. 이 파라미터를 명시해 주면 훈련 결과를 예전의 Haar training 방식의 데이터 포맷으로 저장해줌. (옵션)

 

명령어 입력 예시

opencv_traincascade -data result -vec tr.vec -bg negative_list.txt -numPos 400 -numNeg 5000 -featureType HAAR -numState 14 -baseFormatSave

 

 

negative_list.txt 파일 구성은 아래와 같이 구성해야한다.

c:\negatives\img1.jpg
c:\negatives\img2.jpg

첫번째 인자 : 이미지 파일 경로

 

-npos: 각 cascade 학습 단계(stage)에 사용되는 positive 샘플 개수를 설정. 주의할 점은 .vec 파일에 있는 실제 샘플수를 입력하면 안됨.  npos <= (vec파일에 있는 샘플수 - 100)/(1+(nstages-1)*(1-minhitrate))) 정도로 값을 주기 바람.

 

-nneg: 각 cascade 학습 단계(stage)에 사용될 negative 샘플 개수를 설정. -bg로 입력한 negative 이미지들 중에서 다양한 위치 및 크기로 negative 샘플들을 뽑기 때문에 실제 negative 이미지 개수와 관계없이 원하는 값을 주면 됨.

 

-minhitrate: 각 cascade 단계의 기본 classifier들에게 요구되는 최소 검출율. 최종 검출기의 검출율은 minhitrate^nstages가 됨. 예를 들어, 기본값을 그대로 사용하면 최소 0.995^14 = 0.932 정도의 검출율을 가지는 detector를 얻을 수 있게 됨. 하지만 이것은 어디까지나 .vec 파일로 입력한 training 데이터에 대한 검출율이기 때문에 실제 일반적인 입력에 대한 검출율은 훨씬 떨어질 수 있음.

 

3. .xml 파일을 이용해 객체 검출 해보기

#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include <iostream>
#include <string>

using namespace std;
using namespace cv;


void detectAndDisplay(const CascadeClassifier& classfier, Mat frame )
{
     Mat frame_gray;
     cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
     
     //-- Detect objects
     std::vector<Rect> objects;
     classfier.detectMultiScale( frame_gray, objects );
     for ( size_t i = 0; i < objects.size(); i++ )
     {
         Point center( objects[i].x + objects[i].width/2, objects[i].y + objects[i].height/2 );
         ellipse( frame, center, Size( objects[i].width/2, objects[i].height/2 ), 0, 0, 360, Scalar( 255, 0, 255 ), 4 );
     }
     //-- Show what you got
     imshow( "Capture - object detection", frame );
}

int main(void)
{

    CascadeClassifier classfier;
    
    //-- 1. Load the cascades
    string cascade_name = "trained.xml";
    if( !classfier.load( cascade_name ) )
    {
    cout << "--(!)Error loading face cascade\n";
    return -1;
    };
    
    Mat frame;
    ... 이미지 읽기
    //-- 2. Apply the classifier to the frame
    detectAndDisplay(classfier,frame);
}

 

참고 사이트 : 

https://darkpgmr.tistory.com/70

 

OpenCV Haar/cascade training 튜토리얼

OpenCV의 Haar classifier, Cascade classifier를 학습시키기 위한 샘플 데이터 생성법 및 training 방법에 대한 상세 메뉴얼입니다. Haar training이나 cascade training에 대한 내용은 OpenCV 웹 매뉴얼이나 Naotoshi Seo의

darkpgmr.tistory.com

https://docs.opencv.org/3.4/db/d28/tutorial_cascade_classifier.html

 

OpenCV: Cascade Classifier

Next Tutorial: Cascade Classifier Training Goal In this tutorial, We will learn how the Haar cascade object detection works. We will see the basics of face detection and eye detection using the Haar Feature-based Cascade Classifiers We will use the cv::Cas

docs.opencv.org

목표

1. kernel 트릭을 이용한 SVM을 이해한다.

2. 학습한 kernel SVM에 대해 Decision boundry 그래프를 그려본다.

 

1. kernel 트릭을 이용한 SVM 을 이해한다.

kernel 이란?

-  기존 SVM은 선형 SVM이라 불린다. 이 뜻은 아래 그림처럼 선형특성을 가지는 Decision boundry를 만든다는 것이다.

선형적인 Decision boundry (빨간색 사각형선 표시)

- 비선형성을 가지는 데이터를 분류 할 때는 어떻게 해야 할까?? 아래는 선형 적인 Decision boundry로 구분하기 힘든 XOR 형식의 데이터이다. 

XOR 형식의 데이터

- XOR 형식의 데이터에 대해 기존 SVM을 적용했을 때 그래프이다.

 

XOR 형식의 데이터를 기존 SVM으로 학습 시켰을때

- 이렇게 특징 데이터가 비선형성을 가지는 경우 비선형성의 데이터를 선형적인 특성으로 변환시킬만한 트릭이 필요하다.

- 이때 사용하는 기법이 Kernel 트릭이다.

- Kernel 트릭은 기존의 특징 데이터를 SVM이 구분 가능한 데이터의 형태로 변환 시키는 것이다. 아래는 널리 사용되는 Kernel 중 하나 인 방사 기저 함수(Radial Basis functionl; RBF) 이다.

RBF 필터 계산식

- RBF Kernel 트릭은 샘플 간의 유사도 함수(similarity function)로 사용한다고 한다.

- 이렇게 지수 함수로 얻은 값이 1에 가까우면 두 샘플 간 거의 비슷한 값, 0에 가까우면 매우 다른 샘플이라 한다.

 

 

2. 학습한 kernel SVM에 대해 Decision boundry 그래프를 그려본다.

 

- 아래는 RBF Kernel 트릭을 이용해 데이터를 변환시키고 선형 SVM을 적용하는 코드와 그래프이다.

from sklearn.svm import SVC
svm = SVC(kernel='rbf', C=1.0,gamma=0.2, random_state=1)
svm.fit(X_xor, y_xor)

- 이 코드에서 gamma 값은 크면 클수록 일반화에 약해짐으로 학습시킬 때 주의하자.

RBF kerenel을 이용해 데이터를 변환시키고 SVM을 학습 시켰을때

- 마지막으로 gamma 값을 100.0으로 했을 때 그래프이다. 

gamma 값은 100으로 지정하고&nbsp; SVM을 학습 시켰을 때

목표

1. (Support Vector Machine; SVM)을 이해하고 꽃 분류 모델을 만든다.

2. 학습한 SVM에 대해 Decision boundry 그래프를 그려본다.

 

1. (Support Vector Machine; SVM) 을 이해하고 꽃 분류 모델을 만든다.

SVM 이란?

-  SVM을 이해하려면  Decision boundry, 서포트 벡터, 마진을 알아야한다.

1. decision boundry

  - 직역하면 결정 경계선을 의미한다. 

Decision boundry (빨간색 사각형선 표시)

2. support vector

  - 결정 경계선에서 가장 가까운 데이터를 의미한다. 가깝다는 것은 간단하게 L2 Norm으로 계산했을 때 나오는 값을 봤을 때 다른 데이터 보다 작은 값을 의미한다.

3. margin 

 -  서포트 벡터와 결정 경계선간의 거리

 - 마진을 최대화했다 -> 일반화 오차를 낮췄다. 

 - 마진을 최소화했다 -> 일반화 오차가 크다. (오버피팅)

서포트 벡터(동그라미 표시), 마진(직선)

- SVM은 학습을 통해 서로 다른 레이블과 매칭하는 특성 데이터의 support vector와 생성할 decision boudry 간에 margin값을 최대화하는 것이 목표이다. 

- soft margin classification : 이전의 SVM에서 슬랙 변수를 이용하여 선형적으로 구분되지 않는 데이터에서 선형 제약 조건을 완화시킨 분류기이다. 

 

 

from sklearn.svm import SVC
svm = SVC(kernel='linear', C=1.0, random_state=1)
svm.fit(X_train_std, y_train)

- 위는 SVM을 생성하고 학습시키는 코드이다.

- SVC인 이유는 Support Vector classification 이기 때문이다. (SVM과 학습 원리 동일)

- 이때 C 값은 마진 폭을 조절해 주는 변수이다. C 값을 조절하여 편향-분산 트레이드오프 (bais vs variance trade-off)를 조절한다.

 

2. 학습한 SVM에 대해 Decision boundry 그래프를 그려본다.

plot_decision_regions(X_combined_std, y_combined,classifier=svm, test_idx=range(105, 150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

- 위 코드는 학습한 모델을 아래 챕터에서 정의한 함수를 재사용하여 그리는 코드이다.

https://computervision-is-fun.tistory.com/111

- 아래 그림은 학습한 SVM의 Decision boundry 그래프이다.

목표

1. sklearn API에서 제공하는 꽃 데이터를 받아서 데이터 분포를 정규화한다.

2. sklearn API에서 제공하는 퍼셉트론 모델을 이용하여 꽃 분류 모델 훈련하고 테스트한다.

3. matplotlib를 이용해 입출력 데이터에 대한 꽃 분류 모델의 Decision boudry를 시각화한다.

 

 

1. sklearn API에서 제공하는 꽃 데이터를 받아서 데이터 분포를 정규화 한다.

iris데이터는 아래 3가지 사진에서 보이는 꽃에 대해서 특성데이터(X), 라벨데이터(y)가 주어져 있는 데이터이다.

0 : Iris Versicolor, 1 : Setosa, 2 : Iris Virginica

from sklearn import datasets
import numpy as np

iris = datasets.load_iris()
X = iris.data[:,[2,3]]
y = iris.target
print('클래스 레이블 : ', np.unique(y))

 

여기서 iris.data는 꽃의 샘플 데이터이다. iris.data의 구조는 아래와 같다. 

보통 머신 러닝 프로젝트를 한다면 데이터를 분석하는게 제일 중요한 것 같다. (데이터를 분석한다는 것은 데이터의 차원, 분포도, 평균, 표준편차 등을 예시로 들 수 있다.)

iris.data[:,[2,3]]

위와 같이 구성한 명령어는 꽃 분류 데이터의 2번째와 3번째 특성 데이터만 쓰겠다는 뜻이다. (추 후 Decision boundry로 그리기 위함 + 꽃을 분류하기에 충분히 의미 있는 데이터임)

 

from sklearn.model_selection import train_test_split

#X : 특성 데이터, y : 레이블 데이터, test_size : 데이터에서 테스트에 쓸 데이터 비율, random_state : random seed 값, stratify : 레이블 데이터의 비율 고정
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1, stratify=y)

다음은 퍼셉트론 모델을 학습 하기 위한 데이터 분할이다. 

테스트 셋은 0.3, 학습 데이터 셋은 0.7 비율이다.

그리고 stratify=y 파라미터 의미는 학습용, 테스트용 데이터 셋에 대해서 레이블 값을 균등하게 가지도록 하겠다는 뜻이다.

아래는 stratify=y 파라미터가 있는 것과 없는 것의 차이이다.

 

stratify=y 있을 때
stratify=y 없을 때

위와 같이 학습용 데이터에 대해서 균등하게 나눠지는 것을 볼 수 있다.

 

 

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)

마지막으로 위의 코드는 학습용 특성 데이터 (X) 에 대해서 데이터를 정규화하는 코드이다.

간단하게 StandardScaler Class가 학습용 데이터 X_train을 받아서 데이터의 평균과 분포를 계산하고 이에 맞게 학습용 특성 데이터, 테스트용 특성 데이터를 정규화하는 코드이다.

 

2. sklearn API에서 제공하는 퍼셉트론 모델을 이용하여 꽃 분류 모델 훈련하고 테스트한다.

from sklearn.linear_model import Perceptron

#eta0 : 학습률, random_state : random seed 
ppn = Perceptron(eta0=0.1, random_state=1)
ppn.fit(X_train_std, y_train)

위의 코드는 Perceptron Class를 생성하고 정규화했던 학습용 데이터 셋에 대해서 학습시키는 코드이다.

fit 함수가 입력한 데이터에 대해서 학습하는 코드이다.

y_pred = ppn.predict(X_test_std)
print(f'분류 실패 개수 {(y_test != y_pred).sum()}')
print(f'분류 정확도 {ppn.score(X_test_std, y_test)}')

위의 코드는 학습한 퍼셉트론 모델을 테스트하는 코드이다.  Pytorch나 Tensorflow 였으면 모델 정확도 측정에 대해서 코드량이 좀더 많았겠지만 sclearn은 이런 부분을 모두 구현되어 있어서 편하다.

훈련 결과 정확도 0.97이 나옴

 

3. matplotlib를 이용해 입출력 데이터에 대한 꽃 분류 모델의 Decision boudry를 시각화한다.

from matplotlib.colors import ListedColormap
import matplotlib.pylab as plt

def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02):
    markers = ('s', 'x','o','^','v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors=colors[:len(np.unique(y))])

    x1_min, x1_max = X[:,0].min() -1, X[:,0].max() + 1
    x2_min, x2_max = X[:,1].min() -1, X[:,1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape) 

    plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)   
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())

    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl,0], y=X[y == cl,1],
                    alpha =0.8, c=colors[idx], marker=markers[idx], label=cl, edgecolors='black')
        
    if test_idx:
        X_test, y_test = X[test_idx, :], y[test_idx]

        plt.scatter(X_test[:,0], X_test[:,1], 
        facecolors='none', alpha =1.0, linewidths=1, marker='o', s=100,label='test set', edgecolors='black')
 
X_combined_std = np.vstack((X_train_std, X_test_std))
y_combined = np.hstack((y_train, y_test))
plot_decision_regions(X = X_combined_std, y=y_combined, classifier=ppn, test_idx=range(105,150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

위의 에서 정의한 함수는 특성 데이터(x), 라벨 데이터(y), 모델 분류기(퍼셉트론 모델 등..) 을 받아서 Decision boundry를 그려주는 함수이다. 개인적으로 훈련한 모델이 특성 데이터(X)를 어떻게 보고 있는지 확인할 수 있는 그래프를 보는 것은 정말 재밌다.

    x1_min, x1_max = X[:,0].min() -1, X[:,0].max() + 1
    x2_min, x2_max = X[:,1].min() -1, X[:,1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape)
    
    plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)   
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())

위의 코드 순서

1. 특성 데이터(X)의 첫번째 특성과 두번째 특성 데이터를 그래프의 X축, Y축으로 구분한다.

2.  np.arange()함수를 이용해 특성 데이터 X에 대해서 X축, Y축을 resolution 만큼의 데이터를 나눈 후 xx1, xx2에 저장한다. 

(ex : X축 범위 (1~100) 일때 resolution이 1이라면 100개의 데이터가 생성된다. 이때 생성된 데이터는 1씩 증가한 데이터이다. X = [1,2,3,4,5,6])

3. 이렇게 만든 모든 데이터(xx1, xx2)에 대해서 모든 값을 분류기에 입력하고 출력값 Z를 확인한다.

4. Z 값의 차원을 특성 데이터(xx1)의 차원과 동일하게 만든다.(그리기 위함)

5. 위의 과정을 거쳐서 얻은 xx1, xx2에 값에 대해서 contourf 함수를 통해 그래프를 그린다.

xx1 : petal length(X축), xx2 : petal width(Y축),&nbsp; Z : 그래프의 색

for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl,0], y=X[y == cl,1],
                    alpha =0.8, c=colors[idx], marker=markers[idx], label=cl, edgecolors='black')
        
    if test_idx:
        X_test, y_test = X[test_idx, :], y[test_idx]

        plt.scatter(X_test[:,0], X_test[:,1], 
        facecolors='none', alpha =1.0, linewidths=1, marker='o', s=100,label='test set', edgecolors='black')

위의 코드는 이전 단계에서 그렸던 그래프에서 학습용 데이터와 테스트용 데이터를 입력했을 때 얻은 값을 그래프에 표시하는 것이다.

학습용 데이터, 테스트용 데이터를 입력했을 때 표시

X_combined_std = np.vstack((X_train_std, X_test_std))
y_combined = np.hstack((y_train, y_test))
plot_decision_regions(X = X_combined_std, y=y_combined, classifier=ppn, test_idx=range(105,150))
plt.xlabel('petal length [standardized]')
plt.ylabel('petal width [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

마지막으로 위의 코드는 그래프를 보여주기 위해 데이터를 구성하고 Decision boundry 함수에 입력해서 그래프를 확인하는 코드이다.

X_combined_std = np.vstack((X_train_std, X_test_std))
y_combined = np.hstack((y_train, y_test))

위의 두 코드에서 vstack은 vertical stack, hstack은 horizental stack을 뜻한다. 이는 모델의 입출력에 대해서 데이터를 추가하는 것이다.

여기서 X 데이터에 대해서 vstack인 이유는 N * M 행렬이기 때문이다. (N : 데이터의 갯수, M : N번째 벡터에 포함되어 있는 특성 데이터의 갯수)

여기서 y 데이터에 대해서 hstack인 이유는 N 벡터이기 때문이다. (N : 데이터의 갯수)

 

끝..

class Vector
{

private:
    float x;
    float y;
    float z;

public:
    Vector(float x, float y, float z):
    x(x), y(y), z(z)
    {
    }
    //단항 연산자 -
    Vector operator-() const
    {
        return Vector(-x,-y,-z);
    }
    
    //단항 연산자 +
    Vector operator+() const
    {
        return *this;
    }
    
    //이항 연산자 +
    Vector operator+(const Vector& v) const
    {
        return Vector(this.x + v.x, this.y + v.y, this.z + v.z);
    }
    
    //이항 연산자 -
    Vector operator-(const Vector& v) const
    {
        return Vector(this.x - v.x, this.y - v.y, this.z - v.z);
    }
    
    //스칼라 이항 연산자 *
    Vector operator*(float v) const
    {
        return Vector(this.x * v, this.y * v, this.z * v);
    }
    
    //Vector 이항 연산자 * (dot product)
    Vector operator*(Vector v) const
    {
        return this.x * v.x +  this.y * v.y + this.z * v.z;
    }
    
    //이항 연산자 /
    Vector operator/(float v) const
    {
        return Vector(this.x / v, this.y / v, this.z / v);
    }
    
    //전위 연산자 --
    Vector& operator--()
    {
        --x;
        --y;
        --z;
        return *this;
    }
    
    //전위 연산자 ++
    Vector& operator++()
    {
        ++x;
        ++y;
        ++z;
        return *this;
    }
    
    //후위 연산자 ++
    Vector& operator++(int)
    {
        Vector temp = *this;
        ++(*this);
        return temp;
    }
    
    friend Vector operator*(float v1, const Vector& v2);
    
    //후위 연산자 --
    Vector& operator--(int)
    {
        Vector temp = *this;
        --(*this);
        return temp;
    }   
}

Vector operator*(float v1, const Vector& v2)
{
    return Vector(v2.x * v1, v2.y * v1, v3.z * v1);
}

int main()
{
    const Vector v1(1,1,1);
    const Vector v2(2,2,2);
    
    Vector v3 = +v1;
    Vector v4 = -v1;
    Vector v5 = ++v3;
    Vector v6 = --v4;
    Vector v7 = v1++;
    Vector v8 = v1--;
    Vector v9 = v1 + v2;
    Vector v10 = v1 - v2;
    Vector v11 = v1 * 8.0f;
    Vector v12 = v1 / 8.0f;
    float v13 = v11 * v12
    
    Vector v14 = 3.0f * v12; 
}

구현한 연산자 오버로딩은 사칙 연산을 부분적으로 구현한 것이다.

 

몇가지 유의사항은 아래와 같다.

 

1.  - 오버로딩 함수

Vector operator-() const
{
    return Vector(-x,-y,-z);
}

- 오버로딩 함수에 const 를 붙이는 이유는 const this 포인터를 사용하기 위함이다. (사용자가 const 객체를 이용할 경우를 대비함)

 

2. 후위 연산자 오버로딩 함수

//후위 연산자 ++
Vector& operator++(int)
{
    Vector temp = *this;
    ++(*this);
    return temp;
}

후위 연산자 오버로딩 함수는 한번의 복사가 필요하다. 

 

 

3. 만든 객체의 연산자 오버로딩 다른 방법

class Vector
{
    //code...
    friend Vector operator*(float v1, const Vector& v2);
}

우선 전역에 함수를 정의 하기전에 Vector class의 private으로 숨긴 데이터를 이용하기 위해 friend 키워드로 접근 가능하게 한다.

Vector operator*(float v1, const Vector& v2)
{
    return Vector(v2.x * v1, v2.y * v1, v3.z * v1);
}

전역으로 위와같이 함수를 구현하면 아래와 같이 순서를 바꿔서도 만들 수 있다. (but 코드구조가 별로 인듯..)

Vector v14 = 3.0f * v12;

 

 

쿠키 코딩 끝!

 

 

 

'Program > C,C++' 카테고리의 다른 글

멤버 함수 포인터  (0) 2023.06.28
const 멤버 함수  (0) 2023.06.28
this 포인터를 이용한 빌더 패턴  (0) 2023.06.26
Class 전방 선언  (0) 2023.06.26
#include <iostream>
#include <functional>

using namespace std;

class Person
{
public:
    void print(int i)
    {
        cout << i << endl;
    }
    
};


int main()
{
    function<void(Person*, int)> func = &Person::print;
    
    Person person;
    func(&person, 1);
}

functional 라이브러리에 포함되어 있는 function <t>를 사용하여 객체의 멤버함수 포인터를 얻는 방법이다.

 

function<void(Person*, int)> func = &Person::print;

위의 코드에서 &Person::print는 pring 함수의 주소 값을 의미한다.

 

func(&person, 1);

func 안에 &person 을 넣는 이유는 클래스 내부의 멤버함수는 항상 암시적으로 this 포인터가 첫 번째 매개 변수로 들어가기 때문이다.

 

배운 것

1. function 을 이용한 class 멤버 함수 포인터 정의 간략화

'Program > C,C++' 카테고리의 다른 글

연산자 오버로딩  (0) 2023.07.02
const 멤버 함수  (0) 2023.06.28
this 포인터를 이용한 빌더 패턴  (0) 2023.06.26
Class 전방 선언  (0) 2023.06.26

+ Recent posts