shonen.hateblo.jp

やったこと,しらべたことを書く.

python3に慣れるための線形分類器実装に関するメモ

目的

python3に慣れるために単細胞パーセプトロンを実装します.

数式の解説等は無し.

環境

  • PyCharm Community Edition 2018.1
  • Python 3.6.5(venv)
  • numpy 1.14.2
  • matplotlib 2.2.2

とりあえず実装

import

tkを導入していない場合(?)はGUIを使わない場合でもエラーが出るので,matplotlib.use("Agg")する.

from collections import namedtuple
import random
import numpy

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as pyplot

シグモイド関数

numpyに用意されていると思っていたら無いらしいので作る.

def sigmoid(x, alpha=1):
    return 1.0 / (1.0 + numpy.exp(-alpha*x))

学習用データ生成

正解の分類関数classify_answerを作る.線形分類器を作りたいので,線形分類可能な想定解にする.

N個のサンプルデータを作る.各サンプルデータはD=2次元で,0.0..1.0の範囲の値を取る.

整数の乱数はrandom.randintなのに,実数の乱数はrandom.randfloatではなくrandom.uniform

inputDataの生成がappend実装で回りくどい.もっと良い書き方がありそう.

Data = namedtuple("Data", "input answer")

N = 100
D = 2
inputData = []

for i in range(N):
    p = [random.uniform(0.0, 1.0), random.uniform(0.0, 1.0)]
    inputData.append(Data(input=p, answer=classify_answer(p)))

初期パラメータ

numpy.array を使って定義する.

weight = numpy.array([0.0, 1.0, 1.0])

学習

学習データを1つピックアップして,1つずつ学習させていく.ちなみに以前の記事はデータセットを丸ごと与えていた.

定数項([1] + training.input[1])を付けるのを忘れてしまいがち.

for loopCount in range(50):

    for training in inputData:
        trainingData = numpy.array([1] + training.input)
        trainingAnswer = training.answer

        predicted = sigmoid(numpy.dot(trainingData, weight))
        a = -2*(trainingAnswer - predicted)*(predicted*(1-predicted))

        weight -= a*trainingData

学習結果の確認

pythonに3項演算子は無い.

後置forよりメソッドチェーンの方が個人的には好きかな…

predicted = [1 if sigmoid(numpy.dot([1] + data.input, weight)) > 0.5 else 0 for data in inputData]
correct = sum(1 if predicted[i] == inputData[i].answer else 0 for i in range(N))

print("correct:", correct)
print("weight:", weight)

画像出力

MATLABOctaveに似た書き方

plotTX = list(map(lambda data: data.input[0], filter(lambda data: sigmoid(numpy.dot([1] + data.input, weight)) >= 0.5, inputData)))
plotTY = list(map(lambda data: data.input[1], filter(lambda data: sigmoid(numpy.dot([1] + data.input, weight)) >= 0.5, inputData)))
plotFX = list(map(lambda data: data.input[0], filter(lambda data: sigmoid(numpy.dot([1] + data.input, weight)) < 0.5, inputData)))
plotFY = list(map(lambda data: data.input[1], filter(lambda data: sigmoid(numpy.dot([1] + data.input, weight)) < 0.5, inputData)))
print(plotTX)
pyplot.plot(plotTX, plotTY, "o")
pyplot.plot(plotFX, plotFY, "o")
pyplot.savefig("result.png")

実行結果の確認

correct: 99
weight: [-11.781737883074062, 11.49195397228784, 11.9669277377479]

この様な画像が出力される

f:id:m_buyoh:20180418184337p:plain

リファクタリング

クラスで書き直した.

from collections import namedtuple
import random
import numpy

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as pyplot


def sigmoid(x, alpha=1):
    return 1.0 / (1.0 + numpy.exp(-alpha*x))


def classify_answer(param):
    return 1 if param[0] + param[1] > 1.0 else 0


class Perceptron:
    def __init__(self, d):
        self.D = D
        self.weight = numpy.array([1.0]*d)

    def predict(self, data):
        return sigmoid(numpy.dot(numpy.array([1]+data), self.weight))

    def train(self, data, answer):
        x = numpy.array([1]+data)
        p = sigmoid(numpy.dot(x, self.weight))
        a = -2 * (answer - p) * (p * (1 - p))
        self.weight -= a * x


Data = namedtuple("Data", "input answer")

N = 100
D = 3 # 定数項も含めた次元の数
inputData = []

for i in range(N):
    p = [random.uniform(0.0, 1.0), random.uniform(0.0, 1.0)]
    inputData.append(Data(input=p, answer=classify_answer(p)))


pe = Perceptron(D)

for loopCount in range(50):
    for training in inputData:
        pe.train(training.input, training.answer)


predicted = [1 if pe.predict(data.input) > 0.5 else 0 for data in inputData]
correct = sum(1 if predicted[i] == inputData[i].answer else 0 for i in range(N))

print("correct:", correct)
print("weight:", pe.weight)


plotTX = list(map(lambda data: data.input[0], filter(lambda data: pe.predict(data.input) >= 0.5, inputData)))
plotTY = list(map(lambda data: data.input[1], filter(lambda data: pe.predict(data.input) >= 0.5, inputData)))
plotFX = list(map(lambda data: data.input[0], filter(lambda data: pe.predict(data.input) < 0.5, inputData)))
plotFY = list(map(lambda data: data.input[1], filter(lambda data: pe.predict(data.input) < 0.5, inputData)))
print(plotTX)
pyplot.plot(plotTX, plotTY, "o")
pyplot.plot(plotFX, plotFY, "o")
pyplot.savefig("result.png")

参考サイト・資料

よく見たら横浜駅の人だった.