BAM(Bidirectional Associative Memory) in Ruby

신경망 | 2009/05/08 20:30 | Posted by DMW
크리에이티브 커먼즈 라이선스
Creative Commons License
하악 오랜만에 포스팅이라 신경좀 썼다능. 하지만 이걸 제대루 보려면 선형 연상메모리에 대해서 알 구 있어야 할꺼라능.

BAM(Bidirectional Associative Memory, 양방향 연상 메모리)은 양방향으로 연상이 가능한 연상메모리다. 우리가 친구의 이름을 들으면 얼굴이 떠오르고 친구의 사진을 보면 이름이 떠오르는것과 같이 관련된 페턴쌍들을 양방향으로 연상할 수 있다. 쉽게말하면 X를 입력하면 Y가 나오구 Y를 입력하면 X가 나오는 메모리다.

BAM은 X층과 Y층, 두계층으로 구성되어있고 X층과 Y층은 완전히 연결되어있다. 딱히 입력층과 출력층의 구분은 업ㅂ다능. P개의 연상 패턴쌍 (s, t)를 저장하는 연결강도 w는 다음과 같은 방법으로 구할 수 있는데 반드시 양극성 데이터를 사용해야 된다.


***** 위의 그림에 엄청난 실 수 가 있다능. 아래 그림처럼 구해야 된다능  *****


BAM에 저장된 페턴을 연상하는 과정은 다음과 같다. 먼저, 입력 페턴을 X층 또는 Y층에 페턴을 입력한다. X층에 입력해서 출력을 y1을 구한다고 치고 살펴보자능.


y1은 저런식으로 구해진다능. 그다음에는 y1을 Y층에 입력해서 출력 x1을 구한다능. 이 경우에는 연결강도 w의 치환 메트릭스 wt를 사용한다.


이제 x1을 다시 입력해서 또 출력 y2를 구하고 y2를 입력해서 또 x2을 구하고...........를 계속 반복하는데 언제까지 하나면 더이상 출력이 변하지 않고 특정한 페턴에 수렴할때까지 반복한다능.

대충 이야기는 끝났고(더 풀어낼 내공두 업ㅂ다. >_<) Ruby 소스코드 나간다능.

코드보기



BAM의 단점은 많은 페턴을 기억시키려면 신경망이 너무 커져서 연상하는데 오래걸린다는 거라능. 물론 내가 이걸가지구 실제적인 응용에 써보고 요즘 컴퓨터들에서 얼마나 오래 걸리는지 테스트해보지는 않았다능. 아무튼 뭐 그렇고 일반적으로 BAM에 저장할 수 있는 페턴쌍 P의 수는 X층, Y층의 뉴런수가 n, m일때

P ≤ min(n, m)

정도구 연상 효과를 고려하면
P ≤ √( min(n, m))
라능.


테스트 코드는 귀차느니까 조만간 올려보겠심.

코드보기

저작자 표시 비영리 동일 조건 변경 허락

댓글을 달아 주세요

  1. Favicon of http://lpld.tistory.com BlogIcon Sean 2009/05/08 21:45  댓글주소  수정/삭제  댓글쓰기

    하악 무슨 말인지 모르겠다에요!
    근데 저 수식은 뭘로 그린거임?

  2. Favicon of http://www.filepang.co.kr BlogIcon DMW 2009/05/09 01:56  댓글주소  수정/삭제  댓글쓰기

    수식은 한글로 그린다음 스샷찍어서 포토샵으로 오려냈지말입니다.
    ....보니까 수식이 틀려서 급 수정했습니다.

  3. 2009/05/11 12:11  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  4. jhl0117 2010/02/26 23:41  댓글주소  수정/삭제  댓글쓰기

    고맙습니다 ^-^

    공부하는데 많이 도움이 된것 같아요.

Hopfield network algorithm

신경망 | 2009/04/09 23:48 | Posted by DMW
크리에이티브 커먼즈 라이선스
Creative Commons License

냉무 >_<
저작자 표시 비영리 동일 조건 변경 허락

댓글을 달아 주세요

  1. Favicon of http://shieldhorizon.com BlogIcon PEPE 2009/04/20 00:58  댓글주소  수정/삭제  댓글쓰기

    아오 여기글보다가 교수님이 하라고 던져준 neural network using c-sharp 어쩌구 책이 갑자기 생각이났넥,,

Hopfield network 데모

신경망 | 2009/03/24 08:35 | Posted by DMW
크리에이티브 커먼즈 라이선스
Creative Commons License
간단한 데모 프로그램입니다.

HELLO 라는 문자 페턴을 학습시키구...노이즈가 섞인 입력이 들어갔을때
그걸 원래의 패턴으로 수렴시키는 과정을 볼 수 있다능


http://www.filepang.co.kr/entry/Hopfield-network의-루비-구현

소스 코드는 위의 링크랑 별 다를게 업ㅂ구...'HELLO' 패턴을 타이핑하는게 무지 귀찬았다능 -_-;;
저작자 표시 비영리 동일 조건 변경 허락

댓글을 달아 주세요

  1. sloth 2009/03/24 11:31  댓글주소  수정/삭제  댓글쓰기

    우와아 신기하다능!!!!

  2. sloth 2009/03/25 11:04  댓글주소  수정/삭제  댓글쓰기

    하악하악

  3. Favicon of http://www.filepang.co.kr BlogIcon DMW 2009/03/25 18:29  댓글주소  수정/삭제  댓글쓰기

    읭읭 읭읭 읭읭

Hopfield network의 루비 구현

