3. Linear Neural Networks for Regression

리치·2024년 1월 6일

dive into deep learning

목록 보기
2/6

3.1 Linear Regression

회귀 문제는 일반적으로 수치를 예측할 때 사용한다. 특정 범주에 속하는 분류 문제에도 사용할 수 있다.

예를 들어, 주택 가격을 예측하는 모델을 개발한다고 하면 우선 데이터 셋이 있어야 한다. 이런 데이터 셋을 training set 이라고 부른다. 그리고, 각 데이터를 포함하는 각 행을 data point, instance, sample, example 이라 부른다. 예측하고자 하는 값을 label 혹은 target이라 부른다. 예측에 사용될 수 있는 변수를 features 혹은 covariates라 부른다.

선형 회귀는 features x와 target y 사이에 얼추 선형의 관계를 가진다는 가정에서 시작한다.
E[Y|X=x] can be expressed as a weighted sum of the features x

E[Y|X=x]는 "X가 주어졌을 때 Y의 기대값"을 의미한다. 즉, 특정 X 값에 대해 Y가 어떤 평균적인 값을 가질 것으로 기대되는지를 나타낸다. 이것은 X의 각 특성이 가중치를 곱해진 합으로 표현될 수 있다는 것을 의미한다. 선형 회귀에서는 각 변수에 대한 계수를 찾아내, 가중 합으로 y값을 예측한다.

당연하게도, 예측하는 값과 target 값은 차이가 발생하며, 이 차이를 observation noise라 부른다. 이때, noise가 정규 분포(Gaussian distribution)을 따른다고 가정한다.

예로, 주택을 예측할 때 area와 age 2개의 변수로 모델을 만들면 아래와 같다.
price=wareaarea+wageage+bprice = w_{area}*area + w_{age}*age + b

이때, w는 가중치(weights)가 되고 b는 bias(offset, intercept)가 된다. 가중치는 각 변수가 예측에 미치느 영향력이 된다. bias의 경우 모든 값이 0일 때 추정량이다.

현실적인 관점에서 area나 age가 모두 0이라는 것은 말이 되지 않는다. 그럼에도 불구하고, 이런 bias는 모든 선을 표현할 수 있게 해주기 때문에 필수적으로 필요하다. (affine transformation 이라고도 한다)

더 나아가, 변수의 개수가 d개 일 때 예측 값은 아래와 같이 표현할 수 있다.
y^=w1x1+...+wdxd+b\hat{y} = w_1x_1+ ... + w_dx_d + b
x,wRd,y^=wTx+bx,w \in \R^d , \hat{y}=w^Tx + b

이를 전체 데이터 셋 n개에 대해서 표현하면,
XRnd,y^=Xw+bX \in \R^{n*d}, \hat{y}=Xw+b

따라서, 우리의 목표는 weight vector w를 찾고, bias term b를 찾는 것이다.

이를 위해서 손실함수 Loss funtion을 정의해야 한다. 여기서는 실제 값과 예측 값 사이의 distance를 quantify한다고 표현하고 있다. 일반적으로 음수가 아니며, 값이 작을 수록 좋다고 판단한다. 아래 수식에서 앞의 1/2은 실제로 영향을 주지 않지만, 미분을 할 경우 계산의 편의를 위함이다.
l(i)(w,b)=12(y^(i)y(i))2l^{(i)}(w,b)=\frac{1}{2}(\hat{y}^{(i)}-y^{(i)})^2

앞선 loss function은 실제 값과 예측 값의 차이가 클 수록, 제곱의 형태 때문에 손실에 대한 contribution이 더 커지게 된다. 즉, 이상치에 대해서 과도하게 민감하게 된다.

따라서, 단순하게 전체 데이터 셋에 대해 모델을 평가하기 위해서는 평균을 내면된다.

이때 손실을 최소화 하는 w와 b를 찾는게 목표였으므로,

조금 더 식을 간편하기 위해서, 행렬 X에 모든 원소가 1인 열을 추가하면 bias를 w에 추가할 수 있다. 따라서, yXw2||y-Xw||^2을 최소화 하는 문제로 바뀌게 된다.

