2주차는 무엇을 배웠나요

  1. Tensor 만들기
  2. Tensor 바꾸기
  3. Tensor 연산하기
  4. DataFrame을 Tensor로 만들기
  5. 실습
  6. 마치며

에 대해서 알아보았습니다!
지난주에 배웠던 Numpy와 비슷한 Tensor이지만 더 많은 내용을 다뤘습니다!
내용이 많이 늘어나 많이 힘들었지만 다들 열십히 참여해주셨습니다..! (분량 조절 실패한 사람 반성중.. 🥺)
이번에도 중간중간 과제로 더 공부한 내용들도 있으니 참고해주세요!

함께 기본 데이터 구조인 텐서를 시작으로 본격적으로 딥러닝 기초를 다져보아요!!





1. Tensor 만들기

Tensor를 만들고 Tensor의 요소들을 확인하는 방법을 먼저 알아보았습니다.



Tensor 생성

  • arange : 같은 간격의 1차원 텐서
  • zeros : 0으로 이루어진 텐서
  • ones : 1로 이루어진 텐서
  • randn : 0~1의 값으로 이루어진 텐서
import torch
x = torch.arange(12, dtype=torch.float32)
x
tensor([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11.])
torch.zeros((2, 3, 4))
tensor([[[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]],

        [[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]]])
torch.ones((2, 3, 4))
tensor([[[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.]],

        [[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.]]])
torch.randn(3, 4)
tensor([[-1.4006,  0.6990, -0.3430, -0.4306],
        [ 1.2131, -0.1662, -0.8585,  0.0919],
        [ 1.5555, -1.7267, -1.5563,  0.0836]])
torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
tensor([[2, 1, 4, 3],
        [1, 2, 3, 4],
        [4, 3, 2, 1]])

Tensor 형태 파악하기

  • numel : element 수 파악
  • shape : Tensor 형태 파악
  • dtype : 데이터 타입 파악
x.numel()
12
x.shape
torch.Size([12])
x.dtype
torch.float32

준석 ) Tensor의 타입을 알려주세요.

torch.Tensor()의 기본 텐서 타입은 torch.FloatTensor 이다.

텐서 타입은 초기화 할 때 지정하거나 나중에 다른 타입으로 변경할 수 있다.

  • 초기화 시 타입 지정
    1) FloatTensor 같은 특정 텐서 타입의 생성자를 직접 호출 또는 타입 캐스팅 메소드 사용
    2) torch.Tensor()와 함께 dtype 매개변수를 사용
import torch

def describe(x):
    print("타입: {}".format(x.type()))
    print("값: {}".format(x))
x = torch.FloatTensor([[1,2,3],[4,5,6]])
describe(x) # float형
타입: torch.FloatTensor
값: tensor([[1., 2., 3.],
        [4., 5., 6.]])
x = torch.LongTensor([[1,2,3],[4,5,6]])
describe(x) # long형
타입: torch.LongTensor
값: tensor([[1, 2, 3],
        [4, 5, 6]])
x = torch.ShortTensor([[1,2,3],[4,5,6]])
describe(x) # short형
타입: torch.ShortTensor
값: tensor([[1, 2, 3],
        [4, 5, 6]], dtype=torch.int16)
x = x.double()
describe(x) # 타입 캐스팅 메소드
타입: torch.DoubleTensor
값: tensor([[1., 2., 3.],
        [4., 5., 6.]], dtype=torch.float64)
x = x.int()
describe(x) # 타입 캐스팅 메소드
타입: torch.IntTensor
값: tensor([[1, 2, 3],
        [4, 5, 6]], dtype=torch.int32)

여기까지는 앞서 언급했던 첫 번째 방식으로 tensor의 타입을 지정했다

  • FloatTensor 같은 특정 텐서 타입의 생성자를 직접 호출 또는 타입 캐스팅 메소드 사용
y = torch.tensor([[1,2,3],[4,5,6],[7,8,9]], dtype = torch.float64)
describe(y)
타입: torch.DoubleTensor
값: tensor([[1., 2., 3.],
        [4., 5., 6.],
        [7., 8., 9.]], dtype=torch.float64)
y = torch.tensor([[1,2,3],[4,5,6],[7,8,9]], dtype = torch.int64)
describe(y)
타입: torch.LongTensor
값: tensor([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])

여기까지는 두 번쨰 방식을 이용했다.

# shape 또는 size()를 통해 텐서의 크기를 확인할 수 있다.
y.shape
torch.Size([3, 3])
y.size()
torch.Size([3, 3])

20220928_233616

다음과 같이 각 데이터형 별로 데이터 타입이 정해져있는 것을 알 수 있다.

인덱싱과 슬라이싱

