一张图表中也可以同时绘制多条折线,下面我们就在前面图表的基础上多增加一条表达数学函数 y = sigmoid(x) 的折线。sigmoid这个函数在人工智能的深度学习领域具有重要的地位,在神经网络的优化中发挥过非常巨大的作用,它的数学表达是:
其中,e是数学中的自然底数。sigmoid函数的特性是会把任何数字收敛到[0, 1]的范围之内,就是这点导致它在科学计算中有着广泛的应用范畴。下面我们就用代码来将sigmoid函数的曲线加入到图表中。注意通过增加折线中点的个数,可以模拟出接近平滑曲线的效果。
package main
import (
"io/ioutil"
"github.com/golang/freetype/truetype"
"gonum.org/v1/plot"
"gonum.org/v1/plot/plotter"
"gonum.org/v1/plot/plotutil"
"gonum.org/v1/plot/vg"
"math"
t "tools"
)
// sigmoid 计算sigmoid的函数
func sigmoid(x float64) float64 {
return 1 / (1 + math.Pow(math.E, -x))
}
func main() {
//为即将使用的中文字体起名
fontName := "simhei"
//读入改TrueType字体文件
ttfBytes, err :=ioutil.ReadFile("/xiaoxian/web/txhelp/simhei.ttf")
//读取文件发生异常时的处理
if err != nil {
t.Printfln("读取字体文件失败:%v", err.Error())
return
}
//将字体加入字体表(仅本次运行有效)
font, _ := truetype.Parse(ttfBytes)
vg.AddFont(fontName,font)
// 设置gonum/plot绘图时默认字体为自定义的中文字体
plot.DefaultFont = fontName
//设置所绘制的每个数据点的大小
plotter.DefaultGlyphStyle.Radius =vg.Points(1.0)
//新建一张图表
p, _ := plot.New()
//设置图表标题与X、Y轴说明文字
p.Title.Text = "Gonum plot示例"
p.X.Label.Text = "X轴"
p.Y.Label.Text = "Y轴"
//手动建立第一条折线的数据,共包含3个点
points1 := plotter.XYs{
{0.0, 0.0},
{1.0, 1.0},
{2.0, 4.0},
}
//为第二条折线(sigmoid函数对应的曲线)分配内存空间
points2 := make(plotter.XYs, 0, 4)
//让x的值从-10开始至10结束,每次步进0.1,循环计算每个x值对应的sigmoid函数计算结果值y;然后将该值加入到切片变量points2中
for x := -10.0; x <= 10.0; x = x + 0.1 {
y := sigmoid(x)
points2 = append(points2, plotter.XY{X:x, Y: y})
}
//同时在图表中加入两条折线并起名
plotutil.AddLinePoints(p, "函数 y = x * x", points1, "函数 y = sigmoid(x)", points2)
//保存图表到图片文件
p.Save(6*vg.Inch, 6*vg.Inch,"points.png")
}
代码 5‑48 增加sigmoid函数曲线
* 注:本代码参考网址(https://github.com/topxeq/goexamples/blob/master/plot2/plot2.go)
代码5‑48中,已经书写了很多代码注释来说明各处细节问题。我们在这里仅再做一些重点说明。
* 代码中首先定义了计算sigmoid的函数,里面使用了math包中的Pow函数来计算乘方运算;
* 为更充分地演示汉字的显示效果,除图表标题外,X轴、Y轴的说明和折线说明等也都加上了汉字;
* 为演示Linux和MacOS中加载字体文件时路径与Windows中的不同,本次的路径是Linux和MacOS格式的,注意与Windows的不同之处主要在于没有盘符,以及使用斜杠字符“/”而非反斜杠字符“\”来分隔路径中的各个部分;斜杠字符无需进行转义;
* 表示第一条折线的变量points1使用了简化的声明加直接用数值进行赋值的方式,这种方法对于数据不多的情况非常简单快捷。但注意,更规范的写法应该是:
points1 := plotter.XYs {
{X: 0.0, Y:0.0},
{X: 1.0, Y:1.0},
{X: 2.0, Y:4.0},
}
因为plotter.XYs中的每个数据项应该是plotter.XY类型的,而plotter.XY类型定义如下:
type XY struct{ X, Y float64 }
其中两个成员名字是X和Y,严谨的话应该在赋值时加上这两个字段的名字。
* 表示第二条折线的变量points2是使用make函数来声明并分配内存空间的。当初始化切片变量并且make函数有3个参数时,第一个参数还是表示数据类型,第二个则表示新建几个数据项,第三个参数是表示一开始为该切片变量保留多少个数据项的空间(一般称作“容量”,即英语中的capacity),容量并非实际上有数据项,但是在数据项超出容量之前可以一直不重新分配内存空间,这样代码执行的效率更高。因此,一开始保留多少空间并非非常重要,但如果太小而实际数据量又很大的话,会导致系统频繁为points2重新分配空间,运行效率当然会下降。本例中初始化points2变量时,没有为其新建数据项,但保留了4个数据项的空间容量;
* 如前所述,为了让折线看起来更平滑、更像一条曲线,我们通过增加第二条线上的数据点个数来实现。因此为该条曲线准备数据的循环每次只让循环变量x增加0.1(注意此时当然不能用int类型的变量),然后就可以求得采样频度更高的sigmoid值曲线;
* 循环中为切片变量points2新增每一个数据项用的是Go语言的内置函数append,这是Go语言中专门用于给复合结构类型增加数据项的函数。一般用法是:
a := make([]int, 0, 10)
a = append(a, 8)
注意变量a是int类型的切片变量,并且一开始没有数据项但保留了10个数据项的空间(容量为10),append函数调用之后其中将有一个数据项8,其容量仍然为10,如果用len函数来查看变量a的大小(指数据项的个数)则应该是1。
* plotutil.AddLinePoints函数一次可以增加多条折线,每增加一条折线需要多输入两个参数,第一个是折线的名称,第二个才是线上数据点的切片集合;
* 由于数据较多,本次在保存图片时使用了6英寸的图片大小;
代码5‑48运行后产生的图片文件内容如下,
图5.9 增加了sigmoid函数曲线的图表
可以看出,每隔0.1一个数据点导致sigmoid函数的曲线还是比较平滑的。另外,多条曲线时,gonum/plot包会自动为其改变颜色以示区别。所有汉字的显示也正常。
有疑问加站长微信联系(非本文作者)