LangGraph Essentials

IT/뻘짓 2025. 11. 12. 20:19
반응형

해당 글은 https://academy.langchain.com/ 의 Quickstart: LangGraph Essentials 내용을 정리한 것으로, 영상없이 빠르게 내용을 확인하고 싶은 경우에 읽으시면 도움이 됩니다.

Quickstart: LangGraph Essentials

1. LangGraph Orientation

LangGraph란 AI agent와 application을 위한 지속 가능한 런타임을 제공하는 프레임 워크

AI application을 시작하는것은 쉬우나, 이를 맞춤화하고 확장하는 것은 어렵다는 점에서 사용되는 프레임워크

image Gen에서의 ComfyUI와 비슷한 느낌이라 생각하면 된다.

기본 구성 요소:

LLM application에서의 문제점 : 지연시간, 신회성, 응답의 비결정론적 특성

Lang Graph는 다음 방법으로 이 문제점을 해결(사실 해결이라기보단 완화에 가깝다)한다.

  1. 지연시간

병렬 실행, 스트리밍을 지원

  1. 신뢰성

체크포인팅 기능.(중단지점부터 실행가능)

  1. 응답의 가변성

인간의 입력을 기다리는 인간 개입 루프

LangSmith를 통한 추적 및 평가

Lang Graph는 기본 런타임 위에 두개의 사용자 SDK가 있으며, 여기서 StateGraph 가 우리가 흔히 부르는 LangGraph이기도 하다.

이때 LLM, 도구 등의 구성요소는 LangChain 라이브러리에서 제공된다.

LangSmith Deployment에서 호스팅되며, Studio와 LangSmith를 통해 디버깅, 평가, 테스트가 가능하다.

환경설정

academy.langchain.com에서 예시 코드를 수행해볼 수 있다.

JS를 사용하기 위한 방법은 다음과 같다.

먼저 Node.js 20+, Package manager(npm or yarn), OpenAI API key (엔트로픽, langsmith도 있으면 더 좋다)를 준비하고,

demo를 git 클론하고, env에 API key를 넣은 뒤, pnpm으로 의존성을 설치한다(readme 참조)

만약 node.js 20이 깔기 어렵거나, pnpm 명령어가 안먹는다면?

  1. curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash # nvm 설치
  2. source ~/.bashrc # 쉘 다시 호출 , ( exec $SHELL 를 사용해도된다)
  3. nvm install 22 # 원하는 버전으로 설치한다.
  4. nvm use 22
  5. nvm alias default 22
  6. node -v # 기존에 apt install nodejs로 깔린 버전이 있어도, 일단 현제 쉘에서 20+ 나오면 우선 버전이 20+이기에 OK
  7. corepack enable
  8. corepack prepare pnpm@latest --activate # pnpm 활성화
  9. pnpm -v # 숫자나오면 OK

이제 문서상에 있는 실행 예제를 실행한다.

L2 이메일 워크플로

# email processing in Langsmith Studio
pnpm dev

Python기반을 사용하기위한 방법은 다음과 같다.

python의 경우 기본적으로 conda 환경에 가상환경을 하나 만들어 사용하는 것을 추천하며, Jupyter notebook이나 lab을 사용할 것이기에 환경 생성 후 커널을 추가해준다.

그리고 마찬가지로 git clone 후 .env 파일을 편집해주고, 필요한 패키지를 설치해준다.

Python 환경의 경우 딱히 문제가 생길 소지가 없기에 Readme를 그대로 따라가면 된다.

나의 경우 익숙한 Python 환경에서 수행한다.

ipynb의 경우 md 블록에 설명이 상세하니 해당 부분을 참고해 순서대로 수행해보면 된다.

2. LangGraph foundations

LangGraph의 StateGraph는 5가지의 구성요소가 있다.

  1. State: Data
  2. Node: Functions
  3. Edges: Control Flow
  • Serial, Parallel
  • Conditional
  1. Checkpointing/Memory
  2. Human in the loop : interrupts

LangGraph를 low level 언어라고 생각해보자, (low level일수록 좀더 기계친화적, 예를들면 어셈블리어는 C보다 lowlevel이고, Python은 high level이다)

State는 Application의 실제 Data이고, Node는 해당 Data를 처리하는 함수이며, Edge는 Application의 제어 흐름을 제공한다. 또한 정적이거나 조건부일 수 있으며, 병렬 또는 직렬로 실행 가능하다.

State는 Checkpointing을 통해 지속될 수 있고, Control Flow는 사용자의 상호작용을 위해 정지를 수행 가능하다.

StateGraph

이름과 같이 StateGraph는 State와 그래프를 가지고 있으며, State는 Data이다. 이는 그래프에 적용되고, 그래프에 의해 업데이트되어 사용자에게 반환되며, 그래프 자체는 상태가 없다.

그래프를 정의처럼 다룰수도 있는데, 그래프를 정의할 때는 먼저 그래프가 작동할 상태를 정의하면 이 상태가 그래프의 모든 노드에서 공유된다.

state는 Python 데이터 구조로써, 예제상에서는 단일 필드(문자열 목록)만 있는 타입이 지정된 dict를 사용한다. 따라서 그래프가 호출되면 state가 초기화되고, 그래프 실행 중에 LangGraph 런타임은 실행할 노드를 선택한 다음 현재 상태를 넘겨주고 노드를 실행한 후 최종적으로 state를 업데이트한다.

state 업데이트에는 노드의 결과를 사용하는데, 노드는 함수에 불과하기에 주요 인수는 state고 출력 또한 state에 대한 업데이트이다. (여기서는 문자열 목록에 대한 업데이트) state는 시간의 흐름과 관계없이, 노드에 오류가 발생시에도 유지될 수 있다. 따라서 실행중 노드에 오류 발생시 노드를 다시 시작하고 상태를 복원한 후 함수를 처음부터 다시 실행 가능하다.

이러한 상태 유지를 통한 Application의 복원 탄력성이 langgraph의 핵심 기능중 하나이다(다만 오류 감지를 위해 플렛폼의 지원이 필요).

상태와 노드만 있고, 오른쪽에 단일 노드가 있는 그래프를 만들어보자.

그래프를 만들 떄 가장 먼저 해야할 일은 상태 정의로써 class state를 dict type으로 정의한다. 매개변수는 nlist이고 리스트이며 유형은 문자열이다.(state는 data class가 될수도 있고, Pydantic 기반 모델이 될 수도 있다.)

class State(TypedDict):
    nlist: List[str]

다음으로 노드를 정의해보면, 노드는 state를 받아서 state에 대한 업데이트를 반환하는 함수이다. 따라서 노드 A를 정의해 state를 받아 state에 대한 업데이트를 반환하고, 그 업데이트가 새 문자열이 되도록 한다.

def noda_a(state: State) -> State:
    print(f"node a is receiving {state['nlist']}")
    note = "Hello World!"
    return(State(nlist = [note]))

노드 a에서 Hello world를 반환하고 nlist가 새 문자열을 포함하는 리스트로 설정된 state 리스트를 반환한다. 디버그용 로그를 추가해보면 노드 A는 state['nlist']를 수신한다.

다음으로 그래프를 구축해보자.

bulider = StateGraph(State) # 이를 위해 StateGraph 객체를 상태(state)로 인스턴스화하고, 방금 만든 노드를 추가한다.
bulider.add_node("a", node_a) # 이 노드를 A라고 부르고, 함수 node_a를 추가한 다음,
bulider.add_edge(START, "a") # START에서 A로 가는 간선을 추가한다. 이 경우 Start는 LangGraph에서 가져온 상수이며,
bulider.add_edge("a", END) # 다음으로 A에서 END로 가는 간선을 추가한다. 
graph = bulider.compile() # 마지막으로 그래프를 컴파일한다.

이제 그래프를 실제로 표시해보자. LangGraph의 draw mermaid 유틸리티를 사용하여 모든 작업이 제대로 완료되었는지 확인할 수 있다.

display(Image(graph.get_graph().draw_mermaid_png()))) 
#그래프의 그래프 표현을 가져온 다음 
# draw_mermaid_png 함수를 호출

이 다이어그램을 직접 생성하는 코드를 얻으려면 draw mermaid 메소드를 호출 할 수도 있다.

print(graph.get_graph().draw_mermaid()) # 결과는 코드로 나오며 이 코드를 웹에 임베드할 수 있다.

다시 초기 상태로 그래프를 호출해보자.

initial_state = State( nlist=["Hello Node a, how are you?"]) # 이 초기 상태 변수를 nlist가 있는 상태로 설정
graphinvoke(initial_state) # 이 초기 상태를 사용하여 그래프를 호출
# node a is receiving ['Hello Node a, how are you?']
# {'nlist': ['Hello World']}

이제 노드 a가 들어오는 값을 출력하는걸 보면 nlist가 새 메모로 덮어씌워진 것을 볼 수 있다.

핵심은 그래프와 상태를 정의할 때 모든 노드가 동일한 상태를 공유할 수 있다는 것이다. 상태는 타입 딕셔너리, Python 데이터 클래스 또는 Pydantic 기본 모델이 될 수 있고, 노드는 함수일 뿐이라는 것, 그리고 그래프를 실행할 때 LangGraph 런타임이 어떻게 동작하는지를 확인할 수 있다.

Edges

위에서 state와 node를 확인했다면, 또다른 핵심 구성요소는 edge이다. edge는 한 노드에서 다음 노드로 제어를 전달한다.
직렬의 경우, 첫 번째 단계가 끝나면 LangGraph 런타임이 두 번째 노드를 초기화하고 실행하며, 병렬의 경우, 다음 세 노드가 모두 병렬로 실행된다.
여러 단계를 동시에 실행할 수 있기 때문에 노드 간 이러한 단계를 슈퍼 스텝이라고 하며, 더 자세히 살펴보겠지만. 지금은 이 용어를 기억해 두자. 왼쪽 두 예시는 static edge로, 항상 실행된다는 것을 의미하며, 오른쪽 점선으로 표기되는 두 Edge는 은 Conditional Edge이다.

Conditional Edge에지는 조건에 따라 노드에 제어권을 넘겨주는 의사 결정을 가능하게 하기 때문에 매우 유용하다.
Conditional의 경우 , 왼쪽 edge가 선택되도록 해 노드를 실행했고, 오른쪽은 선택되지 않았다. 따라서 오른쪽 노드는 실행되지 않는다.

그래프와 같이 모든 활성 노드가 완료되고 값을 state에 저장한 후 다음 노드로 계속 진행하는 것을 슈퍼스탭이라고 한다. 왼쪽 그래프에서 주황색 노드의 입력상태는 파란색 노드의 모든 업데이트 결과이며,
가운대와 같이 경로의 길이가 다른 경우 LangGraph에서는 우측노드의 실행 시점을 지정할 수 있다. 일반적으로 초록 노드 완료시 활성화되고, 보라색 노드의 입력으로 사용될 수도 있으나,

실행을 지연시켜 오른쪽노드의 출력이 보라색 노드와 동일한 슈퍼스텝에서 상태에 추가되도록 할 수도 있다.

병렬 실행과 상태 쓰기에 대해 자세히 살펴보자. 이 단계의 시작 부분에서 노드들은 현재 상태를 제공받고, 실행 후에는 공유 상태를 업데이트한다.

그런데 모든 노드가 상태의 nlist 속성에 대한 업데이트를 반환하면 어떻게 될까?

기본적으로 StateGraph에서 살펴본 바와 같이 마지막에 작성된 값이 이전 상태를 덮어쓴다.

이 문제는 리듀서 함수로 해결 가능하다. 리듀서라는 이름은 일반적인 용어인 MapReduce에서 유래되었으며, 이 함수를 사용하면 동일한 상태 키에 대한 여러번의 기록을 어떻게 처리할지 정의할 수 있다.
노드 실행이 끝날 때 상태 값이 업데이트되면 LangGraph는 해당 키에 대해 제공된 리듀서 함수를 사용하여 상태를 어떻게 업데이트할지 결정할 수 있다.

이 예시에서는 리듀서 함수를 operator dot add로 설정했기 때문에 값이 이전 값을 덮어쓰는 대신 값이 list에 추가된다.

이 리듀서 함수는 직접 정의할 수 있으며, 직접 맞춤형 리듀서를 만들 수도 있다. 이를 위해 state를 정의할 때 state변수를 주석으로 달았으며, 구문은 이미지와 같다. 첫번째 인수는 type, 두번째 reducer function은 메타데이터이며, LangGraph는 이를 사용해 리듀서 동작을 지정한다.

edge와 병렬 실행에 대해 알아보자.

이 다이어그램은 세 개의 노드가 모두 병렬로 실행되는 다이어그램이다. 지금 만들 그래프도 바로 이 그래프로,

보라색 추가 노드에 대해서는 두 개의 서로 다른 분기가 있으며, 노드의 병렬 실행 또한 같이 살펴본다. 이러한 상황에서 상태 업데이트가 왜, 그리고 어떻게 발생하는지 이해해보자.

from IPython.display import Image, display
import operator
from typing import Annotated, List, Literal, TypedDict
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt
# 먼저 필요한 모듈을 임포트

class State(TypedDict):
    nlist: Annotated[List[str], operator.add]
# 이전에 정의했던 것과 동일한 상태를 다시 생성, nlist는 문자열 목록이고, 이번에는 리듀서를 도입.
# reducer 함수로 operator.add를 사용하면, 이전 값을 덮어쓰지 않고 모든 리스트 업데이트를 상태에 연결 가능.



def node_a(state: State) -> State:
    print(f"Adding 'A' to {state['nlist']}")
    return(State(nlist = ["A"]))

def node_b(state: State) -> State:
    print(f"Adding 'B' to {state['nlist']}")
    return(State(nlist = ["B"]))

def node_c(state: State) -> State:
    print(f"Adding 'C' to {state['nlist']}")
    return(State(nlist = ["C"]))

def node_bb(state: State) -> State:
    print(f"Adding 'BB' to {state['nlist']}")
    return(State(nlist = ["BB"]))

def node_cc(state: State) -> State:
    print(f"Adding 'CC' to {state['nlist']}")
    return(State(nlist = ["CC"]))

def node_d(state: State) -> State:
    print(f"Adding 'D' to {state['nlist']}")
    return(State(nlist = ["D"]))
# 노드를 정의한다.
# 각 노드는 상태를 받아서 자신의 이름, 입력값을 출력한 뒤
# 자신의 label을 nlist에 대한 업데이트로 반환.

# 그래프를 만들자.
builder = StateGraph(State)
# 먼저 stategraph를 state와 함께 인스턴스화.

# Add nodes
builder.add_node("a", node_a)
builder.add_node("b", node_b)
builder.add_node("c", node_c)
builder.add_node("bb", node_bb)
builder.add_node("cc", node_cc)
builder.add_node("d", node_d)
# 위에서 정의한 노드를 추가

# Add edges
builder.add_edge(START,"a")
builder.add_edge("a", "b")
builder.add_edge("a", "c")
builder.add_edge("b", "bb")
builder.add_edge("c", "cc")
builder.add_edge("bb", "d")
builder.add_edge("cc", "d")
builder.add_edge("d",END)
# Start->A, A-B, A-C, B-BB, C-CC, BB-D, CC-D, D-END로 edge를 추가

# Compile and display
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))
# 마지막으로 그래프를 컴파일한 뒤 Draw Mermaid 함수로 표시하면
# 위의 보래색과 동일한 그래프가 생성. 

이제 실행해보면

initial_state = State(
    nlist = ["Initial String:"]
)
# 그래프를 호출하는 초기 상태는 단일 문자열이 포함된 리스트로 제공한다.

graph.invoke(initial_state)

# 그럼 다음과 같은 결과가 나온다.
# Adding 'A' to ['Initial String:']
# Adding 'B' to ['Initial String:', 'A']
# Adding 'C' to ['Initial String:', 'A']
# Adding 'BB' to ['Initial String:', 'A', 'B', 'C']
# Adding 'CC' to ['Initial String:', 'A', 'B', 'C']
# Adding 'D' to ['Initial String:', 'A', 'B', 'C', 'BB', 'CC']
# {'nlist': ['Initial String:', 'A', 'B', 'C', 'BB', 'CC', 'D']}

먼저, 노드 A가 실행되고 A를 추가한다. 노드 A가 실행될 때 관찰한 유일한 상태는 그래프를 호출할 때 전달한 초기 상태이며 다른 노드들은 아직 레이블을 추가하지 않은 상태이다.이는 실행 순서에서 A가 첫 번째이기 때문이며,

노드 C와 B는 같은 슈퍼 스텝 내에서 실행된다.
둘 다 실행 시 동일한 상태라는것을 확인 가능한데, 이는 둘중 하나가 다른것보다 먼저 실행되거나 늦게 실행될 수 없다는 것을 의미한다.

이제 BB가 실행되고 CC가 다음 슈퍼 스텝을 실행한다. 이때 두 노드 모두 노드 B와 C의 상태 업데이트를 볼 수 있다는 것을 알 수 있다. 여기서 주의해야 할 점이 이 동작이 LangGraph의 중요한 기능을 강조한다는 점으로, Edge는 제어 흐름을 정의하지만, 노드가 접근할 수 있는 데이터를 제어하지는 않는다는 것이다. 따라서 노드가 실행되면 “병렬 분기에서 작성된 값을 포함한 “현재 그래프 상태에 접근할 수 있는 것이다.

다시 말해, BB는 노드 B뿐만 아니라 노드 C의 상태 업데이트에도 접근할 수 있고, 마찬가지로 CC는 노드 B와 노드 C의 상태 업데이트에도 접근할 수 있다. 두 경우 모두 노드 A에도 접근할 수 있다. 그렇기에 노드 BB와 CC가 state에 자신의 레이블을 추가하는 것을 볼 수 있다.

그리고 D가 실행되면 이전의 모든 추가된 상태를 수신하고 D를 추가한다.

마지막으로 그래프는 모든 노드의 레이블을 포함하는 최종 병합 상태 목록을 반환한다.

그럼 핵심은 무엇인가?

  1. 상태를 설정할 때, 상태 정의에서 리듀서 함수를 사용하여 상태가 어떻게 누적될지 변경할 수 있으며,
  2. 그래프를 작성할 때 'add edge'를 사용하여 병렬 경로를 생성할 수 있다.
  3. 실행 중 노드 B와 C는 병렬로 실행된다. 그래프는 리듀서 함수를 사용하여 각 노드에서 반환된 값을 병합한 다음, 노드 B와 C의 결과를 노드 BB와 CC를 시작하기 전에 상태에 저장한다. 이때, 제어는 에지를 따르지만, 데이터는 그렇지 않다.

