2022년 10월 9일 일요일

Tensorflow 2.0 custom gradient 만드는 법( vector output, multiple out의 경우)

 텐서플로우에서 이미 정의되어 있지않거나, 기존의 함수를 새로운 함수로 대체하고 싶을 경우, back propagation을 위한 gradient를 새로 정의할 필요가 있을 때가 있다. 이런 경우, 텐서플로우에서 @tf.custom_gradient 데코레이터를 이용하여 custom gradient를 정의해 줄수 있다. 

간단한 scalar 함수(y=f(x) 모양, 예를 들어 y = log(1+exp(x) ) 처럼)의 경우에는 https://www.tensorflow.org/api_docs/python/tf/custom_gradient 에 있는 예제를 보는 것으로 충분하다. 그러나, 결과값,output 이 array, vector, tensor 인 경우에 어떻게 custom gradient를 정의해야하는지는 예제를 찾기 어렵다. 나도 한참 코드를 만들고 고치면서 실험을 하고서야 방법을 알아낼 수 있었다.

그러나, 그렇게 고생해서 알아낸 것이 이미 위의 링크 페이지 맨 아래에 있는 설명을 잘 읽으면 되는 것이라는 것을 나중에야 알게 되었다. 

원칙적으로 input이 vector이고 output이 vector인 경우 수학적인 의미의 gradient는 Jacobian이 된다. 

예를 들어 input 이 (x1,x2) , output이 (y1,y2,y3) 인 경우, Jacobian은 dy1/dx1, dy1/dx2, dy2/dx1, dy2/dx2, dy3/dx1,dy3/dx2 를 모두 계산해야 한다. 

그러나, 신경망에서 관심이 있는 gradient는 사실상 loss function 에 대한 변수의 미분이기 때문에, 실제로는 dy/dx 가 아니라 d(loss)/dx = d(loss)/dy * dy/dx 만 필요하다. 여기서 , d(loss)/dy 를 upstream 이라고 부르고, 그 shape는 output y 의 shape와 같다.  custom_gradient로 정의하는 gradient는 바로 d(loss)/dx 이지, dy/dx 가 아니라는 것이다. (다만, Gradient tape를 이용하여 tape.gradient(y,x) 를 계산할 경우 y 가 scalar라면 upstream=1이라고 둘 수 있으므로 dy/dx 를 구하는 것과 마찬가지가 된다.)

 gradient의 output인 d(loss)/dx 는 input x의 shape와 같다. 따라서, custom gradient에서 정의하는 함수는 upstream을 input 으로 받아서, input x의 shape와 같은 모양의 결과를 내도록 정의되어야한다. 즉, y 가 vector라면, Jacobian에서 y 의 vector 축으로 그 값들을 모두 합한 결과가 필요한 것이다. 

위의 예의 경우에는 d(loss)/dx1 = d(loss)/dy1 * dy1/dx1+d(loss)/dy2 * dy2/dx1 +d(loss)/dy3 * dy3/dx1 로 구하고,  마찬가지로 d(loss)/dx2 를 계산하여 d(loss)/dx 를 return 하도록 custom gradient를 정의하면 된다. 

한편 새로 정의하는 함수 자신이 variable을 가지고 있을 경우에는 , (y=f(x;w) 처럼), 변수에 대한 미분도 정의해 주어야 한다. 이 경우에도 d(loss)/dw 는 변수 w 의 shape와 같아지도록 custom gradient를 정의한다. (즉,  d(loss)/dw= d(loss)/dy * dy/dw 에서 d(loss)/dy 의 shape는 y 의 shape와 같고, dy/dw의 Jacobian에서 redundant한 축에대해 더해주어 올바른 shape가 되도록 만들어야한다.)  


다음은  Y = X.W +b 인 경우를 custom gradient를 이용하여 구현한 것이다. 여기서 X.shape는 (batch_size, input_size), W.shape 는 (unit_size, input_size), Y는 (batch_size, unit_size) 의 모양을 가지는 tensor 이다.

  

@tf.custom_gradient

def custom_op(x):

    @tf.function

    def _inner_function():

        y = x @ w + b

        return y # y shape = (batch_size,unit_size)

    y = _inner_function()             

    def grads(upstream,variables):

        # here upstream is a shape of (batch_size,unit_size)  

        assert variables[0] is w

        dydx = upstream @ tf.transpose(w)  # (batch_size,input_size)

        dydw = tf.transpose(x) @ upstream  # (input_size,unit_size)

        dydb = tf.reduce_sum(upstream,axis=0) # (unit_size,)

        return dydx, [dydw, dydb]

    return y, grads


      



