百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

Gorgonia为Go开发者打开了机器学习的大门

liuian 2025-05-08 19:41 39 浏览

在Python主导的机器学习领域,Go语言凭借其卓越的并发性能和编译型语言的效率优势逐渐崭露头角。Gorgonia作为Go语言生态中领先的机器学习库,提供了类似Theano/TensorFlow的计算图抽象。

官方文档:https://gorgonia.org/

开源地址:
https://github.com/gorgonia/gorgonia

  • 可以进行自动微分
  • 可以进行符号微分
  • 可以执行梯度下降优化
  • 提供多种便捷功能来帮助创建神经网络
  • 相当快(与 Theano 和 TensorFlow 速度相当)
  • 支持 CUDA/GPU 计算(OpenCL 尚不支持)
  • 将支持分布式计算

获取库及其依赖项

$ go get gorgonia.org/gorgonia

下面是一个使用 Gorgonia 构建的三层卷积网络的示例。使用的是 MNIST 数据集。

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"os/signal"
	"runtime/pprof"
	"syscall"

	"net/http"
	_ "net/http/pprof"

	"github.com/pkg/errors"
	"gorgonia.org/gorgonia"
	"gorgonia.org/gorgonia/examples/mnist"
	"gorgonia.org/tensor"

	"time"

	"gopkg.in/cheggaaa/pb.v1"
)

var (
	epochs     = flag.Int("epochs", 100, "Number of epochs to train for")
	dataset    = flag.String("dataset", "train", "Which dataset to train on? Valid options are \"train\" or \"test\"")
	dtype      = flag.String("dtype", "float64", "Which dtype to use")
	batchsize  = flag.Int("batchsize", 100, "Batch size")
	cpuprofile = flag.String("cpuprofile", "", "CPU profiling")
)

const loc = "../testdata/mnist/"

var dt tensor.Dtype

func parseDtype() {
	switch *dtype {
	case "float64":
		dt = tensor.Float64
	case "float32":
		dt = tensor.Float32
	default:
		log.Fatalf("Unknown dtype: %v", *dtype)
	}
}

type sli struct {
	start, end int
}

func (s sli) Start() int { return s.start }
func (s sli) End() int   { return s.end }
func (s sli) Step() int  { return 1 }

type convnet struct {
	g                  *gorgonia.ExprGraph
	w0, w1, w2, w3, w4 *gorgonia.Node // weights. the number at the back indicates which layer it's used for
	d0, d1, d2, d3     float64        // dropout probabilities

	out *gorgonia.Node
}

func newConvNet(g *gorgonia.ExprGraph) *convnet {
	w0 := gorgonia.NewTensor(g, dt, 4, gorgonia.WithShape(32, 1, 3, 3), gorgonia.WithName("w0"), gorgonia.WithInit(gorgonia.GlorotN(1.0)))
	w1 := gorgonia.NewTensor(g, dt, 4, gorgonia.WithShape(64, 32, 3, 3), gorgonia.WithName("w1"), gorgonia.WithInit(gorgonia.GlorotN(1.0)))
	w2 := gorgonia.NewTensor(g, dt, 4, gorgonia.WithShape(128, 64, 3, 3), gorgonia.WithName("w2"), gorgonia.WithInit(gorgonia.GlorotN(1.0)))
	w3 := gorgonia.NewMatrix(g, dt, gorgonia.WithShape(128*3*3, 625), gorgonia.WithName("w3"), gorgonia.WithInit(gorgonia.GlorotN(1.0)))
	w4 := gorgonia.NewMatrix(g, dt, gorgonia.WithShape(625, 10), gorgonia.WithName("w4"), gorgonia.WithInit(gorgonia.GlorotN(1.0)))
	return &convnet{
		g:  g,
		w0: w0,
		w1: w1,
		w2: w2,
		w3: w3,
		w4: w4,

		d0: 0.2,
		d1: 0.2,
		d2: 0.2,
		d3: 0.55,
	}
}

func (m *convnet) learnables() gorgonia.Nodes {
	return gorgonia.Nodes{m.w0, m.w1, m.w2, m.w3, m.w4}
}