Conditional Edges

이번에는 Conditional 즉 조건부 edge에 대해 확인해보자.

그림에 나와있는 (왼쪽)다이어그램상 조건에 따라 왼쪽 또는 오른쪽으로 분기되는 조건부 edge가 있다. 이번에는 이 그래프를 생성해보자. 점선은 Conditional 조건부이며, 실선은 static 정적 edge이다.

from IPython.display import Image, display
import operator
from typing import Annotated, List, Literal, TypedDict
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt
# 필요한 모듈을 가져온뒤

class State(TypedDict):
    nlist : Annotated[list[str], operator.add]   

# state는 지난번과 동일하게, 주석이 달린 구문을 사용하여 상태 업데이트를 누적하고, 이전의 내용을 덮어쓰지 않는다.
# 이전 코드에서는 노드간의 정적 간선을 정의했으나, 이번에는 그래프가 동적으로 어떤 간선을 선택할지 결정한다.
def node_a(state: State) -> Command[Literal["b", "c", END]]:
    select = state["nlist"][-1]
    if select == "b":
        next_node = "b"
    elif select == "c":
        next_node = "c"
    elif select == "q":
        next_node = END
    else:
        next_node = END

    return Command(
        update = State(nlist = [select]),
        goto = [next_node]
    )

def node_a(state: State):
    return

def node_b(state: State) -> State:
    return(State(nlist = ["B"]))

def node_c(state: State) -> State:
    return(State(nlist = ["C"]))
# 노드 a, 노드 b, 노드 c라는 세 개의 노드를 정의한다. 
# 노드 a는 반환만 하고 상태를 업데이트하지 않는다. 
# 노드 b는 상태에 레이블을 추가하고, 
# 노드 c도 상태에 레이블을 추가한다.

# 이제 그래프를 만들자. 

builder = StateGraph(State)
# stategraph를 state와 함께 인스턴트화 한 뒤

# 노드를 만들고
builder.add_node("a", node_a)
builder.add_node("b", node_b)
builder.add_node("c", node_c)

# 시작에서 a, a에서 end, c에서 end로 가는 간선을 추가한다.
builder.add_edge(START, "a")
builder.add_edge("b", END)
builder.add_edge("c", END)

# 마지막으로 Draw Mermaid 함수로 그래프를 컴파일하고 표시하면 
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))

그래프 모양이 좀 이상한 이유는 b 노드와 c노드에 늘어오거나 나가는 정적 간선을 정의하지 않았기 때문인데, 정의하지 않은 이유는 a에서 end로, a에서 b로, a에서 c로 가는 조건부 간선이 있기를 원하기 때문이다.

이를 상태를 받아들이고 다음으로 분기할 노드를 문자열로 반환하는 조건부 간선 함수로 정의할 수 있다.

from IPython.display import Image, display
import operator
from typing import Annotated, List, Literal, TypedDict
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt

class State(TypedDict):
    nlist : Annotated[list[str], operator.add]   

def node_a(state: State):
    return

def node_b(state: State) -> State:
    return(State(nlist = ["B"]))

def node_c(state: State) -> State:
    return(State(nlist = ["C"]))

###########이제 조건부 간선을 추가하면

def conditional_edge(state: State) -> Literal["b", "c", END]:
    select = state["nlist"][-1]
    if select == "b":
        return "b"
    elif select == "c":
        return "c"
    elif select == "q":
        return END
    else:
        return END
# 그래프 상태를 받아들이고 b,c 또는 end라는 Litreal을 반환하는 조건부 간선을 정의한다.
# end는 LangGraph에서 가져온 예약된 노드 레이블이다.
# 이 조건부 간선 함수는 단순히 state에 마지막으로 기족된 값을 가져오기에
# b였다면 노드 b로 분기하고, c였다면 c, q였다면 end로 분기한다.
# 그렇지 않은 예외 발생시 끝으로 돌아간다.


builder = StateGraph(State)

builder.add_node("a", node_a)
builder.add_node("b", node_b)
builder.add_node("c", node_c)
# 이제 조건부 간선을 추가하는 구문으로 그래프에 조건부 간선을 추가한다.
builder.add_conditional_edges("a", conditional_edge)

builder.add_edge(START, "a")
builder.add_edge("b", END)
builder.add_edge("c", END)

# 마지막으로 Draw Mermaid 함수로 그래프를 컴파일하고 표시하면 
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))  

원하던 형태의 조건부 간선이 그려진다.

이제 사용자로부터 입력을 받아 이 입력으로 입력 state를 생성하고 해당 state로 그래프를 호출해보자

user = input('b, c, or q to quit: ')

input_state = State(
    nlist = [user]
)
graph.invoke(input_state)

# b, c, or q to quit:  
# {'nlist': ['', '']}
# {'nlist': ['b', 'B']} 입력에 맞게 노드로 이동하는 것을 확인할 수 있다.
# {'nlist': ['c', 'C']}
# {'nlist': ['q']}

다음으로 LangGraph의 내장함수로 조건부 분기를 구성해보자

from IPython.display import Image, display
import operator
from typing import Annotated, List, Literal, TypedDict
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt

class State(TypedDict):
    nlist : Annotated[list[str], operator.add]   

# 동적 간선 함수 대신 노드 a가 다음에 방문할 노드를 선택하게 하려면 노드 a를 수정한다.
# def node_a(state: State):
#     return
# 대신 다음과 같이 하면 선택 로직이 노드 a에 포함된다.
def node_a(state: State) -> Command[Literal["b", "c", END]]: # 그래프 상태를 수신하고, 상태에 마지막으로 기록된 값을 기준으로 선택한다.
    # 이때 함수가 명령을 반환할 것이라 명시했고, 그에따라 B,C,END중 하나를 반환한다.
    # 이런 반환 유형 주석은 그래프 작동에 실제로 영향을 주진 않지만 
    # 그래프 그림이 정확히 만들어질 수 있게 해준다
    # 만약  -> Command[Literal["b", "c", END]]부분을 제거하면 
    # 동작은 동일할지라도 그래프 이미지는 다시 이전처럼 이상한 모양이 나온다
    select = state["nlist"][-1]
    if select == "b": # 값이 B이면 노드 B로, 값이 C이면 노드 C로, 값이 Q이면 노드 End로 이동하고, 이외의 값이면 노드 End로 이동한다. 

        next_node = "b"
    elif select == "c":
        next_node = "c"
    elif select == "q":
        next_node = END
    else:
        next_node = END
        # return에서 달라진 부분은 상태를 업데이트하기 위해 단순히 값을 반환하는 대신 명령을 반환한다.

    return Command(
        update = State(nlist = [select]), # 명령어를 사용해 이전과 같이 상태를 업데이트한다 (마지막 값을 복제)
        goto = next_node # 그래프가 이동할 다음 노드를 지정한다.
                # 유의할 점은 goto에 지정된 값은 런타임중에만 확인되기에, 이 문자열을 노드의 이름과 일치시켜야만 
                # 또한 goto가 노드 목록일 수도 있다는 점을 유용히 사용하자.
                # 즉, goto = [next_node]여도 동일하게 동작한다.
    )

**def node_b(state: State) -> State:**
    return(State(nlist = ["B"]))

def node_c(state: State) -> State:
    return(State(nlist = ["C"]))

def conditional_edge(state: State) -> Literal["b", "c", END]:
    select = state["nlist"][-1]
    if select == "b":
        return "b"
    elif select == "c":
        return "c"
    elif select == "q":
        return END
    else:
        return END

builder = StateGraph(State)

# Add nodes
builder.add_node("a", node_a)
builder.add_node("b", node_b)
builder.add_node("c", node_c)

# Add edges
builder.add_edge(START, "a")
builder.add_edge("b", END)
builder.add_edge("c", END)
# builder.add_conditional_edges("a", conditional_edge) 이제 이 줄을 제거해도
# 위 코드와 같이 간선이 제대로 랜더링된다.

# Compile and display
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))

# 이제 기능이 잘 작동하는지 확인을 위해 반복마다 사용자 입력을 받게 해보자.
while True:
    user = input('b, c, or q to quit: ')
    print(user)
    input_state = State(nlist =  [user])
    result = graph.invoke(input_state)
    print( result )
    if result['nlist'][-1] == "q":
        print("quit")
        break


 # b
 # {'nlist': ['b', 'b', 'B']}
 # c
 # {'nlist': ['c', 'c', 'C']}
 # q
 # {'nlist': ['q', 'q']}
 # quit

이상으로

  1. 그래프를 만들 때 그래프 상태를 받아서 다음 노드를 결정하는 조건부 간선 함수를 사용하여 조건부 간선을 추가할 수 있다는것.
  2. 또한 nodes의 반환문에 명령을 사용하여 update로 그래프 상태를 업데이트하고 goto로 제어 경로를 업데이트할 수도 있다는 것

을 확인했고, 두 방법 모두 유효하다

Memory/ Checkpointers

다음으로, 체크포인터를 사용하여 그래프에 메모리를 추가하여 장기 지속성을 구현하는 방법에 대해 확인해보자.

여지것 노드들이 순차적으로 실행되는 것을 확인했고, 특정 단계에서는 병렬로 실행될 수도 있으며, 이러한 과정을 Super Step이라 부른다는 것을 확인했다. 상태는 그래프 전체에서 공유되며, 슈퍼 스텝 시작 시 노드에 제공되고, 슈퍼 스텝 종료 시 해당 노드에 의해 업데이트 된다.

다음으로 실행간 그래프 상태를 유지하기 위한 지속성 개념을 확인해보자.

체크포인터를 사용하여 메모리를 할당할 수 있다. 체크포인터는 각 단계가 끝날 때 상태를 지속성을 가지는 저장소에 저장하는데, 이는 쉽게말해 해당 시점의 상태 스냅샷을 찍는 것과 동일하다.

스레드는 시간 경과에 따라 모인 이러한 체크포인트들의 집합으로, 각 실행 단계의 상태에 대한 전체 이력을 나다낸다. 체크 포인트 사용시의 이점은 다음과 같다.

  1. 노드가 실패하더라도 상태를 복원하고 진행상황을 지속하며 다시 작업 수행이 가능
  2. 이전 시점의 상태를 복원할 수 있음. 예를들어 실행시간이 긴 agent가 길을 잃었다 하더라도 문제없이 필요한 시점. 즉 정상적으로 작동하기까지의 시점을 다시 복원 가능하다
  3. 체크포인팅을 사용시 지속적으로 상태를 유지할 수 있고, 이는 그래프가 “실행중이 아닐 때에도” 상태가 유지된다.
  4. 어느 단계에서든 상태를 복원할 수 있다. 즉, 노드가 중단되어도 중단했던 위치부터 바로 실행이 가능하다.(이 부분은 추후 다룰Interrupt/HIL(Human In The Loop)에서 좀더 상세히 다룬다)

이제 그래프에 메모리를 추가해보자

from IPython.display import Image, display
import operator
from typing import Annotated, List, Literal, TypedDict
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt

class State(TypedDict):
    nlist : Annotated[list[str], operator.add]   
def node_a(state: State) -> Command[Literal["b", "c", END]]:
    select = state["nlist"][-1]
    if select == "b": 

        next_node = "b"
    elif select == "c":
        next_node = "c"
    elif select == "q":
        next_node = END
    else:
        next_node = END

    return Command(
        update = State(nlist = [select]), 
        goto = next_node 
    )

**def node_b(state: State) -> State:**
    return(State(nlist = ["B"]))

def node_c(state: State) -> State:
    return(State(nlist = ["C"]))

def conditional_edge(state: State) -> Literal["b", "c", END]:
    select = state["nlist"][-1]
    if select == "b":
        return "b"
    elif select == "c":
        return "c"
    elif select == "q":
        return END
    else:
        return END

builder = StateGraph(State)
builder.add_node("a", node_a)
builder.add_node("b", node_b)
builder.add_node("c", node_c)
builder.add_edge(START, "a")
builder.add_edge("b", END)
builder.add_edge("c", END)
graph = builder.compile()
display(Image(graph.get_graph().draw_mermaid_png()))

# 위 부분까진 동일하며, 이제 이 조건부 간선 그래프에 체크포인트를 추가해 상태를 지속시킨다.
# LangGraph는 기본적으로 세 가지 체크포인트 구현을 제공한다. 
# 체크포인트 정보를 메모리에 저장하는 인메모리 세이버(in-Memory Saver),
# 체크포인트를 해당 데이터베이스에 저장하는 Postgres 세이버(Postgres Saver) ,
# SQL 라이트 세이버(SQL Light Saver)가 있다.

# 여기서는 설정이 가장 간단한 인메모리 세이버를 사용한다.
from langgraph.checkpoint.memory import InMemorySaver
memory = InMemorySaver() # LangGraph 체크포인트 메모리에서 임포트한 후 인스턴스화하고 스레드 ID를 구성.

config = {"configurable": {"thread_id": "1"}} # 이 커스텀 가능한 키가 있는 dict 구조가 실제로 그래프에 구성을 전달하는 방법이다.
# 일단 임의의 스레드 id로 1을 주고
graph = builder.compile(checkpointer=memory)
# 실행하면 그래프에 메모리가 생기기에, 실행해보면

while True:
    user = input('b, c, or q to quit: ')
    input_state = State(nlist = [user])
    result = graph.invoke(input_state, config )
    print( result )
    if result['nlist'][-1] == "q":
        print("quit")
        break


 # b
 # {'nlist': ['b', 'b', 'B']}
 # c
 # {'nlist': ['b', 'b', 'B','c', 'c', 'C']}
 # q
 # {'nlist': ['b', 'b', 'B','c', 'c', 'C','q', 'q']}
 # quit
 # 위와 같이 반복 루프에서 상태가 계속 누적되는 것을 확인할 수 있다.

여기서 기억할 부분은

  1. 메모리를 설정할 때 체크포인터를 인스턴스화하는데, 여기서는 메모리 내 저장 기능을 사용했지만, 다양한 옵션이 있다. (memory = InMemorySaver() )
  2. 그래프를 작성할 때 해당 체크포인터를 사용하여 그래프를 컴파일한다. (graph = builder.compile(checkpointer=memory))
  3. 그래프 실행 중에는 구성 변수에 설정한 스레드 ID로 그래프를 호출한다. 동일한 스레드 ID를 사용하는 경우 그래프 실행간에 상태가 유지된다 (config = {"configurable": {"thread_id": "1"}})

Human In the Loop: Interrupt

다음으로 인터럽트에 대해 알아보고, 루프 내에서 HIL을 어떻게 구현할 수 있는지 알아보자.

지금까지 한 내용에서 그래프를 호출하고 실행하는 루프가 있고. 이 루프는 Python 런타임으로 돌아와서 반복된다. 방금까지 실행한 코드는 런타임 중에 사용자 입력을 수집했다.

하지만 경우에 따라 그래프가 외부 입력을 읽어야 하는 경우가 있는데, 예를 들어 도구를 실행하거나 데이터베이스에 쓰기 전에 사람의 승인을 받아야 하는 경우가 있다. 사람의 응답에는 시간이 걸릴 수 있으므로, 대기하는 동안 작업을 일시 중지했다가 정보가 제공되면 다시 시작하는 것이 이상적이며, 이러한 경우에 인터럽트가 사용된다.

인터럽트는 대기하는 동안 작업을 일시 중지하고 그래프를 대기 상태로 둔 다음, 다시 시작하면 작업을 Resume한다.

이 저장 및 복원 작업은 체크포인터 덕분에 가능하며, 이를 확인해보면 (Interrupt는 가장 중요한 기능으로 강조되므로, 유의깊게 생각하며 확인할 것)

from IPython.display import Image, display
import operator
from typing import Annotated, List, Literal, TypedDict
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt
# 직전까지 살펴본 그래프를 사용할 것이기에, 필요한 모듈 import
from langgraph.checkpoint.memory import InMemorySaver
memory = InMemorySaver()
config = {"configurable": {"thread_id": "1"}}
# 이전과 동일한 체크포인트
class State(TypedDict):
    nlist : Annotated[list[str], operator.add]  
# 동일한 state 사용하며

def node_a(state: State) -> Command[Literal["b", "c", END]]:
    # 여기서 차이가 발생. 노드 B와 노드 C는 같은 방식으로 정의되어 있지만, 노드 a의 정의는 약간 다르다.
    print("Entered 'a' node") # # 노드 A가 실행될 때마다 명확하게 알 수 있도록 노드 시작 부분에 print 문을 추가. 이는 단순히 디버깅을 위한 것임.
    select = state["nlist"][-1]
    if select == "b":
        next_node = "b"
    elif select == "c":
        next_node = "c"
    elif select == "q":
        next_node = END
        # 조건 분기 논리는 대부분 동일하게 유지된다. 
        # nlist의 마지막 요소를 기준으로 분기한다. 
        # B이면 노드 B로, C이면 노드 C로, Q이면 노드 끝으로 이동하는데,
    else: # illegal value
        # 여기서 차이점은 이전에는 catch all을 사용하면 종료되지만, 이제는 잘못된 값의 경우 인터럽트를 발생시킬 것이다. 인터럽트 함수는 LangGraph.types에서 가져온다.
        admin = interrupt(f"Unexpected input '{select}'")
        #  이 함수의 경우 이 코드 줄을 만나면 그래프가 중단되고 예상치 못한 입력 메시지와 잘못된 값을 반환한다. 그래프를 다시 시작할 때, 결과는 이 admin 변수에 저장되고, 출력된다.
        print(admin)
        if admin == "continue": #  만약 해당 resume 값이 continue이면, 다음으로 이동할 노드는 B가 된다.
            next_node = "b"
        else: #  그렇지 않으면 바로 end로 이동한다.
            next_node = END
            select = "q"

    return Command(
        update = State(nlist = [select]),
        goto = next_node
        # 그리고 이전과 동일하게 상태를 업데이트하고, 다음 노드로 이동하는 명령을 반환한다.
    )

def node_b (state: State) -> State:
    return(State(nlist = ["B"]))
def node_c (state: State) -> State:
    return(State(nlist = ["C"]))

builder = StateGraph(State)
#  이제 동일하게 그래프를 구성하자. 노드 A, B, C를 추가하고, 간선을 추가한 후, 앞서 정의한 체크포인터로 컴파일한다.

# Add nodes
builder.add_node("a", node_a)
builder.add_node("b", node_b)
builder.add_node("c", node_c)

# Add edges
builder.add_edge(START,"a")
builder.add_edge("b", END)
builder.add_edge("c", END)

# Compile
graph = builder.compile(checkpointer=memory)

