単純パーセプトロンの学習python vs julia

juliaで単純パーセプトロン

特にやる理由もなかったんですが、 前回単純パーセプトロンpythonで構築したんで、その比較ついでにjuliaでも実装。

まずはpythonでデータを作っちゃいます。 前回と同じ3次元ですがデータ数を10000に増やします。

の前に、結構長くなっちゃったので結論をこちらに先に書いておく。

実行環境 エポック数 時間
python with numpy(float 64) 217 53.9s
pure python (float32) 163 7.17s
julia(素) 113 3.16s
julia(関数) 113 0.85s

同じ数値、同じ演算をしているのに、 epochのばらつきがあるのは一体…? (型をfloat32に揃えても一緒でした) それはさておき、juliaがepochを考慮しても30倍の速さ(numpy比)を出してくれました。 pure pythonが意外と健闘…。

以下本編。

データ作成

# w0 * x0 + w1 * x1 + w2 * x2 + b = 0
# x2 = -(w0/w2)*x0 - (w1/w2)*x1 - b/w2 = 0
N = 10000
w0 = 5
w1 = -3
w2 = 2
b = 1

# ラベル1
x0_rand_posi = stable_rand.rand(int(N/2))
x1_rand_posi = stable_rand.rand(int(N/2))
x2_rand_posi = -(w0/w2)*x0_rand_posi - (w1/w2)*x1_rand_posi -b/w2 + stable_rand.rand(int(N/2))

# ラベル0
x0_rand_nega = stable_rand.rand(int(N/2))
x1_rand_nega = stable_rand.rand(int(N/2))
x2_rand_nega = -(w0/w2)*x0_rand_nega - (w1/w2)*x1_rand_nega -b/w2 - stable_rand.rand(int(N/2))

train_x = np.array([np.append(x0_rand_posi,x0_rand_nega,axis=0),np.append(x1_rand_posi,x1_rand_nega,axis=0),np.append(x2_rand_posi,x2_rand_nega,axis=0)]).T
train_y = np.append(np.ones(int(N/2)),np.zeros(int(N/2)))

これでtrain_xとtrain_yにデータが格納されました。 juliaでも使えるようにcsvに吐き出しておきます。

np.savetxt('train_x.csv',train_x,delimiter=',')
np.savetxt('train_y.csv',train_y,delimiter=',')

さて、pythonの単純パーセプトロン

# 重み初期化
w = np.ones(3)
# バイアス初期化
b = np.ones(1)
# ステップ関数
def step(x):
    return 1 * (x > 0)
# 予測関数
def predict(w,x,b):
    return step(np.dot(w, x) + b)    

# 学習
# ipythonの%%timeで計測

%%time
eps = .01
epoch_counter = 0
while True:
    epoch_counter += 1
    fin_flag = True
    for i in range(len(train_y)):
        if i % 2 == 0:
            idx = int(i/2)
        else:
            idx = int((i-1)/2 + N/2)
        delta_w = eps * (train_y[idx] - predict(w,train_x[idx],b)) * train_x[idx]
        delta_b = eps * (train_y[idx] - predict(w,train_x[idx],b))    
        w += delta_w
        b += delta_b
        # 一回でも誤り判定があればTrueになる
        fin_flag *= all(delta_w == 0) * (delta_b == 0)
    if fin_flag:
        break
print(epoch_counter)
print(w)
print(b)

さて、実行結果。 あと、w,bも出力

217
[ 1.74633386 -1.04860002  0.69887095]
[0.35]
Wall time: 53.9 s

numpyを使ってやってしまいましたが、 一応pure pythonでも単純パーセプトロンをコーディング データはさっきのを流用。

# numpy arrayは使用禁止のためリストへ
train_x = train_x.tolist()
train_y = train_y.tolist()

# 重みとバイアスを初期化
w = [1,1,1]
b = 1

# ステップ関数
def step(x):
    return 1 * (x > 0)
# 内積関数(np.dotを使えないため)
def dot(a,b):
    ab = 0
    for ai,bi in zip(a,b):
        ab += ai*bi
    return ab
# 予測
def predict(w,x,b):
    return step(dot(w, x) + b)

# 学習
%%time
eps = .01
epoch_counter = 0
while True:
    epoch_counter += 1
    fin_flag = True
    for i in range(len(train_y)):
        if i % 2 == 0:
            idx = int(i/2)
        else:
            idx = int((i-1)/2) + 500
        delta_b = eps * (train_y[idx] - predict(w,train_x[idx],b))
        delta_b_ite = [delta_b for i in train_x[idx]]
        delta_w = list(map(lambda b,x : b * x , delta_b_ite, train_x[idx]))
        
        w = list(map(lambda w1,w2: w1+w2, w,delta_w))
        b += delta_b
        fin_flag *= all(wi == 0 for wi in delta_w) * (delta_b == 0)
    if fin_flag:
        break
