psql을 설치된 서버에서 직접 실행하기

PostgreSQL에 대한 콘솔 관리자는 psql입니다. 현재 CentOS에서 설치해 사용중이고, DB 작업시에는 Telnet을 통해 작업을 해왔는데, 시간을 단축하고자 원격방식이 아닌 직접 서버단에서 작업을 했습니다. 원격 작업시 준비된 SQL에 대한 처리에만 2일정도 소요되는 작업이 10시간정도 소요되었습니다.

이 글은 추후 PostgreSQL이 설치된 서버에서 직접 psql을 실행하고자 할때 입력했던 콘솔 명령을 기록해 둡니다.

먼저 아래처럼 root로 로그인한 상태에서 postgres 계정으로 전환합니다.

sudo -i -u postgres

그리고 psql을 실행합니다. 바로 암호를 묻는데 postgres 계정에 대한 암호를 입력합니다.

기본적으로 psql은 postgres라는 이름의 데이터베이스에 연결됩니다. 이를 내가 원하는 데이터베이스로 연결하고자할 때 아래처럼 입력합니다.

\c database_name_to_be_connected

필요하다면, SQL 문의 인코딩 방식을 변경해줘야 하는데요. 아래처럼 입력하여 원하는 방식으로 변경할 수 있습니다.

set client_encoding = 'UTF8';

이제 준비된 SQL문이 저장된 파일로부터 SQL 문을 실행하기 위해 아래처럼 입력합니다.

\i /somewhere_dir/file_name_to_be_ran

pandas의 DataFrame에 대한 Inner Join, Outer Join, Left Join, Right Join

판다스에서 데이터프레임은 테이블 형식의 데이터셋입니다. DBMS의 Table들 간에도 Join을 맺을 수 있듯이, 마찬가지로 판다스의 데이터프레임들 간에도 Join을 맺을 수 있습니다. 물론 Join을 맺을 공통 필드가 존재한다면 말입니다.

Join에는 모두 4가지 방식이 존재합니다. 즉, 두 데이터셋 간의 중복된 요소만을 Join하는 Inner Join과 두 데이터셋에 대한 모든 데이터를 Join하는 Outter Join, 그리고 왼쪽 데이터셋을 기준으로 하는 Left Join과 오른쪽 데이터셋을 기준으로 하는 Right Join입니다. 보다 명확한 Join의 파악은 아래의 코드 예제를 통해 파악할 수 있습니다.

먼저 Join 하고자 하는 데이터셋으로, 판다스의 데이터프레임을 아래 코드처럼 정의합니다.

import pandas as pd

data_A = {'key': [1,2,3], 'name': ['Jane', 'John', 'Peter']}
dataframe_A = pd.DataFrame(data_A, columns = ['key', 'name'])

data_B = {'key': [2,3,4], 'age': [18, 15, 20]}
dataframe_B = pd.DataFrame(data_B, columns = ['key', 'age'])

print(dataframe_A)
print(dataframe_B)

결과는 아래와 같습니다.

   key   name
0    1   Jane
1    2   John
2    3  Peter
   key  age
0    2   18
1    3   15
2    4   20

두 데이터프레임 간에는 key라는 공통 필드가 존재하는 것을 볼 수 있습니다. 이를 토대로 먼저 Inner Join에 대한 코드입니다.

df_INNER_JOIN = pd.merge(dataframe_A, dataframe_B, left_on='key', right_on='key', how='inner')
print(df_INNER_JOIN)

위의 코드의 결과는 다음과 같습니다.

   key   name  age
0    2   John   18
1    3  Peter   15

다음은 Outer Join에 대한 코드입니다.

df_OUTER_JOIN = pd.merge(dataframe_A, dataframe_B, left_on='key', right_on='key', how='outer')
print(df_OUTER_JOIN)

결과는 다음과 같습니다.

   key   name   age
0    1   Jane   NaN
1    2   John  18.0
2    3  Peter  15.0
3    4    NaN  20.0

다음은 Left Join에 대한 코드입니다.