# 이제 이전에 사용한 루프를 사용해보자. 사용자 입력을 받아서 그래프에 전달하는 루프이기에, 따라서 잘못된 입력으로 이 그래프를 호출하면 해당 인터럽트 명령문이 실행된다.
while True:
    user = input('b, c, or q to quit: ')
    input_state = State(nlist = [user])
    result = graph.invoke(input_state, config) 
        print(result)
        break; # 결과를 출력하고, 인위적인 중단점을 추가해본다.

    if result['nlist'][-1] == "q":
        print("quit")
        break

# 이제 실행하고 잘못된 값을 입력해보면
# Entered 'a' node
# {'nlist': ['b', 'b', 'B', 'a', 'kkk'], '__interrupt__': [Interrupt(value="Unexpected input 'kkk'", id='8d56bc2138dfjka92d0c594f8f39')]}

결과에 실행된 값을 확인해보면, a 노드에 진입했지만 잘못된 값이 입력되었고, 그래프가 제공한 결과에 인터럽트 키가 포함되어 있는 것을 확인할 수 있다. 이 키는 발생한 인터럽트를 포함하는 목록을 저장하며, 값은 발생한 메시지이다. 그리고 ID가 할당되어있는 것을 볼 수 있다.

이제 해야할 일은 이런 인터럽트를 처리하는 방법을 그래프에 알려주는 것으로, 기본적인 인터럽트 핸들러 구현을 통해 수행 가능하다.

from IPython.display import Image, display
import operator
from typing import Annotated, List, Literal, TypedDict
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import InMemorySaver
memory = InMemorySaver()
config = {"configurable": {"thread_id": "1"}}
class State(TypedDict):
    nlist : Annotated[list[str], operator.add]  

def node_a(state: State) -> Command[Literal["b", "c", END]]:
    print("Entered 'a' node") 
    select = state["nlist"][-1]
    if select == "b":
        next_node = "b"
    elif select == "c":
        next_node = "c"
    elif select == "q":
        next_node = END

    else: # illegal value
        admin = interrupt(f"Unexpected input '{select}'")
        print(admin)
        if admin == "continue": 
            next_node = "b"
        else: 
            next_node = END
            select = "q"
    return Command(
        update = State(nlist = [select]),
        goto = next_node
    )

def node_b (state: State) -> State:
    return(State(nlist = ["B"]))
def node_c (state: State) -> State:
    return(State(nlist = ["C"]))

builder = StateGraph(State)
builder.add_node("a", node_a)
builder.add_node("b", node_b)
builder.add_node("c", node_c)
builder.add_edge(START,"a")
builder.add_edge("b", END)
builder.add_edge("c", END)
graph = builder.compile(checkpointer=memory)

while True:
    user = input('b, c, or q to quit: ')
    input_state = State(nlist = [user])
    result = graph.invoke(input_state, config) 
    # break를 제거하고 
    if '__interrupt__' in result:
        print(f"Interrupt:{result}") # 인터럽트의 메시지를 출력한다.
        msg = result['__interrupt__'][-1].value
        print(msg) # 그리고 사람에게 입력을 요청한다.
        human = input(f"\n{msg}: ")

        human_response = Command(
            resume = human # 그리고 해당 입력을 이용해 사용자 응답을 구성한다. 이 경우 재개 속성을 제공시킨다.
        )
        result = graph.invoke(human_response, config) # 마지막으로  인간의 응답과 이전의 설정을 사용해 그래프를 호출한다. 
        # 유의할 점은 그래프가 메모리를 가지려면 이전 실행에서 상태를 복원하려는 동일한 스레드 id로 호출되어야 한다는 점이다.

    if result['nlist'][-1] == "q":
        print("quit")
        break
# 이제 실행해보면..

# b, c, or q to quit:  kkk
# Entered 'a' node
# Interrupt:{'nlist': ['b', 'b', 'B', 'a', 'kkk', 'kkk'], '__interrupt__': [Interrupt(value="Unexpected input 'kkk'", id='1db86e128r9e82j39fj4387c6fb7')]}
# Unexpected input 'kkk'

# Unexpected input 'kkk':  continue
# Entered 'a' node
# continue
# b, c, or q to quit:  b
# Entered 'a' node
# b, c, or q to quit:  q
# Entered 'a' node
# quit

출력을 확인해보면, 노드에 진입 후 인터럽트가 발생했고, 인터럽트 메시지가 나타난다. 그리고 입력하라는 메시지가 표시되었으,. continue를 입력하면 재개된 후 continue를 입력하고 B로 이동한다. 그러면 continue를 입력하고 노드 B로 이동한 것을 볼 수 있는데, 지금 보면, . "Entered a node"를 다시 출력하는 것을 볼 수 있다.

즉, 처음부터 재실행됬다는 것인데, 그 이유는 노드의 크기가 클 수 있기 때문이다.

따라서 인터럽트 발생 시에는 노드를 오프라인 상태로 전환해야 하며, 만약 노드 중간에서 재개했다면 모든 중간 상태는 유지되어야 할 것이다. 따라서 인터럽트는 노드 시작 부분부터 다시 실행되기에 이런 응답이 나오는 것이다.
인터럽트가 계속 발생하는 경우, LangGraph는 자동으로 응답을 체크포인트하고,
이미 발생한 인터럽트를 발견하면 해당 응답을 제공하며, 여러 인터럽트를 연속으로 발생시켜도 LangGraph가 현재 어떤 인터럽트에 있는지 추적할 수 있다.

'interrupt': [Interrupt(value="Unexpected input 'kkk'", id='1db86e128r9e82j39fj4387c6fb7')]를 보면 값이 리스트로 구성되어 있는데, 그 이유는 그래프가 여러 인터럽트를 발생시킬 수 있기 때문이다. 예를 들어, 병렬로 실행 중인 두 노드가 모두 인터럽트를 발생시키면 이 리스트에는 두 인터럽트가 모두 포함된다.

여기까지 노드 로직에 인터럽트 문을 추가했고, 이를 처리하기 위해 인터럽트 핸들러를 추가했다.

기억할 점은 다음과 같다.

  1. 실행 중에 인터럽트가 발생하면 작업이 일시 중지되고 그래프가 인터럽트 필드에 값을 반환하는 방식을 살펴보았고, 재개를 포함하는 명령으로 그래프가 호출되면 그래프에 값이 반환되고 작업이 계속되는것,
  2. 그리고 노드가 처음부터 다시 시작되는 것을 확인했다. 이것은 체크포인터가 인터럽트에 대한 응답을 재생하기 때문이다.
반응형

'IT > 뻘짓' 카테고리의 다른 글

http에서 https로 변경 + 체크해볼 부분  (0) 2025.11.03
Ubuntu GPU 추가  (0) 2025.11.02
Windows server 2016 Hyper-v gpu 할당  (0) 2024.10.21
be700-kr 호환 배터리  (0) 2022.08.09
Posted by creatoryoon
,
반응형

NGINX로 https로 연결 -> 로컬로 프록시를 구현시 유의사항.

 

 

 

1. acme.sh 설치 + Duckdns(여기서는Duckdns 사용) 설정

# acme.sh 설치
curl https://get.acme.sh | sh -s email=[email 주소 적으면댐]
exec $SHELL -l

# 발급기관을 Lets Encrypt로 고정
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt


# DuckDNS 토큰
export DuckDNS_Token="토큰 입력"
echo 'export DuckDNS_Token="토큰 입력"' >> ~/.bashrc
DOMAIN="도메인 입력"


~/.acme.sh/acme.sh --issue --dns dns_duckdns -d "$DOMAIN"



sudo mkdir -p /etc/nginx/ssl/"$DOMAIN"

~/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
  --key-file       /etc/nginx/ssl/"$DOMAIN"/privkey.pem \
  --fullchain-file /etc/nginx/ssl/"$DOMAIN"/fullchain.pem \
  --reloadcmd     "systemctl reload nginx"
  
  
# 오류시 nginx가 없으므로 

sudo apt update
sudo apt install -y nginx


만약 권한 오류가 난다면

sudo -i
DOMAIN="도메인 "
export LE_WORKING_DIR="/home/user/.acme.sh"

mkdir -p /etc/nginx/ssl/"$DOMAIN"

/home/user/.acme.sh/acme.sh --install-cert -d "$DOMAIN" --ecc \
  --key-file       /etc/nginx/ssl/"$DOMAIN"/privkey.pem \
  --fullchain-file /etc/nginx/ssl/"$DOMAIN"/fullchain.pem \
  --reloadcmd     "systemctl reload nginx"

chmod 600 /etc/nginx/ssl/"$DOMAIN"/privkey.pem
chmod 644 /etc/nginx/ssl/"$DOMAIN"/fullchain.pem
exit

 

2. NGINX에 설정 등록

 

sudo tee /etc/nginx/sites-available/ports-ssl.conf >/dev/null <<'NGINX'
# websocket 헬퍼
map $http_upgrade $connection_upgrade { default upgrade; '' close; }


# ======== 포트: 뭐쓸지 ======
server {
    listen 포트 ssl http2;
    server_name 도메인입력;

    ssl_certificate     /etc/nginx/ssl/도메인입력/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/도메인입력/privkey.pem;

    # (선택) 베이직 인증 켜기
    # auth_basic "Restricted";
    # auth_basic_user_file /etc/nginx/.htpasswd-포트;

    client_max_body_size 1G;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_read_timeout 3600; proxy_send_timeout 3600;

        proxy_pass http://127.0.0.1:포트; # 내부 실행이 연결될 포트,
    }
}

server {} (위와 동일하게 사용할 포트마다 지정해준다)



NGINX

이후 다음 명령어로 설정 로드
sudo ln -sf /etc/nginx/sites-available/ports-ssl.conf /etc/nginx/sites-enabled/ports-ssl.conf
sudo nginx -t && sudo systemctl reload nginx

 

 

 

3. 문제 해결

 

 

문제1. 실행중인 서버의 폰트가 문제를 일으켰다. 이 경우 권한문제로 다음 내용을 수행한다.

 

# 1) 공개 정적 경로 생성
sudo install -d -m 755 /var/www/gradio-fonts

# 2) 폰트 복사 
sudo rsync -a \
  /home/문제가 되는 폰트 경로/
  
sudo find /var/www/gradio-fonts -type d -exec chmod 755 {} \;
sudo find /var/www/gradio-fonts -type f -exec chmod 644 {} \;

 

 

또한 해당 사항에 맞게 설정도 수정해준다.

sudo vi /etc/nginx/sites-available/ports-ssl.conf 를 이용해 (vi든 nano든 선호하는걸로..) 열어준다.

열면 위에서 입력해준 다음과 같은 내용이 나올 것이며, 이제 문제별로 해결을 한다.

# websocket 헬퍼
map $http_upgrade $connection_upgrade { default upgrade; '' close; }


# ======== 포트: 뭐쓸지 ======
server {
    listen 포트 ssl http2;
    server_name 도메인입력;

    ssl_certificate     /etc/nginx/ssl/도메인입력/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/도메인입력/privkey.pem;

    # (선택) 베이직 인증 켜기
    # auth_basic "Restricted";
    # auth_basic_user_file /etc/nginx/.htpasswd-포트;

    client_max_body_size 1G;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_read_timeout 3600; proxy_send_timeout 3600;

        proxy_pass http://127.0.0.1:포트; # 내부 실행이 연결될 포트,
    }
}

 

수정1.

# websocket 헬퍼
map $http_upgrade $connection_upgrade { default upgrade; '' close; }


# ======== 포트: 뭐쓸지 ======
server {
    listen 포트 ssl http2;
    server_name 도메인입력;

    ssl_certificate     /etc/nginx/ssl/도메인입력/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/도메인입력/privkey.pem;

    # (선택) 베이직 인증 켜기
    # auth_basic "Restricted";
    # auth_basic_user_file /etc/nginx/.htpasswd-포트;

    client_max_body_size 1G;
###============추가 ============#

# Gradio 폰트 404 핫픽스
	location ^~ /static/fonts/ {
   		alias /home/user/폰트경로;
    	access_log off;
    	expires 30d;
    	add_header Cache-Control "public, max-age=2592000, immutable";
			}
###============끝 ============#

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_read_timeout 3600; proxy_send_timeout 3600;

        proxy_pass http://127.0.0.1:포트; # 내부 실행이 연결될 포트,
    }
}

 

폰트 문제는 해결됬으나, 몇몇 요소가 불러와지지 않는것으로 보아 근본적인 문제는 다른듯하므로 다른 부분을 수정한다. 

이것저것 해보다가 꽤나 허탈한 부분에 실수가 있다는것을 발견했다.

$host로 매핑을 했더니 포트가 전달되지 않아 404가 발생했던것....

문제부분 수정

수정 2

# websocket 헬퍼
map $http_upgrade $connection_upgrade { default upgrade; '' close; }


# ======== 포트: 뭐쓸지 ======
server {
    listen 포트 ssl http2;
    server_name 도메인입력;

    ssl_certificate     /etc/nginx/ssl/도메인입력/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/도메인입력/privkey.pem;

    # (선택) 베이직 인증 켜기
    # auth_basic "Restricted";
    # auth_basic_user_file /etc/nginx/.htpasswd-포트;

    client_max_body_size 1G;
###============추가 ============#

# Gradio 폰트 404 핫픽스
	location ^~ /static/fonts/ {
   		alias /home/폰트경로;
    	access_log off;
    	expires 30d;
    	add_header Cache-Control "public, max-age=2592000, immutable";
			}
###============끝 ============#  -> 이부분은 해결되므로 삭제



    location / {
## 수정======================================

기존 :  proxy_set_header Host $host;
수정 :  proxy_set_header Host $http_host;  # 포트를 포함

추가 :  proxy_set_header X-Forwarded-Host  $http_host;  # 포트를 포함한다.
추가 :  proxy_set_header X-Forwarded-Proto https;
		proxy_set_header X-Forwarded-Port  $server_port;
		proxy_redirect off;
##=====================================

        proxy_set_header X-Real-IP $remote_addr;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_read_timeout 3600; proxy_send_timeout 3600;

        proxy_pass http://127.0.0.1:포트; # 내부 실행이 연결될 포트,
    }
}

 

또한 내/외부 포트를 동일하게 사용시 충돌하는 문제가 있으므로 리슨에는 서버의 아이피를 적어준다.

이렇게 해결이 되고나면 다음과같은 블록을 기본값으로 하면 문제가 발생하지 않는다.

server {
    listen 서버ip:포트 ssl http2;
    server_name 도메인;

    ssl_certificate     /etc/nginx/ssl/도메인/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/도메인/privkey.pem;

    client_max_body_size 2G;

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Port  $server_port;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;


        proxy_buffering off;

        proxy_read_timeout 86400;
        proxy_send_timeout 86400;
        proxy_pass http://127.0.0.1:포트;
    }
}

 

 

이제 문제는 해결되었으나 이번엔 또다른곳에서 문제가 발생했다.

 

 

https로 접속시 Jupyter 서버가 응답이 느려지고 있던것.

이부분은 Jupyter 서버를 담당하는 블록을 수정한다.

server {
    listen ip주소:포트 ssl http2;
    server_name 도메인;

    ssl_certificate     /etc/nginx/ssl/도메인/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/도메인/privkey.pem;

    client_max_body_size 1G;

    # ① Jupyter WebSocket 경로(커널 채널/터미널)는 업그레이드 강제 + 버퍼링 OFF
    location ~* ^/(api/kernels/[^/]+/channels|terminals/websocket)/?$ {
        proxy_pass http://127.0.0.1:포트;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_set_header Host              $http_host;   # 포트 포함 Host
        proxy_set_header X-Forwarded-Host  $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port  $server_port;
        proxy_set_header X-Real-IP         $remote_addr;

        proxy_request_buffering off;     # 입력 지연 줄이기
        proxy_buffering off;             # 출력 지연 줄이기
        proxy_read_timeout 86400;
        proxy_send_timeout 86400;
    }

    # 일반 요청
    location / {
        proxy_pass http://127.0.0.1:포트;

        proxy_http_version 1.1;
        proxy_set_header Host              $http_host;
        proxy_set_header X-Forwarded-Host  $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port  $server_port;
        proxy_set_header X-Real-IP         $remote_addr;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_redirect off;
        proxy_read_timeout 600;
        proxy_send_timeout 600;
    }
}

 

이러면 https로 서빙하기 위한 준비가 완료되었다.

반응형

'IT > 뻘짓' 카테고리의 다른 글

LangGraph Essentials  (1) 2025.11.12
Ubuntu GPU 추가  (0) 2025.11.02
Windows server 2016 Hyper-v gpu 할당  (0) 2024.10.21
be700-kr 호환 배터리  (0) 2022.08.09
Posted by creatoryoon
,

Ubuntu GPU 추가

IT/뻘짓 2025. 11. 2. 14:04
반응형

운용중인 Ubuntu 서버에 GPU를 추가하게 되며 몇가지 삽질이 있었다.

기본 환경은 CLI를 사용하지만, 편의상 GUI를 위해 NoMachine을 사용하는 상황이며, 노헤드머신으로 사용시 특정 프로그램에서의 램누수가 있어 Dummy플러그를 내장 그래픽에 끼우고 사용하는 상황.

 

1. GPU 설치후 그냥 apt로 추천 드라이버를 설치하니 충돌으로 검정화면만 나오는 문제가 생겼다. 이를 해결하기위해 드라이버를 제거하니 왜인지 랜카드가 안잡혀버리는 상황.

이 문제의 경우 해당 버전 우분투 usb를 만들어 LIVE에서 해결해야 한다. 더미플러그를 빼고 KVM을 연결한뒤 작업

# 1. LiveUSB로 부팅 후 터미널 열기

# 2. 기존 시스템 파티션 확인
sudo fdisk -l
# 또는
lsblk

# 3. 루트 파티션 마운트
sudo mount /dev/nvme0n1p2 /mnt  # 실제 파티션명에 맞게 수정

# 4. 필요한 파일시스템 bind 마운트
sudo mount --bind /dev /mnt/dev
sudo mount --bind /proc /mnt/proc
sudo mount --bind /sys /mnt/sys
sudo mount --bind /run /mnt/run

# 5. EFI 파티션 마운트 (UEFI 시스템)
sudo mount /dev/nvme0n1p1 /mnt/boot/efi  # EFI 파티션명에 맞게 수정

# 6. chroot로 진입
sudo chroot /mnt
# 네트워크 드라이버/커널 문제 해결

# A. 커널 재설치
apt install --reinstall linux-image-generic
apt install --reinstall linux-modules-$(uname -r)

# B. NVIDIA 제거 (문제 원인이었다면)
apt purge '*nvidia*'
apt autoremove

