요약


마르코브 랜덤필드(Markov random field, MRF)은 이산확률변수사이에 상호의존성을 표현하는 그래프모델입니다. 주요한 특징은 무방향성(Undirected)이며 인접한 경우에만 상호작용하는(pairwise interaction)하는 그래프의 성질을 지니고 있습니다. 관찰된 데이터로부터 알려지지 않은 변수를 추론하기위해서 주로 사용되며, 특히 이미지에서는 이미지 복원, 이미지 세그멘테이션, 객체인식 등에 주로 쓰입니다.

 

그림1. 마르코프 랜덤 필드의 예시(Pairwise random feild)

 

 

 

마르코브 랜덤 필드 정의(MRF, Markov random fields)


마르코브 랜덤필드는 마르코브 속성을 가진 그래프를 의미합니다. 이산확률변수(노드)사이의 상호의존성(edge)을 표현하는 그래프 모댈입니다. 이 노드 사이에서는 무방향성(undirect, 무향)입니다.

  • 일반적인 Markov network (=마르코브 속성(Markov property)가 있어야함)
  • 그래프모형이기에 노드(node)와 엣지(edge)가 존재하며, node은 확률변수, edge은 확률변수사이에 종속성을 의미함.

 

그림 표현

  • 검정색원(filled circle)은 관찰된 상태의 노드를 의미
  • 흰 원(empty circle)은 관찰되지 않은 상태의 노드를 의미

위의 그림1은 마르코브 랜덤필드의 흔한 예시입니다. 격자형으로 마르코브 속성을 가집니다. 쉽게 말해서 각 노드들이 확률변수 일때, 변수들 변수들간의 상호 의존성이 존재한다는 것입니다. 마르코브 속성은 현재상태가 과거상태에만 의존한다는 것인데, 이전 과거의 T상태전까지(T memory markcov라고도 함)영향을 받을 수 있음을 의미합니다. 위의 격자모형의 그래프에서는 상호의존성을 인접한 노드에서 받을 수 있습니다. 좀 더 나아가서 인접한 노드라고하지만, 이 인접한 노드들은 주변의 인접한 노드가 또 있기에(2칸 떨어진) 이전 T상태전까지 영향을 받는것처럼 생각할 수도 있습니다(하지만 2칸떨어진 노드가 직접영향을 주는 것은 절대아닙니다. 그럴거면 2칸 떨어진 노드에 직접 연결되어있어야합니다.).

 

만일 노드 3개(A, B, C)가 주어져있고, 노드A가 C을 통하지 않으면 B로갈 경로가 없다고하면 다음과 같이 표기합니다. 만일 이러한 경로가 있으면 이 노드들의 구성은 dependent한것입니다.

$X_A \perp X_B \,|\, X_C$

 

 

수식표현

  • $x_{i}$: 확률변수. 노드를 의미. 변수라고도하고 node라도 합니
  • $\psi(x_{i}, x_{j})$: 관찰되지않은 노드($x_{i}$)와 관찰되지 않은노드($x_{j}$)의 종속성을 의미. 발음은 프사이라고 합니다. 이 종속성은 메트릭스로도 표현할 수 있습니다. 통상 대칭인 정방행렬(symmetric, square matrix)여서 노드 i에서 j로 가거나 노드 j에서 노드i로 가는 것이 동일한 종속성입니다. 하지만, 이 두 노드 사이가 비대칭이어도 상관없습니다. 영문표기로는 compatible function라고합니다.
  • $\phi(x_{i}, y_{j})$: 관찰되지않은 노드($x_{i}$)와 관찰된 노드($y_{j}$)의 종속성을 의미합니다. 위의 표기와 다르게 한쪽이 관찰되지 않을 때 쓰는 notation입니다. 영문표기로는 evidence for x라고 합니다. 이미 y은 결정되어있으니 x을 구하기위한 근거라는 것이죠.
  • $N_{n}=\{m \in \mathbb{N} \,|\, (n,m) \in \varepsilon\}$: 노드 n의 엣지로 연결된 노드의 집합 (=노드N의 이웃집합)
  • $P(X_{n} \| X_{N_{n}})$: 노드n의 인접한노드(=로컬 조건부확률)로만 알고있으면$ X_{n}$의 확률을 알 수 있음을 의미합니다. 마르코브 속성을 의미합니다. [1]

 

 

마르코브 랜덤 필드의 예시


아래의 그림은 MRF의 예시입니다. 4개의 노드가 존재합니다. 3개의 노드($x_{1}, x_{2}, x_{3}$)은 관찰되지 않은 노드이고, 유일하게 node 4($y_{2}$)만 관찰된 노드입니다.

 

