jeongabae / PyTorch_Study

논문을 위한 파이토치 기초 정리.
0 stars 0 forks source link

모음 #1

Open jeongabae opened 2 years ago

jeongabae commented 2 years ago

2_2_2

import numpy as np
t = np.array([0., 1., 2., 3., 4., 5., 6.])
# 파이썬으로 설명하면 List를 생성해서 np.array로 1차원 array로 변환함.

print(t) #[0. 1. 2. 3. 4. 5. 6.]
print('Rank of t: ', t.ndim) #.ndim은 몇 차원인지를 출력합니다. 1차원은 벡터, 2차원은 행렬, 3차원은 3차원 텐서. 현재는 벡터이므로 1차원이 출력.
print('Shape of t: ', t.shape) # .shape는 크기를 출력합니다. (7, )는 (1, 7)을 의미합니다. 다시 말해 (1 × 7)의 크기를 가지는 벡터
"""
Rank of t:  1
Shape of t:  (7,)
"""
print('t[0] t[1] t[-1] = ', t[0], t[1], t[-1]) # 인덱스를 통한 원소 접근
print('t[2:5] t[4:-1]  = ', t[2:5], t[4:-1]) # [시작 번호 : 끝 번호]로 범위 지정을 통해 가져온다.

#1-1) Numpy 기초 이해하기
print('t[0] t[1] t[-1] = ', t[0], t[1], t[-1]) # 인덱스를 통한 원소 접근 #t[0] t[1] t[-1] =  0.0 1.0 6.0
print('t[2:5] t[4:-1]  = ', t[2:5], t[4:-1]) # [시작 번호 : 끝 번호]로 범위 지정을 통해 가져온다.
                                             # t[2:5] t[4:-1]  =  [2. 3. 4.] [4. 5.]
print('t[:2] t[3:]     = ', t[:2], t[3:]) # 시작 번호를 생략한 경우와 끝 번호를 생략한 경우#t[:2] t[3:]     =  [0. 1.] [3. 4. 5. 6.]

#2) 2D with Numpy
t = np.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.], [10., 11., 12.]])
print(t)
"""
[[ 1.  2.  3.]
 [ 4.  5.  6.]
 [ 7.  8.  9.]
 [10. 11. 12.]]
"""
print('Rank  of t: ', t.ndim) #.ndim은 몇 차원인지를 출력. 현재는 행렬이므로 2차원이 출력
print('Shape of t: ', t.shape)  #.shape는 크기를 출력합니다. (4, 3)입니다. 다른 표현으로는 (4 × 3)입니다. 이는 행렬이 4행 3열
"""
Rank  of t:  2
Shape of t:  (4, 3)
"""
jeongabae commented 2 years ago

2_2_3_PyTorch Tensor Allocation.py

import torch
t = torch.FloatTensor([0., 1., 2., 3., 4., 5., 6.])
print(t)#tensor([0., 1., 2., 3., 4., 5., 6.])

print(t.dim())  # rank. 즉, 차원
#shape나 size()를 사용하면 크기를 확인 가능
print(t.shape)  # shape
print(t.size()) # size
"""
1
torch.Size([7])
torch.Size([7])
"""

print(t[0], t[1], t[-1])  # 인덱스로 접근
print(t[2:5], t[4:-1])    # 슬라이싱
print(t[:2], t[3:])       # 슬라이싱
"""
tensor(0.) tensor(1.) tensor(6.)
tensor([2., 3., 4.]) tensor([4., 5.])
tensor([0., 1.]) tensor([3., 4., 5., 6.])
"""

#2) 2D with PyTorch
t = torch.FloatTensor([[1., 2., 3.],
                       [4., 5., 6.],
                       [7., 8., 9.],
                       [10., 11., 12.]
                      ])
print(t)
print(t.dim())  # rank. 즉, 차원 #2
print(t.size()) # shape #torch.Size([4, 3])

print(t[:, 1]) # 첫번째 차원을 전체 선택한 상황에서 두번째 차원의 첫번째 것만 가져온다.
print(t[:, 1].size()) # ↑ 위의 경우의 크기
"""
tensor([ 2.,  5.,  8., 11.])
torch.Size([4])
"""

print(t[:, :-1]) # 첫번째 차원을 전체 선택한 상황에서 두번째 차원에서는 맨 마지막에서 첫번째를 제외하고 다 가져온다.
"""
tensor([[ 1.,  2.],
        [ 4.,  5.],
        [ 7.,  8.],
        [10., 11.]])
"""

#3)브로드캐스팅(Broadcasting)
m1 = torch.FloatTensor([[3, 3]])
m2 = torch.FloatTensor([[2, 2]])
print(m1 + m2) #tensor([[5., 5.]])

# Vector + scalar
"""
 크기가 다른 텐서들 간의 연산을 보겠습니다. 
 아래는 벡터와 스칼라가 덧셈 연산을 수행하는 것을 보여줍니다. 
 물론, 수학적으로는 원래 연산이 안 되는게 맞지만 파이토치에서는 브로드캐스팅을 통해 이를 연산합니다.
"""
m1 = torch.FloatTensor([[1, 2]])
m2 = torch.FloatTensor([3]) # [3] -> [3, 3]
print(m1 + m2) #tensor([[4., 5.]])

# 2 x 1 Vector + 1 x 2 Vector : 벡터 간 연산에서 브로드캐스팅이 적용되는 경우
#원래 m1의 크기는 (1, 2)이며 m2의 크기는 (1,)
m1 = torch.FloatTensor([[1, 2]])
m2 = torch.FloatTensor([[3], [4]]) #m2의 크기는 (1,). 그런데 파이토치는 m2의 크기를 (1, 2)로 변경하여 연산을 수행
"""
# 브로드캐스팅 과정에서 실제로 두 텐서가 아래와 같이 변경됨.
[1, 2]
==> [[1, 2],
     [1, 2]]
[3]
[4]
==> [[3, 3],
     [4, 4]]
"""
print(m1 + m2)
"""
tensor([4., 5.],
       [5., 6.]])
"""

#4) 자주 사용되는 기능들
#4-1) 행렬 곱셈과 곱셈의 차이(Matrix Multiplication Vs. Multiplication) :  행렬 곱셈(.matmul)과 원소 별 곱셈(.mul)
m1 = torch.FloatTensor([[1, 2], [3, 4]])
m2 = torch.FloatTensor([[1], [2]])
print('Shape of Matrix 1: ', m1.shape) # 2 x 2
print('Shape of Matrix 2: ', m2.shape) # 2 x 1
"""
[1, 2]   [1]
       *
[3, 4]   [2]
행렬곱은 [1*1+2*2]
        [3*1+4*2]
"""
print(m1.matmul(m2)) # 2 x 1
"""
Shape of Matrix 1:  torch.Size([2, 2])
Shape of Matrix 2:  torch.Size([2, 1])
tensor([[ 5.],
        [11.]])

"""

#element-wise 곱셈 : 동일한 크기의 행렬이 동일한 위치에 있는 원소끼리 곱하는 것
m1 = torch.FloatTensor([[1, 2], [3, 4]])
m2 = torch.FloatTensor([[1], [2]])
print('Shape of Matrix 1: ', m1.shape) # 2 x 2
print('Shape of Matrix 2: ', m2.shape) # 2 x 1
"""
[1, 2]   [1]
       *
[3, 4]   [2]
여기서
# 브로드캐스팅 과정에서 m2 텐서가 아래와 같이 변경
[1]
[2]
==> [[1, 1],
     [2, 2]]
이 되므로,
[1*1,2*1]
[3*2,4*2]
"""
print(m1 * m2) # 2 x 2
print(m1.mul(m2))
"""
Shape of Matrix 1:  torch.Size([2, 2])
Shape of Matrix 2:  torch.Size([2, 1])
tensor([[1., 2.],
        [6., 8.]])
tensor([[1., 2.],
        [6., 8.]])
"""

#4-2) 평균(Mean)
t = torch.FloatTensor([1, 2])
print(t.mean()) #tensor(1.5000)

t = torch.FloatTensor([[1, 2], [3, 4]])
print(t)
print(t.mean())#tensor(2.5000) #4개의 원소의 평균인 2.5

#dim=0이라는 것은 첫번째 차원을 의미. 행렬에서 첫번째 차원은 '행'을 의미
print(t.mean(dim=0)) #tensor([2., 3.])
"""
인자로 dim을 준다면 해당 차원을 제거한다는 의미.
다시 말해 행렬에서 '열'만을 남기겠다는 의미.
기존 행렬의 크기는 (2, 2)였지만 이를 수행하면 열의 차원만 보존되면서 (1, 2)가 됨. 이는 (2,)와 같으며 벡터.
열의 차원을 보존하면서 평균을 구하면 아래와 같이 연산합니다.
"""
"""
# 실제 연산 과정
t.mean(dim=0)은 입력에서 첫번째 차원을 제거한다.

[[1., 2.],
 [3., 4.]]

1과 3의 평균을 구하고, 2와 4의 평균을 구한다.
결과 ==> [2., 3.]
"""

#인자로 dim=1을. 이번에는 두번째 차원을 제거
print(t.mean(dim=1))  #tensor([1.5000, 3.5000])
"""
[[1., 2.],
 [3., 4.]]

1과 3의 평균을 구하고, 2와 4의 평균을 구한다.
결과 ==> [2., 3.]

"""
#인자로 dim=-1을. 이번에는 마지막 차원을 제거
#결국 열의 차원을 제거한다는 의미와 같다. 그러므로 위와 출력 결과가 같다.
print(t.mean(dim=-1))  #tensor([1.5000, 3.5000])

#4-3) 덧셈(Sum)
t = torch.FloatTensor([[1, 2], [3, 4]])
print(t)
"""
tensor([[1., 2.],
        [3., 4.]])
"""
print(t.sum()) # 단순히 원소 전체의 덧셈을 수행 #tensor(10.)
print(t.sum(dim=0)) # 행을 제거 #tensor([4., 6.])
print(t.sum(dim=1)) # 열을 제거 #tensor([3., 7.])
print(t.sum(dim=-1)) # 열을 제거 #tensor([3., 7.])

#4-4) 최대(Max)와 아그맥스(ArgMax)
#최대(Max)는 원소의 최대값을 리턴하고, 아그맥스(ArgMax)는 최대값을 가진 인덱스를 리턴합니다.
t = torch.FloatTensor([[1, 2], [3, 4]])
print(t)
"""
tensor([[1., 2.],
        [3., 4.]])
"""
print(t.max()) # Returns one value: max #tensor(4.)

print(t.max(dim=0)) # Returns two values: max and argmax #(tensor([3., 4.]), tensor([1, 1]))
"""
행의 차원을 제거한다는 의미이므로 (1, 2) 텐서를 만듭니다. 결과는 [3, 4]입니다.

그런데 [1, 1]이라는 값도 함께 리턴되었습니다. 
max에 dim 인자를 주면 argmax도 함께 리턴하는 특징 때문입니다. 
첫번째 열에서 3의 인덱스는 1이었습니다. 두번째 열에서 4의 인덱스는 1이었습니다.
그러므로 [1, 1]이 리턴됩니다. 어떤 의미인지는 아래 설명해봤습니다.
"""
"""
# [1, 1]가 무슨 의미인지 봅시다. 기존 행렬을 다시 상기해봅시다.
[[1, 2],
 [3, 4]]
첫번째 열에서 0번 인덱스는 1, 1번 인덱스는 3입니다.
두번째 열에서 0번 인덱스는 2, 1번 인덱스는 4입니다.
다시 말해 3과 4의 인덱스는 [1, 1]입니다.
"""

print('Max: ', t.max(dim=0)[0]) #Max:  tensor([3., 4.])
print('Argmax: ', t.max(dim=0)[1]) #Argmax:  tensor([1, 1])

print(t.max(dim=1)) #(tensor([2., 4.]), tensor([1, 1]))
print(t.max(dim=-1)) #tensor([1, 1]))
jeongabae commented 2 years ago

2_3_TensorManipulation2.py

import numpy as np
import torch
"""
view(), squeeze(), unsqueeze()는 텐서의 원소 수를 그대로 유지하면서 모양과 차원을 조절합니다.
"""
#4) 뷰(View) - 원소의 수를 유지하면서 텐서의 크기 변경. 매우 중요함!!
#파이토치 텐서의 뷰(View)는 넘파이에서의 리쉐이프(Reshape)와 같은 역할을 합니다.
"""
view는 기본적으로 변경 전과 변경 후의 텐서 안의 원소의 개수가 유지되어야 합니다.
파이토치의 view는 사이즈가 -1로 설정되면 다른 차원으로부터 해당 값을 유추합니다.
"""
# Reshape라는 이름에서 알 수 있듯이, 텐서의 크기(Shape)를 변경해주는 역할
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])

#4-1) 3차원 텐서에서 2차원 텐서로 변경
print(ft.view([-1, 3])) # ft라는 텐서를 (?, 3)의 크기로 변경 #-1은 첫번째 차원은 사용자가 잘 모르겠으니 파이토치에 맡기겠다는 의미
                        #3은 두번째 차원의 길이는 3을 가지도록 하라는 의미
                        #내부적으로 크기 변환은 다음과 같이 이루어짐. (2, 2, 3) -> (2 × 2, 3) -> (4, 3)
"""
tensor([[ 0.,  1.,  2.],
        [ 3.,  4.,  5.],
        [ 6.,  7.,  8.],
        [ 9., 10., 11.]])
"""
print(ft.view([-1, 3]).shape) #torch.Size([4, 3])

""" 규칙 정리
view는 기본적으로 변경 전과 변경 후의 텐서 안의 원소의 개수가 유지되어야 합니다.
파이토치의 view는 사이즈가 -1로 설정되면 다른 차원으로부터 해당 값을 유추합니다.
"""

#4-2) 3차원 텐서의 크기 변경

#아래의 예에서 (2 × 2 × 3) = (? × 1 × 3) = 12를 만족해야 하므로 ?는 4가 됩니다.
print(ft.view([-1, 1, 3]))
"""
tensor([[[ 0.,  1.,  2.]],

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

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

        [[ 9., 10., 11.]]])
"""
print(ft.view([-1, 1, 3]).shape) #torch.Size([4, 1, 3])

#5) 스퀴즈(Squeeze) - 1인 차원을 제거한다.
"""
스퀴즈는 차원이 1인 경우에는 해당 차원을 제거합니다.
실습을 위해 임의로 (3 × 1)의 크기를 가지는 2차원 텐서를 만들겠습니다.
"""
ft = torch.FloatTensor([[0], [1], [2]])
print(ft)
"""
tensor([[0.],
        [1.],
        [2.]])
"""
print(ft.shape)#torch.Size([3, 1])

#스퀴즈(Squeeze) - 1인 차원을 제거한다.
print(ft.squeeze()) #tensor([0., 1., 2.]) #두번째 차원이 1이므로 squeeze를 사용하면 (3,)의 크기를 가지는 텐서로 변경
print(ft.squeeze().shape) #torch.Size([3]) # 1이었던 두번째 차원이 제거되면서 (3,)의 크기를 가지는 텐서로 변경되어 1차원 벡터가 된 것

