[간단한 설명 첨부] 파이썬 스크래치로 심층 볼레즈만 기계를 설치합니다①

이 글은 Wacul Advent Calendar의 18일째 글입니다.

자기소개


나는 1년 전부터 WACUL 분석팀에서 일했다.
python 경력: 3주 정도

하는 일


python 연습과 확률적인 심도 있는 학습을 목적으로 심층 볼레즈만 기계의 스크래치 설치를 시도해 본다.이론면은 모두 아래의 책 속의 내용이다.『深層学習 (機械学習プロフェッショナルシリーズ)』 岡谷 貴之 (著) 『深層学習 Deep Learning』(監修:人工知能学会)이 글은 우선 볼츠만 기계를 제한할 준비를 하고 있다.다음에는 볼레즈만 기계가 쌓이는 것을 제한하고 심층 볼레즈만 기계를 구축한다.

볼레즈만 기계의 간략한 설명


확률 분포는'값에 대한 집합, 이를 얻은 확률 밀도(확률 질량)에 대응하는 함수'이기 때문에 전체적으로'생성 값의 집합의 배후 메커니즘'으로 포착할 수 있다.이를 생성 모델이라고 합니다.
생성 모델을 구축하고 변수 간의 관계를 파악하면 기대치를 계산하고 응용의 폭을 확대할 수 있다.
이번에 언급한 볼레즈만 기계는 볼레즈만 분포를 이용하여 확률 밀도를 계산하는 생성 모델이다.구체적인 확률 밀도는 아래에 정의된 에너지 함수(Φ)철근 커브에 대해 포지셔닝과 방향을 정하다.([0,1]의 닫힌 구간을 수용하기 위해 상수를 곱하기)
에너지 함수는 노드 편이와 모든 링크의 가중선형이다.

표시 변수 및 숨김 변수


가시적 변수는 직접 관측할 수 있는 변수를 가리키고, 숨겨진 변수는 관측할 수 없는 변수를 가리킨다.숨겨진 변수를 도입하면 볼레즈만 기계의 표현력이 높아진다.
또한 숨겨진 변수를 포함하는 모델의 경우 최대 유사 추정 시 목적 함수에서 숨겨진 변수 주변화에 대한 가시적 변수의 분포만 사용합니다.

볼트만 컴퓨터 제한


제한된 볼레즈만 컴퓨터는 일반 볼레즈만 컴퓨터에 은변수를 도입하는 토대에서 일정한 제약을 가하여 매개 변수의 평가를 간소화하는 모델이다.무엇이 구속입니까
① 노드 사이에 링크가 없음
② 숨겨진 노드 간 링크 없음
에서 설명한 대로 해당 매개변수의 값을 수정합니다.
이 두 가지 조건을 보면 제한된 볼츠만 컴퓨터는 보이는 노드로만 구성된'보이는 층'과 숨겨진 노드로만 구성된'숨겨진 층'두 층을 가지고 서로 다른 층 사이에만 링크가 있는 모델로 표시할 것이다.
이것은 한 층에 비해 한 층이 조건이 독립적이라는 것을 의미한다
· 가시층의 모든 노드의 현재 기대에서 숨겨진 층의 모든 노드의 샘플 그룹까지
· 숨겨진 층의 모든 노드의 샘플군에서 숨겨진 층의 모든 노드와 비슷한 기대치
· 숨겨진 층의 모든 노드에 대한 기대에 따라 가시층의 모든 노드에 대한 샘플 그룹
· 가시층의 모든 노드의 샘플군에서 가시층의 모든 노드와 비슷한 기대치
에서 설명한 방법의 대체 방법입니다.
이 계산은 비교적 간단할 뿐만 아니라, 단번에 각 층의 샘플을 얻을 수 있는 장점을 가지고 있다.볼츠만 기계를 제한하는 것은 모델의 표현력으로 이 은혜를 교환하기 위해서라고 할 수 있다.

학습 제한 볼레즈만 컴퓨터


모형의 형상을 확정한 후에 다음에 해야 할 것은 학습 매개 변수다.
볼레즈만 컴퓨터의 매개 변수 학습을 제한하고 유사함수의 최대화를 목적으로 정통에서 사다리(상승)법을 사용한다.
사다리 상승법은 목표 함수 값이 증가하는 방향에서 매개 변수를 조금씩 갱신하는 과정을 가리키며 함수를 최대화하는 방법을 가리킨다. 이번 목표 함수는 데이터의 유사성(모든 학습 샘플의 동시 분포)이다.이 점을 최대화하기 위해 목표 매개 변수로 목표 함수를 미분하여 사다리를 구한다.
경사도를 계산할 때 가시층, 숨겨진 층의 기대치가 필요하지만 직접 계산이 어렵기 때문에 지부스 샘플링을 사용하여 실제 샘플링을 생성하고 평균값을 취하여 비슷하다.이때 상술한 볼츠만기가 가지고 있는'층간 조건의 독립성'을 제한하는 은혜를 통해 간편하게 계산할 수 있다.
큰 측면에서 볼 때 학습은 다음과 같은 과정을 따를 것이다.
⚫️ パラメータをランダムに初期化