만약, 행렬 X가 full rank를 가지게 된다면 (어떤 특성도 다른 특성에 linearly dependet하지 않다면), loss surface에는 오직 하나의 critical point가 생기게 되어 해가 유일하게 된다. 즉, optimal solution이 보장된다.

최소점을 찾기 위해서 앞선 식을 미분하고 0이 되게 하는 w를 찾는다.


위는 XTXX^TX가 invertible 하다면 unique하다. (i.e linearly independet)

아쉽게도 이런식으로 모델을 analytically하게 풀 수 없는 경우가 많다. 이런 경우, loss function을 점진적으로 낮추는 방향으로 파라미터를 업데이트하는 방법을 사용한다. 이를 Gradient Descent라 하며, 대부분의 딥러닝 모델을 optimizing 하는 핵심 기법이다.

가장 naive하게 gradient descent를 적용하는 방법은 loss function의 도함수를 구하고, 모든 데이터 셋에 대해서 계산된 loss의 평균을 구하는 것이다. 하지만, 너무 느리며 데이터에 많은 중복이 있을 경우 이점이 크지 않다.

그렇다면, 전체 데이터 셋을 보지 않고 단일 예제만을 고려하여 파라미터를 업데이트하는 것은 어떨까? 이러한 방법을 stochastic gradient descent (SGD)라 부르며, 대규모 데이터 셋에 대해서 효과적이다. 그러나, 계산과 통계적 부분에서 큰 단점이 발생한다.

이는 프로세서가 데이터를 메인 메모리에서 캐시로 옮기는 속도보다 숫자를 계산하는 속도가 훨씬 빠르기 때문에 발생한다. 때문에, 전체 batch를 모두 처리하는 것보다 시간이 오래 걸릴 수 있다. 또한, 후술할 batch normalization에서는 일부 layer가 둘 이상의 관측값에 접근할 때 제대로 작동한다는 문제점이 있다.

이 두개의 문제는 full batch 혹은 single example을 선택하는 대신 minibatch 관측 값을 가져오는 방법을 통해 해결할 수 있다. minibatch의 크기는 메모리 양, 가속시 수, 레이어, 데이터 크기 등에 따라 달라지지만 일반적으로 32~256 사이의 숫자를 선택하며 2의 거듭제곱의 배수로 선택하는 것이 좋다.

가장 기본적은 형태는 반복 t에서, 랜덤하게 minibath BtB_t를 고정된 값 |B|의 예시를 선택하여 만든다. 다음으로, 모델 파라미터에 대한 loss의 평균 gradient을 계산한다. 계산된 gradient에 learning rate라 부르는 η\eta을 곱해준다. 이제, 기존 파리미터 값에 해당 결과를 빼준다.

위 수식에서 w와 b에 대한 식으로 나누면 아래와 같다.

|B|로 나누어주어 normalize를 한다. 하이퍼 파리미터(learning rate, batch size)는 훈련전에 사용자가 설정하는 변수이지만, Bayesian optimization과 같은 기법을 통해 자동으로 최적화를 할 수 있다고 한다.

훈련 이후, 추정된 모델 파라미터 w^,b^\hat{w},\hat{b}가 기록된다. minibatch 방법은 실제 함수가 완벽한 선형이고 noise가 존재하지 않더라도 최적의 값을 정확하게 찾지 못하며 determinstic 하지도 않다. (minibatch가 랜덤하게 선택되기 때문에)

딥 네트워크의 loss surface에는 많은 saddle point와 minima가 존재한다. 다행이도, 정확한 파라미터 셋을 찾는 것 보다 정확한 예측 혹은 낮은 loss를 이끌어내는 매개변수 집합을 찾는 데만 관심을 가지면 된다. 오히려 더 어려운 작업은 이전에 보지 못한 데이터에 대해 정확한 예측을 이끌어내는 매개변수를 찾는 것이며 이를 "Genralization, 일반화"라 한다.

( 책에서는 Inferecne와 Prediction은 엄연히 다른 것이라 말한다.)
(
계산을 벡터화하고 Python에서 비용이 많이 드는 for-루프를 작성하는 대신 빠른 선형 대수 라이브러리를 활용하는 것이 좋다.)

