본 글은 하기 블로그의 리뷰 글임.

https://deepbaksuvision.github.io/Modu_ObjectDetection/

 

모두를 위한 Object Detection(Object Detection for All) · GitBook

No results matching ""

deepbaksuvision.github.io

 

 

Object Detection이란?

이미지로부터 객체를 판단하는 기술. 

 

Computer Vision에는 주요 3가지 Task가 있는데

  • Classification
  • Single Classfication & Localization & Detection
  • Multi Object Detection & Localization & Classification

이 있다고 한다.

 

이는 목적에 따라 다른듯. 주어진 이미지로부터 어떤 category의 이미지인지 반드시 찾아야하는 문제가 있을 것이고, 주어진 이미지에서 배경과 객체의 구분 / 그리고 그 객체가 '어떤' 이미지인지를 판단하는 문제가 있을 것이고 / 만일 객체라 한 개가 아닌 여러 개라면 여러개에 대한 구분과 각각에 대한 Classification이 필요할 것이다.

 

-> Recognition과 Object Detection이 있는데

  • Object가 어떤 것인지 구분
  • Recognition보다 더 작은 범위로써 Object의 존재 유무만 판단

의 차이라고 한다.

 

-> Recognition 전에 Detection이 선행되어야 함.

 

전통적으로 사용했던 Object Detection 알고리즘은 Feature Engineering 기법을 통해 특징을 추출하여 특징들의 분포에서 경계를 결정하여 찾는 방법을 주로 사용.

 

전통적인 Feature Extraction 방법 예시 : Haar-like feature, HOG(Histogram of Oriented Gradient), SIFT(Scale Invariant Feature Transform), LBP(Local Binary Pattern), MCT(Modified Census Transform) 등 )

전통적인 Boundary Decision 알고리즘 예시 : SVM(Support Vector Machine), Adaboost와 같은 검출 알고리즘(Classifier)

 

결론적으로 전처리 -> 특징 추출 -> 분류 의 파이프라인을 따름.

 

Datasets for Object Detection

사용할 데이터 셋들

 

PASCAL VOC Dataset

위 페이지에서 어찌저찌 다운을 받았다.

근데 다양한 연도의 데이터가 있었지만 여러 사이트들을 보니 2007이 국룰(?)인 것 같아 07년도의 데이터로 받았다.

tar 파일로 되어있는 것을 압축을 풀면 다음과 같다.

 

각 폴더별 내용물은 다음과 같다고 한다.

  • Annotations : JPEGImages 폴더 속 원본 이미지와 같은 이름들의 xml파일들이 존재합니다. Object Detection을 위한 정답 데이터이 됩니다.
  • ImageSets : 어떤 이미지 그룹을 test, train, trainval, val로 사용할 것인지, 특정 클래스가 어떤 이미지에 있는지 등에 대한 정보들을 포함하고 있는 폴더입니다.
  • JPEGImages : *.jpg확장자를 가진 이미지 파일들이 모여있는 폴더입니다. Object Detection에서 입력 데이터가 됩니다.
  • SegmentationClass : Semantic segmentation을 학습하기 위한 label 이미지
  • SegmentationObject : Instance segmentation을 학습하기 위한 label 이미지

 

해당 코드로 이미지를 불러오라고 했지만 나는 ipynb를 사용할 거기에 image_path에다가 이미지 경로를 넣어준다.

그리고 아마 데이터가 조금씩 다른 듯 하다. 저기선 68번 이미지가 들어와졌지만 나는 68번은 없고 숫자가 조금 띄엄띄엄 있었다.

image_path = sys.argv[1]

image = Image.open(image_path).convert("RGB")

plt.figure(figsize=(25,20))
plt.imshow(image)
plt.show()
plt.close()

 

XML 파일 구조

<annotation>
	<folder>VOC2007</folder>
	<filename>000005.jpg</filename>
	<source>
		<database>The VOC2007 Database</database>
		<annotation>PASCAL VOC2007</annotation>
		<image>flickr</image>
		<flickrid>325991873</flickrid>
	</source>
	<owner>
		<flickrid>archintent louisville</flickrid>
		<name>?</name>
	</owner>
	<size>
		<width>500</width>
		<height>375</height>
		<depth>3</depth>
	</size>
	<segmented>0</segmented>
	<object>
		<name>chair</name>
		<pose>Rear</pose>
		<truncated>0</truncated>
		<difficult>0</difficult>
		<bndbox>
			<xmin>263</xmin>
			<ymin>211</ymin>
			<xmax>324</xmax>
			<ymax>339</ymax>
		</bndbox>
	</object>
	<object>
		<name>chair</name>
		<pose>Unspecified</pose>
		<truncated>0</truncated>
		<difficult>0</difficult>
		<bndbox>
			<xmin>165</xmin>
			<ymin>264</ymin>
			<xmax>253</xmax>
			<ymax>372</ymax>
		</bndbox>
	</object>
	<object>
		<name>chair</name>
		<pose>Unspecified</pose>
		<truncated>1</truncated>
		<difficult>1</difficult>
		<bndbox>
			<xmin>5</xmin>
			<ymin>244</ymin>
			<xmax>67</xmax>
			<ymax>374</ymax>
		</bndbox>
	</object>
	<object>
		<name>chair</name>
		<pose>Unspecified</pose>
		<truncated>0</truncated>
		<difficult>0</difficult>
		<bndbox>
			<xmin>241</xmin>
			<ymin>194</ymin>
			<xmax>295</xmax>
			<ymax>299</ymax>
		</bndbox>
	</object>
	<object>
		<name>chair</name>
		<pose>Unspecified</pose>
		<truncated>1</truncated>
		<difficult>1</difficult>
		<bndbox>
			<xmin>277</xmin>
			<ymin>186</ymin>
			<xmax>312</xmax>
			<ymax>220</ymax>
		</bndbox>
	</object>
</annotation>

Annotation에 있는 파일들은 xml 형식의 파일들인데 이는 JPEG이미지들과 1대1 mapping되는 듯 하다.

여기서 각 이미지들이 어떤 정보를 가지고 있는 지 전달해주는 것 같다.

다음은 xml 파일들이 가지고 있는 정보이다.

 

  • <size> : xml파일과 대응되는 이미지의 width, height, channels 정보에 대한 tag입니다.
    • <width> : xml파일에 대응되는 이미지의 width값
    • <height> : xml파일에 대응되는 이미지의 height값
    • <depth> : xml파일에 대응되는 이미지의 channels값
  • <object> : xml파일과 대응되는 이미지속에 object의 정보에 대한 tag입니다.
    • <name> : 클래스 이름을 의미합니다.
    • <bndbox> : 해당 object의 바운딩상자의 정보에 대한 tag입니다.
      • xmin : object 바운딩상자의 왼쪽상단의 x축 좌표값
      • ymin : object 바운딩상자의 왼쪽상단의 y축 좌표값
      • xmax : object 바운딩상자의 우측하단의 x축 좌표값
      • ymax : object 바운딩상자의 우측하단의 y축 좌표값