df_LEFT_JOIN = pd.merge(dataframe_A, dataframe_B, left_on='key', right_on='key', how='left')
print(df_LEFT_JOIN)

결과는 다음과 같습니다.

   key   name   age
0    1   Jane   NaN
1    2   John  18.0
2    3  Peter  15.0

다음은 Right Join에 대한 코드입니다.

df_RIGHT_JOIN = pd.merge(dataframe_A, dataframe_B, left_on='key', right_on='key', how='right')
print(df_RIGHT_JOIN)

다음은 실행 결과입니다.

   key   name  age
0    2   John   18
1    3  Peter   15
2    4    NaN   20

모든 Join은 pd.merge 함수를 통해 이루어지는데요. 위의 예제 코드를 보면 두 데이터프레임의 Join 필드가 모두 ‘key’라는 것을 알 수 있습니다. 이처럼 Join 필드의 이름이 동일할 경우 pd.merge의 left_on과 right_on 인자 대신 on 인자 하나로 대체가 가능합니다. 예를들어, Inner Join의 경우는 아래와 같습니다.

df_INNER_JOIN = pd.merge(dataframe_A, dataframe_B, on='key')

pd.merge 함수의 인자중 how도 생략되었는데, 이는 Inner Join이 pd.merge의 인자 how의 기본값이기 때문입니다.

사람의 눈, 코, 귀 그리고 팔과 다리를 검출하는 Person Keypoints Detection

이미지에 대한 Detection의 한 종류로 Person Keypoints Detection이 있습니다. 이 Detection은 사람의 눈, 코, 귀 그리고 팔과 다리를 검출합니다. 아래처럼요.

머신러닝 라이브리 중에 하나인, PyTorch에서는 Person Keypoints Detection을 위한 모델에 대해서 이미 잘 학습된 데이터를 제공합니다. 이 글은 미리 학습된 데이터를 활용하여 이미지에서 사람의 눈, 코, 귀와 팔 그리고 다리를 검출하는 코드를 살펴봅니다.

아울러 이 글에서는 matplotlib에서 그래프나 이미지를 단순히 표시하는 것에서 원하는 도형을 원하는 위치에 표시하는 API도 파악할 수 있습니다.

먼저 필요한 패키지를 import 합니다.

import numpy as np
from PIL import Image

import torch
import torchvision
from torchvision import models
import torchvision.transforms as T

import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches

미리학습된 데이터를 활용하여 Person Keypoints Detection을 위한 모델을 가져오는데, 머신러닝은 대규모의 행렬연산에 최적화된 GPU에서 수행하는 것이 제맛이므로 GPU 연산을 통해 신경망 모델을 로드합니다.

device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # CPU to GPU
model = models.detection.keypointrcnn_resnet50_fpn(pretrained=True).to(device).eval()

이미지를 불러오고 이미지의 크기를 줄입니다. GPU의 메모리가 충분하다면 이미지를 원본 크기 그대로 사용해도 되지만, 이미지를 가로로 800px로 줄여줍니다. 메모리를 덜 사용하는 것도 있고, 속도도 크게 향상되겠죠.

IMAGE_SIZE = 800
 
img = Image.open('data/running_1.jpg')
img = img.resize((IMAGE_SIZE, int(img.height * IMAGE_SIZE / img.width)))

검출을 수행합니다. 이를 위해 먼저 입력 이미지를 텐서로 변환해야합니다.

trf = T.Compose([
    T.ToTensor()
])

input_img = trf(img).to(device) # CPU to GPU

out = model([input_img])[0]

out 변수에 사람에 대한 눈, 코, 귀 그리고 팔과 다리에 대한 정보가 담겨 있습니다. 이 변수를 이용해 이미지 상에 해당 정보를 시각화 합니다.

codes = [
    Path.MOVETO,
    Path.LINETO,
    Path.LINETO
]

fig, ax = plt.subplots(1)
ax.imshow(img)

THRESHOLD = 0.9 # 해당 정보의 정확도가 90% 이상인 것만 사용

