Go Machine Learning
前言
最近由于一直在弄布置整天c++写的十分头疼,趁着昨天把切割布置写好后计划换换口味,想着试试Go言语来完成一些机器学习,深度学习会是什么姿态.之前引荐过Go+(goplus),不过这次计划用更根底的go语法来测验.
1.准备作业
对于某个从未涉及的领域一开始必定是一脸茫然,所以需求先找点材料入门.网上相关材料也没有特别多,搜的话底子就只有那几本书.不过这不重要随便找一本书了解入个门,后边的就都能够触类旁通了
这儿我看的是这本机器学习Go言语完成,提取码:o69s
.这本书的出书时间是2018年,也就代表着书中的代码不一定全部能用,究竟一些三方库必定会有更新改动.
对应的仓库地址:github.com/PacktPublis…
1.1 底子常识
这儿的底子常识不止包括Go言语语法,编程常识和一些机器学习深度学习的常识,更首要的是了解第三方的库怎样运用.拿到书以后迅速看一看前几章(两章左右吧,不必太细致看文字究竟还有第二次细读),首要关注里边运用的库,这儿总结一下我运用到的(有的书里的库我替换成了自己在github上找的)
-
gonum: ”gonum.org/v1/gonum“
-
gota: “github.com/go-gota/gota”
-
sklearn “github.com/pa-m/sklearn”
-
plot “gonum.org/v1/plot”
大致总结一下:gonum供给一些矩阵核算,相似于numpy;gota下有dataframe和series,相似于pandas;sklearn便是go完成部分sklearn的办法;plot顾名思义便是画图
对于Go来说,由于静态类型的限制在机器学习的代码编写上必定是不如python快捷的,这一点心里要做好准备.假如习惯了python的灵活性,或许是只会python的那部分人最好不要测验,当然估量这部分人也不会有这种兴趣爱好花时间在其他言语的探究上,有些话最终再说.
扯回这篇Blog,上面的三方库都能够去github上搜到源码和文档阐明,配合着书里的代码很容易了解运用.
1.2 项目创立
这个应该是最简略的,go mod init
随便创立一个新的项目,然后依据自己的主意创立相关的文件结构
2. 正式开始
2.1 数据读取部分
既然是机器学习,那么数据当然是必不可少的.所以书上一开始就叙述了数据的搜集和组织.这儿我不多作赘述,只需学会怎样读取数据就好,给个底子的读取csv文件模板(下面的demo都是依据csv文件)
file, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
irisDF := dataframe.ReadCSV(file)
fmt.Println(irisDF)
当然假如不想凭借dataframe,自己写个读取函数也很简略
func PrintCSV(filename string) {
f, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
r := csv.NewReader(f)
records, err := r.ReadAll()
if err != nil {
log.Fatal(err)
}
for _, record := range records {
for _, i := range record {
print(i)
print(" ")
}
println()
}
}
还有一些其他数据格局,例如json\database等都能够通过各自的办法完成读取,剩余的便是在dataframe里边对数据进行操作
2.2 矩阵核算部分
这部分首要便是运用gonum对数据进行核算,相似的还有numcpp,首要是完成一些矩阵核算.这部分看看书里demo就好,用的时分仍是得自己查官网相关的文档
2.3 底子demo
这部分底子便是一些实战,从数据读取到探究性剖析,再到建模评价.这儿书上的库我没用,而是挑选go言语版的sklearn,尽管功用还不太完善,可是底子的几个模型,预处理办法以及评价目标都还比较全.同样的检查文档后去完成书上的几个demo
2.3.1 回归
书上的比方是Advertising.csv,依据自变量回归得到target Sales.依据上述流程,读取数据就直接运用dataframe.然后进行探究性剖析,首要是检查自变量和目标是否符合线性关系或许说存在相关性,咱们必定不会拿一个一看就毫不相关的变量去拟合回归.
func PlotImg(filename string) {
file, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
DF := dataframe.ReadCSV(file)
target := DF.Col("Sales").Float()
for _, colName := range DF.Names() {
pts := make(plotter.XYs, DF.Nrow())
for i, floatVal := range DF.Col(colName).Float() {
pts[i].X = floatVal
pts[i].Y = target[i]
}
p := plot.New()
p.X.Label.Text = colName
p.Y.Label.Text = "y"
p.Add(plotter.NewGrid())
s, err := plotter.NewScatter(pts)
if err != nil {
log.Fatal(err)
}
s.GlyphStyle.Radius = vg.Points(3)
p.Add(s)
if err := p.Save(4*vg.Inch, 4*vg.Inch, colName+"_scatter.jpg"); err != nil {
log.Fatal(err)
}
}
}
想比matplotlib等python的画图库来说这个是不是复杂许多,除了要自己新建figure,还得一对对的添加(x,y).当然也能够把遍历部分封装成一个函数,只需求输入想要画的列名,不过假如不是需求多次运用也没必要,究竟灵活性在这放着.假如改成制作其他图表,不只plotter的目标得用if或许switch判别,其他的格局也有可能要改.这儿列的挑选能够用DF.Select([]string{"xx","xxx"})
应该能够得到书上的作用,从图上也能够看出哪些变量和Sales具有线性关系.这儿demo我挑选一元线性回归,目标是找到Sales=w*TV+b
线性回归的原理许多书,博客都有介绍,怎样运用梯度下降一步步拟合减小丢失函数,所以这儿直接用sklearn来完成.
sklearn的模型输入是gonum中的mat.Matrix
,可是咱们是dataframe读取的数据,并不是同一种类型,这儿便是静态类型的麻烦之处.不过还好mat.Matrix只是一个interface,所以为了能够转化,咱们完成接口相应的办法就好.
type matrix struct {
dataframe.DataFrame
}
func (m matrix) At(i, j int) float64 {
return m.Elem(i, j).Float()
}
func (m matrix) T() mat.Matrix {
return mat.Transpose{m}
}
咱们只需求完成At()和T(),也便是取值和转置,Dims()办法在dataframe中自身就有完成;其实仔细看的话,办法底子都有只是姓名不一样.当然这只是针对float,假如是int或许string,At()中也要改为.Int()或许.String().
转为Matrix之后,剩余的就和python相似了,创立模型->fit()->predict()->评价,完好代码
package p3
import (
"fmt"
"github.com/go-gota/gota/dataframe"
"github.com/pa-m/sklearn/linear_model"
"github.com/pa-m/sklearn/metrics"
modelselection "github.com/pa-m/sklearn/model_selection"
"gonum.org/v1/gonum/mat"
"gonum.org/v1/plot"
"gonum.org/v1/plot/plotter"
"gonum.org/v1/plot/vg"
"image/color"
"log"
"os"
)
// for convert dataframe to matrix
// create nessesary implementations
type matrix struct {
dataframe.DataFrame
}
func (m matrix) At(i, j int) float64 {
return m.Elem(i, j).Float()
}
func (m matrix) T() mat.Matrix {
return mat.Transpose{m}
}
// sales=w*TV+b
func LinearDemo(Isplot bool) {
//load file
filename := "Advertising.csv"
file, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
DF := dataframe.ReadCSV(file)
X := mat.DenseCopyOf(&matrix{DF.Select([]string{"TV"})})
Y := mat.DenseCopyOf(&matrix{DF.Select([]string{"Sales"})})
XTrain, XTest, YTrain, YTest := modelselection.TrainTestSplit(X, Y, 0.2, 0)
lr := linearmodel.NewLinearRegression()
lr.Fit(XTrain, YTrain)
NPred, _ := XTest.Dims()
Pred := mat.NewDense(NPred, 1, nil)
lr.Predict(XTest, Pred)
fmt.Printf("Coefficients: %.3f\n", mat.Formatted(lr.Coef))
fmt.Printf("Coefficients: %.3f\n", mat.Formatted(lr.Intercept))
fmt.Printf("the pred result is: Sales=%.3f *TV + %.3f\n", mat.Formatted(lr.Intercept), mat.Formatted(lr.Coef))
fmt.Printf("Mean squared error: %.2f\n", metrics.MeanSquaredError(YTest, Pred, nil, "").At(0, 0))
fmt.Printf("Mean absolute error: %.2f\n", metrics.MeanAbsoluteError(YTest, Pred, nil, "").At(0, 0))
fmt.Printf("Variance score: %.2f\n", metrics.R2Score(YTest, Pred, nil, "").At(0, 0))
if Isplot {
//predict all
NPred, _ = X.Dims()
Pred := mat.NewDense(NPred, 1, nil)
lr.Predict(X, Pred)
p := plot.New()
xys := func(X, Y mat.Matrix) plotter.XYs {
var data plotter.XYs
for sample := 0; sample < NPred; sample++ {
data = append(data, struct{ X, Y float64 }{X.At(sample, 0), Y.At(sample, 0)})
}
return data
}
s, _ := plotter.NewScatter(xys(X, Y))
l, _ := plotter.NewLine(xys(X, Pred))
l.Color = color.RGBA{0, 0, 255, 255}
p.Add(s, l)
// Save the plot to a PNG file.
pngfile := "linearregression.png"
os.Remove(pngfile)
if err := p.Save(4*vg.Inch, 3*vg.Inch, pngfile); err != nil {
panic(err)
}
}
}
2.3.2 分类
这儿以iris为例,首要测验一下传统的机器学习模型
2.3.2.1 KNN&&SVM
仍是一样先检查数据,探究性剖析这部分我直接略过,首要是后续对数据的处理.
iris.csv的内容如下
对应的label是string,所以需求先变为数值型.这部分能够运用sklearn中的LabelEncoder 或许OneHotEncoder,也能够运用dataframe的Capply()自界说办法完成,这是我的转化函数
func replaceLabel(col series.Series) series.Series {
rows := col.Len()
NewSeries := series.New([]float64{}, series.Float, "")
changeMap := map[string]float64{
"Iris-setosa": 0.0,
"Iris-versicolor": 1.0,
"Iris-virginica": 2.0,
}
for i := 0; i < rows; i++ {
NewSeries.Append(changeMap[col.Elem(i).String()])
}
return NewSeries
}
检查文档能够知道Capply()需求入参和输出都是Series,所以得依据办法要求写转化函数,再次体会一下类型限制.其余的便是运用sklearn没啥好说的,完好代码如下
package p3
import (
"fmt"
"github.com/go-gota/gota/dataframe"
"github.com/go-gota/gota/series"
"github.com/pa-m/sklearn/metrics"
modelselection "github.com/pa-m/sklearn/model_selection"
"github.com/pa-m/sklearn/neighbors"
"github.com/pa-m/sklearn/svm"
"gonum.org/v1/gonum/mat"
"log"
"os"
)
func replaceLabel(col series.Series) series.Series {
rows := col.Len()
NewSeries := series.New([]float64{}, series.Float, "")
changeMap := map[string]float64{
"Iris-setosa": 0.0,
"Iris-versicolor": 1.0,
"Iris-virginica": 2.0,
}
for i := 0; i < rows; i++ {
NewSeries.Append(changeMap[col.Elem(i).String()])
}
return NewSeries
}
func ClassifyDemo() {
filename := "iris.csv"
file, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
DF := dataframe.ReadCSV(file)
//replace the label from string to float
X := mat.DenseCopyOf(&matrix{DF.Select([]int{0, 1, 3})})
Y := mat.DenseCopyOf(&matrix{DF.Select(4).Capply(replaceLabel)})
XTrain, XTest, YTrain, YTest := modelselection.TrainTestSplit(X, Y, 0.2, 0)
clf := neighbors.NewKNeighborsClassifier(3, "uniform")
clf.Fit(XTrain, YTrain)
NPred, _ := XTest.Dims()
Pred := mat.NewDense(NPred, 1, nil)
clf.Predict(XTest, Pred)
fmt.Printf("The accuracy of KNN: %.02f%%\n", metrics.AccuracyScore(YTest, Pred, true, nil)*100)
clf2 := svm.NewSVC()
//clf2.Degree = 3
clf2.Kernel = "linear"
clf2.Fit(XTrain, YTrain)
clf2.Predict(XTest, Pred)
//fmt.Printf("Pred:\n%g\n", mat.Formatted(Pred))
fmt.Printf("The accuracy of SVM: %.02f%%\n", metrics.AccuracyScore(YTest, Pred, true, nil)*100)
}
KNN原本便是用于多分类问题,所以作用还能够;svm只是二分类,没有用上一对多等战略,所以猜测的大多都是某一类导致准确率很差;
2.3.2.2 MLP
测验了传统的办法,在简略数据集上挑选了适宜的模型,作用已经十分好了,那再用深度学习办法来试试呢
这儿能够挑选用sklearn的MLPClassifier,不过书上也给出了纯go完成,这部分仍是值得好好品一品的.
回顾一下神经网络练习的底子内容
- 结构简略网络,界说输入,躲藏层和输出,中心参加激活函数
- 正向传达,核算网络输出与目标的丢失
- 依据丢失函数反向传达核算梯度,对网络中的权重和偏置项进行更新
对于核算推导,能够看看这篇博客,有图有核算步骤www.cnblogs.com/charlotte77…
下面首要来剖析一下这本书中的写法,首要要构思怎样去写这个功用,依据上面几个库的运用,应该体会到假如想复用或许模块化,那必定得构建一个目标,然后一步步完成里边的类办法.go言语尽管没有class,可是struct也能完成咱们想要的.
type neuralNet struct {
config neuralNetConfig
wHidden *mat.Dense
bHidden *mat.Dense
wOut *mat.Dense
bOut *mat.Dense
}
type neuralNetConfig struct {
inputNum int
outputNum int
hiddenNum int
Epochs int
learningRate float64
}
func NewNetwork(config neuralNetConfig) *neuralNet {
return &neuralNet{config: config}
}
这便是最底子的类以及初始化办法,咱们结构神经网络的时分有必要指定输入输出的大小,躲藏层的层数以及练习时的Epochs和学习率
再来看看两个最底子的类办法,train()
和predict()
对于train()
,依据上面说的流程,咱们先初始化一些权重和bias,然后一次次迭代,正向传达核算输出,再反向传达更新网络参数.这儿需求留意的是现在的mat不再允许创立队伍均为0的Matrix,所以得自己一步步核算原代码中的维度大小进行修正填充
原代码中激活函数用的Sigmoid()
,丢失便是target-output
,好处是对于丢失的梯度不必核算,默许便是error(12(y−y)2)′=y−y(\frac{1}{2}(y-\hat{y})^2)^{‘}=y-\hat{y}.sigmoid的导数也能够预先核算设置好.修正后的相关代码如下
func (nn *neuralNet) train(x, y *mat.Dense) error {
N, _ := x.Dims()
randSource := rand.NewSource(time.Now().UnixNano())
randGen := rand.New(randSource)
wHiddenRaw := make([]float64, nn.config.hiddenNum*nn.config.inputNum)
bHiddenRaw := make([]float64, nn.config.hiddenNum)
wOutRaw := make([]float64, nn.config.outputNum*nn.config.hiddenNum)
bOutRaw := make([]float64, nn.config.outputNum)
for _, param := range [][]float64{wHiddenRaw, bHiddenRaw, wOutRaw, bOutRaw} {
for i := range param {
param[i] = randGen.Float64()
}
}
wHidden := mat.NewDense(nn.config.inputNum, nn.config.hiddenNum, wHiddenRaw)
bHidden := mat.NewDense(1, nn.config.hiddenNum, bHiddenRaw)
wOut := mat.NewDense(nn.config.hiddenNum, nn.config.outputNum, wOutRaw)
bOut := mat.NewDense(1, nn.config.outputNum, bOutRaw)
output := mat.NewDense(N, nn.config.outputNum, nil)
// train model.
for i := 0; i < nn.config.Epochs; i++ {
// forward
hiddenLayerInput := mat.NewDense(N, nn.config.hiddenNum, nil)
hiddenLayerInput.Mul(x, wHidden)
addBHidden := func(_, col int, v float64) float64 { return v + bHidden.At(0, col) }
hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)
hiddenLayerActivations := mat.NewDense(N, nn.config.hiddenNum, nil)
applySigmoid := func(_, _ int, v float64) float64 { return Sigmoid(v) }
hiddenLayerActivations.Apply(applySigmoid, hiddenLayerInput)
outputLayerInput := mat.NewDense(N, nn.config.outputNum, nil)
outputLayerInput.Mul(hiddenLayerActivations, wOut)
addBOut := func(_, col int, v float64) float64 { return v + bOut.At(0, col) }
outputLayerInput.Apply(addBOut, outputLayerInput)
output.Apply(applySigmoid, outputLayerInput)
// backpropagation.
networkError := mat.NewDense(N, nn.config.outputNum, nil)
networkError.Sub(y, output)
slopeOutputLayer := mat.NewDense(N, nn.config.outputNum, nil)
applySigmoidPrime := func(_, _ int, v float64) float64 { return SigmoidPrime(v) }
slopeOutputLayer.Apply(applySigmoidPrime, output)
slopeHiddenLayer := mat.NewDense(N, nn.config.hiddenNum, nil)
slopeHiddenLayer.Apply(applySigmoidPrime, hiddenLayerActivations)
dOutput := mat.NewDense(N, nn.config.outputNum, nil)
dOutput.MulElem(networkError, slopeOutputLayer)
errorAtHiddenLayer := mat.NewDense(N, nn.config.hiddenNum, nil)
errorAtHiddenLayer.Mul(dOutput, wOut.T())
dHiddenLayer := mat.NewDense(N, nn.config.hiddenNum, nil)
dHiddenLayer.MulElem(errorAtHiddenLayer, slopeHiddenLayer)
// Adjust the parameters.
wOutAdj := mat.NewDense(nn.config.hiddenNum, nn.config.outputNum, nil)
wOutAdj.Mul(hiddenLayerActivations.T(), dOutput)
wOutAdj.Scale(nn.config.learningRate, wOutAdj)
wOut.Add(wOut, wOutAdj)
bOutAdj, err := sumAlongAxis(0, dOutput)
if err != nil {
return err
}
bOutAdj.Scale(nn.config.learningRate, bOutAdj)
bOut.Add(bOut, bOutAdj)
wHiddenAdj := mat.NewDense(nn.config.inputNum, nn.config.hiddenNum, nil)
wHiddenAdj.Mul(x.T(), dHiddenLayer)
wHiddenAdj.Scale(nn.config.learningRate, wHiddenAdj)
wHidden.Add(wHidden, wHiddenAdj)
bHiddenAdj, err := sumAlongAxis(0, dHiddenLayer)
if err != nil {
return err
}
bHiddenAdj.Scale(nn.config.learningRate, bHiddenAdj)
bHidden.Add(bHidden, bHiddenAdj)
}
nn.wHidden = wHidden
nn.bHidden = bHidden
nn.wOut = wOut
nn.bOut = bOut
return nil
}
func (nn *neuralNet) predict(x *mat.Dense) (*mat.Dense, error) {
N, _ := x.Dims()
// Check to make sure that our neuralNet value
// represents a trained model.
if nn.wHidden == nil || nn.wOut == nil || nn.bHidden == nil || nn.bOut == nil {
return nil, errors.New("the supplied neurnal net weights and biases are empty")
}
// Define the output of the neural network.
output := mat.NewDense(N, nn.config.outputNum, nil)
// Complete the feed forward process.
hiddenLayerInput := mat.NewDense(N, nn.config.hiddenNum, nil)
hiddenLayerInput.Mul(x, nn.wHidden)
addBHidden := func(_, col int, v float64) float64 { return v + nn.bHidden.At(0, col) }
hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)
hiddenLayerActivations := mat.NewDense(N, nn.config.hiddenNum, nil)
applySigmoid := func(_, _ int, v float64) float64 { return Sigmoid(v) }
hiddenLayerActivations.Apply(applySigmoid, hiddenLayerInput)
outputLayerInput := mat.NewDense(N, nn.config.outputNum, nil)
outputLayerInput.Mul(hiddenLayerActivations, nn.wOut)
addBOut := func(_, col int, v float64) float64 { return v + nn.bOut.At(0, col) }
outputLayerInput.Apply(addBOut, outputLayerInput)
output.Apply(applySigmoid, outputLayerInput)
return output, nil
}
func Sigmoid(x float64) float64 {
return 1.0 / (1.0 + math.Exp(-x))
}
func SigmoidPrime(x float64) float64 {
return x * (1.0 - x)
}
然后这次将iris的标签进行one-hot,完成如下
func replaceLabel2(col dataframe.DataFrame) dataframe.DataFrame {
rows, _ := col.Dims()
var NewDF = make([][]string, 0)
changeMap := map[string][]string{
"Iris-setosa": []string{"1.", "0.", "0."},
"Iris-versicolor": []string{"0.", "1.", "0."},
"Iris-virginica": []string{"0.", "0.", "1."},
}
for i := 0; i < rows; i++ {
NewDF = append(NewDF, changeMap[col.Elem(i, 0).String()])
}
return dataframe.LoadRecords(NewDF, dataframe.HasHeader(false))
}
为什么这次得这样,由于Capply()回来的是Series,而one-hot之后必定是二维的dataframe,因而爽性直接转为dataframe.然后便是一些维度求和以及求每一行最大值下标的函数(为了将predict变为one-hot),最终这部分完好代码如下
package p3
import (
"errors"
"fmt"
"github.com/go-gota/gota/dataframe"
"github.com/pa-m/sklearn/metrics"
modelselection "github.com/pa-m/sklearn/model_selection"
"gonum.org/v1/gonum/floats"
"gonum.org/v1/gonum/mat"
"log"
"math"
"math/rand"
"os"
"time"
)
type neuralNet struct {
config neuralNetConfig
wHidden *mat.Dense
bHidden *mat.Dense
wOut *mat.Dense
bOut *mat.Dense
}
type neuralNetConfig struct {
inputNum int
outputNum int
hiddenNum int
Epochs int
learningRate float64
}
func NewNetwork(config neuralNetConfig) *neuralNet {
return &neuralNet{config: config}
}
func (nn *neuralNet) train(x, y *mat.Dense) error {
N, _ := x.Dims()
randSource := rand.NewSource(time.Now().UnixNano())
randGen := rand.New(randSource)
wHiddenRaw := make([]float64, nn.config.hiddenNum*nn.config.inputNum)
bHiddenRaw := make([]float64, nn.config.hiddenNum)
wOutRaw := make([]float64, nn.config.outputNum*nn.config.hiddenNum)
bOutRaw := make([]float64, nn.config.outputNum)
for _, param := range [][]float64{wHiddenRaw, bHiddenRaw, wOutRaw, bOutRaw} {
for i := range param {
param[i] = randGen.Float64()
}
}
wHidden := mat.NewDense(nn.config.inputNum, nn.config.hiddenNum, wHiddenRaw)
bHidden := mat.NewDense(1, nn.config.hiddenNum, bHiddenRaw)
wOut := mat.NewDense(nn.config.hiddenNum, nn.config.outputNum, wOutRaw)
bOut := mat.NewDense(1, nn.config.outputNum, bOutRaw)
output := mat.NewDense(N, nn.config.outputNum, nil)
// train model.
for i := 0; i < nn.config.Epochs; i++ {
// forward
hiddenLayerInput := mat.NewDense(N, nn.config.hiddenNum, nil)
hiddenLayerInput.Mul(x, wHidden)
addBHidden := func(_, col int, v float64) float64 { return v + bHidden.At(0, col) }
hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)
hiddenLayerActivations := mat.NewDense(N, nn.config.hiddenNum, nil)
applySigmoid := func(_, _ int, v float64) float64 { return Sigmoid(v) }
hiddenLayerActivations.Apply(applySigmoid, hiddenLayerInput)
outputLayerInput := mat.NewDense(N, nn.config.outputNum, nil)
outputLayerInput.Mul(hiddenLayerActivations, wOut)
addBOut := func(_, col int, v float64) float64 { return v + bOut.At(0, col) }
outputLayerInput.Apply(addBOut, outputLayerInput)
output.Apply(applySigmoid, outputLayerInput)
// backpropagation.
networkError := mat.NewDense(N, nn.config.outputNum, nil)
networkError.Sub(y, output)
slopeOutputLayer := mat.NewDense(N, nn.config.outputNum, nil)
applySigmoidPrime := func(_, _ int, v float64) float64 { return SigmoidPrime(v) }
slopeOutputLayer.Apply(applySigmoidPrime, output)
slopeHiddenLayer := mat.NewDense(N, nn.config.hiddenNum, nil)
slopeHiddenLayer.Apply(applySigmoidPrime, hiddenLayerActivations)
dOutput := mat.NewDense(N, nn.config.outputNum, nil)
dOutput.MulElem(networkError, slopeOutputLayer)
errorAtHiddenLayer := mat.NewDense(N, nn.config.hiddenNum, nil)
errorAtHiddenLayer.Mul(dOutput, wOut.T())
dHiddenLayer := mat.NewDense(N, nn.config.hiddenNum, nil)
dHiddenLayer.MulElem(errorAtHiddenLayer, slopeHiddenLayer)
// Adjust the parameters.
wOutAdj := mat.NewDense(nn.config.hiddenNum, nn.config.outputNum, nil)
wOutAdj.Mul(hiddenLayerActivations.T(), dOutput)
wOutAdj.Scale(nn.config.learningRate, wOutAdj)
wOut.Add(wOut, wOutAdj)
bOutAdj, err := sumAlongAxis(0, dOutput)
if err != nil {
return err
}
bOutAdj.Scale(nn.config.learningRate, bOutAdj)
bOut.Add(bOut, bOutAdj)
wHiddenAdj := mat.NewDense(nn.config.inputNum, nn.config.hiddenNum, nil)
wHiddenAdj.Mul(x.T(), dHiddenLayer)
wHiddenAdj.Scale(nn.config.learningRate, wHiddenAdj)
wHidden.Add(wHidden, wHiddenAdj)
bHiddenAdj, err := sumAlongAxis(0, dHiddenLayer)
if err != nil {
return err
}
bHiddenAdj.Scale(nn.config.learningRate, bHiddenAdj)
bHidden.Add(bHidden, bHiddenAdj)
}
nn.wHidden = wHidden
nn.bHidden = bHidden
nn.wOut = wOut
nn.bOut = bOut
return nil
}
func (nn *neuralNet) predict(x *mat.Dense) (*mat.Dense, error) {
N, _ := x.Dims()
// Check to make sure that our neuralNet value
// represents a trained model.
if nn.wHidden == nil || nn.wOut == nil || nn.bHidden == nil || nn.bOut == nil {
return nil, errors.New("the supplied neurnal net weights and biases are empty")
}
// Define the output of the neural network.
output := mat.NewDense(N, nn.config.outputNum, nil)
// Complete the feed forward process.
hiddenLayerInput := mat.NewDense(N, nn.config.hiddenNum, nil)
hiddenLayerInput.Mul(x, nn.wHidden)
addBHidden := func(_, col int, v float64) float64 { return v + nn.bHidden.At(0, col) }
hiddenLayerInput.Apply(addBHidden, hiddenLayerInput)
hiddenLayerActivations := mat.NewDense(N, nn.config.hiddenNum, nil)
applySigmoid := func(_, _ int, v float64) float64 { return Sigmoid(v) }
hiddenLayerActivations.Apply(applySigmoid, hiddenLayerInput)
outputLayerInput := mat.NewDense(N, nn.config.outputNum, nil)
outputLayerInput.Mul(hiddenLayerActivations, nn.wOut)
addBOut := func(_, col int, v float64) float64 { return v + nn.bOut.At(0, col) }
outputLayerInput.Apply(addBOut, outputLayerInput)
output.Apply(applySigmoid, outputLayerInput)
return output, nil
}
func Sigmoid(x float64) float64 {
return 1.0 / (1.0 + math.Exp(-x))
}
func SigmoidPrime(x float64) float64 {
return x * (1.0 - x)
}
func sumAlongAxis(axis int, m *mat.Dense) (*mat.Dense, error) {
numRows, numCols := m.Dims()
var output *mat.Dense
switch axis {
case 0:
data := make([]float64, numCols)
for i := 0; i < numCols; i++ {
col := mat.Col(nil, i, m)
data[i] = floats.Sum(col)
}
output = mat.NewDense(1, numCols, data)
case 1:
data := make([]float64, numRows)
for i := 0; i < numRows; i++ {
row := mat.Row(nil, i, m)
data[i] = floats.Sum(row)
}
output = mat.NewDense(numRows, 1, data)
default:
return nil, errors.New("invalid axis, must be 0 or 1")
}
return output, nil
}
func MaxAlongAxis(m *mat.Dense) (*mat.Dense, error) {
numRows, numCols := m.Dims()
var output *mat.Dense
res := []float64{}
for i := 0; i < numRows; i++ {
row := mat.Row(nil, i, m)
idx := floats.MaxIdx(row)
for j := 0; j < numCols; j++ {
if j == idx {
res = append(res, 1)
} else {
res = append(res, 0)
}
}
}
output = mat.NewDense(numRows, numCols, res)
return output, nil
}
func replaceLabel2(col dataframe.DataFrame) dataframe.DataFrame {
rows, _ := col.Dims()
var NewDF = make([][]string, 0)
changeMap := map[string][]string{
"Iris-setosa": []string{"1.", "0.", "0."},
"Iris-versicolor": []string{"0.", "1.", "0."},
"Iris-virginica": []string{"0.", "0.", "1."},
}
for i := 0; i < rows; i++ {
NewDF = append(NewDF, changeMap[col.Elem(i, 0).String()])
}
return dataframe.LoadRecords(NewDF, dataframe.HasHeader(false))
}
func NNClassify() {
config := neuralNetConfig{
inputNum: 4,
outputNum: 3,
hiddenNum: 10,
Epochs: 1000,
learningRate: 0.009,
}
filename := "iris.csv"
file, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
DF := dataframe.ReadCSV(file, dataframe.HasHeader(true))
//replace the label from string to float
X := mat.DenseCopyOf(&matrix{DF.Select([]int{0, 1, 2, 3})})
Y := mat.DenseCopyOf(&matrix{replaceLabel2(DF.Select(4))})
XTrain, XTest, YTrain, YTest := modelselection.TrainTestSplit(X, Y, 0.33, 0)
//fmt.Printf("X_train:\n%g\n", mat.Formatted(XTrain))
//fmt.Printf("Y_train:\n%g\n", mat.Formatted(YTrain))
network := NewNetwork(config)
start := time.Now()
if err := network.train(XTrain, YTrain); err != nil {
log.Fatal(err)
}
fmt.Println("Train cost time:", time.Since(start))
f := mat.Formatted(network.wHidden, mat.Prefix(" "))
fmt.Printf("\nwHidden = % v\n\n", f)
f = mat.Formatted(network.bHidden, mat.Prefix(" "))
fmt.Printf("\nbHidden = % v\n\n", f)
f = mat.Formatted(network.wOut, mat.Prefix(" "))
fmt.Printf("\nwOut = % v\n\n", f)
f = mat.Formatted(network.bOut, mat.Prefix(" "))
fmt.Printf("\nbOut = % v\n\n", f)
//predict
predictions, err := network.predict(XTest)
fmt.Printf("\npredictions = % v\n\n", mat.Formatted(predictions))
if err != nil {
log.Fatal(err)
}
//fmt.Println(mat.Formatted(predictions))
pred, err := MaxAlongAxis(predictions)
if err != nil {
log.Fatal(err)
}
//fmt.Println(mat.Formatted(pred))
fmt.Printf("The accuracy of MLP: %.02f%%\n", metrics.AccuracyScore(YTest, pred, true, nil)*100)
}
迭代1000次只花了66ms,准确率98%也超过了传统模型.
2.4 More
当然远远不止书上这些,社区里还有许多很好的库能帮助go完成机器学习/深度学习.比方tensorflow(libtorch) binding for Go,可惜已经很久没更新.特别是libtorch的,我去了解的时分一看装置,里边仍是cuda10.x,就没有再去探究了.相反的一些点赞数不多的库github.com/kuroko1t/go…
最终
花了一天抛开聚类和时间序列两章没详细细看,从完全不会这些库的运用到依据文档底子能完成自己想要的功用;一起还把自己放空了一天.不必再去想为什么onnx转化后trt的精度会有丢失,布置的时分各种后处理怎样完成等等,我觉得这种自我调节休息仍是很有必要的.长期只运用python,c++,都快忘了其他言语怎样写.
作为喜爱写写代码,研究技能的人,在闲暇时抛开作业自我学习自身便是一种休闲.python在好几年前刚学习的时分我对它爱不释手,由于学习本钱很低也不必编译,只需配好解释器随处能够运转,不像c++有的时分为了验证一个demo还得写CMakeLists或许命令行链接一堆头文件.跟着不断学习,有的时分运转效率,适用性底子达不到要求.不可能每台电脑都得配python环境再给你pip装一堆库,pyinstaller打包只需引入一些三方库(numpy,pandas等)出来的文件就不会很小,不必三方库那python的优势又在哪呢?
像咱们这种科研狗,假如毕业计划继续从事研究,那用python快速验证模型作用等等无可厚非.假如计划从事互联网开发或许算法工程师,只是会写点python只能说坑了同事也坑了自己.不是clone一个项目略微改几句,或许添加点东西点个run能跑便是会写代码.不考虑项目结构,不考虑代码效率,不考虑格局标准真的很头疼.(有感而发,别怼怼便是你对)
现在许多“火箭”三方库已经完成了,咱们只需求会用就行.小的“轮子”在这根底上自己造一造,触类旁通.