분석의 핵심은 시각화다.

먼저 이 글은 사이토 고키의 세번째 딥러닝 서적인 Deep Learning from Scratch에 대한 내용(p225)의 코드의 변형된 내용을 언급하고 있습니다. 이 책은 텐서플로나 파이토치 자체를 구현하기 위한 핵심 내용을 다루고 있습니다. 이 글은 이 책에서 다루는 내용 중 역전파에 대한 시각화를 보여주는 코드를 토대로 어떤 수식에 대한 역전파를 통해 미분값을 계산하는데 수행되는 계산 흐름을 시각화하는 내용이며 시각화에 대한 중요성을 언급합니다.

sin 함수의 미분은 해석적으로 볼 때 cos입니다. 다른 방법으로 테일러 급수를 이용해서도 미분값을 구할 수 있고 코드는 다음과 같습니다.

def my_sin(x, threshold=0.00001):
    y = 0
    for i in range(100000):
        c = (-1) ** i / math.factorial(2 * i + 1)
        c = Variable(np.array(c))
        c.name = "c"
        t = c * x ** (2 * i + 1)
        y = y + t
        if abs(t.data) < threshold:
            break
    return y

위의 sin 값을 얻기 위한 함수를 실행하고, 미분값을 얻기 위한 코드는 다음과 같습니다.

x = Variable(np.array(np.pi/4), "x")  
y = my_sin(x)
y.name = "y"
y.backward()

print(y.data)
print(x.grad)

plot_dot_graph(y, verbose=False, to_file="diagram.svg")

위의 코드가 실행되면 PI/4에 대한 sin 값과 PI/4에 대한 sin 미분값이 얻어집니다. 그리고 미분값을 얻기 위해 계산된 처리 과정이 다음처럼 diagram.svg으로 시각화됩니다.

테일러 급수를 통한 sin 값을 얻기 위한 실제 계산의 흐름은 위의 시각화를 통해 좀 더 이해할 수 있습니다.

시각화는 이처럼 무언가를 좀 더 이해하고, 이러한 과정을 통해 더 깊고 정확하게 이해할 수 있는 기회를 제공합니다. 또한 이러한 이해를 통해 더 나은 방법을 찾을 수 있는 최적화와 그 다음 단계로 나아갈 수 있는 입구를 마련해 줍니다. 분석은 어떤 복잡한 현상을 보다 쉽게 이해하기 위해 작은 것들로 나눠 풀어 놓는 작업이라고 할 때 분석에서 매우 중요한 핵심 도구는 시각화입니다. 시각화가 되지 못한 분석은 타인을 이해 시키기도 힘들 뿐더러 과연 그 분석이 정확히 이뤄졌는지도 확인하기 어렵기 때문입니다.

three.js에서 유리 재질에 동적 환경맵 적용하기

three.js에서 유리 재질을 표현하기 위해서 MeshPhysicalMaterial를 사용합니다. 굴절률(ior)과 유리의 두께(thickness)도 표현할 수 있습니다. 그러나 매쉬에 MeshPhysicalMaterial를 사용하여 유리 재질을 적용하고 정적인 환경 맵과 동적인 환경 맵을 적용해 보면 정적 환경맵은 문제가 없으나 동적 환경맵의 경우 표현되지 않습니다.

이는 MeshPhysicalMaterial에서 동적 환경맵을 적용하기 위해서는 반드시 PMREM 형식으로 만들어진 텍스쳐맵을 사용해야 한다고 합니다. 즉 WebGLCubeRenderTarget으로부터 텍스쳐맵을 바로 재질의 envMap 속성에 지정해서는 안되고 PMREMGenerator 클래스를 사용해여 변환된 텍스쳐맵을 envMap 속성에 지정해줘야 합니다.

일반적인 동적 환경맵 생성과 PMREM 형식의 환경맵 가공을 위한 객체에 대한 코드는 다음과 같습니다.

const sphereRenderTarget = new THREE.WebGLCubeRenderTarget(1024, {
    format: THREE.RGBFormat,
    generateMipmaps: true,
    minFilter: THREE.LinearMipmapLinearFilter
});

sphereRenderTarget._pmremGen = new THREE.PMREMGenerator(this._renderer);
const sphereCamera = new THREE.CubeCamera(0.01, 10, sphereRenderTarget);

그리고 환경맵 지정을 위한 매쉬에 CubeCamera를 추가합니다.

sphere.add(sphereCamera);
this._sphere = sphere;

그리고 렌더링 시에 환경맵을 업데이트 해줘야 합니다.

if(this._sphere) {
    this._sphere.visible = false;

    const cubeCamera = this._sphere.children[0];
    cubeCamera.update(this._renderer, this._scene);
    const renderTarget = cubeCamera.renderTarget._pmremGen.fromCubemap(
        cubeCamera.renderTarget.texture);
    this._sphere.material.envMap = renderTarget.texture;
    this._sphere.visible = true;
}

실행 결과는 다음과 같이 정적인 환경맵과 동적인 환경맵 모두 잘표현되는 것을 확인할 수 있습니다.

전체 코드는 아래를 클릭해서 다운로드 받을 수 있습니다. (three.js 라이브러리는 포함되어 있지 않습니다)


GLSL, 원(Circle)

완전한 3차원 장면은 Shader로 완성된다고 할 수 있는데요. Shader를 통해 매우 사실적인 물, 안개, 번개 등과 같은 표현이 가능합니다. 아래는 Shader 언어 중 GLSL로 작성된 원을 렌더링하는 코드입니다.

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;

float circle(vec2 position, float radius) {
    return step(length(position), radius);
}

void main() {
    vec2 position = gl_FragCoord.xy / u_resolution;
    position -= 0.5;
    vec3 color = vec3(circle(position, 0.3));
    gl_FragColor = vec4(color, 1.0);

}

결과는 아래와 같구요.

코드를 보면 12번은 색상을 결정해야 하는 위치 좌표를 (0,0) ~(1,1)의 범위로 정규화해 줍니다. (0,0)은 화면의 좌측하단입니다. 13번은 다시 이 좌표를 (-0.5,-0.5) ~ (0.5,0.5)로 변경해 줍니다. 원의 가운데 위치하도록 하기 위함입니다. 8번은 원을 표현하기 위한 픽셀의 색상값을 결정하는 함수의 코드인데요. step(A, B)는 B가 A보다 크거나 같으면 1을 반환하고, 아니라면 0을 반환합니다.

JavaScript의 class에서 private Field, Method 정의

최근 JS의 class에서 private 접근자를 지원한다는 것을 알게 되었습니다. 그전에는 밑줄(_)로 시작하는 필드와 매서드가 private라는 강제성이 없는 방식으로 해왔는데요. 아래는 #width와 #height 필드 그리고 #getArea 매서드가 private 접근자의 성격을 갖습니다.

class Rectangle {
    #width = 10;
    #height = 10;

    constructor(height, width) {
        this.#width = width;
        this.#height = height;
    }

    #getArea() {
        return this.#width * this.#height;
    }

    get area() {
        return this.#getArea();
    }
}

이제 아래의 코드는 에러입니다.

const rec = new Rectangle(10, 10);
console.log(rec.#width);

에러의 내용도 명확히 “Private field ‘#width’ …”라고 되어 있습니다.

그러나 Rectangle에 width라는 필드를 추가할 수 있는데, 이 width는 public입니다. #width와는 별개이구요. 혼란스럽습니다. 게다가 아직 protected 접근자는 제공하지도 않습니다.

웹에서의 객체지향적 프로그래밍을 위해서는 아직까지 TypeScript가 강력한 대안으로 생각됩니다.