#6) 언스퀴즈(Unsqueeze) - 특정 위치에 1인 차원을 추가한다.
ft = torch.Tensor([0, 1, 2])
print(ft.shape) #torch.Size([3])

#현재는 차원이 1개인 1차원 벡터입니다. 여기에 첫번째 차원에 1인 차원을 추가해보겠습니다.
# 첫번째 차원의 인덱스를 의미하는 숫자 0을 인자로 넣으면 첫번째 차원에 1인 차원이 추가됩니다.
print(ft.unsqueeze(0)) #tensor([[0., 1., 2.]]) # 인덱스가 0부터 시작하므로 0은 첫번째 차원을 의미한다.
print(ft.unsqueeze(0).shape) #torch.Size([1, 3])
"""뷰로도 위와 같은 연산 가능
print(ft.view(1, -1)) #tensor([[0., 1., 2.]])
print(ft.view(1, -1).shape) #torch.Size([1, 3])
"""

print(ft.unsqueeze(1)) #unsqueeze의 인자로 1을 넣어보겠습니다. 인덱스는 0부터 시작하므로 이는 두번째 차원에 1을 추가하겠다는 것을 의미
print(ft.unsqueeze(1).shape)
"""
tensor([[0.],
        [1.],
        [2.]])
torch.Size([3, 1]) #현재 크기는 (3,)이었으므로 두번째 차원에 1인 차원을 추가하면 (3, 1)의 크기를 가지게 됩니다. 
"""

print(ft.unsqueeze(-1)) #-1은 인덱스 상으로 마지막 차원을 의미합니다. 현재 크기는 (3,)
print(ft.unsqueeze(-1).shape)
"""
tensor([[0.],
        [1.],
        [2.]])
torch.Size([3, 1]) #마지막 차원에 1인 차원을 추가하면 (3, 1)의 크기를 가지게 됩니다. 
"""

#7) 타입 캐스팅(Type Casting) : 자료형을 변환하는 것
lt = torch.LongTensor([1, 2, 3, 4]) #long 타입의 lt라는 텐서를 선언
print(lt) #tensor([1, 2, 3, 4])
print(lt.float())#텐서에다가 .float()를 붙이면 바로 float형으로 타입이 변경됨 #tensor([1., 2., 3., 4.])

bt = torch.ByteTensor([True, False, False, True]) # Byte 타입의 bt라는 텐서
print(bt) #tensor([1, 0, 0, 1], dtype=torch.uint8)
print(bt.long()) #tensor([1, 0, 0, 1])
print(bt.float()) #tensor([1., 0., 0., 1.])

#8) 두 텐서 연결하기(concatenate)

# 1. (2 × 2) 크기의 텐서를 두 개 만듭니다.
x = torch.FloatTensor([[1, 2], [3, 4]])
y = torch.FloatTensor([[5, 6], [7, 8]])

#2. 두 텐서를 torch.cat([ ])를 통해 연결
print(torch.cat([x, y], dim=0)) #dim=0은 첫번째 차원을 늘리라는 의미
"""dim=0을 인자로 했더니 두 개의 (2 × 2) 텐서가 (4 × 2) 텐서가 된 것을 볼 수 있습니다.
tensor([[1., 2.],
        [3., 4.],
        [5., 6.],
        [7., 8.]])
"""

print(torch.cat([x, y], dim=1))
"""dim=1을 인자로 했더니 두 개의 (2 × 2) 텐서가 (2 × 4) 텐서가 된 것을 볼 수 있습니다.
tensor([[1., 2., 5., 6.],
        [3., 4., 7., 8.]])
"""

#9) 스택킹(Stacking) - 연결(concatenate)을 하는 또 다른 방법

#크기가 (2,)로 모두 동일한 3개의 벡터
x = torch.FloatTensor([1, 4])
y = torch.FloatTensor([2, 5])
z = torch.FloatTensor([3, 6])

# torch.stack을 통해서 3개의 벡터를 모두 스택킹
print(torch.stack([x, y, z])) #print(torch.cat([x.unsqueeze(0), y.unsqueeze(0), z.unsqueeze(0)], dim=0))와 같음
"""3개의 벡터가 순차적으로 쌓여 (3 × 2) 텐서가 된 것
tensor([[1., 4.],
        [2., 5.],
        [3., 6.]])
"""

#스택킹에 추가적으로 dim을 인자로 줄 수도 있습니다. 이번에는 dim=1 인자를 주겠습니다.
#이는 두번째 차원이 증가하도록 쌓으라는 의미로 해석할 수 있습니다.
print(torch.stack([x, y, z], dim=1))
"""
tensor([[1., 2., 3.],
        [4., 5., 6.]])
"""

#10) ones_like와 zeros_like - 0으로 채워진 텐서와 1로 채워진 텐서
x = torch.FloatTensor([[0, 1, 2], [2, 1, 0]])
print(x)
"""
tensor([[0., 1., 2.],
        [2., 1., 0.]])
"""
print(torch.ones_like(x)) # 입력 텐서와 크기를 동일하게 하면서 값을 1로 채우기
"""
tensor([[1., 1., 1.],
        [1., 1., 1.]])
"""
print(torch.zeros_like(x)) # 입력 텐서와 크기를 동일하게 하면서 값을 0으로 채우기
"""
tensor([[0., 0., 0.],
        [0., 0., 0.]])
"""

#11) In-place Operation (덮어쓰기 연산) -  연산 뒤에 _를 붙이면 기존의 값을 덮어쓰기 합니다.
x = torch.FloatTensor([[1, 2], [3, 4]])
print(x.mul(2.)) # 곱하기 2를 수행한 결과를 출력
"""
tensor([[2., 4.],
        [6., 8.]])
"""
print(x) # 기존의 값 출력
"""
tensor([[1., 2.],
        [3., 4.]])
"""

#그런데 연산 뒤에 _를 붙이면 기존의 값을 덮어쓰기 합니다.
print(x.mul_(2.))  # 곱하기 2를 수행한 결과를 변수 x에 값을 저장하면서 결과를 출력
print(x) # 기존의 값 출력
"""
tensor([[2., 4.],
        [6., 8.]])
tensor([[2., 4.],
        [6., 8.]])
"""
jeongabae commented 2 years ago

2_4_1,2_PythonClass.py

#https://wikidocs.net/28
#함수(function)과 클래스(Class)의 차이
#1. 함수(function)로 덧셈기 구현하기
result = 0
def add(num):
    global result
    result += num
    return result

print(add(3)) #3
print(add(4)) #7

#2. 함수(function)로 두 개의 덧셈기 구현하기
result1 = 0
result2 = 0

def add1(num):
    global result1
    result1 += num
    return result1

def add2(num):
    global result2
    result2 += num
    return result2

print(add1(3))#3
print(add1(4))#7
print(add2(3))#3
print(add2(7))#10

#서로의 값에 영향을 주지않고 서로 다른 연산을 하고 있음을 볼 수 있습니다. 그렇다면 이런 두 개의 덧셈기를 클래스로 만들면 어떻게 될까요?
jeongabae commented 2 years ago

2_4_3_PythonClass.py

class Calculator:
    def __init__(self): # 객체 생성 시 호출될 때 실행되는 초기화 함수. 이를 생성자라고 한다.
        self.result = 0

    def add(self, num): # 객체 생성 후 사용할 수 있는 함수.
        self.result += num
        return self.result

cal1 = Calculator()
cal2 = Calculator()
print(cal1.add(3))
print(cal1.add(4))
print(cal2.add(3))
print(cal2.add(7))
"""출력
3
7
3
10
"""
jeongabae commented 2 years ago

3_1_4_Implement_LinearRegression_With_PyTorch.py

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

# 현재 실습하고 있는 파이썬 코드를 재실행해도 다음에도 같은 결과가 나오도록 랜덤 시드(random seed)를 줍니다.
torch.manual_seed(1)

x_train = torch.FloatTensor([[1], [2], [3]])
y_train = torch.FloatTensor([[2], [4], [6]])

print(x_train)
print(x_train.shape)
"""
tensor([[1.],
        [2.],
        [3.]])
torch.Size([3, 1])
"""

print(y_train)
print(y_train.shape)
"""
tensor([[2.],
        [4.],
        [6.]])
torch.Size([3, 1])
"""

#3. 가중치와 편향의 초기화
"""
선형 회귀란 학습 데이터와 가장 잘 맞는 하나의 직선을 찾는 일입니다.
그리고 가장 잘 맞는 직선을 정의하는 것은 바로 W와 b입니다.
선형 회귀의 목표는 가장 잘 맞는 직선을 정의하는 W와 b의 값을 찾는 것입니다.
"""
# 가중치 W를 0으로 초기화하고 학습을 통해 값이 변경되는 변수임을 명시함.
W = torch.zeros(1, requires_grad=True)
# 가중치 W를 출력
print(W)#tensor([0.], requires_grad=True)
"""
가중치 W가 0으로 초기화되어있으므로 0이 출력된 것을 확인할 수 있습니다.
 위에서 requires_grad=True가 인자로 주어진 것을 확인할 수 있습니다. 이는 이 변수는 학습을 통해 계속 값이 변경되는 변수
"""

#마찬가지로 편향 b도 0으로 초기화하고, 학습을 통해 값이 변경되는 변수
b = torch.zeros(1, requires_grad=True)
print(b) #tensor([0.], requires_grad=True)

#현재 가중치 W와 b 둘 다 0이므로 현 직선의 방정식은 y=0*x+0
#지금 상태에선 x에 어떤 값이 들어가도 가설은 0을 예측하게 됩니다. 즉, 아직 적절한 와 의 값이 아닙니다.

#4. 가설 세우기
#파이토치 코드 상으로 직선의 방정식에 해당되는 가설을 선언 H(x)=Wx+b
hypothesis = x_train * W + b
print(hypothesis)
"""
tensor([[0.],
        [0.],
        [0.]], grad_fn=<AddBackward0>)
"""
#5. 비용 함수 선언하기
# 앞서 배운 torch.mean으로 평균을 구한다.
cost = torch.mean((hypothesis - y_train) ** 2)
print(cost) #tensor(18.6667, grad_fn=<MeanBackward1>)

#6. 경사 하강법 구현하기
"""
이제 경사 하강법을 구현합니다. 아래의 'SGD'는 경사 하강법의 일종입니다. lr은 학습률(learning rate)를 의미합니다.
학습 대상인 W와 b가 SGD의 입력이 됩니다.
"""
optimizer = optim.SGD([W, b], lr=0.01)
"""
optimizer.zero_grad()를 실행하므로서 미분을 통해 얻은 기울기를 0으로 초기화합니다. 
기울기를 초기화해야만 새로운 가중치 편향에 대해서 새로운 기울기를 구할 수 있습니다. 
그 다음 cost.backward() 함수를 호출하면 가중치 W와 편향 b에 대한 기울기가 계산됩니다. 
그 다음 경사 하강법 최적화 함수 opimizer의 .step() 함수를 호출하여 
인수로 들어갔던 W와 b에서 리턴되는 변수들의 기울기에 학습률(learining rate) 0.01을 곱하여 빼줌으로서 업데이트합니다.
"""
# gradient를 0으로 초기화
optimizer.zero_grad()
# 비용 함수를 미분하여 gradient 계산
cost.backward()
# W와 b를 업데이트
optimizer.step()