넘파이 같은 방식으로 인덱싱과 슬라이싱, 값도 변환할 수 있습니다.

print(x[-1])
print(x[1:3])
tensor(11.)
tensor([1., 2.])
x[1] = 17
x
tensor([ 0., 17.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11.])
x[:2] = 12
x
tensor([12., 12.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11.])




2. Tensor 바꾸기

Tensor 형태를 변환하고 Tensor을 합치고, 나누는 방법을 알아보았습니다.

Tensor 형태 변환

  • reshape : contiguous하지 않을 때도 변환 가능!
  • view : contiguous하지 않으면 변환 불가능!
  • squeeze : 1인 차원 제거
  • unsqueeze : 1인 차원 추가
  • transpose : 전치 행렬
X = x.reshape(3, 4)
X
tensor([[12., 12.,  2.,  3.],
        [ 4.,  5.,  6.,  7.],
        [ 8.,  9., 10., 11.]])
import numpy as np

t = np.array([[[0, 1, 2],
               [3, 4, 5]],
              [[6, 7, 8],
               [9, 10, 11]]])
ft = torch.FloatTensor(t)
print(ft.shape)
torch.Size([2, 2, 3])

-1은 다른 차원으로부터 맞춰서 변환됩니다.

print(ft.view([-1, 3]))
print(ft.view([-1, 3]).shape)
tensor([[ 0.,  1.,  2.],
        [ 3.,  4.,  5.],
        [ 6.,  7.,  8.],
        [ 9., 10., 11.]])
torch.Size([4, 3])
print(ft.view([-1, 1, 3]))
print(ft.view([-1, 1, 3]).shape)
tensor([[[ 0.,  1.,  2.]],

        [[ 3.,  4.,  5.]],

        [[ 6.,  7.,  8.]],

        [[ 9., 10., 11.]]])
torch.Size([4, 1, 3])
ft = torch.FloatTensor([[0], [1], [2]])
print(ft)
print(ft.shape)
tensor([[0.],
        [1.],
        [2.]])
torch.Size([3, 1])

squeeze를 통해 2차원에서 1차원으로 변형되었습니다.

print(ft.squeeze())
print(ft.squeeze().shape)
tensor([0., 1., 2.])
torch.Size([3])
ft = torch.Tensor([0, 1, 2])
print(ft.shape)
torch.Size([3])

다시 unsqueeze를 통해 2차원으로 변경되었습니다.

print(ft.unsqueeze(0))
print(ft.unsqueeze(0).shape)
tensor([[0., 1., 2.]])
torch.Size([1, 3])
print(ft.view(1, -1))
print(ft.view(1, -1).shape)
tensor([[0., 1., 2.]])
torch.Size([1, 3])
print(ft.unsqueeze(1))
print(ft.unsqueeze(1).shape)
tensor([[0.],
        [1.],
        [2.]])
torch.Size([3, 1])
print(ft.unsqueeze(-1))
print(ft.unsqueeze(-1).shape)
tensor([[0.],
        [1.],
        [2.]])
torch.Size([3, 1])

Transpose는 행과 열을 바꿔주는 방법입니다.

A = torch.arange(6).reshape(3, 2)
A
tensor([[0, 1],
        [2, 3],
        [4, 5]])
A.T
tensor([[0, 2, 4],
        [1, 3, 5]])
A = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
A == A.T
tensor([[True, True, True],
        [True, True, True],
        [True, True, True]])

혁 ) contiguous()에 대해 알려주세요.

contiguous()는 텐서를 numpy같은 방식으로 메모리에 저장하는 방식을 말합니다.
numpy는 인접한 배열의 데이터는 인접한 메모리에 저장함으로써 접근속도나 transpose속도가 매우 빠르게됩니다.
보통 view(),narrow(),expand(),tranpose()를 써서 텐서모양을 고칠때 contiguous형식이 요구되는데
예를 들어 view()같은 경우는 reshape나 resize와는 다르게 어떤 경우에도 메모리 복사없이 이루어집니다.
따라서 contiguous형식이 아닐때는 텐서모양을 고칠수 없게되고 런타임에러가 발생합니다.

코드를 통해서 contiguous에 대해 설명하겠습니다.

import torch

a = torch.randn(3,4)
print(a.is_contiguous())
a.transpose_(0,1)
b = torch.randn(4,3)
print(a)
print(b.is_contiguous())
print(b)
True
tensor([[ 1.0009,  0.1026, -1.1214],
        [ 0.7532,  2.0344,  1.4519],
        [-0.5742, -1.3162,  1.5134],
        [ 1.2349, -1.2356,  1.7235]])
True
tensor([[-0.2183,  2.2920, -0.2232],
        [ 0.0463, -0.4723,  0.8091],
        [-0.7852,  1.6145, -0.2895],
        [ 0.5062,  0.6791,  1.3657]])