바운딩 박스는 다음의 박스를 의미함.

 

xml파일을 load하고 xml package를 이용하여 label을 파싱해보자.

 

xml_path = '~~'
print("XML parsing Start\n")

xml = open(xml_path, "r")
tree = Et.parse(xml)
root = tree.getroot()

size = root.find("size")

width = size.find("width").text
height = size.find("height").text
channels = size.find("depth").text

print("Image properties\nwidth : {}\nheight : {}\nchannels : {}\n".format(width, height, channels))

objects = root.findall("object")
print("Objects Description")
for _object in objects:
    name = _object.find("name").text
    bndbox = _object.find("bndbox")
    xmin = bndbox.find("xmin").text
    ymin = bndbox.find("ymin").text
    xmax = bndbox.find("xmax").text
    ymax = bndbox.find("ymax").text

    print("class : {}\nxmin : {}\nymin : {}\nxmax : {}\nymax : {}\n".format(name, xmin, ymin, xmax, ymax))

print("XML parsing END")

다음과 같이 불러지게 된다.

 

#필요한 거 import
import os
import sys
import matplotlib.pyplot as plt
import xml.etree.ElementTree as Et
from xml.etree.ElementTree import Element, ElementTree

from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw

#Annotations와 JPEGImages의 상위 폴더에 위치를 잡아준다.
dataset_path = '/Users/kbj110/Desktop/workspace/ml_dl/VOCdevkit/VOC2007'

#각각의 폴더 이름을 잡아주
IMAGE_FOLDER = "JPEGImages"
ANNOTATIONS_FOLDER = "Annotations"

#각 폴더에 대한 정보를 받아온다. (하위 파일들 리스트 또한 불러옴)
ann_root, ann_dir, ann_files = next(os.walk(os.path.join(dataset_path, ANNOTATIONS_FOLDER)))
img_root, amg_dir, img_files = next(os.walk(os.path.join(dataset_path, IMAGE_FOLDER)))

#그래서 해당 폴더서 각 파일별로 loop를 돌리며
for xml_file in ann_files:

    # XML파일와 이미지파일은 이름이 같으므로, 확장자만 맞춰서 찾습니다.
    img_name = img_files[img_files.index(".".join([xml_file.split(".")[0], "jpg"]))]
    img_file = os.path.join(img_root, img_name)
    image = Image.open(img_file).convert("RGB")
    draw = ImageDraw.Draw(image)

    #xml을 파싱하고 사이즈를 찾아서 각 width, height, channels 값을 받아온다.
    xml = open(os.path.join(ann_root, xml_file), "r")
    tree = Et.parse(xml)
    root = tree.getroot()

    size = root.find("size")

    width = size.find("width").text
    height = size.find("height").text
    channels = size.find("depth").text

    objects = root.findall("object")

    #그러고 objects들의 정보를 받아 loop를 돌리는데 이는 하나의 객체만을 담고 있지 않을 수 있기 때문인듯
    for _object in objects:
        name = _object.find("name").text
        bndbox = _object.find("bndbox")
        xmin = int(bndbox.find("xmin").text)
        ymin = int(bndbox.find("ymin").text)
        xmax = int(bndbox.find("xmax").text)
        ymax = int(bndbox.find("ymax").text)

        # Box를 그릴 때, 왼쪽 상단 점과, 오른쪽 하단 점의 좌표를 입력으로 주면 됩니다.
        draw.rectangle(((xmin, ymin), (xmax, ymax)), outline="red")
        draw.text((xmin, ymin), name)
    
    #그리고 그린다
    plt.figure(figsize=(25,20))
    plt.imshow(image)
    plt.show()
    plt.close()

주어진 코드를 잘 실행하면 다음과 같은 귀여운 고양이 사진도 찾을 수 있다.

 

convert2Yolo

https://github.com/ssaru/convert2Yolo

 

GitHub - ssaru/convert2Yolo: This project purpose is convert voc annotation xml file to yolo-darknet training file format

This project purpose is convert voc annotation xml file to yolo-darknet training file format - ssaru/convert2Yolo

github.com

 

 

convert2Yolo란? :  각종 datasets들을 YOLO저자가 만든 darknet 프레임워크가 사용하는 label format으로 변경해주는 프로젝트

뒤에 나올 내용에서 모든 Object Detection의 구현체의 Dataloader는 이를 사용하므로 해당 프로젝트가 어떻게 구성되어있고 사용하는지를 숙지해아하 Dataloader 이해 가능.

 

---작성 및 공부중---

이론만 공부하면 지치니 실습 코드 리뷰를 하나 진행하자.

손글씨 Dataset으로 유명한 mnist 데이터 셋이다.

 

1. Module Import

import numpy as np
import matplotlib.pyplot as plt

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import transforms, datasets

 

필요한 모듈들 import한다.

- numpy, plt 는 수치 계산과 시각화를 위하여.

- nn은 신경망 모델을 위해, F는 함수들 따오기 위해 등

 

2. 딥러닝 모델을 설계할 때 활용되는 장비 확인

if torch.cuda.is_available():
    DEVICE = torch.device('cuda')
else:
    DEVICE = torch.device('cpu')

print('Using PyTorch version:', torch.__version__, ' Device:', DEVICE)

 

- cuda를 지원하는 gpu가 사용가능한지 확인, 만일 가능하면 gpu를, 그렇지 않다면 cpu를 사용하도록 설정.

 

BATCH_SIZE = 32
EPOCHS = 10

- hyperparameter 설정. 이전에 확인했듯 batch size는 2의 거듭승이 좋음.

 

3. MNIST 데이터 다운로드

train_dataset = datasets.MNIST(root = "../data/MNIST",
                               train = True,
                               download = True,
                               transform = transforms.ToTensor())

test_dataset = datasets.MNIST(root = "../data/MNIST",
                              train = False,
                              transform = transforms.ToTensor())

train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                           batch_size = BATCH_SIZE,
                                           shuffle = True)

test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                          batch_size = BATCH_SIZE,
                                          shuffle = False)

 

- train에 대한 param 설정을 통해 train set / test set 구분하여 다운

- transforms.ToTensor(): PIL 이미지나 넘파이 배열을 PyTorch의 FloatTensor로 변환하고, 픽셀 값을 [0, 1] 범위로 스케일링합니다. 

 

- 그 후 받아진 dataset을 dataloader로 변환(변환이 맞는 표현인진 모르겠다).

- 이전에 설정한 BATCH_SIZE (하이퍼파라미터)를 받아오며, train data에 대해선 shuffle을 사용하여 bias를 방지.

 

4.  데이터 확인하기 (1)

for (X_train, y_train) in train_loader:
    print('X_train:', X_train.size(), 'type:', X_train.type())
    print('y_train:', y_train.size(), 'type:', y_train.type())
    break

결과는 다음과 같다.

 