신경망 | 2009/02/14 18:39 | Posted by DMW
크리에이티브 커먼즈 라이선스
Creative Commons License
역시 별 내용은 업ㅂ구 소스 코드만 나온다능

require "matrix"
require "pp"

class Hopfield
    attr_reader :output_pattern
    
    def initialize(pattern_size)
        @pattern_size = pattern_size
        @w = Matrix[ *[].fill([].fill(0, 0, @pattern_size), 0, @pattern_size) ]
    end
    
    def learn(pattern)
        delta_w = Matrix[pattern].t * Matrix[pattern]        
        @w += delta_w - Matrix.I(@pattern_size)
    end
    
    def recall(input_pattern, iteration_sequence)
        old_output = [].fill(rand, 0, @pattern_size)
        new_output = input_pattern
        
        while true
            iteration_sequence.each do |iter|
                net = input_pattern[iter] + Vector[*input_pattern].inner_product(@w.column(iter))
                new_output[iter] = af(net, new_output[iter])
                print "#{iter+1} : "
                pp new_output
            end
            puts
            if old_output == new_output
                @output_pattern = new_output
                break
            else
                old_output = new_output
            end
        end
    end
    
    private
    def af(x, net)
        if x > 0
            +1
        elsif x == 0
            net
        else
            -1
        end
    end
end

x = [ [+1, +1, -1, -1], [-1, -1, +1, +1] ]

net = Hopfield.new(4)

x.each do |i|
    net.learn i
end

puts "sequence [2, 1, 4, 3]"
net.recall [+1, -1, -1, -1], [1, 0, 3, 2]
puts "sequence [4, 1, 3, 2]"
net.recall [+1, -1, -1, -1], [3, 0, 2, 1]


Output:
sequence [2, 1, 4, 3]
2 : [1, 1, -1, -1]
1 : [1, 1, -1, -1]
4 : [1, 1, -1, -1]
3 : [1, 1, -1, -1]

2 : [1, 1, -1, -1]
1 : [1, 1, -1, -1]
4 : [1, 1, -1, -1]
3 : [1, 1, -1, -1]

sequence [4, 1, 3, 2]
4 : [1, -1, -1, -1]
1 : [1, -1, -1, -1]
3 : [1, -1, -1, -1]
2 : [1, 1, -1, -1]

4 : [1, 1, -1, -1]
1 : [1, 1, -1, -1]
3 : [1, 1, -1, -1]
2 : [1, 1, -1, -1]


저작자 표시 비영리 동일 조건 변경 허락

댓글을 달아 주세요

  1. Favicon of http://www.filepang.co.kr BlogIcon DMW 2009/02/19 13:33  댓글주소  수정/삭제  댓글쓰기

    지금보니까 좀 이상하네....하지만 귀차느니까 고치진 않는다능. recall 메소드에서
    new_output = input_pattern 를 new_output = input_pattern.clone 로
    net = input_pattern[iter] + Vector[*input_pattern].inner_product(@w.column(iter)) 를
    net = new_output[iter] + Vector[*new_output].inner_product(@w.column(iter)) 로 바꿔야 될꺼임

  2. Favicon of http://marocchino.tistory.com BlogIcon progr_ 2009/02/22 19:10  댓글주소  수정/삭제  댓글쓰기

    이론을 보르니 입력 * 결과가 무슨 의미인지 모르겠다능 ..;ㅅ; 어흐흐흐 늅늅.

크리에이티브 커먼즈 라이선스
Creative Commons License
역전파망 backpropagation network 혹은
다층퍼셉트론인지 multi-layer perceptron 으로 불리는 신경망의 구현 
↑ 다층퍼셉으론이라구 검색을 마니 길래.....수정

별 내용은 업ㅂ구 코드만 나옵니다. 구현은 더럽지만 어째든 작동함 >_<

require 'matrix'

def generate_random(min = 0.0, max = 1.0)
    rand * (max - min) + min
end

class BackPropagationNet
    attr_reader :output_pattern, :error
    attr_reader :weights
    
    def initialize(number_of_neurons, a = 0.1, polarity = :bipolar )
        @number_of_neurons = number_of_neurons
        @a = a
        
        if polarity == :bipolar
            @af = Proc.new { |x| (1.0 - Math.exp(-x)) / (1.0 + Math.exp(-x)) }
            @diff_af = Proc.new { |x| 0.5 * (1 - x*x) }
        elsif polarity == :unipolar
            @af = Proc.new { |x| 1.0 / (1 + Math.exp(-x)) }
            @diff_af = Proc.new { |x| x * (1 - x) }
        end
        
        # initialize weights
        @weights = []
        for i in 1..@number_of_neurons.size-1
           @weights << Matrix[ *[].fill(0..@number_of_neurons[i]-1) { 
            [].fill(0..@number_of_neurons[i-1]) { generate_random(-0.5, 0.5) } } ]
        end
    end
    
    def recall(input_pattern)
        @net_weights = []
        @outputs = [input_pattern]
        
        # compute net_weight and output for each layer
        @weights.each do |w|
            net_weight = (Matrix[ [1, *@outputs[-1]] ] * w.t).to_a.flatten
            
            @net_weights << net_weight
            @outputs << net_weight.collect { |y| af(y) }
        end
        
        @output_pattern = @outputs[-1]
    end
    
    def learn(input_pattern, desired_pattern)
        recall input_pattern                # comput output
        compute_error desired_pattern       # comput error
        
        deltas = []
        temp = []
        
        # compute delta of output layer
        desired_pattern.each_with_index do |d, i| 
            temp << (d - @output_pattern[i]) * diff_af(@output_pattern[i])
        end
        deltas << temp
        
        # compute delta for each hidden layer
        (@number_of_neurons.size-3).downto(0) do |i|    # number of hidden layer
            temp = []
            
            @number_of_neurons[i+1].times do |j|    # number of nuerons in current layer
                current_delta = 0
                
                @number_of_neurons[i+2].times do |k|   # number of nuerons in next layer
                    a = deltas[-1][k]
                    b = @weights[i+1][k, j+1]
                    current_delta += a * b
                end
                temp << diff_af(@outputs[i+1][j]) * current_delta
            end
            deltas << temp
        end
        deltas.reverse!
        
        # update weights        
        (@weights.size).times do |i|
            temp = @weights[i].to_a
            temp.size.times do |j|
                temp[j].size.times do |k|
                    delta = deltas[i][j]
                    output = 1
                    output = @outputs[i][k-1] if k != 0
                    temp[j][k] += @a * delta * output
                end
            end
            
            @weights[i] = Matrix[*temp]
        end
        
        @error
    end
    
    private
    def af(net)
        @af.call(net)
    end
    
    def diff_af(net)
        @diff_af.call(net)
    end
    
    def compute_error(desired_pattern)
        @error = 0        
        desired_pattern.each_with_index do |d, i| 
            @error += (d - @output_pattern[i])**2
        end
        
        @error = 0.5 * @error
    end
