파이토치 딥러닝 마스터_4장

코넬·2023년 3월 28일
0

ComputerVision_Pytorch

목록 보기
4/10
post-thumbnail

📊 실제 데이터를 텐서로 표현해보자 !

4장에서는...

  • 파이토치 텐서로 실제 데이터를 표현하고,
  • 데이터 타입 범위를 다루고
  • 파일에서 데이터를 읽으며
  • 데이터를 텐서로 변환한다.
  • 신경망 모델 입력으로 사용하기 위한 텐서까지 만들어보자 !

이번 4장에서 가장 중요한 부분은 어떻게 하면 비디오나 텍스트 같은 데이터를 텐서로 표현해 딥러닝 모델을 훈련시키기 적합하도록 가공할 수 있을까? 에 대한 정답을 말할 수 있어야한다.

🖼️ 이미지 다루기

이미지는 픽셀 단위의 높이와 너비를 가지는 표준적인 그리드에 나열된 복수 개의 스칼라 값 모음으로 표현된다. 그리드의 포인트 , 한 픽셀에 대한 하나의 스칼라 값을 가지는 흑백 이미지 또는 여러 색을 표현하는 여러개의 스칼라 값을 가진다. 색상 외에도 카메라에서 얻은 다양한 피처(feature) 를 포함하기도 한다.

이미지를 다루는 파이토치 모듈은 대체적으로 텐서가 C X H X W 채널, 높이, 너비 순 으로 배치되어야한다.

이미지 텐서 변환 중 레이아웃 변경하는 방법

이미지마다 특성에 따라 적절한 레이아웃을 얻기 위해 새로운 차원으로 변경하려면 텐서의 permute method 를 사용한다.

img = torch.from_numpy(img_arr)
out = img.permute(2,0,1)
#채널2와 채널 0 그리고 채널 1 순으로 나열되도록 변경 !

이런 식으로 변경하면 되는데, 여기서 주의할 점은 텐서 복사본을 만들어 저장하는게 아니라 있는 원본을 변경하는 방법이라는 것 ! (out은 img 의 저장공간과 동일하며 단순히 텐서 레벨에서 크기와 스트라이드 정보만 변경된 것!)

자, 이제 여러장의 이미지 데이터셋을 다루는 경우의 텐서 표현은 어떻게 되는지 살펴보자 !

첫 번째 차원에서 여러 이미지를 배치 (batch) 로 넣어 N X C X H X W 텐서로 저장한다. 텐서를 만드는 작업을 효율적으로 하려면 stack을 사용하여 미리 적당한 공간을 할당하고, 디렉토리에서 읽은 이미지로 채워 넣으면 된다.

batch_size = 3
batch = torch.zeros(batch_size, 3, 256, 256, dtype=torch.uint8)

여기서 배치의 높이가 256, 픽셀 너비는 256 픽셀, RGB 이미지 세개로 이루어진 배치로 구성되어있음을 알 수 있다.

다음 코드는 입력 디렉토리에서 PNG 이미지를 읽어 텐서에 바로 저장하는 방식이다 !

import os

data_dir = '../data/p1ch4/image-cats/'
filenames = [name for name in os.listdir(data_dir)
             if os.path.splitext(name)[-1] == '.png']
for i, filename in enumerate(filenames):
    img_arr = imageio.imread(os.path.join(data_dir, filename))
    img_t = torch.from_numpy(img_arr)
    img_t = img_t.(2, 0, 1)
    img_t = img_t[:3] # RGB만 사용하기 때문에 3개 채널만 유지 !
   
    batch[i] = img_t

데이터 정규화 방법

데이터 정규화를 왜 하는건가?! 신경망은 일반적으로 부동소수점 텐서를 입력으로 사용하는데, 대략 0에서 1 사이이거나 -1에서 1 사이일 때 훈련 성능이 가장 좋은 특징을 띄기 때문이다. 대부분의 경우 텐서를 부동소수점으로 캐스팅 하고 픽셀값을 정규화 한다.