for-loop를 바로 break 때리는 것을 보아 하나의 배치만 확인하고자 하는 용도로 사용한듯.

-> 데이터 형태와 타입이 제대로 들어오는 지 단순 확인 용도.

 

X train set에 대해서는

 

  • 배치 크기(batch size): 32
  • 채널 수(channels): 1 (MNIST는 흑백 이미지이므로 채널이 1개)
  • 이미지 높이(height): 28 픽셀
  • 이미지 너비(width): 28 픽셀

 

와 같이 이루어지며, float tensor로 지정되게 됨.

반면 y train 데이터는 1차원 텐서로 label 값인 '정수'를 표현하게 됨.

 

5.  데이터 확인하기 (2)

pltsize = 1
plt.figure(figsize=(10 * pltsize, pltsize))
for i in range(10):
    plt.subplot(1, 10, i + 1)
    plt.axis('off')
    plt.imshow(X_train[i, :, :, :].numpy().reshape(28, 28), cmap = "gray_r")
    plt.title('Class: ' + str(y_train[i].item()))

- figsize (10,1)로 설정.

- 위에서 for loop을 돌려서 X_train과 y_train에 첫번째 batch에 대한 데이터가 할당됨.

- 그 중 1~10번째 데이터들만 가져오고 y_train[i]를 통해 같이 표기하여 붙여줌.

 

--------------------- 여기까진 기본적인 data load 및 세팅 ---------------------

6. Multi Layer Perceptron (MLP) 모델 설계하기

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(28 * 28, 512)
        self.fc2 = nn.Linear(512, 256)
        self.fc3 = nn.Linear(256, 10)

    def forward(self, x):
        x = x.view(-1, 28 * 28)
        x = self.fc1(x)
        x = F.sigmoid(x)
        x = self.fc2(x)
        x = F.sigmoid(x)
        x = self.fc3(x)
        x = F.log_softmax(x, dim = 1)
        return x

- 여기서 다층 퍼셉트론에 대한 모델 정의

- MLP는 입력층, 하나 이상의 은닉층, 출력층으로 구성된 feed-forward 신경망

- MNIST 데이터셋의 이미지(28x28 픽셀)를 입력으로 받아서, 10개의 클래스(숫자 0~9)에 대한 예측을 출력

 

여기서 PyTorch의 장점 -> feed-forward function만 정의를 하여도 backward function에 대해서는 자동으로 정의가 됨.

(물론 function들은 nn.functional에서 제공해주는 함수들을 사용하여하여함.)

 

Net 클래스는 nn.Module을 상속하여 모델 정의. 부모 클래스인 nn.Module의 생성자를 호출하여 초기화.

 

fc1, fc2, fc3은 레이어 정의.

fc1은 28*28 픽셀 데이터를 1차원 벡터로 펼침. (MNIST 이미지 사이즈가 28*28 임)

fc3의 출력 노드수는 당연히 10으로 맞춰준다. (0~9까지의 10가지 숫자)

 

forward 메서드를 정의하며

입력 이미지를 flatten한 후 first fully-connected layer를 통과 시킨 후 sigmoid적용. 이하 반복.

그리고 마지막에 fc3까지 적용된 출력 값에 log softmax 함수를 적용하여 클래스별 로그 확률 계산

 

 

 

7. Optimizer, Objective Function 설정하기

model = Net().to(DEVICE)
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01, momentum = 0.5)
criterion = nn.CrossEntropyLoss()

print(model)

- 위에서 생성한 Net class의 객체 생성.

- 옵티마이저 설정. SGD 사용하며 learning rate은 0.01.

- momentum은 처음 봤는데 '이전 업데이트의 방향을 일정 부분 유지하여 학습 속도를 향상시키는 역할'이라고 한다.

- loss function (criterion) 설정. 크로스 엔트로피 손실 함수 사용.

 

print한 결과값은 다음과 같다.

 

8. MLP 모델 학습을 진행하며 학습 데이터에 대한 모델 성능을 확인하는 함수 정의

def train(model, train_loader, optimizer, log_interval):
    model.train()
    for batch_idx, (image, label) in enumerate(train_loader):
        image = image.to(DEVICE)
        label = label.to(DEVICE)
        optimizer.zero_grad()
        output = model(image)
        loss = criterion(output, label)
        loss.backward()
        optimizer.step()

        if batch_idx % log_interval == 0:
            print("Train Epoch: {} [{}/{} ({:.0f}%)]\tTrain Loss: {:.6f}".format(
                epoch, batch_idx * len(image), 
                len(train_loader.dataset), 100. * batch_idx / len(train_loader), 
                loss.item()))

 

- train이라는 function 정의. 뒤에 10에서 보면 알겠지만 한 epoch에 대해 학습 / 학습에 대한 성능 확인하는 함수.

- 큰 틀에서 함수는 parameter로 모델과 훈련 데이터(엄밀하게 말하면 배치 단위로 제공되는), 옵티마이저, 학습 상태를 출력할 간격 총 4개를 받는다.

 

- model.train() : 모델의 학습 모드를 설정하는 메소드.

- for loop에서 enumerate을 통해 batch index를 출력하며 돌림.

- image와 label로 가져오며 이는 X(우리가 학습시킬 이미지), y(label) 정답을 의미한다.

- pytorch의 옵티마이저는 gradient가 누적되므로 계속 초기화 해준다.

- 모델에 image를 집어넣어 output을 계산해주고 이를 loss function에 넣어 오차를 구한다.

- 역전파가 계산되며 각 파라미터에 gradient가 저장된다.

- 이를 optimizer.step()을 통해 모델의 파라미터들을 업데이트 시킨다.

 

그 아래는 batch를 돌리며 모델이 어느 정도 성능을 내는지, 배치가 얼마나 돌아갔는지 등을 표기한다.

 

9. 학습되는 과정 속에서 검증 데이터에 대한 모델 성능을 확인하는 함수 정의

def evaluate(model, test_loader):
    model.eval()
    test_loss = 0
    correct = 0

    with torch.no_grad():
        for image, label in test_loader:
            image = image.to(DEVICE)
            label = label.to(DEVICE)
            output = model(image)
            test_loss += criterion(output, label).item()
            prediction = output.max(1, keepdim = True)[1]
            correct += prediction.eq(label.view_as(prediction)).sum().item()
    
    test_loss /= (len(test_loader.dataset) / BATCH_SIZE)
    test_accuracy = 100. * correct / len(test_loader.dataset)
    return test_loss, test_accuracy

모델의 성능을 평가할 evaluate이라는 function을 정의하자.

model.eval()을 호출하면 모델이 평가 모드로 전환됨.

- 평가 모드로 전환되면 드롭아웃 비활성화, 배치 정규화의 이동 평균과 이동 분산이 업데이트 되지 않음.

- test_loss와 correct을 0으로 초기화.

- test_loss : 평가할 데이터의 총 loss 수치. correct : 모델이 정확하게 맞춘 숫자.