# C. 네트워크 도구 재설치 (필요시)
apt install --reinstall network-manager

# D. initramfs 재생성
update-initramfs -u -k all

# E. GRUB 업데이트
update-grub
# 1. chroot 종료
exit

# 2. bind mount 역순으로 umount (순서 중요)
sudo umount /mnt/run
sudo umount /mnt/sys
sudo umount /mnt/proc
sudo umount /mnt/dev

# 3. EFI 파티션 umount
sudo umount /mnt/boot/efi

# 4. 마지막으로 루트 파티션 umount
sudo umount /mnt

# 5. 재부팅
sudo reboot

 

2. 이제 NVIDIA 드라이버를 조심히 설치한다.

1. AMD BusID 확인
lspci | grep VGA
#01:00.0 VGA compatible controller: NVIDIA Corporation ~~~~
#15:00.0 VGA compatible controller: Advanced Micro Devices, Inc. ~~~

2. Xorg 설정 - AMD 강제
sudo mkdir -p /etc/X11/xorg.conf.d
sudo tee /etc/X11/xorg.conf.d/10-amd-primary.conf > /dev/null << 'EOF'
Section "ServerLayout"
    Identifier "layout"
    Screen 0 "amdgpu"
EndSection

Section "Device"
    Identifier "amdgpu"
    Driver "amdgpu"
    BusID "PCI:21:0:0"
EndSection

Section "Screen"
    Identifier "amdgpu"
    Device "amdgpu"
EndSection

Section "ServerFlags"
    Option "AutoAddGPU" "off"
EndSection
EOF

# NVIDIA 설정 #

# 3. Nouveau 블랙리스트 확인
cat /etc/modprobe.d/blacklist-nouveau.conf

# 없다면 다음과 같이 생성/수정
# blacklist nouveau
# options nouveau modeset=0

# 4. NVIDIA 디스플레이 모드셋 비활성화
sudo tee /etc/modprobe.d/nvidia-drm-nomodeset.conf > /dev/null << 'EOF'
options nvidia-drm modeset=0
EOF

# 5. NVIDIA 드라이버 설치
sudo ubuntu-drivers install

# 6. initramfs 업데이트
sudo update-initramfs -u -k all

# 7. Xorg 설정 확인
cat /etc/X11/xorg.conf.d/10-amd-primary.conf

# 8. modprobe 설정 확인
ls -la /etc/modprobe.d/ | grep -E "nvidia|nouveau"
cat /etc/modprobe.d/nvidia-drm-nomodeset.conf

# 9. 현재 GRUB 설정 (amdgpu.dc=0 없어야 함)
cat /proc/cmdline

# 10. 재부팅


### 재부팅 후 ###
# 11. 디스플레이 - 내장 사용 중인지
glxinfo | grep "OpenGL renderer"
# AMD Radeon Graphics

# 12. NVIDIA 드라이버 로드
nvidia-smi

# 13. PyTorch CUDA 확인
python -c "import torch; print(torch.cuda.is_available())"

# 14. 간단한 GPU 연산 테스트
python << 'EOF'
import torch
if torch.cuda.is_available():
    x = torch.rand(1000, 1000).cuda()
    y = torch.rand(1000, 1000).cuda()
    z = x @ y
    print(f"연산 성공 Device: {torch.cuda.get_device_name(0)}")
else:
    print("CUDA 사용 불가")
EOF


# 만약 화면이 검정?
# Xorg 설정 제거 후 재부팅
sudo mv /etc/X11/xorg.conf.d/10-amd-primary.conf /root/xorg-backup/
반응형

'IT > 뻘짓' 카테고리의 다른 글

LangGraph Essentials  (1) 2025.11.12
http에서 https로 변경 + 체크해볼 부분  (0) 2025.11.03
Windows server 2016 Hyper-v gpu 할당  (0) 2024.10.21
be700-kr 호환 배터리  (0) 2022.08.09
Posted by creatoryoon
,
반응형

Chpter05 Sequence-to-Sequence with Attention

5.1 Seq2seq:Encoder-DecoderModel

 

 

5.1.1 Seq2seq : Encoder

input token $x_1, \ldots, x_T$ 에 대하여
Context vector C 는 T시점에서의 RNN 의 hidden state, 즉 $h_T$

5.1.2 Seq2seq : Decoder

5.1.2 Seq2seq : Decoder (Cho et al.)

 

5.1.3 Seq2seq : Encoder-Decoder Graph Repressentation

간소화를 위하여 hidden state와 input vector의 dimension을 모두 2로 고정 동일한 weight matrix에 속하는 weight들은 같은 색으로 표시

 

 

5.1.3 Seq2seq : Encoder-Decoder Model Training

Encoder-decoder is optimized as a single system. Backpropagation operates "end-to-end"

5.1.4 Seq2seq : Encoder-Decoder Model for ASR

5.2 Seq2Seq: The Bottleneck Problem

5.2.1 Attention

- Bottleneck problem에 대한 해결을 위해 attention 개념이 제안됨

- Core idea : 각 시점마다 decoder에서 입력 sequence의 특정 부분에 초점을 맞출 수 있도록 encoder로 직접적으로 연결

 

5.2.2 Seq2Seq with Attention

5.2.2 Seq2Seq with Attention

 

5.2.2.1 Attention: in equations

 

Encoder Hidden States $h_1, \ldots, h_N \in \mathbb{R}^h$
t 시점에서의 decoder hidden state $s_t \in \mathbb{R}^h$
Attention score $e^t$ 는 다음과 같이 계산
$$
\boldsymbol{e}^t=\left[\boldsymbol{s}_t^T \boldsymbol{h}_1, \ldots, \boldsymbol{s}_t^T \boldsymbol{h}_N\right] \in \mathbb{R}^N
$$
Attention score에 softmax를 적용한 뒤, attention distribution $\alpha^t$ 를 계산
- $\alpha^t$ 는 더해서 1 이 되는 확률 분포
$$
\alpha^t=\operatorname{softmax}\left(e^t\right) \in \mathbb{R}^N
$$
Attention output $\boldsymbol{a}_t$ 은 $\alpha^t$ 와 encoder hidden state의 weighted sum을 통해서 계산
$$
\boldsymbol{a}_t=\sum_{i=1}^N \alpha_i^t \boldsymbol{h}_i \in \mathbb{R}^h
$$
Attention output $a_t$ 와 decoder hidden state $s_t$ 를 concatenate한 뒤, 일반적인 seq2seq모델 처럼 처리 $\left[a_t ; s_t\right] \in \mathbb{R}^{2 h}$
$$
\begin{gathered}
e^t=\left[s_t^T h 1, \ldots, s_t^T h_N\right] \in \mathbb{R}^N \\
\alpha^t=\operatorname{softmax}\left(e^t\right) \in \mathbb{R}^N \\
a_t=\sum_{i=1}^N \alpha_i^t h_i \in \mathbb{R}^h
\end{gathered}
$$


 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
Posted by creatoryoon
,
반응형

처음 사용해보는 win server 2016. docker도 리눅스 system은 생성이 불가하며, 이것저것 다 해보다 hyper-v로 가상머신 생성, gpu할당을 하기위해 시간을 허비하던 중, 해결에 성공했다.

출처는 https://www.2cpu.co.kr/lec/4093

 

Hyper-v GPU DDA(GPU passthrough) 설정 :: 2cpu, 지름이 시작되는 곳!

sfl 제목 내용 제목+내용 회원아이디 회원아이디(코) 이름 이름(코) stx sop and or 검색

www.2cpu.co.kr

0) 설치 버전
호스트 OS : windows 2016 64bit
게스트 VM : ubuntu 18.04 64bit
GPU : K80

 

0-1) 주의사항
* DDA 설정 시 VM에서 아래의 기능을 사용할 수 없습니다.
VM 저장/복원
VM의 실시간 마이그레이션
동적 메모리 사용
HA(고가용성) 클러스터에 VM 추가


Windows 10 hyper-v에서 설정 시 VM 실행이 되지 않습니다.

 

1) 확인 사항






VM 이름, GPU 인스턴스 경로, GPU 위치 경로
VM 이름 : gpu_test


제어판 – 장치관리자 – 디스플레이 어댑터 – VM에 할당할 GPU 카드 속성 – 자세히
인스턴스 경로 : PCI\VEN_10DE&DEV_102D&SUBSYS_106C10DE&REV_A1\6&41A1BBC&0&00400018
위치 경로 : PCIROOT(0)#PCI(0300)#PCI(0000)#PCI(0800)#PCI(0000)


설정 작업은 PowerShell 에서 진행됩니다.
* VM 종료 후 진행합니다.
에러메시지가 나오지 않을 경우 정상적으로 적용된 것 입니다.

 

2) 이름 변수 선언 및 할당




#vm 이름 변수 선언 및 할당
$vm = "gpu_test"
#GPU 장치 인스턴스 경로 변수 선언 및 할당
$gpudevs = "PCI\VEN_10DE&DEV_102D&SUBSYS_106C10DE&REV_A1\6&41A1BBC&0&00400018"
#GPU 위치 경로 변수 선언 및 할당
$locationPath = "PCIROOT(0)#PCI(0300)#PCI(0000)#PCI(0800)#PCI(0000)"

 

3) VM 설정






#VM 설정
#자동 중지 작업 설정(가상 컴퓨터 끄기 로 설정이 변경됩니다.)
Set-VM -Name $vm -AutomaticStopAction TurnOff
#CPU에 Write-Combining 설정
Set-VM -GuestControlledCacheTypes $true -VMName $vm
#32 bit MMIO 공간 구성
Set-VM -LowMemoryMappedIoSpace 3Gb -VMName $vm
#32 bit 이상 MMIO 공간 구성
Set-VM -HighMemoryMappedIoSpace 33280Mb -VMName $vm


* MMIO 공간에 대해서 추가적인 확인은 URL 참조
https://docs.microsoft.com/ko-kr/windows-server/virtualization/hyper-v/plan/plan-for-deploying-devices-using-discrete-device-assignment

 

4) 호스트 서버 설정





#호스트 서버에서 GPU 장치 사용 안 함 설정 (사용 안 함 설정이 되어있다면 무시 가능)
Disable-PnpDevice  -InstanceId $gpudevs


#호스트 서버에서 GPU 장치 분리
Dismount-VMHostAssignableDevice -force -LocationPath $locationPath

 

5) VM에 GPU 장치 할당




#VM에 GPU 장치 할당
Add-VMAssignableDevice -LocationPath $locationPath -VMName $vm

 

6) VM 확인





 

7) VM에서 GPU 장치 회수







* VM 종료 후 진행합니다.


#vm 이름 변수 선언 및 할당
$vm = "gpu_test"
#GPU 위치 경로 변수 선언 및 할당
$locationPath = "PCIROOT(0)#PCI(0300)#PCI(0000)#PCI(0800)#PCI(0000)"


#VM에 연결된 PCI 장치 삭제
Remove-VMAssignableDevice -LocationPath $locationPath -VMName $vm


#PCI 장치를 호스트서버에 연결
Mount-VMHostAssignableDevice -LocationPath $locationPath
반응형

'IT > 뻘짓' 카테고리의 다른 글

LangGraph Essentials  (1) 2025.11.12
http에서 https로 변경 + 체크해볼 부분  (0) 2025.11.03
Ubuntu GPU 추가  (0) 2025.11.02
be700-kr 호환 배터리  (0) 2022.08.09
Posted by creatoryoon
,
반응형

</p

 

4.1 Introduction

- 아래와 같은 공이 있을 때 다음 위치를 예측 할 수 있을까?

- 이전 시점들의 정보가 주어진다면?

4.1.1 Examples of Sequence Data

4.1.2 Sequence Modeling Applications

4.1.2 Handling Individual Time steps

4.2   Neurons with Recurrence

 

4.2.1  Recurrent Neural Networks (RNNs)

 

 

 4.2.2 Activation Functions

 

 

4.2.3  The Role of the Hidden Layer

The role of the hidden layer is to map samples from the input space to the hidden layer space.

 

4.2.4  RNN State Update and Output

 

4.2.5  RNN : Graph Representation

발성에 있어 인접한 t에는 dependency가 존재한다.

 

4.2.6  RNNs : Computational Graph Across Time

 

4.2.7 Example : Predict Sequence of Character


§ 다음 문자를 예측하는 네트워크를 설계

● "hello"라는 단어 기준

●  h,e, 1,1 이 입력으로 들어왔을 때 o 를 예측

- {'h': 0, 'e':1, 'l':2, 'o':3\}

●  one-hot encoding

h [1,0,0,0]
e [0,1,0,0]
l [0,0,1,0]
o [0,0,0,1]


●  hidden layer는 1 층의 RNN을 사용

전체적으로 봤을 땐 Language 모델, $t=1$ 일 때 $h$ 오면 e 예측 $t=2$ 일때 e 오면 i를 예측 한다.


● 다음과 같은 one-hot encoding을 입력으로 받음

●  RNN model :