랜덤으로 a와 b라는 Tensor를 만들었습니다.
a와 b는 형성되어졌을 때, is_contiguous를 통해서 두 Tensor 모두 contiguous한 것을 알 수 있습니다.
그럼 아래의 코드를 통해 tranpose 이후 a와 b의 차이를 보겠습니다.

for i in range(4):
  for j in range(3):
    print(a[i][j].data_ptr())
  
print('*' * 10)

for i in range(4):
  for j in range(3):
    print(b[i][j].data_ptr())    
1863763599552
1863763599568
1863763599584
1863763599556
1863763599572
1863763599588
1863763599560
1863763599576
1863763599592
1863763599564
1863763599580
1863763599596
**********
1863763604288
1863763604292
1863763604296
1863763604300
1863763604304
1863763604308
1863763604312
1863763604316
1863763604320
1863763604324
1863763604328
1863763604332

각 텐서 요소의 주소를 추출하였습니다.
각 데이터 타입인 torch.float32 자료형은 4바이트 이므로,
메모리 1칸 당 주소 값이 4씩 증가하는 것을 볼 수 있습니다.
b는 한 줄에 4씩 값이 증가하지만 a는 그렇지 않은 것을 확인할 수 있습니다.
아래코드를 보겠습니다.

print(a.stride())
print(b.stride())
print(a.is_contiguous())
print(b.is_contiguous())
(1, 4)
(3, 1)
False
True

a가 tranpose된 이후에 false로 바뀐 것을 알 수 있습니다.
즉 b는 오른쪽 방향으로 자료가 순서대로 저장됨에 비해
a는 tranpose 연산을 거치며 axis = 1인 아래 방향으로 자료가 저장되고 있습니다.
stride메소드를 보면, 데이터의 저장 방향을 알 수 있습니다.
a의 stride 결과값을 해석하자면
a[0][0]->a[1][0]으로 증가할 때는 자료 1개 만큼 메모리 주소가 이동되고,
a[0][0]->a[0][1]으로 증가할 때는 자료 4개 만큼의 메모리 주소가 바뀐다는 의미입니다.
그래서 a의 for문 결과의 처음 3줄에는 메모리가 16씩 증가하는 것을 확인할 수 있습니다.

현재 tranpose 된 a 같은 경우는 contiguous는 False로 자료를 순서대로 저장하고 있지 않습니다.
이상태에서 reshape,resize가 아닌 view를 써보겠습니다.

print(a.is_contiguous())
a.view(2,6)
False



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

RuntimeError                              Traceback (most recent call last)

~\AppData\Local\Temp/ipykernel_26164/2291854614.py in <module>
      1 print(a.is_contiguous())
----> 2 a.view(2,6)


RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.

이렇게 RuntimeError가 발생하는 것을 확인할 수 있습니다.

그럼 a가 view를 사용할 수 있도록 contiguous()를 통해서 바꾸어보겠습니다.

a = a.contiguous()
print(a.is_contiguous())
a.view(2,6)
True





tensor([[ 1.0009,  0.1026, -1.1214,  0.7532,  2.0344,  1.4519],
        [-0.5742, -1.3162,  1.5134,  1.2349, -1.2356,  1.7235]])

현재는 a가 contigious True 형태로 바뀌었고 view가 적용되는 모습을 확인할 수 있습니다.

감사합니다.

Tensor 합치기, 나누기

  • cat, stacking : 텐서를 합칩니다.
  • chunk, split : 텐서를 나눠줍니다.


Tensor 합치기

dim을 통해 합쳐줄 축을 정해줍니다.

cat은 주어진 차원을 기준으로, stack은 새로운 차원으로 주어진 텐서를 붙입니다.

x = torch.FloatTensor([[1, 2], [3, 4]])
y = torch.FloatTensor([[5, 6], [7, 8]])
print(torch.cat([x, y], dim=0))
tensor([[1., 2.],
        [3., 4.],
        [5., 6.],
        [7., 8.]])
print(torch.cat([x, y], dim=1))
tensor([[1., 2., 5., 6.],
        [3., 4., 7., 8.]])
print(torch.stack([x, y]))
tensor([[[1., 2.],
         [3., 4.]],

        [[5., 6.],
         [7., 8.]]])

cat은 (4,2)의 크기를, stack은 (2,2,2)의 크기를 갖는 것을 알수 있습니다.

x = torch.FloatTensor([1, 4])
y = torch.FloatTensor([2, 5])
z = torch.FloatTensor([3, 6])
print(torch.stack([x, y, z]))
tensor([[1., 4.],
        [2., 5.],
        [3., 6.]])
