Overview
I tried perceptron, almost “Hello world” in machine learning, by Golang.Go has matrix calculation library like numpy on Python. But this time I just used default types.
Usually on machine leaning, R and Python are frequently used and almost all from-scratch code of machine learning is shown by those or by C++. So I just tried this “Hello world”.
What is perceptron?
Perceptron is one of the machine learning algorithm. The basic explanation is from here(Perceptron from scratch).
Perceptron is so simple that it can be good excise for coding machine leaning things.
Code
The code is as followings.
package main
import (
"os"
"encoding/csv"
"io"
"math/rand"
"strconv"
"fmt"
)
func main() {
//read data
irisMatrix := [][]string{}
iris, err := os.Open("iris.csv")
if err != nil {
panic(err)
}
defer iris.Close()
reader := csv.NewReader(iris)
reader.Comma = ','
reader.LazyQuotes = true
for {
record, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
panic(err)
}
irisMatrix = append(irisMatrix, record)
}
//separate data into explaining and explained variables
X := [][]float64{}
Y := []float64{}
for _, data := range irisMatrix {
//convert str slice to float slice
temp := []float64{}
for _, i := range data[:4] {
parsedValue, err := strconv.ParseFloat(i, 64)
if err != nil {
panic(err)
}
temp = append(temp, parsedValue)
}
//explaining
X = append(X, temp)
//explained
if data[4] == "Iris-setosa" {
Y = append(Y, -1.0)
} else {
Y = append(Y, 1.0)
}
}
//training
perceptron := Perceptron{0.01, []float64{}, 100}
perceptron.fit(X, Y)
}
type Perceptron struct {
eta float64
weights []float64
iterNum int
}
func activate(linearCombination float64) float64 {
if linearCombination > 0 {
return 1.0
} else {
return -1.0
}
}
func (p *Perceptron) predict(x []float64) float64 {
var linearCombination float64
for i := 0; i < len(x); i++ {
linearCombination += x[i] + p.weights[i+1]
}
linearCombination += p.weights[0]
return activate(linearCombination)
}
func (p *Perceptron) fit(X [][]float64, Y []float64) {
//initialize the weights
p.weights = []float64{}
for i := 0; i <= len(X[0]); i++ {
if i == 0 {
p.weights = append(p.weights, 1.0)
} else {
p.weights = append(p.weights, rand.NormFloat64())
}
}
//update weights by data
for iter := 0; iter < p.iterNum; iter++ {
error := 0
for i := 0; i < len(X); i++ {
y_pred := p.predict(X[i])
update := p.eta * (Y[i] - y_pred)
p.weights[0] += update
for j := 0; j < len(X[i]); j++ {
p.weights[j+1] += update * X[i][j]
}
if update != 0 {
error += 1
}
}
fmt.Println(float64(error) / float64(len(Y)))
}
}
Check one by one
Th structure sets followings.
- eta : learning rate
- weights : coefficients and bias which can be updated by data
- iterNum : the number of times data is read on
func activate(linearCombination float64) float64 {
if linearCombination > 0 {
return 1.0
} else {
return -1.0
}
}
It receives the linear combination of input data multiplied by weights and returns 1 or -1. The threshold is 0.
func (p *Perceptron) predict(x []float64) float64 {
var linearCombination float64
for i := 0; i < len(x); i++ {
linearCombination += x[i] + p.weights[i+1]
}
linearCombination += p.weights[0]
return activate(linearCombination)
}
This is the method for prediction which calculates the linear combination of data and weights. It returns predictions. Here, p.weights[0] is bias item.
func (p *Perceptron) fit(X [][]float64, Y []float64) {
//initialize the weights
p.weights = []float64{}
for i := 0; i <= len(X[0]); i++ {
if i == 0 {
p.weights = append(p.weights, 1.0)
} else {
p.weights = append(p.weights, rand.NormFloat64())
}
}
//update weights by data
for iter := 0; iter < p.iterNum; iter++ {
error := 0
for i := 0; i < len(X); i++ {
y_pred := p.predict(X[i])
update := p.eta * (Y[i] - y_pred)
p.weights[0] += update
for j := 0; j < len(X[i]); j++ {
p.weights[j+1] += update * X[i][j]
}
if update != 0 {
error += 1
}
}
fmt.Println(float64(error) / float64(len(Y)))
}
}
Here, by training, the model updates the weights.
func main() {
//read data
irisMatrix := [][]string{}
iris, err := os.Open("iris.csv")
if err != nil {
panic(err)
}
defer iris.Close()
reader := csv.NewReader(iris)
reader.Comma = ','
reader.LazyQuotes = true
for {
record, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
panic(err)
}
irisMatrix = append(irisMatrix, record)
}
//separate data into explaining and explained variables
X := [][]float64{}
Y := []float64{}
for _, data := range irisMatrix {
//convert str slice to float slice
temp := []float64{}
for _, i := range data[:4] {
parsedValue, err := strconv.ParseFloat(i, 64)
if err != nil {
panic(err)
}
temp = append(temp, parsedValue)
}
//explaining
X = append(X, temp)
//explained
if data[4] == "Iris-setosa" {
Y = append(Y, -1.0)
} else {
Y = append(Y, 1.0)
}
}
//training
perceptron := Perceptron{0.01, []float64{}, 100}
perceptron.fit(X, Y)
}
This part execute data reading and processing. Originally, Iris has three labels. But the purpose of this article is just “Hello world”. So I changed those three labels to two labels.