$$
\begin{aligned}
& o_{t}=h_{t} W_{h o} \\
& h_{t}=\tanh \left(h_{t-1}\left(W_{h h}+x_{t}\left(W_{x h}\right)\right)\right.
\end{aligned}
$$

 

 

 $W_{x h}$ 는 다음과 같은 0 에서 1 사이의 값으로 ($4 \times 3$ )행렬을  random initialize 

$$
W_{x h}=\left(\begin{array}{lll}
0.2973 & 0.2766 & 0.7974 \\
0.3869 & 0.9170 & 0.4125 \\
0.5538 & 0.5646 & 0.5026 \\
0.2206 & 0.6801 & 0.3880
\end{array}\right)
$$

 

 $x_{1} W_{x h}$ 는 다음과 같이 계산

$
x_{1} W_{x h}=\left(\begin{array}{llll}
(1 & 0 & 0 & 0
\end{array}\right) \times\left(\begin{array}{lll}
0.2973 & 0.2766 & 0.7974 \\
0.3869 & 0.9170 & 0.4125 \\
0.5538 & 0.5646 & 0.5026 \\
0.2206 & 0.6801 & 0.3880
\end{array}\right)=\left(\begin{array}{llll}
0.2973 & 0.2766 & 0.7974
\end{array}\right)
$$

 

 

 

 $W_{h h}$ 는 다음과 같은 0 에서 1 사이의 값으로 random initialized $3 \times 3$ 행렬

$$
W_{h h}=\left(\begin{array}{lll}
0.3152 & 0.5083 & 0.9454 \\
0.3008 & 0.6058 & 0.2999 \\
0.9741 & 0.3419 & 0.9133
\end{array}\right)
$$

 

 $h_{t-1} W_{h h}$ 를 계산하기 위해서는 initial hidden state $h_{0}$ 가 필요

- 일반적으로 initial hidden state로는 $O$ 를 사용(영행렬)

 

 

 $h_{1}$ 은 다음과 같이 update

 $W_{h y}$ 는 같은 0 에서 1 사이의 값으로 random initialized $3 \times 4$ 행렬

$
W_{h o}=\left(\begin{array}{llll}
0.3189 & 0.6216 & 0.5164 & 0.7501 \\
0.0260 & 0.6230 & 0.0179 & 0.6123 \\
0.1320 & 0.9009 & 0.3873 & 0.8142
\end{array}\right)
$

 

 

 $o_{1}$ 의 softmax 값과 이중 가장 큰 값의 index를 추출하는 argmax를 적용

미분이 아름답기에 exp를 사용한다.

 

 $W_{x h}, W_{h h}$ 는 동일한 weight를 사용

 $x_{2}W_{x h}$와 $h_{1}W_{h h}$는 다음과 같이 계산한다.

 

 $h_{2}$ 는 다음과 같이 update

 

$$
\begin{aligned}
& h_{2}=\tanh \left(h_{1} W_{h h}+x_{2} W_{x h}\right) \\
& =\tanh \left(\left(\begin{array}{lll}
0.8176 & 0.5368 & 0.9591
\end{array}\right)+\left(\begin{array}{lll}
0.3869 & 0.9170 & 0.4125
\end{array}\right)\right) \\
& =\tanh \left(\left(\begin{array}{lll}
1.2045 & 1.4538 & 1.3716
\end{array}\right)\right) \\
& =\left(\begin{array}{lll}
0.8350 & 0.8964 & 0.8791
\end{array}\right)
\end{aligned}
$$

$o_2$는 시점 1에서와 동일한 방법으로 계산할 수 있다.

$
\begin{aligned}
o_2 & =h_2 W_{h o} \\
& =\left(\begin{array}{lll}
0.8350 & 0.8946 & 0.8791
\end{array}\right)\left(\begin{array}{llll}
0.3189 & 0.6216 & 0.5164 & 0.7501 \\
0.0260 & 0.6230 & 0.0179 & 0.6123 \\
0.1320 & 0.9009 & 0.3873 & 0.8142
\end{array}\right) \\
& =\left(\begin{array}{llll}
0.4056 & 1.8696 & 0.7877 & 1.8910
\end{array}\right)
\end{aligned}
$

이때 $o_2$는 확률이 아님에 유의.

$
\begin{aligned}
& \left.\operatorname{softmax}\left(o_2\right)=\operatorname{softmax}\left(\begin{array}{llll}
0.4056 & 1.8696 & 0.7877 & 1.8910
\end{array}\right)\right) \\
& =\left(\begin{array}{llll}
0.0892 & 0.3858 & 0.1308 & 0.3942
\end{array}\right) 
\end{aligned}
$

 잘못된 예측 발생

- 올바른 예측은 2:1이 되어야 한다.

잘못된 예측

 올바르게 학습된 RNN Model

 

4.3   RNN 학습

4.3.1  RNNs : Computational Graph Across Time

 

4.3.2 Backpropagation Through Time(BPTT)

Backpropagation through time을 계산 하기 위하여 다음과 같은 RNN 모델을 정의한다.

 

$h_t = f(X_t \cdot W_{xh} + h_{t-1}\cdot W_{hh})$  ($f$ is activation function)

$o_t = h_t \cdot W_(h0)$

$\hat{y_{t}}=\text{softmax}(o_t)$

이때, $o_{t}$ 는 출력 logit이고 $\hat{y}_{t}$ 는 여기에 SoftMax를 취한 값이다.$C$ 개의 Class를 갖는 출력에 대한 cross entropy를 이용할 때, 시점 t 애서의 정답 $y_{t}$ 에 대한 loss $\ell_{t}$ 를 이용하여 길이 $T$ 의 sequence에 대한 총 loss function 은 다음과 같이 주어진다

 

Hidden state를 출력과 연결하는 weight와 관련된 gradient를, 즉 $W_{h o}$ 의 속하는 가중치 $w_{i j}$ 에 대하여 계산한다.  $w_{i j}$ 는 hidden state의 $i$ 번째 unit을 $j$ 번째 출력 unit에 연결하는 welght이다.

 

$w_{i j}$ 에 대한 loss function $L$ 에 관한 식을 계산 하기 위해서는 각 시점에서의 gradient를 합산 해야한다. 그러므로 $\frac{\partial L}{\partial w_{i j}}$ 에 대한 식은 다음과 같아진다.

$
\frac{\partial L}{\partial w_{i j}}=\sum_{t=1}^{T} \frac{\partial \ell_{t}}{\partial w_{i j}}=\sum_{t=1}^{T} \frac{\partial \ell_{t}}{\partial \hat{y}_{t}^{(j)}} \frac{\partial \hat{y}_{t}^{(j)}}{\partial o_{t}^{(j)}} h_{t}^{(i)}
$

$\frac{\partial L}{\partial w_{i j}}$ 에 대한 식에서 각 항들의 값들은 다음과 같이 구할 수 있다.


다음으로 $T-1$ 시점에서의 hidden state와 $T$ 시점의 hidden state의 weight, 즉 $W_{h h}$ 에 속하는 weight $u_{k i}$ 에 대해 계산한다.  $u_{k i}$ 는 hidden state 내의 $k$ 번째 unit과 $i$ 번째 unit을 연결하는 weight이며, hidden state는 이전 시점의 값에 따라 변경되기 때문에 이를 이해하기 위해 시점 $t$ 에서 $i$ 번째 hidden state unit, 즉 $h_{t}^{(i)}$ 에 대해$
h_{t}^{(i)}=f\left(\sum_{l=1}^{N} u_{l i} h_{t-1}^{(l)}+\sum_{m=1}^{D} v_{m i} x_{t}^{(m)}+b_{h i}\right)
$로 계산된다

weight $u_{k i}$ 에 대한 t 시점에서의 loss function에 대한 gradient는 다음과 같다.

$
\frac{\partial \ell_{t}}{\partial u_{k i}}=\frac{\partial \ell_{t}}{\partial h_{t}^{(i)}} \frac{\partial h_{t}^{(i)}}{\partial u_{k i}}
$

여기서의 $c_{t}^{(i)}$ 는 다음과 같다.

$
c_{t}^{(i)}=\sum_{l=1}^{N} u_{l i} h_{t-1}^{(l)}+\sum_{m=1}^{D} v_{m i} x_{t}^{(m)}
$

$h_{t-1}^{(i)}$ 는 점화식에 의해 $f\left(u_{k i} h_{t-2}^{(k)}+u_{i i} h_{t-2}^{(i)}+c_{t-1}^{(i)}\right)$ 로 표현 가능하므로 계산하려는 weight $u_{k i}$ 와 $h_{t-1}^{(i)}$ 에 관한 항으로 정리하며, 시점 t 에서의 점화식은 첫번째 시점까지 계속 될 것이므로, $\mathrm{t}=\mathrm{t}$ 에서 부터 $\mathrm{t}=1$ 까지의 모든 gradient의 합을 고려해야한다. weight $u_{k i}$ 에 관한 $h_{t}^{(i)}$ 의 gradient가 점화식에 의해 정의 되므로, $\mathrm{u}_{\mathrm{ki}}$ 에 대한 $h_{t}^{(i)}$ 편미분은 다음과 같이 정리할 수 있다.

$
\begin{gathered}
h_{t}^{(i)}=f\left(u_{k i} h_{t-1}^{(k)}+u_{i i} h_{t-1}^{(i)}+c_{t}^{(i)}\right) \\
\frac{\partial h_{t}^{(i)}}{\partial u_{k i}}=\sum_{t^{\prime}=1}^{t} \frac{\partial h_{t}^{(i)}}{\partial h_{t^{\prime}}^{(i)}} \frac{\partial h_{t^{\prime}}^{(i)}}{\partial u_{k i}}
\end{gathered}
$

$\frac{\bar{\partial} h_{t^{\prime}}^{(i)}}{\partial u_{k i}}$ 닌 $_{h_{t^{\prime}-1}^{(i)}}$ 를 상수로 유지한 채 계산한 $u_{k i}$ 에 대한 $h_{t}^{(i)}$ 의 local gradient를 나타낸다.

$
\frac{\partial \ell_{t}}{\partial u_{k i}}=\sum_{t^{\prime}=1}^{t} \frac{\partial \ell_{t}}{\partial h_{t}^{(i)}} \frac{\partial h_{t}^{(i)}}{\partial h_{t^{\prime}}^{(i)}} \frac{\partial h_{t^{\prime}}^{(i)}}{\partial u_{k i}}
$

이전 식에서 시점 t에서의 loss function에 관한 gradient로 일반화를 하기 위해서는 모든 시점에서의 gradient의 합으로 표현

$
\frac{\partial L}{\partial u_{k i}}=\sum_{t=1}^{T} \sum_{t^{\prime}=1}^{t} \frac{\partial \ell_{t}}{\partial h_{t}^{(i)}} \frac{\partial h_{t}^{(i)}}{\partial h_{t^{\prime}}^{(i)}} \frac{\partial h_{t^{\prime}}^{(i)}}{\partial u_{k i}}
$

$\frac{\partial h_{t}^{(i)}}{\partial h_{t^{\prime}}^{(i)}}$ 는 다음과 같이 나타낼 수 있다.

$
\frac{\partial h_{t}^{(i)}}{\partial h_{t^{\prime}}^{(i)}}=\prod_{g=t^{\prime}+1}^{t} \frac{\partial h_{g}^{(i)}}{\partial h_{g-1}^{(i)}}
$

이를 위 식에 대입하면 다음과 같은 수식을 얻을 수 있다

$
\frac{\partial L}{\partial u_{k i}}=\sum_{t=1}^{T} \sum_{t^{\prime}=1}^{t} \frac{\partial \ell_{t}}{\partial h_{t}^{(i)}}\left(\prod_{g=t^{\prime}+1}^{t} \frac{\partial h_{g}^{(i)}}{\partial h_{g-1}^{(i)}}\right) \frac{\bar{\partial} h_{t^{\prime}}^{(i)}}{\partial u_{k i}}
$

$W_{x h}$ 에 속하는 weight에 대해서도 유사한 방법으로 계산 가능

 

4.3.3 Problem of Vanilla RNN

 Short term memory 문제

1 초의 음성을 들었으면? 마지막 $t=50$ 에서 나오는 hl 의 벡터를 통해 전체 음성입력의 벡터로 바뀌었다.. 라도 하게 되는데, $x_50$이 가장 큰 영향을 주고있음. 마지막에 준 값이 가장 영향이 큰 것이 문제.

 

 Non-linearity functions에 따른 문제


- sigmoid보다 derivative의 폭이 넓은 $\tanh$ 를 사용


- 그럼에도 $\tanh$ 의 값이 거의 항상 1 보다 작음 $\rightarrow$ Vanishing gradient(기울기 소실)

$$
\left.\frac{\partial L_{T}}{\partial W}=\frac{\partial L_{T}}{\partial h_{T}}\left(\prod_{t=2}^{T} \tanh ^{\prime}\left(W_{h h} h_{t-1}+W_{x h} x_{t}\right)\right)\right) W_{h h}^{T-1} \frac{\partial h_{1}}{\partial W}
$$

 

 

 

 

 

 

그렇다고 Non-linearity를 제거한다면,

 

 

4.3.4  Solution to The Problem of Vanilla RNN

 

 Gate를 추가하여 선택적으로 정보를 기억할 수 있도록 개선

  - Long Short Term Memory (LSTM) [Hochreiter, 1997], [Gers, 2000]

  - Gated Recurrent Unit (GRU) [Cho, 2014]

기억을 좀더 오래하고, 잊을건 빨리 잊자는.,

 

반응형
Posted by creatoryoon
,
반응형

 

Chapter 3: Feed Forward Neural Net

3.1 Perceptron – 신경망

퍼셉트론이란 뉴런을 모방한 회로를 말한다. 

http ://cs231n.stanford.edu/slides/winter1516_lecture4.pdf
© 2017, SNU BioIntelligence Lab, h+p ://bi.snu.ac.kr/

다만, 이 퍼셉트론은 선형분류라는 특성상 한계가 존재한다.

 

 §  동그라미와 세모를 분리하려면 어떻게 해야 할까?

 §  y = ax + b 형태의 직선을 이용할 수 있다.

 §  일반화해서 표현해 보자.

 §  Generalization

3.1 Perceptron – AND 분류

 

3.1 Perceptron – OR 분류


3.1 Perceptron – XOR 분류

 

 §  Perceptron XOR을 분류하지 못한다

 §  직선 두 개를 이용하여 XOR 분류를 해결 할 수 있다.

 §  그러면, 어떤 모델을 이용하여 직선 두 개를 나타낼 수 있을까?

 

3.2.1 Multi-later Perceptron

 §  여기서 나온 해결책이 MLP로  layer를 추가하면 해결 가능하다.

 

 레이어를 추가하면 아래와 같이 공간이 사상되어 선형 분류가 가능해진다. 다만 이는 svm과의 커널 트릭과는 다름에 유의.

 

 

 

 

 

 

 

 

3.2.2 Multi layer Perceptron 학습

 §  MLP 학습이란?

  • 학습 자료를 이용하여 $W$  를 추정하는 것이다.

 §  MLP 학습을 위해 해주어야 하는 일

  • Input layer node 수 결정

    - Domain에 따라 결정

  • Output layer node 수 결정

    - Domain에 따라 결정한다

  • Hidden layer node 수 결정

    - 실험을 통해 결정

  • 학습 algorithm을 이용한 weight 추정

    - Back-propagation알고리즘을 이용하여 레이블 된 학습 자료에 최적 weight를 추정한다

  

 

3.2.2 Back propagation algorithm

 §  학습은 back propagation 알고리즘으로 수행됨

 §  Activation 함수로는 sigmoid를 이용한다.


 

 

 §  두개의 input, 두개의 hidden neurons, 두개의 output neurons으로 구성된 기본 neural network 구조

 

 §  예제

 §  Backpropagation

  • weight들을 최적화하여, 임의의 input/output에 대하여 올바른 답을 낼 수 있도록 함

 §  The forward Pass

  • $h_{1}$뉴런 input

    - $ \text { net }_{h 1}=w_{(i 1, h 1)} * i_1+w_{(i 2, h 1)} * i_2+b_1 * 1 $

    - $ \text { net }_{h 1}=0.15 * 0.05+0.3 * 0.1+0.35 * 1=0.38075 $

  • logistic function을 거친  $h_{1}$ output

    -$\text { out }_{h 1}=\frac{1}{1+e^{- \text {net }_{h 11}}}=\frac{1}{1+e^{-0.3775}}=0.593269992$

  • $h_{2}$에 대하여 같은 과정을 반복

    -$\text { out }_{h 2}=0.596884378$

  • $h_{1}$과 $h_{2}$ 의 방법으로 $o_1$을 진행

    -$\text { net }_{o 1}=w_{(h 1,01)} * \text { out }_{h 1}+w_{(h 2,01)} * \text { out }_{h 2}+b 3 * 1$

    -$\text { net}_{o 1}=0.4 * 0.593269992+0.45 * 0.596884378+0.6 * 1=1.105905967$

    -$\text { out}_{01}=\frac{1}{1+e^{-n e t_{01}}}=\frac{1}{1+e^{-1.105905967}}=0.75136507$

 

  • $o_{1}$의 방법으로 $o_{2}$를 반복

    -$\text { out }_{o 2}=0.772928465$

 §  Calculating the Total Error

  • 앞서 계산된 뉴런의 output squared error function input으로 에러를 계산

    - 수식의 계수 ½ 은 미분의 편의성을 위함. 후 약분 됨.

  • 예시

    - $o_1$ target output = 0.01

    - $o_1$ neural net output = 0.75136507

    - 따라서, error는 $
\begin{aligned}
E_{o 1} & =\frac{1}{2}\left(\text { target }_{\text {o1 }}-\text { out }_{\text {o1 }}\right)^2 =\frac{1}{2}(0.01-0.75136507)^2 =0.274811083
\end{aligned}
$

 §  The Backwards Pass

  • Back propagation의 목표인 weight update를 진행

 §  Output Layer

  • The backwards pass

  • 예를 들어, $w_{(h_{1},o_{1})}$를 update 한다면

    - total error에 어느정도 영향을 주는지 알기 위해 편미분을 진행 (체인 룰 적용)

  • 위 내용을 시각화 하면 아래와 같다

  • 각 부분별로 계산을 진행

  • $
\frac{\partial E_{\text {total }}}{\partial w_{\left(h_1, \omega_1\right)}} * \frac{\partial \text { out }_{a 1}}{\partial \text { net }_{o 1}} * \frac{\partial E_{\text {total }}}{\partial o u t_{o 1}}=\frac{\partial E_{\text {total }}}{\partial w_{\left(h_1, \omega_1\right)}}
$

https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/

 

  •  먼저, output($o_1$)의 변화에 대한 total error의 변화를 계산

    -$
E_{\text {total }}=\frac{1}{2}\left(\text { target }_{01}-\text { out }_{01}\right)^2+\frac{1}{2}\left(\text { target }_{02}-o u t_{02}\right)^2
$

    -$
\frac{\partial E_{\text {total }}}{\partial \text { out }}=2 * \frac{1}{2}\left(\text { target }_{o 1}-\text { out }_{01}\right)^{2-1} *-1+0
$

    -$
\frac{\partial E_{\text {totai }}}{\text { out }_{01}}=-\left(\text { target }_{01}-\text { out }_{01}\right)=-(0.01-0.75136507)=0.74136507
$

  • total net input의 변화에 대한 output($o_1$)의 변화를 계산

    - logistic function의 편미분 결과는 $out(1-out)$

    - $
\text { out }{ }_{o 1}=\frac{1}{1+e^{-n e t}}
$

    -$
\frac{\partial o u t_{o 1}}{\partial e_{o 1}}=\text { out }_{o 1}\left(1-\text { out }_{o 1}\right)=0.75136507(1-0.75136507)=0.186815602
$

  • 그리고, $w_{()}$의 변화에 대한 의 변화를 계산

    -$
\text { net }_{o 1}=w_{(h 1, o 1)} * \text { out }_{h 1}+w_{(h 2, o 1)} * \text { out }_{h 2}+b_2 * 1
$

    - $
\frac{\partial n e t_{o 1}}{\partial w_{(\hbar 1,01)}}=1 * \text { out }_{h 1} * w_{\left(h_1, o_1\right)}^{(1-1)}+0+0=\text { out }_{h 1}=0.593269992
$

  • 위 결과 식들을 종합

    - $w_{(h_1,o_1)}$ 의 변화에 대한 total error

     *$
\frac{\partial E_{\text {total }}}{\partial w_{\left(h_1, o_1\right)}}=\frac{\partial E_{\text {total }}}{\partial o u t_{o 1}} * \frac{\partial o u t_{o 1}}{\partial \text { net }_{o 1}} * \frac{\partial \text { net }_{o 1}}{\partial w_{(h 1, o 1)}}
$

     *$
\frac{\partial E_{\text {total }}}{\partial w_{(\hbar 1,01)}}=0.74136507 * 0.186815602 * 0.593269992=0.082167041
$

  •  error를 감소시키기 위해, 위 식에서 얻은 값을 현재 weight에서 빼준다. 이때 learning rate(eta)을 곱한 뒤 뺀다.

    -$
w_{\left(h_1, 0_1\right)}^{+}=w_{\left(h_1, 0_1\right)}-\eta * \frac{\partial E_{\text {total }}}{\partial w_{\left(h_1, 0_1\right)}}=0.4-0.5 * 0.082167041=0.35891648
$

  •  동일한 프로세스를 $w^{+}_{(h_1, o_1)}~w^{+}_{(h_2,o_2)}$에 대하여 반복

    -$
w_{\left(h_2, o_1\right)}^{+}=0.408666186
$

    -$
w_{\left(h_1, o_2\right)}^{+}=0.511301270
$

    -$
w_{\left(h_2, 0_2\right)}^{+}=0.561370121
$

  •  weight실제 update hidden layer에 대해서 새로운 weight모두 구한 후 update를 진행

 

 §  Hidden Layer

  • The backwards pass (Cont.)

    - $w_{(i_1, h_1)}~w_{(i_2, h_2)}$에 대해서 값을 계산

  • output layer에서 진행했던 방식과 유사한 방식

    - 차이점 : 여러 개의 output neurons의 변화량 사용
      $h_1$ out부분이 $o_1, o_2$에 영향을 줌

         $
\frac{\partial E_{\text {total }}}{w_{(i 1, \boldsymbol{h} \mathbf{1})}}=\frac{\partial E_{\text {total }}}{\partial o u t_{h \mathbf{1}}} * \frac{\partial o u t_{\boldsymbol{h} \mathbf{1}}}{\partial n e t_{\boldsymbol{h} 1}} * \frac{\partial \text { net }_{\boldsymbol{h} \mathbf{1}}}{\partial w_{(i 1, \boldsymbol{h} \mathbf{)}}}
    $

                    **$
\frac{\partial E_{\text {total }}}{\partial o u t_{h 1}}=\frac{\partial E_{o 1}}{\partial o u t_{h 1}}+\frac{\partial E_{o 2}}{\partial o u t_{h 1}}
$

https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/

 

 

 §  Hidden Layer (Cont.)

  • $
\frac{\partial E_{o 1}}{\partial o u t_{h 1}}
$계산시 앞서 계산한
$
\frac{\partial E_{o 1}}{\partial n e t_{h 1}}
$
을 사용

    -$
\frac{\partial E_{o 1}}{\partial o u t_{h 1}}=\frac{\partial E_{o 1}}{\partial \text { net }_{o 1}} * \frac{\text { net }_{o 1}}{\partial o u t_{h 1}}
$

    -$
\frac{\partial E_{01}}{\partial n e t_{o 1}}=\frac{\partial E_{01}}{\partial o u t_{o 1}} * \frac{\partial o u t_{o 1}}{\partial n e t_{01}}=0.74136507 * 0.186815602=0.138498562
$

  •  $
\frac{\partial n e t_{o 1}}{\partial o u t_{h 1}}
$와 $w_{(h_1, o_1)}$이 같으므로

    -$
\text { net }_{o 1}=w_{(h 1, o 1)} * \text { out }_{h 1}+w_{(h 2,01)} * \text { out }_{h 2}+b 3 * 1
$

    -$