📉 3차원 이미지 : 용적 데이터 처리방법

앞으로 이 책에서 의료 관련 영상 및 데이터를 많이 다루기 때문에, CT의 처리 과정에 대해 간단하게 알아보도록하자. CT 스캔은 밀도가 높아지는 차례대로 폐, 지방층, 수분, 근육, 뼈 같은 신체부위를 구별할 수 있는데 임상 장비의 화면상에서는 순서대로 어두운 쪽에서 밝은 쪽으로 매핑 된다.

단층 사진의 각 포인트는 신체를 투과하여 검출기에 도달한 엑스레이의 양에 따라 밀도를 계산 하는데, 센서에 들어온 값을 역합성 곱으로 계산해 전체 덩어리로 만들기 위한 복잡한 수식이 동원된다.

CT는 흑백 이미지처럼 하나의 밀도 채널 만 존재하는데, 2차원 단면을 스택처럼 쌓아 3차원 텐서 를 만들면 대상의 3차원 해부도를 표현한 용적 데이터를 만들 수 있다.

여기서 알아가면 되는 것은, 용적 데이터를 저장하는 텐서와 이미지 데이터는 근본적으로 큰 차이가 없다는 것 !

자, 그럼 이제부터 의료 데이터를 조금씩 다뤄볼까? 맛보기니까 간단하게 보는걸로 ~!

특수 포맷 로딩하기

샘플 CT 스캔 로딩 같은 경우, imageio 모듈 에 있는 volread함수 를 사용한다.

volread 함수 : 인자로 받은 디렉토리에 있는 의료용 디지털 영상파일을 연속된 형태로 조합하여 3차원 넘파이 배열을 만든다.

추가로, batch 방향을 따라 여러 용적 데이터를 쌓으면 5차원 데이터셋을 만들 수 있다. 2부에서는 더 많은 CT 데이터를 살펴보자 !

테이블 데이터 표현하기

테이블 데이터의 경우 각 열은 특정 위치에서의 온도 같은 숫자 값이나 샘플의 속성을 나타내는 문자열 등의 레이블 을 가질 수 있다. 즉, 테이블 형식의 데이터는 일반적으로 각 열의 타입이 동일하지 않다. 열이 다르면 데이터 타입도 다르다. 반면 파이토치 텐서의 내부 값들은 형태가 동일하다.

실제 데이터셋으로 모델 구현을 진행해보자 !

여기서는 와인 데이터셋을 불러와서 진행해보자.
파이토치를 사용할 것이므로, 와인 데이터를 텐서 로 읽어온다.
파이썬에서 CSV 파일을 간편하게 읽는 세가지 방법은

  • 파이썬에 내장된 csv 모듈을 사용하는 법
  • 넘파이로 읽기
  • 판다스 활용

여기서 세번째 판다스를 사용하는 방법이 가장 빠르며 메모리도 덜 쓴다.

자세한 코드는 여기서 확인할 수 있다!

점수 표현하는 방법

와인에서 품질 점수를 표현하는 경우는, 품질 점수를 연속값으로 취급 해서 실수 형태를 유지한 채로 회귀 작업을 수행할 수 있고, 레이블로 취급 해서 분류 작업에서 화학 정보를 분석하여 레이블을 예측하는 시도를 할 수 있다.

통상 두 경우 모두, 입력 데이터의 텐서에서 점수를 제거하여 독자적인 텐서로 유지하고, 모델에 입력 하는 대신 점수를 정답 값으로 사용할 수 있다.

원-핫 인코딩

가장 중요 ! 점수를 처리하는 방식 중 하나로, 원-핫 인코딩(one-hot encoding) 이라고 한다. 1부터 10까지의 값이 벡터 안의 10개의 원소에 대응하도록 정해두고 원소 하나만 1로 설정하고 나머지는 모두 0으로 설정하는 방법이다. 전의 파이토치 내용에서도 설명했지만, 완전히 이산적 관계의 경우 값 사이의 순서나 거리 개념이 없는 원핫 인코딩 이 더 낫다. 원핫 인코딩은 scatter_ method 를 사용한다. ( 이 메소드는 소스 텐서와 함께 전달된 인자의 인덱스를 따라 새 텐서를 채워 넘겨준다. )