2022년 10월 4일 화요일

양자역학, CHSH 부등식, Bell 부등식

고전 역학과 양자 역학중 어느 것이 옳은지를 확실히 구분할 수 있는 실험 방법으로 Bell 부등식 또는 CHSH 부등식 실험이 있다. 결론부터 말하면, 고전역학에 기반한 (realistic local  hidden variable theory) 어떠한 이론이건 부등식을 만족시켜야하지만, 양자역학의 경우에는 부등식을 만족시키지 않을 수 있다. 따라서, 어떤 실험을 통해 부등식이 만족되지 않는 경우를 관찰한다면, 고전 역학이 옳을 수 없다는 것이 증명된다. 

양자역학적으로 서로 entangle 되어 있는, 예를 들어 두 개의 전자가 total spin zero 인 상태 $|S=0>=1/sqrt(2)(|up,down>-|down,up>)$ 에서 두 입자를 서로 다른 방향으로 보내어 A와 B에서 관찰한다고 생각해보자. 이 때, 두 관찰의 시간 차이동안 빛의 속도로 정보를 보내도 영향을 미치지 못할 정도로 A와 B는 매우 멀리 떨어져 있다. 양자 역학적으로는 A와 B의 측정 결과는 아무리 멀리 떨어져 있더라도 서로 관련이 있어 한 입자의 측정 결과는 입자 자체의 성질이 아니고 다른 입자와 함께 떼어 놓고 생각할 수 없다. 반면, 고전 역학적으로는 A와 B의 측정 결과는 서로에게 영향을 미칠 수 없고, A에서 관측된 결과는 오직 그 입자가 원래 가지고 있던 성질에 의해 결정된다.  

좀 더 구체적으로, A에서 실험의 관측값이 오직 두가지만 가능한, A0와 A1 이라는 두가지 실험중 하나를 랜덤하게 한다고 하자. 입자에 대해 A0 실험을 하면 +1 이거나 -1 인 결과를 얻는다. 마찬가지로 A1 실험에 대해서도 +1 이거나 -1 인 결과를 얻는다. 
만약, 입자의 관측 결과가 입자 자체의 성질에 의한 것이라면, 입자는 
(1,1),(1,-1),(-1,1),(-1,-1) 의 4가지 성질을 가지고 있을 수 있다. 즉, 원래 입자가 (1,-1)이라는 성질을 가지고 있었다면 A0 실험을 할 경우 1 , A1 실험을 할 경우 -1 이라는 관찰을 하게 될 것이다. 따라서, A에서 입자가 가지는 성질을 (a0,a1) 이라고 나타낼 수 있다. 
이 때, 입자가 4가지 성질 중 어느 것을 가지더라도, a0+a1 이나 a0-a1 중 하나는 반드시 0이 되고, 다른 하나는 반드시 +/- 2 값을 가지게 된다.   
마찬가지로 B에 있는 입자의 성질도 (b0,b1)이라고 나타낼 수 있다. 

두 입자의 성질의 다음과 같은 조합을 생각해 보자.

S = b0(a0 +a1)+b1(a0-a1) = a0*b0 + a1*b0 + a0*b1 -a1*b1 

(a0+a1) 과 (a0-a1) 중 하나는 반드시 0 이 되고, 나머지 하나는 +/- 2가 되고, b0와 b1 은 +/-1의 값을 가지므로, S의 값은 +2 나 -2 가 될 수 밖에 없다. 
이것은 입자 한 쌍에 대한 결과이다. 실험을 반복하여 많은 수의 실험 결과에 대한 평균을 낸다고 하자.  
S의 평균 값을 <S >으로 나타내면, 
<S> = < A0*B0> + < A1*B0> +< A0*B1> - <A1*B1> 
으로 쓸 수 있다. 여기서, < A0*B0>는 A0와 B0의 실험을 했을 때의 평균값을 나타낸다.
예를 들어 두 입자가 A0와 B0 실험에 대해 a0,b0라는 성질을 가지고 있을 경우, 
A0*B0 의 값은 단순히 a0*b0 로 두 값의 곱이 된다.    
만약 많은 수의 입자 쌍에 대해 실험을 하여 평균을 내면, 
하나의 입자쌍에 대해 S 는 +/- 2를 가질 것이므로, 
S의 평균값은 -2 에서 +2 사이에 있게 될 것이다. 
이것이 CHSH  부등식( -2<= S <= 2 ) 이고, 고전역학이 맞다고 반드시 만족시켜야하는 조건이다. 
(실제로는 하나의 입자에 대해서는 한가지 종류의 실험만이 이루어 질 것이다. 
예를 들어 첫번째 입자 쌍에 대해 A0와 B1이라는 실험을 하면, a1과 b0값은 측정이 안된다.
하지만, 많은 수의 입자에 대해 실험을 반복할 경우, A0 실험의 평균값 < A0> 는 모든 입자에 대한 a0 의 평균값에 가깝게 될 것이다. 다른 실험의 평균값의 경우도 마찬가지.)      

