1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go言語の機械学習パッケージで異常検知する方法

Posted at

はじめに

Go言語の機械学習パッケージを使って異常検知する方法の紹介です。数値データーの配列から異常値(外れ値)を見つける2つのアルゴリズムを実装したパッケージの使い方を紹介します。

Local Outlier Factor(LOF)

日本語では「局所外れ値因子法」というらしいです。

LOFのGo言語実装

メンテナンスされていないようなのとログの出力が多いのでパッケージとして使いやすようにForkしたのが

です。

サンプルプログラム


package main

import (
	"image/color"
	"log"

	"github.com/twsnmp/golof/lof"
	"gonum.org/v1/plot"
	"gonum.org/v1/plot/plotter"
	"gonum.org/v1/plot/vg"
)

func main() {
	// テストデータ
	points := [][]float64{
		{-4.8447532242074978, -5.6869538132901658},
		{1.7265577109364076, -2.5446963280374302},
		{-1.9885982441038819, 1.705719643962865},
		{-1.999050026772494, -4.0367551415711844},
		{-2.0550860126898964, -3.6247409893236426},
		{-1.4456945632547327, -3.7669258809535102},
		{-4.6676062022635554, 1.4925324371089148},
		{-3.6526420667796877, -3.5582661345085662},
		{6.4551493172954029, -0.45434966683144573},
		{-0.56730591589443669, -5.5859532963153349},
		{-5.1400897823762239, -1.3359248994019064},
		{5.2586932439960243, 0.032431285797532586},
		{6.3610915734502838, -0.99059648246991894},
		{-0.31086913190231447, -2.8352818694180644},
		{1.2288582719783967, -1.1362795178325829},
		{-0.17986204466346614, -0.32813130288006365},
		{2.2532002509929216, -0.5142311840491649},
		{-0.75397166138399296, 2.2465141276038754},
		{1.9382517648161239, -1.7276112460593251},
		{1.6809250808549676, -2.3433636210337503},
		{0.68466572523884783, 1.4374914487477481},
		{2.0032364431791514, -2.9191062023123635},
		{-1.7565895138024741, 0.96995712544043267},
		{3.3809644295064505, 6.7497121359292684},
		{-4.2764152718650896, 5.6551328734397766},
		{-3.6347215445083019, -0.85149861984875741},
		{-5.6249411288060385, -3.9251965527768755},
		{4.6033708001912093, 1.3375110154658127},
		{-0.685421751407983, -0.73115552984211407},
		{-2.3744241805625044, 1.3443896265777866},
	}
	log.Println("start")
	// LOFの計算
	samples := lof.GetSamplesFromFloat64s(points)
	lofGetter := lof.NewLOF(5)
	lofGetter.Train(samples)
	mapping := lofGetter.GetLOFs(samples, "fast")
	// グラフの作成
	pts1 := plotter.XYs{}
	pts2 := plotter.XYs{}
	for sample, factor := range mapping {
		point := sample.GetPoint()
		if factor > 1.5 {
			pts1 = append(pts1, plotter.XY{X: point[0], Y: point[1]})
			log.Printf("anomaly: %v,  \tLOF: %f\n", point, factor)
		} else {
			pts2 = append(pts2, plotter.XY{X: point[0], Y: point[1]})
		}
	}
	lofGetter.Reset()
	p := plot.New()
	p.Title.Text = "golof Example"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"
	p.Add(plotter.NewGrid())
	s1, err := plotter.NewScatter(pts1)
	if err != nil {
		log.Fatal(err)
	}
	s2, err := plotter.NewScatter(pts2)
	if err != nil {
		log.Fatal(err)
	}
	p.Add(s1, s2)
	p.Legend.Add("anomaly", s1)
	p.Legend.Add("normal", s2)
	s1.GlyphStyle.Color = color.RGBA{R: 255, B: 128, A: 255}
	s2.GlyphStyle.Color = color.RGBA{R: 0, B: 255, A: 255}
	if err := p.Save(4*vg.Inch, 4*vg.Inch, "points.png"); err != nil {
		log.Fatal(err)
	}
	log.Println("end")
}

実行結果

$go run main.go
2024/11/02 05:01:57 start
2024/11/02 05:01:57 anomaly: [-4.27641527186509 5.6551328734397766],  	LOF: 1.768054
2024/11/02 05:01:57 anomaly: [3.3809644295064505 6.749712135929268],  	LOF: 1.932724
2024/11/02 05:01:57 end

出力されたグラフ

image.png

赤い点が異常値です。外れている感じがわかります。

Isolation Forest

Isolation ForestのGo言語実装