다시 돌아와서, 앞서 선형 회귀 모델에 대한 가정중 하나는 noise가 정규분포를 따른다는 것이다. 또한, 정의했던 loss function을 생각해보자. 이 가정은 formal motivation을 부여한다. 정규 분포에서 평균으로부터의 차이를 제곱하는 것은 선형 회귀에서 관측된 데이터와 예측 값 사이의 확률적 불일치를 최소화하는 것과 같기 때문이다.

따라서, noise ϵ\epsilonN(0,σ2)N(0,\sigma^2)을 따른다면,

이를 다시 likelihood로 작성하면,

결국 w와 b에 대한 파라미터를 찾는 것은 likelihood를 최대화하는 문제로 바뀌게 된다.

현재 지수 함수의 곱을 최대화하는 것은 어려우므로, log를 씌워 단순화 시킬 수 있다. 이때, 역사적인 이유로, 대부분의 optimization 문제들은 최대화 보다 최소화 문제로 변환을 많이 하기 때문에 마찬가지로 negative log-likelihood를 minimize하는 문제로 변환한다.

만약, σ\sigma가 고정되어 있다고 가정하면 우리는 첫 번째 항을 무시할 수 있다. (w와 b에 대해 무관하기 때문) 두 번째 항의 역시 σ\sigma가 곱해져 있기는 하지만 해와는 독립이므로, 가우시안 noise 가정하에 선형 모델의 maximum likelihood 추정과 동일하게 된다.

선형회귀를 neural network로 묘사하면 Fig.3.1.2와 같다.

d를 number of inputs 혹은 the feature dimensionality라고 부른다. 네트워크의 출력은 o1o_1이며, 하나의 숫자 값만 예측하려고 하기 때문에 출력 뉴런은 하나다. 선형 회귀는 단일 계층으로 완전히 연결된 신경망이라고 생각할 수 있다.

3.2 Object-Oriented Design for Implementation

선형 회귀는 가장 간단한 머신 러닝 모델 이다. 이 모델의 여러 구성 요소(모델, 데이터, 손실 함수, 최적화 알고리즘)를 클래스로 정의하고, 이들을 상호 작용하게 만들어자.

def add_to_class(Class):  #@save
    """Register functions as methods in created class."""
    def wrapper(obj):
        setattr(Class, obj.__name__, obj)
    return wrapper

