PyBrain による複雑なデータの学習

In [1]:
import csv
import random
from matplotlib import pyplot as plt
%matplotlib inline

from pybrain.tools.shortcuts import buildNetwork
from pybrain.datasets import SupervisedDataSet
from pybrain.supervised import BackpropTrainer

モジュールのインポート。

In [2]:
reader = csv.reader(open('data.csv', 'r'))

next(reader)
data = [r for r in reader]

n = len(data)

min1 = min([float(d[1]) for d in data])
max1 = max([float(d[1]) for d in data])
min2 = min([float(d[2]) for d in data])
max2 = max([float(d[2]) for d in data])

output_data = []
input_data = []

for i in xrange(n):
    x = (float(data[i][1]) - min1)/(max1 - min1)
    y = (float(data[i][2]) - min2)/(max2 - min2)
    input_data.append((x, y))
    output_data.append(float(data[i][0]))

データの用意。

In [3]:
ds = SupervisedDataSet(2, 1)

m = 40

random.seed(1)

index0 = [i for i in xrange(n) if output_data[i] == 0]
index1 = [i for i in xrange(n) if output_data[i] == 1]

index0 = random.sample(index0, m/2)
index1 = random.sample(index1, m/2)

input_sample = [input_data[i] for i in index0] + [input_data[i] for i in index1]
output_sample = [output_data[i] for i in index0] + [output_data[i] for i in index1]

for i in xrange(m):
    ds.addSample(input_sample[i], output_sample[i])

学習させるデータの設定。学習には時間がかかるので、全データではなく、乱数でデータを選択する。上では結果が 0 になるものと 1 になるものからそれぞれ同じ数だけの点を選択して設定してる。この方法だと、データの範囲を広くカバーできず、予測精度が落ちるので、代わりに以下のようにする。

In [4]:
ds = SupervisedDataSet(2, 1)

m = 40

random.seed(1)

index = [i for i in xrange(n) if input_data[i][0] <= 0.5 and input_data[i][1] <= 0.5]
index = random.sample(index, m/4)

input_sample = [input_data[i] for i in index]
output_sample = [output_data[i] for i in index]

index = [i for i in xrange(n) if input_data[i][0] > 0.5 and input_data[i][1] <= 0.5]
index = random.sample(index, m/4)

input_sample += [input_data[i] for i in index]
output_sample += [output_data[i] for i in index]

index = [i for i in xrange(n) if input_data[i][0] <= 0.5 and input_data[i][1] > 0.5]
index = random.sample(index, m/4)

input_sample += [input_data[i] for i in index]
output_sample += [output_data[i] for i in index]

index = [i for i in xrange(n) if input_data[i][0] > 0.5 and input_data[i][1] > 0.5]
index = random.sample(index, m/4)

input_sample += [input_data[i] for i in index]
output_sample += [output_data[i] for i in index]

for i in xrange(m):
    ds.addSample(input_sample[i], output_sample[i])

データの範囲を 4 つに分け、それぞれから乱数で選択するようにしている。

In [5]:
data0_a = [input_sample[i][0]*(max1 - min1) + min1 for i in xrange(m) if output_sample[i] == 0]
data0_b = [input_sample[i][1]*(max2 - min2) + min2 for i in xrange(m) if output_sample[i] == 0]
data1_a = [input_sample[i][0]*(max1 - min1) + min1 for i in xrange(m) if output_sample[i] == 1]
data1_b = [input_sample[i][1]*(max2 - min2) + min2 for i in xrange(m) if output_sample[i] == 1]

plt.plot(data0_a, data0_b, '+')
plt.plot(data1_a, data1_b, 'r+');

選択されたデータ。赤い点が 1 に対応する点、青い点が 0 に対応する点である。

In [6]:
import numpy as np

def init_weights(n, rang):
    np.random.seed(1)
    return np.random.rand(n)*rang*2. - rang

nn = buildNetwork(2, 3, 1)
nn.params[:] = init_weights(len(nn.params), 1.)

ニューラルネットの作成と重みの初期化。

In [7]:
from tqdm import tqdm

trainer = BackpropTrainer(nn, ds, learningrate=0.01, momentum=0.)

error0 = trainer.train()
errors = [error0]

for i in tqdm(xrange(1, 10000)):
    error = trainer.train()
    relerror = error/error0
    
    errors.append(error)
        
    #if i % 1000 == 0:
    #    print('%5d %e %e' % (i, error, relerror))
    
    if error < 1e-4 or relerror < 1e-8:
        break

print('%5d %e %e' % (i, error, relerror))

 9999 3.749157e-02 1.586741e-01

学習。

In [8]:
plt.yscale('log')
plt.plot(errors);

エラーの減少具合のプロット。

In [9]:
result = [nn.activate(input_data[i]) for i in xrange(n)]

予測データの取得。

In [10]:
count = 0
for i in xrange(n):
    if result[i] >= 0.5:
        v = 1
    else:
        v = 0
    
    if output_data[i] == v:
        count += 1

print 'n = ', n, 'right = ', count, '(%.2f %%)' % (100.*count/n)
n =  200 right =  179 (89.50 %)

成績。そこそこの予測精度である。

In [11]:
data0_a = [input_data[i][0]*(max1 - min1) + min1 for i in xrange(n) if result[i] < 0.5]
data0_b = [input_data[i][1]*(max2 - min2) + min2 for i in xrange(n) if result[i] < 0.5]
data1_a = [input_data[i][0]*(max1 - min1) + min1 for i in xrange(n) if result[i] >= 0.5]
data1_b = [input_data[i][1]*(max2 - min2) + min2 for i in xrange(n) if result[i] >= 0.5]

plt.plot(data0_a, data0_b, '+')
plt.plot(data1_a, data1_b, 'r+');

学習結果。

In [12]:
random.seed(2)

data0_a = []
data0_b = []
data1_a = []
data1_b = []

for i in xrange(n):
    a = random.random()
    b = random.random()

    r = nn.activate((a, b))
    
    if r < 0.5:
        data0_a.append(a*(max1 - min1) + min1)
        data0_b.append(b*(max2 - min2) + min2)
    else:
        data1_a.append(a*(max1 - min1) + min1)
        data1_b.append(b*(max2 - min2) + min2)

plt.plot(data0_a, data0_b, '+')
plt.plot(data1_a, data1_b, 'r+');

ランダムに選んだ点による予測結果。少し外しているところがある。