print(torch.cat([x.unsqueeze(0), y.unsqueeze(0), z.unsqueeze(0)], dim=0))
tensor([[1., 4.],
        [2., 5.],
        [3., 6.]])
print(torch.stack([x, y, z], dim=1))
tensor([[1., 2., 3.],
        [4., 5., 6.]])

Tensor 나누기

chunk는 n개의 그룹을 만들고, split은 n개의 데이터가 있는 그룹을 만듭니다.

# 3개의 그룹을 만드는 chunk
tensor = torch.rand(3,6)
print(tensor)

t1, t2, t3 = torch.chunk(tensor, 3, dim=1)

print(t1)
print(t2)
print(t3)
tensor([[0.3124, 0.3670, 0.4461, 0.3799, 0.4759, 0.7645],
        [0.9245, 0.2099, 0.4981, 0.2253, 0.1432, 0.4176],
        [0.3214, 0.4409, 0.4165, 0.0611, 0.4758, 0.6666]])
tensor([[0.3124, 0.3670],
        [0.9245, 0.2099],
        [0.3214, 0.4409]])
tensor([[0.4461, 0.3799],
        [0.4981, 0.2253],
        [0.4165, 0.0611]])
tensor([[0.4759, 0.7645],
        [0.1432, 0.4176],
        [0.4758, 0.6666]])
# 3개의 데이터가 있는 그룹을 만드는 split (2개의 tensor로 분리)
tensor = torch.rand(3,6)
print(tensor)

t1, t2 = torch.split(tensor, 3, dim=1)

print(t1)
print(t2)
tensor([[0.5875, 0.2308, 0.4805, 0.2589, 0.5313, 0.8857],
        [0.9570, 0.9571, 0.2845, 0.7530, 0.4334, 0.5157],
        [0.6430, 0.3617, 0.3446, 0.9501, 0.4462, 0.8290]])
tensor([[0.5875, 0.2308, 0.4805],
        [0.9570, 0.9571, 0.2845],
        [0.6430, 0.3617, 0.3446]])
tensor([[0.2589, 0.5313, 0.8857],
        [0.7530, 0.4334, 0.5157],
        [0.9501, 0.4462, 0.8290]])





3. Tensor 연산하기

Tensor 값들끼리의 연산, Tensor간의 연산을 알아보았습니다.


기본 연산

  • +, -, *, / : 같은 위치끼리 연산
  • sum, cumsum : 합과 누적합
  • mean, std : 통계값
  • exp : 지수합수
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y
(tensor([ 3.,  4.,  6., 10.]),
 tensor([-1.,  0.,  2.,  6.]),
 tensor([ 2.,  4.,  8., 16.]),
 tensor([0.5000, 1.0000, 2.0000, 4.0000]),
 tensor([ 1.,  4., 16., 64.]))
x == y
tensor([False,  True, False, False])
x.sum()
tensor(15.)
torch.exp(X)
tensor([[1.6275e+05, 1.6275e+05, 7.3891e+00, 2.0086e+01],
        [5.4598e+01, 1.4841e+02, 4.0343e+02, 1.0966e+03],
        [2.9810e+03, 8.1031e+03, 2.2026e+04, 5.9874e+04]])
A
tensor([[1, 2, 3],
        [2, 0, 4],
        [3, 4, 5]])

axis를 통해 연산해줄 행, 열을 지정해줍니다.

A.shape, A.sum(axis=0)
(torch.Size([3, 3]), tensor([ 6,  6, 12]))
A.shape, A.sum(axis=1)
(torch.Size([3, 3]), tensor([ 6,  6, 12]))

keepdims를 통해 차원을 유지하며 연산할 수 있습니다.

sum_A = A.sum(axis=1, keepdims=True)
sum_A, sum_A.shape
(tensor([[ 6],
         [ 6],
         [12]]), torch.Size([3, 1]))
A / sum_A
tensor([[0.1667, 0.3333, 0.5000],
        [0.3333, 0.0000, 0.6667],
        [0.2500, 0.3333, 0.4167]])
A.cumsum(axis=0)
tensor([[ 1,  2,  3],
        [ 3,  2,  7],
        [ 6,  6, 12]])

정준 ) in-place 연산에 대해 알려주세요

우선 2X2형태의 텐서를 만들어 변수 x에 저장합니다.

x = torch.FloatTensor([[1, 2], [3, 4]])

곱하기 연산을 한 값과 기존의 값을 출력하면?

print(x.mul(2.)) # 곱하기 2를 수행한 결과를 출력
print(x) # 기존의 값 출력
    tensor([[2., 4.],
            [6., 8.]])
    tensor([[1., 2.],
            [3., 4.]])