end

net = BackPropagationNet.new [2, 10, 20, 2, 1], 1
x = [ [-1.0, -1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, 1.0] ] y = [ [-1.0], [1.0], [1.0], [-1.0] ] print "BackPropagation Network(bipolar) simulation\n\n" puts "Before training" x.each do |inp| puts "[#{inp.join(", ")}] : #{net.recall(inp).join(", ")}" end print "\n\n" print "Training.....\n" 20000.times do |t| errors = [] total_error = 0 x.each_with_index do |inp, i| error = net.learn(inp, y[i]) total_error += error errors << error end #puts "#{t} : total error : #{total_error}" if errors[0] <= 0.0005 and errors[1] <= 0.0005 and errors[2] <= 0.0005 and errors[3] <= 0.0005 puts "#{t} - training complete" break end end print "\n\n" puts "After training" x.each do |inp| puts "[#{inp.join(", ")}] : #{net.recall(inp).join(", ")}" end puts "\n\nd>_<b"


실행결과
BackPropagation Network(bipolar) simulation

Before training
[-1.0, -1.0] : -0.0542964479707475
[-1.0, 1.0] : -0.0321681144483251
[1.0, -1.0] : -0.0630540475165143
[1.0, 1.0] : -0.0423248262571378


Training.....
0 : total error : 2.67503298609203
1 : total error : 2.61532655049581
2 : total error : 2.58326168229993
3 : total error : 2.56751261506715
4 : total error : 2.56008686383712
.
.
.
    대략 생략
.
.
.
907 : total error : 0.00179664781743692
908 : total error : 0.00177502154732301
909 : total error : 0.00175389506569839
910 : total error : 0.00173325145741755
911 : total error : 0.00171307455680966
911 - training complete


After training
[-1.0, -1.0] : -0.97101067663663
[-1.0, 1.0] : 0.97292656672447
[1.0, -1.0] : 0.968576984668481
[1.0, 1.0] : -0.971187181479301


d>_<b

저작자 표시 비영리 동일 조건 변경 허락

댓글을 달아 주세요

모멘텀 BP 알고리즘 momentum BP algorithm

신경망 | 2009/01/15 17:54 | Posted by DMW
크리에이티브 커먼즈 라이선스
Creative Commons License
Step 1. Initialize weights and count
  v, w <- small random number
  p <- number of traning pattern pairs
  k <- 1
  E <- 0
Initialize temporary weight variation
  temp_w <- 0
  temp_v <- 0

Step 2. Set learning rate(a) and Emax α(>0)
Set momentum constant β(0 < β < 0.8)

Step 3. For each traning pattern pair (X, D)
do step 4 - 9 until k = p

Step 4. compute output
NET_z = Xk inner_product Vk
Z = f(NET_z)

f(NET_z) = 1 / (1 + exp(-NET_z))                                    ; unipolar sigmoid
                (1 - exp(-NET_z)) / (1 + exp(-NET_z))           ; bipolar sigmoid

NET_y = Z inner_product Wt
Y = f(NET_y)

f(NET_y) = 1 / (1 + exp(-NET_y))                                    ; unipolar sigmoid
                (1 - exp(-NET_y)) / (1 + exp(-NET_y))           ; bipolar sigmoid

Step 5. Compute output error
E <- 0.5 * (Dk - Yk)^2 + E

Step 6. Compute error signal
delta_y = (d-y) * y * (1 - y)                             ; unipolar sigmoid
              0.5 * (d-y) * (1-y^2)                         ; bipolar sigmoid

delta_z = z * (1-z) * sumation of delta_y*W      ; unipolar sigmoid
             0.5 * (1-z^2) * sumation of delta_y*W ; bipolar sigmoid

Setp 7. Update weights
Wk+1 = Wk + ΔWk
        = Wk + a * δy * Zk + βΔWk-1

Vk+1 = Vk + ΔVk
       = Vk + a * δz * Xk + βΔVk-1

Step 8. Save weight variation
temp_w <- Δwk
temp_v <- Δvk


Step 9. Increase counter and goto Step 3
k <- k + 1
 