여기서 ! scatter_인자 에 대해 더 알아보자.

파이토치에서 이름 끝에 _ 가 붇으면 새로운 텐서를 반환하는 메소드가 아닌 텐서를 바꿔치기하는 방법으로 변경하는 메소드이다.

  • scatter 인자는 뒤에 오는 두 개의 인자가 따라야 하는 차원을 명세한다.
  • 원핫으로 인코딩할 요소를 가리키는 인덱스가 들어있는 텐서이다.
  • 원핫 인코딩할 원소가 들어있는 텐서 혹은 단일 스칼라이다.
  • scatter_의 두번째 인자인 인덱스 텐서는 텐서를 원핫 인코딩할 때 동일한 차원 수를 만들기 위하여 필요하다.

그래서 다음 예제 코드에서 unsqueezed를 사용하여 2차원을 맞춰주기 위해 target 에 추가 차원을 만들어 주는 것이다 !

target_onehot = torch.zeros(target.shape[0], 10)

target_onehot.scatter_(1, target.unsqueeze(1), 1.0)

#결과
tensor([[0., 0.,  ..., 0., 0.],
        [0., 0.,  ..., 0., 0.],
        ...,
        [0., 0.,  ..., 0., 0.],
        [0., 0.,  ..., 0., 0.]])
target_unsqueezed = target.unsqueeze(1)
target_unsqueezed

#결과
tensor([[6],
        [6],
        ...,
        [7],
        [6]])

4,898개의 요소가 있는 1차원 텐서가 값의 변화나 추가 요소 없이 4,898 * 1 크기의 2차원 텐서 로 바뀐다. 이는 요소를 접근하기 위한 인덱스가 추가로 만들어졌다고 생각하면 된다.

카테고리화를 진행해보자

우리의 데이터는 카테코리 데이터로 취급하거나, 연속 데이터로 취급할 수 있다.
카테고리화를 진행하기 위해서는 고급 인덱싱을 진행하고, 직접 임계값을 찾고 기준점을 세워 판단하는 방식이 있다. 이는 실습에서 직접 진행해보도록 하자 !

🧮 시계열 데이터 다루기

지금까지는 ! 테이블로 구조화된 데이터를 어떻게 표현하는지 를 배웠다.
자자, 이번에는 시계열 데이터 속 2차원 데이터셋을 3차원 데이터셋으로 바꿔보는 과정을 배워보자 ! (기준 축을 하나 더 추가시키는 방법~)

첫 번째, 시간 차원 더하기

데이터를 먼저 읽는 과정부터 시작한다.

import numpy as np
import torch
torch.set_printoptions(edgeitems=2, threshold=50, linewidth=75)

import pandas as pd
from google.colab import drive
drive.mount('/content/drive')
bikes_numpy = np.loadtxt("/content/drive/MyDrive/pytorchdeeplearningmaster/data/p1ch4/bike-sharing-dataset/hour-fixed.csv", 
    dtype=np.float32, 
    delimiter=",", 
    skiprows=1, 
    converters={1: lambda x: float(x[8:10])})
bikes = torch.from_numpy(bikes_numpy)
bikes

#결과
tensor([[1.00e+00, 1.00e+00,  ..., 1.30e+01, 1.60e+01],
        [2.00e+00, 1.00e+00,  ..., 3.20e+01, 4.00e+01],
        ...,
        [1.74e+04, 3.10e+01,  ..., 4.80e+01, 6.10e+01],
        [1.74e+04, 3.10e+01,  ..., 3.70e+01, 4.90e+01]])