- with torch.no_grad(): 이 블럭 내에서는 자동 미분 비활성화. 불필요한 계산 안하니 메모리 할당(사용량?) 이점.

- 이전 train function과 대략적 비슷한 흐름. test_loader를 인자로 받아 for문을 돌리며, loss값을 계속 test_loss에 누적시켜 더함.

 

- output의 가장 큰 값을의 Index를 출력하여 prediction과 label의 동일한지 여부 확인. 맞으면 더하고 아님말고.

 

10. MLP 학습 실행하며 Train, Test set의 Loss 및 Test set Accuracy 확인하기

for epoch in range(1, EPOCHS + 1):
    train(model, train_loader, optimizer, log_interval = 200)
    test_loss, test_accuracy = evaluate(model, test_loader)
    print("\n[EPOCH: {}], \tTest Loss: {:.4f}, \tTest Accuracy: {:.2f} % \n".format(
        epoch, test_loss, test_accuracy))

맨 마지막 부분이다.

위에서 정의한 함수들을 사용하며 맨 앞에서 정의한 epoch만큼 for문을 돌리며 train, evaluate을 실행한다.

 

Notice

모든 코드는 하기 링크에서 가져왔으며 내 이해를 위한 글임.

https://github.com/Justin-A/DeepLearning101/blob/master/2-1_MNIST_MLP.ipynb

 

DeepLearning101/2-1_MNIST_MLP.ipynb at master · Justin-A/DeepLearning101

Code about DeepLearning101 Book. Contribute to Justin-A/DeepLearning101 development by creating an account on GitHub.

github.com

 

위 코드를 설명한 https://velog.io/@gr8alex/PyTorch%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-MNIST-%EB%B6%84%EB%A5%98-%EB%AA%A8%EB%8D%B8-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0 도 참고함.

 

 

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

이제 모델을 만들고 학습을 했으면 실제 상황에 써봐야하지 않겠는가.

 

1. 테스트 할 데이터 생성

파워포인트로 직접 그렸다. 물론 데이터는 하나하나 저장해주어야 한다.

 

2. Preprocessing

기존의 이미지

우리가 이전에 데이터셋으로 쓴 이미지는 다음과 같이

1. 검은색 배경에 흰색 글씨

2. 28*28 pixel의 데이터이다.

 

def preprocess_image(image_path):
    image = Image.open(image_path).convert('L')
    image = image.resize((28, 28), Image.LANCZOS)
    image = ImageOps.invert(image)
    transform = transforms.Compose([
        transforms.ToTensor()
    ])
    image = transform(image)
    image = image.unsqueeze(0)
    print(image)
    return image

따라서     image = ImageOps.invert(image) 를 통해 반전을 시켜주며 Resize로 사이즈를 맞춰준다.

그럼 다음과 같은 이미지가 나온다.

 

3. 실제 테스트

image_path = '/test_data_5.png'
input_image = preprocess_image(image_path).to(DEVICE)

with torch.no_grad():
    output = model(input_image)
    _, predicted = torch.max(output.data, 1)
    print('예측된 숫자:', predicted.item())

이를 예측한다.

(근데 생각보다 많이 부정확하다. 5개 중 3개만 맞췄다. 원인 파악을 해봐야겠다.)

 

논문 원문: https://www.ri.cmu.edu/pub_files/2014/7/Ji_LidarMapping_RSS2014_v8.pdf

 

추후 참고(할) 깃헙

1. https://github.com/laboshinl/loam_velodyne

2. https://github.com/stevenliu216/Lidar-Odometry-and-Mapping

 

읽으면서 참고한 블로그 : https://alida.tistory.com/44

 

[SLAM] Lidar Odometry And Mapping (LOAM) 논문 리뷰

본 포스트는 공부 목적으로 작성하였습니다.혹시 보시는 도중 잘못된 부분이나 개선할 부분이 있다면 댓글이나 메일주시면 확인 후 수정하도록 하겠습니다.해당 포스트는 앞서 pdf 파일로 정리

alida.tistory.com

 

 

Abstract

이 연구는 lidar(라이다) 센서를 이용하여 odometry와 mapping을 실시간으로 하는 방법에 대해 설명한다.

(라이다 센서 -> 사물까지의 거리를 타 센서들에 비해 정확하게 알 수 있음.)

우리의 연구는 'low-drift and low-computational complexity'를 모두 뛰어나다.

주요 알고리즘은 크게 두 가지로 나뉜다.

(1) 높은 빈도로 odometry를 계산하며

(2) 그보다는 낮은 빈도로 mapping한다.

 

1. Introduction

- 라이다는 high frequency range measurement가 가능하기에 mapping에 용이하며 오차에 대한 일관성이 있기에 유용함.

 

- 라이다의 the only motion은 rotating하는 레이저 빔이지만, 만일 라이다가 움직이면 어려워진다.

-> 이 문제를 해결하기 위해 GPS/INS를 이용하여 문제를 해결할 수 있음. 다른 방법으로는 odometry measurement를 이용하여 해결.

 

근데 odometry를 Integrates하면 조그마한 오차들이 많이 더해져 큰 오차가 발생할 수 있다. 이런 오차를 해소하기 위해 loop-closure(로봇이 이전에 방문했던 장소로 다시 돌아왔을 때 이를 인식하여, 현재의 위치와 맵 정보를 이전의 정보와 비교합니다. 이를 통해 누적된 오차를 조정하고 전체적인 위치 추정과 맵의 정확도를 향상시는 방법.)을 사용할 수 있음.

 

! 우리는 inpendent한 position estimation 없이 할거임. loop-closure도 안 쓸거임.

우리의 모델은 다른 기술 안 쓰고 low-drift 하며 low-computational complexity한 모델을 만듦.

(low-drift는 위치의 정확성, low-computational complexity는 real-time에서 빠른 연산을 위함인듯.)

 

두 알고리즘이 사용되는데

1. 높은 빈도로 performs odometry (but low fidelity)

2. 상대적으로 낮은 빈도, but fine matching and registration (of the point cloud).

 

두 알고리즘 모두 feature points를 추출하는데 sharp edges거나 planar surfaces이다.

odometry 알고리즘에선 추출된 feature points(특징점?)들을 corresponding (mapping이랑 같은 얘긴듯)을 빠른 속도로 한다.

 

이렇게 parallel한 알고리즘 구조가 real-time 속도로 위 내용들을 실행 가능하게 한다.

 

2. Related Works

Lidar는 매우 유용함.

lidar의 scan rate에 비해 외적 무빙이 작다면 스캔에 대한 왜곡은 무시해도 괜찮음.

-> 이 경우 two different scan에 대해 point끼리 mapping을 할 때 ICP method를 사용해도 됨.

그러나 scanning motion이 상대적으로 느리면 모션 왜곡이 무시 못 할 수 있음.

 