Step 10. Test stop condition
If E < Emax, stop
else E <- 0, goto Step 3

역시 책업ㅂ을때 보려구 블로그에 백업
저작자 표시 비영리 동일 조건 변경 허락

댓글을 달아 주세요

단층 신경망

신경망/퍼셉트론 | 2009/01/09 16:50 | Posted by DMW
크리에이티브 커먼즈 라이선스
Creative Commons License
 이번에는 퍼셉트론 학습 방법에 대해서 알아보고 그걸 더러운 C++로 구현해 볼껀데...그전에 신경망의 의한 패턴 분류가 어떻게 일루어 지는지를 살펴보자능. 이제 끝임 하악하악


페턴 분류

 일단 페턴이란거 뭐냐믄.....어떤 오브젝트를 정량적으로 표현한거라구 한다능. 외우고 있긴 하지만 이게 뭔소린진 나도 가슴으로 느껴지진 않네 -_-;;; 아무튼 페턴은 크가 공간 페턴 spatial pattern 이랑 시변 페턴 temporal pattern 으로 구분된다능. 공간 페턴은 쉽게 말하면 그림, 그러니까 이미지이고 시변 페턴은 음성 신호, 심전도 파형같은 것들이라능.

 페턴 분류 시스템은 아래 그림과 같은 걸로 구성되 있다능. 일단 그림을 보고 하나씩 살펴보자능.


 입력은 그냥 입력이구....트랜스튜서 transducer 라는건 실 세계의 데이터를 입력받아서 뉴로 컴퓨터, 그러니까 신경망이 처리하기 용이한 형태로 변환하는 기능을 한다능. 디지털 데이터를 다루는 신경망이면 디지털로 바꾸고 아날로그 데이터를 다루는 신경망이면 아날로그로 바꾸는거라능. 트랜스튜서의 출력을 페턴 백터 pattern vector 라구 한다능.

  요 페턴 백터를 가지구 바로 분류기 classifier 에 입력해도 상관은 업ㅂ지만 특징 추출기 feature extractor 를 한번 거쳐서 분류할 페턴들 간의 특징이 잘 드러나도록 하면 분류기의 성능을 더 높일 수 있따능. 이제 특징 추출기에서 뽑아낸 특징 백터를 분류기로 분류하면 되는거라능. 단층 신경망을 사용한 패턴 분류기는 대충 이런식이라능


 입력층 뉴런은 n개고 그걸 m개의 클러스터로 분류하는 분류기의 그림이라능. 그래서 출력층 뉴런이 m개임. 아무튼 이 뉴런들의 출력값중에 가장 큰값을 선택해서 입력된 특징 백터 x가 어떤 클러스터에 속하는지를 판별해 내는거라능

신경망의 패턴 분류

 이제 신경망이 페턴을 어떻게 분류해 내는지를 또 보자. 이거까지만 알아보구 퍼셉트론 만들어 볼꺼라능. n차원의 패턴 공간에서 요렇게 생긴 패턴 하나는 하나의 점으로 표시 할 수 있다능.

x = [ x1 x2 x3 ... xn ]

비슷한 페턴들은 패턴 공간에서 끼리끼리 뭉쳐있을꺼라능. 그러니까 대충 이런 그림이 나올꺼라능.


 저 빨간선을 기준으로 페턴을 두 그룹 j랑 k로 으로 나눌 수 있다능. 저 빨간선을 판단면 decision surface 라고 한다능. 그그에서는 패턴 공간이 2차원이라 선으로 표현된거구 페턴이 n차원이라면 n차원 초평면 hyperplane 이 될꺼심. 그리구 이 판단면을 정의하는 함수를 판별 함수 discriminant function 라구 한다능.

 d(x)를 판별 함수라고 치구 dk(x)랑 dj(x) 를  각 그룹에 속한 패턴 x의 판별 함수 값이라고 하자. 그러면 어떤 패턴 x가 다음 조건을 만족하면 k 그룹에 속해 있다고 할 수 있다능.

dk(x) > dj(x)         j ≠ k,  j = 1, 2, 3, ...... R

 R 은 클러스터의 갯수라능. 만약 패턴들을 두개의 클러스터로 분류하는 경우일 때

d1(x) = d2(x)          or         d1(x) - d2(x) = 0

 이 된다면 클러스터 1, 2의 경계면이기 때문에 어떤 클러스터에 속한다고 말할 수 업ㅂ게된다. 바루 위에 있는 식을 판변면이라구 한다능. 그러면 T가 0인 양극성 계단 함수를 쓰는 신경망으로 패턴을 두개로 분류한다고 해보자. 입력 패턴이 클러스터 1에 속해면 +1, 클러스터 2에 속하면 -1 이라구 가르치면 될꺼라능.

입력 가중합은 NET는 이렇게 되고

NET = x · w
       = x1*w1 + x2*w2 + x3*w3 ..... xn*wn + b

NET > T인 영역이랑 NET < T인 영역의 경계면은 요렇게 된다능

x1*w1 + x2*w2 + x3*w3 ..... xn*wn + b = 0

어떠냐능. 평면의 방정식 아니냐능. 요게바루 판단면....그러니까 저 빨간선이 되는거심. 그럼 이제 손으로 신경망을 한번 만들어 보자능.


 2차원 패턴 공간에 패턴 4개가 있고 두개로 분류도 있다능. 빨간선은 대충 그어 봤다능.

 입력 페턴
 [ x1 x2 ]
 클러스터
