PHP 환경설정

/var/log/php-fpm에 로그 파일이 존재함

[로그파일 예시]
[11-Apr-2022 12:27:11] WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 16 children, there are 3 idle, and 44 total children
[11-Apr-2022 12:27:12] WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 32 children, there are 2 idle, and 46 total children
[11-Apr-2022 12:27:15] WARNING: [pool www] server reached pm.max_children setting (50), consider raising it

/etc/php-fpm.d 디렉토리의 www.conf 파일에 pm.max_children 설정값 등이 지정되어 있음

TensorFlow2를 이용한 간단한 회귀분석

TensorFlow v2가 정식버전으로 배포된지 몇달이 지났습니다. 필자는 딥러닝 라이브러리로 PyTorch를 주력으로 하고 있으나, TensorFlow로 만들어진 많은 코드 분석 및 협업을 위해 TensorFlow에 대한 API도 관심이 많습니다. 이 글에서는 TensorFlow 버전2에서 sin 함수에 대한 회귀분석에 대한 샘플 코드를 설명합니다.

필요한 패키지에 대한 import 및 훈련 데이터와 테스트 데이터를 아래 코드를 통해 준비합니다.

import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD, RMSprop, Adam
from tensorflow.keras import metrics

np.random.seed(0)

train_x = np.linspace(0,np.pi*2,10).reshape(10,1)
train_y = np.sin(train_x)
test_x = np.linspace(0,np.pi*2,100).reshape(100,1)
test_y = np.sin(test_x)

sin 함수에 대한 회귀분석을 위한 신경망 모델은 다음과 같습니다.

sin 함수는 1개의 입력값을 받아 1개의 출력값을 가지므로 입력층과 출력층의 뉴런 개수는 1개입니다. 중간의 은닉층의 뉴런은 임의로 2개로 잡았습니다. 이 신경망 모델을 구성하는 코드는 다음과 같습니다.

input_nodes = 1
hidden_nodes = 2
output_nodes = 1

model = Sequential()

model.add(Dense(hidden_nodes, input_dim=input_nodes, activation='sigmoid'))
model.add(Dense(output_nodes))

print(model.summary())

모델 상세 정보가 콘솔에 표시되는데, 다음과 같습니다.

파라메터의 개수는 가중치 w 뿐만 아니라 편차값인 b 값도 고려해야 합니다.

신경망을 학습할 것인데, 학습에 사용할 최적화 방식으로 4가지를 사용합니다. 아래 코드에 사용할 최적화 방법에 대한 구체적인 코드는 다음과 같습니다.

optimizers = {
    'SGD': SGD(lr=0.1),
    'Momentum': SGD(lr=0.1, momentum=0.9), 
    'RMSProp': RMSprop(lr=0.01),
    'Adam': Adam(lr=0.01)
}

다음 코드는 학습입니다.

train_results = []
train_y_predicted = []
test_y_predicted = []

for optimizer_name, optimizer in optimizers.items():
    print(optimizer_name, 'Training ...')

    model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mse'])

    result = model.fit(train_x, train_y, epochs=1000, verbose=0)
    train_results.append(result)

    train_result = model.predict(train_x)    
    train_y_predicted.append(train_result)

    test_result = model.predict(test_x)
    test_y_predicted.append(test_result)

위의 코드 중 8번의 model.compile은 모델 학습하기 위해 먼저 호출해야 하는 코드입니다. 인자로 loss와 metrics가 있는데, 각각 가질 수 있는 값은 ‘mean_squared_error'(주로 회귀용), ‘categorilcal_crossentropy'(주로 다중분류), binary_crossentropy'(주로 이진분류) 등과 ‘mse'(주로 회귀용), ‘accuracy'(주로 분류용) 등입니다. 그리고 실제 학습은 10번 코드를 통해 이뤄집니다. 단 1줄로 말입니다. 이 부분은 케라스의 장점이죠. model.fit 함수의 결과값은 손실값과 정확도에 대한 값을 포함합니다. 13번 코드와 16번 코드는 훈련된 모델을 통해 실제 계산을 수행하는 코드입니다. 각각 학습 데이터와 테스트 데이터로 계산을 수행해 그 결과를 배열에 담습니다.

앞의 코드에서는 중간 결과를 배열에 담았는데요. 이렇게 담은 배열은 최종 결과 그래프를 생성하기 위해 다음처럼 사용됩니다.

fig, axes = plt.subplots(3,1)
axes[0].plot(train_x, train_y, '-o', label = 'sin(x)')
for i, optimizer_name in enumerate(optimizers.keys()):
    axes[0].plot(train_x, train_y_predicted[i], '--', label=optimizer_name)
axes[0].legend()    

axes[1].plot(test_x, test_y, '-o', label = 'sin(x)')
for i, optimizer_name in enumerate(optimizers.keys()):
    axes[1].plot(test_x, test_y_predicted[i], '--', label=optimizer_name)
axes[1].legend()

for i, optimizer_name in enumerate(optimizers.keys()):
    axes[2].plot(train_results[i].history['loss'], '--', label=optimizer_name)
axes[2].legend()

plt.show()

결과는 다음과 같습니다.

이 경우 Momentum와 RMSprop의 장점을 섞은 Adam 최적화 방식이 가장 좋은 결과를 제공하는 것을 알 수 있습니다.

몬테카를로 방법(Montecarlo Method)

몬테카를로 방법(Monte Carlo method)은 어떤 문제에 대한 해를 무수히 많은 시도를 통해 얻어진 확률을 기반으로 하는 계산법입니다. 아래의 그림은 위키디피아의 몬테카를로에 대한 소개에 나온 이미지로써 원주율 π 값을 구하는 예입니다.