for box, score, keypoints in zip(out['boxes'], out['scores'], out['keypoints']):
    score = score.detach().cpu().numpy() # GPU to CPU

    if score < THRESHOLD:
        continue

    box = box.detach().cpu().numpy() # GPU to CPU
    keypoints = keypoints.detach().cpu().numpy()[:, :2] # GPU to CPU

    # 사람에 대한 영역을 그리기
    rect = patches.Rectangle((box[0], box[1]), box[2]-box[0], box[3]-box[1], linewidth=2, edgecolor='white', facecolor='none')
    ax.add_patch(rect)

    # 왼쪽 팔에 대한 선 그리기
    path = Path(keypoints[5:10:2], codes)
    line = patches.PathPatch(path, linewidth=2, facecolor='none', edgecolor='red')
    ax.add_patch(line)
    
    # 오른쪽 팔에 대한 선 그리기
    path = Path(keypoints[6:11:2], codes)
    line = patches.PathPatch(path, linewidth=2, facecolor='none', edgecolor='red')
    ax.add_patch(line)
    
    # 왼쪽 다리에 대한 선 그리기
    path = Path(keypoints[11:16:2], codes)
    line = patches.PathPatch(path, linewidth=2, facecolor='none', edgecolor='red')
    ax.add_patch(line)
    
    # 오른쪽 다리에 대한 선 그리기
    path = Path(keypoints[12:17:2], codes)
    line = patches.PathPatch(path, linewidth=2, facecolor='none', edgecolor='red')
    ax.add_patch(line)

    # 눈,코,귀는 노란색으로 그리고 팔다리의 시작점과 끝점은 빨간색으로 그리기
    for i, k in enumerate(keypoints):
        if i < 5:
            RADIUS = 5
            FACE_COLOR = 'yellow'
        else:
            RADIUS = 10
            FACE_COLOR = 'red'

        circle = patches.Circle((k[0], k[1]), radius=RADIUS, facecolor=FACE_COLOR)
        ax.add_patch(circle)    

plt.show()    

어제 휴일이라 일찍 잠들었더니, 새벽 3시에 일어났습니다. 글을 작성하던 중에 새벽 4시 조금 넘어서 마켓컬리 배송이 왔네요. 시간을 초월한 마켓컬리의 배송.. 고생이 많으십니당..

네이버 주식 서비스로부터 종목 시가, 종가, 고가, 저가, 거래량, 전일대비에 대한 시계열 데이터 얻기

주식 종목에 대한 하루 단위의 시가, 종가, 거래량 등을 웹을 통해 얻을 수 있는데요. 이러한 데이터를 파이썬을 이용해, 네이버의 주식 서비스로부터 얻는 코드에 대해 설명합니다.

코드는 다음과 같습니다. 원하는 종목에 대해 원하는 페이지만큼.. (이 부분은 주식 서비스마다 가능 여부 및 방식이 달라짐) 정보를 얻어옵니다.

import requests
from bs4 import BeautifulSoup

def print_stock_price(code, page_num):
    result = [[], [], [], [], [], [], [], [], []]

    for n in range(page_num):
        url = 'https://finance.naver.com/item/sise_day.nhn?code='+code+'&page='+str(n+1)
        print(url)

        r = requests.get(url)

        if not r.ok: 
            print('Not more data !')
            break

        html = r.content
        soup = BeautifulSoup(html, 'html.parser')
        tr = soup.select('table > tr')

        for i in range(1, len(tr)-1):
            td = tr[i].select('td')
            if td[0].text.strip():
                result[0].append(td[0].text.strip()) # 날짜
                result[1].append(td[1].text.strip()) # 종가
                
                img = td[2].select('img')
                if len(img) != 0: 
                    if 'src' in img[0].attrs:
                        src = img[0]['src']
                        if 'up' in src: result[2].append('상승')
                        else: result[2].append('하락')
                else: result[2].append('보합')

                result[3].append(td[2].text.strip()) # 전일대비
                result[4].append(td[3].text.strip()) # 시장가
                result[5].append(td[4].text.strip()) # 최고가
                result[6].append(td[5].text.strip()) # 최저가
                result[7].append(td[6].text.strip()) # 거래량

    for i in range(len(result[0])):
        #     날짜          종가           상승/하락/보합+a           시장가         최고가        최저가        거래량
        print(result[0][i], result[1][i], result[2][i]+result[3][i], result[4][i], result[5][i], result[6][i], result[7][i])