첫번째 출력은 곱하기 2가 수행된 결과를 보여주고, 두번째 출력은 기존의 값이 그대로 출력된 것을 확인할 수 있습니다.
곱하기 2를 수행했지만 이를 x에다가 다시 저장하지 않았으니, 곱하기 연산을 하더라도 기존의 값 x는 변하지 않는 것이 당연합니다.

그런데 연산 뒤에 _를 붙이면 어떻게 될까요? 기존의 값을 덮어쓰기 합니다.

print(x.mul_(2.))  # 곱하기 2를 수행한 결과를 변수 x에 값을 저장하면서 결과를 출력
print(x) # 기존의 값 출력
    tensor([[2., 4.],
            [6., 8.]])
    tensor([[2., 4.],
            [6., 8.]])

이렇듯 x의 값이 덮어 씌워져서 2 곱하기 연산이 된 결과가 출력됩니다.
in-place 연산(연산 뒤에 _붙이기)을 사용하면 새로운 변수를 지정하지 않아도 기존 변수의 값이 바뀝니다.

이렇듯 값을 자주 업데이트 해 줘야 할 때 in-place 연산을 사용한다면 편리합니다.
예로 들어, 선형 회귀 구현 시 w와 b의 값들을 업데이트할 때,
in-place 연산을 써주게 된다면, 새로운 변수를 사용하는 것보다 속도도 빠르고 코드도 간편해진다는 장점이 있습니다.

한가지 예시를 더 들어보겠습니다.

import torch

a = torch.ones(5)
print(a)

b = a.numpy()
print(b)

a.add_(1)
print(a)
print(b)
    tensor([1., 1., 1., 1., 1.])
    [1. 1. 1. 1. 1.]
    tensor([2., 2., 2., 2., 2.])
    [2. 2. 2. 2. 2.]

선형대수학

선형대수학에서 사용하는 연산들을 공부했습니다

  • dot : dot product
  • mv, @ : Matrix - Vector product
  • mm, @ : Matrix - Matrix product
x = torch.arange(3, dtype=torch.float32)
y = torch.ones(3, dtype = torch.float32)
x, y, torch.dot(x, y)
(tensor([0., 1., 2.]), tensor([1., 1., 1.]), tensor(3.))
A = torch.arange(6, dtype=torch.float32).reshape(2, 3)

A.shape, x.shape, torch.mv(A, x), A@x
(torch.Size([2, 3]), torch.Size([3]), tensor([ 5., 14.]), tensor([ 5., 14.]))
B = torch.ones(3, 4)

print(torch.mm(A, B)) 
print(A@B)
tensor([[ 3.,  3.,  3.,  3.],
        [12., 12., 12., 12.]])
tensor([[ 3.,  3.,  3.,  3.],
        [12., 12., 12., 12.]])

Norm

벡터의 크기 또는 길이를 측정하는 Norm을 공부하였습니다.
L1 Norm과 L2 Norm의 식도 간단하게 알아보았습니다.

u = torch.tensor([3.0, -4.0])
torch.norm(u) # L2 Norm
tensor(5.)
torch.abs(u).sum() # L1 Norm
tensor(7.)
torch.ones((4, 9))
tensor([[1., 1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1., 1.]])
torch.norm(torch.ones((4, 9)))
tensor(6.)
torch.norm(torch.arange(4,dtype=torch.float32).reshape(2,2))
tensor(3.7417)

Autograd

autograd는 파이토치의 자동 미분 엔진입니다.
미분값(gradient)을 수집하고 저장해줍니다.

  • requires_grad : 연산 추적 여부를 선택할 수 있습니다.
  • backward : 역전파 시작
  • grad.zero : 기울기 초기화
  • x.grad : 저장된 기울기 반환
x = torch.arange(4.0)
x
tensor([0., 1., 2., 3.])
x.requires_grad_(True)
x.grad 
y = 2 * torch.dot(x, x)
y
tensor(28., grad_fn=<MulBackward0>)
y.backward()
x.grad
tensor([ 0.,  4.,  8., 12.])
x.grad == 4 * x
tensor([True, True, True, True])
x.grad.zero_()  # Reset the gradient
y = x.sum()
y.backward()
x.grad
tensor([1., 1., 1., 1.])
x.grad.zero_()
y = x * x
y.backward(gradient=torch.ones(len(y)))  # Faster: y.sum().backward()
x.grad
tensor([0., 2., 4., 6.])
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x

z.sum().backward()
x.grad == u
tensor([True, True, True, True])

연수 ) detach(), clone() 에 대해 알려주세요.

Tensor를 복사하는 방법