// This function is particularly verbose for educational reasons. In reality, you'd wrap up the layers within a layer struct type and perform per-layer activations
func (m *convnet) fwd(x *gorgonia.Node) (err error) {
	var c0, c1, c2, fc *gorgonia.Node
	var a0, a1, a2, a3 *gorgonia.Node
	var p0, p1, p2 *gorgonia.Node
	var l0, l1, l2, l3 *gorgonia.Node

	// LAYER 0
	// here we convolve with stride = (1, 1) and padding = (1, 1),
	// which is your bog standard convolution for convnet
	if c0, err = gorgonia.Conv2d(x, m.w0, tensor.Shape{3, 3}, []int{1, 1}, []int{1, 1}, []int{1, 1}); err != nil {
		return errors.Wrap(err, "Layer 0 Convolution failed")
	}
	if a0, err = gorgonia.Rectify(c0); err != nil {
		return errors.Wrap(err, "Layer 0 activation failed")
	}
	if p0, err = gorgonia.MaxPool2D(a0, tensor.Shape{2, 2}, []int{0, 0}, []int{2, 2}); err != nil {
		return errors.Wrap(err, "Layer 0 Maxpooling failed")
	}
	log.Printf("p0 shape %v", p0.Shape())
	if l0, err = gorgonia.Dropout(p0, m.d0); err != nil {
		return errors.Wrap(err, "Unable to apply a dropout")
	}

	// Layer 1
	if c1, err = gorgonia.Conv2d(l0, m.w1, tensor.Shape{3, 3}, []int{1, 1}, []int{1, 1}, []int{1, 1}); err != nil {
		return errors.Wrap(err, "Layer 1 Convolution failed")
	}
	if a1, err = gorgonia.Rectify(c1); err != nil {
		return errors.Wrap(err, "Layer 1 activation failed")
	}
	if p1, err = gorgonia.MaxPool2D(a1, tensor.Shape{2, 2}, []int{0, 0}, []int{2, 2}); err != nil {
		return errors.Wrap(err, "Layer 1 Maxpooling failed")
	}
	if l1, err = gorgonia.Dropout(p1, m.d1); err != nil {
		return errors.Wrap(err, "Unable to apply a dropout to layer 1")
	}

	// Layer 2
	if c2, err = gorgonia.Conv2d(l1, m.w2, tensor.Shape{3, 3}, []int{1, 1}, []int{1, 1}, []int{1, 1}); err != nil {
		return errors.Wrap(err, "Layer 2 Convolution failed")
	}
	if a2, err = gorgonia.Rectify(c2); err != nil {
		return errors.Wrap(err, "Layer 2 activation failed")
	}
	if p2, err = gorgonia.MaxPool2D(a2, tensor.Shape{2, 2}, []int{0, 0}, []int{2, 2}); err != nil {
		return errors.Wrap(err, "Layer 2 Maxpooling failed")
	}
	log.Printf("p2 shape %v", p2.Shape())

	var r2 *gorgonia.Node
	b, c, h, w := p2.Shape()[0], p2.Shape()[1], p2.Shape()[2], p2.Shape()[3]
	if r2, err = gorgonia.Reshape(p2, tensor.Shape{b, c * h * w}); err != nil {
		return errors.Wrap(err, "Unable to reshape layer 2")
	}
	log.Printf("r2 shape %v", r2.Shape())
	if l2, err = gorgonia.Dropout(r2, m.d2); err != nil {
		return errors.Wrap(err, "Unable to apply a dropout on layer 2")
	}

	ioutil.WriteFile("tmp.dot", []byte(m.g.ToDot()), 0644)

	// Layer 3
	if fc, err = gorgonia.Mul(l2, m.w3); err != nil {
		return errors.Wrapf(err, "Unable to multiply l2 and w3")
	}
	if a3, err = gorgonia.Rectify(fc); err != nil {
		return errors.Wrapf(err, "Unable to activate fc")
	}
	if l3, err = gorgonia.Dropout(a3, m.d3); err != nil {
		return errors.Wrapf(err, "Unable to apply a dropout on layer 3")
	}

	// output decode
	var out *gorgonia.Node
	if out, err = gorgonia.Mul(l3, m.w4); err != nil {
		return errors.Wrapf(err, "Unable to multiply l3 and w4")
	}
	m.out, err = gorgonia.SoftMax(out)
	return
}