#7. 전체 코드
# 데이터
x_train = torch.FloatTensor([[1], [2], [3]])
y_train = torch.FloatTensor([[2], [4], [6]])
# 모델 초기화
W = torch.zeros(1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# optimizer 설정
optimizer = optim.SGD([W, b], lr=0.01)

nb_epochs = 1999 # 원하는만큼 경사 하강법을 반복
for epoch in range(nb_epochs + 1):

    # H(x) 계산
    hypothesis = x_train * W + b

    # cost 계산
    cost = torch.mean((hypothesis - y_train) ** 2)

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    # 100번마다 로그 출력
    if epoch % 100 == 0:
        print('Epoch {:4d}/{} W: {:.3f}, b: {:.3f} Cost: {:.6f}'.format(
            epoch, nb_epochs, W.item(), b.item(), cost.item()
        ))
""" 
결과적으로 훈련 과정에서 W와 b는 훈련 데이터와 잘 맞는 직선을 표현하기 위한 적절한 값으로 변화해갑니다. 
에포크(Epoch)는 전체 훈련 데이터가 학습에 한 번 사용된 주기를 말합니다.이번 실습의 경우 2,000번을 수행
Epoch    0/2000 W: 0.187, b: 0.080 Cost: 18.666666
Epoch  100/2000 W: 1.746, b: 0.578 Cost: 0.048171
Epoch  200/2000 W: 1.800, b: 0.454 Cost: 0.029767
Epoch  300/2000 W: 1.843, b: 0.357 Cost: 0.018394
Epoch  400/2000 W: 1.876, b: 0.281 Cost: 0.011366
Epoch  500/2000 W: 1.903, b: 0.221 Cost: 0.007024
Epoch  600/2000 W: 1.924, b: 0.174 Cost: 0.004340
Epoch  700/2000 W: 1.940, b: 0.136 Cost: 0.002682
Epoch  800/2000 W: 1.953, b: 0.107 Cost: 0.001657
Epoch  900/2000 W: 1.963, b: 0.084 Cost: 0.001024
Epoch 1000/2000 W: 1.971, b: 0.066 Cost: 0.000633
Epoch 1100/2000 W: 1.977, b: 0.052 Cost: 0.000391
Epoch 1200/2000 W: 1.982, b: 0.041 Cost: 0.000242
Epoch 1300/2000 W: 1.986, b: 0.032 Cost: 0.000149
Epoch 1400/2000 W: 1.989, b: 0.025 Cost: 0.000092
Epoch 1500/2000 W: 1.991, b: 0.020 Cost: 0.000057
Epoch 1600/2000 W: 1.993, b: 0.016 Cost: 0.000035
Epoch 1700/2000 W: 1.995, b: 0.012 Cost: 0.000022
Epoch 1800/2000 W: 1.996, b: 0.010 Cost: 0.000013
Epoch 1900/2000 W: 1.997, b: 0.008 Cost: 0.000008
Epoch 2000/2000 W: 1.997, b: 0.006 Cost: 0.000005
최종 훈련 결과를 보면 최적의 기울기 W는 2에 가깝고, b는 0에 가까운 것을 볼 수 있습니다.
현재 훈련 데이터가 x_train은 [[1], [2], [3]]이고 y_train은 [[2], [4], [6]]인 것을 감안하면
실제 정답은 W가 2이고, b가 0인 H(x)=2x이므로 거의 정답을 찾은 셈입니다.
    """

#5. optimizer.zero_grad()가 필요한 이유
#파이토치는 미분을 통해 얻은 기울기를 이전에 계산된 기울기 값에 누적시키는 특징이 있음
#계속해서 미분값인 2가 누적되는 것을 볼 수 있습니다. 그렇기 때문에 optimizer.zero_grad()를 통해 미분값을 계속 0으로 초기화시켜줘야함
"""
import torch
w = torch.tensor(2.0, requires_grad=True)

nb_epochs = 20
for epoch in range(nb_epochs + 1):

  z = 2*w

  z.backward()
  print('수식을 w로 미분한 값 : {}'.format(w.grad))
"""
""" 실행결과
수식을 w로 미분한 값 : 2.0
수식을 w로 미분한 값 : 4.0
수식을 w로 미분한 값 : 6.0
수식을 w로 미분한 값 : 8.0
수식을 w로 미분한 값 : 10.0
수식을 w로 미분한 값 : 12.0
수식을 w로 미분한 값 : 14.0
수식을 w로 미분한 값 : 16.0
수식을 w로 미분한 값 : 18.0
수식을 w로 미분한 값 : 20.0
수식을 w로 미분한 값 : 22.0
수식을 w로 미분한 값 : 24.0
수식을 w로 미분한 값 : 26.0
수식을 w로 미분한 값 : 28.0
수식을 w로 미분한 값 : 30.0
수식을 w로 미분한 값 : 32.0
수식을 w로 미분한 값 : 34.0
수식을 w로 미분한 값 : 36.0
수식을 w로 미분한 값 : 38.0
수식을 w로 미분한 값 : 40.0
수식을 w로 미분한 값 : 42.0
"""

#6. torch.manual_seed()를 하는 이유
"""
torch.manual_seed()를 사용한 프로그램의 결과는 다른 컴퓨터에서 실행시켜도 동일한 결과를 얻을 수 있습니다. 
그 이유는 torch.manual_seed()는 난수 발생 순서와 값을 동일하게 보장해준다는 특징때문입니다. 
"""
"""
import torch
torch.manual_seed(3)
print('랜덤 시드가 3일 때')
for i in range(1,3):
  print(torch.rand(1))
"""
"""실행결과
랜덤 시드가 3일 때
tensor([0.0043])
tensor([0.1056])
"""

"""
torch.manual_seed(5)
print('랜덤 시드가 5일 때')
for i in range(1,3):
  print(torch.rand(1))
"""
"""실행결과
랜덤 시드가 5일 때
tensor([0.8303])
tensor([0.1261])
"""

"""
#0.8303과 0.1261이 나옵니다. 
#이제 다시 랜덤 시드값을 3으로 돌려보겠습니다. 이렇게 하면 프로그램을 다시 처음부터 실행한 것처럼 난수 발생 순서가 초기화됩니다.
import torch
torch.manual_seed(3)
print('랜덤 시드가 3일 때')
for i in range(1,3):
  print(torch.rand(1))
"""
"""실행결과 : 다시 동일하게 0.0043과 0.1056이 나옵니다
랜덤 시드가 3일 때
tensor([0.0043])
tensor([0.1056])
"""

#cf텐서에는 requires_grad라는 속성이 있습니다. 이것을 True로 설정하면 자동 미분 기능이 적용됩니다.
# 선형 회귀부터 신경망과 같은 복잡한 구조에서 파라미터들이 모두 이 기능이 적용됩니다.
# requires_grad = True가 적용된 텐서에 연산을 하면, 계산 그래프가 생성되며 backward 함수를 호출하면 그래프로부터 자동으로 미분이 계산됩니다.
jeongabae commented 2 years ago

3_1_Linear Regression.py

#1. 데이터에 대한 이해(Data Definition)
# 예측을 위해 사용하는 데이터를 훈련 데이터셋(training dataset)
#학습이 끝난 후, 이 모델이 얼마나 잘 작동하는지 판별하는 데이터셋을 테스트 데이터셋(test dataset)

#1-2. 훈련 데이터셋의 구성
#데이터는 파이토치의 텐서의 형태(torch.tensor)
#_train은 공부한 시간, y_train은 그에 맵핑되는 점수
import torch

x_train = torch.FloatTensor([[1], [2], [3]])
y_train = torch.FloatTensor([[2], [4], [6]])

#2. 가설(Hypothesis) 수립 : 선형 회귀의 가설 : y=Wx+b
#W :가중치(Weight),  b : 편향(bias)

#3. 비용 함수(Cost function)에 대한 이해
#비용 함수(cost function) = 손실 함수(loss function) = 오차 함수(error function) = 목적 함수(objective function)
"""
오차들을 제곱해준 뒤에 전부 더하고 데이터의 개수인 으로 나누면, 오차의 제곱합에 대한 평균을 구할 수 있는데
이를 평균 제곱 오차(Mean Squared Error, MSE)라고 합니다.
"""
#비용함수 Cost(W,b)를 최소가 되게 만드는 W와 b를 구하면 훈련 데이터를 가장 잘 나타내는 직선을 구할 수 있습니다.

#4. 옵티마이저 - 경사 하강법(Gradient Descent)
"""
비용 함수(Cost Function)의 값을 최소로 하는 W와 b를 찾는 방법.
-> 이때 사용되는 것이 옵티마이저(Optimizer) 알고리즘 : 최적화 알고리즘이라고도 함.
가장 기본적인 옵티마이저 알고리즘인 경사 하강법(Gradient Descent)에 대해서 배움
"""
"""
 cost가 최소화가 되는 지점은 접선의 기울기가 0이 되는 지점이며, 또한 미분값이 0이 되는 지점입니다. 
 경사 하강법의 아이디어는 비용 함수(Cost function)를 미분하여 현재 W에서의 접선의 기울기를 구하고, 
 접선의 기울기가 낮은 방향으로 W의 값을 변경하는 작업을 반복하는 것에 있습니다.
"""
"""
기울기가 음수일 때 : W의 값이 증가
기울기가 양수일 때 : W의 값이 감소
"""
# 학습률(learning rate)이라고 말하는 α 는 어떤 의미를 가질까요? 학습률 α 은 의 W값을 변경할 때, 얼마나 크게 변경할지를 결정
jeongabae commented 2 years ago

3_2_Autograd.py

#경사 하강법 코드를 보고있으면 requires_grad=True, backward() 등이 나옵니다.
# 이는 파이토치에서 제공하고 있는 자동 미분(Autograd) 기능을 수행하고 있는 것

#1. 경사 하강법 리뷰
# 경사 하강법은 비용 함수를 미분하여 이 함수의 기울기(gradient)를 구해서 비용이 최소화 되는 방향을 찾아내는 알고리즘
#모델이 복잡해질수록 경사 하강법을 넘파이 등으로 직접 코딩하는 것은 까다로움.
# 파이토치에서는 이런 수고를 하지 않도록 자동 미분(Autograd) 지원

#2. 자동 미분(Autograd) 실습하기 : 임의로 2w^2+5라는 식을 세워보고, w에 대해 미분
import torch
"""
값이 2인 임의의 스칼라 텐서 w를 선언합니다. 이때 required_grad를 True로 설정합니다. 
이는 이 텐서에 대한 기울기를 저장하겠다는 의미입니다. 뒤에서 보겠지만, 이렇게 하면 w.grad에 w에 대한 미분값이 저장됩니다.
"""
w = torch.tensor(2.0, requires_grad=True)

#수식 정의
y = w**2
z = 2*y + 5

z.backward() # 해당 수식을 w에 대해서 미분해야합니다. .backward()를 호출하면 해당 수식의 w에 대한 기울기를 계산

#w.grad를 출력하면 w가 속한 수식을 w로 미분한 값이 저장된 것을 확인 가능
print('수식을 w로 미분한 값 : {}'.format(w.grad)) #수식을 w로 미분한 값 : 8.0
jeongabae commented 2 years ago

3_3_Multivariable Linear regression.py

#다수의 x로부터 y를 예측하는 다중 선형 회귀(Multivariable Linear Regression)

#1. 데이터에 대한 이해(Data Definition)
"""
ex)3개의 퀴즈 점수로부터 최종 점수를 예측하는 모델
독립 변수 의 개수가 이제 1개가 아닌 3개일 때
H(x)=w1x1+w2x2+w3x3+b
"""

#2. 파이토치로 구현하기
#우선 필요한 도구들을 임포트하고 랜덤 시드를 고정
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
torch.manual_seed(1)

# 훈련 데이터 (x를 3개 선언)
x1_train = torch.FloatTensor([[73], [93], [89], [96], [73]])
x2_train = torch.FloatTensor([[80], [88], [91], [98], [66]])
x3_train = torch.FloatTensor([[75], [93], [90], [100], [70]])
y_train = torch.FloatTensor([[152], [185], [180], [196], [142]])

# 가중치 w와 편향 b 초기화 (가중치 w도 3개 선언해주어야)
w1 = torch.zeros(1, requires_grad=True)
w2 = torch.zeros(1, requires_grad=True)
w3 = torch.zeros(1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)

#이제 가설, 비용 함수, 옵티마이저를 선언한 후에 경사 하강법을 1,000회 반복
# optimizer 설정
optimizer = optim.SGD([w1, w2, w3, b], lr=1e-5)

nb_epochs = 1000
for epoch in range(nb_epochs + 1):

    # H(x) 계산
    hypothesis = x1_train * w1 + x2_train * w2 + x3_train * w3 + b

    # cost 계산
    cost = torch.mean((hypothesis - y_train) ** 2)

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    # 100번마다 로그 출력
    if epoch % 100 == 0:
        print('Epoch {:4d}/{} w1: {:.3f} w2: {:.3f} w3: {:.3f} b: {:.3f} Cost: {:.6f}'.format(
            epoch, nb_epochs, w1.item(), w2.item(), w3.item(), b.item(), cost.item()
        ))
"""실행 결과
Epoch    0/1000 w1: 0.294 w2: 0.294 w3: 0.297 b: 0.003 Cost: 29661.800781
Epoch  100/1000 w1: 0.674 w2: 0.661 w3: 0.676 b: 0.008 Cost: 1.563634
Epoch  200/1000 w1: 0.679 w2: 0.655 w3: 0.677 b: 0.008 Cost: 1.497607
Epoch  300/1000 w1: 0.684 w2: 0.649 w3: 0.677 b: 0.008 Cost: 1.435026
Epoch  400/1000 w1: 0.689 w2: 0.643 w3: 0.678 b: 0.008 Cost: 1.375730
Epoch  500/1000 w1: 0.694 w2: 0.638 w3: 0.678 b: 0.009 Cost: 1.319511
Epoch  600/1000 w1: 0.699 w2: 0.633 w3: 0.679 b: 0.009 Cost: 1.266222
Epoch  700/1000 w1: 0.704 w2: 0.627 w3: 0.679 b: 0.009 Cost: 1.215696
Epoch  800/1000 w1: 0.709 w2: 0.622 w3: 0.679 b: 0.009 Cost: 1.167818
Epoch  900/1000 w1: 0.713 w2: 0.617 w3: 0.680 b: 0.009 Cost: 1.122429
Epoch 1000/1000 w1: 0.718 w2: 0.613 w3: 0.680 b: 0.009 Cost: 1.079378
"""
"""
위의 경우 가설을 선언하는 부분인 hypothesis = x1_train * w1 + x2_train * w2 + x3_train * w3 + b에서도 
x_train의 개수만큼 w와 곱해주도록 작성해준 것을 확인할 수 있습니다.
"""

#3. 벡터와 행렬 연산으로 바꾸기
""" x_train1 ~ x_train1000을 전부 선언하고, w1 ~ w1000을 전부 선언해야 합니다.
다시 말해 와  변수 선언만 총 합 2,000개를 해야합니다.
또한 가설을 선언하는 부분에서도 마찬가지로 x_train과 w의 곱셈이 이루어지는 항을 1,000개를 작성해야 합니다. 이는 굉장히 비효율적입니다.
이를 해결하기 위해 행렬 곱셈 연산(또는 벡터의 내적)을 사용
"""
#행렬의 곱셈 과정에서 이루어지는 벡터 연산을 벡터의 내적(Dot Product)

#3-1. 벡터 연산으로 이해하기
#H(X)=w1x1+w2x2+w3x3  => H(X)=XW
#x의 개수가 3개였음에도 이제는 X와 W라는 두 개의 변수로 표현된 것을 볼 수 있습니다.

#3-2. 행렬 연산으로 이해하기
"""
전체 훈련 데이터의 개수를 셀 수 있는 1개의 단위를 샘플(sample)
각 샘플에서 y를 결정하게 하는 각각의 독립 변수 x를 특성(feature)
"""
# H(X)=XW+B
"""
결과적으로 전체 훈련 데이터의 가설 연산을 3개의 변수만으로 표현하였습니다.
이와 같이 벡터와 행렬 연산은 식을 간단하게 해줄 뿐만 아니라 다수의 샘플의 병렬 연산이므로 속도의 이점을 가집니다.

이를 참고로 파이토치로 구현
"""

#4. 행렬 연산을 고려하여 파이토치로 구현하기
x_train  =  torch.FloatTensor([[73,  80,  75],
                               [93,  88,  93],
                               [89,  91,  80],
                               [96,  98,  100],
                               [73,  66,  70]])
y_train  =  torch.FloatTensor([[152],  [185],  [180],  [196],  [142]])
print(x_train.shape) #torch.Size([5, 3])
print(y_train.shape) #torch.Size([5, 1])

# 가중치와 편향 선언
W = torch.zeros((3, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

# X_train의 행렬의 크기는 (5 × 3)이며,  X벡터의 크기는 (3 × 1)이므로 두 행렬과 벡터는 행렬곱이 가능합니다.
# 행렬곱으로 가설을 선언
hypothesis = x_train.matmul(W) + b

#전체 코드!!!!!!!!!!!
x_train  =  torch.FloatTensor([[73,  80,  75],
                               [93,  88,  93],
                               [89,  91,  80],
                               [96,  98,  100],
                               [73,  66,  70]])
y_train  =  torch.FloatTensor([[152],  [185],  [180],  [196],  [142]])

# 모델 초기화
W = torch.zeros((3, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# optimizer 설정
optimizer = optim.SGD([W, b], lr=1e-5)

nb_epochs = 20
for epoch in range(nb_epochs + 1):

    # H(x) 계산
    # 편향 b는 브로드 캐스팅되어 각 샘플에 더해집니다.
    hypothesis = x_train.matmul(W) + b

    # cost 계산
    cost = torch.mean((hypothesis - y_train) ** 2)

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    print('Epoch {:4d}/{} hypothesis: {} Cost: {:.6f}'.format(
        epoch, nb_epochs, hypothesis.squeeze().detach(), cost.item()
    ))
    """
    Epoch    0/20 hypothesis: tensor([0., 0., 0., 0., 0.]) Cost: 29661.800781
Epoch    1/20 hypothesis: tensor([66.7178, 80.1701, 76.1025, 86.0194, 61.1565]) Cost: 9537.694336
Epoch    2/20 hypothesis: tensor([104.5421, 125.6208, 119.2478, 134.7862,  95.8280]) Cost: 3069.590088
Epoch    3/20 hypothesis: tensor([125.9858, 151.3882, 143.7087, 162.4333, 115.4844]) Cost: 990.670898
Epoch    4/20 hypothesis: tensor([138.1429, 165.9963, 157.5768, 178.1071, 126.6283]) Cost: 322.482086
Epoch    5/20 hypothesis: tensor([145.0350, 174.2780, 165.4395, 186.9928, 132.9461]) Cost: 107.717064
Epoch    6/20 hypothesis: tensor([148.9423, 178.9730, 169.8976, 192.0301, 136.5279]) Cost: 38.687496
Epoch    7/20 hypothesis: tensor([151.1574, 181.6346, 172.4254, 194.8856, 138.5585]) Cost: 16.499043
Epoch    8/20 hypothesis: tensor([152.4131, 183.1435, 173.8590, 196.5043, 139.7097]) Cost: 9.365656
Epoch    9/20 hypothesis: tensor([153.1250, 183.9988, 174.6723, 197.4217, 140.3625]) Cost: 7.071114
Epoch   10/20 hypothesis: tensor([153.5285, 184.4835, 175.1338, 197.9415, 140.7325]) Cost: 6.331847
Epoch   11/20 hypothesis: tensor([153.7572, 184.7582, 175.3958, 198.2360, 140.9424]) Cost: 6.092532
Epoch   12/20 hypothesis: tensor([153.8868, 184.9138, 175.5449, 198.4026, 141.0613]) Cost: 6.013817
Epoch   13/20 hypothesis: tensor([153.9602, 185.0019, 175.6299, 198.4969, 141.1288]) Cost: 5.986785
Epoch   14/20 hypothesis: tensor([154.0017, 185.0517, 175.6785, 198.5500, 141.1671]) Cost: 5.976325
Epoch   15/20 hypothesis: tensor([154.0252, 185.0798, 175.7065, 198.5800, 141.1888]) Cost: 5.971208
Epoch   16/20 hypothesis: tensor([154.0385, 185.0956, 175.7229, 198.5966, 141.2012]) Cost: 5.967835
Epoch   17/20 hypothesis: tensor([154.0459, 185.1045, 175.7326, 198.6059, 141.2082]) Cost: 5.964969
Epoch   18/20 hypothesis: tensor([154.0501, 185.1094, 175.7386, 198.6108, 141.2122]) Cost: 5.962291
Epoch   19/20 hypothesis: tensor([154.0524, 185.1120, 175.7424, 198.6134, 141.2145]) Cost: 5.959664
Epoch   20/20 hypothesis: tensor([154.0536, 185.1134, 175.7451, 198.6145, 141.2158]) Cost: 5.957089
    """
jeongabae commented 2 years ago

3_4__1_Implement_Linearregression_With_nn.Module1.py

#파이토치에서 이미 구현되어져 제공되고 있는 함수들을 불러오는 것으로 더 쉽게 선형 회귀 모델을 구현
#ex)파이토치에서는 선형 회귀 모델이 nn.Linear()라는 함수로, 또 평균 제곱오차가 nn.functional.mse_loss()라는 함수로 구현되어져 있음.

"""두 함수의 사용 예제
import torch.nn as nn
model = nn.Linear(input_dim, output_dim)

import torch.nn.functional as F
cost = F.mse_loss(prediction, y_train)
"""

#1. 단순 선형 회귀 구현하기
import torch
import torch.nn as nn
import torch.nn.functional as F

torch.manual_seed(1)

# 데이터를 선언합니다. 아래 데이터는 y=2x를 가정된 상태에서 만들어진 데이터로
# 우리는 이미 정답이 W=2, b=0임을 알고 있는 사태입니다.
# 모델이 이 두 W와 b의 값을 제대로 찾아내도록 하는 것이 목표
# 데이터
x_train = torch.FloatTensor([[1], [2], [3]])
y_train = torch.FloatTensor([[2], [4], [6]])

# 모델을 선언 및 초기화. 단순 선형 회귀이므로 input_dim=1, output_dim=1.
model = nn.Linear(1,1) #nn.Linear()는 입력의 차원, 출력의 차원을 인수로 받음.

#model에는 가중치 W와 편향 b가 저장되어져 있습니다. 이 값은 model.parameters()라는 함수를 사용하여 불러올 수 있음.
print(list(model.parameters()))
""" 첫번째 값이 W고, 두번째 값이 b에 해당, 두 값 모두 학습의 대상이므로 requires_grad=True
[Parameter containing:
tensor([[0.5153]], requires_grad=True), Parameter containing:
tensor([-0.4414], requires_grad=True)]
"""

# optimizer 설정. 경사 하강법 SGD를 사용하고 learning rate를 의미하는 lr은 0.01
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # model.parameters()를 사용하여 W와 b를 전달

# 전체 훈련 데이터에 대해 경사 하강법을 2,000회 반복
nb_epochs = 2000
for epoch in range(nb_epochs+1):

    # H(x) 계산
    prediction = model(x_train) #prediction = model(x_train)은 x_train으로부터 예측값을 리턴하므로 forward 연산

    # cost 계산
    cost = F.mse_loss(prediction, y_train) # <== 파이토치에서 제공하는 평균 제곱 오차 함수

    # cost로 H(x) 개선하는 부분
    # gradient를 0으로 초기화
    optimizer.zero_grad()
    # 비용 함수를 미분하여 gradient 계산
    cost.backward() # backward 연산 : 학습 과정에서 비용 함수를 미분하여 기울기를 구하는 것
                    #cost.backward()는 비용 함수로부터 기울기를 구하라는 의미이며 backward 연산
    # W와 b를 업데이트
    optimizer.step()

    if epoch % 100 == 0:
    # 100번마다 로그 출력
      print('Epoch {:4d}/{} Cost: {:.6f}'.format(
          epoch, nb_epochs, cost.item()
      ))
"""학습이 완료됨. Cost의 값이 매우 작다.
Epoch    0/2000 Cost: 13.103540
... 중략 ...
Epoch 2000/2000 Cost: 0.000000
"""

#아래는 x에 임의의 값 4를 넣어 모델이 예측하는 y의 값을 확인해보겠습니다.
# 임의의 입력 4를 선언
new_var =  torch.FloatTensor([[4.0]])
# 입력한 값 4에 대해서 예측값 y를 리턴받아서 pred_y에 저장
pred_y = model(new_var) # forward 연산 :  H(x)식에 입력 x로부터 예측된 y를 얻는 것
                        #pred_y = model(new_var)는 임의의 값 new_var로부터 예측값을 리턴하므로 forward 연산

# y = 2x 이므로 입력이 4라면 y가 8에 가까운 값이 나와야 제대로 학습이 된 것
print("훈련 후 입력이 4일 때의 예측값 :", pred_y)
"""
훈련 후 입력이 4일 때의 예측값 : tensor([[7.9989]], grad_fn=<AddmmBackward>)
#이 문제의 정답은 y=2x가 정답이므로 y값이 8에 가까우면 W와 b의 값이 어느정도 최적화가 된 것으로 볼 수 있습니다. 
#실제로 예측된 y값은 7.9989로 8에 매우 가깝습니다.
"""

# 학습 후의 W와 b의 값을 출력
print(list(model.parameters()))
"""
#W의 값이 2에 가깝고, b의 값이 0에 가까운 것을 볼 수 있다.
[Parameter containing:
tensor([[1.9994]], requires_grad=True), Parameter containing:
tensor([0.0014], requires_grad=True)]
"""
jeongabae commented 2 years ago

3_4__2_Implement_Linearregression_With_nn.Module2.py

#2. 다중 선형 회귀 구현하기
import torch
import torch.nn as nn
import torch.nn.functional as F

torch.manual_seed(1)

"""
데이터를 선언해줍니다. 여기서는 3개의 x로부터 하나의 y를 예측하는 문제입니다.
즉, 가설 수식은 H(x)=w1x1+w2x2+w3x3+b
"""
# 데이터
x_train = torch.FloatTensor([[73, 80, 75],
                             [93, 88, 93],
                             [89, 91, 90],
                             [96, 98, 100],
                             [73, 66, 70]])
y_train = torch.FloatTensor([[152], [185], [180], [196], [142]])

## 모델을 선언 및 초기화. 다중 선형 회귀이므로 input_dim=3, output_dim=1.
model = nn.Linear(3,1) # nn.Linear()는 입력의 차원, 출력의 차원을 인수로 받음.

print(list(model.parameters()))
"""첫번째 출력되는 것이 3개의 w고, 두번째 출력되는 것이 b에 해당(두 값 모두 현재는 랜덤 초기화가 되어져 있음)
# 두 출력 결과 모두 학습의 대상이므로 requires_grad=True가 되어져 있음
[Parameter containing:
tensor([[ 0.2975, -0.2548, -0.1119]], requires_grad=True), Parameter containing:
tensor([0.2710], requires_grad=True)]
"""

optimizer = torch.optim.SGD(model.parameters(), lr=1e-5)

nb_epochs = 2000
for epoch in range(nb_epochs+1):

    # H(x) 계산
    prediction = model(x_train)
    # model(x_train)은 model.forward(x_train)와 동일함.

    # cost 계산
    cost = F.mse_loss(prediction, y_train) # <== 파이토치에서 제공하는 평균 제곱 오차 함수

    # cost로 H(x) 개선하는 부분
    # gradient를 0으로 초기화
    optimizer.zero_grad()
    # 비용 함수를 미분하여 gradient 계산
    cost.backward()
    # W와 b를 업데이트
    optimizer.step()

    if epoch % 100 == 0:
    # 100번마다 로그 출력
      print('Epoch {:4d}/{} Cost: {:.6f}'.format(
          epoch, nb_epochs, cost.item()
      ))
"""
Epoch    0/2000 Cost: 31667.597656
... 중략 ...
Epoch 2000/2000 Cost: 0.199777
"""

#학습이 완료되었습니다. Cost의 값이 매우 작습니다.
# 3개의 w와 b의 값도 최적화가 되었는지 확인해봅시다.
#x에 임의의 입력 [73, 80, 75]를 넣어 모델이 예측하는 y의 값을 확인

# 임의의 입력 [73, 80, 75]를 선언
new_var =  torch.FloatTensor([[73, 80, 75]])
# 입력한 값 [73, 80, 75]에 대해서 예측값 y를 리턴받아서 pred_y에 저장
pred_y = model(new_var)

print("훈련 후 입력이 73, 80, 75일 때의 예측값 :", pred_y)
#훈련 후 입력이 73, 80, 75일 때의 예측값 : tensor([[151.2305]], grad_fn=<AddmmBackward>)
#당시 y의 값은 152였는데, 현재 예측값이 151이 나온 것으로 보아 어느정도는 3개의 w와 b의 값이 최적화 된것으로 보입니다.
# 이제 학습 후의 3개의 w와 b의 값을 출력

print(list(model.parameters()))
"""
[Parameter containing:
tensor([[0.9778, 0.4539, 0.5768]], requires_grad=True), Parameter containing:
tensor([0.2802], requires_grad=True)]
"""
jeongabae commented 2 years ago

3_5_Implement_Class_With_PyTorch.py

#선형 회귀를 클래스로 구현
#1. 모델을 클래스로 구현하기
"""단순 선형 회귀 모델 구
import torch.nn as nn
# 모델을 선언 및 초기화. 단순 선형 회귀이므로 input_dim=1, output_dim=1.
model = nn.Linear(1,1)
"""

"""
#단순 선형 회귀 모델 클래스로 구현
import torch.nn as nn
class LinearRegressionModel(nn.Module): # torch.nn.Module을 상속받는 파이썬 클래스
def __init__(self): #모델의 구조와 동작을 정의하는 생성자를 정의
super().__init__() #super() 함수를 부르면 여기서 만든 클래스는 nn.Module 클래스의 속성들을 가지고 초기화
self.linear = nn.Linear(1, 1) # 단순 선형 회귀이므로 input_dim=1, output_dim=1.

def forward(self, x): # foward() 함수는 모델이 학습데이터를 입력받아서 forward 연산을 진행시키는 함수
#forward() 함수는 model 객체를 데이터와 함께 호출하면 자동으로 실행
#forward연산? H(x)식에 입력 x로부터 예측된 y를 얻는 것
return self.linear(x)

model = LinearRegressionModel()
"""

""" 다중 선형 회귀 모델 구현
import torch.nn as nn
# 모델을 선언 및 초기화. 다중 선형 회귀이므로 input_dim=3, output_dim=1.
model = nn.Linear(3,1)
"""

"""
# 다중 선형 회귀 모델 클래스로 구현
class MultivariateLinearRegressionModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(3, 1) # 다중 선형 회귀이므로 input_dim=3, output_dim=1.

def forward(self, x):
return self.linear(x)

model = MultivariateLinearRegressionModel()
"""

#2. 단순 선형 회귀 클래스로 구현하기
"""
import torch
import torch.nn as nn
import torch.nn.functional as F

torch.manual_seed(1)

# 데이터
x_train = torch.FloatTensor([[1], [2], [3]])
y_train = torch.FloatTensor([[2], [4], [6]])

class LinearRegressionModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(1, 1)

def forward(self, x):
return self.linear(x)

model = LinearRegressionModel()

# optimizer 설정. 경사 하강법 SGD를 사용하고 learning rate를 의미하는 lr은 0.01
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 전체 훈련 데이터에 대해 경사 하강법을 2,000회 반복
nb_epochs = 2000
for epoch in range(nb_epochs+1):

# H(x) 계산
prediction = model(x_train)

# cost 계산
cost = F.mse_loss(prediction, y_train) # <== 파이토치에서 제공하는 평균 제곱 오차 함수

# cost로 H(x) 개선하는 부분
# gradient를 0으로 초기화
optimizer.zero_grad()
# 비용 함수를 미분하여 gradient 계산
cost.backward() # backward 연산
# W와 b를 업데이트
optimizer.step()

if epoch % 100 == 0:
# 100번마다 로그 출력
print('Epoch {:4d}/{} Cost: {:.6f}'.format(
epoch, nb_epochs, cost.item()
))
"""

#3. 다중 선형 회귀 클래스로 구현하기
"""
import torch
import torch.nn as nn
import torch.nn.functional as F

torch.manual_seed(1)

class MultivariateLinearRegressionModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(3, 1) # 다중 선형 회귀이므로 input_dim=3, output_dim=1.

def forward(self, x):
return self.linear(x)

model = MultivariateLinearRegressionModel()

optimizer = torch.optim.SGD(model.parameters(), lr=1e-5)

nb_epochs = 2000
for epoch in range(nb_epochs+1):

# H(x) 계산
prediction = model(x_train)
# model(x_train)은 model.forward(x_train)와 동일함.

# cost 계산
cost = F.mse_loss(prediction, y_train) # <== 파이토치에서 제공하는 평균 제곱 오차 함수

# cost로 H(x) 개선하는 부분
# gradient를 0으로 초기화
optimizer.zero_grad()
# 비용 함수를 미분하여 gradient 계산
cost.backward()
# W와 b를 업데이트
optimizer.step()

if epoch % 100 == 0:
# 100번마다 로그 출력
print('Epoch {:4d}/{} Cost: {:.6f}'.format(
epoch, nb_epochs, cost.item()
))

"""
jeongabae commented 2 years ago

3_6_MiniBatch_And_DataLoad.py

#1. 미니 배치와 배치 크기(Mini Batch and Batch Size)
# 전체 데이터를 더 작은 단위로 나누어서 해당 단위로 학습하는 개념이 나오게 되었습니다. => 이 단위를 미니 배치(Mini Batch)
"""
 미니 배치 학습을 하게되면 미니 배치만큼만 가져가서 미니 배치에 대한 대한 비용(cost)를 계산하고, 경사 하강법을 수행합니다.
 그리고 다음 미니 배치를 가져가서 경사 하강법을 수행하고 마지막 미니 배치까지 이를 반복합니다.
 이렇게 전체 데이터에 대한 학습이 1회 끝나면 1 에포크(Epoch)가 끝나게 됩니다.
 cf)에포크(Epoch) : 전체 훈련 데이터가 학습에 한 번 사용된 주기

 전체 데이터에 대해서 한 번에 경사 하강법을 수행하는 방법을 '배치 경사 하강법'
  반면, 미니 배치 단위로 경사 하강법을 수행하는 방법을 '미니 배치 경사 하강법'

배치 경사 하강법은 경사 하강법을 할 때,
전체 데이터를 사용하므로 가중치 값이 최적값에 수렴하는 과정이 매우 안정적이지만, 계산량이 너무 많이 듭니다.
미니 배치 경사 하강법은 경사 하강법을 할 때,
전체 데이터의 일부만을 보고 수행하므로 최적값으로 수렴하는 과정에서 값이 조금 헤매기도 하지만 훈련 속도가 빠릅니다.

배치 크기는 보통 2의 제곱수를 사용
"""

#2. 이터레이션(Iteration) : 한 번의 에포크 내에서 이루어지는 매개변수인 가중치 W와 b의 업데이트 횟수
# 전체 데이터가 2,000일 때 배치 크기를 200으로 한다면 이터레이션의 수는 총 10개입니다. 이는 한 번의 에포크 당 매개변수 업데이트가 10번 이루어짐을 의미

#3. 데이터 로드하기(Data Load)
#파이토치에서는 데이터를 좀 더 쉽게 다룰 수 있도록 유용한 도구로서 데이터셋(Dataset)과 데이터로더(DataLoader)를 제공
#이를 사용하면 미니 배치 학습, 데이터 셔플(shuffle), 병렬 처리까지 간단히 수행 가능
#기본적인 사용 방법은 Dataset을 정의하고, 이를 DataLoader에 전달하는 것

#텐서를 입력받아 Dataset의 형태로 변환해주는 TensorDataset을 사용해보자
import torch
import torch.nn as nn
import torch.nn.functional as F

#TensorDataset과 DataLoader를 임포트합니다.
from torch.utils.data import TensorDataset # 텐서데이터셋
from torch.utils.data import DataLoader # 데이터로더

#TensorDataset은 기본적으로 텐서를 입력으로 받습니다. 텐서 형태로 데이터를 정의
x_train  =  torch.FloatTensor([[73,  80,  75],
                               [93,  88,  93],
                               [89,  91,  90],
                               [96,  98,  100],
                               [73,  66,  70]])
y_train  =  torch.FloatTensor([[152],  [185],  [180],  [196],  [142]])

#이제 이를 TensorDataset의 입력으로 사용하고 dataset으로 저장
dataset = TensorDataset(x_train, y_train)

"""
파이토치의 데이터셋을 만들었다면 데이터로더를 사용 가능합니다. 
데이터로더는 기본적으로 2개의 인자를 입력받는다. 하나는 데이터셋, 미니 배치의 크기입니다. 
이때 미니 배치의 크기는 통상적으로 2의 배수를 사용합니다. (ex) 64, 128, 256...)
 그리고 추가적으로 많이 사용되는 인자로 shuffle이 있습니다. 
shuffle=True를 선택하면 Epoch마다 데이터셋을 섞어서 데이터가 학습되는 순서를 바꿉니다.
(순서에 익숙해지는 것을 방지하여 학습할 때는 이 옵션을 True로 주는 걸 권장)
"""
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)

#모델과 옵티마이저를 설계
model = nn.Linear(3,1)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-5)

#훈련을 진행
nb_epochs = 20
for epoch in range(nb_epochs + 1):
  for batch_idx, samples in enumerate(dataloader):
    # print(batch_idx)
    # print(samples)
    x_train, y_train = samples
    # H(x) 계산
    prediction = model(x_train)

    # cost 계산
    cost = F.mse_loss(prediction, y_train)

    # cost로 H(x) 계산
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    print('Epoch {:4d}/{} Batch {}/{} Cost: {:.6f}'.format(
        epoch, nb_epochs, batch_idx+1, len(dataloader),
        cost.item()
        ))
"""Cost의 값이 점차 작아집니다. (에포크를 더 늘려서 훈련하면 Cost의 값이 더 작아질 수 있음)
Epoch    0/20 Batch 1/3 Cost: 26085.919922
Epoch    0/20 Batch 2/3 Cost: 3660.022949
Epoch    0/20 Batch 3/3 Cost: 2922.390869
... 중략 ...
Epoch   20/20 Batch 1/3 Cost: 6.315856
Epoch   20/20 Batch 2/3 Cost: 13.519956
Epoch   20/20 Batch 3/3 Cost: 4.262849
"""

#모델의 입력으로 임의의 값을 넣어 예측값을 확인
# 임의의 입력 [73, 80, 75]를 선언
new_var =  torch.FloatTensor([[73, 80, 75]])
# 입력한 값 [73, 80, 75]에 대해서 예측값 y를 리턴받아서 pred_y에 저장
pred_y = model(new_var)
print("훈련 후 입력이 73, 80, 75일 때의 예측값 :", pred_y)
"""
훈련 후 입력이 73, 80, 75일 때의 예측값 : tensor([[154.3850]], grad_fn=<AddmmBackward>)
"""
jeongabae commented 2 years ago

3_7_CustomDataset.py

#기본적인 사용 방법은 Dataset을 정의하고, 이를 DataLoader에 전달하는 것
# torch.utils.data.Dataset을 상속받아 직접 커스텀 데이터셋(Custom Dataset)을 만드는 경우도 있음!
#torch.utils.data.Dataset은 파이토치에서 데이터셋을 제공하는 추상 클래스입니다.
# Dataset을 상속받아 다음 메소드들을 오버라이드 하여 커스텀 데이터셋 만들기
"""커스텀 데이터셋을 만들 때, 가장 기본적인 뼈대
class CustomDataset(torch.utils.data.Dataset):
  def __init__(self):
  데이터셋의 전처리를 해주는 부분

  def __len__(self):
  데이터셋의 길이. 즉, 총 샘플의 수를 적어주는 부분

  def __getitem__(self, idx):
  데이터셋에서 특정 1개의 샘플을 가져오는 함수

#len(dataset)을 했을 때 데이터셋의 크기를 리턴할 len
#dataset[i]을 했을 때 i번째 샘플을 가져오도록 하는 인덱싱을 위한 get_item
"""

#2. 커스텀 데이터셋(Custom Dataset)으로 선형 회귀 구현하기
import torch
import torch.nn.functional as F

from torch.utils.data import Dataset
from torch.utils.data import DataLoader

# Dataset 상속
class CustomDataset(Dataset):
  def __init__(self):
    self.x_data = [[73, 80, 75],
                   [93, 88, 93],
                   [89, 91, 90],
                   [96, 98, 100],
                   [73, 66, 70]]
    self.y_data = [[152], [185], [180], [196], [142]]

  # 총 데이터의 개수를 리턴
  def __len__(self):
    return len(self.x_data)

  # 인덱스를 입력받아 그에 맵핑되는 입출력 데이터를 파이토치의 Tensor 형태로 리턴
  def __getitem__(self, idx):
    x = torch.FloatTensor(self.x_data[idx])
    y = torch.FloatTensor(self.y_data[idx])
    return x, y

dataset = CustomDataset()
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)

model = torch.nn.Linear(3,1)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-5)

nb_epochs = 20
for epoch in range(nb_epochs + 1):
  for batch_idx, samples in enumerate(dataloader):
    # print(batch_idx)
    # print(samples)
    x_train, y_train = samples
    # H(x) 계산
    prediction = model(x_train)

    # cost 계산
    cost = F.mse_loss(prediction, y_train)

    # cost로 H(x) 계산
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    print('Epoch {:4d}/{} Batch {}/{} Cost: {:.6f}'.format(
        epoch, nb_epochs, batch_idx+1, len(dataloader),
        cost.item()
        ))
"""
Epoch    0/20 Batch 1/3 Cost: 29410.156250
Epoch    0/20 Batch 2/3 Cost: 7150.685059
Epoch    0/20 Batch 3/3 Cost: 3482.803467
... 중략 ...
Epoch   20/20 Batch 1/3 Cost: 0.350531
Epoch   20/20 Batch 2/3 Cost: 0.653316
Epoch   20/20 Batch 3/3 Cost: 0.010318
"""

# 임의의 입력 [73, 80, 75]를 선언
new_var =  torch.FloatTensor([[73, 80, 75]])
# 입력한 값 [73, 80, 75]에 대해서 예측값 y를 리턴받아서 pred_y에 저장
pred_y = model(new_var)
print("훈련 후 입력이 73, 80, 75일 때의 예측값 :", pred_y)
"""
훈련 후 입력이 73, 80, 75일 때의 예측값 : tensor([[151.2319]], grad_fn=<AddmmBackward>)
"""
jeongabae commented 2 years ago

4_1_LogisticRegression.py

#둘 중 하나를 결정하는 문제를 이진 분류(Binary Classification)
# 그리고 이진 분류를 풀기 위한 대표적인 알고리즘으로 로지스틱 회귀(Logistic Regression)가 있음
#->알고리즘의 이름은 회귀이지만 실제로는 분류(Classification) 작업에 사용

#1. 이진 분류(Binary Classification)
#로지스틱 회귀의 가설은 선형 회귀 때의 H(x)=Wx+b가 아니라,
# S자 모양의 그래프를 만들 수 있는 어떤 특정 함수 f를 추가적으로 사용하여 H(x)=f(Wx+b)의 가설을 사용
#S자 모양의 그래프를 그릴 수 있는 어떤 함수 f가 이미 널리 알려져있음. 바로 시그모이드 함수.

#2. 시그모이드 함수(Sigmoid function)
#선형 회귀에서는 최적의 W와 h를 찾는 것이 목표. 여기서도 그게 목표.
#파이썬에서는 그래프를 그릴 수 있는 도구로서 Matplotlib을 사용할 수 있습니다.
"""
%matplotlib inline #아나콘다(Anaconda)를 통해 Juptyer Notebook에서 사용되는 코드
                  #파이참에서 %matplolib inline를 사용하고 싶다면 plt.show()를 대신 작성하여 사용
"""

import numpy as np # 넘파이 사용
import matplotlib.pyplot as plt # 맷플롯립사용

def sigmoid(x): # 시그모이드 함수 정의
    return 1/(1+np.exp(-x))

#1. W가 1이고 b가 0인 그래프
x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)

