수달이네 기술 블로그

3. 파이토치 논리회귀(단항, 다중 논리회귀) 본문

AI공부/딥러닝

3. 파이토치 논리회귀(단항, 다중 논리회귀)

슬픈 수달이 2026. 1. 29. 17:36

논리회귀

입력 데이터를 기반으로 두가지 이상 범주로 분류하는 지도 학습 알고리즘

  • 이진 분류 문제 등 사건 발생 확률 예측
  • sigmoid, logistic function등 비선형 함수를 사용하여 0~1사이의 값으로 변환
  • 0.5를 기준으로 두 범주 중 하나로 분류한다.(반올림?)

sigmoid

입력값을 받아 0~1사이의 값으로 분류하는 방법 중 하나.

$$ \sigma (x)=\frac{1}{1+e^{-x}} $$

위 수식에 넣으면 어떤 값이 들어와도 0~1사이가 된다.

  • 0.5보다 아래면 0 / 0.5보다 위면 1로 분류한다.(이진 분류)
  • 매우 큰 양수일수록 1에 가까워짐, 반대면 0에 가까워짐, 0은 0.5

단항 논리 회귀

독립변수 하나로 이진 분류 문제를 해결

x = torch.tensor([1.0,2.0,3.0])
w = torch.tensor([0.1,0.2,0.3])
b = torch.tensor(0.5)
  • x값과 기울기 편향이 위와 같을때, 계산은 내적(행렬곱)으로 한다.
  • $y = x1 * w1 + x2 * w2 … xn * wn + b$
z = torch.dot(w,x) + b
z

# tensor(1.9000)
sigmoid = nn.Sigmoid()
output = sigmoid(z)
output
# tensor(0.8699)
  • 위와 같이 내적을 구하고 sigmoid함수에 넣어주는 방식을 통해 분류한다.
    • 위값은 0.8699이므로 1로 판단.

적용

torch.manual_seed(2025)

x_train = torch.FloatTensor([[0], [1], [3], [5], [8], [11], [15], [20]])
y_train = torch.FloatTensor([[0], [0], [0], [0], [1], [1], [1], [1]])
print(x_train.shape)
print(y_train.shape)
# torch.Size([8, 1])
# torch.Size([8, 1])

plt.figure(figsize=(8, 5))
plt.scatter(x_train, y_train)

위와 같은 데이터셋을 구성했다.

model = nn.Sequential(
    nn.Linear(1,1),
    nn.Sigmoid()
)

list(model.parameters())
# W : [-0.4199]
# B : [-0.2018]
  • 위와 같이 랜덤 파라미터가 적용된 모델이 있을 때
y_pred = model(x_train)
y_pred
# tensor([[4.4972e-01],
#         [3.4939e-01],
#         [1.8823e-01],
#         [9.1004e-02],
#         [2.7620e-02],
#         [7.9943e-03],
#         [1.5001e-03],
#         [1.8401e-04]], grad_fn=<SigmoidBackward0>)
  • 단순 예측하면 의미 없는 값이 나온다.
  • 여기서 손실함수를 통해 모델을 학습해 주어야 하는데

BCE(Binary Cross Entropy) 손실함수

이진 분류 문제에서 모델이 예측한 확률 분포와 실제 레이블 사이의 차이를 측정하는 손실 함수

$$ L = -\frac{1}{N} \sum_{i=1}^{N} [y_i \log(\hat{y}_i) + (1 - y_i) \log(1 - \hat{y}_i)] $$

  • 실제 값이 1일때 예측 확률이 1에 가까우면 손실이 작아지고, 실제 값이 0일때 예측 확률이 0에 가까우면 손실이 작아진다.
    • $L: Loss$
    • $N: 전체 데이터$
    • $y_i: 실제 정답(0 or 1)$
    • $\hat{y}_i: 모델이 예측한 확률 (0~1 사이의 값)$
loss = nn.BCELoss()(y_pred, y_train)
loss

# tensor(0.4471, grad_fn=<BinaryCrossEntropyBackward0>)
  • 위와 같이 정의하고 구현할 수 있다.

이제 실제로 모델을 학습하자면

optimizer = optim.SGD(model.parameters(), lr = 0.01)

epochs = 1000