위 MRF을 수식으로 표현해보겠습니다.

  • 노드: 관찰되지 않은 노드1은 $x_{1}$, ..., 노드3은 $x_{3}$입니다.  반면 관찰된 노드4은 $y_{4}$입니다.
  • MRF은 각 노드사이의 종속성을 표현한다고 했습니다. 각 노드의 종속성을 아래와 같이  표현할 수 있습니다.

 

에너지함수(energy function)와 포탠셜(Potential)


마르코브 랜덤필드의 확률분포는 에너지함수를 사용하여 계산할 수 있습니다. 즉, 이런 마르코브랜덤 필드가 나오려면 어떤 확률분포였겠드냐를 의미합니다. 여기서 에너지함수라는 것을사용하는데,에너지함수는 다음과 같은 아이디에 기반합니다.

  • 높은 에너지를 가지고 있는 상태는 불안정하니 낮은 확률을 갖는다는 것
  • 에너지함수는 확률변수의 집합의 각 상태에 대한 에너지값을 부여하며, 에너지가 낮을수록 높은확률

 

깁스 분포(Gibbs distribution)을 이용해서 마르코브 랜덤필드를 정의해보겠습니다. (깁스 분포는 결합확률분포가 각 확률변수의 조건부 독립적이어서 곱한것을 의미)

 

$P(X)= \frac {1}{Z} \prod _{c\in C} \phi_{c} (x_{c})$

여기서 C은 maximal clique을 의미합니다. 즉 이 그래프에서 클리크에 속하는 모든 노드들의 확률을 다 하나하나 곱하면 전체 그래프가 될 수 있다는 것을 의미합니다. 그리고 여기서 Z은 보통 정규화 상수인데, 이 그래프에서는 다음과 같이 표현합니다.

$Z=\sum_{x}\prod _{c \in C}\phi_{c}(x_{c})$

 

이 그래프에서 $phi_{c}(x_{c})$을 다음과 같이 쓸 수 있습니다. 보통 여기서 T은 온도(temperature)라고해서 1을 쓰씁니다. 

$\phi_{c}(x_{c})=e^{-1 \frac{1}{T}V_{c}(x_{c})}$

 

각각 Z랑 $phi_{c}(x_{c})$을 알고있으니, 깁스분포에 대입해보면, 아래와 같고, 지수의 승을 덧셈으로 표현할 수 있으니까 결국 두번째식까지 얻어낼 수 있습니다. 

$P(x)=\frac{1}{Z}\prod _{c \in C} e^{-\frac{1}{T}V_{c}(x_{c})}$

$P(X)=\frac{1}{Z}e^{-\frac{1}{T}\sum_{c \in C}V_{c}(x_{c})}$

이때, $\sum_{c \in C}V_{c}(x_{c})$을 보통  $U(x)$라고하고, 에너지라고합니다. 그리고 V은 클리크 포탠셜(clique potential)이라고합니다.

 

 

마르코브 랜덤 필드와 베이지안 네트워크의 차이


마르코브랜덤필드는 베이지안 네트워크와 유사합니다. 표로 정리하면 아래와 같은 차이점이 있습니다.

  마르코브 랜덤 필드 베이지안 네트워크
정의 확률 변수 간의 조건부 독립 관계를 나타내는 무방향 그래프 확률 변수의 조건부 독립 관계를 나타내는 방향성 있는 그래프(Acyclic)
방향 Undirected Directed
두 노드의 확률 compatible function ψ_ij (x_i,x_j) Conditional probability p(x_i |x_j)
변수타입 이산 이산, 연속
순환가능 가능 불가능

 

 

 

 

Python example


MRF의 예시

import networkx as nx
import matplotlib.pyplot as plt

# 그래프 생성
G = nx.Graph()

# 노드 추가
nodes = ["x1", "x2", "x3", "y2"]
G.add_nodes_from(nodes)

# 간선 추가 및 가중치 설정
edges = [("x1", "x2", 0.9), ("x2", "x3", 0.1), ("y2", "x2", 0.1)]
G.add_weighted_edges_from(edges)

# 각 노드에 확률 변수 값을 할당 (예: 이진 랜덤 변수)
for node in G.nodes:
    G.nodes[node]['value'] = 0  # 초기값을 0 또는 1로 설정

# 노드 위치 지정
pos = {
    "x1": (-1, 0),  # 왼쪽에 위치
    "x2": (0, 0),  # 6시 방향에 위치
    "x3": (1, 0),   # 오른쪽에 위치
    "y2": (0, 1)    # 12시 방향에 위치
}

