Home [PTM] 딥러닝과 파이토치, 신경망, 텐서
Post
Cancel

[PTM] 딥러닝과 파이토치, 신경망, 텐서

[PTM] Section 1

1. 딥러닝과 파이토치 라이브러리 소개

  • 일반화된 알고리즘 분류는 기존에 사람만 할 수 있던 일을 자동화해주며 복잡한 비선형 프로세스들을 효과적이면서 꽤 그럴듯하게 처리
  • 지능은 자의식을 포함한 개념이라고 생각해왔지만, 작업을 수행하는 데 자의식은 굳이 필요도 없을 것 같다
  • 딥러닝은, 대량의 데이터를 사용해 입력과 출력이 동떨어진 복잡한 함수들을 근사하는 방법을 사용

1.1 딥러닝 혁명

  • 머신러닝(ML)은 피처 엔지니어링(feature engineering)에 크게 의존 (다운스트림 알고리즘이 새로운 데이터를 기반으로 올바른 결과물을 낼 수 있도록 촉진하기 위한 데이터 변형)
  • 딥러닝(DL)은 원본 데이터로부터 자동으로 표현을 찾아냄
  • 주어진 예제로부터 유용한 표현을 추출하는 신경망의 능력은 딥러닝을 좀 더 강력하게 만듬

1.2 딥러닝을 위한 파이토치

  • 텐서(Tensor)는 넘파이 배열과 여러 면에서 유사한 다차원 배열

1.3 왜 파이토치인가?

  • 단순함
  • 텐서를 기본 제공
  • 산술 최적화 지원

1.4~1.5

  • pass

1.6 연습문제

    1. 파이썬을 실행해 프롬포트를 띄워보자
      • 어떤 버전을 사용 중인가? -> 3.7
      • import torch가 되는가? -> yes
      • torch.cuda.is_available() 실행 결과는? -> TF와 Torch 환경 구축 중
    1. 주피터 노트북 서버를 띄워보자
      • 주피터가 사용하는 파이썬 버전은?
      • 경로가 같은지?

2. 사전 훈련된 신경망

2.1 이미지를 인식하는 사전 훈련된 신경망

  • 이미지 분류
  • 사물 측위
  • 사물 인식
  • 자연 분류
  • 장면 파싱

2.1.1 사전 훈련된 신경망 가져오기

1
2
import torch
from torchvision import models

2.1.2 알렉스넷 (AlexNet)

  • 2012, ILSVRC, 상위 5위 테스트에서 15.4% 오차율 -> 개선된 아키텍처와 훈련 방법 사용시 오차율 3% AlexNet
1
alexnet = models.AlexNet()

2.1.3 레즈넷(ResNet)

  • 잔차 신경망(residual network)이 만들어지기 전까지는, 깊은 신경망을 안정적으로 훈련시키는 것은 어려운 일이었음
1
2
3
# 이미지넷으로 훈련시킨 가중치를 가진
from torchvision.models import ResNet101_Weights
resnet = models.resnet101(weights=ResNet101_Weights.DEFAULT)
1
2
Downloading: "https://download.pytorch.org/models/resnet101-cd907fc2.pth" to C:\Users\spec3/.cache\torch\hub\checkpoints\resnet101-cd907fc2.pth
100.0%

2.1.4 레즈넷 구조

1
resnet
1
2
3
4
5
6
7
8
9
10
11
12
13
# 이미지는 먼저 전처리가 필요 -> 동일한 숫자 범위 안에 색상값이 들어올 수 있도록 크기 조정
# transforms를 이용해 파이프라인을 빠르게 구성 가능
from torchvision import transforms

preprocess = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize( # RGB 정규화
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225]
    )
])

Q1. 256*256으로 리사이즈하고, 224*224로 크롭하면 최종 이미지 사이즈는?

1
img_t.shape
1
torch.Size([3, 224, 224])
1
2
3
# 테스트 이미지
from PIL import Image
img = Image.open("./data/p1ch2/bobby.jpg")
1
2
img
# img.show()

png