for epoch in range (epochs + 1):
    y_pred = model(x_train)
    loss = nn.BCELoss()(y_pred, y_train)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if epoch % 100 == 0:
        print(f'epoch: {epoch}/{epochs} Loss: {loss: .6f}')

# epoch: 0/1000 Loss:  0.447143
# epoch: 100/1000 Loss:  0.313473
# epoch: 200/1000 Loss:  0.296310
# epoch: 300/1000 Loss:  0.281232
# epoch: 400/1000 Loss:  0.267894
# epoch: 500/1000 Loss:  0.256020
# epoch: 600/1000 Loss:  0.245387
# epoch: 700/1000 Loss:  0.235812
# epoch: 800/1000 Loss:  0.227145
# epoch: 900/1000 Loss:  0.219263
# epoch: 1000/1000 Loss:  0.212061
  • 위와 같이 손실값이 줄어드는 것을 볼 수 있다.
list(model.parameters())

# 초기값 
# W : [-0.4199]
# B : [-0.2018]
# 학습이후 값
# W: [0.3921]
# B: [-2.0678]
  • 위와 같이 기울기와 편향이 나타난다. 이전의 값에서 변한 것을 볼 수 있다.
x_test = torch.FloatTensor([[10]])
y_pred = model(x_test)
y_pred

# tensor([[0.8645]], grad_fn=<SigmoidBackward0>)

y_bool = (y_pred >= 0.5).float()
y_bool

# tensor([[1.]])
  • 만약 y의 값이 임계값 0.5 보다 크거나 같으면 1이 아니면 0이 출력된다.

다항 논리 회귀

다항 논리 회귀는 n : 1로 예측하는 다중 클래스 분류 문제를 해결하는 알고리즘이다.

  • 소프트맥스(Softmax)함수(0~1사이로 정규화)
  • 특정 클래스가 속할 가능성을 계산, 가장 높은 확률의 클래스를 최정 예측 값으로 선택한다.
  • 자연어 처리, 이미지 분류, 다양한 다중 클래스 분류 문제등에 사용

아래와 같이 데이터 셋을 구성했다.

x_train = [[1, 2, 1, 1],
           [2, 1, 3, 2],
           [3, 1, 3, 4],
           [4, 2, 5, 5],
           [1, 6, 5, 5],
           [1, 4, 5, 8],
           [1, 7, 7, 7],
           [2, 8, 7, 8],
           [2, 7, 6, 7],
           [2, 6, 6, 6]]

y_train = [0, 0, 0, 1, 1, 1, 2, 2, 2, 2]

x_train = torch.FloatTensor(x_train)
y_train = torch.LongTensor(y_train)
print(x_train.shape)
print(y_train.shape)

# torch.Size([10, 4])
# torch.Size([10])
model = nn.Sequential(
    nn.Linear(4, 3)
)
model
  • 독립변수가 4종으로 구성되어있고, 종속변수는 0,1,2 총 3개의 클래스로 나뉘므로 Linear(4,3)으로 초기화 해준다.

$$ Y = XW + b $$

  • $X : 입력벡터(위에선 x1,x2,x3,x4, 1 × 4크기)$
  • $W: 가중치 행렬(4 × 3)$
  • $b: 편향 벡터 (1 × 3)$
  • $Y: 출력 벡터 (1 × 3)$

위와 같이 구성되며

$$ y_1=(x_1\cdot w_{11})+(x_2\cdot w_{21})+(x_3\cdot w_{31})+(x_4\cdot w_{41})+b_1\\ y_2=(x_1\cdot w_{12})+(x_2\cdot w_{22})+(x_3\cdot w_{32})+(x_4\cdot w_{42})+b_2\\ y_3=(x_1\cdot w_{13})+(x_2\cdot w_{23})+(x_3\cdot w_{33})+(x_4\cdot w_{43})+b_3 $$

  • 계산하면 위와 같이 계산한다.(행렬곱)
list(model.parameters())

# [Parameter containing:
#  tensor([[-0.4346,  0.2855, -0.1117,  0.1340],
#          [ 0.4447, -0.0227, -0.2139, -0.1113],
#          [-0.3901, -0.1394,  0.3450,  0.3059]], requires_grad=True),
#  Parameter containing:
#  tensor([-0.4480, -0.1562,  0.0326], requires_grad=True)]
  • 구성된 가중치 행렬과 편향은 랜덤으로 위와 같이 구성되었고