매 시간대 별 데이터셋 정보를 가져오고, 이렇게 시계열 데이터셋에서 각 행은 연속적인 시간상에서의 한 지점을 표현한다. 각 행이 순서대로 나열되며, 만들어진 차원이 존재한다.
이렇게 순서가 있는 데이터는 시간에 따른 데이터 사이의 관계를 분석할 기회를 준다.

신경망 모델에서는 크기가 C인 N개의 병렬 시퀀스 로 표현하는데, C는 채널 을 나타내고, N차원은 시간 축 을 표현한다. (C는 데이터를 위한 열과 동일, N은 한 차원 당 한 시간을 의미)

시간 단위로 데이터를 만들어보자.

자 일단, 3차원 데이터로 만들어지는데, 길이가 L 인 C 개의 시퀀스를 가지는 N개의 컬렉션을 얻게 된다. 이것이 L C N 의 3차원 텐서 가 된다.

일단, 일별로 매 시간의 데이터셋을 구하기 위해서 텐서를 24시간 배치로 바라보는 뷰가 필요하기 때문에, bikes 텐서의 차원 정보와 스트라이드를 확인해보자.

bikes.shape, bikes.stride()

#결과
(torch.Size([17520, 17]), (17, 1))

17,520 시간에 17개의 열이다. 데이터를 일자, 시간 , 17개의 열의 세개 축으로 만들어보자!

daily_bikes = bikes.view(-1, 24, bikes.shape[1])
daily_bikes.shape, daily_bikes.stride()
#results
(torch.Size([730, 24, 17]), (408, 17, 1))

이를 통해서 텐서가 저장 공간에 있는 동일한 데이터를 다르게 바라보는 역할을 한다는 것 을 확인할 수 있다.

view를 호출하면 저장 공간을 바꾸지 않고 차원 수나 스트라이드 정보를 바꾼 새 텐서를 넘겨준다.

즉, 어떤 데이터도 복사하지 않으므로 연산 비용을 들이지 않고 텐서를 바꾼 것같은 효과를 낸다는 것 !!

차원 표현을 보면, 가장 오른쪽 차원은 원래 데이터셋에서와 같이 열의 수 가 되는데, 가운데 차원은 연속된 24시간을 나타내는 시간 차원이다. C개의 채널을 가진 하루를 L 시간으로 나눈 N개의 연속된 값을 가진다고 보면, N C L 순서로 놓고 싶으면 텐서를 전치해야한다!

daily_bikes = daily_bikes.transpose(1, 2)
daily_bikes.shape, daily_bikes.stride()

#results
(torch.Size([730, 17, 24]), (408, 1, 17))

훈련을 위한 데이터 준비!

전부 코드 과정으로 확인해보자.

#첫 날의 데이터만을 생각!
first_day = bikes[:24].long()
weather_onehot = torch.zeros(first_day.shape[0], 4)
first_day[:,9]

#results
tensor([1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 2, 2,
        2, 2])
        
#날씨 수준에 따라 행렬을 원핫 인코딩으로 변환, unsqueeze를 사용해 싱글톤 차원 더하기
weather_onehot.scatter_(
    dim=1, 
    index=first_day[:,9].unsqueeze(1).long() - 1,
    value=1.0)

#results
tensor([[1., 0., 0., 0.],
        [1., 0., 0., 0.],
        ...,
        [0., 1., 0., 0.],
        [0., 1., 0., 0.]])
#만들어진 행렬을 원래 데이터셋에 cat 함수를 사용하여 병합하기
torch.cat((bikes[:24], weather_onehot), 1)[:1]

#results
tensor([[ 1.00,  1.00,  1.00,  0.00,  1.00,  0.00,  0.00,  6.00,  0.00,
          1.00,  0.24,  0.29,  0.81,  0.00,  3.00, 13.00, 16.00,  1.00,
          0.00,  0.00,  0.00]])
          

bikes 데이터셋에 날씨 상태를 원핫인코딩하여 데이터 열이 있는 차원을 따라 병합하였다.