근데 우리가 할 것은 2-axis lidar로 다른 센서(IMU, GPS/INS 등) 도움 없이 하는 것임을 기억하자. (아닌가)

그러면, 모션 추정이나 왜곡 보정에이 또 하나의 문제가 된다.

우리의 방법은 edge/planar한 특징점을 뽑아 Cartesian space에 matching 시켜 높은 cloud density가 필요하지 않음.

 

우리 연구랑 가장 비슷한건 Bosse and Zlot의 것들(주석 3, 6, 22. 나중에 찾아보면 좋을 듯)

하지만 우리의 방법은 REAL-TIME에서 가능함 -> 이게 가장 큰 차별점인듯.

(결국 적은 연산량과 높은 정확성 사이에서 잘 타협을 본 method라는 듯.)

 

3. Notations And Task Description

(ego-motion : 환경 내에서 카메라의 3차원 이동)

 

아래는 이 paper에서 사용할 notation들.

좌표계 : 오른쪽 위 대문자 표기.

a sweep : 한 번의 scan coverage.

Pk : k번째 sweep 동안 받아진 point cloud.

 

좌표계는 크게 두 가지가 있는데 L와 W.

- L : Lidar 좌표계. Lidar 중심.

- W : L의 시작점 기준으로 우리의 세상(?) 기준 좌표계로 이해하면 될듯.

(좌표계를 잘 읽자. 뒤에서 읽다가 혼란 온다.)

 

 

결국 이 내용들을 하나의 Problem으로 정의한다면 다음과 같이 귀결된다.

 

4. System Overview

A. Lidar Hardware

Fig. 2.

우리가 사용할 모델. 하드웨어 스펙은 다음과 같다.

- The laser scanner has a field of view of 180도, 0.25 resolution, 40 lines/sec scan rate.

-The laser scanner is connected to a motor, 180도의 움직임 (-90도 ~ 90도).

 

A sweep : A rotation from -90 ~ 90도 (or in the inverse direction). 대략 1초

 

그럼 하나의 스윕동안 (모터가 1회 움직일 동안, laser scanner는 40줄을 훑는다. 두 개 구분 필요.)

 

B. Software System Overview

P hat : laser 스캔으로 부터 얻어진 점들.

각각의 스윕동안 P hat은 {L} 좌표계에 등록(registered)(?)됨.

k번째의 sweep에서 이렇게 registered된 point cloud를 Pk라 하며, 이런 Pk는 이제 두 개의 알고리즘을 타게 됨.

 

Lidar Odometry는 Point cloud를 받아서 두 연속적 sweep간의 motion을 계산한다.

이렇게 추정된 motion은 Pk에서의 왜곡을 수정하기 위해 사용되며, 이 알고리즘은 10Hz의 빈도로 발생.

추가로 이 결과는 lidar mapping에 의해서도 1Hz 주기로 처리됨.

 

이런 두 알고리즘에 의한 pose transforms가 10Hz 주기로 publish됨.

(어차피 5와 6에서 자세히 설명하니 대략적 흐름만 파악하고 넘어가자.)

 

5. Lidar Odometry

A. Feature Point Extraction

주요 내용 : Pk에서 feature points들을 뽑는 방법 설명.

 

(laser scanner의 구조가 이해가 가지 않아 확인해보니, 이런 원통형의 모양으로 돌아감. 이러면 상하 방향으로의 scan이 어렵기 때문에 이를 motor에 연결하여 움직이는 것인 아닐까? 라는 생각. 그리고 motor가 돌아가는 각도도 180도(-90도 ~ 90도)인 것도 사실 이런 모양이면 360도까지는 필요가 없기 때문 아닐까. 하나의 sweep으로 정의하는 것이 그럼 온전한 3D 이미지를 얻는 최소 단위기에 쪼개는 것이 아닐까.)

 

perpendicular한 방향의 resolution이 다른 것은 다 필요 없이 only laser scan의 rate이 40Hz이니 모터의 움직임(1초)동안 180도를 움직이는 동안, perpendicular하게는 180도 / 40 = 4.5도의 resolution. 

 

우리는 feature points를 sharp deges와 planar surface patches에서 뽑을 것.

 

S : set of consecutive points of i returned by the laser scanner in the same scan.

하나의 scan에서 point i를 기준으로 연속적인 점들 뽑은 것.

 

다음과 같이 smoothness of the local surface를 정의. (근데 엄밀하게 말하면 반대로 굽혀진 정도라 해야하지 않을까. 거기서 거긴가..)

하나의 스캔에서 c value로 sorting하면 큰 c를 갖는 점들이 edge points, 작은 값을 갖는 점들이 planar points라 명명.

 

우리는 하나의 scan을 4개의 identical한 subregion으로 나눔. 각 subregion에서 최대 2개의 edge points들과 4개의 planar한 point를 뽑음.

 

 

이러한 feature points를 뽑을 때 좀 이상한 case들을 제외해야함.

- 연속된 점들, 레이저 빔과 평행한 평면에 있는 점들. (a의 B)

- 어떤 물체에 가려진 점들. (b의 A)

 

정리하면

- c value의 sorting으로 골라진 edge points들과 planar points들의 수는 subregion의 최댓값을 넘지 못함.

- 이미 골라진 점들의 주위 점들을 뽑지 않음.

- 특이 점들(위의 예시)는 뽑지 않음.

 

Fig 5처럼 각각 노랑 : egde point, 빨강 : planar point로 시각화를 색깔로 표기.

 

B. Finding Feature Point Correspondence

odometry 알고리즘은 한 번의 스윕동안의 라이더의 motion을 추정.

 

tk : sweep k의 시작 '시각'.

각 스윕이 끝나면 sweep동안 얻어진 point cloud Pk또한 얻어짐. 이러한 Pk는 t(k+1)에 대해 reprojection 되는데 이렇게 얻어진 Pk_bar와 다음 스윕서 새롭게 얻어지는 Pk+1를 사용하여 lidar motion을 추정한다.

 

우리에게 Pk_bar와 Pk+1가 모두 주어졌을때 두 lidar clouds간 correspondences를 구하자.

 

 

 

 

 

 

 

 

 

C. Motion Estimation

 

D. Lidar odometry Algorithm

 

 

6. Lidar Mapping

dd

 

7. Experiments

 

 

 

8.Conclusion and Future work

What is Reinforcement Learning

강화학습이란 무엇인가 ?

강화 학습(Reinforcement learning)은 기계학습이 다루는 문제 중에서 다음과 같이 기술 되는 것을 다룬다. 어떤 환경을 탐색하는 에이전트가 현재의 상태를 인식하여 어떤 행동을 취한다. 그러면 그 에이전트는 환경으로부터 포상을 얻게 된다. 포상은 양수와 음수 둘 다 가능하다. 강화 학습의 알고리즘은 그 에이전트가 앞으로 누적될 포상을 최대화하는 일련의 행동으로 정의되는 정책을 찾는 방법이다.

위키피디아에서는 위와 같이 설명하고 있다.
 