# 그래프 시각화
nx.draw(G, pos, with_labels=True, font_weight='bold', node_size=800, node_color='lightblue', font_color='black')

plt.show()

 

MRF을 이용한 iterated conditional modes(ICM)을 적용

import cv2
import numpy as np
from PIL import Image

# 이미지 불러오기
image = cv2.imread('/home/heon/repositories/detection_models/sample.png', cv2.IMREAD_GRAYSCALE)
image = cv2.resize(image, (128, 128))


noisy_image = image + np.random.normal(0, 15, image.shape).astype(np.uint8)
binary_image = cv2.threshold(noisy_image, 100, 255, cv2.THRESH_BINARY)[1]

Image.fromarray(image)
Image.fromarray(noisy_image)
Image.fromarray(binary_image)

def calculate_cost(y_p, x_p, alpha, beta, neighbors, y_k):
    cost = alpha * (1 - (y_p == x_p))
    for q in neighbors:
        cost += beta * (1 - (y_p == y_k[q]))
    return cost

def icm_reconstruction(observed_image, alpha, beta, max_iterations):
    height, width = observed_image.shape
    y_k = np.copy(observed_image)  # Initialize the restored image as the observed image.
    
    neighbors = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
    
    for _ in range(max_iterations):
        updated_y = np.copy(y_k)
        
        for i in range(1, height - 1):
            for j in range(1, width - 1):
                current_pixel = observed_image[i, j]
                best_cost = float('inf')
                best_pixel_value = None
                
                for y_p in [0, 1]:
                    cost = calculate_cost(y_p, current_pixel, alpha, beta, neighbors, y_k)
                    # print(i,j,cost, best_cost)
                    if cost < best_cost:
                        best_cost = cost
                        best_pixel_value = y_p
                updated_y[i, j] = best_pixel_value
        
        if np.array_equal(updated_y, y_k):
            break
        
        y_k = updated_y
    
    return y_k

 

Reference


[1]https://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/AV0809/ORCHARD/#:~:text=A%20Markov%20Random%20Field%20(MRF,the%20set%20of%20nodes%20S.

반응형

요약


FastAPI middleware은 어플리케이션의 요청 및 응답처리과정중에 중간에 위치하여, 여러 작업을 수행할 수 있는 요청->동작 사이, 응답->동작사이에 여러 기능을 수행
주로 요청 및 응답을 변형하거나(예, 암호화), 인증, 로깅및 모니터링, 예외처리등을 수행.
ASGI (Asynchronous Server Gateway Interface) 프로토콜을 따르는 웹 애플리케이션에서 비동기 요청을 처리하기 위해, request._receive속성에 설정하도록함


fastAPI의 미들웨어는 @app.middleware("http")와같이 함수위의 데코레이터로 사용가능합니다.
async def set_body(request: Request, body: bytes) 함수는 원래 요청 객체에 바이트로 된 본문을 설정하기 위해 사용됩니다. 
아래의 함수 내에서 async def receive() -> Message 함수를 정의하고, 이 함수는 request 객체의 _receive 속성으로 설정됩니다. 
따라서 ASGI 프레임워크는 요청을 수신할 때 이 _receive 함수를 호출하여 요청 객체의 본문을 설정하여 로깅할 수 있습니다.

async def set_body(request: Request, body: bytes):
    async def receive() -> Message:
        return {"type": "http.request", "body": body}

    request._receive = receive


@app.middleware("http")
async def log_request_payload(request: Request, call_next):
    payload = await request.body()
    await set_body(request, payload)
    request.app.state.logger.debug(f"Request payload: {payload.decode()}")
    response = await call_next(request)
    return response

 



아래의 call_next의 인자가 callable로 (reqeust)을다시 인자로 받아야하는데, FastAPI.reqeust은 아래와 같이 구성되어있습니다.
request._receive 속성이 property로 설정되어있고 타입이 empty_receive라 Courutine의 타입을 받게되어있습니다.
그래서 async def로 정의한 함수를 넣어주도록하고, 그 메시지의 형태를 stream내애 얻어지는 형태랑 유사하게 받도록합니다.