daily_bikes 텐서에 대해서도 동일한 작업을 수행하자.

# 데이터는 (B,C,L)형태, L=24시간 !, 추가 열의 크기는 C인 텐서를 만들자.
daily_weather_onehot = torch.zeros(daily_bikes.shape[0], 4,
                                   daily_bikes.shape[2])
daily_weather_onehot.shape

#results
torch.Size([730, 4, 24])

#텐서의 C차원에 원핫 인코딩을 늘어놓자. (텐서안 수행이므로 텐서 내용이 바뀜)
daily_weather_onehot.scatter_(
    1, daily_bikes[:,9,:].long().unsqueeze(1) - 1, 1.0)
daily_weather_onehot.shape

#results
torch.Size([730, 4, 24])

#C 차원을 따라 원핫 인코딩을 병합하자
daily_bikes = torch.cat((daily_bikes, daily_weather_onehot), dim=1)

#값의 범위 매핑하기, 특정 값으로 바꾸기
daily_bikes[:, 9, :] = (daily_bikes[:, 9, :] - 1.0) / 3.0

temp = daily_bikes[:, 10, :]
temp_min = torch.min(temp)
temp_max = torch.max(temp)
daily_bikes[:, 10, :] = ((daily_bikes[:, 10, :] - temp_min)
                         / (temp_max - temp_min))
                         
temp = daily_bikes[:, 10, :]
daily_bikes[:, 10, :] = ((daily_bikes[:, 10, :] - torch.mean(temp))
                         / torch.std(temp))
#표준편차로 나누는방법

🛠️ 텍스트 표현 방법

이번에는 텍스트를 이전처럼 신경망이 처리할 수 있는 숫자의 텐서로 바꿔보자.

텍스트를 숫자로 변환

신경망으로 텍스트를 다루는 직관적인 방법은 두가지가 있다.

  • 문자 단위로 한 번에 하나의 문자를 처리하는 방법
  • 단어 단위로 신경망이 바라보는 세밀한 엔티티로 개별 단어를 처리하는 방법

이 때, 어떤 방법이든지 텍스트 정보를 텐서로 인코딩하는 기술은 동일하다.

첫번째, 신경망이 이해할 수 있는 표현으로 문장을 원핫 인코딩을 수행한다. (여기서는 아스키 코드를 사용하는데, 128개의 문자를 128개의 정수로 인코딩할 수 있다.)

두번째, 단어 단위 인코딩은 같은 식이지만, 출현된 단어로 사전을 만들어 문장에 나오는 단어 시퀀스에 대해 한 단어를 한 행으로 원핫 인코딩한다.
사전에 포함되는 단어가 매우 많기 때문에 인코딩 벡터가 매우 길어지면 실용성이 떨어진다. 이를 위해 임베딩(embedding) 을 사용하여 단어 단위로 텍스트를 표현하는 효율적인 방법은 다음장에서 볼 예정이다 !

자 여기서, 문자 레벨단어 레벨 인코딩의 장단점을 살펴보자.

대부분의 언어는 단어 수보다 문자 수가 훨씬 적다. 문자를 표현해 사용하면 표현할 수 있는 클래스도 몇개 되지 않는다. 반면에 단어 레벨은 매우 큰 수의 클래스를 표현할 수 있으며, 실제 애플리케이션에서는 사전에 없는 단어도 다루게 된다.

단어는 개별 문자보다 더 많은 의미를 내포하므로, 단어의 표현은 자체적으로 훨씬 많은 정보를 갖게 된다.

  • 임베딩의 핵심은 개별 단어를 100차원의 공간에 매핑하여 이후 학습을 가능하게 하는 것이다.
  • 100개의 부동소수점을 가진 벡터로 변환하는 것이 임베딩 !

  • 만들어진 임베딩에서 비슷한 단어들끼리 군집할 뿐 아니라 일관된 공간 관계를 유지한다.

profile
어서오세요.

0개의 댓글