강화학습은 Machine Learning의 범주 안에 있는 학습 방법 중의 하나.
아이가 환경과 상호 작용하며 걷는 방법을 알아가는 것처럼 배워가는 학습 방법.
(-> 강화학습 자체가 행동심리학에서 영감을 받았기에 실제 인간이 배우는 방법과 유사하여 아이디어 자체는 익숙한듯.)
 
강화 학습도 agent가 아무것도 모르는 상태에서 환경 속에서 경험을 통해 학습하는 것
(바둑으로 유명한 알파고 또한 강화학습 알고리즘에 기반)

Reinforcement learning is defined not by characterizing learningmethods, but by characterizing a learning problem.

Sutton 교수님의 책에서 강화학습은 학습하는 방식(method)로 정의 되는 것이 아닌, 문제로 정의 되어 짐.
 

출처 : https://dnddnjs.gitbook.io/rl/chapter-1-introduction-1/1.1-what-is-reinforcement-learning

Machine Learning은 크게 세 가지로 나뉜다.
1. Supervised Learning (지도학습) : 정답(label)을 알 수 있어 바로바로 피드백을 받을 수 있음.
2. Unsupervised Learning (비지도학습) : 정답이 없는 분류(classification)와 같은 문제를 푸는 것
3. Reinforcement Learning (강화학습) : 정답은 모르지만, 자신이 한 행동(action)에 대한 보상(reward)를 알 수 있어 그로부터 학습하는 것. 강화학습은 MDP(Markov Decision Process)로 표현되어지는 문제를 푸는 것.
 
강화학습의 가장 중요한 두가지 특징
1. Trial And Error : 해보지 않고 예측으로 움직이는 것이 아닌, 직접 해보며 조정해나가는 것.
2. Delayed Reward : 어떠한 행동에 대한 보상이 '즉각'적으로 이루어지는 것이 아닌 Delayed 될 수 있다. 환경이 반응할 때까지 여러 다른 행동들을 시간의 순서대로 했기에 어떤 행동이 좋은 행동이었는지 판단하기 어려운 점이 있음.
 

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

0. Rrologue  (0) 2024.10.06

Before to Start

해당 카테고리(RL)의 글들은 모두 Reinforcement Learning의 개인적 학습을 위한 자료이며 타 자료를 review하는 식으로 진행됩니다.

(최종 목적은 AD-tech에서 DSP의 입찰을 위해 공부를 진행하고자 한다.)

 