plt.plot(x, y, 'g')
plt.plot([0,0],[1.0,0.0], ':') # 가운데 점선 추가
plt.title('Sigmoid Function')
plt.show()
"""
위의 그래프를 통해시그모이드 함수는 출력값을 0과 1사이의 값으로 조정하여 반환함을 알 수 있습니다. 
x가 0일 때 0.5의 값을 가집니다. x가 매우 커지면 1에 수렴합니다. 반면, x가 매우 작아지면 0에 수렴합니다.
"""

#2. W값의 변화에 따른 경사도의 변화
x = np.arange(-5.0, 5.0, 0.1)
y1 = sigmoid(0.5*x)
y2 = sigmoid(x)
y3 = sigmoid(2*x)

plt.plot(x, y1, 'r', linestyle='--') # W의 값이 0.5일때 빨간색선
plt.plot(x, y2, 'g') # W의 값이 1일때  초록색선
plt.plot(x, y3, 'b', linestyle='--') # W의 값이 2일때 파란색선
plt.plot([0,0],[1.0,0.0], ':') # 가운데 점선 추가
plt.title('Sigmoid Function')
plt.show() #위의 그래프는 b의 값에 따라서 그래프가 좌, 우로 이동하는 것을 보여줍니다.

#4. 시그모이드 함수를 이용한 분류
"""
시그모이드 함수는 입력값이 한없이 커지면 1에 수렴하고, 입력값이 한없이 작아지면 0에 수렴합니다.
시그모이드 함수의 출력값은 0과 1 사이의 값을 가지는데 이 특성을 이용하여 분류 작업에 사용할 수 있습니다.
예를 들어 임계값을 0.5라고 정해보겠습니다. 출력값이 0.5 이상이면 1(True), 0.5이하면 0(False)으로 판단하도록 할 수 있습니다. 
이를 확률이라고 생각하면 해당 레이블에 속할 확률이 50%가 넘으면 해당 레이블로 판단하고,
 해당 레이블에 속할 확률이 50%보다 낮으면 아니라고 판단하는 것으로 볼 수 있습니다.
"""