데코레이터는 함수를 수정하지 않은 상태에서 추가 기능을 구현할 때 사용한다. @와 함께 사용한다. (참고: https://dojang.io/mod/page/view.php?id=2427)

class A:
    def __init__(self):
        self.b = 1

a = A()

@add_to_class(A)
def do(self):
    print('Class attribute "b" is', self.b)

a.do() # A라는 class의 인스턴스 'a'에 데코레이터를 사용해 do 함수를 추가 

아래 클래스는 init 메소드에서 받은 모든 인자를 클래스 속성으로 저장한다. 클래스를 정의할 때 HyperParameters를 상속받아 save_hyperparameters 메소드를 호출함으로써, 추가 코드 없이 생성자 호출 시그니처를 확장할 수 있다.

class HyperParameters:  #@save
    """The base class of hyperparameters."""
    def save_hyperparameters(self, ignore=[]):
        raise NotImplemented

# Call the fully implemented HyperParameters class saved in d2l
class B(d2l.HyperParameters):
    def __init__(self, a, b, c):
        self.save_hyperparameters(ignore=['c'])
        print('self.a =', self.a, 'self.b =', self.b)
        print('There is no self.c =', not hasattr(self, 'c'))

b = B(a=1, b=2, c=3) 
# self.a = 1 self.b = 2
# There is no self.c = True

ProgressBoard 클래스는 진행 상황을 시각화 한다.

class ProgressBoard(d2l.HyperParameters):  #@save
    """The board that plots data points in animation."""
    def __init__(self, xlabel=None, ylabel=None, xlim=None,
                 ylim=None, xscale='linear', yscale='linear',
                 ls=['-', '--', '-.', ':'], colors=['C0', 'C1', 'C2', 'C3'],
                 fig=None, axes=None, figsize=(3.5, 2.5), display=True):
        self.save_hyperparameters()

    def draw(self, x, y, label, every_n=1):
        raise NotImplemented

board = d2l.ProgressBoard('x')
for x in np.arange(0, 10, 0.1):
    board.draw(x, np.sin(x), 'sin', every_n=2)
    board.draw(x, np.cos(x), 'cos', every_n=10)

이제 베이스 클래스인 Module 클래스를 구현한다. 이 클래스는 최소 3가지 메서드가 필요하다. 첫 번째 메서드인 init은 학습 가능한 파라미터를 저장하고, training_step 메서드는 데이터 배치를 받아 손실 값을 반환하며, 마지막으로 configure_optimizers는 학습 가능한 파라미터를 업데이트하는 데 사용되는 optimization 메서드나 목록을 반환한다. 추가적으로 validation_step을 정의하여 평가 측정값을 확인할 수 있다. 또한, forward 메서드를 분리하여 재사용성을 높이기도 한다.

class Module(nn.Module, d2l.HyperParameters):  #@save
    """The base class of models."""
    def __init__(self, plot_train_per_epoch=2, plot_valid_per_epoch=1):
        super().__init__()
        self.save_hyperparameters()
        self.board = ProgressBoard()

    def loss(self, y_hat, y):
        raise NotImplementedError

    def forward(self, X):
        assert hasattr(self, 'net'), 'Neural network is defined'
        return self.net(X)

    def plot(self, key, value, train):
        """Plot a point in animation."""
        assert hasattr(self, 'trainer'), 'Trainer is not inited'
        self.board.xlabel = 'epoch'
        if train:
            x = self.trainer.train_batch_idx / \
                self.trainer.num_train_batches
            n = self.trainer.num_train_batches / \
                self.plot_train_per_epoch
        else:
            x = self.trainer.epoch + 1
            n = self.trainer.num_val_batches / \
                self.plot_valid_per_epoch
        self.board.draw(x, value.to(d2l.cpu()).detach().numpy(),
                        ('train_' if train else 'val_') + key,
                        every_n=int(n))

    def training_step(self, batch):
        l = self.loss(self(*batch[:-1]), batch[-1])
        self.plot('loss', l, train=True)
        return l

    def validation_step(self, batch):
        l = self.loss(self(*batch[:-1]), batch[-1])
        self.plot('loss', l, train=False)

    def configure_optimizers(self):
        raise NotImplementedError

코드를 확인해보면, module이 pytorch의 nn.Module의 서브클래스임을 알 수 있다. 이는 PyTorch에서 nn.Module 객체의 인스턴스에 대해 괄호를 사용하여 호출하면 (a(X)와 같이), 내부적으로 call 메소드가 실행되어 이는 다시 forward 메소드를 호출하기 때문이다. 즉, a.forward(X)를 직접 호출하는 대신에 a(X)를 사용하여 같은 결과를 얻을 수 있다. 따라서, 입력 데이터를 모델의 여러 층에 통과시키고 결과를 반환하는 코드를 forward에 작성할 수 있다.

비슷하게 Data Module 역시 init 메서드가 자주 사용된다. 일반적으로 다운도르 및 전처리가 포함된다. train_dataloader는 학습 데이터 세트에 대한 데이터 로더를 반환한다. 데이터 로더는 사용할 때마다 데이터 배치를 생성하는 제너레이터이다. 그런 다음 이 배치를 모듈의 training_step 메서드에 전달하여 손실을 계산한다. 유효성 검사 데이터 세트 로더를 반환하는 val_dataloader 로더는 동일한 방식으로 작동하지만 모듈의 validation_step 메서드에 대한 데이터 배치를 생성한다는 점이 다르다.

class DataModule(d2l.HyperParameters):  #@save
    """The base class of data."""
    def __init__(self, root='../data', num_workers=4):
        self.save_hyperparameters()

    def get_dataloader(self, train):
        raise NotImplementedError

    def train_dataloader(self):
        return self.get_dataloader(train=True)

    def val_dataloader(self):
        return self.get_dataloader(train=False)

Trainer 클래스는 Module 클래스의 학습 가능한 파라미터를 DataModule에 지정된 데이터로 훈련하게 된다. 핵심 메서드인 fit은 두 개의 인자, Module의 인스턴스인 model과 DataModule의 인스턴스인 data를 받고 전체 데이터 세트를 max_epochs 횟수만큼 반복하여 모델을 훈련한다.

class Trainer(d2l.HyperParameters):  #@save
    """The base class for training models with data."""
    def __init__(self, max_epochs, num_gpus=0, gradient_clip_val=0):
        self.save_hyperparameters()
        assert num_gpus == 0, 'No GPU support yet'

    def prepare_data(self, data):
        self.train_dataloader = data.train_dataloader()
        self.val_dataloader = data.val_dataloader()
        self.num_train_batches = len(self.train_dataloader)
        self.num_val_batches = (len(self.val_dataloader)
                                if self.val_dataloader is not None else 0)

    def prepare_model(self, model):
        model.trainer = self
        model.board.xlim = [0, self.max_epochs]
        self.model = model

    def fit(self, model, data):
        self.prepare_data(data)
        self.prepare_model(model)
        self.optim = model.configure_optimizers()
        self.epoch = 0
        self.train_batch_idx = 0
        self.val_batch_idx = 0
        for self.epoch in range(self.max_epochs):
            self.fit_epoch()

    def fit_epoch(self):
        raise NotImplementedError

3.3 Synthetic Regression Data

Synthetic Data, 합성 데이터는 실제 데이터를 모방한, 인간이 생성하지 않은 데이터다. 머신러닝은 데이터에서 정보를 추출한다. 합성 데이터로 학습 알고리즘의 속성을 평가하고, 구현이 예상대로 작동하는 지 확인하는 데 도움이 된다.

아래 코드로 간단하게 노이즈가 포함되는 데이터셋을 생성한다.

class SyntheticRegressionData(d2l.DataModule):  #@save
    """Synthetic data for linear regression."""
    def __init__(self, w, b, noise=0.01, num_train=1000, num_val=1000,
                 batch_size=32):
        super().__init__()
        self.save_hyperparameters()
        n = num_train + num_val
        self.X = torch.randn(n, len(w))
        noise = torch.randn(n, 1) * noise
        self.y = torch.matmul(self.X, w.reshape((-1, 1))) + b + noise

data = SyntheticRegressionData(w=torch.tensor([2, -3.4]), b=4.2)
print('features:', data.X[0],'\nlabel:', data.y[0])
# features: tensor([0.9026, 1.0264])
# label: tensor([2.5148])

기계 학습 모델은 훈련할 때 데이터 셋을 여러번 전달하는 경우가 많다.

@d2l.add_to_class(SyntheticRegressionData)
def get_dataloader(self, train):
    if train:
        indices = list(range(0, self.num_train))
        # The examples are read in random order
        random.shuffle(indices)
    else:
        indices = list(range(self.num_train, self.num_train+self.num_val))
    for i in range(0, len(indices), self.batch_size):
        batch_indices = torch.tensor(indices[i: i+self.batch_size])
        yield self.X[batch_indices], self.y[batch_indices]

그러나, 이렇게 구현할 경우 모든 데이터를 메모리에 로드하고 많은 무작위 메모리 액세스를 수행해야 하기 때문에 실제 문제를 해결하는 데 있어 비효율적이다. 프레임워크에 구현되어있는 반복자는 훨씬 더 효율적이다.

@d2l.add_to_class(d2l.DataModule)  #@save
def get_tensorloader(self, tensors, train, indices=slice(0, None)):
    tensors = tuple(a[indices] for a in tensors)
    dataset = torch.utils.data.TensorDataset(*tensors)
    return torch.utils.data.DataLoader(dataset, self.batch_size,
                                       shuffle=train)

@d2l.add_to_class(SyntheticRegressionData)  #@save
def get_dataloader(self, train):
    i = slice(0, self.num_train) if train else slice(self.num_train, None)
    return self.get_tensorloader((self.X, self.y), train, i)

Data loader는 추상화한 편리한 방법이다.

3.4 Linear Regression Implementation from Scratch

Minibatch SGD로 모델의 파라미터를 최적화하기 전에, 가중치를 초기화한다. 이때, 평균이 0이고 표준편차가 0.01인 정규분포에서 난수를 뽑는다. 0.01은 매직넘버로 대개 잘 동작한다. 이후, 입력과 파라미터를 출력과 연결해 모델을 정의한다.

class LinearRegressionScratch(d2l.Module):  #@save
    """The linear regression model implemented from scratch."""
    def __init__(self, num_inputs, lr, sigma=0.01):
        super().__init__()
        self.save_hyperparameters()
        self.w = torch.normal(0, sigma, (num_inputs, 1), requires_grad=True)
        self.b = torch.zeros(1, requires_grad=True)
        
@d2l.add_to_class(LinearRegressionScratch)  #@save
def forward(self, X):
    return torch.matmul(X, self.w) + self.b

모델을 업데이트 하기 위해서는 loss function을 정의해야한다. 여기서는 앞서 배웠던 기본적은 squared loss function을 사용한다.

@d2l.add_to_class(LinearRegressionScratch)  #@save
def loss(self, y_hat, y):
    l = (y_hat - y) ** 2 / 2
    return l.mean()

각 step에서는 데이터셋으로 부터 랜덤하게 추출된 minibatch를 사용한다. 추출된 minibatch를 통해 파라미터에 대한 gradient of loss를 측정한다. 다음으로는 loss를 줄이는 방향으로 파라미터를 업데이트 한다.

아래 코드는 learning rate으로 업데이트한다. loss의 경우 minibatch의 평균으로 계산되므로, learning rate의 값은 batch size와 무관함을 알 수 있다.

class SGD(d2l.HyperParameters):  #@save
    """Minibatch stochastic gradient descent."""
    def __init__(self, params, lr):
        self.save_hyperparameters()

    def step(self):
        for param in self.params:
            param -= self.lr * param.grad

    def zero_grad(self):
        for param in self.params:
            if param.grad is not None:
                param.grad.zero_()

@d2l.add_to_class(LinearRegressionScratch)  #@save
def configure_optimizers(self):
    return SGD([self.w, self.b], self.lr)

zero_grad는 backpropagation step전에 실행하여 모든 gradient를 0으로 초기화한다.

이제, 본격적으로 training loop을 구현한다.

각 epoch에서는 전체 데이터 셋(batch size로 나누어진다고 가정)을 반복하면서 모든 예제가 한번 통과한다. 이때, 각 반복에서는 minibatch의 training example로 loss를 게산한다. (training_step_method) 다음으로, 파라미터의 gradients를 계산하고, 마지막으로 optimization 알고리즘을 통해 파라미터를 업데이트한다.

@d2l.add_to_class(d2l.Trainer)  #@save
def prepare_batch(self, batch):
    return batch

@d2l.add_to_class(d2l.Trainer)  #@save
def fit_epoch(self):
    self.model.train()
    for batch in self.train_dataloader:
        loss = self.model.training_step(self.prepare_batch(batch))
        self.optim.zero_grad()
        with torch.no_grad():
            loss.backward()
            if self.gradient_clip_val > 0:  # To be discussed later
                self.clip_gradients(self.gradient_clip_val, self.model)
            self.optim.step()
        self.train_batch_idx += 1
    if self.val_dataloader is None:
        return
    self.model.eval()
    for batch in self.val_dataloader:
        with torch.no_grad():
            self.model.validation_step(self.prepare_batch(batch))
        self.val_batch_idx += 1

일반적으로, 하이퍼 파라미터의 설정이 까다롭기 때문에 training, for hyperparameter selection, final evaluation 3가지로 나눈다.

앞서, Synthetic data를 사용했기 때문에 실제 파라미터 값이 무엇인지 정확하게 알고 있다. 실제로 값을 비교해보면 비슷한 것을 알 수 있다. 다만, 정확하게 복구하는 능력은 일반적이지 않기 때문에 실제 파라미터를 복구하는 능력보다는 정확한 예측으로 이어지는 파라미터를 찾는 것에 관심을 둔다.

3.5 Concise Implementation of Linear Regression

3.5에서는 딥러닝 프레임워크의 고급 API를 사용해 3.4에서 구현했던 선형회귀 모델을 간단하게 구현한다.

Recall the architecture of a single-layer network as described in Fig. 3.1.2. The layer is called fully connected, since each of its inputs is connected to each of its outputs by means of a matrix–vector multiplication.

PyTorch에서 fully connected layer는 Linear 혹은 LazyLinear 클래스로 정의된다.

class LinearRegression(d2l.Module):  #@save
    """The linear regression model implemented with high-level APIs."""
    def __init__(self, lr):
        super().__init__()
        self.save_hyperparameters()
        self.net = nn.LazyLinear(1)
        self.net.weight.data.normal_(0, 0.01)
        self.net.bias.data.fill_(0)
        
@d2l.add_to_class(LinearRegression)  #@save
def forward(self, X):
    return self.net(X)

forward method는 call method에 내장되어 있는 걸 호출한다.

이제, loss function을 정의해야한다. 여기서는 MSELoss를 사용한다. 이는 평균 제곱 오차를 계산하고 반환한다. MSELoss returns the average loss over examples. It is faster (and easier to use) than implementing our own.

@d2l.add_to_class(LinearRegression)  #@save
def loss(self, y_hat, y):
    fn = nn.MSELoss()
    return fn(y_hat, y)

Minibatch SGD는 PyTorch에서 지원한다.

@d2l.add_to_class(LinearRegression)  #@save
def configure_optimizers(self):
    return torch.optim.SGD(self.parameters(), self.lr)

Training은 처음에 구현한 것과 동일하다. we just call the fit method (introduced in Section 3.2.4), which relies on the implementation of the fit_epoch method in Section 3.4, to train our model.

model = LinearRegression(lr=0.03)
data = d2l.SyntheticRegressionData(w=torch.tensor([2, -3.4]), b=4.2)
trainer = d2l.Trainer(max_epochs=3)
trainer.fit(model, data)

대부분의 경우 프레임워크가 제공하는 기능을 사용하는 것이 좋다. 성능에 최적화되어 있으며, 안정성이 좋기 때문이다!

3.6 Generalization

Machine learning은 데이터의 패턴을 발견하는 것이다. 이때, how to discover patterns that generalize이 근본적인 문제가 된다.

실생활에서 데이터는 유한하다. 즉, 유한한 샘플로 훈련을 하게될 경우 훈련 데이터에 대해서는 fit하지만 generalizable pattern을 발견하는 데 실패 했을 수 있음을 염두해야한다.

훈련 데이터에 가깝게 fitting 되는 현상을 overfitting(과적합)이라고 부르며, 이러한 과적합을 방지하는 방법을 regularization이라고 부른다.

Standard supervised learning에서는 훈련데이터와 테스트 데이터가 동일한 분포에서 independent하게 추출된다고 가정한다. 이러한 가정을 IID라 한다. 이러한 가정은 강력하지만, 서로 다른 분포에서 생성된 테스트 데이터에 대해 예측하는 방법을 알려주어야 한다.

Remp: which is a statistic calculated on the training dataset
generalization error R: which is an expectation taken with respect to the underlying distribution

Generalization error는 기본 데이터 분포에서 추출된 추가 데이터의 infinite steam에 모델을 적용한 경우 볼 수 있다.(?)

업로드중..

그러나, R은 정확하게 계산할 수 없다. 밀도 함수 p(x,y)를 모르기 때문이다. 따라서, training 세트로부터 무작위 선택으로 구성된 독립적인 test세트에 모델을 적용하여 R을 추정해야한다. (This consists of applying the same formula that was used for calculating the empirical training error but to a test set X′,y′.)

Note that the model we wind up with depends explicitly on the selection of the training set and thus the training error will in general be a biased estimate of the true error on the underlying population. The central question of generalization is then when should we expect our training error to be close to the population error (and thus the generalization error).

profile
이것저것

0개의 댓글