⚫️ 以下繰り返し ----------------------------------------------

    ①    現在のパラメータの値を用いて、可視層と隠れ層を交互にサンプリング

    ②    ①を用いて可視層、隠れ層の各ノードの期待値を計算

  ③    ②を用いて可視層のバイアス、隠れ層のバイアス、リンクそれぞれの勾配(微分)を計算
    
    ④    ③を用いてパラメータを更新
  ---------------------------------------------------------

볼레즈만 컴퓨터의 Python 구현 제한


샘플링을 할 때persistentcontrastivedivergence법을 사용합니다.
처리 효율을 고려하지 않아 수렴 판정을 생략했다.
RBM 클래스 구현
from typing import List
Input = List[int]

def sigmoid(z):
    return 1/(1+np.exp(-z))


class RBM:
    learningRate = 0.005
    sample_num = 100

    #Vnは可視ノード数、Hnは隠れノード数
    def __init__(self , Vn : int , Hn : int):
        self.Vn = Vn
        self.Hn = Hn
        self.bias_v = np.array(np.random.rand(Vn)) #ランダムに初期化
        self.bias_h = np.array(np.random.rand(Hn)) #ランダムに初期化
        self.link  = np.array(np.random.rand(Vn,Hn)) #ランダムに初期化

        self.hidden_value = []

    def train(self, data_for_learning , iteration_times):
        for iteration_times in range(iteration_times):

            # samples_vh はv * h のサンプル
            samples_h =[]
            samples_v = []
            samples_vh = np.zeros((self.Vn,self.Hn))

            messege_in_sampling = 0
            sigmoid_belief = 0

            # ①  現在のパラメータの値を用いて、可視層と隠れ層を交互にサンプリング
            for index in range(RBM.sample_num):

                current_v = data_for_learning[0]
                current_h = np.zeros(self.Hn)

                ## 隠れ層をサンプリング
                for j in  range(self.Hn) :  
                    messege_in_sampling = self.bias_h[j] + sum(list(map(lambda i: current_v[i] * self.link[i][j]  , range(self.Vn))))                

                    sigmoid_belief = 1/(1+ np.exp(-1 * messege_in_sampling))

                    r = np.random.rand(1)

                    if sigmoid_belief > r[0]  :
                        current_h[j] = 1
                    else : 
                        current_h[j] = 0

                samples_h.append(current_h)

                ## 可視層をサンプリング
                for i in range(self.Vn):
                    messege_in_sampling = self.bias_v[i] + sum(list(map(lambda j: current_h[j] * self.link[i][j]  , range(self.Hn))))                

                    sigmoid_belief = 1/(1+ np.exp(-1 * messege_in_sampling))

                    r = np.random.rand(1)
                    if sigmoid_belief > r[0]  :
                        current_v[i] = 1
                    else : 
                        current_v[i] = 0

                samples_v.append(current_v)  

                ## 可視層 ✖️ 隠れ層 をサンプリング
                z = itertools.product(current_v,current_h)

                product = []
                for element in z:
                    product.append(element)

                current_vh = (np.array(list(map(lambda x : x[0] * x[1] ,product))) ) .reshape(self.Vn,self.Hn)

                samples_vh += current_vh

            # ② 可視層、隠れ層の各ノードの期待値を計算
            E_V  = np.sum(np.array(samples_v),axis=0) / RBM.sample_num
            E_H = np.sum(np.array(samples_h),axis=0) / RBM.sample_num
            E_VH = samples_vh / RBM.sample_num

            # ③ 可視層のバイアス、隠れ層のバイアス、リンクそれぞれの勾配(微分)を計算
            ## 可視層のバイアスの微分
            gradient_v = sum(np.array(data_for_learning)) / len(data_for_learning) - E_V

            ## 隠れ層のバイアスの微分
            gradient_h = []
            for j in range(len(self.bias_h)):
                gradient_h_1 = []
                sigmoid_beliefs_h_1 = []

                for n in range(len(data_for_learning)):
                    messege_h_1 = self.bias_h[j] + sum(list(map(lambda i: data_for_learning[n][i] * self.link[i][j]  , range(self.Vn))))
                    sigmoid_belief_h_1 = 1/(1+ np.exp(-1 * messege_h_1))
                    sigmoid_beliefs_h_1.append(sigmoid_belief_h_1)

                gradient_h_1 = sum(sigmoid_beliefs_h_1) / len(data_for_learning)      
                gradient_h_2 = E_H[j]

                gradient_h_j =  gradient_h_1 +  gradient_h_2
                gradient_h.append(gradient_h_j)

            ## リンクの微分
            gradient_vh = []
            for i in range(len(self.bias_v)):
                for j in range(len(self.bias_h)):
                    gradient_vh_1 = []
                    sigmoid_beliefs_vh_1 = []

                    for n in range(len(data_for_learning)):
                        messege_vh = self.bias_h[j] + sum(list(map(lambda i: data_for_learning[n][i] * self.link[i][j]  , range(self.Vn))))
                        sigmoid_belief_vh_1 = 1/(1+ np.exp(-1 * messege_vh))
                        sigmoid_beliefs_vh_1.append(sigmoid_belief_vh_1 * data_for_learning[n][i])

                    gradient_vh_1 = sum(sigmoid_beliefs_vh_1) / len(data_for_learning)      
                    gradient_vh_2 = E_VH[i][j]

                    gradient_vh_ij =  gradient_vh_1 +  gradient_vh_2

                    gradient_vh.append(gradient_vh_ij)

            # ④  ③を用いてパラメータを更新
            self.bias_v += RBM.learningRate *  np.array(gradient_v)
            self.bias_h += RBM.learningRate *  np.array(gradient_h)
            self.link +=  RBM.learningRate *  np.array(gradient_vh).reshape(self.Vn,self.Hn)            

    def energy(self,input : Input) -> float:
        # 可視層のエネルギー
        energy_v = sum(list(map(lambda i : self.bias_v[i] * input[i]  , range(self.Vn))))

        # 隠れ層のエネルギー
        ## 隠れ層の推定値
        hidden_layer = self.getHiddenValue(input)

        energy_h = sum(list(map(lambda j : self.bias_h[j] * hidden_layer[j]  , range(self.Hn))))

        # linkのエネルギー
        accumlator = []        
        for i in range(self.Vn):
            for j in range(self.Hn):
                accumlator.append(input[i]*hidden_layer[j] * self.link[i][j])

        energy_vh = sum(accumlator)       

        energy = -1 * (energy_v + energy_h + energy_vh)

        return energy

    def estimate(self,input:Input) -> float:            

        #エネルギー関数を計算
        energy = self.energy(input)

        #分配関数を計算
        all_patterns = list(itertools.product(range(0,2), repeat=self.Vn))
        partition = sum(list(map(lambda x: np.exp(-1 *self.energy(list(x))) , all_patterns)))

        return  np.exp(-1*  energy ) / partition

    def getHiddenValue(self,input:Input) :

        hidden_layer = np.zeros(self.Hn)

        for j in  range(self.Hn) :  
            messege_in_sampling = self.bias_h[j] + sum(list(map(lambda i: input[i] * self.link[i][j]  , range(self.Vn))))                
            sigmoid_belief = 1/(1+ np.exp(-1 * messege_in_sampling))

            r = np.random.rand(1)
            if sigmoid_belief > r[0]:
                hidden_layer[j] = 1
            else : 
                hidden_layer[j] = 0

        self.hidden_value = hidden_layer

        return hidden_layer
실행 예
# 構築
visible_nord = 3
hidden_nord = 4
rbm = RBM(visible_nord,hidden_nord)

# 学習データ作成
training_data = np.array(list(map(lambda x : np.random.randint(0,2) , range(0,300)))).reshape(100,3)

# パラメータ学習
rbm.train(training_data,10)
print(rbm.link)

input  = [1,0,1]

# 隠れ層の値取得
rbm.getHiddenValue(input)

# エネルギー関数の出力
rbm.energy(input)

# 確率密度の出力
rbm.estimate()

움직일 것 같은데 맞는지 모르겠어...
나 먼저 앞으로 갈게.

다음


다음에 우리는 이 제한된 볼레즈만 기계와 결합하여 심층 볼레즈만 기계를 배치할 것이다.
층이 깊어지면 학습이 급격히 어려워지기 때문에 보르츠만 기계를 제한하여 사전 학습을 하고 파라미터가 좋은 초기값을 얻어 이를 이용하여 전체적인 모델을 미세하게 조정한다.

좋은 웹페이지 즐겨찾기