#3. 비용 함수(Cost function)
#비용 함수 수식에서 가설은 이제 H(x)=Wx+b가 아니라 H(x)=sigmoid(Wx+b)입니다.
"""
시그모이드 함수의 특징은 함수의 출력값이 0과 1사이의 값이라는 점입니다.
즉, 실제값이 1일 때 예측값이 0에 가까워지면 오차가 커져야 하며, 실제값이 0일 때, 예측값이 1에 가까워지면 오차가 커져야 합니다. 
그리고 이를 충족하는 함수가 바로 로그 함수입니다.
다음은 y=0.5에 대칭하는 두 개의 로그 함수 그래프입니다.
"""

#4. 파이토치로 로지스틱 회귀 구현하기
#파이토치로 로지스틱 회귀 중에서도 다수의 x로 부터 y를 예측하는 다중 로지스틱 회귀를 구현
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

torch.manual_seed(1)

#x_train과 y_train을 텐서로 선언
x_data = [[1, 2], [2, 3], [3, 1], [4, 3], [5, 3], [6, 2]]
y_data = [[0], [0], [0], [1], [1], [1]]
x_train = torch.FloatTensor(x_data)
y_train = torch.FloatTensor(y_data)

print(x_train.shape) #torch.Size([6, 2])
print(y_train.shape) #torch.Size([6, 1])

"""
 x_train을 X라고 하고, 이와 곱해지는 가중치 벡터를 W라고 하였을 때, 
 XW가 성립되기 위해서는  W벡터의 크기는 2 × 1이어야 
"""
#W와 b를 선언
W = torch.zeros((2, 1), requires_grad=True) # 크기는 2 x 1
b = torch.zeros(1, requires_grad=True)

#가설식을 세워보겠습니다. 파이토치에서는 e^x를 구현하기 위해서 torch.exp(x)를 사용
hypothesis = 1 / (1 + torch.exp(-(x_train.matmul(W) + b))) #행렬 연산을 사용한 가설식