한편, 양자역학적으로 얽힌 상태의 경우, 예를 들어 $|S=0>=1/sqrt(2)(|up,down>-|down,up>)$의 경우  A0*B0의 실험 값은 expectation value, $<S=0| A0*B0| S=0>$
에 의해 결정된다. 이것은 다음과 같이 계산할 수 있다. 

<S=0|A0*B0|S=0> = (1/2)*(up|A0|up)*(down|B0|down)
                          -(1/2)*(up|A0|down)*(down|B0|up)
                          -(1/2)*(down|A0|up)*(up|B0|down) 
                          +(1/2)*(down|A0|down)*(up|B0|up)

즉, 양자역학의 경우는 결과가 단순히 곱으로 분리되지 않는다. 

특별히, 다음과 같은 실험을  준비한다고 하자. 

A0 : z축. 즉 ( 0,0, 1) 방향으로의 spin 값 측정
A1 : x축, 즉 ( 1,0,0 ) 방향으로의 spin 값 측정
B0 : 45도 축, 즉 (-1,0,-1) 방향 으로의 spin값 측정
B1:  -45도 축 , 즉 (1,0,-1)방향 으로의 spin값 측정

양자역학적으로는 각 실험에 다음과 같은 operator를 정해주는 것에 해당한다. 
A0 = sigma_z, 
A1 = sigma_x 
B0 = -1/sqrt(2)( sigma_x+sigma_z) 
B1= 1/sqrt(2)( sigma_x-sigma_z)

이 경우에 계산을 해보면 
< S=0| A0*B0| S=0> =  < S=0| A0*B1| S=0> =< S=0| A1*B0| S=0>= 1/sqrt(2) 
< S=0| A1*A1 |S=1> = -1/sqrt(2) 
으로 
S의 평균값은  (1/sqrt(2)+1/sqrt(2)+1/sqrt(2)-(-1/sqrt(2)))= 2*sqrt(2) 가 예상되게 된다. 이것은 고전역학이 만족시켜야하는 CHSH 부등식( -2 <= S <= 2)을 분명히 만족시키지 않는다. 따라서, <S> 의 값을 측정한 결과가 양자역학의 예상과 일치한다면, 고전역학은 틀릴 수 밖에 없다. 

photon 을 이용한 실험결과 <S>의 값이 고전적인 CHSH 부등식을 만족시키지 않고 양자역학적인 예측값과 일치하는 것을 확인할 수 있다고 한다. 

2022년 5월 24일 화요일

mingw-w64 install

 At some point, the install program of mingw-w64 does not work properly with error message " The file is not downloaded properly." 


The solution to this problem is to directly download the compiled files. 

(1) Go to the link

https://sourceforge.net/projects/mingw-w64/files/

(2) Select the chosen version of files (I choose x86_64-posix-seh )

(3) download and unzip. copy the file to c:\mingw-w64 (or to any path of choice)

(4) include the path (/bin and /include) to the Windows. 

   (For example c:\mingw-w64\bin and c:\mingw-w64\include) 

    ("system property"->"advanced"->"environment variables"-> "Path"->"add") 

 (5) Now one can use mingw in the command prompt. 

 

2022년 4월 19일 화요일

compile of the code for Eigenvector Continuation for elastic scattering

 Original Reference 

https://github.com/buqeye/eigenvector-continuation

which is the code for  arXiv:2007.03635,

To use the code in Windows, some modification is necessary.

Environment: Mingw64, gfortran, python, no lapack

(1) change the code of fortran file to use internal subroutine instead of lapack library. 

    this can be done simple commenting. 

(2) compile all fortran source code with f2py 

    python -m numpy.f2py -c  -m rmatrix_f2py   rmatrix_f2py.f

    python -m numpy.f2py -c  -m rmatrix_f2py_complex_potential rmatrix_f2py_complex_potential.f

    python -m numpy.f2py -c  -m coulomb_Barnett coulomb_Barnett.f 

(3) copy created dll files(in each sub-directories) to the source directory.

(4) Now we can use the fortran subroutines in python. 

(5) The jupyter notebook works now. 


2022년 1월 11일 화요일