y_pred = model(x_train)
y_pred

# tensor([[-0.2892, -0.0821,  0.0146],
#         [-1.0986, -0.1538,  0.7598],
#         [-1.2651,  0.0684,  0.9815],
#         [-1.5035, -0.0487,  1.4479],
#         [ 0.9420, -1.4737,  2.0605],
#         [ 0.7732, -1.7621,  3.2569],
#         [ 1.2722, -2.1468,  3.2229],
#         [ 1.2571, -1.8361,  2.9992],
#         [ 0.9493, -1.4882,  2.4878],
#         [ 0.5298, -1.3542,  2.3213]], grad_fn=<AddmmBackward0>)
  • 예측값은 위와 같이 의미 없는 값으로 구해진다.

CrossEntropyLoss 손실함수

다중 클래스 분류 문제에서 모델의 예측과 실제 정답의 차이를 측정하는 손실함수.

모델이 예측한 확률 분포와 실제 레이블 간의 불확실성을 수치화 한다.

$$ L = -\sum^{C}_{c=1}y_c\cdot log(\hat{y}_c) $$

  • $L: Loss$
  • $C: 클래스 수$
  • $y_c: 실제 레이블$
  • $\hat{y}_c: 모델이 예측한 클래스 c에 대한 확률$
  • $ex) y_c: [1,0,0] , \hat{y}_c: [0.7,0.2,0.1] 일때$

$$ L=-(1\cdot log(0.7)+0 \cdot log(0.2) + 0\cdot log(0.1)) \\ L = -log(0.7) $$

  • 위와 같이 계산되어 손실은 약 0.3567으로 출력된다.
loss = nn.CrossEntropyLoss()(y_pred, y_train)
loss

# tensor(1.7610, grad_fn=<NllLossBackward0>)

현재 내 모델에서 CE를 계산하면 위와 같이 나온다.

epochs = 10000

for epoch in range(epochs + 1):
    y_pred = model(x_train)
    loss = nn.CrossEntropyLoss()(y_pred, y_train)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if epoch % 100 == 0:
        print(f'epoch: {epoch} / {epochs}, loss: {loss: .6f}')
# ...
# epoch: 9700 / 10000, loss:  0.347429
# epoch: 9800 / 10000, loss:  0.346335
# epoch: 9900 / 10000, loss:  0.345250
# epoch: 10000 / 10000, loss:  0.344174

CE를 통해 학습시킨 결과는 위와 같으며

x_test = torch.FloatTensor([[1,9,9,8]])
y_pred = model(x_test)
y_pred
# tensor([[-11.2851,   5.6718,   8.5757]], grad_fn=<AddmmBackward0>)

해당 모델을 통해 예측하면 위와 같다.

Softmax

위에서 예측한 값을 0~1사이의 값으로 정규화 시키기 위해서 다중 클래스 분류 문제에서는 softmax 활성화 함수를 사용한다.

$$ \mathrm{Softmax}(z_i)=\frac{e^{z_i}}{\sum _{j=1}^Ke^{z_j}} $$

  • 위에서 예측한 값을 이용해 softmax함수를 적용한다.
y_prob = nn.Softmax(1)(y_pred)
y_prob

# tensor([[2.2459e-09, 5.1957e-02, 9.4804e-01]], grad_fn=<SoftmaxBackward0>)
  • 위와 같이 적용 했다면
print(f'0일 확률: {y_prob[0][0]:.2f}')
print(f'1일 확률: {y_prob[0][1]:.2f}')
print(f'2일 확률: {y_prob[0][2]:.2f}')

torch.argmax(y_prob, axis=1)

# 0일 확률: 0.00
# 1일 확률: 0.05
# 2일 확률: 0.95
# tensor([2])
  • 총합 1로 확률을 만들어 주고, 그 결과에서 가장 큰 값으로 예측한다.

위와 같이 논리 회귀를 구현 가능하다.

'AI공부 > 딥러닝' 카테고리의 다른 글

5. ANN과 퍼셉트론  (0) 2026.01.31
4. 손글씨 숫자 데이터셋 처리  (0) 2026.01.30
2. 다중선형회귀  (0) 2026.01.28
1. 단순 선형 회귀(손실함수, 최적화, 경사하강법, 학습률)  (0) 2026.01.27
딥러닝. Alexnet  (0) 2026.01.15