print(epoch_counter)
print(w)
print(b)

はてさて結果は

163
[1.0707060146288323, -0.6444864547742656, 0.43073711329007247]
0.2199999999999993
Wall time: 7.17 s

なんやこれ…? numpy使うより収束も速ければ、 処理速度も速い!?

よくわからない…。 でも、重みもバイアスも約0.614倍になってるだけなので正しそう….。

npはデフォルトでfloat64になるので、 numpy側を明示的にfloat32にして実行してみる。

データをこんな感じで修正

train_x = np.array([np.append(x0_rand_posi,x0_rand_nega,axis=0),np.append(x1_rand_posi,x1_rand_nega,axis=0),np.append(x2_rand_posi,x2_rand_nega,axis=0)],dtype=np.float32).T
train_y = np.append(np.ones(int(N/2),dtype=np.float32),np.zeros(int(N/2),dtype=np.float32))

w = np.ones(3,dtype=np.float32)
b = np.ones(1,dtype=np.float32)

再度numpyで学習!

%%time
eps = .01
epoch_counter = 0
while True:
    epoch_counter += 1
    fin_flag = True
    for i in range(len(train_y)):
        if i % 2 == 0:
            idx = int(i/2)
        else:
            idx = int((i-1)/2 + N/2)
        delta_w = np.array(eps * (train_y[idx] - predict(w,train_x[idx],b)) * train_x[idx],dtype=np.float32)
        delta_b = np.array(eps * (train_y[idx] - predict(w,train_x[idx],b)),dtype=np.float32)
        w += delta_w
        b += delta_b
        # 一回でも誤り判定があればTrueになる
        fin_flag *= all(delta_w == 0) * (delta_b == 0)
    if fin_flag:
        break
print(epoch_counter)
print(w)
print(b)
116
[ 1.4922788  -0.89688647  0.5975643 ]
[0.30000067]
Wall time: 30.4 s

ううーん…? epoch回数は減ったけど…。それでもpure pythonのほうが速いし…。 わからん。

まぁ一旦おいておく。(誰か教えてエロい人)

さて、juliaで同じように。

using CSV
import Base: * , >
train_x = convert(Array,CSV.read("train_x.csv", header=false, delim=','))
train_y = convert(Array,CSV.read("train_y.csv", header=false, delim=','))

# 学習率
l_rate = 0.01

# 重み初期化
w = ones(3)
# バイアス初期化
b = 1

# ステップ関数
function fn_step(x)
  return 1 * (x > 0)
end

# 予測
function fn_predict(w,x,b)
  return fn_step(w' * x +b)
end

さて、こっから学習

epoch_counter = 0
@time while true
  global epoch_counter += 1
  local fin_flag = true
  for i in 1:size(train_y)[1]
    if i % 2 == 0
      idx = Int((i)/2) + 500
    else
      idx = Int(i+2/2)
    end
    delta_w = l_rate * (train_y[idx] - fn_predict(w,train_x[idx,:],b)) * train_x[idx,:]
    delta_b = l_rate * (train_y[idx] - fn_predict(w,train_x[idx,:],b))
    global w += delta_w
    global b += delta_b
    fin_flag *= (delta_w == zeros(size(delta_w))) * (delta_b == 0)
  end
  if fin_flag
    println(epoch_counter)
    println(w)
    println(b)
    break
  end
end

はてさて結果は

113
[1.24965, -0.749514, 0.49939]
0.24999999999999933
  3.161580 seconds (37.60 M allocations: 1.272 GiB, 4.11% gc time)

速い。…が10倍程度。 まだjuliaの本気ではないですね。 やはり関数化が必要な模様。 学習用関数を作ります。

function fit(x,y,w,b,epoch_counter=0,learning_rate = 0.01)
  while true
    epoch_counter += 1
    local fin_flag = true
    for i in 1:size(y)[1]
      if i % 2 == 0
        idx = Int((i)/2) + 500
      else
        idx = Int(i+2/2)
      end
      delta_w = learning_rate * (y[idx] - fn_predict(w,x[idx,:],b)) * x[idx,:]
      delta_b = learning_rate * (y[idx] - fn_predict(w,x[idx,:],b))
      w += delta_w
      b += delta_b
      fin_flag *= (delta_w == zeros(size(delta_w))) * (delta_b == 0)
    end
    if fin_flag
      println(epoch_counter)
      return w,b
      break
    end
  end
end

さて実行

w = ones(3)
b = 1
@time fit(train_x,train_y,w,b)

…?

113
  0.850472 seconds (24.86 M allocations: 1.061 GiB, 11.68% gc time)
([1.24965, -0.749514, 0.49939], 0.24999999999999933)

例によって一瞬ですね…。

早すぎィ!

Juliaデータサイエンス―Juliaを使って自分でゼロから作るデータサイエンス世界の探索

Juliaデータサイエンス―Juliaを使って自分でゼロから作るデータサイエンス世界の探索