1
2
3
# 전처리 파이프라인 적용
img_t = preprocess(img)
batch_t = torch.unsqueeze(img_t, 0)

2.1.5 실행

딥러닝 사이클에서 훈련된 모델에 새로운 데이터를 넣어 결과를 보는 과정 -> 추론(inference)
-> 신경망을 eval 모드로 설정해야 함

1
resnet.eval()
1
2
out = resnet(batch_t)
out
  • 1,000개의 스코어를 만들어냈고 점수 하나 하나는 이미지넷 클래스에 각각 대응 => 점수가 가장 높은 클래스의 레이블 찾으면 됨
1
2
with open("./data/p1ch2/imagenet_classes.txt", "r") as f:
    labels = [line.strip() for line in f.readlines()]
1
_, index = torch.max(out, 1)
1
2
percentage = torch.nn.functional.softmax(out, dim=1)[0] * 100
labels[index[0]], percentage[index[0]].item()
1
(['golden', 'retriever'], 74.68071746826172)
1
2
_, indices = torch.sort(out, descending=True)
[(labels[idx], percentage[idx].item()) for idx in indices[0][:5]]
1
2
3
4
5
6
[(['golden', 'retriever'], 74.68071746826172),
 (['Labrador', 'retriever'], 1.4816735982894897),
 (['tennis', 'ball'], 1.1526576280593872),
 (['toilet', 'tissue,', 'toilet', 'paper,', 'bathroom', 'tissue'],
  0.21645964682102203),
 (['Bernese', 'mountain', 'dog'], 0.20590734481811523)]

2.2 가짜 이미지를 만드는 사전 훈련된 모델

2.2.1 GAN 게임

  • 생상적 적대 신경망(Generative Adversarial Network)
  • 만들어지고(generative), 서로 더 뛰어나기 위해 경쟁하는(adversarial), 신경망(network)
  • 생성자(generator)와 식별자(discriminator)

2.2.2 사이클GAN (CycleGAN)

  • 사이클을 만듦으로써 훈련 과정에서 문제가 되었던 GAN의 안정화 이슈 해결

2.2.3 말을 얼룩말로 바꾸는 신경망

1
import torch.nn as nn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class ResNetBlock(nn.Module): # <1>

    def __init__(self, dim):
        super(ResNetBlock, self).__init__()
        self.conv_block = self.build_conv_block(dim)

    def build_conv_block(self, dim):
        conv_block = []

        conv_block += [nn.ReflectionPad2d(1)]

        conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=0, bias=True),
                       nn.InstanceNorm2d(dim),
                       nn.ReLU(True)]

        conv_block += [nn.ReflectionPad2d(1)]

        conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=0, bias=True),
                       nn.InstanceNorm2d(dim)]

        return nn.Sequential(*conv_block)

    def forward(self, x):
        out = x + self.conv_block(x) # <2>
        return out


class ResNetGenerator(nn.Module):

    def __init__(self, input_nc=3, output_nc=3, ngf=64, n_blocks=9): # <3> 

        assert(n_blocks >= 0)
        super(ResNetGenerator, self).__init__()

        self.input_nc = input_nc
        self.output_nc = output_nc
        self.ngf = ngf

        model = [nn.ReflectionPad2d(3),
                 nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0, bias=True),
                 nn.InstanceNorm2d(ngf),
                 nn.ReLU(True)]

        n_downsampling = 2
        for i in range(n_downsampling):
            mult = 2**i
            model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3,
                                stride=2, padding=1, bias=True),
                      nn.InstanceNorm2d(ngf * mult * 2),
                      nn.ReLU(True)]

        mult = 2**n_downsampling
        for i in range(n_blocks):
            model += [ResNetBlock(ngf * mult)]

        for i in range(n_downsampling):
            mult = 2**(n_downsampling - i)
            model += [nn.ConvTranspose2d(ngf * mult, int(ngf * mult / 2),
                                         kernel_size=3, stride=2,
                                         padding=1, output_padding=1,
                                         bias=True),
                      nn.InstanceNorm2d(int(ngf * mult / 2)),
                      nn.ReLU(True)]

        model += [nn.ReflectionPad2d(3)]
        model += [nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0)]
        model += [nn.Tanh()]

        self.model = nn.Sequential(*model)

    def forward(self, input): # <3>
        return self.model(input)