\frac{\partial n e t_{o 1}}{\partial o u t_{h 1}}=w_{(h 1, o 1)}=0.40
$

  • 위 식들을 합쳐주면

    -$
\frac{\partial E_{o 1}}{\partial \text { out }_{h 1}}=\frac{\partial E_{o 1}}{\partial \text { net }_{o 1}} * \frac{\partial n e t_{o 1}}{\partial \text { out }_{h 1}}=0.138498562 * 0.40=0.055399425
$

  • 같은 방식으로 $
\frac{\partial E_{o 2}}{\partial o u t_{h 1}}
$
를 계산

    ⁻$$
\frac{\partial E_{o 2}}{\partial \text { out }}=-0.019049119
$$

  • 따라서 $
\frac{\partial E_{\text {total }}}{\partial o u t_{h 1}}
$
계산 가능

    ⁻$
\frac{\partial E_{\text {total }}}{\partial \text { out }_{h 1}}=\frac{\partial E_{o 1}}{\partial o u t_{h 1}}+\frac{\partial E_{o 2}}{\partial o u t_{h 1}}=0.055399425+-0.019049119=0.036350306
$

  • $
\frac{\partial E_{\text {total }}}{\partial o u t_{h 1}}
$를 얻었으므로, 각 weight
에 대해 $
\frac{\text { oout }_{h 1}}{\partial \text { net }_{h 1}}
$
$
\frac{\partial n e t_{h 1}}{\partial w}
$
를 계산

    -$
\text { out }_{\boldsymbol{h} \mathbf{1}}=\frac{1}{1+e^{- \text {net }_{h 1}}}
$$

    -$
\frac{\text { ouut }_{h 1}}{\partial \text { net }_{h 1}}=\text { out }_{h 1}\left(1-\text { out }_{h 1}\right)=0.59326999(1-0.59326999)=0.241300709
$

  • output 뉴런에서 했던 방식을 적용하여, $w_{(i_1, h_1)}$에 대한 total net input to $h_1$ 의 편미분을 계산

    -$
\text { net }_{h 1}=w_{(i 1, h 1)} * i_1+w_{(i 2, h 1)} * i_2+b 1 * 1
$

    -$
\frac{\partial n e t_{h 1}}{\partial w_{(i 1, h 1)}}=i_1=0.05
$

  • 식을 모두 합치면$
\frac{\partial E_{\text {total }}}{\partial w_{(i 1, h 1)}}=\frac{\partial E_{\text {total }}}{\partial o u t_{h 1}} * \frac{\partial o u t_{h 1}}{\partial n e t_{h 1}} * \frac{\partial n e t_{h 1}}{\partial w_{(i 1, h 1)}}
$

    -$
\frac{\partial E_{\text {total }}}{\partial w_{\left(i_1, h_1\right)}}=0.036350306 * 0.241300709 * 0.05=0.000438568
$

  • $w_{(i_1, h_1)}$을 update

    -$
w_{\left(i_1, h_1\right)}^{+}=w_{\left(w_1, h_1\right)}-\eta \frac{\partial E_{\text {total }}}{\partial w_{\left(i_1, h_1\right)}}=0.15-0.5 * 0.000438568=0.149780716
$

  • 같은 방법으로 $
w_{\left(i_2, h_1\right)}^{+} \sim w_{\left(i_2, h_2\right)}^{+}
$ 를 반복

    -$
w_{\left(i_2, h_1\right)}^{+}=0.19956143
$

    -$
w_{\left(i_1, h_2\right)}^{+}=0.24975114
$

    -$
w_{\left(i_2, h_2\right)}^{+}=0.29950229
$

 §  학습 결과 예제

  •  1st error = 0.298371109

  •  2nd error = 0.291027924

    -
    -

  •  10000th error = 0.00035085

    - 이때, 두 output 뉴런을 보면,

     0.015912196 (vs 0.01 target)
     0.984065734 (vs 0.99 target)

 

반응형
Posted by creatoryoon
,
반응형

흔히 게임이론이라고 하면, 게임을 만드는 방법으로 생각하고는 한다.

나에게 누군가 게임이론이 무엇이냐고 질문한다면, "특정 관념에 기초한 최적화 기법" 이라는 말을 할 것이다. 

게임이론이란, 의사결정, 분배에 대한 문제로써 모든 상황과 문제를 게임 상황에 대입시켜 계산하기 위한 기법이다. 

 

게임이론은 몇가지 테마로 나뉘게 되는데, 대표적으로 협력게임과 비협력 게임이다. 다만, 이를 단순히 구분하기는 어려운 것이 협력게임에서도 (예를들면 경쟁이 아예 없다면 사회의 발전이 어려울 것이다.) 계산과정에서 비협력게임을 도입하는 경우도 존재한다.

이 글에서 설명할 바게닝은 전통적으로 협력게임에 속한다.

 

 

바게닝을 접하면 가장 많이 접하게 되는 내쉬 바게닝 솔루션은, Feasible space에서 내쉬곱의 최대가 되는 지점을 찾는 방법으로, 수식으로 표현하자면 다음과 같다.

 

 

Find NBS for Two-person bargaining problem

$$
\begin{aligned}
& S=\left\{\left(y_1, y_2\right) \mid 0 \leq y_1 \leq 30,0 \leq y_2 \leq 30-y_1\right\} \\
& d=(0,0)
\end{aligned}
$$
$$
\begin{aligned}
& \operatorname{NBS}(S)=\operatorname{argmax}\left(y_1 y_2\right) \\
& y_1 y_2=y 1\left(30-y_1\right) \\
& y_1\left(30-y_1\right) \frac{d}{d y_1}=0 \Leftrightarrow 2 y_1=30 \\
& y_1=15, y_2=15
\end{aligned}
$$

$$
S=\left\{\left(y_1, y_2\right) \mid 0 \leq y_1 \leq 30,0 \leq y_2 \leq \sqrt{30-y_1}\right\}
$$
$$
\begin{aligned}
& y_1 y_2=y_1 \sqrt{30-y_1} \\
& y_1 \sqrt{30-y_1} \frac{d}{d y_1}=0 \\
& \sqrt{30-y_1}+\frac{-1 \cdot y_1}{2 \sqrt{30-y_1}}=\frac{60-3 y_1}{2 \sqrt{30-y_1}}=0 \\
& y_1=20, y 2=\sqrt{10}
\end{aligned}
$$

 

 

$$
\begin{aligned}
& N B S=\operatorname{argmax}\left(U_E U_C \mid U_E, U_C \in \operatorname{scenario}(n)\right) \\
& E S=\left(U_E, U_C \mid U_E=U_C,\left(U_E, U_C \in \operatorname{scenario}(n)\right)\right) \\
& K S B S=\left(U_E, U_C \mid U_E, U_c \in \operatorname{scenario}(n) \text { and } U_E, U_C \in d+(a-d)\right) \\
& N=\{E, C\} . T=[0,1]
\end{aligned}
$$


$$
\begin{aligned}
& \text { scenario1. } U_E(t)=1-t, U_C(t)=t \\
& N B S:\left(U_E U_C\right)^r=1-2 t=0, t=0.5,\left(U_E, U_C\right)=(0.5,0.5) \\
& E S: U_E=U_C \Leftrightarrow 1-t=t \Leftrightarrow t=0.5,\left(U_E, U_C\right)=(0.5,0.5) \\
& K S B S: \max \left(U_E, U_C\right)-\min \left(U_E, U_C\right)=(1,1) \Leftrightarrow U_E=U_C \\
& 1-t=t \Leftrightarrow t=0.5,\left(U_E, U_C\right)=(0.5,0.5)
\end{aligned}
$$

$$
\begin{aligned}
& \operatorname{scenario2.} U_E(t)=1-t, U_C(t)=t^{\frac{1}{2}} \\
& N B S:\left(U_E U_C\right)^{\prime}=1 / 2 \sqrt{t}-3 / 2 \cdot \sqrt{t}=(1-3 t) / 2 \sqrt{t}=0, t=1 / 3,\left(U_E, U_C\right)=(2 / 3, \sqrt{1 / 3}) \\
& E S: U_E=U_C \Leftrightarrow 1-t=\sqrt{t} \Leftrightarrow t=3 / 2-\sqrt{5} / 2 \\
& \left(U_E, U_C\right)=((\sqrt{5}-1) / 2,(\sqrt{6-2 \sqrt{5}}) / 2)=\left((\sqrt{5}-1) / 2,\left(\sqrt{(1-\sqrt{5})^2}\right) / 2\right)=((\sqrt{5}-1) / 2,(\sqrt{5}-1) / 2) \\
& K S B S: \max \left(U_E, U_C\right)-\min \left(U_E, U_C\right)=(1,1) \Leftrightarrow U_E=U_C, \text { same as ES }
\end{aligned}
$$

$$
\begin{aligned}
& \text { scenario3. } U_E(t)=(1-t)^{\frac{1}{2}}, U_C(t)=t^{\frac{1}{2}} \\
& N B S:\left(U_E U_C\right)^{\prime}=\frac{1-2 t}{2 \sqrt{t-t^2}}=0, t=0.5,\left(U_E, U_C\right)=(\sqrt{1 / 2}, \sqrt{1 / 2}) \\
& E S: U_E=U_C \Leftrightarrow \sqrt{1-t}=\sqrt{t} \Leftrightarrow t=0.5,\left(U_E, U_C\right)=(\sqrt{1 / 2}, \sqrt{1 / 2}) \\
& K S B S: \max \left(U_E, U_C\right)-\min \left(U_E, U_C\right)=(1,1) \Leftrightarrow U_E=U_C, \text { same as } E S
\end{aligned}
$$

$$
\begin{aligned}
& \text { scenario4. } U_E(t)=2(1-t), U_C(t)=t \\
& N B S:\left(U_E U_C\right)^{\prime}=2-4 t=0, t=0.5 \\
& E S: U_E=U_C \Leftrightarrow 2-2 t=t \Leftrightarrow t=2 / 3 \\
& K S B S: \max \left(U_E, U_C\right)-\min \left(U_E, U_C\right)=\operatorname{vector}(2,1) \Leftrightarrow U_E=2 U_C \\
& 2-2 t=2 t \Leftrightarrow t=0.5
\end{aligned}
$$

 

 

반응형
Posted by creatoryoon
,
반응형

Intreoduction

Bayesian Decision Theory

-       근본적인 통계 패턴 분류 문제에 대한 접근
-       
다양한 분류간의 트레이드 오프를 정량화하는 것을 기반
-       
확률을 이용한 결정과 그러한 결정에 수반되는 비용

State of nature ω  자연의 상태

-        $\omega=\omega_1$ 을 위한 sea bass, $\omega=\omega_2$ 를 위한 salmon

-       자연의 상태는 예측불가
-       
확률적으로 기술되어야 하는 변수

A priori probability

-       Seabass, salmon에 대한 사전지식의 반영

$P\left(\omega_1\right):$ Seabass일 사전확률

$P\left(\omega_2\right):$ Salmon일 사전확률

$P\left(\omega_1\right)+P\left(\omega_2\right)=1$ (, 다른 생선이 없다면.)

Decision Rule

-       물고기를 보지 못하고 결정해야 한다면?
$P\left(\omega_1\right)>P\left(\omega_2\right)$ 
$\omega_1$ 판정, 반대라면 반대로 판정

 

클레스-조건부(class-conditional)확률 밀도 함수 $p(x \mid \omega)$

-       자연의 상태가 ω  라고 주어졌을 때, 에 대한 확률 밀도 함수

$p\left(x \mid \omega_1\right), p\left(x \mid \omega_2\right)$간의 차이는 SeabassSalmon의 모집단 간 밝기의 차이를 묘사

 

부류 $\omega_j$ 에 있고 특징 값를 갖는 패턴을 발견할 결합 확률 밀도는 두 가지 방법을 쓸 수 있다:

$p\left(\omega_j, x\right)=P\left(\omega_j \mid x\right) p(x)=p\left(x \mid \omega_j\right) P\left(\omega_j\right)$

Bayes formula

$P\left(\omega_j \mid x\right)=\frac{p\left(x \mid \omega_j\right) P\left(\omega_j\right)}{p(x)}$

이때 이 두부류의 경우는 $p(x)=\sum_{j=1}^2 p\left(x \mid \omega_j\right) P\left(\omega_j\right)$. posterior $=\frac{\text { likelihood } \times \text { prior }}{\text { evidence }}$

Bayes 공식은$x$ 의 값을 관찰함으로써 사전확률prior을 사후확률posterior(특징$x$ 가 측정되었을 때 자연 상태가$\omega_j$ 일 확률) 로 전환할 수 있다. $p\left(x \mid \omega_j\right)$는 $x$에 대한 $\omega_j$우도(likelihood)라고 부른다.

 

클레스 조건부 확률 밀도에 대한 특정 사정 확률에 대한 사후 확률. 모든 x에서 사후확률의 합은 1.0이다.

$P\left(\omega_1\right)=\frac{2}{3}$ and $P\left(\omega_2\right)=\frac{1}{3}$

 

Probability of error when decision is made