class Request(HTTPConnection):
    _form: typing.Optional[FormData]

    def __init__(
        self, scope: Scope, receive: Receive = empty_receive, send: Send = empty_send
    ):
        super().__init__(scope)
        assert scope["type"] == "http"
        self._receive = receive
        self._send = send
        self._stream_consumed = False
        self._is_disconnected = False
        self._form = None

    @property
    def method(self) -> str:
        return self.scope["method"]

    @property
    def receive(self) -> Receive:
        return self._receive

    async def stream(self) -> typing.AsyncGenerator[bytes, None]:
        if hasattr(self, "_body"):
            yield self._body
            yield b""
            return
        if self._stream_consumed:
            raise RuntimeError("Stream consumed")
        self._stream_consumed = True
        while True:
            message = await self._receive()
            if message["type"] == "http.request":
                body = message.get("body", b"")
                if body:
                    yield body
                if not message.get("more_body", False):
                    break
            elif message["type"] == "http.disconnect":
                self._is_disconnected = True
                raise ClientDisconnect()
        yield b""
반응형

conda 설치후 아래와 같은 에러가 발생


$ conda activate myenv
usage: conda [-h] [--no-plugins] [-V] COMMAND ...
conda: error: argument COMMAND: invalid choice: 'activate' (choose from 'clean', 'compare', 'config', 'create', 'info', 'init', 'install', 'list', 'notices', 'package', 'remove', 'uninstall', 'rename', 'run', 'search', 'update', 'upgrade', 'build', 'content-trust', 'convert', 'debug', 'develop', 'doctor', 'index', 'inspect', 'metapackage', 'render', 'skeleton', 'verify', 'pack', 'token', 'server', 'env', 'repo')

 

 

해결방법: conda 초기화가 안되어 발생하는 문제


1. Conda 업데이트: 먼저 Conda를 최신 버전으로 업데이트하십시오. 아래 명령어를 실행하여 Conda를 업데이트합니다.

$ conda update -n base -c defaults conda

 

2. 시스템 환경 변수 확인: Conda가 설치된 경로를 시스템 환경 변수에 제대로 추가했는지 확인하십시오. 다음 명령어를 사용하여 Conda의 경로를 확인하고 확인하세요. "/home/사용자/anaconda3/bin" 이러한 경로가 존재해야합니다.

$ echo $PATH
/home/<사용자>/anaconda3/bin:/home/heon/anaconda3/condabin:/home/heon/.vscode-server/bin/e7e037083ff4455cf320e344325dacb480062c3c/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/lib/wsl/lib:/mnt/c/Windows/system32:/mnt/c/Windows:/mnt/c/Windows/System32/Wbem:/mnt/c/Windows/System32/WindowsPowerShell/v1.0:/mnt/c/Windows/System32/OpenSSH:/mnt/c/Users/4pygm/AppData/Local/Microsoft/WindowsApps:/mnt/c/Users/4pygm/AppData/Local/Programs/Microsoft VS Code/bin:/snap/bin

 

3. shell 재시작

$ source ~/.bashrc  # 또는 ~/.zshrc

 

4. Conda 초기화

$ conda init

 

반응형
import mlflow
from setting import parent_run_id

mlflow.set_tracking_uri(TRACKING_URI)

child_run_ids = list()
for run in client.search_runs(experiment_ids=[6]):

    if "mlflow.parentRunId" not in run.data.tags:
        continue

    if run.data.tags["mlflow.parentRunId"] != parent_run_id:
        continue

    child_run_ids.append(run.info.run_id)
반응형
%load_ext autoreload
%autoreload 2
반응형

 

 

에지(edge)


에지(엣지, edge)은 서로 다른 물체의 경계면에 나타내는 경계선을 의미합니다. 이 경계 주변이 픽셀들의 분포를 생각해보면, 명암의 급격한 변화가 있게 됩니다. 아래의 강아지 사진을 보면, 강아지의 눈을 주변으로 경계는 명암차이가 극명합니다(Figure 1). 이러한 명암차이로, 흰색강아지털과 검은색 눈동자의 경계면을 사람도 인식할 수 있습니다. 에지 검출알고리즘은 이렇듯 명암차이가 극명하게 나는 지점을 추출하기 위한, 여러 연산자(+알고리즘)을 사용하는 것이 공통적입니다.

Figure 1. 포메라니안 강아지

 

 

미분을 이용한 에지의 검출


에지의 검출은 명암변화가 급격히 일어나는 지점을 찾는 것이라고 했습니다. 수학적으로는 변화가 급격하게 일어나는 지점을 찾는 것은 미분을 이용합니다. 하지만, 디지털영상에서는 변화가 급격하게 일어나는 지점을 찾으려면 어떻게 해야할까요? 미분을 이용하고 싶지만, 아래와 같이 미분연산에서 극한을 취급할 수 없습니다. 왜냐하면, 디지털영상은 이산형이이기 때문에(=연속이 아님), 미분을 취급하기 어렵습니다. 따라서, 아래와 같이 옆 픽셀과의 차이로 계산합니다.