# W와 b는 torch.zeros를 통해 전부 0으로 초기화 된 상태입니다. 이 상태에서 예측값을 출력
print(hypothesis) # 예측값인 H(x) 출력
"""실제값 y_train과 크기가 동일한 6 × 1의 크기를 가지는 예측값 벡터가 나오는데 모든 값이 0.5
tensor([[0.5000],
        [0.5000],
        [0.5000],
        [0.5000],
        [0.5000],
        [0.5000]], grad_fn=<MulBackward>)
"""

#다음은 torch.sigmoid를 사용하여 좀 더 간단히 구현한 가설식(더 간단)
hypothesis = torch.sigmoid(x_train.matmul(W) + b)
print(hypothesis)
"""
tensor([[0.5000],
        [0.5000],
        [0.5000],
        [0.5000],
        [0.5000],
        [0.5000]], grad_fn=<SigmoidBackward>)
"""

print(hypothesis)# 현재 예측값
print(y_train)# 실제값
"""
tensor([[0.5000],
        [0.5000],
        [0.5000],
        [0.5000],
        [0.5000],
        [0.5000]], grad_fn=<SigmoidBackward>)
tensor([[0.],
        [0.],
        [0.],
        [1.],
        [1.],
        [1.]])
"""
"""
#현재 총 6개의 원소가 존재하지만 하나의 샘플. 즉, 하나의 원소에 대해서만 오차를 구하는 식을 작성해보자
-(y_train[0] * torch.log(hypothesis[0]) +
  (1 - y_train[0]) * torch.log(1 - hypothesis[0]))
#tensor([0.6931], grad_fn=<NegBackward>)
"""
losses = -(y_train * torch.log(hypothesis) +
           (1 - y_train) * torch.log(1 - hypothesis))
print(losses)
"""
tensor([[0.6931],
        [0.6931],
        [0.6931],
        [0.6931],
        [0.6931],
        [0.6931]], grad_fn=<NegBackward>)
"""

#전체 오차에 대한 평균
cost = losses.mean()
print(cost)#tensor(0.6931, grad_fn=<MeanBackward1>)
#결과적으로 얻은 cost는 0.6931

"""
#지금까지 비용 함수의 값을 직접 구현하였는데, 사실 파이토치에서는 로지스틱 회귀의 비용 함수를 이미 구현해서 제공
#사용 방법은 torch.nn.functional as F와 같이 임포트 한 후에 F.binary_cross_entropy(예측값, 실제값)과 같이 사용하면됨
F.binary_cross_entropy(hypothesis, y_train)
"""

