マイノート

いろいろ勉強したことをまとめていきます

MENU

ホップフィールドネットワーク

未完成

ホップフィールドネットワークとは

箇条書きで書くと、ホップフィールドネットワークとは以下のような特徴をもっている。

  • ホップフィールドネットワークとは、ニューラルネットワークの一種
  • ボルツマンマシンの前身となったものである。
  • 全結合の無向グラフ
  • データをネットワークに記憶し、 ノイズを加えたデータから元データを想起することが可能である。
  • 以下、イメージ図

f:id:ttt242242:20170128203338p:plain

ホップフィールドネットの処理の流れ

ネットワークに記憶

ヘブ則により記憶によりネットワークに情報を記憶する。

  • ホップフィールドネットワークで言うヘブ則とは、リンクの両端のノードが同じ値であれば、結合を強くする、というものである。以下は取れる値が{1,-1}の時である。 $$ \begin{equation} w_{ij} = \sum_{s} x^{s}_i x^{s}_j \end{equation} $$

ノイズありのデータからの想起

ここでは、記憶させたデータにノイズが加えられたデータを ネットワークに入力させ、元データを出力させる手順を説明する。

1. ランダムにノードを選択、更新

以下の式を用いて、各ノードの値を更新する. この式は選択されたノードと結合しているリンクとノードの積の総和である。

$$ \begin{equation} y_i = \sum_{j=1}^{N} w_{ij} x_j - \theta _i \end{equation} $$

  • 上記の$y_i$の値によって$x_i$をの値を更新する

$$ x_{i}= \left\{ \begin{array}{rcl} 1 & y_i > 0 \\ x_i & y_i = 0 \\ -1 & y_i < 0 \\ \end{array} \right. $$

f:id:ttt242242:20170128202850p:plain

2. エネルギー関数が最小化するまで更新

  • エネルギー関数$E$はネットワーク全体の状態を表す関数である。
  • ここの説明はslideshare参照

$$ E= -\frac{1}{2}\sum_{ij}^{N} w_{ij} x_i x_j - \sum_{i} \theta _i x_i \ $$

3. 1,2をエネルギー関数が最小化するまで繰り返す。

更新する度にエネルギー関数の値が減少していく理由

補足

入力値が{0,1}の場合

入力値が{0,1}の場合には、 ヘブ則による更新式が異なる。 具体的には以下の式を用いる

 \displaystyle 
w_{ij} = \sum_{s} (2x^{s}_i - 1)  (2x^{s}_j - 1) \\

上記の式も同様にリンクの両端のノードの値が一致していれば、 そのリンクの重みを強くすることを表してる。

プログラム

rubyで書いてみた。

下に記述してあるdataというarrayをネットワークに記憶させ、

dataにノイズを加えたデータから元のdataを想起させるといった動作を行っている。

class HopFieldNet
  attr_accessor :net,:train_datas,:threshold ,:nodes,:dim,:is_train

  def initialize(threshold=nil, data)
    if threshold == nil
      @threshold = 0.8
    else
      @threshold = threshold
    end
    @train_datas=Array.new 
    load_train_data(data)
    @nodes = Array.new(@train_datas[0].length, 1.0)
    @dim = @train_datas[0].length
    @net = Array.new(@dim**2,0.0) 

  end

  def memorize
    @nodes.length.times do |node_id|
      @nodes.length.times do |node2_id|
        sum = 0.0 
        @train_datas.each do |train_data|
          sum += train_data[node_id] * train_data[node2_id] if(node_id != node2_id)
        end
        @net[node_id * @dim + node2_id] = sum
        @net[node2_id*@dim + node_id] = sum
      end
    end
  end

  #
  # ===
  #
  # @param [Array] datas datas which has noise
  #
  def remember(datas)
    @nodes = datas
    e = energy
    loop do
      @nodes.each_with_index do |node,node_id|
        internal_w = calc_connected_factor(node_id) 
        update_external_w(node_id,internal_w)
      end
      new_e = energy
      break if (e == new_e)
      e = new_e
    end
    puts "energy : #{energy}"
  end 

  def calc_connected_factor(target_node_id)
    sum = 0.0
    @nodes.each_with_index do |node,node_id|
        sum += @net[target_node_id*@dim + node_id] * node if (target_node_id != node_id )
    end
    return sum
  end

  def update_external_w(node_id,i_w)
    if i_w >= @threshold
      @nodes[node_id] = 1.0
    else
      @nodes[node_id] = -1.0
    end
  end

  #
  # calc energy function
  #
  def energy
    sum = 0.0
    @nodes.each_with_index do |node,node_id|
      @nodes.each_with_index do |node2,node2_id|
        sum += @net[node2_id*@dim + node_id] * node * node2 if ( node != node2)
      end
    end
    sum2 = 0.0 
    @nodes.each do |node|
      sum2 += @threshold * node 
    end

    result = (-1.0/2.0)*sum + sum2
    return result
  end


  def load_train_data(data)
    @train_datas.push(data)
  end

end

#
# === add noise to sample datas
# @param data Array data which we want to add noise
# @param noise_rate float rate of noise
#
def add_noise_data(data,noise_rate)
  data_with_noise = Marshal.load(Marshal.dump(data))
  data.size.times do |n|
    if rand <= noise_rate
      if data_with_noise[n] == -1.0
        data_with_noise[n] = 1.0
      else
        data_with_noise[n] = -1.0
      end
    end
  end

  return data_with_noise
end

#
# === evaluate predict data with teatcher data
#
def evaluate(teacher_data,data)
  dominator = 0.0
  molecule = 0.0 
  teacher_data.zip(data).each do |td,d|
    dominator += 1 
    molecule += 1 if td == d
  end

  return (molecule/dominator)*100
end

if($0 == __FILE__) then
  data = [1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0] # test data
  hopFiledNet = HopFieldNet.new(0.0, data)
  hopFiledNet.memorize 
  noise_data = add_noise_data(data, 0.2)
  puts "======[before]======"
  puts "#{evaluate(data, noise_data)}%"
  hopFiledNet.remember(noise_data)
  puts "======[after]======"
  puts "#{ evaluate(data,hopFiledNet.nodes) }%"
end