결정 방법은 $P($ error $\mid x)=\left\{\begin{array}{l}P\left(\omega_1 \mid x\right) * \text { if decide } \omega_2 \\ P\left(\omega_2 \mid x\right) * \text { if decide } \omega_1\end{array}\right.$

- 결정을 내릴 때 오류가 나올 확률은,$P($ error $)=\int_{-\infty}^{\infty} p($ error,$x) d x=\int_{-\infty}^{\infty} P($ error $\mid x) p(x) d x$ 만일 모든 x에 대해 $P(\operatorname{error} \mid x)$ 를 작게 만든다면 이 적분은 가능한 작아야 한다.

 

$P\left(\omega_j \mid x\right)=\frac{p\left(x \mid \omega_j\right) P\left(\omega_j\right)}{p(x)}$

Bayes Decision Rule (for minimizing the probability of error)

- Decide $\omega_1$ if $p\left(\omega_1 \mid x\right) P\left(\omega_1\right)>p\left(\omega_2 \mid x\right) P\left(\omega_2\right) ; \omega_2$ 로 판정 otherwise

- $\boldsymbol{p}(\boldsymbol{x})$ : 는 결정에 있어서는 크게 중요하지 않음 $\left(P\left(\omega_1 \mid x\right)+P\left(\omega_2 \mid x\right)=1\right)$

 

Decide $\omega_1$ if $p\left(\omega_1 \mid x\right) P\left(\omega_1\right)>p\left(\omega_2 \mid x\right) P\left(\omega_2\right) ; \omega_2$ 로 판정 otherwise

사후 확률의 역할을 강조.
-
만일 어떤 x에 대해서 $p\left(x \mid \omega_1\right)=p\left(x \mid \omega_2\right)$라면 판정은 전적으로 사전 확률에 의해 정해진다.
- $P\left(\omega_1\right)=P\left(\omega_2\right)$라면 판정은 전적으로 우도$p\left(x \mid \omega_j\right)$ 근거하게 된다.

 

 

Bayesion decion theory – continuious features(연속적 특징)

Bayesian Theory의 일반화

-       둘 이상(more than one feature)의 특징을 사용하는 것을 허용하는 것
-
스칼라 x

를 특징vector x

로 대체
-
x

특징공간이라고 부르는 d

-차원 유클리드 공간 Rd

에 속함

Bayesion decion theory – continuious features(연속적 특징)

Bayesian Theory의 일반화

- 둘 이상(more than one feature)의 특징을 사용하는 것을 허용하는 것
-
스칼라 를 특징vector 로 대체
-
특징공간이라고 부르는 d-차원 유클리드 공간 $\mathbb{R}^d$ 에 속함

 

- 셋 이상(more than two states)의 자연의 상태를 허용하는 경우

- $\left\{\omega_1, \ldots, \omega_c\right\}: c$개의 자연의 상태(“categories”)의 유한 집합

 

- 분류 외의 행동을 허용하는 것

- $\left\{\alpha_1, \ldots, \alpha_a\right\}$: a개의 가능한 행동의 유한 집합

 

그림은 분류기준을 만들 때, 일정 수준 이하는 결정을 못하게 하는경우. - 사람이 해야함 .....확실한 것만 분류기가 분류하게 한다.

- 오류 확률(probability of error)보다 더 일반적이라 할 수 있는 손실 함수(loss function)를 도입.
  -
손실 함수는 각 행동의 비용을 정확하게 나타내며, 확률 측정을 판정으로 전환에 사용된다.
$\lambda\left(\alpha_i \mid \omega_j\right)$ : 자연의 상태가 $\alpha_i$ 일 때, $\omega_j$ 라는 행동을 취해서 초래되는 손실

$P\left(\omega_j \mid x\right)=\frac{p\left(x \mid \omega_j\right) P\left(\omega_j\right)}{{p(x)}}$ 이때, $\quad p(x)=\sum_{j=1}^c p\left(x \mid \omega_j\right) P\left(\omega_j\right)$

 

행동 $\alpha_i$를 취하는 것과 관련된 기대 손실은 단순하게 $R\left(\alpha_i \mid x\right)=\sum_{j=1}^c \lambda\left(\alpha_i \mid \omega_j\right) P\left(\omega_j \mid x\right)$

-       판정-이론 용어로는 기대 손실을 리스크라고 부르며, $R\left(\alpha_i \mid x\right)$ 를 조건부 리스크라고 부른다.

-      문제는 $P\left(\omega_j\right)$에 대해 전체적 리스크를 최소화하는 판정 룰을 찾는 것.

$R=\int R(\alpha(x) \mid x) p(x) d x \quad R$ : 최소화된 전체적 리스크

 

-      전체적 리스크를 최소화하기 위한 조건부 리스크 계산 $R\left(\alpha_i(x)\right)$ 가 가능한 작도록 $\alpha(x)$가 선택된다면 전체적 리스크는 최소화

$R\left(\alpha_i \mid x\right)=\sum_{j=1}^c \lambda\left(\alpha_i \mid \omega_j\right)\left(\omega_j \mid x\right)$

$i=1, \ldots, a$ 에 대해 계산하고, $R\left(\alpha_i \mid x\right)$ 가 최소인 행동 $\alpha_i$ 를 선택

 

Bayesion decion theory – 두 부류(Two-Category) 분류

-     $\alpha_1$ 자연의 참 상태가 $\omega_1$ 이라고 판정을 내리는 것

-     $\alpha_2$ 자연의 참 상태가 $\omega_2$ 이라고 판정을 내리는 것

-     $\lambda_{i j}=\lambda\left(\alpha_i \mid \omega_j\right)$: 자연의 참 상태가 $\omega_j$일 때 $\omega_i$라고 판정시 따르는 손실 이를 적용해서 $R\left(\alpha_i \mid x\right)=\sum_{j=1}^c \lambda\left(\alpha_i \mid \omega_j\right)\left(\omega_j \mid x\right)$ 를 다시 쓰면

-      조건부 리스크 $$
\begin{aligned}
& R\left(\alpha_1 \mid x\right)=\lambda_{11} P\left(\omega_1 \mid x\right)+\lambda_{12} P\left(\omega_2 \mid x\right) \\
& R\left(\alpha_2 \mid x\right)=\lambda_{21} P\left(\omega_1 \mid x\right)+\lambda_{22} P\left(\omega_2 \mid x\right)
\end{aligned}
$$
$\lambda_{11}, \lambda_{22}$ 는 잘한 것

-      최소 리스크 판정 룰을 표현하는 다양한 방법 
1. $R\left(\alpha_1 \mid x\right)<R\left(\alpha_2 \mid x\right)$ 이면 $\omega_1$ 로 판정
2. $\left(\lambda_{21}-\lambda_{11}\right) P\left(\omega_1 \mid x\right)>\left(\lambda_{12}-\lambda_{22}\right) P\left(\omega_2 \mid x\right)$ 일 때 $\omega_1$ 이라고 판정(사후확률로 표현)
3. $\left(\lambda_{21}-\lambda_{11}\right) p\left(x \mid \omega_1\right) P\left(\omega_1\right)>\left(\lambda_{12}-\lambda_{22}\right) p\left(x \mid \omega_2\right) P\left(\omega_2\right)$ 이면 $\omega_1$ 로 판정하고 아니면 $\omega_2$ 로 판정 (Bayes공식을 사용함으로 사후 확률을 사전 확률과 조건부 밀도로 대체)
4. $\lambda_{21}>\lambda_{11}$ 이라는 논리적 가정 하에서 만약 $\frac{p\left(x \mid \omega_1\right)}{p\left(x \mid \omega_2\right)}>\frac{\lambda_{12}-\lambda_{22}}{\lambda_{21}-\lambda_{11}} \frac{P\left(\omega_2\right)}{P\left(\omega_1\right)}$ 이면 $\omega_1$ 로 판정 

(Likelihood ratio: 이 형태의 판정 룰은 확률 밀도들의 $x$-종속성에 초점을 맞춘다. $p\left(x \mid \omega_j\right)$ 를 $\omega_j$ 의 함수(즉, 우도 함수)로 간주하고 우도 비 $\frac{p\left(x \mid \omega_1\right)}{p\left(x \mid \omega_2\right)}$ 를 만들 수 있다. 따라서 Bayes 판정 룰은 관찰 $x$ 에 독립적인 어떤 문턱 값을 우도비가 넘으면 $\omega_1$ 로 판정할 것을 요구하는 것으로 해석)

 

Bayesion decion theory – Minimum-error-rate Classification(최소 에러율 분류)

에러를 피하기 위해서는 자연의 상태와 차이가 가장 적은(오류를 최소화하는) 판정 룰을 찾는 것이 당연하다.

Zero-One loss function

$\lambda\left(\alpha_i \mid \omega_j\right)=\left\{\begin{array}{ll}0 & i=j \\ 1 & i \neq j\end{array} i, j=1, \ldots, c\right.$

 

-       옳은 판정에 대해서는 손실이 없음

-       모든 에러에 단위 손실을 부여

-       모든 에러는 같은 비용이 든다.

 

$\sum_{j=1}^c \lambda\left(\alpha_i \mid \omega_j\right) P\left(\omega_j \mid x\right)=\sum_{j \neq i} P\left(\omega_j \mid x\right)=1-P\left(\omega_i \mid x\right)$

조건부 리스크를 최소화하는 행동을 선택 if. $P\left(\omega_i \mid x\right)>P\left(\omega_j \mid x\right)$ we decide $\omega_i \forall j \neq i$

 

The likelihood ratio $p\left(x \mid \omega_1\right) / p\left(x \mid \omega_2\right)$

$\frac{p\left(x \mid \omega_1\right)}{p\left(x \mid \omega_2\right)}>\frac{\lambda_{12}-\lambda_{22}}{\lambda_{21}-\lambda_{11}} \frac{P\left(\omega_2\right)}{P\left(\omega_1\right)}$

만일 0-1 분류 손실을 채택하면 판정 경계들은 문턱치 $\theta_{a}$ 에 의해 결정된다. 만약 손실함수가 $\omega_{2}$ 를 $\omega_{1}$ 로 오분류하는 것에 큰 패널티를 가한다면, 더 큰 문턱치 $\ theta_{b}$ 를 가지고, $R_{1}$ 은 더 작아진다.

 

 

 

Classifiers, Discriminant functions, and Decision surfaces

다분류 경우

패턴 분류기를 표현하는 다양한 방법중에 가장 쓸만한 방법중 하나
-
판별함수$g_i(x), i=1, \ldots, c$ 에 의한 것

- 만일 $\boldsymbol{g}_{\boldsymbol{i}(\boldsymbol{x})}>\boldsymbol{g}_{\boldsymbol{j}}(\boldsymbol{x}) \forall \boldsymbol{j} \neq \boldsymbol{i}$ 이면 특징 벡터 $x$ 를 클레스 $\omega_i$ 에 할당한다.

 

분류기

-      분류기는 c개의 판별 함수를 계산하고 최대 판별식에 해당하는 부류를 선택하는 네트워크 또는 기계

$g_i(x)=\left\{\begin{array}{c}g_1(x)=0.1 \\ g_2(x)=0.05 \\ \vdots \\ g_{n(x)}=0.85\end{array}\right.$ 중 가장 큰 것 선택

 

 

판별 함수들의 선택은 유일하지 않다.

$g_i(x)=-R\left(\alpha_i \mid x\right)$ (for risk)
$g_i(x)=P\left(\omega_i \mid x\right) \quad($ for minimum $-$ error $-$ rate $)$

 

판별함수의 수정이 가능

판정에 영향을 주지 않고 우리는 항상 모든 판별 함수들을 같은 양의 상수로 곱하거나 같은 상수를 더해서 이동시킬 수 있다. 더 일반적으로는 모든 $g_i(x)$ 를 단조증가함수 $f(\cdot)$ 에 의해 $f\left(g_i(x)\right)$ 로 대체시, 그로인한 분류는 변하지 않는다. 이것은 현저한 분석 및 계산 단순화로 이끌 수 있다.

$\left\{\begin{array}{l}g_i(x)=P\left(\omega_i \mid x\right)=\frac{p\left(x \mid \omega_i\right) P\left(\omega_i\right)}{\Sigma^c p\left(x \mid \omega_j\right)^{P\left(\omega_j\right)}} \\ g_i(x)=p\left(x \mid \omega_i\right) P\left(\omega_i\right) \\ g_i(x)=\ln p\left(x \mid \omega_i\right)+\ln P\left(\omega_i\right)\end{array}\right.$

모든 판정 룰의 효과는 특징 공간을 $c$ 개의 판정 영역 $\mathcal{R}_1, \ldots, \mathcal{R}_c$ 로 나누는 것 

 

두 분류 경우

이분기(dichotomizer)
두 부류 경우는 다부류의 일종이나 정통적으로 독립해 다뤄왔다.
두 판별 함수 대신 단일 판별 함수를 정의하고 판정하는 것이 더 보편적이다.
$g(x) \equiv g_1(x)-g_2(x)$

$g(x)>0$ 이면 $\omega_1$, 아니면 $\omega_2$ 로 판정
$\boldsymbol{g}(\boldsymbol{x})=\boldsymbol{P}\left(\boldsymbol{\omega}_1 \mid \boldsymbol{x}\right)-\boldsymbol{P}\left(\boldsymbol{\omega}_2 \mid \boldsymbol{x}\right)$
$\boldsymbol{g}(\boldsymbol{x})=\ln \frac{\boldsymbol{p}\left(\boldsymbol{x} \mid \boldsymbol{\omega}_1\right)}{\boldsymbol{p}\left(\boldsymbol{x} \mid \boldsymbol{\omega}_2\right)}+\ln \frac{\boldsymbol{P}\left(\boldsymbol{\omega}_1\right)}{\boldsymbol{P}\left(\boldsymbol{\omega}_2\right)}$

 

The normal density

Normal density인가?

-       분석의 용이함(해석학적으로 다루기 쉬움)으로, 다변량 normal밀도, 또는 Gaussian밀도는 많은 관심을 받았다.

-        중요한 상황에 적합한 모델. class $\omega_j$에 특징벡터 x가 단일 또는 프로토타입 벡터 $\mu_i$의 연속적 값을 가지고 랜덤하게 오염된 버전일 경우에 적합.

Expectation (expected value)
$$
E[f(x)]=\int_{-\infty}^{\infty} f(x) p(x) d x
$$
만약 특징 $\mathrm{x}$ 의 값들이 이산 집합 $\mathrm{D}$ 의 점이라면.
$$
E[f(x)]=\sum_{x \in D} f(x) P(x)
$$

The normal density – 단변량 밀도

$p(x)=\frac{1}{\sqrt{2 \pi} \sigma} \exp \left[-\frac{1}{2}\left(\frac{x-\mu}{\sigma}\right)^2\right]$

 

- 평균
$\mu=E[x]=\int_{-\infty}^{\infty} x p(x) d x$
- 분산
$$
\begin{aligned}
& \sigma^2=E\left[(x-\mu)^2\right]=\int_{-\infty}^{\infty}(x-\mu)^2 p(x) d x \\
& -\quad \boldsymbol{p}(\boldsymbol{x}) \sim \boldsymbol{N}\left(\boldsymbol{\mu}, \boldsymbol{\sigma}^2\right)
\end{aligned}
$$
$x$ 는 평균 $\mu$ 와 분산 $\sigma^2$ 에 의해 분포된다.

 

 

The normal density – 다변량 분포

 

$$
p(\boldsymbol{x}) \sim N(\boldsymbol{\mu}, \boldsymbol{\Sigma}) \quad p(\boldsymbol{x})=\frac{1}{(2 \pi)^{d / 2}|\boldsymbol{\Sigma}|^{1 / 2}} \exp \left[-\frac{1}{2}(\boldsymbol{x}-\boldsymbol{\mu})^t \boldsymbol{\Sigma}^{-1}(\boldsymbol{x}-\boldsymbol{\mu})\right]
$$
- 평균 벡터
$$
\text { - } \boldsymbol{\mu}=E[\boldsymbol{x}]=\int_{-\infty}^{\infty} \boldsymbol{x p}(x) d \boldsymbol{x}
$$
- 공분산 행렬 (Convariance)
$$
\Sigma=E\left[(\boldsymbol{x}-\boldsymbol{\mu})(\boldsymbol{x}-\boldsymbol{\mu})^t\right]=\int_{-\infty}^{\infty}(\boldsymbol{x}-\boldsymbol{\mu})(\boldsymbol{x}-\boldsymbol{\mu})^t p(x) d x{ }^*[x-\mu]=\left[\begin{array}{lll}
\sigma_{11} & & \\
& \ddots & \\
& \sigma_{n n}
\end{array}\right]
$$
- 통계적 독립성(statistical independence)
만약 $x_i$ 와 $x_j$ 가 통계적으로 독립적이면, $\sigma_{i j}=0$ 일 것이다. 만약 모든 비대각선 요소들이 0 이면, $p(x)$ 는 $x$ 의 요소들에 대한 단변량 노멀 밀도들의 곱으로 축소된다.

 

 

- 독립적이거나 아니거나, 결합적으로(jointly) 노멀하게 분포 하는 랜덤 변수들의 선형 결합(combination)은 노멀하게 분포한다.
$$
\begin{aligned}
& p(\boldsymbol{x}) \sim N(\boldsymbol{\mu}, \mathbf{\Sigma})  \\
& \boldsymbol{y}=\boldsymbol{A}^t \boldsymbol{x} \rightarrow p(\boldsymbol{y}) \sim N\left(\boldsymbol{A}^{\boldsymbol{t}} \boldsymbol{\mu}, \boldsymbol{A}^{\boldsymbol{t}} \boldsymbol{\Sigma} \boldsymbol{A}\right) \\
& * y=A^t y=\left[\begin{array}{lll}
y_1 \\
y_2
\end{array}\right]=\left[\begin{array}{ccc}
1 & \cdots & 0 \\
\vdots & A & \vdots \\
0 & \cdots & 1
\end{array}\right]\left[\begin{array}{l}
x_1 \\
x_2
\end{array}\right] 
\end{aligned}
$$
-임의의 다변량 분포를 구형(spherical)분포로 변환(공분산 행렬이 항등 행렬 $I$ 에 비례하는 분포)할 수 있다. (백색변환)
$$
A_\omega=\Phi \Lambda^{1 / 2} 
$$
$\boldsymbol{\Phi}$ : 열들이 $\Sigma$ 인 정규직교 고유 벡터들인 행렬
$\mathbf{\Lambda}$ : 해당 고윳값들의 대각선 행렬

 

- 다변량 정규분포는 $\boldsymbol{d}+\boldsymbol{d}(\boldsymbol{d}+\mathbf{1}) / \mathbf{2}$ 개의 파라미터 즉, 평균 벡터 $\boldsymbol{\mu}$ 의 요소들과 공분산 행렬 $\boldsymbol{\Sigma}$ 에 의해 완전하게 정의된다.

 

- 아래 그림에서:↓
- 클러스터의 중심은 평균 벡터에 의해 결정 
- 클러스터의 모양은 공분산 행렬에 의해 결정.
- 상수 밀도의 점들의 위치는 $(\boldsymbol{x}-\boldsymbol{\mu})^t \boldsymbol{\Sigma}^{-1}(\boldsymbol{x}-\boldsymbol{\mu})$ 가 상수 인 초타원체들이다
- 이 초타원체들의 주축은 $\Phi$ 에 의해 묘사되는 $\boldsymbol{\Sigma}$ 의 고유 벡터들에 의해 주어지며,  
고윳값들 $(\boldsymbol{\Lambda})$ 은 이 축들의 길이를 결정한다.
- Mahalanobis distance (from $x$ to $\boldsymbol{\mu}$ ) 마할라노비스 거리 $r^2=(\boldsymbol{x}-\boldsymbol{\mu})^t \boldsymbol{\Sigma}^{-1}(\boldsymbol{x}-\boldsymbol{\mu}) 
(분산이 커지면, 거리는 작게 해석)

(PRML 2.3) The Gaussian Distribution

빨간색 선은 이차원 공간 $x=\left(x_1, x_2\right)$ 상에서의 상수 가우시안 확률 분포의 타원형 표면을 나타낸다. 여기서 말도는 $x=\mu$ 일 경우의 값의 $\exp (-1 / 2)$ 에 해당한다. 타원의 축들은 공분산 행렬의 고유 벡터들 $u_i$ 에 의해 정의 되 며, 각각의 축은 각각의 고윳값 $\lambda_i$ 에 대응된다.

 

이차원 가우시안 분포에서의 상수확률 밀도의 경로.
(a)
는 공분산 행렬의 형태가 일반적일 경우
(b)
는 공분산 행렬이 대각 행렬인 형태
(c)
는 공분산행렬이 항등행렬의 상수배일 경우이며 이 경우 경로가 동심원의 형태를 띈다.

정규분포에 대한 판별 함수

- 최소 에러율 분류는 아래의 판별 함수로 달성될 수 있다.
$\begin{aligned} & p(\boldsymbol{x})= \frac{1}{(2 \pi)^{d / 2}|\boldsymbol{\Sigma}|^{1 / 2}} \exp \left[-\frac{1}{2}(\boldsymbol{x}-\boldsymbol{\mu})^t \boldsymbol{\Sigma}^{-1}(\boldsymbol{x}-\boldsymbol{\mu})\right] \text { 에 의해 } \\ & g_i(\boldsymbol{x})=-\frac{1}{2}\left(\boldsymbol{x}-\boldsymbol{\mu}_i\right)^t \boldsymbol{\Sigma}_i^{-1}\left(\boldsymbol{x}-\boldsymbol{\mu}_i\right)-\frac{d}{2} \ln 2 \pi-\frac{1}{2} \ln \left|\boldsymbol{\Sigma}_i\right|+\ln P\left(\omega_i\right)\end{aligned}$

 

- 판별함수의 3가지 경우

1. $\boldsymbol{\Sigma}_i=\sigma^2 \mathbf{I}$
2. $\boldsymbol{\Sigma}_i=\boldsymbol{\Sigma}$
3. $\mathbf{\Sigma}_i=$ arbitrary

 

Case 1: $\Sigma_i=\sigma^2 I$

- 가장 간단한 경우
-
특징들이 통계적으로 독집적이고 각 특징이 같은 분산 $\sigma^2$
를 가짐.
-
기하학적으로 샘플들이 같은 크기의 초구 클러스터에 놓이는 상황
-  $i$ 번째 클래스에 대한 클러스터는 평균 벡터 $\mu_i$ 가 중심으로 함.
- $\Sigma_{\mathrm{i}}$ 의 행렬식과 역의 계산이 쉬움 

$$
\left|\boldsymbol{\Sigma}_i\right|=\sigma^{2 d} \quad \boldsymbol{\Sigma}_i^{-1}=\frac{1}{\sigma^2} \mathbf{I}
$$

 

- $g_i(x)=-\frac{1}{2}\left(\boldsymbol{x}-\boldsymbol{\mu}_i\right)^t \boldsymbol{\Sigma}_i^{-1}\left(\boldsymbol{x}-\boldsymbol{\mu}_i\right)-\frac{d}{2} \ln 2 \pi-\frac{1}{2} \ln \left|\boldsymbol{\Sigma}_i\right|+\ln P\left(\omega_i\right)$

$\boldsymbol{\Sigma}_i^{-1},\left|\boldsymbol{\Sigma}_i\right|, \ln 2 \pi$ 가 $i$ 에 대해 독립 
$$
\rightarrow g_i(x)=-\frac{\left\|x-\mu_i\right\|^2}{2 \sigma^2}+\ln P\left(\omega_i\right) 
$$
여기서 $\left\|x-\mu_i\right\|^2=\left(x-\mu_i\right)^t\left(x-\mu_i\right)$ 이며, 유클리드 놈을 나타냄.

 

- $g_i(\boldsymbol{x})=-\frac{1}{2 \sigma^2}\left[\boldsymbol{x}^t \boldsymbol{x}-2 \boldsymbol{\mu}_i^\tau \boldsymbol{x}+\boldsymbol{\mu}_i^\tau, \boldsymbol{\mu}_i\right]+\ln P\left(\omega_i\right)$
$g_i(x)=\boldsymbol{w}_{\boldsymbol{i}}^t \boldsymbol{x}+\omega_{i 0}$ 여기서 $w_i=\frac{1}{\sigma^2} \boldsymbol{\mu}_i$ and $\omega_{i 0}=\frac{-1}{2 \sigma^2} \boldsymbol{\mu}_i^t \boldsymbol{\mu}_i+\ln P\left(\omega_i\right)$

 

 

- 선형 식 $g_i(x)=g_j(x)$ 에 의해 정의되는 초평면들

$\mathbf{w}^t\left(\mathbf{x}-\mathbf{x}_0\right)$ 여기서 $\mathbf{w}=\boldsymbol{\mu}_i-\boldsymbol{\mu}_j$ and $\mathbf{x}_0=\frac{1}{2}\left(\boldsymbol{\mu}_i+\boldsymbol{\mu}_j\right)-\frac{\sigma^2}{\left\|\boldsymbol{\mu}_i-\boldsymbol{\mu}_j\right\|^2} \ln \frac{P\left(\omega_i\right)}{P\left(\omega_j\right)}\left(\boldsymbol{\mu}_i-\boldsymbol{\mu}_j\right)$

Case2: $\boldsymbol{\Sigma}_{\boldsymbol{i}}=\boldsymbol{\Sigma}$


- 모든 클래스의 공분산 행렬이 동일하다.
- 샘플들이 같은 크기와 모양의 초타원체 클러스터에 놓이는 상황에 해당
- $i$ 번째 클래스의 클러스터는 평균 벡터 $\boldsymbol{\mu}_i$ 를 중심으로 한다.

$g_i(x)=-\frac{1}{2}\left(\boldsymbol{x}-\boldsymbol{\mu}_{\boldsymbol{i}}\right)^t \boldsymbol{\Sigma}_i^{-1}\left(\boldsymbol{x}-\boldsymbol{\mu}_i\right)-\frac{d}{2} \ln 2 \pi-\frac{1}{2} \ln \left|\boldsymbol{\Sigma}_i\right|+\ln P\left(\omega_i\right)$
$\frac{d}{2} \ln 2 \pi,\left|\boldsymbol{\Sigma}_i\right|$ 가 $i$ 에 대해 독립
$$
\rightarrow g_i(\boldsymbol{x})=-\frac{1}{2}\left(\boldsymbol{x}-\boldsymbol{\mu}_i\right)^t \boldsymbol{\Sigma}_i^{-1}\left(\boldsymbol{x}-\boldsymbol{\mu}_i\right)-\ln P\left(\omega_i\right)
$$
- $i$ 에 독립적인 2차 항 $\boldsymbol{x}^t \boldsymbol{\Sigma}_i^{-1} \boldsymbol{x}$ 를 빼면,
$g_i(\boldsymbol{x})=\boldsymbol{w}_i \boldsymbol{x}+\omega_{i 0}$ 여기서 $\boldsymbol{w}_i=\boldsymbol{\Sigma}^{-1} \boldsymbol{\mu}_i$ and $\omega_{i 0}=-\frac{1}{2} \boldsymbol{\mu}_i^t \boldsymbol{\Sigma}^{-1} \boldsymbol{\mu}_i+\ln P\left(\omega_i\right) $
이 판별식들은 선형적이므로 그로 인한 경계는 초평면이다. 이 초평면의 경계 식은.
$\boldsymbol{w}^t\left(\boldsymbol{x}-\boldsymbol{x}_0\right)=1$ where $\boldsymbol{w}_i=\boldsymbol{\Sigma}^{-1} \boldsymbol{\mu}_i$ and $\boldsymbol{x}_0=\frac{1}{2}\left(\boldsymbol{\mu}_i+\boldsymbol{\mu}_j\right)-\frac{\ln \left[P\left(\omega_i\right) / P\left(\omega_j\right)\right]}{\left(\boldsymbol{\mu}_i-\boldsymbol{\mu}_j\right)^t \boldsymbol{\Sigma}^{-1}\left(\boldsymbol{\mu}_i-\boldsymbol{\mu}_j\right)}\left(\boldsymbol{\mu}_i-\boldsymbol{\mu}_j\right)$
- $\boldsymbol{w}=\boldsymbol{\Sigma}^{-1}\left(\boldsymbol{\mu}_i-\boldsymbol{\mu}_j\right)$ 는 일반적으로 $\boldsymbol{\mu}_i-\boldsymbol{\mu}_j$ 방향이 아니기 때문에 영역을 분리하는 초평면은 일반적으로 이 평균들을 잇는 선에 직교하지 않는다.

 

Case3: $\Sigma_i=\operatorname{arbitrary}($ (임의적)


- 일반적인 정규분포의 경우 공분산 행렬은 각 부류마다 다르다.
$$
\begin{aligned}
& g_i(x)=-\frac{1}{2}\left(\boldsymbol{x}-\boldsymbol{\mu}_i\right)^t \boldsymbol{\Sigma}_i^{-1}\left(\boldsymbol{x}-\boldsymbol{\mu}_i\right)-\frac{d}{2} \ln 2 \pi-\frac{1}{2} \ln \left|\boldsymbol{\Sigma}_i\right|+\ln P\left(\omega_i\right) \\
& \frac{1}{2} \ln 2 \pi \text { 만이 i에 대해 독립 } \\
& \rightarrow g_i(\boldsymbol{x})=\boldsymbol{x}^t \boldsymbol{W}_i \boldsymbol{x}+\boldsymbol{w}_i^t \boldsymbol{x}+\omega_{i 0^2}  \\
& \text { where, } \boldsymbol{W}_i=-\frac{1}{2} \boldsymbol{\Sigma}_i^{-1}, \boldsymbol{w}_i=\boldsymbol{\Sigma}_i^{-1} \boldsymbol{\mu}_i \text { and }  \\
& \omega_{i 0}=-\frac{1}{2} \boldsymbol{\mu}_i^t \boldsymbol{\Sigma}_i^{-1} \boldsymbol{\mu}_i-\frac{1}{2} \ln \left|\boldsymbol{\Sigma}_i\right|+\ln P\left(\omega_i\right) 
\end{aligned}
$$

 

 

4개의 정규분포에 대한 판정 영역. 경계영역의 모양은 꽤 복잡해질 수 있다.

EXAMPLE1: 2차원 가우스 데이터에 대한 판정 영역

(에러확률과 적분)

정규 분포의 오차 범위

 

 

ROC(receiver operation characteristic)(수신기 동작 특성)

 

횡좌표는 허위 경고 확률이고, 세로 좌표는 히트 확률이다. 여기서는 히트 및 허위 경고율로부터 d&rsquo;=3을 추론가능

Bayes decision theory-이산적 특징

- 많은 실제 응용에서 구성요소 (특징벡터) x 2, 3, 또는 더 높은 진수의 정수값을 가지고, $x$ 는 $m$ 개의 이산 값 $v_1, \ldots, v_m$ 중 하나만 취할 수 있다.
$$
\int p\left(x \mid \omega_j\right) d x \rightarrow \sum_x P\left(x \mid \omega_j\right)
$$

- bayes 공식은 확률 밀도가 아닌 확률들을 포함

$$
P\left(\omega_j \mid x\right)=\frac{P\left(x \mid \omega_j\right) P\left(\omega_j\right)}{P(x)} \quad P(x)=\sum_{j=1}^c P\left(x \mid \omega_j\right) P\left(\omega_j\right)
$$

- 조건부 리스크 $R(\alpha \mid x)$ 의 정의는 변하지 않으며, bayes판정 룰도 동일하다.
- 사후 확률을 최대화하여 오류율을 최소화하는 기본 룰도 바뀌지 않는다.

 

독립적 2진 특징

- 특징 벡터의 요소들이 2진 값이고, 조건부 독립인 2부류 문제를 고려
$$
\begin{aligned}
& \text { Let } x=\left(x_1, \ldots, x_d\right)^t \text { 여기서 요소 } x_i \text { 는 } 0 \text { 또는 } 1 \text { 로 놓고 확률들은 다음과 같다. } \\
& p_i=\operatorname{Pr}\left[x_i=1 \mid \omega_1\right] \text { and } q_i=\operatorname{Pr}\left[x_i=1 \mid \omega_2\right] \\
& \rightarrow P\left(x \mid \omega_1\right)=\prod_{i=1}^d p_i^{x_i}\left(1-p_i\right)^{1-x_i} \text { and } P\left(x \mid \omega_2\right)=\prod_{i=1}^d q_i^{x_i}\left(1-q_i\right)^{1-x_i} \\
&
\end{aligned}
$$
그럼 우도비는
$$
\frac{P\left(x \mid \omega_1\right)}{P\left(x \mid \omega_2\right)}=\prod_{i=1}^d\left(\frac{p_i}{q_i}\right)^{x_i}\left(\frac{1-p_i}{1-q_i}\right)^{1-x_i}
$$

$$
\begin{aligned}
& \text { - 판별함수 }\left(g(x)=P\left(\omega_1 \mid x\right)-P\left(\omega_2 \mid x\right)-(30), g(x)=\ln \frac{p\left(x \mid \omega_1\right)}{p\left(x \mid \omega_2\right)}+\ln \frac{P\left(\omega_1\right)}{P\left(\omega_2\right)}-(31)\right. \text { 로 부터) } \\
& g(x)=\sum_{i=1}^d\left[x_i \ln \frac{p_i}{q_i}+\left(1-x_i\right) \ln \frac{1-p_i}{1-q_i}\right]+\ln \frac{P\left(\omega_1\right)}{P\left(\omega_2\right)}
\end{aligned}
$$
- 이 판별 함수는 $x_i$ 에서 선형적이다. 따라서...
$$
\begin{aligned}
g(\boldsymbol{x}) & =\sum_{i=1}^d \omega_i x_i+\omega_0  \\
\text { 여기서 } \omega_i=\ln \frac{p_i\left(1-q_i\right)}{q_i\left(1-p_i\right)} \quad i=1, \ldots, d \quad & \omega_0=\sum_{i=1}^d \ln \frac{1-p_i}{1-q_i}+\ln \left(\frac{P\left(\omega_1\right)}{P\left(\omega_2\right)}\right)
\end{aligned}
$$

 

반응형
Posted by creatoryoon
,
반응형

CH01 intro

1.1 기계인지

패턴을 인식은 음성인식, 지문식별, OCR, DNA시퀀스 식별 등 많은 곳에서 유용하다.

패턴인식의 방법론은 자연계, 특히 인간의 패턴 인식 체계에 대해 실제로 자연계가 이 문제를 어떻게 푸는가에 대한 지식에 영향을 받아 이뤄진다.

1.2 보기

예를 들어 생선 통조림 공장에서 생선을 분류하는 과정을 자동화하기를 원한다고 하자.
생선은 컨베이어 벨트에 실려 지나가며, 광학 센서를 이용해서 Sea bass salmon을 분류할 것이며, 카메라를 설치하여 sample 영상을 얻어 두 어종간의 외형적 차이(길이, 밝기, , 지느러미 수와 모양, 입의 위치 등)을 적다보면, 이중 분류기에 사용할 특징이 나올 것이다.
이때, 영상의 노이즈, 또는 변화(밝기, 컨베이어위의 위치,. 잡영(static))도 주목해야 할 것이다.

 

그림&nbsp;1.1분류될 생선은 먼저1.&nbsp;카메라에 의해 감지되고,2.&nbsp;전처리를 거친 뒤, Feature를 추출,3. Classification을 거쳐 최종적으로4. Sea Bass, Salmon인지를 추출할 것이다.보통 이 정보의 흐름은 소스로부터 분류기의 방향으로 흐르나,&nbsp;처리 레벨이 변경될 수도 있고,&nbsp;단계를 통합하는 경우도 있을 수 있다.

Sea bassSalmon의 모집단 간에 차이가 있다는 사실이 주어지면, 이들을 (보통 수학적 형태인 다른 묘사들)모델로 본다.

Pattern classification에서 전체를 지배하는 목표와 방식은 이 모델들의 class를 가정하고, 감지된 data를 처리해서 노이즈를 제거한 뒤, 감지된 모든 pattern에 대해 가장 잘 일치하는 모델을 선택하는 것이다.
전처리 과정에서는 segmentation 연산이 가능한데, 이때, 배경분리, 서로간에 분리가 이뤄진다.

이후 그 정보들이 Feature 추출기로 가면, 어떤 특징, 특성을 측정해서 데이터를 축소한다.

이 값들이 분류기도 가면, 이것들을 평가하고, 최종적으로 어종에 대한 판정을 내린다.

그렇다면 이 특징 추출기와 분류기가 어떻게 설계될 것인가를 고려하자면, 누군가가 Sea bassSalmon보다 더 길다고 정보를 알려줬다면, 이 사실은 이 어류에 대한 모델을 제공한 것이다.

모델: Seabass는 어떤 전형적 길이를 가지고, 그 길이는 Salmon보다 크다. 따라서 길이는 분명한 특징이 되며, 생선의 길이 $ l $을 통해 분류를 시도할 수 있을 것이다.

이를 위해 sample을 얻고 측정하고 결과들을 살펴보면 그림 1.2와 같다.

 

그림 1.2 두 부류의 길이 특징에 대한 히스토그램. * 로 표시된 값이 가장 작은 수의 err을 일으킬 것이다.

 

 

그림 1.3 두 부류의 밝기 특징에 대한 히스토그램. x* 로 표시된 값 하나만으로는 명확한 구분이 어렵다.

그림 1.2에서는 SeabassSalmon보다 다소 긴 것을 알려주지만, 사실 신뢰성은 떨어진다.

이번엔, 그림 1.3과 같이 밝기로 시험을 해보니, 상당히 만족스럽게 나옴을 알 수 있다.

 

다만, 이제까지 분류의 결과들은 비용이 같다고 가정되었다. 예를들어 SalmonSeabass로 판정하는 것과 그 역이 동일하다는 것.
하지만, Seamon을 샀는데 Seabass가 들었다면 더 강력한 항의가 있을 것이기에 이 비용은 현실적에서는 맞지 않다.

따라서 decision boundary를 왼쪽으로 더 이동시켜 Salmon으로 분류되는 Seabass의 수를 줄여야 한다. 이는 비용에 따라 더 왼쪽으로 이동할 수도, 오른쪽으로 이동할 수도 있다.
본질적으로 이러한 비용을 최소화하도록 판정경계를 나누는 것이 decision theory의 핵심이다.

판정과 관련된 비용을 알고 있어 최적의 기준 값 x*을 선택했다 해도, 결과가 만족스럽지 않다면, 더 나은 성능을 제공할 방법을 찾아야 한다. , 여러 특징에 의존하게 된다.

이번엔, Seabass가 일반적으로 Salmon보다 폭이 더 넓다는 사실을 이용해보자.
밝기 $x_1$ 과 폭 $x_2$ 2차원 특징 공간의 점 또는 vector x로 축소시킨다. $x=\left(\begin{array}{l}x_1 \\ x_2\end{array}\right)$

이제 문제는 이 특징공간을 두 영역으로 나누는 것이고, 한 영역의 모든 점은 Seabass, 다른 영역은 Salmon이 될 것이다.
sample
에서 그림 1.4의 분포를 얻었다고 하자.

그림 1.4 Seabass와 Salmon의 밝기 및 폭 특징. 직선을 판정경계로 사용할 수 있을 것이다. 분류 에러는 그림1.3보다 낮으나 아직 어느정도 존재한다.

이 결과를 보면, 더 많은 특징을 사용하는 것이 꽤나 바람직하다. 이외에도 등지느러미의 꼭대기 각도, 눈의 배치(-꼬리까지의 거리에 대한 비)같은 특징을 포함시킬 수 있을 것이다.
다만, 이 특징들은 중복될 수도 있고, 어떤 특징은 성능향상이 없을수도 있으며, 차원의 저주를 야기할 수 있다. 또한 측정 비용도 무시못할 요소이다.

그래서 두 특징들에 판정을 내리도록 강요된다 가정해보자. 만약 모델이 너무 복잡하다면, 그림1.5와 같은 복잡한 판정경계를 갖는다.

그림 1.5 지나치게 복잡한 모델은 복잡한 판정경계를 만들고, sample을 완벽하게 분류하겠지만, 실제로는 과적합으로 낮은 성능을 보일 것이다.

이 경우 sample은 완벽히 분류하겠지만 새로운 패턴에는 정확도가 떨어질 것이다. 이것이 일반화(General-Ization)에 대한 이슈이다.
사실상 그림 1.5는 특정 패턴에 과적합 되어 있다. 해결방법은 더 많은 학습 샘플을 구하는 것이지만, 이게 어려울 때는 다른 방법을 사용해야 한다.
그렇기에 실제 자연은 복잡한 판정경계가 아닐 것이라는 확신을 가지고 단순화한 것이 그림 1.6이다.

그림 1.6 훈련 집합에 대한 성능과 분류기의 단순성 간의 최적 절충(tradeoff)를 나타내고 있다. 이는 새 패턴들에 대해서도 최고의 정확도를 제공할 것이다.

 이 경우 새로운 데이터에 더 좋은 결과를 보인다면, sample에 대해 조금 저조해도 만족할 수 있을 것이다. 하지만, 더 간단한 분류기가 좋다는 것을 어떻게 뒷받침할까?
 
그림 1.41.5보다 좋다고 말할 수 있는가? 이 절충관계를 어떻게든 최적화할 수 있다고 가정하면, 시스템이 모델을 얼마나 잘 일반화할 수 있을것인지를 예측할 수 있는가? 이것이 바로 통계적 패턴 인식의 핵심 문제에 속한다.

우리의 판정은 특정 작업, 비용을 위한 것이며, 범용성을 가지에 만든다는 것은 어려운 도전이다.
기본적으로 분류는 그 패턴들을 만든 모델을 복구하는 작업이기에, 모델의 유형에 따라 분류 기법은 달라질 수 있다.
이러한 기법은 NeuralNet, 통계적 인식, 구문론적 패턴 인식 등 다양한 방법이 있다.
다만 거이 모든 문제에서의 중점은 구성성분간 구조적 관계가 간단하고 자연스레 드러나며, 패턴의 전형적 모델이 나타내질 수 있는 좋은 표현을 달성하는 것이다.
경우에 따라 패턴들은 실수값의 vector, 정렬된 속성, 관계에 대한 표사로 표현될 것인데, 어쨌든 비슷한 애들끼리는 서로 가까이 있고, 다른 애들끼리는 멀리 있게 되는 표현을 찾는 문제라 할 수 있다.


 

reference: 김경환 교수님 패턴인식 강의, duda pattern recognition

반응형
Posted by creatoryoon
,