넓이가 1인 정사각형, 이 정사각형 내부에 반지름이 1인 사분원이 있습니다. 그러면 사분원이 차지하는 넓이는 π/4가 될 것이다. 이제 0 이상, 1 이하인 x와 y의 값을 무작위로 뽑은 후 x^2 + y^2 ≤ 1의 조건을 만족할 확률은 사분원의 넓이와 같은 π/4가 됩니다.

위의 논리를 코드로 작성하여 π를 구하면 다음과 같습니다.

import random

n = 1000000 # 백만번의 시도
count = 0

for i in range(n):
    # x, y를 무작위로 0~1사이의 값으로 결정
    x = random.uniform(0, 1)
    y = random.uniform(0, 1)

    # 사분원 내부에 발생하는 경우수 
    if (x**2 + y**2) <= 1: count += 1

# 백만번의 시도 중 사분원 내부일 경우에 대한 확률은 사분원의 넓이이므로 이를 4배 곱하여 π 계산
print('phi', 4*count/n)

위의 코드 중 4*count/n은 다음의 비례식을 통해 도출된 결과입니다.

전체확률 : 사분원 내부일 경우에 대한 확률 = 사각형의 넓이 : 사분원의 넓이

위의 비례식에 수치값을 대입하면 다음과 같습니다.

1 : count/n = 1 : π/4

몬테카를로 방법을 통해 실제와 가까운 해를 얻기 위해서는 방대한 단순 계산을 매우 빠르게 처리할 수 있는 컴퓨터가 필수입니다. 이 몬테카를로 방법은 핵폭탄이나 수소폭탄의 개발에서 핵심적인 역활을 담당했다고 합니다. 제 경우도 핵폭탄 개발이 필요해서... 가 아닌 강화학습(Reinforcement learning)의 한 방법으로 접하게 되었습니다.

FingerEyes-Xr에서 속성값으로 도형 심벌 및 라벨문자열 설정하기

여러 개의 속성값으로 라벨 문자열을 조립하여 실제 표시되는 라벨을 결정하는 코드의 예입니다.

CustomLabelFormatter = Xr.Class({
    name: "CustomLabelFormatter",
    extend: Xr.label.ProgrammableLabelFormatter,
    requires: [Xr.label.ILabelFormatter],
    construct: function (layer) {
        this.superclass(layer);
        this._MGN_CD = -1;
        this._MGN_DT = -1;
        this._codeValues = {
            'C01': '시설A',
            'C02': '시설B',
            'P01': '시설C',
            'P03': '시설D',
            'R03': '시설E',
            'T01': '시설F'
        };
    },
    methods: {
        value: function (shapeRow, fieldSet, attributeRow) {
            if (this._EQMT_FIXPLC_RGN_SE_CD == -1) {
                this._EQMT_FIXPLC_RGN_SE_CD = fieldSet.fieldIndex("MGN_CD");
            }

            if (this._FST_REGI_TSP == -1) {
                this._FST_REGI_TSP = fieldSet.fieldIndex("_MGN_DT");
            }

            let code = attributeRow.valueAsString(this._MGN_CD);

            // 필드 2개의 값(_MGN_CD 필드의 코드값 + _MGN_DT 의 값)을 조합한 라벨 표시, 예: 시설B(2020-07-27 14:32:32)
            return this._codeValues[code] + "(" + attributeRow.valueAsString(this._MGN_DT) + ")";
        }
    }
});

var lyr = new Xr.layers.ShapeMapLayer("lyr", ...);
let label = lyr.label();
label.enable(true);

let formatter = new CustomLabelFormatter(lyr);
label.formatter(formatter);

lm.add(lyr);

다음은 속성값으로 도형의 스타일 심벌을 지정하는 코드의 예입니다.

CustomLayerTheme = Xr.Class({
    name: "CustomLayerTheme",
    extend: Xr.theme.ProgrammableShapeDrawTheme,
    requires: [Xr.theme.IShapeDrawTheme],
    construct: function (/* ShapeMapLayer */ layer) {
        this.superclass(layer);
        this._fieldIndex = -1;
        let codes = ['R01', 'P02', 'R03', 'P01', 'P02', 'T01', 'T02', 'C01', 'C02'];
        let colors = ['#f1c40f', '#f39c12', '#e67e22', '#e74c3c', '#c0392b', '#ff0000', '#00ff00', '#0000ff', '#ff00ff'];
        let symbols = [];
        let cntCodes = codes.length;
        for (let i = 0; i < cntCodes; i++) {
            let SDS = new Xr.symbol.ShapeDrawSymbol();
            SDS.brushSymbol().color(colors[i]).opacity(0.5);
            SDS.penSymbol().color(colors[i]).width(2);

            let symbol = {
                code: codes[i],
                symbol: SDS
            };

            symbols[i] = symbol;
        }

        this._symbols = symbols;
    },
    methods: {
/* ShapeDrawSymbol */ symbol: function (/* ShapeRow */ shapeRow, /* FieldSet */ fieldSet, /* AttributeRow */ attributeRow) {
            if (this._fieldIndex === -1) {
                this._fieldIndex = fieldSet.fieldIndex("MGN_CD");
            }

            let value = attributeRow.valueAsString(this._fieldIndex);
            let symbols = this._symbols;
            let symbol = undefined;
            let cntSymbols = symbols.length;
            for (var i = 0; i < cntSymbols; i++) {
                symbol = symbols[i];
                if (value === symbol.code) {
                    break;
                }
            }
            return symbol.symbol;
        },

/* boolean */ needAttribute: function () {
            return true;
        }
    }
});


var lyr = new Xr.layers.ShapeMapLayer("lyr", ...);

var newTheme = new CustomLayerTheme(lyr)
lyr.theme(newTheme);

lm.add(lyr);