먼저 디시전트리를 보고 오자.

 

개요

랜덤 포레스트(Random Forest)는 앙상블 학습 방법의 일종으로, 여러 개의 결정 트리를 학습시키고 그들의 예측을 결합하여 작동. 이 방식은 개별 트리의 예측의 정확도를 향상시키며, 과적합을 방지할 수 있다.

 

단계1. 부트스트랩 샘플링 (Bootstrap Sampling):

랜덤 포레스트는 각 트리를 학습시키기 위해 부트스트랩 샘플을 생성.

(Bootstrap"은 통계학에서 무작위로 샘플을 복원 추출하는 방법을 의미)

 

배깅의 기본 아이디어

부트스트랩 샘플은 원본 데이터 세트에서 중복을 허용하여 무작위로 선택된 샘플로 구성되며, 데이터개수는 원본의 크기를 유지.

 

부트스트랩이란 용어에 대해서는 다음을 참고하자.

"Bootstrapping"이라는 용어는 오래된 서양 표현 "to pull oneself up by one's bootstraps"에서 비롯되었습니다. 이 표현은 불가능한 일을 수행하려는 노력을 의미하며, 원래는 물리적으로 자신의 부츠 끈(bootstraps)을 당겨서 자신을 공중에 띄우는 것이 불가능하다는 것을 나타내기 위해 사용되었습니다. 하지만 시간이 지나면서, 이 표현은 더 긍정적이고 상징적인 의미로 발전하게 되었습니다. 이제 "to pull oneself up by one's bootstraps"는 개인이 자신의 노력과 자원으로 어려운 상황을 극복하고 성공을 달성할 수 있음을 나타냅니다. 이는 무엇인가를 시작하거나 개선하기 위해 외부 도움 없이 자신의 능력과 자원을 사용하는 것을 의미합니다.웹 개발과 통계학에서 "Bootstrapping"의 사용은 이러한 개념을 반영합니다:

웹 개발의 부트스트랩:웹 개발 분야에서 부트스트랩은 개발자가 기본 구조와 디자인을 빠르게 설정하고, 프로젝트를 더 빠르게 시작하고 진행할 수 있도록 돕는 프레임워크입니다. 이는 개발자가 외부 디자인 팀이나 추가 자원 없이도 효과적인 웹사이트를 구축할 수 있게 해줍니다.

통계학의 부트스트랩 샘플링:통계학에서 부트스트랩 샘플링은 원본 데이터셋만을 사용하여 통계적 추정을 수행하는 방법을 제공합니다. 이는 외부 데이터 또는 추가 정보 없이도 원본 데이터셋의 특성을 이해하고 분석할 수 있게 해줍니다.

이런 방식으로, "Bootstrapping"은 독립성과 자립성의 중요성을 강조하며, 제한된 자원으로도 무언가를 성취할 수 있음을 상징합니다.

 

단계2. 특성 무작위 선택:

데이터만 무작위 샘플링하는게 아니라, 날씨/온도/습도등 각 노드에서 분할을 수행하는 특성도 일부 특성만을 무작위로 선택하여 사용. 이 방식은 트리의 다양성을 증가시키며, 과적합을 방지

 

단계3. 단계1,2를 통해 생성된 다수의 결정 트리를 개별 학습

랜덤 포레스트는 위의 두 과정을 통해 여러 개의 결정 트리를 독립적으로 학습.

각 트리는 약간 다른 부트스트랩 샘플과 약간 다른 특성 집합을 사용하여 학습됨.

 

단계4. 앙상블을 통한 집계:

분류 문제의 경우, 랜덤 포레스트는 각 트리의 예측을 모아서 투표를 통해 최종 클래스 레이블을 결정.
회귀 문제의 경우, 랜덤 포레스트는 각 트리의 예측을 평균내어 최종 예측을 생성.

 

단계5. 아웃 오브 백 (Out of Bag) 평가:

중복을 허용하는 부트스르랩 샘플링의 특성에 의해 피전홀 원칙에 따라 일부 샘플은 특정 개별 트리 학습과정에서 제외된다(위의 예시에서는 #2번 샘플)
이러한 샘플을 사용하여 트리의 성능을 평가하고, 랜덤 포레스트의 전반적인 성능을 추정할 수 있다.

학습에 사용되지 않은 아웃 오브 백 샘플을 사용하여 각 트리의 성능을 평가하고, 이는 각 트리에 대한 오류율을 측정하는 데 사용될 수 있다.
랜덤 포레스트 평가: 모든 트리의 아웃 오브 백 오류율을 평균하여 랜덤 포레스트의 전반적인 아웃 오브 백 오류율을 계산. 이는 랜덤 포레스트 모델의 전반적인 성능을 추정하는 데 사용될 수 있다.

 

단계6. 일반적인 평가

랜덤 포레스트의 평가에 있어서 아웃 오브 백(Out of Bag, OOB) 평가는 선택적인 방법.
이는 별도의 검증 데이터셋을 필요로 하지 않으므로 유용할 수 있지만, 이는 랜덤 포레스트의 성능을 평가하는 유일한 방법은 아님. 실제로는, 다음과 같은 다양한 평가 방법들이 널리 사용됨

분할 검증 (Holdout Validation):
데이터를 학습 세트와 검증 세트로 분할하고, 학습 세트로 모델을 학습시킨 후 검증 세트로 모델의 성능을 평가.


교차 검증 (Cross-Validation):
데이터를 여러 개의 폴드로 분할하고, 각 폴드를 검증 세트로 사용하여 모델의 성능을 평가.

  • K번의 반복을 수행.
  • 각 반복에서 하나의 폴드를 검증 세트로 선택하고, 나머지  K−1개의 폴드를 학습 세트로 사용
  • 모델을 학습 세트로 학습시키고, 검증 세트로 모델의 성능을 평가.

이 방법은 모델의 성능을 보다 안정적으로 평가할 수 있음


부트스트랩 검증 (Bootstrap Validation):
여러 번의 부트스트랩 샘플을 생성하고, 각 샘플로 모델을 학습 및 검증하여 모델의 성능을 평가.


반응형

'AI, ML > ML' 카테고리의 다른 글

디시전트리기반 코드 실습  (0) 2023.10.16
그레디언트 부스팅 (Gradient Boosting)  (0) 2023.10.15
윈도우 환경에서 ML환경 구축  (0) 2022.03.09
케라스(Keras)  (0) 2020.04.13
Bayesian Online Changepoint Detection 논문리딩  (0) 2019.08.21

Tutorial

여기 따라하면 기본적인건 해볼 수 있다.

 

설치

서버사이드(Java/Maven)

maven의존성

springframework에서 지원하는 java버전이 있고

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-graphql</artifactId>
        </dependency>

 

expediagroup에서 지원하는 kotlin버전이 있다.

        <dependency>
            <groupId>com.expediagroup</groupId>
            <artifactId>graphql-kotlin-spring-server</artifactId>
            <version>7.0.1</version>
        </dependency>

후자가 Resolver(리졸버)만 구현하면 스키마를 자동생성해주고 kotlin 타입과 연동되는등 kotlin을 사용한다면 더 편리한 측면이 있지만, 여기서는 전자를 사용한 기준으로 서술한다.

expediagroup 아티펙트의 경우, 내 경우엔 /graphql 404문제가 해결이 안돼서 springframework를 쓰기로 했다.
추가로 조사해보니 webmvc대신 webflux로 교체해야 호환되는 이슈도 있었다(여기)
spring-boot-starter-web 대신 spring-boot-starter-webflux의존성으로 바꿔야 하는데 WebConfig.kt등 여러곳에서 코딩방식을 바꿔야 하는 걸로 보인다.

 

클라이언트 사이드(React/Next.js)

관련 library설치

npm install @apollo/client graphql

 

아폴로?

Apollo Client는 JavaScript 어플리케이션에서 GraphQL API와 통신할 수 있게 해주는 라이브러리입니다.

Facebook에서는 GraphQL을 발명했으며, Relay라는 GraphQL 클라이언트를 만들어 공개했습니다. 그러나 Apollo가 Relay보다 더 널리 사용되는 이유는
사용자 친화적: Apollo는 사용자 친화적이고, 초보자에게 친숙하며, 설정이 상대적으로 간단합니다. 문서화도 잘 되어 있어, 개발자들이 쉽게 접근하고 사용할 수 있습니다.
커뮤니티 지원: Apollo는 강력한 커뮤니티 지원을 받고 있으며, 다양한 추가 기능과 툴이 개발되고 있습니다. 또한 꾸준한 업데이트와 개선이 이루어지고 있어, 더 많은 개발자들이 Apollo를 선호하게 되었습니다.

 

코드

이번 문서 에서는 특정문제에 대해서 제출된 횟수를 리턴하는 예제를 해보기로 하자.

프로젝트 구성에 대해서는 여기를 보고오자.

 

서버사이드

Query

1. GraphQL 스키마 정의

src/main/resources/graphql/schema.graphqls 파일에 쿼리를 추가

type Query {
  submissionCountByProblem(problemId: ID!): Int
}


2. 서비스 수정

SubmissionService에 getSubmissionCountByProblem매서드를 추가하여 문제별 제출 수를 가져옴 

@Service
class SubmissionService(private val submissionRepository: SubmissionRepository) {

    // 아래는 기존에 존재하던 매서드
    fun submitProblem(userId: Long, problemId: Int, code: String): Submission {
        val submission = Submission(
            userId = userId,
            problemId = problemId,
            code = code,
            status = "PENDING" // 초기 상태
        )
        return submissionRepository.save(submission)
    }

    // 아래 매서드 추가
    fun getSubmissionCountByProblem(problemId: Int): Int {
        return submissionRepository.countByProblemId(problemId)
    }
}

 

3. 컨트롤러에 로직 추가

컨트롤러에 @QueryMapping 어노테이션을 사용하여 submissionCountByProblem GraphQL쿼리를 처리하는 메서드를 추가(엔드포인트 추가는 아니지만 약간 유사)

package com.sevity.problemservice.controller

import ...

@RestController
class SubmissionController(private val submissionService: SubmissionService) {

    // 기존 코드
    // ...

    @QueryMapping
    fun submissionCountByProblem(@Argument problemId: Int): Int {
        return submissionService.getSubmissionCountByProblem(problemId)
    }
}

 

Mutation

//TBD

 

클라이언트사이드

본격적으로 클라이언트 사이드 코딩을 하기전에

https://localhost:9993/graphiql 

위 url에 접속해서 아래 처럼 날려볼 수 있다.

query  {submissionCountByProblem(problemId: 13)}

그럼 결과가 다음처럼 보일 것이다.

 

apollo library 사용을 위해 아래 코드를 기본적으로 넣어준다.

// src/apolloClient.js
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://sevity.com:9993/graphql',  // GraphQL 서버 URL
  cache: new InMemoryCache(),
});