기본적인 자료는 이웅원님의 'Fundamental of Reinforcement Learning' (링크 : https://dnddnjs.gitbook.io/rl) 를 참고합니다.

 

Fundamental of Reinforcement Learning | Fundamental of Reinforcement Learning

2016년 초부터 모두의 연구소의 자율주행 드론 연구실 DCULab의 연구실장을 맡아서 드론을 연구을 해오고 있었습니다. 그러던 중에 "드론의 제어에 사용되는 PID 계수를 자동으로 맞춰주는 방법이

dnddnjs.gitbook.io

(또한 위 자료를 리뷰한 https://sumniya.tistory.com/1 또한 참고하였다.)

 

 

 

추후 공부를 위한 자료 

https://mclearninglab.tistory.com/49

 

강화학습 공부 자료 정리

강화학습 공부를 하기위해서 매번 찾아야하는 번거로움을 줄이기 위해 자료들을 모아놓기로 했다. Online Tutorial(Video) 기본적인 개념 뿐만 아니라 강화학습과 관련된 영상들을 정리하였다. 김성

mclearninglab.tistory.com

(https://arxiv.org/pdf/1811.12560 : 가볍게 읽어보기 좋을듯)

 

 

문의는 : dodongdog01@gmail.com

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

CHAPTER 1 : INTRODUCTION  (0) 2024.10.06

문제 출처

https://www.acmicpc.net/problem/1260

 

 

 

문제

그래프를 DFS로 탐색한 결과와 BFS로 탐색한 결과를 출력하는 프로그램을 작성하시오. 단, 방문할 수 있는 정점이 여러 개인 경우에는 정점 번호가 작은 것을 먼저 방문하고, 더 이상 방문할 수 있는 점이 없는 경우 종료한다. 정점 번호는 1번부터 N번까지이다.

 

입력

첫째 줄에 정점의 개수 N(1 ≤ N ≤ 1,000), 간선의 개수 M(1 ≤ M ≤ 10,000), 탐색을 시작할 정점의 번호 V가 주어진다. 다음 M개의 줄에는 간선이 연결하는 두 정점의 번호가 주어진다. 어떤 두 정점 사이에 여러 개의 간선이 있을 수 있다. 입력으로 주어지는 간선은 양방향이다.

 

출력

첫째 줄에 DFS를 수행한 결과를, 그 다음 줄에는 BFS를 수행한 결과를 출력한다. V부터 방문된 점을 순서대로 출력하면 된다.

 

풀이

import sys
input = sys.stdin.readline

N, M, V = map(int, input().split())

graph_matrix = [[0]*(N+1) for _ in range(N+1)]

for _ in range(M): # 간선의 개수만큼 반복하여 데이터를 입력받음
    a, b = map(int,input().split())
    graph_matrix[a][b] = 1
    graph_matrix[b][a] = 1 # 양방향으로 연결

is_visited_dfs = [0]*(N+1) # DFS 방문 체크
is_visited_bfs = [0]*(N+1) # BFS 방문 체크

def dfs(V):
    is_visited_dfs[V] = 1
    print(V, end=' ')
    for i in range(1, N+1):
        if graph_matrix[V][i] == 1 and is_visited_dfs[i] == 0: # 연결되어 있고 방문하지 않았다면
            dfs(i)

def bfs(V):
    queue = [V]
    is_visited_bfs[V] = 1
    while queue:
        V = queue.pop(0) # 큐에서 첫 번째 요소 제거
        print(V, end=' ')
        for i in range(1, N+1):
            if is_visited_bfs[i] == 0 and graph_matrix[V][i] == 1: # 연결되어 있고 방문하지 않았다면
                queue.append(i)
                is_visited_bfs[i] = 1

dfs(V)
print()  # DFS 결과 출력 후 줄바꿈
bfs(V)

 

해설

출처 : https://velog.io/@gusdh2/DFS-vs-BFS-%EA%B9%8A%EC%9D%B4%EC%9A%B0%EC%84%A0%ED%83%90%EC%83%89-vs-%EB%84%88%EB%B9%84%EC%9A%B0%EC%84%A0%ED%83%90%EC%83%89

DFS

- 이름부터 Depth-First Search로 깊이 우선 탐색 알고리즘이다.

한 노드에서 부터 시작하여 탐색을 진행할 때, 진입한 지점에서 더 깊이 먼저 들어가는 것이다.

 

주로 그래프 풀이서 visited에 대한 list를 만들어두고 이 지점을 방문했었는가 ? 여부를 판별한다.

 

BFS

- Breadth-First Search로 탐색을 할 때, 너비를 우선으로 탐색한다.

개인적으로 구현 난이도는 DFS보다는 조금 더 어렵지 않나 생각한다.

 

 

예시를 들면, 한 학생이 수학 / 물리 / 영어를 공부한다고 하자.

그 학생이 수학 기초부터 -> 수학 박사과정 공부, 물리 기초부터 -> 물리 박사과정 공부, 영어 기초부터 -> 영어 박사과정 공부 순으로 공부하면 DFS이고, 수학 물리 영어 기초 -> 수학 물리 영어 심화 와 같이 공부를 하면 BFS이다.

 

bfs에서는 queue를 만들어 나와 동급 라인에 있는 애들이 누구인지를 저장해둔다. 하나씩 탐색 후 queue가 empty가 되면 (동급 라인 애들을 다 서칭하면) 끝난다.

'알고리즘 & PS > 백준' 카테고리의 다른 글

[백준] 12865번 평범한 배낭 (DP)  (1) 2024.10.04
[백준] 2231번 분해합  (0) 2023.12.10
[백준] 5430번 AC  (0) 2023.12.09

문제 출처

https://www.acmicpc.net/problem/12865

문제

이 문제는 아주 평범한 배낭에 관한 문제이다.

한 달 후면 국가의 부름을 받게 되는 준서는 여행을 가려고 한다. 세상과의 단절을 슬퍼하며 최대한 즐기기 위한 여행이기 때문에, 가지고 다닐 배낭 또한 최대한 가치 있게 싸려고 한다.

준서가 여행에 필요하다고 생각하는 N개의 물건이 있다. 각 물건은 무게 W와 가치 V를 가지는데, 해당 물건을 배낭에 넣어서 가면 준서가 V만큼 즐길 수 있다. 아직 행군을 해본 적이 없는 준서는 최대 K만큼의 무게만을 넣을 수 있는 배낭만 들고 다닐 수 있다. 준서가 최대한 즐거운 여행을 하기 위해 배낭에 넣을 수 있는 물건들의 가치의 최댓값을 알려주자.

 

입력

첫 줄에 물품의 수 N(1 ≤ N ≤ 100)과 준서가 버틸 수 있는 무게 K(1 ≤ K ≤ 100,000)가 주어진다. 두 번째 줄부터 N개의 줄에 거쳐 각 물건의 무게 W(1 ≤ W ≤ 100,000)와 해당 물건의 가치 V(0 ≤ V ≤ 1,000)가 주어진다.

입력으로 주어지는 모든 수는 정수이다.

 

출력

한 줄에 배낭에 넣을 수 있는 물건들의 가치합의 최댓값을 출력한다.

 

 

풀이

유명한 냅색(KnapSack) 문제로 DP에 속한다.

DP는 Dynamic Programming으로 큰 문제를 작은 문제로 나눠푸는 알고리즘이다.

 

다음과 같은 input이 있을 때,

4 7
6 13
4 8
3 6
5 12

4개의 물품, 7의 무게를 의미하는 첫 행을 제외하면 다음과 같은 표로 변환할 수 있다.

출처 : https://velog.io/@keynene/Python%ED%8C%8C%EC%9D%B4%EC%8D%AC5-%EB%B0%B1%EC%A4%80-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-12865-%ED%8F%89%EB%B2%94%ED%95%9C%EB%B0%B0%EB%82%AD

다음 표가 의미하는 다음과 같다.

1. [6,13]행부터 K만큼 들었을 때 최대 가치를 의미한다. 첫 행에선 6 무게의 물품밖에 없기에 6과 7에 해당하는 최대 value는 13이다.

2. 그 아래 [4,8]의 물건이 추가가 되며 K=4~5일때는 4를 챙겼을 때의 무게인 8, K=6~7일때는 6의 물건을 챙겼을 때의 무게이다.

3. 그 아래 [3,6]의 물건까지 추가가 된다면 나머지는 이전과 동일하지만 K=7에서 13이 아닌 14가 된다. 이 부분이 가장 중요한데, 7(K)-3(W)에 해당하는 지점 + 6(New V)와 이전에 채워져있던 값 중 Maximum 값을 택한다.

 

(사실 맨 처음 생각했을때는 iteration을 돌며 확인하면 되는 것이 아닌가. 혹은 Brutal Force로 확인하면 되는 것이 아닌가 생각을 했었지만, 결국 어떤 한 물건을 기준으로 생각했을 때 최대 효용 case에 그 물건이 들어가거나 / 안 들어가거나 둘 중 하나다. 이 아이디어를 극대화 한 것 같다. 또한, 생각을 조금만 깊게 해보면 처음 주어진 Input의 순서는 상관 없다는 것을 알 수 있다.)

 

import sys
input = sys.stdin.readline

N, K = map(int, input().split())
# 여기까진 input 대신 sys.stdin.readline 사용 (시간 초과 나지 않기 위해서)

bag = [list(map(int, input().split())) for _ in range(N)]
#가방에 [W,V]의 형태로 물건들을 담아준다

dp = [[0]*(K+1) for _ in range(N+1)]
#빈 N+1 by K+1 Matrix를 만들어준다.


for i in range(N+1): #물건을 하나 하나 돌며 확인
    for j in range(K+1): #그럼 k value를 하나 하나 돌며 확인한다.
        if j >= bag[i-1][0]: #만약 i번째 물건의 무게가 j(현재 가방에 넣을 무게)보다 작다면 ? 들어갈 가능성이 생긴다.
            dp[i][j] = max(bag[i-1][1]+dp[i-1][j-bag[i-1][0]],dp[i-1][j]) #이때 이전의 최댓값과 현재 만들 수 있는 최대를 비교하여 더 큰 값으로 변경한다.
        else: #나머지는 물건의 무게가 더 무거워 집어 넣지 못하는 경우이다.
             dp[i][j] = dp[i-1][j] #이는 이전에 받은 최댓값을 그대로 받는다.
        
print(dp[N][K]) #그러면 이게 최대다.

 

 

한줄평 : dp는 참 끝이 없는 것 같다.

 

'알고리즘 & PS > 백준' 카테고리의 다른 글

[백준] 1260번 DFS와 BFS  (0) 2024.10.04
[백준] 2231번 분해합  (0) 2023.12.10
[백준] 5430번 AC  (0) 2023.12.09

원문 자료 : 딥러닝을 이용한 자연어 처리 입문 (링크: https://wikidocs.net/22886)

 

서문

앞서 배운 피드 포워드 신경망은 입력의 길이가 고정되어 있어 자연어 처리를 위한 신경망으로는 한계가 있었습니다. 결국 다양한 길이의 입력 시퀀스를 처리할 수 있는 인공 신경망이 필요하게 되었는데, 자연어 처리에 대표적으로 사용되는 인공 신경망이 바로 순환 신경망(Recurrent Neural Network, RNN)입니다. 이번 챕터에서는 가장 기본적인 순환 신경망인 바닐라 RNN, 이를 개선한 LSTM, GRU에 대해서 학습해봅시다. LSTM과 GRU를 이해한다면 텍스트 분류나 기계 번역과 같은 다양한 자연어 처리 문제들을 풀 수 있습니다.

 

다양한 길이의 입력 시퀀스를 처리하기 위해 RNN고안.

하지만 RNN에서도 단점을 보완한 점이 LSTM 및 GRU.

 

내용

RNN(Recurrent Neural Network)은 순환 신경망이라고도 하며, 시퀀스 데이터를 처리하기 위해 설계된 딥러닝 모델이다.

(이름에서 알 수 있듯이 'recurrent'는 '반복되는'이라는 의미.)

 

RNN의 핵심 특징은 입력 데이터의 순서를 고려할 수 있다는 점.

=> 이는 시계열 데이터, 텍스트, 음성 인식 등 시간에 따라 변화하는 데이터를 다루는 데 유리함

 

입력과 출력을 시퀀스 단위로 처리

(*시퀀스는 문장과 같은 단어가 나열된 것을 의미)

 

이러한 시퀀스를 처리하기 위해 만든 시퀀스 모델 중 딥러닝의 가장 기본적인 시퀀스 모델.

TMI(or TIP) : 용어는 비슷하지만 순환 신경망과 재귀 신경망(Recursive Neural Network)은 전혀 다른 개념

 

 

앞서 배운 것들은 은닉층에서 활성화 함수(activation function)를 지닌 값은 오직 출력층 방향으로만 향함.

-> 이와 같은 애들을 피드 포워드 신경망(Feed Forward). 개인적인 생각으론 앞방향으로 '먹인다'는 것이 잘 표현한듯.

=> 하지마 RNN은 은닉층 노드에서 활성화 함수를 통해 나온 result를 출력층 방향으로도 보내지만 다시 은닉층 노드의 입력으로도 보냄

 

출처 : https://wikidocs.net/22886

위 그림서 x는 입력층의 벡터, y는 출력층의 출력 벡터이다. (bias 또한 입력으로 존재할 수 있지만 위 그림에선 생략)

- 셀(cell) : RNN에서 은닉층에서 활성화 함수를 통해 결과를 내보내는 역할을 하는 노드

이 셀은 이전 값을 기억하려고 하는 (일종의 메모리 역할) -> 이를 메모리 셀 or RNN 셀 이라고 표현

은닉 층의 메모리 셀은 각각의 시점에서 바로 이전 시점에서의 은닉층의 메모리 셀에서 나온 값을 자신의 입력으로 사용하는 재귀적 활동을 함. 현재 시점 t에서 메모리 셀이 다음 시점인 t+1의 자신에게 보내는 값을 은닉 상태(hidden state)라고 함.

해당 내용을 그림으로 표현하면 다음과 같음.

(좌측 사진은 화살표로 사이클을 그려 재귀 표현, 우측은 여러 시점으로 펼쳐서 표현)

Feed Forward 신경망에서는 뉴런이라는 단위를 사용했지만, RNN에서는 입력 벡터, 출력 벡터, 은닉층에서는 은닉 상태라는 표현을 주로 더 사용.

이를 뉴런 단위로 시각화 한다면 다음과 같고, 뉴런 단위로 해석 시 입력층의 뉴런 수는 4, 은닉 층의 뉴런 수는 2, 출력층의 뉴런 수는 2. 

RNN은 입력과 출력의 길이를 다르게 설계 가능. (입, 출력의 단위는 정의하기 나름이지만 가장 보편적인 단위는 '단어 벡터')

--- 24년 10월 5일. (08-01 보충 필요)

 

바닐라 RNN의 한계

(앞서 배운 가장 단순한 형태의 RNN을 Vanila RNN이라 한다. 바닐라 RNN의 한계를 극복하기 위해 나온 다양한 RNN의 변형 중 LSTM에 대한 소개.)

 

바닐라 RNN은 출력 결과가 이전의 계산 결과에 의존. 하지만 비교적 짧은 시퀀스에 대해서만 효과를 보임.

(어쩌면 가장 중요한 정보가 시점의 앞 쪽에 위치할 수 있음.)

=> 이 문제를 장기 의존성 문제(The problem of Long-Term Dependencies)라고 함.

 

바닐라 RNN의 내부 (bias는 생략)

바닐라 RNN은 x_t와 h_t-1이라는 두 입력이 (가중치가 곱해져) 메모리 셀의 입력이 됨.

-> 이를 hyperbolic tangent의 입력으로 사용. -> 이 값은 은닉층의 출력인 은닉 상태가 됨.

 

LSTM의 내부

 

--- 24년 10월 6일. (08-02 추가 필요)

주요 특징

  • 순환 구조: RNN은 시퀀스의 각 항목을 처리할 때, 이전의 항목에서 얻은 정보를 현재 항목의 계산에 활용할 수 있도록 설계되어 있습니다. 이 때문에 RNN은 현재 시점의 입력뿐만 아니라 이전 시점의 입력도 기억하고 활용할 수 있습니다.
  • 상태 유지: RNN의 각 노드는 현재 시점의 입력과 이전 시점의 은닉 상태(hidden state)를 결합하여 새로운 은닉 상태를 만듭니다. 이 과정에서 이전의 정보가 지속적으로 전달되므로, 모델이 순차적인 정보를 처리하고 학습하는 데 도움을 줍니다.

한계와 문제점

  • 기울기 소실/폭발 문제: RNN은 긴 시퀀스를 처리할 때, 역전파 과정에서 기울기(gradient)가 소실되거나 폭발하는 문제가 발생할 수 있습니다. 이는 RNN이 긴 시퀀스에서 장기 의존성을 학습하는 데 어려움을 겪게 만듭니다.
  • 단기 기억 문제: RNN은 상대적으로 짧은 시퀀스에서 잘 동작하지만, 시퀀스가 길어질수록 이전 정보가 사라지기 쉬워 장기 의존성(long-term dependency)을 학습하는 데 어려움이 있습니다.

LSTM과 GRU

RNN의 위 한계를 극복하기 위해 RNN의 변형 모델인 LSTM(Long Short-Term Memory)과 GRU(Gated Recurrent Unit) 등이 개발되었으며 이들 모델은 내부 게이트 구조를 통해 기억과 잊기의 과정을 조절함으로써 장기 의존성을 더 잘 학습할 수 있음.

RNN은 자연어 처리, 음성 인식, 기계 번역 등 다양한 시퀀스 처리 작업에 사용되었으며, 여전히 일부 응용 분야에서 중요한 역할을 하고 있습니다. 하지만, 현재는 트랜스포머(Transformer)와 같은 모델이 더 널리 사용되고 있습니다.

 

 

 

 

참고

https://casa-de-feel.tistory.com/39

https://wikidocs.net/22886

 

 

+ Recent posts