1. detach() : 기존 Tensor에서 gradient 전파가 안되는 텐서 생성
2. clone() : 기존 Tensor와 내용을 복사한 텐서 생성
import torch
a = torch.randn(5)
a
tensor([-0.0803, -0.1637, -0.0640, -0.4010, -0.9398])
b = a.detach()
c = a.clone()
print(b,c)
tensor([-0.0803, -0.1637, -0.0640, -0.4010, -0.9398]) tensor([-0.0803, -0.1637, -0.0640, -0.4010, -0.9398])
b[0] = 1
a
tensor([ 1.0000, -0.1637, -0.0640, -0.4010, -0.9398])

b를 변경하자 a도 변한것을 알 수 있다.

c[0] = 2
print(c, a)
tensor([ 2.0000, -0.1637, -0.0640, -0.4010, -0.9398]) tensor([ 1.0000, -0.1637, -0.0640, -0.4010, -0.9398])

clone()한 tensor는 값이 변경되어도 원래 tensor 값이 변하지 않는다






4. DataFrame을 Tensor로 만들기

torch.tensor을 통해 데이터프레임 형태도 Tensor로 만들어줄 수 있습니다.

import os

os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tiny.csv')
with open(data_file, 'w') as f:
    f.write('''NumRooms,RoofType,Price
NA,NA,127500
2,NA,106000
4,Slate,178100
NA,NA,140000''')
import pandas as pd

data = pd.read_csv(data_file)
print(data)
   NumRooms RoofType   Price
0       NaN      NaN  127500
1       2.0      NaN  106000
2       4.0    Slate  178100
3       NaN      NaN  140000
inputs, targets = data.iloc[:, 0:2], data.iloc[:, 2]
inputs = pd.get_dummies(inputs, dummy_na=True)
print(inputs)
   NumRooms  RoofType_Slate  RoofType_nan
0       NaN               0             1
1       2.0               0             1
2       4.0               1             0
3       NaN               0             1
inputs = inputs.fillna(inputs.mean())
print(inputs)
   NumRooms  RoofType_Slate  RoofType_nan
0       3.0               0             1
1       2.0               0             1
2       4.0               1             0
3       3.0               0             1
X, y = torch.tensor(inputs.values), torch.tensor(targets.values)
X, y
(tensor([[3., 0., 1.],
         [2., 0., 1.],
         [4., 1., 0.],
         [3., 0., 1.]], dtype=torch.float64),
 tensor([127500, 106000, 178100, 140000]))

재영 ) Array - Tensor 간 변환 방법을 알려주세요.

Pytorch에서 numpy array를 Tensor 자료형으로 바꾸기 위해서는 torch.Tensor()torch.from_numpy()를 활용할 수 있습니다.

먼저, torch.Tensor()pytorch 공식 문서에서 확인해본다면 말그대로 Tensor를 만들어내는 구문이기 때문에 나와 있는 예시에는 array가 직접 들어가 있는 경우를 많이 확인할 수 있습니다.

그리고 아래와 같이 생성되는 Tensor의 데이터 타입마다 어떠한 구문을 써야하는지 나타나 있습니다. 아래의 이미지는 그 중 일부를 발췌한 것입니다.

또한, torch.from_numpy()pytorch 공식 문서에서 확인해본다면 Creates a Tensor from a numpy.ndarray 라고 설명되어 있는 것을 확인할 수 있습니다. 말 그대로 numpy로 만들어진 array를 tensor로 만들어주는 역할을 하게 됩니다.

다만, 주의해야할 점은 만들어진 tensor는 기존의 ndarray와 동일한 메모리를 공유한다는 점입니다. 그렇기에 tensor에 대한 수정 사항이 ndarray에 반영되고, 그 반대의 경우 또한 적용된다는 점을 고려해야 합니다.

또한, 그로 인해 만들어진 tensor의 크기는 그 이후 조정할 수 없습니다. 이와 관련된 공식 문서의 설명은 아래와 같습니다.

그렇다면 정말 작동하는지 확인해봅시다.

먼저, torch.Tensor() 함수를 활용하여 numpy array를 Tensor로 바꾸어봅시다.

import numpy as np
import torch

arr = np.array([1,2,3,4,5])
print(type(arr))

arr_tensor = torch.Tensor(arr)
print(type(arr_tensor))

아래와 같이 해당하는 type이 변경되는 것을 쉽게 확인할 수 있습니다.

다음으로는 torch.from_numpy() 함수를 활용하여 numpy array를 Tensor로 바꾸어봅시다.

import numpy as np
import torch

arr2 = np.array([2,3,4])
print(type(arr2))

arr_tensor2 = torch.from_numpy(arr2)
print(type(arr_tensor2))

아래와 같이 해당하는 type이 변환되는 것을 확인할 수 있습니다.

이와 반대로 Pytorch에서 tensor에서 numpy로 변경하기 위해서는 torch.Tensor.numpy() 구문을 활용할 수 있습니다. 이를 pytorch 공식문서에서 확인해보면 아래와 같이 간결하게 역할을 확인할 수 있습니다.