#모델의 훈련 과정까지 추가한 전체 코드
x_data = [[1, 2], [2, 3], [3, 1], [4, 3], [5, 3], [6, 2]]
y_data = [[0], [0], [0], [1], [1], [1]]
x_train = torch.FloatTensor(x_data)
y_train = torch.FloatTensor(y_data)
# 모델 초기화
W = torch.zeros((2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# optimizer 설정
optimizer = optim.SGD([W, b], lr=1)

nb_epochs = 1000
for epoch in range(nb_epochs + 1):

    # Cost 계산
    hypothesis = torch.sigmoid(x_train.matmul(W) + b)
    cost = -(y_train * torch.log(hypothesis) +
             (1 - y_train) * torch.log(1 - hypothesis)).mean()

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    # 100번마다 로그 출력
    if epoch % 100 == 0:
        print('Epoch {:4d}/{} Cost: {:.6f}'.format(
            epoch, nb_epochs, cost.item()
        ))
"""
Epoch    0/1000 Cost: 0.693147
... 중략 ...
Epoch 1000/1000 Cost: 0.019852
"""

"""
학습이 끝났습니다. 이제 훈련했던 훈련 데이터를 그대로 입력으로 사용했을 때, 제대로 예측하는지 확인해보겠습니다.
현재 W와 b는 훈련 후의 값을 가지고 있습니다. 현재 W와 b를 가지고 예측값을 출력해보겠습니다.
"""
hypothesis = torch.sigmoid(x_train.matmul(W) + b)
print(hypothesis)
"""
tensor([[2.7648e-04],
        [3.1608e-02],
        [3.8977e-02],
        [9.5622e-01],
        [9.9823e-01],
        [9.9969e-01]], grad_fn=<SigmoidBackward>)
"""

prediction = hypothesis >= torch.FloatTensor([0.5])
print(prediction)
"""실제값은 [[0], [0], [0], [1], [1], [1]]이므로, 이는 결과적으로 False, False, False, True, True, True와 동일
tensor([[False],
        [False],
        [False],
        [ True],
        [ True],
        [ True]])
"""
print(W)
print(b)
"""훈련이 된 후의 W와 b의 값을 출력
tensor([[3.2530],
        [1.5179]], requires_grad=True)
tensor([-14.4819], requires_grad=True)
"""
jeongabae commented 2 years ago

4_2_Implement_LogisticRegression_With_nn.Module.py

#비용 함수 수식에서 가설은 이제 H(x)=Wx+b가 아니라 H(x)=sigmoid(Wx+b)입니다.
#파이토치에서는 nn.Sigmoid()를 통해서 시그모이드 함수를 구현하므로
# 결과적으로 nn.Linear()의 결과를 nn.Sigmoid()를 거치게하면 로지스틱 회귀의 가설식이 됩니다.

#1. 파이토치의 nn.Linear와 nn.Sigmoid로 로지스틱 회귀 구현하기
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
torch.manual_seed(1)

#훈련 데이터를 텐서로 선언
x_data = [[1, 2], [2, 3], [3, 1], [4, 3], [5, 3], [6, 2]]
y_data = [[0], [0], [0], [1], [1], [1]]
x_train = torch.FloatTensor(x_data)
y_train = torch.FloatTensor(y_data)

#nn.Sequential()은 nn.Module 층을 차례로 쌓을 수 있도록함
#nn.Sequential()은 Wx+b와 같은 수식과 시그모이드 함수 등과 같은 여러 함수들을 연결해주는 역할
model = nn.Sequential(
   nn.Linear(2, 1), # input_dim = 2, output_dim = 1
   nn.Sigmoid() # 출력은 시그모이드 함수를 거친다
)

#현재 W와 b는 랜덤 초기화가 된 상태입니다. 훈련 데이터를 넣어 예측값을 확인
model(x_train)
"""현재 W와 b는 임의의 값을 가지므로 현재의 예측은 의미가 없습니다.
tensor([[0.4020],
        [0.4147],
        [0.6556],
        [0.5948],
        [0.6788],
        [0.8061]], grad_fn=<SigmoidBackward>)
"""

# 경사 하강법을 사용하여 훈련. 총 100번의 에포크를 수행
# optimizer 설정
optimizer = optim.SGD(model.parameters(), lr=1)

nb_epochs = 1000
for epoch in range(nb_epochs + 1):

    # H(x) 계산
    hypothesis = model(x_train)

    # cost 계산
    cost = F.binary_cross_entropy(hypothesis, y_train)

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    # 20번마다 로그 출력
    if epoch % 10 == 0:
        prediction = hypothesis >= torch.FloatTensor([0.5]) # 예측값이 0.5를 넘으면 True로 간주
        correct_prediction = prediction.float() == y_train # 실제값과 일치하는 경우만 True로 간주
        accuracy = correct_prediction.sum().item() / len(correct_prediction) # 정확도를 계산
        print('Epoch {:4d}/{} Cost: {:.6f} Accuracy {:2.2f}%'.format( # 각 에포크마다 정확도를 출력
            epoch, nb_epochs, cost.item(), accuracy * 100,
        ))
"""중간부터 정확도는 100%가 나오기 시작
Epoch    0/1000 Cost: 0.539713 Accuracy 83.33%
... 중략 ...
Epoch 1000/1000 Cost: 0.019843 Accuracy 100.00%
"""

model(x_train)
"""
tensor([[0.0240],
        [0.1476],
        [0.2739],
        [0.7967],
        [0.9491],
        [0.9836]], grad_fn=<SigmoidBackward>)
"""
"""
0.5를 넘으면 True, 그보다 낮으면 False로 간주합니다. 실제값은 [[0], [0], [0], [1], [1], [1]]입니다.
 이는 False, False, False, True, True, True에 해당되므로 전부 실제값과 일치하도록 예측한 것을 확인할 수 있습니다.
"""

#훈련 후의 W와 b의 값을 출력
print(list(model.parameters()))
"""
print(list(model.parameters()))
[Parameter containing:
tensor([[3.2534, 1.5181]], requires_grad=True), Parameter containing:
tensor([-14.4839], requires_grad=True)]
"""

#2. 인공 신경망으로 표현되는 로지스틱 회귀.
jeongabae commented 2 years ago

4_3_Implement_PyTorchModel_With_Class.py

#1. 모델을 클래스로 구현하기
"""앞에서 구현한 로지스틱 회귀 모델
model = nn.Sequential(
   nn.Linear(2, 1), # input_dim = 2, output_dim = 1
   nn.Sigmoid() # 출력은 시그모이드 함수를 거친다
)
"""

#클래스로 구현한 로지스텍 모델
"""
class BinaryClassifier(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(2, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        return self.sigmoid(self.linear(x))
"""

#2. 로지스틱 회귀 클래스로 구현하기
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

torch.manual_seed(1)

x_data = [[1, 2], [2, 3], [3, 1], [4, 3], [5, 3], [6, 2]]
y_data = [[0], [0], [0], [1], [1], [1]]
x_train = torch.FloatTensor(x_data)
y_train = torch.FloatTensor(y_data)

class BinaryClassifier(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(2, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        return self.sigmoid(self.linear(x))

model = BinaryClassifier()

# optimizer 설정
optimizer = optim.SGD(model.parameters(), lr=1)

nb_epochs = 1000
for epoch in range(nb_epochs + 1):

    # H(x) 계산
    hypothesis = model(x_train)

    # cost 계산
    cost = F.binary_cross_entropy(hypothesis, y_train)

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    # 20번마다 로그 출력
    if epoch % 10 == 0:
        prediction = hypothesis >= torch.FloatTensor([0.5]) # 예측값이 0.5를 넘으면 True로 간주
        correct_prediction = prediction.float() == y_train # 실제값과 일치하는 경우만 True로 간주
        accuracy = correct_prediction.sum().item() / len(correct_prediction) # 정확도를 계산
        print('Epoch {:4d}/{} Cost: {:.6f} Accuracy {:2.2f}%'.format( # 각 에포크마다 정확도를 출력
            epoch, nb_epochs, cost.item(), accuracy * 100,
        ))
jeongabae commented 2 years ago

5_1_One-HotEncoding.py

#1. 원-핫 인코딩(One-hot encoding)이란?
"""
원-핫 인코딩은 선택해야 하는 선택지의 개수만큼의 차원을 가지면서,
각 선택지의 인덱스에 해당하는 원소에는 1, 나머지 원소는 0의 값을 가지도록 하는 표현 방법
cf)원-핫 인코딩으로 표현된 벡터를 원-핫 벡터(one-hot vector)라고 함
"""

#2. 원-핫 벡터의 무작위성
"""
꼭 실제값을 원-핫 벡터로 표현해야만 다중 클래스 분류 문제를 풀 수 있는 것은 아니지만, 
대부분의 다중 클래스 분류 문제가 각 클래스 간의 관계가 균등하다는 점에서 원-핫 벡터는 이러한 점을 표현할 수 있는 적절한 표현 방법

정수 인코딩이 아니라 원-핫 인코딩을 사용하는 것이 보다 클래스의 성질을 잘 표현하였다고 할 수 있음.

일반적인 분류 문제에서는 각 클래스는 순서의 의미를 갖고 있지 않으므로 각 클래스 간의 오차는 균등한 것이 옳음.
정수 인코딩과 달리 원-핫 인코딩은 분류 문제 모든 클래스 간의 관계를 균등하게 분배.

원-핫 벡터는 이처럼 각 클래스의 표현 방법이 무작위성을 가진다는 점을 표현 가능
원-핫 벡터의 관계의 무작위성은 때로는 단어의 유사성을 구할 수 없다는 단점으로 언급되기도 함.
"""
jeongabae commented 2 years ago

5_2_SoftmaxRegression.py

#소프트맥스 회귀를 통해 3개 이상의 선택지 중에서 1개를 고르는 다중 클래스 분류(Multi-Class Classification)

#1. 다중 클래스 분류(Multi-class Classification)
#이진 분류가 두 개의 답 중 하나를 고르는 문제였다면,
# 세 개 이상의 답 중 하나를 고르는 문제를 다중 클래스 분류(Multi-class Classification)라고 함
"""
1. 로지스틱 회귀
로지스틱 회귀에서 시그모이드 함수는 예측값을 0과 1 사이의 값으로 만듦.
예를 들어 스팸 메일 분류기를 로지스틱 회귀로 구현하였을 때,
출력이 0.75이라면 이는 이메일이 스팸일 확률이 75%라는 의미가 됩니다. 반대로, 스팸 메일이 아닐 확률은 25%가 됩니다. 이 두 확률의 총 합은 1

2. 소프트맥스 회귀
소프트맥스 회귀는 확률의 총 합이 1이 되는 이 아이디어를 다중 클래스 분류 문제에 적용합니다. 소프트맥스 회귀는 각 클래스.
즉, 각 선택지마다 소수 확률을 할당합니다. 이때 총 확률의 합은 1이 되어야 합니다. 이렇게 되면 각 선택지가 정답일 확률로 표현됩니다.
가설 H(x)=softmax(WX+B)
"""

#2. 소프트맥스 함수(Softmax function)
#소프트맥스 함수는 분류해야하는 정답지(클래스)의 총 개수를 k라고 할 때, k차원의 벡터를 입력받아 각 클래스에 대한 확률을 추정

#3. 붓꽃 품종 분류하기 행렬 연산으로 이해하기
#나머지는 https://wikidocs.net/59427참고! 잘 설명 되어 있음.

#4. 비용 함수(Cost function)
#소프트맥스 회귀에서는 비용 함수로 크로스 엔트로피 함수를 사용
"""
1) 크로스 엔트로피 함수
2) 이진 분류에서의 크로스 엔트로피 함수
"""
jeongabae commented 2 years ago

5_3_Implement_SoftmaxRegression_CostFunction.py

#앞으로의 코드에서는 아래 세 줄의 코드가 이미 진행되었다고 가정
import torch
import torch.nn.functional as F

torch.manual_seed(1)

#1. 파이토치로 소프트맥스의 비용 함수 구현하기 (로우-레벨)

#3개의 원소를 가진 벡터 텐서를 정의
z = torch.FloatTensor([1, 2, 3])

#이 텐서를 소프트맥스 함수의 입력으로 사용하고, 그 결과를 확인
hypothesis = F.softmax(z, dim=0)
print(hypothesis)
"""3개의 원소의 값이 0과 1사이의 값을 가지는 벡터로 변환된 것 확인 가능
tensor([0.0900, 0.2447, 0.6652])
"""
hypothesis.sum()#tensor(1.) #총 원소의 값의 합은 1

# 아래 코드부터는 비용 함수를 직접 구현

# 임의의 3 × 5 행렬의 크기를 가진 텐서
z = torch.rand(3, 5, requires_grad=True)

#텐서에 대해서 소프트맥스 함수를 적용
hypothesis = F.softmax(z, dim=1)# 각 샘플에 대해서 소프트맥스 함수를 적용하여야 -> 두번째 차원에 대해서 소프트맥스 함수를 적용한다는 의미에서 dim=1
print(hypothesis)
"""각 행의 원소들의 합은 1이 되는 텐서로 변환됨.
tensor([[0.2645, 0.1639, 0.1855, 0.2585, 0.1277],
        [0.2430, 0.1624, 0.2322, 0.1930, 0.1694],
        [0.2226, 0.1986, 0.2326, 0.1594, 0.1868]], grad_fn=<SoftmaxBackward>)
"""
"""
소프트맥스 함수의 출력값은 결국 예측값.
즉, 위 텐서는 3개의 샘플에 대해서 5개의 클래스 중 어떤 클래스가 정답인지를 예측한 결과.
"""

# 각 샘플에 대해서 임의의 레이블 만듦.
y = torch.randint(5, (3,)).long()
print(y) #tensor([0, 2, 1])

#각 레이블에 대해서 원-핫 인코딩을 수행
# 모든 원소가 0의 값을 가진 3 × 5 텐서 생성
y_one_hot = torch.zeros_like(hypothesis) #torch.zeros_like(hypothesis)를 통해 모든 원소가 0의 값을 가진 3 × 5 텐서만듦.(이 텐서는 y_one_hot에 저장이 된 상태)
#scatter의 첫번째 인자로 dim=1에 대해서 수행하라고 알려주고, 세번째 인자에 숫자 1을 넣어주므로서 두번째 인자인 y_unsqeeze(1)이 알려주는 위치에 숫자 1을 넣음.
y_one_hot.scatter_(1, y.unsqueeze(1), 1) # y.unsqueeze(1)를 하면 (3,)의 크기를 가졌던 y 텐서는 (3 × 1) 텐서가 됨
                                         #연산 뒤에 _를 붙이면 In-place Operation (덮어쓰기 연산)
"""
print(y.unsqueeze(1))
실행결과
tensor([[0],
        [2],
        [1]])
"""
print(y_one_hot)
"""
tensor([[1., 0., 0., 0., 0.],
        [0., 0., 1., 0., 0.],
        [0., 1., 0., 0., 0.]])
"""

cost = (y_one_hot * -torch.log(hypothesis)).sum(dim=1).mean()
print(cost) #tensor(1.4689, grad_fn=<MeanBackward1>)

#2. 파이토치로 소프트맥스의 비용 함수 구현하기 (하이-레벨)
#2-1. F.softmax() + torch.log() = F.log_softmax()
# Low level
torch.log(F.softmax(z, dim=1))
# High level
F.log_softmax(z, dim=1)# 파이토치에서는 F.log_softmax()라는 도구를 제공
"""둘 다 출력은 다음과 같음.
tensor([[-1.3301, -1.8084, -1.6846, -1.3530, -2.0584],
        [-1.4147, -1.8174, -1.4602, -1.6450, -1.7758],
        [-1.5025, -1.6165, -1.4586, -1.8360, -1.6776]], grad_fn=<LogSoftmaxBackward>)
"""

#2-2. F.log_softmax() + F.nll_loss() = F.cross_entropy()
"""
 로우-레벨로 구현한 비용 함수
 # Low level
# 첫번째 수식
(y_one_hot * -torch.log(F.softmax(z, dim=1))).sum(dim=1).mean() #tensor(1.4689, grad_fn=<MeanBackward1>)

# 두번째 수식
(y_one_hot * - F.log_softmax(z, dim=1)).sum(dim=1).mean() #tensor(1.4689, grad_fn=<MeanBackward0>)

"""
# High level
# 세번째 수식
F.nll_loss(F.log_softmax(z, dim=1), y) #F.nll_loss()를 사용할 때는 원-핫 벡터를 넣을 필요없이 바로 실제값을 인자로 사용
                            #tensor(1.4689, grad_fn=<NllLossBackward>)

# 네번째 수식
F.cross_entropy(z, y) #F.cross_entropy()는 F.log_softmax()와 F.nll_loss()를 포함
                    # #tensor(1.4689, grad_fn=<NllLossBackward>)
jeongabae commented 2 years ago

5_4_Implement_SoftmaxRegression.py

# 소프트맥스 회귀를 로우-레벨과 F.cross_entropy를 사용해서 구현
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

torch.manual_seed(1)

#x_train의 각 샘플은 4개의 특성을 가지고 있으며, 총 8개의 샘플이 존재
x_train = [[1, 2, 1, 1],
           [2, 1, 3, 2],
           [3, 1, 3, 4],
           [4, 1, 5, 5],
           [1, 7, 5, 5],
           [1, 2, 5, 6],
           [1, 6, 6, 6],
           [1, 7, 7, 7]]
#y_train은 각 샘플에 대한 레이블인데, 여기서는 0, 1, 2의 값을 가지는 것으로 보아 총 3개의 클래스가 존재
y_train = [2, 2, 2, 1, 1, 1, 0, 0]
x_train = torch.FloatTensor(x_train)
y_train = torch.LongTensor(y_train)

#1. 소프트맥스 회귀 구현하기(로우-레벨)
print(x_train.shape) #torch.Size([8, 4])
print(y_train.shape) #torch.Size([8])

#x_train의 크기는 8 × 4이며, y_train의 크기는 8 × 1입니다. 그런데 최종 사용할 레이블은 y_train에서 원-핫 인코딩을 한 결과이어야 합니다.
# 클래스의 개수는 3개이므로 y_train에 원-핫 인코딩한 결과는 8 × 3의 개수를 가져야 합니다.
y_one_hot = torch.zeros(8, 3)
y_one_hot.scatter_(1, y_train.unsqueeze(1), 1)
print(y_one_hot.shape) #torch.Size([8, 3])

#y_train에서 원-핫 인코딩을 한 결과인 y_one_hot의 크기는 8 × 3입니다. 즉, W 행렬의 크기는 4 × 3이어야
# 모델 초기화 : W와 b를 선언
W = torch.zeros((4, 3), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# optimizer 설정 : 옵티마이저로는 경사 하강법을 사용
optimizer = optim.SGD([W, b], lr=0.1)

#F.softmax()와 torch.log()를 사용하여 가설과 비용 함수를 정의하고, 총 1,000번의 에포크를 수행
nb_epochs = 1000
for epoch in range(nb_epochs + 1):

    # 가설
    hypothesis = F.softmax(x_train.matmul(W) + b, dim=1)

    # 비용 함수
    cost = (y_one_hot * -torch.log(hypothesis)).sum(dim=1).mean()

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    # 100번마다 로그 출력
    if epoch % 100 == 0:
        print('Epoch {:4d}/{} Cost: {:.6f}'.format(
            epoch, nb_epochs, cost.item()
        ))

#2. 소프트맥스 회귀 구현하기(하이-레벨)
#->F.cross_entropy()는 그 자체로 소프트맥스 함수를 포함하고 있으므로 가설에서는 소프트맥스 함수를 사용할 필요가 없습니다.
# 모델 초기화
W = torch.zeros((4, 3), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# optimizer 설정
optimizer = optim.SGD([W, b], lr=0.1)

nb_epochs = 1000
for epoch in range(nb_epochs + 1):

    # Cost 계산
    z = x_train.matmul(W) + b
    cost = F.cross_entropy(z, y_train)

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    # 100번마다 로그 출력
    if epoch % 100 == 0:
        print('Epoch {:4d}/{} Cost: {:.6f}'.format(
            epoch, nb_epochs, cost.item()
        ))

#3. 소프트맥스 회귀 nn.Module로 구현하기
#->선형 회귀에서 구현에 사용했던 nn.Linear()를 사용합니다.
# ->output_dim이 1이었던 선형 회귀때와 달리 output_dim은 이제 클래스의 개수여야 합니다.
# 모델을 선언 및 초기화. 4개의 특성을 가지고 3개의 클래스로 분류. input_dim=4, output_dim=3.
model = nn.Linear(4, 3)

# optimizer 설정
optimizer = optim.SGD(model.parameters(), lr=0.1)

# F.cross_entropy()를 사용할 것이므로 따로 소프트맥스 함수를 가설에 정의하지 않습니다.
nb_epochs = 1000
for epoch in range(nb_epochs + 1):

    # H(x) 계산
    prediction = model(x_train)

    # cost 계산
    cost = F.cross_entropy(prediction, y_train)

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    # 20번마다 로그 출력
    if epoch % 100 == 0:
        print('Epoch {:4d}/{} Cost: {:.6f}'.format(
            epoch, nb_epochs, cost.item()
        ))

#4. 소프트맥스 회귀 클래스로 구현하기
#->소프트맥스 회귀를 nn.Module을 상속받은 클래스로 구현
class SoftmaxClassifierModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(4, 3) # Output이 3!

    def forward(self, x):
        return self.linear(x)

model = SoftmaxClassifierModel()

# optimizer 설정
optimizer = optim.SGD(model.parameters(), lr=0.1)

nb_epochs = 1000
for epoch in range(nb_epochs + 1):

    # H(x) 계산
    prediction = model(x_train)

    # cost 계산
    cost = F.cross_entropy(prediction, y_train)

    # cost로 H(x) 개선
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    # 20번마다 로그 출력
    if epoch % 100 == 0:
        print('Epoch {:4d}/{} Cost: {:.6f}'.format(
            epoch, nb_epochs, cost.item()
        ))
jeongabae commented 2 years ago

5_5_MNIST.py

#1. MNIST 데이터 이해하기
#MNIST는 숫자 0부터 9까지의 이미지로 구성된 손글씨 데이터셋
#MNIST 문제는 손글씨로 적힌 숫자 이미지가 들어오면, 그 이미지가 무슨 숫자인지 맞추는 문제

#784차원의 벡터로 만드는 코드를 미리보기로 보면
"""
for X, Y in data_loader:
  # 입력 이미지를 [batch_size × 784]의 크기로 reshape
  # 레이블은 원-핫 인코딩
  X = X.view(-1, 28*28)
"""

#2. 토치비전(torchvision) 소개하기
# torchvision은 유명한 데이터셋들, 이미 구현되어져 있는 유명한 모델들, 일반적인 이미지 전처리 도구들을 포함하고 있는 패키지
#torchvision에 어떤 데이터셋들(datasets)과 모델들(models) 그리고 어떤 전처리 방법들(transforms)을 제공

#3. 분류기 구현을 위한 사전 설정
import torch
import torchvision.datasets as dsets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import torch.nn as nn
import matplotlib.pyplot as plt
import random

USE_CUDA = torch.cuda.is_available() # GPU를 사용가능하면 True, 아니라면 False를 리턴
device = torch.device("cuda" if USE_CUDA else "cpu") # GPU 사용 가능하면 사용하고 아니면 CPU 사용
print("다음 기기로 학습합니다:", device)
"""
구글의 Colab에서 '런타임 > 런타임 유형 변경 > 하드웨어 가속기 > GPU'를 선택하면 USE_CUDA의 값이 True가 되면서 '다음 기기로 학습합니다: 
cuda'라는 출력이 나옵니다. 즉, GPU로 연산하겠다는 의미입니다. 
반면에 '하드웨어 가속기 > None'을 선택하면 USE_CUDA의 값이 False가 되면서 '다음 기기로 학습합니다: cpu'라는 출력이 나옵니다. 
즉, CPU로 연산하겠다는 의미입니다.
"""

# for reproducibility
random.seed(777)
torch.manual_seed(777)
if device == 'cuda':
    torch.cuda.manual_seed_all(777)

# hyperparameters
training_epochs = 15
batch_size = 100

#4. MNIST 분류기 구현하기
#torchvision.datasets.dsets.MNIST를 사용하여 MNIST 데이터셋 불러오기
# MNIST dataset
mnist_train = dsets.MNIST(root='MNIST_data/', #첫번째 인자 root는 MNIST 데이터를 다운로드 받을 경로
                          train=True,       #. 두번째 인자 train은 인자로 True를 주면, MNIST의 훈련 데이터를 리턴받으며 False를 주면 테스트 데이터를 리턴받음.
                          transform=transforms.ToTensor(), #세번째 인자 transform은 현재 데이터를 파이토치 텐서로 변환
                          download=True) #네번째 인자 download는 해당 경로에 MNIST 데이터가 없다면 다운로드 받겠다는 의미

mnist_test = dsets.MNIST(root='MNIST_data/',
                         train=False,
                         transform=transforms.ToTensor(),
                         download=True)
# dataset loader
#첫번째 인자인 dataset은 로드할 대상을 의미하며,
# 두번째 인자인 batch_size는 배치 크기,
# shuffle은 매 에포크마다 미니 배치를 셔플할 것인지의 여부,
# drop_last는 마지막 배치를 버릴 것인지
data_loader = DataLoader(dataset=mnist_train,
                                          batch_size=batch_size, # 배치 크기는 100
                                          shuffle=True,
                                          drop_last=True)

# MNIST data image of shape 28 * 28 = 784
linear = nn.Linear(784, 10, bias=True).to(device)

# 비용 함수와 옵티마이저 정의
criterion = nn.CrossEntropyLoss().to(device) # 내부적으로 소프트맥스 함수를 포함하고 있음.
optimizer = torch.optim.SGD(linear.parameters(), lr=0.1)

for epoch in range(training_epochs): # 앞서 training_epochs의 값은 15로 지정함.
    avg_cost = 0
    total_batch = len(data_loader)

    for X, Y in data_loader:
        # 배치 크기가 100이므로 아래의 연산에서 X는 (100, 784)의 텐서가 된다.
        X = X.view(-1, 28 * 28).to(device)
        # 레이블은 원-핫 인코딩이 된 상태가 아니라 0 ~ 9의 정수.
        Y = Y.to(device)

        optimizer.zero_grad()
        hypothesis = linear(X)
        cost = criterion(hypothesis, Y)
        cost.backward()
        optimizer.step()

        avg_cost += cost / total_batch

    print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.9f}'.format(avg_cost))

print('Learning finished')
"""
Epoch: 0001 cost = 0.535468459
Epoch: 0002 cost = 0.359274209
Epoch: 0003 cost = 0.331187516
Epoch: 0004 cost = 0.316578060
Epoch: 0005 cost = 0.307158142
Epoch: 0006 cost = 0.300180763
Epoch: 0007 cost = 0.295130193
Epoch: 0008 cost = 0.290851474
Epoch: 0009 cost = 0.287417054
Epoch: 0010 cost = 0.284379572
Epoch: 0011 cost = 0.281825274
Epoch: 0012 cost = 0.279800713
Epoch: 0013 cost = 0.277808994
Epoch: 0014 cost = 0.276154339
Epoch: 0015 cost = 0.274440885
Learning finished
"""

# 테스트 데이터를 사용하여 모델을 테스트한다.
with torch.no_grad(): # torch.no_grad()를 하면 gradient 계산을 수행하지 않는다.
    X_test = mnist_test.test_data.view(-1, 28 * 28).float().to(device)
    Y_test = mnist_test.test_labels.to(device)

    prediction = linear(X_test)
    correct_prediction = torch.argmax(prediction, 1) == Y_test
    accuracy = correct_prediction.float().mean()
    print('Accuracy:', accuracy.item())

    # MNIST 테스트 데이터에서 무작위로 하나를 뽑아서 예측을 해본다
    r = random.randint(0, len(mnist_test) - 1)
    X_single_data = mnist_test.test_data[r:r + 1].view(-1, 28 * 28).float().to(device)
    Y_single_data = mnist_test.test_labels[r:r + 1].to(device)

    print('Label: ', Y_single_data.item())
    single_prediction = linear(X_single_data)
    print('Prediction: ', torch.argmax(single_prediction, 1).item())

    plt.imshow(mnist_test.test_data[r:r + 1].view(28, 28), cmap='Greys', interpolation='nearest')
    plt.show()

    """
    Accuracy: 0.8883000016212463
    Label:  8
    Prediction:  3
    """
jeongabae commented 2 years ago

7_1_Convolution_And_Pooling.py

#합성곱 신경망(Convolutional Neural Network)은 이미지 처리에 탁월한 성능을 보이는 신경망
#합성곱 신경망은 크게 합성곱층과(Convolution layer)와 풀링층(Pooling layer)으로 구성

#1. 합성곱 신경망의 대두
#다층 퍼셉트론은 몇 가지 픽셀만 값이 달라져도 민감하게 예측에 영향을 받는다는 단점
#이미지의 공간적인 구조 정보를 보존하면서 학습할 수 있는 방법이 필요해졌고, 이를 위해 사용하는 것이 합성곱 신경망

#2. 채널(Channel)(=깊이(depth))
#이미지는 (높이, 너비, 채널)이라는 3차원 텐서
#높이는 이미지의 세로 방향 픽셀 수, 너비는 이미지의 가로 방향 픽셀 수, 채널은 색 성분
#흑백 이미지는 채널 수가 1이며, 각 픽셀은 0부터 255 사이의 값을 가짐.
#하나의 픽셀은 세 가지 색깔, 삼원색의 조합으로 이루어집니다.
# 만약, 높이가 28, 너비가 28인 컬러 이미지가 있다면 이 이미지의 텐서는 (28 × 28 × 3)의 크기를 가지는 3차원 텐서

#3. 합성곱 연산(Convolution operation)
#합성곱층은 합성곱 연산을 통해서 이미지의 특징을 추출하는 역할
#커널(kernel) 또는 필터(filter)라는  n*m 크기의 행렬로 높이*너비 크기의 이미지를
#처음부터 끝까지 겹치며 훑으면서 n*m크기의 겹쳐지는 부분의 각 이미지와 커널의 원소의 값을 곱해서 모두 더한 값을 출력으로 하는 것
#커널(kernel)은 일반적으로 3 × 3 또는 5 × 5를 사용

#입력으로부터 커널을 사용하여 합성곱 연산을 통해 나온 결과를 특성 맵(feature map)
#이동 범위를 스트라이드(stride)

#4. 패딩(Padding)
#합성곱 연산 이후에도 특성 맵의 크기가 입력의 크기와 동일하게 유지되도록 하고 싶다면 패딩(padding)을 사용
#패딩은 (합성곱 연산을 하기 전에) 입력의 가장자리에 지정된 개수의 폭만큼 행과 열을 추가해주는 것
#주로 값을 0으로 채우는 제로 패딩(zero padding)을 사용

#5. 가중치와 편향
#최종적으로 특성 맵을 얻기 위해서는 동일한 커널로 이미지 전체를 훑으며 합성곱 연산을 진행
#각 합성곱 연산마다 이미지의 모든 픽셀을 사용하는 것이 아니라, 커널과 맵핑되는 픽셀만을 입력으로 사용
#->결국 합성곱 신경망은 다층 퍼셉트론을 사용할 때보다 훨씬 적은 수의 가중치를 사용하며 공간적 구조 정보를 보존한다는 특징
#합성곱 연산을 통해서 특성 맵을 얻고, 활성화 함수를 지나는 연산을 하는 합성곱 신경망의 은닉층을 합성곱 신경망에서는 합성곱 층(convolution layer)

#합성곱 신경망에도 편향(bias)를 당연히 추가할 수 있습니다.
# 만약, 편향을 사용한다면 커널을 적용한 뒤에 더해집니다. 편향은 하나의 값만 존재하며, 커널이 적용된 결과의 모든 원소에 더해집니다.

#6. 특성 맵의 크기 계산 방법
#7. 다수의 채널을 가질 경우의 합성곱 연산(3차원 텐서의 합성곱 연산)
#8. 3차원 텐서의 합성곱 연산
#https://wikidocs.net/62306 참고

#9. 풀링(Pooling)
#일반적으로 합성곱 층(합성곱 연산 + 활성화 함수) 다음에는 풀링 층을 추가하는 것이 일반적
#풀링 층에서는 특성 맵을 다운샘플링하여 특성 맵의 크기를 줄이는 풀링 연산 이뤄짐
#일반적으로 최대 풀링(max pooling)과 평균 풀링(average pooling)이 사용됨.
"""
풀링 연산은 커널과 스트라이드 개념이 존재한다는 점에서 합성곱 연산과 유사하지만,
합성곱 연산과의 차이점은 학습해야 할 가중치가 없으며 연산 후에 채널 수가 변하지 않는다는 점
"""
jeongabae commented 2 years ago

7_2_Sort_MNIST_With_CNN.py

#1. 모델 이해하기

"""1. 첫번째 표기 방법
합성곱(nn.Cov2d) + 활성화 함수(nn.ReLU)를 하나의 합성곱 층으로 보고, 맥스풀링(nn.MaxPoold2d)은 풀링 층으로 별도로 명명
"""
"""2. 두번째 표기 방법
합성곱(nn.Conv2d) + 활성화 함수(nn.ReLU) + 맥스풀링(nn.MaxPoold2d)을 하나의 합성곱 층으로 봄

#모델의 아키텍처는 총 3개의 층으로 구성!!
# 1번 레이어 : 합성곱층(Convolutional layer)
합성곱(in_channel = 1, out_channel = 32, kernel_size=3, stride=1, padding=1) + 활성화 함수 ReLU
맥스풀링(kernel_size=2, stride=2))

# 2번 레이어 : 합성곱층(Convolutional layer)
합성곱(in_channel = 32, out_channel = 64, kernel_size=3, stride=1, padding=1) + 활성화 함수 ReLU
맥스풀링(kernel_size=2, stride=2))

# 3번 레이어 : 전결합층(Fully-Connected layer)
특성맵을 펼친다. # batch_size × 7 × 7 × 64 → batch_size × 3136
전결합층(뉴런 10개) + 활성화 함수 Softmax
"""

#2. 모델 구현하기
#2-1. 필요한 도구 임포트와 입력의 정의
import torch
import torch.nn as nn

# 배치 크기 × 채널 × 높이(height) × 너비(widht)의 크기의 텐서를 선언
inputs = torch.Tensor(1, 1, 28, 28) #텐서의 크기는 1 × 1 × 28 × 28입니다
print('텐서의 크기 : {}'.format(inputs.shape)) #텐서의 크기 : torch.Size([1, 1, 28, 28])

#2. 합성곱층과 풀링 선언하기
# 첫번째 합성곱 층을 구현
conv1 = nn.Conv2d(1, 32, 3, padding=1) # 1채널 짜리를 입력받아서 32채널을 뽑아내는데 커널 사이즈는 3이고 패딩은 1
print(conv1)#Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))

#두번째 합성곱 층을 구현
conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1) #32채널 짜리를 입력받아서 64채널을 뽑아내는데 커널 사이즈는 3이고 패딩은 1
print(conv2) #Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))

#맥스풀링을 구현
pool = nn.MaxPool2d(2) #정수 하나를 인자로 넣으면 커널 사이즈와 스트라이드가 둘 다 해당값으로 지정됨
print(pool) #MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)

#3. 구현체를 연결하여 모델 만들기
#지금까지는 선언만한 것이고 아직 이들을 연결시키지는 않았음
#이들을 연결시켜서 모델을 완성

#첫번째 합성곱층을 통과시키고 합성곱층을 통과시킨 후의 텐서의 크기알아보기
out = conv1(inputs)
print(out.shape) #torch.Size([1, 32, 28, 28]) #32채널의 28너비 28높이의 텐서
"""
채널이 32가 나온 이유는 conv1의 out_channel로 32를 지정해주었기 때문.
또한, 28너비 28높이가 된 이유는 패딩을 1폭으로 하고 3 × 3 커널을 사용하면 크기가 보존되기 때문
"""

# 맥스풀링을 통과한 후의 텐서의 크기 알아보기
out = pool(out)
print(out.shape) #torch.Size([1, 32, 14, 14])

# 두번째 합성곱층에 통과시키고 통과한 후의 텐서의 크기 알아보기
out = conv2(out)
print(out.shape) #torch.Size([1, 64, 14, 14])
"""
채널이 64가 나온 이유는 conv2의 out_channel로 64를 지정해주었기 때문.
또한, 14너비 14높이가 된 이유는 패딩을 1폭으로 하고 3 × 3 커널을 사용하면 크기가 보존되기 때문
"""

#맥스풀링을 통과시키고 맥스풀링을 통과한 후의 텐서의 크기 알아보기
out = pool(out)
print(out.shape) #torch.Size([1, 64, 7, 7])

#텐서를 펼치는 작업
"""
#텐서의 n번째 차원을 접근하게 해주는 .size(n)
out.size(0) #1
out.size(1) #64
out.size(2) #7
out.size(3) #7
"""

# .view()를 사용하여 텐서를 펼치는 작업
# 첫번째 차원인 배치 차원은 그대로 두고 나머지는 펼쳐라
out = out.view(out.size(0), -1)
print(out.shape) #torch.Size([1, 3136]) #배치 차원을 제외하고 모두 하나의 차원으로 통합됨.

#전결합층(Fully-Connteced layer)를 통과시킴.
fc = nn.Linear(3136, 10) # input_dim = 3,136, output_dim = 10 #출력층으로 10개의 뉴런을 배치하여 10개 차원의 텐서로 변환
out = fc(out)
print(out.shape) #torch.Size([1, 10])

#3. CNN으로 MNIST 분류하기
import torch
import torchvision.datasets as dsets
import torchvision.transforms as transforms
import torch.nn.init

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

# 랜덤 시드 고정
torch.manual_seed(777)

# GPU 사용 가능일 경우 랜덤 시드 고정
if device == 'cuda':
    torch.cuda.manual_seed_all(777)

#학습에 사용할 파라미터를 설정
learning_rate = 0.001
training_epochs = 15
batch_size = 100

#데이터로더를 사용하여 데이터를 다루기 위해서 데이터셋을 정의
mnist_train = dsets.MNIST(root='MNIST_data/', # 다운로드 경로 지정
                          train=True, # True를 지정하면 훈련 데이터로 다운로드
                          transform=transforms.ToTensor(), # 텐서로 변환
                          download=True)

mnist_test = dsets.MNIST(root='MNIST_data/', # 다운로드 경로 지정
                         train=False, # False를 지정하면 테스트 데이터로 다운로드
                         transform=transforms.ToTensor(), # 텐서로 변환
                         download=True)

#데이터로더를 사용하여 배치 크기를 지정
data_loader = torch.utils.data.DataLoader(dataset=mnist_train,
                                          batch_size=batch_size,
                                          shuffle=True,
                                          drop_last=True)

# 클래스로 모델을 설계
class CNN(torch.nn.Module):

    def __init__(self):
        super(CNN, self).__init__()
        # 첫번째층
        # ImgIn shape=(?, 28, 28, 1)
        #    Conv     -> (?, 28, 28, 32)
        #    Pool     -> (?, 14, 14, 32)
        self.layer1 = torch.nn.Sequential(
            torch.nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size=2, stride=2))

        # 두번째층
        # ImgIn shape=(?, 14, 14, 32)
        #    Conv      ->(?, 14, 14, 64)
        #    Pool      ->(?, 7, 7, 64)
        self.layer2 = torch.nn.Sequential(
            torch.nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size=2, stride=2))

        # 전결합층 7x7x64 inputs -> 10 outputs
        self.fc = torch.nn.Linear(7 * 7 * 64, 10, bias=True)

        # 전결합층 한정으로 가중치 초기화
        torch.nn.init.xavier_uniform_(self.fc.weight)

    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = out.view(out.size(0), -1)   # 전결합층을 위해서 Flatten
        out = self.fc(out)
        return out

# CNN 모델 정의
model = CNN().to(device)

#비용 함수와 옵티마이저를 정의
criterion = torch.nn.CrossEntropyLoss().to(device)    # 비용 함수에 소프트맥스 함수 포함되어져 있음.
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

#총 배치의 수를 출력
total_batch = len(data_loader)
print('총 배치의 수 : {}'.format(total_batch)) #총 배치의 수 : 600

#총 배치의 수는 600입니다. 그런데 배치 크기를 100으로 했으므로 결국 훈련 데이터는 총 60,000개
for epoch in range(training_epochs):
    avg_cost = 0

    for X, Y in data_loader: # 미니 배치 단위로 꺼내온다. X는 미니 배치, Y느 ㄴ레이블.
        # image is already size of (28x28), no reshape
        # label is not one-hot encoded
        X = X.to(device)
        Y = Y.to(device)

        optimizer.zero_grad()
        hypothesis = model(X)
        cost = criterion(hypothesis, Y)
        cost.backward()
        optimizer.step()

        avg_cost += cost / total_batch

    print('[Epoch: {:>4}] cost = {:>.9}'.format(epoch + 1, avg_cost))
"""
[Epoch:    1] cost = 0.224006683
[Epoch:    2] cost = 0.062186949
[Epoch:    3] cost = 0.0449030139
[Epoch:    4] cost = 0.0355709828
[Epoch:    5] cost = 0.0290450025
[Epoch:    6] cost = 0.0248527844
[Epoch:    7] cost = 0.0207189098
[Epoch:    8] cost = 0.0181982815
[Epoch:    9] cost = 0.0153046707
[Epoch:   10] cost = 0.0124179339
[Epoch:   11] cost = 0.0105423154
[Epoch:   12] cost = 0.00991860125
[Epoch:   13] cost = 0.00894770492
[Epoch:   14] cost = 0.0071221008
[Epoch:   15] cost = 0.00588585297
"""

#테스트 # 학습을 진행하지 않을 것이므로 torch.no_grad()
with torch.no_grad():
    X_test = mnist_test.test_data.view(len(mnist_test), 1, 28, 28).float().to(device)
    Y_test = mnist_test.test_labels.to(device)

    prediction = model(X_test)
    correct_prediction = torch.argmax(prediction, 1) == Y_test
    accuracy = correct_prediction.float().mean()
    print('Accuracy:', accuracy.item()) #Accuracy: 0.9883000254631042 #98퍼의 정확도