func main() {
	flag.Parse()
	parseDtype()
	rand.Seed(1337)

	// intercept Ctrl+C
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
	doneChan := make(chan bool, 1)

	var inputs, targets tensor.Tensor
	var err error

	go func() {
		log.Println(http.ListenAndServe("localhost:6060", nil))
	}()

	trainOn := *dataset
	if inputs, targets, err = mnist.Load(trainOn, loc, dt); err != nil {
		log.Fatal(err)
	}

	// the data is in (numExamples, 784).
	// In order to use a convnet, we need to massage the data
	// into this format (batchsize, numberOfChannels, height, width).
	//
	// This translates into (numExamples, 1, 28, 28).
	//
	// This is because the convolution operators actually understand height and width.
	//
	// The 1 indicates that there is only one channel (MNIST data is black and white).
	numExamples := inputs.Shape()[0]
	bs := *batchsize
	// todo - check bs not 0

	if err := inputs.Reshape(numExamples, 1, 28, 28); err != nil {
		log.Fatal(err)
	}
	g := gorgonia.NewGraph()
	x := gorgonia.NewTensor(g, dt, 4, gorgonia.WithShape(bs, 1, 28, 28), gorgonia.WithName("x"))
	y := gorgonia.NewMatrix(g, dt, gorgonia.WithShape(bs, 10), gorgonia.WithName("y"))
	m := newConvNet(g)
	if err = m.fwd(x); err != nil {
		log.Fatalf("%+v", err)
	}
	losses := gorgonia.Must(gorgonia.HadamardProd(m.out, y))
	cost := gorgonia.Must(gorgonia.Mean(losses))
	cost = gorgonia.Must(gorgonia.Neg(cost))

	// we wanna track costs
	var costVal gorgonia.Value
	gorgonia.Read(cost, &costVal)

	// if _, err = gorgonia.Grad(cost, m.learnables()...); err != nil {
	// 	log.Fatal(err)
	// }

	// debug
	// ioutil.WriteFile("fullGraph.dot", []byte(g.ToDot()), 0644)
	// log.Printf("%v", prog)
	// logger := log.New(os.Stderr, "", 0)
	// vm := gorgonia.NewTapeMachine(g, gorgonia.BindDualValues(m.learnables()...), gorgonia.WithLogger(logger), gorgonia.WithWatchlist())

	prog, locMap, _ := gorgonia.Compile(g)
	log.Printf("%v", prog)

	vm := gorgonia.NewTapeMachine(g, gorgonia.WithPrecompiled(prog, locMap), gorgonia.BindDualValues(m.learnables()...))
	solver := gorgonia.NewRMSPropSolver(gorgonia.WithBatchSize(float64(bs)))
	defer vm.Close()
	// pprof
	// handlePprof(sigChan, doneChan)

	var profiling bool
	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		profiling = true
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}
	go cleanup(sigChan, doneChan, profiling)

	batches := numExamples / bs
	log.Printf("Batches %d", batches)
	bar := pb.New(batches)
	bar.SetRefreshRate(time.Second)
	bar.SetMaxWidth(80)

	for i := 0; i < *epochs; i++ {
		bar.Prefix(fmt.Sprintf("Epoch %d", i))
		bar.Set(0)
		bar.Start()
		for b := 0; b < batches; b++ {
			start := b * bs
			end := start + bs
			if start >= numExamples {
				break
			}
			if end > numExamples {
				end = numExamples
			}

			var xVal, yVal tensor.Tensor
			if xVal, err = inputs.Slice(sli{start, end}); err != nil {
				log.Fatal("Unable to slice x")
			}

			if yVal, err = targets.Slice(sli{start, end}); err != nil {
				log.Fatal("Unable to slice y")
			}
			if err = xVal.(*tensor.Dense).Reshape(bs, 1, 28, 28); err != nil {
				log.Fatalf("Unable to reshape %v", err)
			}

			gorgonia.Let(x, xVal)
			gorgonia.Let(y, yVal)
			if err = vm.RunAll(); err != nil {
				log.Fatalf("Failed at epoch  %d: %v", i, err)
			}
			solver.Step(gorgonia.NodesToValueGrads(m.learnables()))
			vm.Reset()
			bar.Increment()
		}
		log.Printf("Epoch %d | cost %v", i, costVal)

	}
}

func cleanup(sigChan chan os.Signal, doneChan chan bool, profiling bool) {
	select {
	case <-sigChan:
		log.Println("EMERGENCY EXIT!")
		if profiling {
			log.Println("Stop profiling")
			pprof.StopCPUProfile()
		}
		os.Exit(1)

	case <-doneChan:
		return
	}
}

func handlePprof(sigChan chan os.Signal, doneChan chan bool) {
	var profiling bool
	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		profiling = true
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}
	go cleanup(sigChan, doneChan, profiling)
}

使用 Gorgonia 的主要原因是开发人员的舒适度。如果您广泛使用 Go 技术栈,现在您可以在熟悉且舒适的环境中创建可用于生产的机器学习系统。