A [ 0    0 ]  0
B [ 1    0 ]  0
C [ 0    1 ]  0
D [ 1    1 ]  1

 보면 알겠지만 AND의 진리표임 >_<. 대충 끄셔놓은 빨간선이 새로축 x2 하고 만나는 값을 1.5라고 하고 가로축 x1하고 만나는 값을 1.5 라고하고 기울기를 -1 이라고 치면.....

x2 = -x1 + 1.5
x1 + x2 - 1.5 = 0


이걸 가지고 신경망을 설계해보면....


요렇게 나온다. 그럼 다음 패턴을 또 해보자. 이번꺼는 XOR의 패턴이라능.


죽어따 깨나도 직선 하나로는 세모랑 네모를 구별할 수 업ㅂ다. 이렇게 직선 하나로 분리가 불가능한 경우를 선형 분리 불가능 linear non-separable 라고 한다. 반면 AND와 같은 경우 처럼 분리가 가능한 경우를 선형 분리 가능 linear separable 이라고 한다능.

 벌써 눈치깠겠지만....단층 신경망은 선형 분리 불가능한 페턴을 분류해 낼 수가 업ㅂ다능. 이 경우에는 신경망을 다층으로 만들어서 은닉층에서 원래의 페턴 공간을 선형 분리 가능한 공간으로 맵핑 시킨후에 분리를 해내야 된다능. 이 정도로만 하구 다층 신경망 얘기는 나중에 기회가 되면 또 하자능.


퍼셉트론 Preceptron 학습법

 누가 퍼셉트론을 처음 제안 했는지 같은 구질 구질한 역사 얘기는 건너 뛰고.....원래 퍼셉트론은 수용층, 연합층, 반응층 3계층으로 구성된 신경망인데 수용층은 별루 하는일이 업ㅂ어서 수용층이랑 연합층을 하나루 합칠수가 있다능. 그래서 생긴건 이렇게 생겼다능.


 전에 봤던 그림이라능. 데헷 >_<. 아무튼 퍼셉트론 학습법에는 이진 활성화 함수랑 연속 활성화 함수 둘다를 쓸 수 있다능. 초기 연결 강도는 임의의 작은 값으로 설정하면 되구 학습 신호 γ 는 아래 처럼 구할 수 있다능.


 간단하네. 신선함! 목표치 d랑 실제 출력 y의 오차를 학습 신호로 쓸 고 있음. 아무튼 k 단계의 연결 강도 변화량은 아래 처럼 구할 수 있다능.

k+1 단계의 연결 강도는 아래처럼 구하면 된다능.

다 끝났음. 이제 구현하면 된다능 >_<. 구체적인 프로세스는 http://www.filepang.co.kr/entry/퍼셉트론-학습-알고리즘 여기 나와 있으니까 꼭 보라능.

구현

 먼저 NeuralNetwork 라는 이름의 namespace를 하나 열구 클래스를 만들자능. 그리구나서 생성자에서 연결강도를 초기화 하면 되겠심.

namespace NeuralNetwork {
    template<int N, int M>
    class Perceptron{
    public:
        Perceptron(double (*activation_function)(double), double a = 0.1) {
            this->a = a;
            this->activation_function = activation_function;

            for (int i = 0; i < M; i++){
                weights[i].resize(N + 1);
                std::generate_n(weights[i].begin(), N + 1, RandomGenerator(-0.1, 0.1));
            }
        }
    private:
        std::vector<double> weights[M];
        double (*activation_function)(double);
        double a;
    };
}

 N은 입력의 갯수고 M은 뉴런의 갯수라능. a는 학습률 α고 activation_function은 활성함수로 사용할 함수의 함수 포인터라능. 생성자에서 각 뉴런들의 연결강도를 바이어스의 연결강도까지 포함한 N+1개의 랜덤값으로 초기화 해줬다능. RandomGenerator 는 요렇게 생겼심.

1
2
3
4
5
6
7
8
9
10
namespace NeuralNetwork {
    struct RandomGenerator {
        RandomGenerator(double _min = 0.0, double _max = 0.1) : min(_min), max(_max) {}
        double operator()(void) {
            double random = std::rand() / RAND_MAX;
            return random * (max - min) - min;
        }
        double min, max;
    };
}

T가 0인 양극성 계단 함수도 만들자능.

1
2
3
4
5
6
7
namespace NeuralNetwork {
    double bipolar_step_function(double x){
        if (x >= 0.0)
            return 1.0;
        return -1.0;
    }
}

이제 신경망의 출력을 구하는 함수를 만들어 보자능.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace NeuralNetwork {
    template<int N, int M>
    class Perceptron{
    public:
        template<typename T>
        void recall(T input_begin, T input_end) {
            for (int i = 0; i < M; i++) {
                output_pattern[i] = std::inner_product(weights[i].begin(), weights[i].begin()+N, input_begin, 0.0);
                output_pattern[i] += weights[i][N] * 1.0;

                output_pattern[i] = activation_function(output_pattern[i]);
            }
        }
    public:
        double output_pattern[M];
    };
}

패턴 x를 받아서 가중합을 구하는데...입력에는 바이어스를 포함하지 않아도 되도록 했다능.

output_pattern[i] = std::inner_product(weights[i].begin(), weights[i].begin()+N, input_begin, 0.0);

바어어스를 제외한 가중합을 구하고...

output_pattern[i] += weights[i][N] * 1.0;
output_pattern[i] = activation_function(output_pattern[i]);