$f'(x)=\frac{df}{dx}=\frac{f(x+1)-f(x)}{\Delta x}=f(x+1)-f(x)$ (픽셀위치차이를 1로 고려하여)

함수 식은 위와 같고, 이를 영상에 적용려면, 필터(연산자, 커널)를 하나 정할 수 있습니다. 바로 [-1, 1]커널 입니다. 아래와 같이 f(x+1), f(x)인 박스(픽셀)에 [-1, 1] 어레이를 곱하면, 미분값을 구할 수 있습니다. 

하지만, 현실세계에서는 이와 같이 경계면이 확확 바뀌지않습니다. 위의 식으로 아래의 그림(figure 3)의 좌측을 계산한다면, 엣지가 f(x+1), f(x)의 차이가 커서 구분이 잘되겠지만, 우측의 램프엣지(ramp edge)의 형태인 경우는 램프엣지 구간의 명암차이가 일정하게 나타날 것입니다. 그래서, 이 방법론으로는 한계가 있습니다.

Figure 3. 이상적인 엣지(계단형 엣지)와 램프엣지의 형태 (10.14445/22312803/IJCTT-V23P110)

 

하지만, 1차미분이아닌 2차미분을 이용하면 램프엣지(ramp edge)에서도 경계면을 찾을 수 있습니다. 아래의 그림의 좌측(Figure 4), 램프엣지가 있다고할 때, 이 명암의 크기는 우측상단의 그림처럼 보여질 것 입니다. 그리고 이를 1차미분하면 위의 블록이 하나 있는 것처럼 보이는 봉우리가 나타날 것입니다. 그리고, 여기서 한번 더 미분한 (2차미분) 값을 구하면, 1차미분에서 봉우리의 시작점, 출발점 사이의 가상의 선이 무조건 0을 지나게됩니다. 즉, 2차 도함수를 이용하여, 영교차(zero-crossing)하는 지점을 찾으면, 램프엣지에서도 경계선을 찾을 수 있습니다.

Figure 4. 램프엣지에서의 경계구하기 https://www.52coding.com.cn/2018/12/04/RS%20-%20Recognizing%20Image%20Features%20and%20Patterns/

위의 영상은 1차원의 공간에서 명암을 기준으로 했기에, 이제 2차원으로 확장해봅니다. 단순히 1차원을 2차원으로 확장하면, x, y축에서의 미분값을 구하면됩니다. 그 식은 아래와 같습니다. 1차도함수 먼저 정의하고, 1차도함수를 2번사용하면 2차도함수를 구할 수 있습니다.

$\nabla f(y,x)=f'(y, x)=(f(y+1, x)-f(y,x), f(y, x+1)-f(y,x))= (d_{y},d_{x})$

하지만, 실제로는 잡음이 있어서 위와 같이 픽셀 하나차이의로만은 노이즈가 있어, 두 픽셀 사이로 영교차 이론을 적용합니다. 그렇기에 실무적으로 쓰이는 공식은 아래와 같습니다.  이렇게 되면 실제 마스크(kernel)은 [-1, 0, 1]와 같은 커널을 적용할 수 있습니다.

$\nabla f(y,x)=f'(y, x)=(f(y+1, x)-f(y-1,x), f(y, x+1)-f(y,x+1))= (d_{y},d_{x})$

위와 같이 엣지를 구할 수 있으면, 추가적으로 다음의 두 개념(엣지강도, 그레디언트 방향)을 구할 수 있습니다.

  • manitude($\nabla f(y,x)$) = $\sqrt{d_{y}^{2}+d_{x}^{2}}$
  • Gradient orientation: $ \theta = tan^{-1}(\frac{d_{y}}{d_{x}}) $

 

Sobel filter: 1차원 가우시안 필터(Smoothing)을 이용한 필터  + 미분


현실세계에서는 필터를 적용할 때, 이미지의 픽셀이 노이즈가 많기 때문에 이를 리덕션해줄 필요가 있습니다. 가우시안 필터는 이러한 경우 흐릿하게하는 블러(blurring)에 사용되는데요. 소벨필터는 이 가우시안 필터와 x,y 축별로의 그레디언트를 혼합하여 사용하는 필터입니다. 그렇기에 상대적으로 노이즈에 덜 민감한 편입니다.

Sobel filter 및 분해

 

 

Prewitt operation: 평균필터 + x,y 미분


프레윗 연산은 소벨과 다르게, 평균으로만 스무딩필터를 적용합니다. 그 결과 아래와 같은 커널을 얻을 수 있습니다.

Prewitt filter 및 분해

[1] https://theailearner.com/tag/cv2-sobel/

반응형

+ Recent posts