export default client;

 

그다음 _app.js를 수정해서 각 페이지에서 해당 기능을 사용할 수 있게 해준다.

import ...
import { ApolloProvider } from '@apollo/client';
import client from '../apolloClient';  // Adjust the path if necessary

function MyApp({ Component, pageProps }) {
  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  )
}

export default MyApp;

 

실제 조회하는 코드를 [id].js에 넣어준다.(제출 횟수 및 관련 부분)

...
import { useQuery } from '@apollo/client';
import gql from 'graphql-tag';

// 아래 스트링 방식이 후진적으로 보일 수 있는데, 간접적으로 문법검사나, 자동생성 툴이 존재한다.
// 하지만, 결국 서버측에 문자열로 graphQL을 보내야한다. 현재 graphQL의 한계점.
const GET_PROBLEM_SUBMISSION_COUNT = gql`
  query GetProblemSubmissionCount($problemId: Int!) {
    submissionCountByProblem(problemId: $problemId)
  }
`;


const Problem = () => {
  ...
  const { data, loading, error } = useQuery(GET_PROBLEM_SUBMISSION_COUNT, {
    variables: { problemId: Number(id) },
    skip: !id  // id가 없는 경우 쿼리를 건너뜁니다.
  });

  // JavaScript의 옵셔널 체이닝(Optional Chaining)사용('?.'부분들)
  const submissionCount = data?.submissionCountByProblem;
  if(data)
    console.log('GraphQL response:', JSON.stringify(data, null, 2));

...
  
  
  return (
    <div className="container">
      <div className="alert alert-success mt-3">username: {username} </div>
      {problem ? (
        <>
          <h1>{problem.title}</h1>
          <p>{problem.description}</p>
          <p><strong>예제 입력:</strong> {problem.exampleInput}</p>
          <p><strong>예제 출력:</strong> {problem.exampleOutput}</p>
          <p><strong>실제 입력:</strong> {problem.realInput}</p>
          <p><strong>실제 출력:</strong> {problem.realOutput}</p>
          {submissionCount && (
            <p><strong>제출 횟수:</strong> {submissionCount}</p>
          )}
          <textarea
            className="form-control"
            rows="10"
            value={sourceCode}
            onChange={(e) => setSourceCode(e.target.value)}
          ></textarea>
          <button className="btn btn-primary" onClick={handleSubmit}>
            제출
          </button>
        </>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
};

export default Problem;

 

 

트러블슈팅

/graphql 404

src/main/resources/graphql 폴더와 그안에 anyname.graphqls 파일을 생성안하면 /graphql 경로접근시 404뜨는 문제가 있어서 반나절 이상 소모했다 ㅠ

여기에도 기록함

 

expediagroup 아티펙트의 경우, 위의 해결책을 적용해도 여전히 404가 떴고, 추가로 조사해보니 webmvc대신 webflux로 교체해야 하는 이슈가 있었다(여기)
spring-boot-starter-web 대신 spring-boot-starter-webflux의존성으로 바꿔야 하는데 WebConfig.kt등 여러곳에서 코딩방식을 바꿔야 하는 걸로 보인다.

반응형

개요

RESTful API: N endpoints
GraphQL: 단일 endpoint

조회는 Query, 생성/수정/삭제는 Mutation 이라고 함

 

명시적 정의와 자유도

  • 명시적 정의
    • GraphQL은 서버 개발자에게 데이터 타입, 쿼리, 뮤테이션을 명시적으로 정의하도록 요구합니다. 
      이는 GraphQL 스키마에서 수행되며, 서버가 제공할 수 있는 데이터와 작업을 명확하게 정의합니다.
      예를 들어, 서버 개발자는 사용자 데이터를 반환하는 getUser 쿼리와 사용자 데이터를 업데이트하는 updateUser 뮤테이션을 스키마에 정의할 수 있습니다.
  • 클라이언트의 자유도 (Client Flexibility):
    • 클라이언트는 서버에 정의된 스키마를 기반으로 데이터를 요청합니다. 
      • 필드 선택 (Field Selection):
        • 클라이언트는 쿼리를 통해 필요한 필드만 선택할 수 있습니다. 예를 들어, 사용자 객체에 대한 쿼리를 만들 때, 클라이언트는 이름과 이메일만 요청할 수 있으며, 다른 필드는 무시할 수 있습니다. 이는 네트워크 트래픽을 최적화하고, 필요한 데이터만 가져오도록 할 수 있습니다.
      • 중첩 및 복잡한 쿼리 (Nested and Complex Queries):
        • 또한, 클라이언트는 중첩된 쿼리를 생성하여 관련된 객체와 필드를 한 번의 요청으로 가져올 수 있습니다. 이는 데이터의 관계와 구조를 유연하게 표현할 수 있으며, 복잡한 데이터 요청을 단순화할 수 있습니다.
      • 응답 구조의 맞춤화 (Customized Response Structure):
        • 클라이언트는 요청의 구조를 지정할 수 있으며, 서버는 클라이언트의 요청에 따라 응답을 제공합니다. 이는 클라이언트가 받고 싶은 데이터의 구조를 정확하게 지정할 수 있게 해줍니다.

스키마

아래와 같은 스키마 개념이 있다.(여기 참조)

type Query {
  bookById(id: ID): Book
}

type Book {
  id: ID
  name: String
  pageCount: Int
  author: Author
}

type Author {
  id: ID
  firstName: String
  lastName: String
}

 

 

장단점

 

Q&A

  • 아니 무슨 단일 endpoint라고 자랑하더니 mutation마다 명세서를 만들어야 하는거 같네.(암튼 클라이언트 맘대로 쿼리하는 방임형 자유는 아닌거 같네) 이럴거면 endpoint 여러개인거랑 뭐가 다르지? Query의 경우는 개별 명세서가 필요 없나?
  • federation?
반응형

'System Architect' 카테고리의 다른 글

위임(delegate) 패턴  (0) 2024.02.17
Application  (0) 2023.10.28
gRPC  (0) 2023.10.11
시스템설계 Q&A 2  (0) 2023.09.20
데이터 분석 관련 정리  (0) 2023.08.19

+ Recent posts