1
netG = ResNetGenerator()
1
2
3
4
# 가중치 로드
model_path = "./data/p1ch2/horse2zebra_0.4.0.pth"
model_data = torch.load(model_path)
netG.load_state_dict(model_data)
1
<All keys matched successfully>
1
netG.eval()

이미지를 픽셀 단위로 보고, 한 개 이상의 말을 찾아, 각 픽셀을 수정해서 실제 같은 얼룩말을 만드는 과정

1
2
3
4
preprocess = transforms.Compose([
    transforms.Resize(256),
    transforms.ToTensor()
])
1
2
img = Image.open("./data/p1ch2/horse.jpg")
img

png

1
2
3
4
img_t = preprocess(img)
batch_t = torch.unsqueeze(img_t, 0)

batch_out = netG(batch_t)
1
2
3
4
# tensor -> img
out_t = (batch_out.data.squeeze()+1.0) / 2.0
out_img = transforms.ToPILImage()(out_t)
out_img

png

2.3 장면을 설명하는 사전 훈련된 신경망

2.3.1 뉴럴토크2

pass

2.4 토치허브

1
2
3
from torch import hub

resnet18_model = hub.load("pytorch/vision:v0.10.0", "resnet18", pretrained=True)

3. 텐서 구조체

  • 딥러닝 프로세스는 입력을 부동소수점 수로 변환하는 것부터 시작

3.1 부동소수점 수의 세계

  • 심층 신경망(deep neural network)은 보통 여런 단계를 거쳐 데이터 변환을 학습중
  • 중간 표현값은, 입력과 이전 층의 뉴런이 가진 가중치를 조합한 결과

3.2 텐서: 다차원 배열

3.2.1 파이썬 리스트에서 파이토치 텐서로
1
2
3
a = [1.0, 2.0, 1.0]
a[2] = 3.0
a
1
[1.0, 2.0, 3.0]

텐서 자료구조를 사용해 이미지와 시계열 데이터 혹은 문장들을 나타내는 것이 더 효율적

3.2.2 첫 텐서 만들어보기
1
2
3
# 크기가 3이고 1.0으로 채워진 텐서
a = torch.ones(3)
a
1
tensor([1., 1., 1.])

겉으로는 숫자 객체의 리스트처럼 보이지만, 내부 동작은 완전히 다름

3.2.3 텐서의 핵심
  • 숫자값으로 만든 파이썬 리스트나 튜플은 메모리에 따로따로 할당
  • 파이토치의 텐서나 넘파이 배열은, 언박싱(unboxing)된 C언어의 숫자 타입을 포함한 연속적인 메모리가 할당 (32bit float)
1
2
3
4
5
6
7
8
points = torch.zeros(6)

points[0] = 4.0
points[1] = 1.0
points[2] = 5.0
points[3] = 3.0
points[4] = 2.0
points[5] = 1.0
1
points
1
tensor([4., 1., 5., 3., 2., 1.])
1
2
3
# 생성자에 파이썬 리스트를 넘겨도 됨
points = torch.tensor([4.0, 1.0, 5.0, 3.0, 2.0, 1.0])
points
1
tensor([4., 1., 5., 3., 2., 1.])
1
float(points[0]), float(points[1])
1
(4.0, 1.0)
1
2
3
4
5
6
7
# 2차원 텐서
points = torch.tensor([
    [4., 1.],
    [5., 3.],
    [2., 1.]
])
points
1
2
3
tensor([[4., 1.],
        [5., 3.],
        [2., 1.]])
1
points.shape
1
torch.Size([3, 2])
1
2
points = torch.zeros(3, 2)
points
1
2
3
tensor([[0., 0.],
        [0., 0.],
        [0., 0.]])

3.3 텐서 인덱싱