바이어스까지 포함시킨후에 출력값을 구했다능. 출력 output_pattern은 getter를 만들기 귀차느니까 public 으로 했다능. 이제 퍼셉트론 학습 방법을 사용해서 학습 시킬는 함수를 만들면 된다능.

namespace NeuralNetwork {
    template<int N, int M>
    class Perceptron{
    public:
        template<typename T>
        double learn(T input_begin, T input_end, T desired_being, T desired_end) {
            recall(input_begin, input_end);	                                        
            double error = hamming_distance(output_pattern, output_pattern + M,
                                            desired_being, desired_end);	        

            for (int i = 0; i < M; i++) { 			
                int j;
                double r = *(desired_being+i) - output_pattern[i];	

                for (j = 0; j < N; j++)
                    weights[i][j] += a * r * *(input_begin+j);		
                weights[i][j] += a * r * 1;                         
            }

            return error;
        }
    };
}

 파라미터로 패턴 x랑 기대하는 값 d를 입력 받았다능. 그리고나서 위에 링크에 있는데로 패턴 x를 입력으로 하는 출력을 구해내고 구해진 출력을 가지고 학습신호 γ를 구했심. γ랑 r이랑 비슷하게 생겼길래 학습 신호의 변수 이름은 r로 했다능.

recall(input_begin, input_end);

 출력을 구해나는 코드고 외부에서 학습이 얼마나 진행됐는지 알 수 있도록 현재의 출력과 기대값과의 오차를 계산해서 리턴하도록 해줬다능. 오차는 해밍 거리를 사용해서 구해냈다능.

double error = hamming_distance(output_pattern, output_pattern + M, desired_being, desired_end);

안에 있는  for문이 실제 학습을 진행 시키는 부분이라능. 출력층의 모든 뉴런들에서 학습 신호를 구해내고 바이어스를 포함한 모든 연결 강도를 변경한다능.

double r = *(desired_being+i) - output_pattern[i];

for (j = 0; j < N; j++)
    weights[i][j] += a * r * *(input_begin+j);
weights[i][j] += a * r * 1;

 마찬 가지로 learn 함수를 사용할때도 입력 패턴에 바이어스를 포함시키지 않도록 했기 때문에....바이어스 연결 강도는 내부 for 문에서 빼내서 따로 구했다능. 저렇게 밖에 못하겠더라능 -_-;;

전체 소스코드랑 OR 게이트의 학습 결과라능.

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <ctime>
#include <cmath>

namespace NeuralNetwork {
    struct RandomGenerator {
        RandomGenerator(double _min = 0.0, double _max = 0.1) : min(_min), max(_max) {}
        double operator()(void) {
            double random = std::rand() / RAND_MAX;
            return random * (max - min) - min;
        }
        double min, max;
    };

    template<typename T>
    double hamming_distance(T input_begin, T input_end, T desired_begin, T desired_end) {
        double distance = 0.0;

        for (; input_begin != input_end && desired_begin != desired_end; ++input_begin, ++desired_begin)
            distance += std::pow(*desired_begin - *input_begin, 2);

        return std::sqrt(distance);
    }

    double bipolar_step_function(double x){
        if (x >= 0.0)
            return 1.0;
        return -1.0;
    }

    double bipolar_sigmoid_function(double x){
        return (1.0 - std::exp(-x)) / (1.0 + std::exp(-x));
    }

    template<int N, int M>
    class Perceptron{
    public:
        Perceptron(double (*activation_function)(double), double a = 0.1) {
            this->a = a;                                            // learning rate
            this->activation_function = activation_function;        // activation functoin

            // initialize weights include bias
            for (int i = 0; i < M; i++){
                weights[i].resize(N + 1);
                std::generate_n(weights[i].begin(), N + 1, RandomGenerator(-0.1, 0.1));
            }

            std::fill(output_pattern, output_pattern + M, 0.0);     // initialize output
        }
        ~Perceptron() {}

        template<typename T>
        void recall(T input_begin, T input_end) {
            for (int i = 0; i < M; i++) {
                output_pattern[i] = std::inner_product(weights[i].begin(), weights[i].begin()+N, input_begin, 0.0);
                output_pattern[i] += weights[i][N] * 1.0;

                output_pattern[i] = activation_function(output_pattern[i]); // compute output
            }
        }

        template<typename T>
        double learn(T input_begin, T input_end, T desired_being, T desired_end) {
            recall(input_begin, input_end);	                                        // compute output
            double error = hamming_distance(output_pattern, output_pattern + M,
                                            desired_being, desired_end);	        // compute error

            for (int i = 0; i < M; i++) { 			// for all neurons in output layer
                int j;
                double r = *(desired_being+i) - output_pattern[i];	// compute learning signal

                for (j = 0; j < N; j++)
                    weights[i][j] += a * r * *(input_begin+j);		// compute delta_w and update weight
                weights[i][j] += a * r * 1;                         // update weight of bias
            }

            return error;
        }

    private:
        std::vector<double> weights[M];
        double (*activation_function)(double);
        double a;

    public:
        double output_pattern[M];
    };
}