앞선 torch.from_numpy() 와 마찬가지로 torch.Tensor.numpy() 또한 각 tensor와 ndarray는 같은 메모리를 공유하게 됩니다.

이제는 numpy() 를 활용하여 변환된 Tensor를 numpy array로 바꾸어봅시다.

import numpy as np
import torch

arr_numpy = arr_tensor.numpy()
print(type(arr_numpy))

아래와 같이 numpy.ndarray 타입으로 변환된 것을 확인할 수 있습니다.





5. 실습

오늘 배운 내용을 직접 타이핑해보는 시간도 가졌습니다!
시간이 여유롭지않아 완벽하게는 못했지만 다음번에는 더 나은 실습들로 마련해볼게요 💯

1. (2,3) 형태의 0~1 사이의 무작위 값으로 이루어진 텐서를 생성해보세요.

x = torch.randn(2, 3)
x
tensor([[-1.1449e+00, -5.8245e-01, -4.2747e-01],
        [ 2.1382e-05, -2.5037e-01,  1.3060e-01]])

2. 위 텐서의 타입을 출력해보세요.(dtype)

x.dtype
torch.float32

3. 1부터 3까지 순서대로 이루어진 int type의 텐서를 생성해보세요.

y = torch.arange(1,4, dtype=torch.int64)
y
tensor([1, 2, 3])

4. 첫번째 생성한 텐서와 두번째 생성한 텐서의 곱을 하고 연산이 되는 이유는 무엇인가요?

x*y
tensor([[-1.1449e+00, -1.1649e+00, -1.2824e+00],
        [ 2.1382e-05, -5.0074e-01,  3.9180e-01]])

5. 첫번째 생성한 텐서를 Transpose하고 첫번째 생성한 텐서와 행렬곱을 진행해보세요

z = x.T
z
tensor([[-1.1449e+00,  2.1382e-05],
        [-5.8245e-01, -2.5037e-01],
        [-4.2747e-01,  1.3060e-01]])
z@x
tensor([[1.3107, 0.6668, 0.4894],
        [0.6668, 0.4019, 0.2163],
        [0.4894, 0.2163, 0.1998]])

6. 행렬곱을 진행한 텐서와 첫번째 생성한 텐서를 Concatenate하세요

x
tensor([[-1.1449e+00, -5.8245e-01, -4.2747e-01],
        [ 2.1382e-05, -2.5037e-01,  1.3060e-01]])
print(torch.cat([z@x, x], dim=0))
tensor([[ 1.3107e+00,  6.6682e-01,  4.8940e-01],
        [ 6.6682e-01,  4.0193e-01,  2.1628e-01],
        [ 4.8940e-01,  2.1628e-01,  1.9979e-01],
        [-1.1449e+00, -5.8245e-01, -4.2747e-01],
        [ 2.1382e-05, -2.5037e-01,  1.3060e-01]])

7. 두번째에 만든 텐서를 unsqueeze하고 shape를 출력하세요 (dim=1)

print(y.unsqueeze(1))
print(y.unsqueeze(1).shape)
tensor([[1],
        [2],
        [3]])
torch.Size([3, 1])

8. unsqueeze한 텐서를 squeeze하세요 shape를 출력하세요 (dim=1)

k = y.unsqueeze(1)
print(k.squeeze(1))
print(k.squeeze(1).shape)
tensor([1, 2, 3])
torch.Size([3])

9. 0부터 5의 숫자로 이루어진 (2,3)형태의 텐서고 Norm을 구해주세요

A = torch.arange(6, dtype=torch.float32).reshape(2, 3)
A
tensor([[0., 1., 2.],
        [3., 4., 5.]])
torch.norm(A)
tensor(7.4162)

10. 해당 코드가 돌아가도록 알맞은 파라미터를 추가해주세요!

import torch

x = torch.ones(5)  # input tensor
y = torch.zeros(3)  # expected output
w = torch.randn(5, 3,requires_grad=True)
b = torch.randn(3,requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)
print(f"Gradient function for z = {z.grad_fn}")
print(f"Gradient function for loss = {loss.grad_fn}")
Gradient function for z = <AddBackward0 object at 0x7f5523e0a210>
Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x7f5523e0a5d0>
loss.backward()
print(w.grad)
print(b.grad)
tensor([[0.0139, 0.3273, 0.0794],
        [0.0139, 0.3273, 0.0794],
        [0.0139, 0.3273, 0.0794],
        [0.0139, 0.3273, 0.0794],
        [0.0139, 0.3273, 0.0794]])
tensor([0.0139, 0.3273, 0.0794])

11. 아래 False가 나온 결과와 같은 결과가 나오도록 코드를 완성해주세요.

