008 Go 版のビルド・テスト・静的解析・機械学習・API ライブラリの選定¶
「機械学習から始めるプログラミング入門」Go 版で使うライブラリを決める。
日付: 2026-09-20
ステータス¶
2026-09-20 提案されました
2026-09-20 承認されました(第 1〜15 章の執筆で確かめました)
コンテキスト¶
執筆計画 の「対象言語」では、Go 版の機械学習ライブラリの候補を gonum としていた。Go は「ライブラリが限定的なので自作の比重が大きい」言語として選んでおり、どこまでライブラリに任せられるかを確かめる必要があった。
B39 のステップ 1 で、Go module proxy・モジュールのキャッシュ・使い捨てのプロジェクトによって、次を確かめた。
| 確認したこと | 結果 | 確認方法 |
|---|---|---|
| gonum の版とライセンス | 最新は v0.17.0(2025-12-29)、BSD 3 条項 | Go module proxy、LICENSE |
| gonum にあるもの | stat に LinearRegression(単回帰)・CovarianceMatrix・PC(主成分分析)・ROC、mat に行列 |
stat のファイル一覧と関数 |
| gonum に無いもの | 決定木・ランダムフォレスト・K-means・ロジスティック回帰 | 同上 |
stat.LinearRegression |
単回帰だけで、切片と傾きを返す。重回帰は mat で正規方程式を解く |
使い捨てのプロジェクトで実行 |
stat.PC |
PrincipalComponents が成功可否を返し、VarsTo で分散、VectorsTo で固有ベクトルの行列を取れる。寄与率は分散から自分で求める |
同上 |
| GoLearn | 最新が 2022-12-28 のコミット(タグ無し)で、3 年以上更新されていない | Go module proxy |
| 静的解析 | わざと崩したファイルで、gofmt -l がファイル名を、go vet が「declared and not used」を、golangci-lint run が同じ指摘を typecheck として報告した |
使い捨てのプロジェクト |
| Nix 環境 | Go 1.25.5、golangci-lint 2.7.2(nix eval が示す nixpkgs の最新は 2.8.0 だが、flake.lock で固定された環境に入るのは 2.7.2)、gopls 0.21.0。手元は Go 1.26.5 |
nix eval、go version |
| testify | 最新 v1.12.1(2026-08-17)、MIT | Go module proxy |
各章で確かめた結果¶
| 章 | 確かめたこと |
|---|---|
| 1 | わざと崩したファイルで、gofmt -l がファイル名を、go vet が「declared and not used」を、golangci-lint run が同じ指摘を typecheck として報告した。go test ./... は Nix の Go 1.25.5 でも手元の 1.26.5 でも動く(go.mod の go 指令は 1.25) |
| 2 | Go の math/rand は、Java・Scala の java.util.Random とも .NET の Random とも乱数列が違う。同じ Fisher-Yates・同じシード 0 でも並びが違い(Go は [6 8 2 3 7 5 9 1 0 4]、Java は [4 8 9 6 3 5 2 1 7 0])、分かれる行も訓練データの平均値もほかの言語版と一致しない。件数(105 件と 45 件)は一致する。Features はスライスを持つので == で比べられず、テストでは reflect.DeepEqual を使う。rand.New(rand.NewSource(seed)) は gosec の指摘(G404)を受けるので、再現のための擬似乱数である理由をコメントに書いて //nolint:gosec で抑える |
| 3 | 判別共用体が無いので、非公開のメソッドを持つインターフェース(type Tree interface { isTree() })と Leaf・Node の構造体で決定木を表し、予測は型スイッチにした。網羅性はコンパイラが検査しないので、既定の分岐でエラーを返す。無名の構造体では float64(count) の変換がコンパイルを通らず、labelCount 型を起こした。gonum に決定木は無いので、ライブラリとの突き合わせの節は省略した。深さ 2 の決定木のテストデータの正解率は 45 件中 43 件で Java 版と同じだが、深さ 1・3・4・5・制限なしの正解率と分割の境界(花弁幅 0.6900、Java 版は 0.6500)は分け方が違うので一致しない |
| 4〜6 | go test はパッケージのディレクトリで走るので、既定の ../data/sukkiri-ml には届かず実データのテストは必ずスキップされる(ML_DATA_DIR を渡す)。テストは全 64 件で、データ無し 55 PASS / 9 SKIP、カバレッジはデータ無し 60.6%・データ有り 79.2%。gofmt は CRLF のファイルを「未整形」と報告するので .gitattributes に apps/go/** text=auto eol=lf を足した。golangci-lint の既定 5 検査器で足り、mnd は採らない。errcheck が defer file.Close() を指摘するので os.ReadFile か defer func(){ _ = file.Close() }() を使う。vendoring と tool 指令は使わない |
| 7 | stat.LinearRegression は error を返さずパニックするので、包む側で件数を検査する。mat.VecDense.SolveVec は非正方行列に QR の最小二乗解を返すので正規方程式を組まなくても同じ解が出る(実測で一致)が、ほかの言語版と式をそろえるため正規方程式を明示する。map は反復順がランダムなので係数は列名スライス+値スライスで持つ。単回帰は gonum と 1e-9 で一致するが、重回帰を突き合わせる相手は gonum に無い |
| 8 | map のキーにスライスを使えないので、グループキーは \x1f 区切りの文字列にした。モデルの保存は encoding/gob。インターフェースの具体型を gob.Register で登録しないと保存時に失敗し、公開フィールドしか保存されない。Java のシリアライズと違い任意の型を復元しないので ObjectInputFilter 相当は不要。第 3 章の Tree はラベルが string で型引数を後付けできないため、整数ラベルの木を作り直し Split だけ共有した |
| 9 | gonum の stat.StdDev は標本標準偏差(n−1 で割る)で、scikit-learn の StandardScaler やほかの言語版の母標準偏差と食い違う(換算は √((n−1)/n))。標準化は自作を最終実装にし、gonum は突き合わせに使う。重回帰は mat.SymDense.SymOuterK と mat.Cholesky で正規方程式を解く(Factorize は bool 返しなので error に変える)。Shift_JIS は標準ライブラリで読めず golang.org/x/text/encoding/japanese が要る。デコーダは例外を投げず U+FFFD に置換するので、文字コード違いは実行時に気づけない |
| 10 | ロジスティック回帰もランダムフォレストも gonum に無いので自作が最終実装。特徴量重要度は木ごとに正規化してから平均する(ほかの言語版と同じ) |
| 11 | stat.ROC は使えて、自作の ROC・AUC と 1e-12 以内で一致した。ただし入力は昇順必須で、破ると error ではなくパニックする(stat.SortWeightedLabeled で先に並べる)。正解ラベルは []bool 固定で、AUC は返らないので integrate.Trapezoidal を別に呼ぶ。混同行列・適合率・再現率・F 値・K 分割交差検証は gonum に無い。staticcheck の ST1005 は、日本語のエラー文でも先頭が英語の列名だと「大文字で始まる」と判定するので、列名は後ろに回す |
| 12 | 正則化付き回帰は gonum に無いので自作が最終実装。ラッソの目的関数は ‖t − X w‖² + alpha·Σ|w|(平均で割らない流儀)なので、alpha の値はほかの言語版と比較できない |
| 13 | gonum には主成分分析(stat.PC)がある。ただし VarsTo は分散までで寄与率は自前計算になり、符号もそろわないので、自作を最終実装として stat.PC を検証の相手にした。固有値分解は mat.EigenSym で、固有値は小さい順・固有ベクトルは列に並ぶ。mat.NewSymDense に非対称な値を渡してもエラーにもパニックにもならず、右上の三角だけが使われる。実データでは mat.EigenSym と stat.PC が第 2 主成分だけ逆向きのベクトルを返した(値は小数第 13 位まで一致)ので、符号そろえは必須。突き合わせの許容幅は 1e-8 |
| 14 | gonum にクラスタリングは無いので自作が最終実装 |
| 15 | Web フレームワークを入れず標準の net/http で足りる。Go 1.22 のルーティングでメソッドとパスを書け、404 と 405 は ServeMux が返す。json.NewDecoder はストリームを読むので、JSON が 2 つ続いていても 1 つ目だけ読んで成功する(本文の残りが io.EOF であることを確かめる)。デコードのエラーのメッセージには内部の型名が入るので、そのまま返さない。http.ListenAndServe の 1 行では時間切れが無制限になるので http.Server に ReadHeaderTimeout を設定する(gosec の G112) |
決定¶
| 用途 | 採用 | バージョン | ライセンス | 初出 |
|---|---|---|---|---|
| 言語・ビルド | Go(go.mod の go 指令は 1.25)、Go Modules |
1.25 | — | 第 1 章 |
| テスト | 標準の testing(表駆動テスト) |
Go と同じ | — | 第 1 章 |
| 整形 | gofmt(gofmt -l で検査) |
Go と同じ | — | 第 1 章(記事での解説は第 5 章) |
| 静的解析 | go vet と golangci-lint(既定の検査器は errcheck・govet・ineffassign・staticcheck・unused。マジックナンバーの mnd は既定で無効) |
2.7.2 | — | 第 1 章(記事での解説は第 5 章) |
| カバレッジ | go test -cover |
Go と同じ | — | 第 1 章(記事での解説は第 5 章) |
| データの表現 | 構造体と map[string]string(セルの文字列)。データフレームのライブラリを使わない |
— | — | 第 1 章 |
| 乱数 | math/rand の rand.New(rand.NewSource(seed)) と Fisher-Yates |
Go と同じ | — | 第 2 章 |
| 機械学習 | gonum(線形回帰・共分散行列・主成分分析・ROC) | v0.17.0 | BSD 3 条項 | 第 7 章 |
| 文字コード | golang.org/x/text/encoding/japanese(Shift_JIS の CSV) |
v0.40.0 | BSD 3 条項 | 第 9 章 |
| 行列 | gonum の mat(自作しない) |
v0.17.0 | BSD 3 条項 | 第 7 章 |
| API | 標準の net/http(Go 1.22 以降のルーティング) |
Go と同じ | — | 第 15 章 |
- GoLearn は使わない。 3 年以上更新されておらず、保守されているとは言えない
- gonum に無いアルゴリズムは自作を最終実装とする。 決定木(第 3 章)・ロジスティック回帰とランダムフォレスト(第 10 章)・K-means(第 14 章)が該当し、置き換えの節は「ライブラリ未対応」と理由を書いて省略する。TypeScript 版(ml.js が未成熟)と同じ扱い
- 行列は gonum の
matを使う。 ほかの言語版は行列を自作したが、Go には標準の数値計算ライブラリとしてmatがあり、正規方程式を解く手順を見せるうえでも使いやすい。自作との違いは第 7 章で説明する - 依存は
go.modにまとめ、使う章に入ってから追加する
章ごとのライブラリへの置き換え方針¶
| 章 | 置き換え | 方針 |
|---|---|---|
| 3 | なし | 決定木が無いので自作を最終実装とし、置き換えの節を省略する |
| 7 | gonum の mat(正規方程式)と stat.LinearRegression(単回帰) |
重回帰は mat で解き、単回帰の結果を stat.LinearRegression と突き合わせる |
| 8 | なし | 決定木が無いので自作のみ |
| 9 | gonum の stat.Mean・stat.StdDev |
自作の標準化と突き合わせる(標本標準偏差か母標準偏差かを第 9 章で確かめる) |
| 10 | なし | ロジスティック回帰・ランダムフォレストが無いので自作のみ |
| 11 | gonum の stat.ROC と integrate.Trapezoidal |
ROC・AUC は突き合わせ、混同行列と交差検証は自作のみ |
| 12 | なし | 正則化付き回帰が無いので自作のみ |
| 13 | gonum の stat.PC |
自作の固有値分解と突き合わせ、寄与率は分散から求める。符号はそろえる |
| 14 | なし | K-means が無いので自作のみ |
検討した代替案¶
| 代替案 | 採用しなかった理由 |
|---|---|
| GoLearn | 2022 年 12 月から更新されていない |
| gota(データフレーム) | ほかの言語版と同じく、構造体と map で表す。保守も緩やか |
| testify | 標準の testing と表駆動テストで足りる。依存を増やさない |
| gofumpt | gofmt で足りる |
| Echo・Gin(API) | 第 15 章の主題(層の分離と統合テスト)には標準の net/http で足りる |
| 行列の自作 | gonum の mat が標準的で、正規方程式・固有値分解のどちらにも使える。自作はほかの言語版で示している |
影響¶
- 良い影響: 依存が gonum だけになり、標準ライブラリの比重が大きい Go らしい構成になる
- 良い影響: 自作の比重が大きいので、第 3・8・10・12・14 章は「ライブラリが無いときにどう作るか」を示せる。TypeScript 版と並べて読める
- 悪い影響: ライブラリとの突き合わせができる章が第 7・9・11・13 章に限られ、ほかの言語版より「自作とライブラリを比べる」節が少ない
- 悪い影響: 行列だけ gonum を使うので、ほかの言語版の「行列も自作する」構成とは違う。その理由を第 7 章に明記する
コンプライアンス¶
apps/go/go.modに gonum とgolang.org/x/textだけが記載されている- Go CI(
.github/workflows/go-ci.yml)がグリーンである - わざと違反を入れると
gofmt -l・go vet・golangci-lint runが失敗する(B39 で確かめる)
備考¶
- 著者: claude-code/claude-opus-5