int main(void)
{
    using namespace std;
    using namespace NeuralNetwork;

    srand(time(NULL));

    const int n = 2;
    const int m = 1;

    Perceptron<n, m> net(bipolar_step_function, 0.3);

    double input_pattern[4][n] = { {-1.0, -1.0}, {-1.0, 1.0}, {1.0, -1.0}, {1.0, 1.0} };
    double desired_pattern[4][m] = { {-1.0}, {1.0}, {1.0}, {1.0} };

    cout.precision(5);
    cout << showpoint << showpos;
    cout << "before learning...." << endl;

    for (int i = 0; i < 4; i++) {
        net.recall(input_pattern[i], input_pattern[i]+n);

        cout << "    [ ";
        copy(input_pattern[i], input_pattern[i]+n, ostream_iterator<double>(cout, " "));
        cout << "] : ";

        copy(net.output_pattern, net.output_pattern+m, ostream_iterator<double>(cout, " "));
        cout << endl;
    }

    cout << "\ntraining...." << endl;

    cout << noshowpos;
    for (int i = 0; i < 200; i++) {
        double error = 0.0;

        for (int j = 0; j < 4; j++)
            error += net.learn(input_pattern[j], input_pattern[j]+n,
                                desired_pattern[j], desired_pattern[j]+m);

        cout << "    #"  << i << " : error : " << error << endl;

        if (error == 0.0) {
            cout << "    Traning complete" << endl;
            break;
        }
    }

    cout << "\nafter learning...." << endl;
    cout << showpos;

    for (int i = 0; i < 4; i++) {
        net.recall(input_pattern[i], input_pattern[i] + n);

        cout << "    [ ";
        copy(input_pattern[i], input_pattern[i]+n, ostream_iterator<double>(cout, " "));
        cout << "] : ";

        copy(net.output_pattern, net.output_pattern+m, ostream_iterator<double>(cout, " "));
        cout << endl;
    }

    return 0;
}


Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
before learning....
    [ -1.0000 -1.0000 ] : -1.0000 
    [ -1.0000 +1.0000 ] : +1.0000 
    [ +1.0000 -1.0000 ] : +1.0000 
    [ +1.0000 +1.0000 ] : +1.0000 

training....
    #0 : error : 0.0000
    Traning complete

after learning....
    [ -1.0000 -1.0000 ] : -1.0000 
    [ -1.0000 +1.0000 ] : +1.0000 
    [ +1.0000 -1.0000 ] : +1.0000 
    [ +1.0000 +1.0000 ] : +1.0000 

웃기게도 초기연결강도가 분리에 성공했음 -_-;;
저작자 표시 비영리 동일 조건 변경 허락

'신경망 > 퍼셉트론' 카테고리의 다른 글

단층 신경망  (5) 2009/01/09
신경망의 분류와 학습  (2) 2009/01/07
생물학적 신경망과 인공 신경망  (5) 2009/01/06
뉴로 컴퓨터 개요  (2) 2008/12/29

댓글을 달아 주세요

  1. Favicon of http://eufonius.tistory.com BlogIcon prismatic 2009/01/09 18:01  댓글주소  수정/삭제  댓글쓰기

    우와 코드 길어! 루비로 반정도 하지 않았나요?!

    여튼 너무 감사합니다 ㅠㅜ 감동했음! 열심히 읽을게요!

  2. Favicon of http://blog.bab2min.pe.kr BlogIcon ∫2tdt=t²+c 2009/01/12 22:35  댓글주소  수정/삭제  댓글쓰기

    와우 이제 나도 신경망 프로그래밍을 하는건가ㅎㅎㅎ

신경망의 분류와 학습

신경망/퍼셉트론 | 2009/01/07 08:33 | Posted by DMW
크리에이티브 커먼즈 라이선스
Creative Commons License
 플라스틱흉의 기대에 부흥하고자 쓰기 시작한 신경망 이론에 관한 글들이 거의 끝나가고 있다. 이번에 신경망의 분류랑 학습에 대해서 알아보구, 다음글에서 신경망 모델중에 하나인 퍼셉트론을 구현해볼꺼다. 그러면 끝이라능.

신경망의 분류

지금까지 나온 신경망 모델은 디게 많은데 아래와 같은 조건들로 구별해 볼 수 있다능

1. 계층수 : 단층 구조, 다층 구조
2. 출력 형태 : 순방향 구조, 순환 구조
3. 데이터 유형 : 디지털, 아날로그
4. 학습 방법 : 지도 학습, 자율 학습, 경쟁식
5. 활성화 함수 : 단극성, 양극성

이제 이것들에 대해서 하나씩 살펴볼꺼라능.

먼저 계층수에 대해서 살펴보자. 신경망은 계층 layer 의 수에 따라 단층과 다층으로 나뉜다. 일단 그림을 보자능



 단층 single-layer 신경망은 가장 단순한 구조로 입력층 input layer 하구 출력층 output layer 으구 구성되 있다. 입력층 X는 외부로부터 입력을 받아들이는 역활을 하고 출력층 Y로 신경망에서 처리된 결과가 출력된다. 단층 신경망의 출력 y1....ym 은 이렇게 구한다. wij는 입력층 뉴런 j랑 출력층 뉴런 i와의 연결강도다.


 다층 multi-layer 신경망 은 단층 신경망보다 계층의 수가 더 많다. 당연한가 -_-;;;;......아무튼 입력층과 출력층 사이의 계층을 은닉층 hidden layer 라구 하는데 신경망의 외부에서는 이게 있는지 업ㅂ는지 확인할 방법이 업ㅂ어서 그렇게 부른다. 그림에는 은닉층이 하나 밖에 업ㅂ지만 몇개가 되도 상관은 업ㅂ다능. 아무튼 다층 신경망의 출력은 이렇게 구한다. vij 는 입력층 뉴련 j랑 은닉층 뉴런 i와의 연결 강도다.



 별다를꺼업ㅂ다 >_<.


 순방향 신경망 feedforward network 과 순환 신경망 recurrent network 은 출력층의 출력이 다시 신경망의 입력으로 회귀 되는지 않되는지를 가지고 구별하면 쉽다. 그러니까 뭐 이런식이다.


 출력이 다시 입력으로 들어간다. 꼭 조합논리회로랑 순차논리회로 그림이랑 비슷하다 >_<. 쪼금더 정확히 얘기하면 어떤 레이어의 출력이 더 앞에 있는 레이어나 자신의 입력으로 회귀되면 순환 신경망이다. 순환 신경망의 출력을 어떻게 구하는지는 대충 감이 올태니 그냥 넘어가자능.