1
2
some_list = list(range(6))
some_list[:]
1
[0, 1, 2, 3, 4, 5]
1
points[1:]
1
2
tensor([[5., 3.],
        [2., 1.]])
1
points[1:, :]
1
2
tensor([[5., 3.],
        [2., 1.]])
1
2
# 첫 번째 이후 모든 행에 대해 첫 번째 열만 포함
points[1:, 0]
1
tensor([5., 2.])
1
2
# 길이가 1인 차원을 추가, unsqueeze와 동일
points[None]
1
2
3
tensor([[[4., 1.],
         [5., 3.],
         [2., 1.]]])

3.4 이름이 있는 텐서

1
2
img_t = torch.randn(3, 5, 5) # [채널 크기, 행 크기, 열 크기]
weights = torch.tensor([0.2126, 0.7152, 0.0722])
1
batch_t = torch.randn(2, 3, 5, 5) # [채널 크기, 행 크기, 열 크기]
1
2
3
img_gray_naive = img_t.mean(-3)
batch_gray_naive = batch_t.mean(-3)
img_gray_naive.shape, batch_gray_naive.shape
1
(torch.Size([5, 5]), torch.Size([2, 5, 5]))
1
2
3
4
5
6
7
8
9
unsqueezed_weights = weights.unsqueeze(-1).unsqueeze(-1)

img_weights = (img_t * unsqueezed_weights)
batch_weights = (batch_t * unsqueezed_weights)

img_gray_weighted = img_weights.sum(-3)
batch_gray_weighted = batch_weights.sum(-3)

batch_weights.shape, batch_t.shape, unsqueezed_weights.shape
1
(torch.Size([2, 3, 5, 5]), torch.Size([2, 3, 5, 5]), torch.Size([3, 1, 1]))

einsum: 넘파이에서 차용, 차원별로 이름을 부여

1
2
3
4
img_gray_weighted_fancy = torch.einsum("...chw, c->...hw", img_t, weights)
batch_gray_weighted_fancy = torch.einsum("...chw, c->...hw", batch_t, weights)