Gorgonia 目前性能相当出色——其速度与 PyTorch 和 Tensorflow 的 CPU 实现相当。由于 CGO 负担过重,GPU 实现的比较略显困难,但请放心,这是一个正在积极改进的领域。

相关推荐

Optional是个好东西,如果用错了就太可惜了

原文出处:https://xie.infoq.cn/article/e3d1f0f4f095397c44812a5be我们都知道,在Java8新增了一个类-Optional,主要是用来解决程...

IDEA建议:不要在字段上使用@Autowire了!

在使用IDEA写Spring相关的项目的时候,在字段上使用@Autowired注解时,总是会有一个波浪线提示:Fieldinjectionisnotrecommended.纳尼?我天天用,咋...

Spring源码|Spring实例Bean的方法

Spring实例Bean的方法,在AbstractAutowireCapableBeanFactory中的protectedBeanWrappercreateBeanInstance(String...

Spring技巧:深入研究Java 14和SpringBoot

在本期文章中,我们将介绍Java14中的新特性及其在构建基于SpringBoot的应用程序中的应用。开始,我们需要使用Java的最新版本,也是最棒的版本,Java14,它现在还没有发布。预计将于2...

Java开发200+个学习知识路线-史上最全(框架篇)

1.Spring框架深入SpringIOC容器:BeanFactory与ApplicationContextBean生命周期:实例化、属性填充、初始化、销毁依赖注入方式:构造器注入、Setter注...

年末将至,Java 开发者必须了解的 15 个Java 顶级开源项目

专注于Java领域优质技术,欢迎关注作者:SnailClimbStar的数量统计于2019-12-29。1.JavaGuideGuide哥大三开始维护的,目前算是纯Java类型项目中Sta...

字节跨平台框架 Lynx 开源:一个 Web 开发者的原生体验

最近各大厂都在开源自己的跨平台框架,前脚腾讯刚宣布计划四月开源基于Kotlin的跨平台框架「Kuikly」,后脚字节跳动旧开源了他们的跨平台框架「Lynx」,如果说Kuikly是一个面向...

我要狠狠的反驳“公司禁止使用Lombok”的观点

经常在其它各个地方在说公司禁止使用Lombok,我一直不明白为什么不让用,今天看到一篇文章列举了一下“缺点”,这里我只想狠狠地反驳,看到列举的理由我竟无言以对。原文如下:下面,结合我自己使用Lomb...

SpringBoot Lombok使用详解:从入门到精通(注解最全)

一、Lombok概述与基础使用1.1Lombok是什么Lombok是一个Java库,它通过注解的方式自动生成Java代码(如getter、setter、toString等),从而减少样板代码的编写,...

Java 8之后的那些新特性(六):记录类 Record Class

Java是一门面向对象的语言,而对于面向对象的语言中,一个众所周知的概念就是,对象是包含属性与行为的。比如HR系统中都会有雇员的概念,那雇员会有姓名,ID身份,性别等,这些我们称之为属性;而雇员同时肯...

为什么大厂要求安卓开发者掌握Kotlin和Jetpack?优雅草卓伊凡

为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡一、Kotlin:Android开发的现代语言选择1.1Kotlin是什么?Kotlin是由...

Kotlin这5招太绝了!码农秒变优雅艺术家!

Kotlin因其简洁性、空安全性和与Java的无缝互操作性而备受喜爱。虽然许多开发者熟悉协程、扩展函数和数据类等特性,但还有一些鲜为人知的特性可以让你的代码从仅仅能用变得真正优雅且异常简洁。让我们来看...

自行部署一款免费高颜值的IT资产管理系统-咖啡壶chemex

在运维时,ICT资产太多怎么办,还是用excel表格来管理?效率太低,也不好多人使用。在几个IT资产管理系统中选择比较中,最终在Snipe-IT和chemex间选择了chemex咖啡壶。Snip...

PHP对接百度语音识别技术(php对接百度语音识别技术实验报告)

引言在目前的各种应用场景中,语音识别技术已经越来越常用,并且其应用场景正在不断扩大。百度提供的语音识别服务允许用户通过简单的接口调用,将语音内容转换为文本。本文将通过PHP语言集成百度的语音识别服务,...

知识付费系统功能全解析(知识付费项目怎么样)

开发知识付费系统需包含核心功能模块,确保内容变现、用户体验及运营管理需求。以下是完整功能架构:一、用户端功能注册登录:手机号/邮箱注册,第三方登录(微信、QQ)内容浏览:分类展示课程、文章、音频等付费...