입력 1개와 출력 2개에 대한 선형회귀 신경망 구성 (복합 출력 / 다중 출력 신경망 모델)

다음과 같은 구조의 신경망을 구현에 대한 내용이다.

위의 신경망을 통해 판단할 수 있는 것은 입력값은 1개이고 출력값이 2개이므로 각각의 텐서구조는 [x], [y1, y2]라는 것이다. 신경망의 마지막 은닉층의 뉴런개수는 출력 개수와 동일하므로 2개이다.

코드를 보자. 먼저 필요한 패키지의 임포트이다. 파이토치를 사용한 예이다.

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.init as init

데이터를 준비한다.

num_data = 4000

x = torch.Tensor(num_data,1)
init.uniform_(x,-10,10)

noise = torch.FloatTensor(num_data,2)
init.normal_(noise, std=1)

def func1(x): return 4*x+5 
def func2(x): return 7*x+3 

y1 = func1(x)
y2 = func2(x)

y_noise = torch.Tensor(num_data,2)
y_noise[:,0] = y1[:,0] + noise[:,0]
y_noise[:,1] = y2[:,0] + noise[:,1]

데이터셋의 구성 개수는 4000개로 했다. 입력값(x)에 대해 2개의 출력값을 위한 선형공식이 9와 10 라인에 보인다. 12-17라인은 데이터에 잡음을 추가한 것이다. 잡음이 추가된 데이터를 통해 가중치(기울기)인 4, 7과 편향(y절편)인 5, 3을 결과를 얻어내면 된다. 아래는 이를 위한 학습 코드다.

model = nn.Linear(1,2)
loss_func = nn.L1Loss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
label = y_noise
num_epoch = 2000

for i in range(num_epoch):
    optimizer.zero_grad()
    output = model(x)
    loss = loss_func(output, label)
    loss.backward()
    optimizer.step()

    if i%10 == 0:
        print(loss.data)

param_list = list(model.parameters())
print(param_list[0], param_list[1])

결과는 다음과 같다.

tensor(28.9745)
tensor(27.7681)
tensor(26.5639)
tensor(25.3614)
tensor(24.1623)
tensor(22.9677)
tensor(21.7777)
.
.
.
tensor(0.7945)
tensor(0.7945)
tensor(0.7945)
Parameter containing:
tensor([[3.9986],
        [7.0006]], requires_grad=True) Parameter containing:
tensor([4.9845, 3.0214], requires_grad=True)

총 2000번 학습 시켰고, 그 결과로 손실값이 약 27로 시작해서 약 0.79 줄었다. 그리고 결과는 4, 7 그리고 5, 3에 근사한 값이 나온것을 알 수 있다.

이 코드를 통해 알아낸 것은 1개의 특성을 통해 그보다 더 많은 2개의 특성을 얻어내야 하는 이 경우에는 입력 데이터가 상대적으로 많아야 한다는 것이다. 이 경우는 4000개이다. 아울러 적당한 손실함수를 사용해야 한다. 위의 예제는 L1 손실함수를 사용했지만 평균제곱오차 손실함수를 사용하면 더 적은 데이터(이 부분은 확인이 필요함)와 반복학습이 가능하다.

여기서 입력 데이터와 분석 결과를 그래프로 시각화 해보자. 해당 코드는 아래와 같다. 지금까지의 코드에서 마지막에 붙이면 된다.

import matplotlib.pyplot as plt
import numpy as np

plt.figure(figsize=(15,15))
plt.scatter(x.numpy(),y_noise[:,0].numpy(),s=3,c="gray")
plt.scatter(x.numpy(),y_noise[:,1].numpy(),s=3,c="black")

x = np.arange(-10, 10, 0.01)
plt.plot(x, func1(x), linestyle='-', label="func1", c='red')
plt.plot(x, func2(x), linestyle='-', label="func2", c='blue')

plt.axis([-10, 10, -30, 30])
plt.show()

그래프는 다음과 같다.

회색점과 검정색점은 입력 데이터이고, 빨간색선과 파란색선은 선형회귀 결과를 표시한 것이다.

Python에서 외부 데이터 파일 읽기

파이썬에서 다양한 데이터 파일을 읽어오는 코드를 정리한 글로 추후 빠르게 참조하기 위한 목적으로 작성 되었습니다.

CSV 파일 읽기

import csv

rows = []
with open('../data/fileName.csv') as csvfile:
    csvreader = csv.reader(csvfile)
    next(csvreader, None)
    for row in csvreader:
        rows.append(row)

3번의 next 함수는 csv 파일의 첫줄에 있는 필드명을 건너뛰기 위함입니다. rows에 데이터가 저장됩니다.

JSON 파일 읽기

import json

with open('../data/fileName.json') as jsonfile:
    data = json.load(jsonfile)
    value_plainType = data["key1"]
    value_arrayType = data["key2"]
    value_dictionaryType = data["key3"]

    print(value_plainType)
    print(value_arrayType)
    print(value_dictionaryType["name"])

위의 fileName.json 파일의 내용이 다음과 같을때..

{
    "key1" : "string/numeric/bool",
    "key2" : [1, 2, 3, 4, 5],
    "key3" : { "name":"DoWise", "age": 12 }
}

출력 결과는 다음과 같습니다.

string/numeric/bool
[1, 2, 3, 4, 5]
DoWise

Python에서 객체에 대한 key, value 조회

key, value 자료 구조는 매우 효율적인으로 데이터를 저장하고 빠르게 검색할 수 있는 구조입니다. Python에서도 제공하는 key, value 자료구조는 Javascript와 매우 유사한데요. 예를들어 다음처럼 key와 value를 자유롭게 구성할 수 있습니다.

book = {
    'Boy': (100, 200),
    'Guy': (200, 400),
    'Girl': (300, 150),
    'Woman': (400, 800)
}

총 4개의 요소로 구성되어 있는데.. 각 요소를 순회하면서 조회하기 위한 코드는 다음과 같습니다.

for word, pages in book.items():
    print(word, pages)

위의 실행 결과는 다음과 같습니다.

Boy (100, 200)
Guy (200, 400)
Girl (300, 150)
Woman (400, 800)

앞서 book 변수에 대한 타입명은 dictionary입니다. 이 타입에서 key 요소는 중복될 수 없습니다.

특정 key 값을 갖는 요소를 제거하기 위해서는, 예를들어 key가 ‘Boy’ 인 요소를 제거하기 위한 코드는 다음과 같습니다.

del book['Boy']

또한 특정 키를 가진 요소가 존재하는지에 대한 검사는 다음과 같습니다.

print('Boy' in book)
print('Girl' in book)

앞서 key가 ‘Boy’인 요소는 제거되었으므로, 위의 코드에 대한 결과는 다음과 같습니다.

False
True

Python의 Thread API

Python의 언어적 한계로 Thread 기능의 지원이 적합하진 않다고 하지만, 문제점을 최대한 개선하여 GIL이라는 장치를 통해 지원한다고 합니다. GIL은 Global Interpreter Lock의 약자입니다. 코드는 다음과 같습니다.

import threading

class T(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        pass

    def run(self):
        for i in range(100):
             print(self.name, i)

13번 줄의 코드인 self.name은 Thread-n과 같은 스레드의 내부 이름이며, 여기에서 n은 내부 번호입니다. 앞서 정의한 스레드 T의 사용은 다음과 같습니다.

ts = [T() for i in range(4)]

for t in ts:
    t.start()

총 4개의 스레드를 생성하고 start 매서드를 통해 스레드를 시작합니다.