batch_gray_weighted_fancy.shape
1
torch.Size([2, 5, 5])
1
2
weights_named = torch.tensor([0.2126, 0.7152, 0.0722], names=["channels"])
weights_named
1
2
3
4
5
6
7
8
c:\Users\spec3\anaconda3\envs\NN\lib\site-packages\ipykernel_launcher.py:1: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at  C:\cb\pytorch_1000000000000\work\c10/core/TensorImpl.h:1408.)
  """Entry point for launching an IPython kernel.





tensor([0.2126, 0.7152, 0.0722], names=('channels',))

텐서를 먼저 만들고 나중에 이름을 지정하려면 refine_names 함수를 사용
-> 텐서 접근시, ...를 이요하면 다른 차원은 건드리지 않음
-> 지우려면 None을 이름으로 넘기면 됨

1
2
3
4
5
img_named = img_t.refine_names(..., "channels", "rows", "columns")
batch_named = batch_t.refine_names(..., "channels", "rows", "columns")

print(f"img named: {img_named.shape} {img_named.names}")
print(f"batch named: {batch_named.shape} {batch_named.names}")
1
2
img named: torch.Size([3, 5, 5]) ('channels', 'rows', 'columns')
batch named: torch.Size([2, 3, 5, 5]) (None, 'channels', 'rows', 'columns')

텐서끼리의 연산은 먼저 각 차원의 크기가 같은지 혹은 한쪽이 1이고 다른 쪽으로 브로드캐스팅될 수 있는지도 확인해야 함
-> 이름이 지정되어있다면 토치가 대신해서 체크
-> 토치가 차원을 자동으로 정렬해주지는 않아서 명시적으로 수행할 필요가 있음
-> align_as 함수는 빠진 차원을 채우고, 존재하는 차원을 올바른 순서로 바꿔줌

1
2
weights_aligned = weights_named.align_as(img_named)
weights_aligned.shape, weights_aligned.names
1
(torch.Size([3, 1, 1]), ('channels', 'rows', 'columns'))
1
2
gray_named = (img_named * weights_aligned).sum("channels")
gray_named.shape, gray_named.names
1
(torch.Size([5, 5]), ('rows', 'columns'))
1
2
# 이름이 다른 차원을 결합하면 오류 발생
(img_named[..., :3] * weights_named)
1
2
3
4
5
6
7
8
9
---------------------------------------------------------------------------

RuntimeError                              Traceback (most recent call last)

~\AppData\Local\Temp\ipykernel_26500\2758185046.py in <module>
----> 1 (img_named[..., :3] * weights_named)


RuntimeError: Error when attempting to broadcast dims ['channels', 'rows', 'columns'] and dims ['channels']: dim 'columns' and dim 'channels' are at the same position from the right but do not match.
1
2
3
4
# 이름있는 텐서를 사용하는 연산을 함수 밖에서도 사용하려면,
# 차원 이름에 None을 만들어 이름이 없는 텐서로 만들어야 함
gray_plain = gray_named.rename(None)
gray_plain.shape, gray_plain.names
1
(torch.Size([5, 5]), (None, None))

3.5 텐서 요소 타입

3.5.1 dtype으로 숫자 타입 지정하기

텐서 생성자 실행 시, 넘겨주는 dtype 인자로 텐서 내부에 들어갈 데이터 타입을 지정할 수 있음

3.5.2 모든 경우에 사용하는 dtype

3.5.3 텐서의 dtype 속성 관리

숫자 타입이 올바르게 지정된 텐서를 하나 할당할 때에는 생성자에 dtype 인자를 정확하게 전달해야 함

1
2
double_points = torch.ones(10, 2, dtype=torch.double)
short_points = torch.tensor([[1, 2], [3, 4]], dtype=torch.short)
1
double_points.dtype, short_points.dtype
1
(torch.float64, torch.int16)

텐서 생성 함수가 반환하는 텐서의 타입을 대응하는 캐스팅 메소드를 사용해 올바른 타입으로 변환하는 것도 가능

1
2
double_points = torch.zeros(10, 2).double()
short_points = torch.ones(10, 2).to(dtype=torch.short)
  • to 메소드는 변환이 필요한 경우에만 진행
  • 여러 타입을 가진 입력들이 연산을 거치며 서로 섞일 때 자동으로 제일 큰 타입으로 만들어 짐
1
2
3
points_64 = torch.rand(5, dtype=torch.double)
points_short = points_64.to(torch.short)
points_64 * points_short
1
tensor([0., 0., 0., 0., 0.], dtype=torch.float64)

3.6 텐서 API

1
2
3
4
a = torch.ones(3, 2)
a_t = torch.transpose(a, 0, 1)

a.shape, a_t.shape
1
(torch.Size([3, 2]), torch.Size([2, 3]))
1
2
3
4
a = torch.ones(3, 2)
a_t = a.transpose(0, 1)

a.shape, a_t.shape
1
(torch.Size([3, 2]), torch.Size([2, 3]))
  • Creation ops: ones나 from_numpy 같이 텐서를 만드는 함수
  • Indexing, slicing, joining, mutating ops: shape, stride, transpose처럼 텐서 내부를 바꾸는 함수
  • Match ops: 연산을 통해 텐서 내부를 조작하는 함수
    • Pointwise ops: 요소 하나 하나에 대해
    • Reduction ops: 여러 텐서를 순회
    • Comparison ops: 텐서 요소에 대해 참거짓을 숫자로 평가해 반환
    • Spectral ops: stft나 hamming_window처럼 주파수 영역에 대해 변환이나 연산을 수행
    • Other operations: 벡터에 대한 연산, 행렬에 대한 특수 연산 등
    • BLAS and LAPACK operations: 기본 선형 대수 서브프로그램(BLAS) 정의를 따르며, 스칼라, 벡터-벡터, 행렬-벡터, 행렬-행렬에 대해 연산
  • Random sampling: 확률 분포에 기반해서 난수를 만드는 함수
  • Serialization: 텐서를 저장하거나 불러오는 함수
  • Parallelism: 병렬 CPU 처리 시 스레드 수를 제어하는 함수

3.7 텐서 저장소 관점에서 머릿속에 그려보기

  • 텐서 내부값은 실제로는 torch.Storage 인스턴스로 관리하며 연속적인 메모리 조각으로 할당된 상태
  • 서로 다른 방식으로 구성된 텐서가 동일한 메모리 공간을 가리키고 있을 수도 있음

3.7.2 저장 공간 인덱싱

1
2
3
4
5
6
7
points = torch.tensor([
    [4., 1.],
    [5., 3.],
    [2., 1.]
])

points.storage()
1
2
3
4
5
6
7
 4.0
 1.0
 5.0
 3.0
 2.0
 1.0
[torch.storage._TypedStorage(dtype=torch.float32, device=cpu) of size 6]

3r2c로 이뤄진 텐서이지만, 실제로는 크기가 6인 배열 공간일 뿐

1
2
points_storage = points.storage()
points_storage[0]
1
4.0
1
points.storage()[1]
1
1.0

저장 공간에 접근해서 값을 바꾸면 참조하고 있는 텐서에서의 내용도 변경 됨

1
2
3
4
5
6
7
8
9
points = torch.tensor([
    [4., 1.],
    [5., 3.],
    [2., 1.]
])

points_storage = points.storage()
points_storage[0] = 2.0
points
1
2
3
tensor([[2., 1.],
        [5., 3.],
        [2., 1.]])

3.7.2 저장된 값을 수정하기: 텐서 내부 연산

_로 끝나는 연산은, 연산 결과로 기존 텐서의 내용이 바뀜

1
2
a = torch.ones(3, 2)
a
1
2
3
tensor([[1., 1.],
        [1., 1.],
        [1., 1.]])
1
a.zero_()
1
2
3
tensor([[0., 0.],
        [0., 0.],
        [0., 0.]])
1
a
1
2
3
tensor([[0., 0.],
        [0., 0.],
        [0., 0.]])

3.8 텐서 메타데이터: 사이즈, 오프셋, 스트라이드

  • 저장 공간을 인덱스로 접근하기 위해 텐서는 저장 공간에 포함된 몇 가지 명확한 정보 (사이즈, 오프셋, 스트라이드)에 의존

3.8.1 다른 텐서의 저장 공간에 대한 뷰 만들기

1
2
3
4
5
6
7
8
points = torch.tensor((
    [4., 1.],
    [5., 3.],
    [2. ,1.]
))

second_point = points[1]
second_point.storage_offset()
1
2
1
second_point.size()
1
torch.Size([2])
1
points.stride()
1
(2, 1)

스트라이드는 값을 가진 튜플, 각 차원에서 인덱스를 하나 증가했을 때 저장 공간상에서 몇개 요소를 건너 뛰어야 하는지를 값으로 가짐

새 텐서가 원래의 points 텐서보다 하나 작은 차원을 가지지만 여전히 동일한 저장 공간을 가리키고 있음
-> 새 텐서를 변경하면 원래의 텐서도 바뀜

1
2
3
4
5
6
7
8
9
points = torch.tensor([
    [4., 1.],
    [5., 3.],
    [2., 1.]
])

second_point = points[1]
second_point[0] = 10.
points
1
2
3
tensor([[ 4.,  1.],
        [10.,  3.],
        [ 2.,  1.]])

이런 동장 방식이 항상 바람직하지는 않으므로, 결과적으로 서브텐서를 새 텐서로 복제할 수 있음

1
2
3
4
5
6
7
8
9
points = torch.tensor([
    [4., 1.],
    [5., 3.],
    [2., 1.]
])

second_point = points[1].clone()
second_point[0] = 10.
points
1
2
3
tensor([[4., 1.],
        [5., 3.],
        [2., 1.]])

3.8.2 복사 없이 텐서 전치하기

1
2
3
4
5
6
7
points = torch.tensor([
    [4., 1.],
    [5., 3.],
    [2., 1.]
])

points
1
2
3
tensor([[4., 1.],
        [5., 3.],
        [2., 1.]])
1
2
points_t = points.t()
points_t
1
2
tensor([[4., 5., 2.],
        [1., 3., 1.]])
1
2
# 두 텐서가 같은 공간을 가리키고 있는지는 다음과 같이 확인 가능
id(points.storage()) == id(points_t.storage)
1
False

3.8.3 더 높은 파원에서의 전치 연산

다차원 배열에 대해서도 차원 정보와 스트라이드가 바뀔 두 차원을 각각 지정해주면 전치 됨

1
2
3
some_t = torch.ones(3, 4, 5)
transpose_t = some_t.transpose(0, 2)
some_t.shape
1
torch.Size([3, 4, 5])
1
transpose_t.shape
1
torch.Size([5, 4, 3])
1
some_t.stride(), transpose_t.stride()
1
((20, 5, 1), (1, 5, 20))

인접한(contiguous) 텐서는 값 순회 시 띄엄띄엄 참조하지 않기 때문에 데이터 지역성(data locality) 관점에서 CPU 메모리 접근 효율이 좋음

3.8.4 인접한 텐서

  • 토치 텐서 연산 중에는 인접한 텐서에 대해서만 동작하는 경우가 있음 (view)
1
points.is_contiguous()
1
True
1
points_t.is_contiguous()
1
False

contiguous 메소드를 사용하면 인접하지 않은 텐서를 인접한 텐서로 만들 수 있음
-> 텐서 내용은 동일하나, 값의 배치와 스트라이드가 바뀐 텐서가 만들어짐

1
2
3
4
5
6
7
8
points = torch.tensor([
    [4., 1.],
    [5., 3.],
    [2., 1.]
])

points_t = points.t()
points_t
1
2
tensor([[4., 5., 2.],
        [1., 3., 1.]])
1
points_t.storage()
1
2
3
4
5
6
7
 4.0
 1.0
 5.0
 3.0
 2.0
 1.0
[torch.storage._TypedStorage(dtype=torch.float32, device=cpu) of size 6]
1
points_t.stride()
1
(1, 2)
1
2
points_t_cont = points_t.contiguous()
points_t_cont
1
2
tensor([[4., 5., 2.],
        [1., 3., 1.]])
1
points_t_cont.stride()
1
(3, 1)
1
points_t_cont.storage()
1
2
3
4
5
6
7
 4.0
 5.0
 2.0
 1.0
 3.0
 1.0
[torch.storage._TypedStorage(dtype=torch.float32, device=cpu) of size 6]

3.9 텐서를 GPU로 옮기기

3.9.1 텐서 디바이스 속성 관리

  • dtype 외에 파이토치의 Tensor는 device라는 인자로 텐서 데이터가 실제 컴퓨터의 어디에 위치할지도 지정할 수 있음
1
2
3
4
5
points_gpu = torch.tensor([
    [4., 1.],
    [5., 3.],
    [2., 1.]
], device="cuda")
1
points_gpu
1
2
3
tensor([[4., 1.],
        [5., 3.],
        [2., 1.]], device='cuda:0')
1
2
# CPU에 만들어진 텐서를 GPU로 옮김
points_gpu = points.to(device="cuda")
1
points.storage()
1
2
3
4
5
6
7
 4.0
 1.0
 5.0
 3.0
 2.0
 1.0
[torch.storage._TypedStorage(dtype=torch.float32, device=cpu) of size 6]
1
points_gpu.storage()
1
2
3
4
5
6
7
 4.0
 1.0
 5.0
 3.0
 2.0
 1.0
[torch.storage._TypedStorage(dtype=torch.float32, device=cuda:0) of size 6]
1
2
3
4
# CPU에서 실행되는 곱셈
points = 2 * points
# GPU에서 실행되는 곱셈
points_gpu = 2 * points.to(device="cuda")
1
points
1
2
3
tensor([[ 8.,  2.],
        [10.,  6.],
        [ 4.,  2.]])
1
points_gpu
1
2
3
tensor([[16.,  4.],
        [20., 12.],
        [ 8.,  4.]], device='cuda:0')

계산 완료 후, points_gpu 텐서는 CPU 영역으로 옮겨지지 않음

  1. points 텐서를 GPU에 복사
  2. GPU에 새 텐서를 할당한 후 곱셈 결과를 저장
  3. GPU 텐서 핸들을 반환
1
points_gpu = points_gpu + 4
1
points_cpu = points_gpu.to(device="cpu")
1
points_gpu
1
2
3
tensor([[20.,  8.],
        [24., 16.],
        [12.,  8.]], device='cuda:0')
1
points_cpu
1
2
3
tensor([[20.,  8.],
        [24., 16.],
        [12.,  8.]])
1
2
3
4
# 축약 메소드 사용 가능
points_gpu = points.cuda()
points_gpu = points.cuda(0)
points_cpu = points_gpu.cpu()

3.10 넘파이 호환

1
2
3
points = torch.ones(3, 4)
points_np = points.numpy()
points_np
1
2
3
array([[1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.]], dtype=float32)
1
points = torch.from_numpy(points_np)

버퍼를 공유하는 전략을 사용하고 있음
-> GPU에 할당 된 경우, 변환 과정에서 CPU 영역으로 텐서 내용을 복사해서 배열을 만듬

3.11 일반화된 텐서도 텐서다

pass

3.12 텐서 직렬화

내부적으로 pickle을 사용

1
torch.save(points, "./ourpoints.t")
1
2
with open("./ourpoints.t", "wb") as f:
    torch.save(points, f)
1
points = torch.load("./ourpoints.t")
1
2
with open("./ourpoints.t", "rb") as f:
    points = torch.load(f)

3.12.1 h5py로 HDF5 병렬화하기

  • 텐서를 호환 가능한 형태로 저장
  • HDF5는 이식성이 높고, 광범위하게 지원되는, 중첩된 키-값 딕셔너리에서 직렬화된 정형 다차원 배열을 표현하는 포맷
1
import h5py
1
2
3
f = h5py.File("./data/p1ch3/ourpoints.hdf5", 'w')
dset = f.create_dataset("coords", data=points.numpy()) # k, v
f.close
1
<bound method File.close of <HDF5 file "ourpoints.hdf5" (mode r+)>>
1
2
3
f = h5py.File("./data/p1ch3/ourpoints.hdf5", "r")
dset = f["coords"]
last_points = dset[-2:]
1
points[-1]
1
tensor([1., 1., 1., 1.])
1
dset[-1]
1
array([1., 1., 1., 1.], dtype=float32)
1
torch.from_numpy(dset[-2:])
1
2
tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.]])
1
f.close()

3.13 결론

3.14 연습 문제

1
2
3
# 1
a = torch.tensor(list(range(9)))
a
1
tensor([0, 1, 2, 3, 4, 5, 6, 7, 8])
1
a.size(), a.storage_offset(), a.stride()
1
(torch.Size([9]), 0, (1,))
1
2
# 1.a
b = a.view(3, 3)
1
b
1
2
3
tensor([[0, 1, 2],
        [3, 4, 5],
        [6, 7, 8]])

기존의 데이터와 같은 메모리 공간을 공유하며, stride 크기만 변경해 보여주기만 달라짐 (contigious 해야만 동작)

1
id(a) == id(b)
1
False
1
b.size(), b.storage_offset(), b.stride()
1
(torch.Size([3, 3]), 0, (3, 1))
1
2
3
# 1.b
# [2, 2] / 4 / [3, 1]
c = b[1:, 1:]
1
c.size(), c.storage_offset(), c.stride()
1
(torch.Size([2, 2]), 4, (3, 1))
1
2
# 2.a
a
1
tensor([0, 1, 2, 3, 4, 5, 6, 7, 8])
1
torch.cos(a)
1
2
tensor([ 1.0000,  0.5403, -0.4161, -0.9900, -0.6536,  0.2837,  0.9602,  0.7539,
        -0.1455])
1
2
# 2.b
# pointwise
1
# 2.c ?
This post is licensed under CC BY 4.0 by the author.

22년 종합 회고

[PTM] 텐서, 학습 기법

Comments powered by Disqus.