サンプルコード

GitHUBのリポジトリにあるサンプルは不完全なので動作しません。
LOFのサンプルを移植してみました。

package main

import (
	"image/color"
	"log"

	go_iforest "github.com/codegaudi/go-iforest"
	"gonum.org/v1/plot"
	"gonum.org/v1/plot/plotter"
	"gonum.org/v1/plot/vg"
)

func main() {
	// テストデータ
	points := [][]float64{
		{-4.8447532242074978, -5.6869538132901658},
		{1.7265577109364076, -2.5446963280374302},
		{-1.9885982441038819, 1.705719643962865},
		{-1.999050026772494, -4.0367551415711844},
		{-2.0550860126898964, -3.6247409893236426},
		{-1.4456945632547327, -3.7669258809535102},
		{-4.6676062022635554, 1.4925324371089148},
		{-3.6526420667796877, -3.5582661345085662},
		{6.4551493172954029, -0.45434966683144573},
		{-0.56730591589443669, -5.5859532963153349},
		{-5.1400897823762239, -1.3359248994019064},
		{5.2586932439960243, 0.032431285797532586},
		{6.3610915734502838, -0.99059648246991894},
		{-0.31086913190231447, -2.8352818694180644},
		{1.2288582719783967, -1.1362795178325829},
		{-0.17986204466346614, -0.32813130288006365},
		{2.2532002509929216, -0.5142311840491649},
		{-0.75397166138399296, 2.2465141276038754},
		{1.9382517648161239, -1.7276112460593251},
		{1.6809250808549676, -2.3433636210337503},
		{0.68466572523884783, 1.4374914487477481},
		{2.0032364431791514, -2.9191062023123635},
		{-1.7565895138024741, 0.96995712544043267},
		{3.3809644295064505, 6.7497121359292684},
		{-4.2764152718650896, 5.6551328734397766},
		{-3.6347215445083019, -0.85149861984875741},
		{-5.6249411288060385, -3.9251965527768755},
		{4.6033708001912093, 1.3375110154658127},
		{-0.685421751407983, -0.73115552984211407},
		{-2.3744241805625044, 1.3443896265777866},
	}
	log.Println("start")
	/// Isolation Forestの計算
	f, err := go_iforest.NewIForest(points, 10000, 20)
	if err != nil {
		log.Fatal(err)
	}
	// グラフの作成
	ptsA := plotter.XYs{}
	ptsN := plotter.XYs{}
	for i := 0; i < len(points); i++ {
		score := f.CalculateAnomalyScore(points[i])
		if score > 0.6 {
			log.Printf("anomaly: %v, \tScore: %f", points[i], score)
			ptsA = append(ptsA, plotter.XY{X: points[i][0], Y: points[i][1]})
		} else {
			ptsN = append(ptsN, plotter.XY{X: points[i][0], Y: points[i][1]})
		}
	}
	p := plot.New()
	p.Title.Text = "Isolation Forest Example"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"
	p.Add(plotter.NewGrid())
	s1, err := plotter.NewScatter(ptsA)
	if err != nil {
		log.Fatal(err)
	}
	s2, err := plotter.NewScatter(ptsN)
	if err != nil {
		log.Fatal(err)
	}
	p.Add(s1, s2)
	p.Legend.Add("anomaly", s1)
	p.Legend.Add("normal", s2)
	s1.GlyphStyle.Color = color.RGBA{R: 255, B: 128, A: 255}
	s2.GlyphStyle.Color = color.RGBA{R: 0, B: 255, A: 255}
	if err := p.Save(4*vg.Inch, 4*vg.Inch, "points.png"); err != nil {
		log.Fatal(err)
	}
	log.Println("end")
}

実行結果

$go run main.go
2024/11/02 05:01:57 start
2024/11/02 05:01:57 Anomary: [-4.27641527186509 5.6551328734397766],  	LOF: 1.768054
2024/11/02 05:01:57 Anomary: [3.3809644295064505 6.749712135929268],  	LOF: 1.932724
2024/11/02 05:01:57 end

判定するスコアのしきい値を調整すれば、同じ結果になりました。

グラフ出力

image.png

余談

この2つの異常検知のパッケージは、TWSNMPシリーズで使っています。

ポーリングの結果の数値から異常を検知するAI分析という名前にしています。

image.png

TWSNMP FCでは両方のアルゴリズムに対応しています。(最初は、LOFだけだったのですが、Isolation Forestを知ってから追加しました。
切り替えできます。

image.png

同じデータでテストするとLOFはIsolation Forestの40倍もメモリーを使用しました。
Isolation Forestを使ったほうがよいと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?