데이터 유형에 따라 아날로그랑 디지털로 나누는거는 뭐...다 알태니까 대충 넘어가고 지도 학습 supervised learning 이랑 자율 학습 unsupervised learning, 경쟁식 학습 competitive learning 에 대해서 잠깐만 얘기하자능.

 지도 학습은 신경망의 학습에 입력 패턴 x랑 그에 해당하는  목표치 d 를 사용하는 학습방법이다. x와 d의 쌍 (x ,d)를 학습 패턴쌍 traning pattern pair 이라구 한다. 쉽게 말하면 영문자 A의 그림을 보여주고 이건 A다 라구 가르쳐주는거다. 자율 학습은 학습 패턴쌍이 존제하지 않는다. 입력 패턴 x들을 마구 보여주면 지가 알아서 비슷한것끼리 분류해논다. 신기빵빵함. 경쟁식 학습은 지도 학습이랑 비슷한데...연결 강도를 변화시키는 과정에서 차이가 있다. 학습 과정에서 승자 뉴런 winner neron 이 정해지고 승저 뉴련 주변의 뉴런들에 속한 연결강도들만 변한다.

이제 활성화 함수 얘기를 해보자능. 활성화 함수는 단조 증가하는 함수여야 한다. 대충 3가지 기준으로 구분해 볼 수 있다.

1. 단극성 unipolar / 양극성 bipolar 함수
2. 선형 linear / 비선형 nonlinear 함수
3. 연속 continuous / 이진 binary 함수

활성함수 몇 개들을 수식으로 살펴보자능.


많이 쓰인다고 생각하는거 몇 개들만 써봤다능. 저거 말고도 많음 -_-;. 더 설명한거는 업ㅂ고....시그모이드 함수의 경우 람다가 무한대에 가까워지면 계단 함수랑 동일한 형태가 된다. 보통 람다값으로 1을 많이 쓴다고 한다능.


신경망의 학습

 학습 방법의 분류는 위에서 봤으니까 일반적으로 학습이 어떤 단계를 거쳐서 이루어 지는지를 보자능. 먼저 지도 학습의 경우임.

1. 응용 목적에 적절한 신경망 구조를 설계한다.
2. 연결 강도를 초기화 한다.
3. 학습 패턴쌍 (x, d)를 입력하여 신경망의 출력 y를 구한다.
4. 출력 y랑 목표치 d를 비교해서 오차를 산출한다.
5. 오차를 학습 신호 발생기에 입력해서 연결강도 변화랑 Δw를 구한다.
5. 연결강도를 Δw만큼 변경한다.
7. 변화단 연결 강도에 대해서 3 ~ 6 단계를 반복한다.
8. 더 이상 연결 강도가 변하지 않으면 학습을 종료한다.

다음으로 자율 학습의 경우다

1. 응용 목적에 적합한 신경망 구조를 설계한다.
2. 연결 강도를 초기화한다.
3. 학습 패턴 x를 입력하여 신경망의 출력 y를 구한다.
4. 출력 y를 학습 신호 발생기에 입력하여 연결강도 변화랑 Δw를 구한다.
5. 연결강도를 Δw만큼 변경한다.
6. 변화단 연결 강도에 대해서 3 ~ 5 단계를 반복한다.
7. 더 이상 연결 강도가 변하지 않으면 학습을 종료한다.

경쟁식 학습의 경우는 지도 학습이랑 비슷하다구 위에서 썼고....연결 강도가 변경되는 방식을 쪼금 더 살펴보자능.


 위에 그림에서 보면 연결 강도 변화량 Δw는 학습 신호 learning signal γ, 입력 패턴 x, 목표치 d, 학습률 learning rate α 와 관련이 있음을 알 수 있다. 구체적으로 이런 관계다. 여기서 학습률은 신경망 모델에 따라 다른 값으로 연결 가중치의 변화률을 말한다. 0에서 1사이의 값이다. 자율 학습일경우 d만 홀랑 빼버리면 된다. k번째 학습 단계에서 Δw는 다음과 같이 구할 수 있다능.


 k+1번째 연결 강도를 구하는 공식두 나와있으니까 같이 보자능. 아무튼 신경망을 학습 시키는 방법은 신경망 모델에 따라 다른데 학습 신호를 발생 시키는 과정이 다를뿐이다. 어찌됐던 Δw는


요렇게 구하고 변화된 연결 강도는 Δw를 더해버리면 구할 수 있다.


저작자 표시 비영리 동일 조건 변경 허락

'신경망 > 퍼셉트론' 카테고리의 다른 글

단층 신경망  (5) 2009/01/09
신경망의 분류와 학습  (2) 2009/01/07
생물학적 신경망과 인공 신경망  (5) 2009/01/06
뉴로 컴퓨터 개요  (2) 2008/12/29

댓글을 달아 주세요

  1. heroant 2012/01/15 12:26  댓글주소  수정/삭제  댓글쓰기

    보기 좋게 정리 잘해놨네 ㅎㅎ 잘 보고 감.