print_stock_price(code='005930', page_num=1)

코드를 보면, print_stock_price 함수의 url 변수에 저장된 주소에 대한 결과 DOM을 해석하고 있는 것을 알 수 있습니다. 즉, DOM에 대한 구조를 먼저 파악해야 한다는 것이 핵심인데요. 위의 코드가 정상적으로 작동할 당시의 실제 DOM의 한가지 예는 다음과 같습니다.

코드와 추출하고자 하는 DOM 요소가 명확하게 1:1로 매칭되고 있는 것을 확인할 수 있습니다.

최적 가중치값을 얻기 위한 경사하강법을 Python 코드로 이해하기

이 글은 텐서플로나 파이토치와 같은 같은 딥러닝 라이브러리의 기반을 이해하거나, 직접 개발하고자 할때 참고할 만한 글로 작성한 코드는 이해를 위해 나이브하게 작성했다 . 아래는 이 글에서 사용할 모델로 단순화를 위해 은닉층은 없고 입력층과 출력층만이 존재한다.

입력값 x와 해당 입력값에 대한 라벨값 t는 다음과 같다.

x = np.array([20.1, 32.2])
t = np.array([0, 0, 1])

데이터가 단일 항목인데, 실제는 그 개수가 상당이 많을 것이다. 가중치 W는 입력값의 특성개수가 2개이고 츨력값의 분류수가 3개이므로 2×3 행렬이며, 초기값은 아래처럼 난수로 잡는다.

W = np.random.randn(2, 3)

모델에 대한 입력 데이터가 정해졌으므로, 초기에 난수로 정한 가중치값을 보정하기 위한 방법인 경사하강법에 대한 함수인 gradient를 다음처럼 사용할 수 있다.

dW = gradient(x, t, W)
print(dW)

gradient 함수는 인자로 입력값과 라벨값 그리고 보정할 가중치값이 저장된 행렬을 받으며, 가중치값들을 보정할 경사도(미분값)를 가중치 행렬과 동일한 크기로 반환한다. 먼저 손실함수 loss는 다음과 같다.

def predict(input, weight):
    return np.dot(input, weight)

def softmax(input):
    input = input - np.max(input)
    return np.exp(input) / np.sum(np.exp(input))

def cee(activated, label):
    label = label.argmax(axis=0)
    result = -np.sum(np.log(activated[label] + 1e-7))

    return result    

def loss(input, label, weight):
    output = predict(input, weight)
    activated = softmax(output)
    loss = cee(activated, label)

    return loss

손실함수는 교차 엔트로피 오차(Cross Entropy Error, CEE) 함수를 사용했으며, 출력층에 대한 활성화 함수는 Softmax를 사용한 것을 알 수 있다. 이제 gradient 함수는 아래와 같다.

def gradient(input, label, weight):
    h = 1e-4
    grad = np.zeros_like(weight)
    
    it = np.nditer(weight, flags=['multi_index'])
    while not it.finished:
        idx = it.multi_index
        v = weight[idx]

        weight[idx] = v + h
        fxh1 = loss(input, label, weight)
        
        weight[idx] = v - h 
        fxh2 = loss(input, label, weight)
        
        grad[idx] = (fxh1 - fxh2) / (2*h)
        
        weight[idx] = v

        it.iternext()   
        
    return grad

가중치에 대한 W의 각 요소별로 편미분을 기울기 방향을 구하고 있다. 이 기울기 방향으로 일정한 길이만큼 가중치값을 이동해 주는 것을 반복하면 최소의 손실값을 갖는 가중치들의 모음을 얻을 수 있게 된다. 기울기 방향을 구하기 위한 방법으로 편미분에 대한 수치해석 기법을 활용했으나, 실제 텐서플로우나 파이토치 등과 같은 머신러닝 라이브러리에서는 역전파기법을 활용한다.