z = torch.matmul(x, w)+b
print(z.requires_grad)

with torch.no_grad():
    z = torch.matmul(x, w)+b
print(z.requires_grad)
True
False
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
False

12. 코랩 Sample data의 california_housing_train에서 median_house_value를 target으로 하고 Tensor형태로 변환해보세요

import pandas as pd

data = pd.read_csv('/content/sample_data/california_housing_train.csv')
print(data)
       longitude  latitude  housing_median_age  total_rooms  total_bedrooms  \
0        -114.31     34.19                15.0       5612.0          1283.0   
1        -114.47     34.40                19.0       7650.0          1901.0   
2        -114.56     33.69                17.0        720.0           174.0   
3        -114.57     33.64                14.0       1501.0           337.0   
4        -114.57     33.57                20.0       1454.0           326.0   
...          ...       ...                 ...          ...             ...   
16995    -124.26     40.58                52.0       2217.0           394.0   
16996    -124.27     40.69                36.0       2349.0           528.0   
16997    -124.30     41.84                17.0       2677.0           531.0   
16998    -124.30     41.80                19.0       2672.0           552.0   
16999    -124.35     40.54                52.0       1820.0           300.0   

       population  households  median_income  median_house_value  
0          1015.0       472.0         1.4936             66900.0  
1          1129.0       463.0         1.8200             80100.0  
2           333.0       117.0         1.6509             85700.0  
3           515.0       226.0         3.1917             73400.0  
4           624.0       262.0         1.9250             65500.0  
...           ...         ...            ...                 ...  
16995       907.0       369.0         2.3571            111400.0  
16996      1194.0       465.0         2.5179             79000.0  
16997      1244.0       456.0         3.0313            103600.0  
16998      1298.0       478.0         1.9797             85800.0  
16999       806.0       270.0         3.0147             94600.0  

[17000 rows x 9 columns]
data.columns
Index(['longitude', 'latitude', 'housing_median_age', 'total_rooms',
       'total_bedrooms', 'population', 'households', 'median_income',
       'median_house_value'],
      dtype='object')
inputs, targets = data.iloc[:, 0:-1], data.iloc[:, -1]
inputs = pd.get_dummies(inputs, dummy_na=True)
print(inputs.head())
   longitude  latitude  housing_median_age  total_rooms  total_bedrooms  \
0    -114.31     34.19                15.0       5612.0          1283.0   
1    -114.47     34.40                19.0       7650.0          1901.0   
2    -114.56     33.69                17.0        720.0           174.0   
3    -114.57     33.64                14.0       1501.0           337.0   
4    -114.57     33.57                20.0       1454.0           326.0   

   population  households  median_income  
0      1015.0       472.0         1.4936  
1      1129.0       463.0         1.8200  
2       333.0       117.0         1.6509  
3       515.0       226.0         3.1917  
4       624.0       262.0         1.9250  
inputs = inputs.fillna(inputs.mean())
print(inputs.head())
   longitude  latitude  housing_median_age  total_rooms  total_bedrooms  \
0    -114.31     34.19                15.0       5612.0          1283.0   
1    -114.47     34.40                19.0       7650.0          1901.0   
2    -114.56     33.69                17.0        720.0           174.0   
3    -114.57     33.64                14.0       1501.0           337.0   
4    -114.57     33.57                20.0       1454.0           326.0   

   population  households  median_income  
0      1015.0       472.0         1.4936  
1      1129.0       463.0         1.8200  
2       333.0       117.0         1.6509  
3       515.0       226.0         3.1917  
4       624.0       262.0         1.9250  
X, y = torch.tensor(inputs.values), torch.tensor(targets.values)
X, y
(tensor([[-114.3100,   34.1900,   15.0000,  ..., 1015.0000,  472.0000,
             1.4936],
         [-114.4700,   34.4000,   19.0000,  ..., 1129.0000,  463.0000,
             1.8200],
         [-114.5600,   33.6900,   17.0000,  ...,  333.0000,  117.0000,
             1.6509],
         ...,
         [-124.3000,   41.8400,   17.0000,  ..., 1244.0000,  456.0000,
             3.0313],
         [-124.3000,   41.8000,   19.0000,  ..., 1298.0000,  478.0000,
             1.9797],
         [-124.3500,   40.5400,   52.0000,  ...,  806.0000,  270.0000,
             3.0147]], dtype=torch.float64),
 tensor([ 66900.,  80100.,  85700.,  ..., 103600.,  85800.,  94600.],
        dtype=torch.float64))





6. 마치며

너무 많은 내용을 했었던 2주차였습니다!!
Tensor를 어떤 식으로 만지고 변경할 수 있는지 알게되는 2주차였으면 좋겠습니다!