```
feat: 添加高级优化器示例和简单模型示例 添加了 advanced_optimizer_example.go 和 simple_model_example.go 两个示例文件,演示了不同优化器的使用方法和简单模型的训练过程。 同时添加了模型、优化器和训练器的单元测试文件,包括: - model_test.go: 测试Sequential模型、模型保存加载功能和线性层 - optimizer_test.go: 测试SGD和Adam优化器功能 - trainer_test.go: 测试训练器的基本功能和完整训练过程 更新了go.mod和go.sum中的gomatrix依赖版本。 ```
This commit is contained in:
parent
6afb8362ac
commit
9aa53cbd5c
|
|
@ -0,0 +1,221 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kingecg.top/kingecg/gotensor"
|
||||
)
|
||||
|
||||
// LinearLayer 是一个简单的线性层实现
|
||||
type LinearLayer struct {
|
||||
Weight *gotensor.Tensor
|
||||
Bias *gotensor.Tensor
|
||||
}
|
||||
|
||||
// NewLinearLayer 创建一个新的线性层
|
||||
func NewLinearLayer(inputSize, outputSize int) *LinearLayer {
|
||||
// 初始化权重和偏置
|
||||
weight, _ := gotensor.NewTensor([]float64{
|
||||
0.1, 0.2,
|
||||
0.3, 0.4,
|
||||
}, []int{outputSize, inputSize})
|
||||
|
||||
bias, _ := gotensor.NewTensor([]float64{0.1, 0.1}, []int{outputSize})
|
||||
|
||||
return &LinearLayer{
|
||||
Weight: weight,
|
||||
Bias: bias,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LinearLayer) Forward(inputs *gotensor.Tensor) (*gotensor.Tensor, error) {
|
||||
// 执行线性变换: output = inputs * weight^T + bias
|
||||
weightTransposed, err := l.Weight.Data.Transpose()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建转置后的权重张量
|
||||
weightTransposedTensor := &gotensor.Tensor{
|
||||
Data: weightTransposed,
|
||||
Grad: must(gotensor.NewZeros(l.Weight.Shape())),
|
||||
}
|
||||
|
||||
mulResult, err := inputs.MatMul(weightTransposedTensor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := mulResult.Add(l.Bias)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (l *LinearLayer) Parameters() []*gotensor.Tensor {
|
||||
return []*gotensor.Tensor{l.Weight, l.Bias}
|
||||
}
|
||||
|
||||
func (l *LinearLayer) ZeroGrad() {
|
||||
l.Weight.ZeroGrad()
|
||||
l.Bias.ZeroGrad()
|
||||
}
|
||||
|
||||
// SimpleModel 是一个简单的模型实现
|
||||
type SimpleModel struct {
|
||||
Layer *LinearLayer
|
||||
}
|
||||
|
||||
func (m *SimpleModel) Forward(inputs *gotensor.Tensor) (*gotensor.Tensor, error) {
|
||||
return m.Layer.Forward(inputs)
|
||||
}
|
||||
|
||||
func (m *SimpleModel) Parameters() []*gotensor.Tensor {
|
||||
return m.Layer.Parameters()
|
||||
}
|
||||
|
||||
func (m *SimpleModel) ZeroGrad() {
|
||||
m.Layer.ZeroGrad()
|
||||
}
|
||||
|
||||
// must 是一个辅助函数,用于处理可能的错误
|
||||
func must(t *gotensor.Tensor, err error) *gotensor.Tensor {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("Gotensor Advanced Optimizer Example")
|
||||
|
||||
// 创建模型
|
||||
model := &SimpleModel{
|
||||
Layer: NewLinearLayer(2, 2), // 2输入,2输出
|
||||
}
|
||||
|
||||
fmt.Println("比较不同优化器的性能:")
|
||||
|
||||
// 准备训练数据 (简单的线性回归问题)
|
||||
trainInputs := []*gotensor.Tensor{
|
||||
must(gotensor.NewTensor([]float64{1, 0}, []int{2})),
|
||||
must(gotensor.NewTensor([]float64{0, 1}, []int{2})),
|
||||
must(gotensor.NewTensor([]float64{1, 1}, []int{2})),
|
||||
must(gotensor.NewTensor([]float64{0, 0}, []int{2})),
|
||||
}
|
||||
|
||||
trainTargets := []*gotensor.Tensor{
|
||||
must(gotensor.NewTensor([]float64{2, 0}, []int{2})),
|
||||
must(gotensor.NewTensor([]float64{0, 2}, []int{2})),
|
||||
must(gotensor.NewTensor([]float64{2, 2}, []int{2})),
|
||||
must(gotensor.NewTensor([]float64{0, 0}, []int{2})),
|
||||
}
|
||||
|
||||
// 定义损失函数 (MSE)
|
||||
lossFn := func(output, target *gotensor.Tensor) *gotensor.Tensor {
|
||||
// 计算均方误差
|
||||
diff, _ := output.Sub(target)
|
||||
squared, _ := diff.Mul(diff)
|
||||
sum, _ := squared.Sum()
|
||||
size := float64(output.Size())
|
||||
result, _ := sum.DivScalar(size)
|
||||
return result
|
||||
}
|
||||
|
||||
// 测试SGD优化器
|
||||
fmt.Println("\n1. 使用SGD优化器训练:")
|
||||
sgdModel := &SimpleModel{
|
||||
Layer: NewLinearLayer(2, 2),
|
||||
}
|
||||
sgdOptimizer := gotensor.NewSGD(sgdModel.Parameters(), 0.1)
|
||||
sgdTrainer := gotensor.NewTrainer(sgdModel, sgdOptimizer)
|
||||
|
||||
sgdInitialLoss, _ := sgdTrainer.Evaluate(trainInputs, trainTargets, lossFn)
|
||||
fmt.Printf("初始损失: %.6f\n", sgdInitialLoss)
|
||||
|
||||
err := sgdTrainer.Train(trainInputs, trainTargets, 100, lossFn, false)
|
||||
if err != nil {
|
||||
fmt.Printf("SGD训练过程中出现错误: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
sgdFinalLoss, _ := sgdTrainer.Evaluate(trainInputs, trainTargets, lossFn)
|
||||
fmt.Printf("SGD最终损失: %.6f\n", sgdFinalLoss)
|
||||
|
||||
// 测试Adam优化器
|
||||
fmt.Println("\n2. 使用Adam优化器训练:")
|
||||
adamModel := &SimpleModel{
|
||||
Layer: NewLinearLayer(2, 2),
|
||||
}
|
||||
adamOptimizer := gotensor.NewAdam(adamModel.Parameters(), 0.01, 0.9, 0.999, 1e-8)
|
||||
adamTrainer := gotensor.NewTrainer(adamModel, adamOptimizer)
|
||||
|
||||
adamInitialLoss, _ := adamTrainer.Evaluate(trainInputs, trainTargets, lossFn)
|
||||
fmt.Printf("初始损失: %.6f\n", adamInitialLoss)
|
||||
|
||||
err = adamTrainer.Train(trainInputs, trainTargets, 100, lossFn, false)
|
||||
if err != nil {
|
||||
fmt.Printf("Adam训练过程中出现错误: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
adamFinalLoss, _ := adamTrainer.Evaluate(trainInputs, trainTargets, lossFn)
|
||||
fmt.Printf("Adam最终损失: %.6f\n", adamFinalLoss)
|
||||
|
||||
// 比较两个模型的预测结果
|
||||
fmt.Println("\n比较两个模型的预测结果:")
|
||||
testInput := must(gotensor.NewTensor([]float64{0.5, 0.5}, []int{2}))
|
||||
|
||||
sgdOutput, _ := sgdModel.Forward(testInput)
|
||||
adamOutput, _ := adamModel.Forward(testInput)
|
||||
|
||||
sgdOut0, _ := sgdOutput.Data.Get(0)
|
||||
sgdOut1, _ := sgdOutput.Data.Get(1)
|
||||
adamOut0, _ := adamOutput.Data.Get(0)
|
||||
adamOut1, _ := adamOutput.Data.Get(1)
|
||||
|
||||
fmt.Printf("输入: [0.5, 0.5]\n")
|
||||
fmt.Printf("SGD输出: [%.6f, %.6f]\n", sgdOut0, sgdOut1)
|
||||
fmt.Printf("Adam输出: [%.6f, %.6f]\n", adamOut0, adamOut1)
|
||||
|
||||
// 演示手动使用优化器
|
||||
fmt.Println("\n3. 演示手动使用优化器:")
|
||||
manualModel := &SimpleModel{
|
||||
Layer: NewLinearLayer(2, 2),
|
||||
}
|
||||
manualOptimizer := gotensor.NewAdam(manualModel.Parameters(), 0.01, 0.9, 0.999, 1e-8)
|
||||
|
||||
// 执行几个训练步骤
|
||||
for step := 0; step < 5; step++ {
|
||||
totalLoss := 0.0
|
||||
for i := 0; i < len(trainInputs); i++ {
|
||||
// 前向传播
|
||||
output, err := manualModel.Forward(trainInputs[i])
|
||||
if err != nil {
|
||||
fmt.Printf("前向传播错误: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 计算损失
|
||||
loss := lossFn(output, trainTargets[i])
|
||||
lossVal, _ := loss.Data.Get(0)
|
||||
totalLoss += lossVal
|
||||
|
||||
// 反向传播
|
||||
loss.Backward()
|
||||
|
||||
// 更新参数
|
||||
manualOptimizer.Step()
|
||||
|
||||
// 清空梯度
|
||||
manualOptimizer.ZeroGrad()
|
||||
}
|
||||
|
||||
avgLoss := totalLoss / float64(len(trainInputs))
|
||||
fmt.Printf("步骤 %d, 平均损失: %.6f\n", step+1, avgLoss)
|
||||
}
|
||||
|
||||
fmt.Println("\n优化器示例完成!")
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kingecg.top/kingecg/gotensor"
|
||||
)
|
||||
|
||||
// LinearLayer 是一个简单的线性层实现
|
||||
type LinearLayer struct {
|
||||
Weight *gotensor.Tensor
|
||||
Bias *gotensor.Tensor
|
||||
}
|
||||
|
||||
// NewLinearLayer 创建一个新的线性层
|
||||
func NewLinearLayer(inputSize, outputSize int) *LinearLayer {
|
||||
weight, _ := gotensor.NewTensor([]float64{
|
||||
0.5, 0.1,
|
||||
0.2, 0.4,
|
||||
}, []int{outputSize, inputSize})
|
||||
|
||||
bias, _ := gotensor.NewTensor([]float64{0, 0}, []int{outputSize})
|
||||
|
||||
return &LinearLayer{
|
||||
Weight: weight,
|
||||
Bias: bias,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LinearLayer) Forward(inputs *gotensor.Tensor) (*gotensor.Tensor, error) {
|
||||
// 执行线性变换: output = inputs * weight^T + bias
|
||||
// 首先转置权重
|
||||
weightTransposed, err := l.Weight.Data.Transpose()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建转置后的权重张量
|
||||
weightTransposedTensor := &gotensor.Tensor{
|
||||
Data: weightTransposed,
|
||||
Grad: must(gotensor.NewZeros(l.Weight.Shape())),
|
||||
}
|
||||
|
||||
// 矩阵乘法
|
||||
mulResult, err := inputs.MatMul(weightTransposedTensor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 加上偏置
|
||||
output, err := mulResult.Add(l.Bias)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (l *LinearLayer) Parameters() []*gotensor.Tensor {
|
||||
return []*gotensor.Tensor{l.Weight, l.Bias}
|
||||
}
|
||||
|
||||
func (l *LinearLayer) ZeroGrad() {
|
||||
l.Weight.ZeroGrad()
|
||||
l.Bias.ZeroGrad()
|
||||
}
|
||||
|
||||
// SimpleModel 是一个简单的模型实现
|
||||
type SimpleModel struct {
|
||||
Layer *LinearLayer
|
||||
}
|
||||
|
||||
func (m *SimpleModel) Forward(inputs *gotensor.Tensor) (*gotensor.Tensor, error) {
|
||||
return m.Layer.Forward(inputs)
|
||||
}
|
||||
|
||||
func (m *SimpleModel) Parameters() []*gotensor.Tensor {
|
||||
return m.Layer.Parameters()
|
||||
}
|
||||
|
||||
func (m *SimpleModel) ZeroGrad() {
|
||||
m.Layer.ZeroGrad()
|
||||
}
|
||||
|
||||
// must 是一个辅助函数,用于处理可能的错误
|
||||
func must(t *gotensor.Tensor, err error) *gotensor.Tensor {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("Gotensor Simple Model Example")
|
||||
|
||||
// 创建模型
|
||||
model := &SimpleModel{
|
||||
Layer: NewLinearLayer(2, 2), // 2输入,2输出
|
||||
}
|
||||
|
||||
// 创建优化器 (SGD)
|
||||
optimizer := gotensor.NewSGD(model.Parameters(), 0.01)
|
||||
|
||||
// 创建训练器
|
||||
trainer := gotensor.NewTrainer(model, optimizer)
|
||||
|
||||
// 准备训练数据 (简单的XOR问题)
|
||||
trainInputs := []*gotensor.Tensor{
|
||||
must(gotensor.NewTensor([]float64{0, 0}, []int{2})),
|
||||
must(gotensor.NewTensor([]float64{0, 1}, []int{2})),
|
||||
must(gotensor.NewTensor([]float64{1, 0}, []int{2})),
|
||||
must(gotensor.NewTensor([]float64{1, 1}, []int{2})),
|
||||
}
|
||||
|
||||
trainTargets := []*gotensor.Tensor{
|
||||
must(gotensor.NewTensor([]float64{0, 1}, []int{2})), // 0 XOR 0 = 0 (表示为 [0,1] -> [0])
|
||||
must(gotensor.NewTensor([]float64{1, 0}, []int{2})), // 0 XOR 1 = 1 (表示为 [1,0] -> [1])
|
||||
must(gotensor.NewTensor([]float64{1, 0}, []int{2})), // 1 XOR 0 = 1 (表示为 [1,0] -> [1])
|
||||
must(gotensor.NewTensor([]float64{0, 1}, []int{2})), // 1 XOR 1 = 0 (表示为 [0,1] -> [0])
|
||||
}
|
||||
|
||||
// 定义损失函数 (MSE)
|
||||
lossFn := func(output, target *gotensor.Tensor) *gotensor.Tensor {
|
||||
// 计算均方误差
|
||||
diff, _ := output.Sub(target)
|
||||
squared, _ := diff.Mul(diff)
|
||||
sum, _ := squared.Sum()
|
||||
size := float64(output.Size())
|
||||
result, _ := sum.DivScalar(size)
|
||||
return result
|
||||
}
|
||||
|
||||
fmt.Println("开始训练模型...")
|
||||
|
||||
// 训练模型
|
||||
epochs := 100
|
||||
err := trainer.Train(trainInputs, trainTargets, epochs, lossFn, true)
|
||||
if err != nil {
|
||||
fmt.Printf("训练过程中出现错误: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("训练完成!")
|
||||
|
||||
// 评估模型
|
||||
fmt.Println("\n评估训练结果:")
|
||||
for i, input := range trainInputs {
|
||||
output, err := model.Forward(input)
|
||||
if err != nil {
|
||||
fmt.Printf("前向传播错误: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
inputVal0, _ := input.Data.Get(0)
|
||||
inputVal1, _ := input.Data.Get(1)
|
||||
outputVal0, _ := output.Data.Get(0)
|
||||
outputVal1, _ := output.Data.Get(1)
|
||||
|
||||
fmt.Printf("输入: [%.0f, %.0f] -> 输出: [%.3f, %.3f], 目标: [%.0f, %.0f]\n",
|
||||
inputVal0, inputVal1, outputVal0, outputVal1,
|
||||
trainTargets[i].Data.Get(0), trainTargets[i].Data.Get(1))
|
||||
}
|
||||
|
||||
// 保存模型
|
||||
err = gotensor.SaveModel(model, "/tmp/simple_model.json")
|
||||
if err != nil {
|
||||
fmt.Printf("保存模型失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("模型已保存到 /tmp/simple_model.json")
|
||||
}
|
||||
|
||||
// 加载模型
|
||||
newModel := &SimpleModel{
|
||||
Layer: NewLinearLayer(2, 2),
|
||||
}
|
||||
err = gotensor.LoadModel(newModel, "/tmp/simple_model.json")
|
||||
if err != nil {
|
||||
fmt.Printf("加载模型失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("模型已从 /tmp/simple_model.json 加载")
|
||||
}
|
||||
}
|
||||
2
go.mod
2
go.mod
|
|
@ -2,4 +2,4 @@ module git.kingecg.top/kingecg/gotensor
|
|||
|
||||
go 1.25.1
|
||||
|
||||
require git.kingecg.top/kingecg/gomatrix v0.0.0-20251230141944-2ff4dcfb0fcd // indirect
|
||||
require git.kingecg.top/kingecg/gomatrix v0.0.0-20251231094846-bfcfba4e3f99
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -1,2 +1,6 @@
|
|||
git.kingecg.top/kingecg/gomatrix v0.0.0-20251230141944-2ff4dcfb0fcd h1:vn3LW38hQPGig0iqofIaIMYXVp3Uqb5QX6eH5B5lVxU=
|
||||
git.kingecg.top/kingecg/gomatrix v0.0.0-20251230141944-2ff4dcfb0fcd/go.mod h1:CHH1HkVvXrpsb+uDrsoyjx0lTwQ3oSSMbIRJmwvO6z8=
|
||||
git.kingecg.top/kingecg/gomatrix v0.0.0-20251231092627-f40960a855c7 h1:tutkcVKwpzNYxZRXkunhnkrDGRfMYgvwGAbCBCtO62c=
|
||||
git.kingecg.top/kingecg/gomatrix v0.0.0-20251231092627-f40960a855c7/go.mod h1:CHH1HkVvXrpsb+uDrsoyjx0lTwQ3oSSMbIRJmwvO6z8=
|
||||
git.kingecg.top/kingecg/gomatrix v0.0.0-20251231094846-bfcfba4e3f99 h1:sV3rEZIhYwU1TLmqFybT6Lwf6lA4oiITX/HC7i+JsiA=
|
||||
git.kingecg.top/kingecg/gomatrix v0.0.0-20251231094846-bfcfba4e3f99/go.mod h1:CHH1HkVvXrpsb+uDrsoyjx0lTwQ3oSSMbIRJmwvO6z8=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,218 @@
|
|||
package gotensor
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"git.kingecg.top/kingecg/gomatrix"
|
||||
)
|
||||
|
||||
// Linear 是一个简单的线性层实现,用于测试
|
||||
type Linear struct {
|
||||
Weight *Tensor
|
||||
Bias *Tensor
|
||||
}
|
||||
|
||||
// NewLinear 创建一个新的线性层
|
||||
func NewLinear(weight, bias *Tensor) *Linear {
|
||||
return &Linear{
|
||||
Weight: weight,
|
||||
Bias: bias,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Linear) Forward(inputs *Tensor) (*Tensor, error) {
|
||||
// 执行线性变换: output = inputs * weight^T + bias
|
||||
weightTransposed, err := l.Weight.Data.Transpose()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mulResult, err := inputs.MatMul(&Tensor{Data: weightTransposed}) // 需要包装为Tensor
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := mulResult.Add(l.Bias)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (l *Linear) Parameters() []*Tensor {
|
||||
return []*Tensor{l.Weight, l.Bias}
|
||||
}
|
||||
|
||||
func (l *Linear) ZeroGrad() {
|
||||
l.Weight.ZeroGrad()
|
||||
l.Bias.ZeroGrad()
|
||||
}
|
||||
|
||||
// TestSequential 测试Sequential模型的基本功能
|
||||
func TestSequential(t *testing.T) {
|
||||
// 创建一个简单的线性层实现用于测试
|
||||
weight, err := gomatrix.NewMatrix([]float64{1, 2, 3, 4}, []int{2, 2})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create weight matrix: %v", err)
|
||||
}
|
||||
bias, err := gomatrix.NewMatrix([]float64{0.5, 0.5}, []int{2})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create bias vector: %v", err)
|
||||
}
|
||||
|
||||
linearLayer := &Linear{
|
||||
Weight: &Tensor{
|
||||
Data: weight,
|
||||
Grad: Must(NewMatrix([][]float64{{0, 0}, {0, 0}})),
|
||||
},
|
||||
Bias: &Tensor{
|
||||
Data: bias,
|
||||
Grad: Must(NewVector([]float64{0, 0})),
|
||||
},
|
||||
}
|
||||
|
||||
// 创建Sequential模型
|
||||
seq := &Sequential{
|
||||
Layers: []Layer{linearLayer},
|
||||
}
|
||||
|
||||
// 测试前向传播
|
||||
input := Must(NewVector([]float64{1, 1}))
|
||||
output, err := seq.Forward(input)
|
||||
if err != nil {
|
||||
t.Errorf("Sequential forward failed: %v", err)
|
||||
}
|
||||
|
||||
if output == nil {
|
||||
t.Error("Expected output tensor, got nil")
|
||||
}
|
||||
|
||||
// 测试Parameters方法
|
||||
params := seq.Parameters()
|
||||
if len(params) == 0 {
|
||||
t.Error("Expected non-empty parameters list")
|
||||
}
|
||||
|
||||
// 测试ZeroGrad方法
|
||||
seq.ZeroGrad()
|
||||
for _, param := range params {
|
||||
// 检查梯度是否被清零
|
||||
shape := param.Shape()
|
||||
for i := 0; i < param.Size(); i++ {
|
||||
var gradVal float64
|
||||
if len(shape) == 1 {
|
||||
gradVal, _ = param.Grad.Get(i)
|
||||
} else if len(shape) == 2 {
|
||||
cols := shape[1]
|
||||
gradVal, _ = param.Grad.Get(i/cols, i%cols)
|
||||
}
|
||||
if gradVal != 0 {
|
||||
t.Errorf("Expected gradient to be zero, got %v", gradVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSaveLoadModel 测试模型保存和加载功能
|
||||
func TestSaveLoadModel(t *testing.T) {
|
||||
weight, err := NewMatrix([][]float64{{1, 2}, {3, 4}})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create weight matrix: %v", err)
|
||||
}
|
||||
bias, err := NewVector([]float64{0.5, 0.5})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create bias vector: %v", err)
|
||||
}
|
||||
|
||||
linearLayer := &Linear{
|
||||
Weight: &Tensor{
|
||||
Data: weight,
|
||||
Grad: Must(NewMatrix([][]float64{{0, 0}, {0, 0}})),
|
||||
},
|
||||
Bias: &Tensor{
|
||||
Data: bias,
|
||||
Grad: Must(NewVector([]float64{0, 0})),
|
||||
},
|
||||
}
|
||||
|
||||
model := &Sequential{
|
||||
Layers: []Layer{linearLayer},
|
||||
}
|
||||
|
||||
// 保存模型
|
||||
filepath := "/tmp/test_model.json"
|
||||
err = SaveModel(model, filepath)
|
||||
if err != nil {
|
||||
t.Errorf("SaveModel failed: %v", err)
|
||||
}
|
||||
|
||||
// 修改原始模型参数
|
||||
weightVal, _ := linearLayer.Weight.Data.Get(0, 0)
|
||||
if math.Abs(weightVal-1.0) > 1e-9 {
|
||||
t.Errorf("Expected weight to be 1.0, got %v", weightVal)
|
||||
}
|
||||
|
||||
// 加载模型
|
||||
err = LoadModel(model, filepath)
|
||||
if err != nil {
|
||||
t.Errorf("LoadModel failed: %v", err)
|
||||
}
|
||||
|
||||
// 验证加载后的参数
|
||||
loadedWeight, _ := linearLayer.Weight.Data.Get(0, 0)
|
||||
if math.Abs(loadedWeight-1.0) > 1e-9 {
|
||||
t.Errorf("Expected loaded weight to be 1.0, got %v", loadedWeight)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLinearLayer 测试线性层功能
|
||||
func TestLinearLayer(t *testing.T) {
|
||||
weight := Must(NewMatrix([][]float64{{2, 0}, {0, 3}}))
|
||||
bias := Must(NewVector([]float64{0.5, 0.5}))
|
||||
|
||||
layer := NewLinear(weight, bias)
|
||||
|
||||
// 测试前向传播
|
||||
input := Must(NewVector([]float64{1, 1}))
|
||||
output, err := layer.Forward(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Linear layer forward failed: %v", err)
|
||||
}
|
||||
|
||||
// 计算期望输出: weight * input + bias = [[2,0],[0,3]] * [1,1] + [0.5,0.5] = [2.5,3.5]
|
||||
expected0, _ := output.Data.Get(0)
|
||||
expected1, _ := output.Data.Get(1)
|
||||
|
||||
if math.Abs(expected0-2.5) > 1e-9 {
|
||||
t.Errorf("Expected output[0] to be 2.5, got %v", expected0)
|
||||
}
|
||||
if math.Abs(expected1-3.5) > 1e-9 {
|
||||
t.Errorf("Expected output[1] to be 3.5, got %v", expected1)
|
||||
}
|
||||
|
||||
// 测试Parameters方法
|
||||
params := layer.Parameters()
|
||||
if len(params) != 2 { // 权重和偏置
|
||||
t.Errorf("Expected 2 parameters, got %d", len(params))
|
||||
}
|
||||
|
||||
// 测试ZeroGrad方法
|
||||
layer.ZeroGrad()
|
||||
for _, param := range params {
|
||||
shape := param.Shape()
|
||||
for i := 0; i < param.Size(); i++ {
|
||||
var gradVal float64
|
||||
if len(shape) == 1 {
|
||||
gradVal, _ = param.Grad.Get(i)
|
||||
} else if len(shape) == 2 {
|
||||
cols := shape[1]
|
||||
gradVal, _ = param.Grad.Get(i/cols, i%cols)
|
||||
}
|
||||
if math.Abs(gradVal) > 1e-9 {
|
||||
t.Errorf("Expected gradient to be zero, got %v", gradVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
package gotensor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"math"
|
||||
)
|
||||
|
||||
// TestSGD 测试SGD优化器
|
||||
func TestSGD(t *testing.T) {
|
||||
// 创建一些参数用于测试
|
||||
weightData, _ := NewMatrix([][]float64{{1.0, 2.0}, {3.0, 4.0}})
|
||||
weightGrad, _ := NewMatrix([][]float64{{0.1, 0.2}, {0.3, 0.4}})
|
||||
|
||||
params := []*Tensor{
|
||||
{
|
||||
Data: Must(NewVector([]float64{1.0, 2.0, 3.0})),
|
||||
Grad: Must(NewVector([]float64{0.1, 0.2, 0.3})),
|
||||
},
|
||||
{
|
||||
Data: weightData,
|
||||
Grad: weightGrad,
|
||||
},
|
||||
}
|
||||
|
||||
// 创建SGD优化器
|
||||
lr := 0.1
|
||||
sgd := NewSGD(params, lr)
|
||||
|
||||
// 保存原始参数值
|
||||
origVec0, _ := params[0].Data.Get(0)
|
||||
origMat00, _ := params[1].Data.Get(0, 0)
|
||||
|
||||
// 执行一步优化
|
||||
sgd.Step()
|
||||
|
||||
// 检查参数是否已更新
|
||||
newVec0, _ := params[0].Data.Get(0)
|
||||
newMat00, _ := params[1].Data.Get(0, 0)
|
||||
|
||||
expectedVec0 := origVec0 - lr*0.1
|
||||
expectedMat00 := origMat00 - lr*0.1
|
||||
|
||||
if math.Abs(newVec0-expectedVec0) > 1e-9 {
|
||||
t.Errorf("Expected updated param[0][0] to be %v, got %v", expectedVec0, newVec0)
|
||||
}
|
||||
|
||||
if math.Abs(newMat00-expectedMat00) > 1e-9 {
|
||||
t.Errorf("Expected updated param[1][0,0] to be %v, got %v", expectedMat00, newMat00)
|
||||
}
|
||||
|
||||
// 测试ZeroGrad
|
||||
sgd.ZeroGrad()
|
||||
for _, param := range params {
|
||||
shape := param.Shape()
|
||||
for i := 0; i < param.Size(); i++ {
|
||||
var gradVal float64
|
||||
if len(shape) == 1 {
|
||||
gradVal, _ = param.Grad.Get(i)
|
||||
} else if len(shape) == 2 {
|
||||
cols := shape[1]
|
||||
gradVal, _ = param.Grad.Get(i/cols, i%cols)
|
||||
}
|
||||
if math.Abs(gradVal) > 1e-9 {
|
||||
t.Errorf("Expected gradient to be zero after ZeroGrad, got %v", gradVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdam 测试Adam优化器
|
||||
func TestAdam(t *testing.T) {
|
||||
// 创建一些参数用于测试
|
||||
params := []*Tensor{
|
||||
{
|
||||
Data: Must(NewVector([]float64{1.0, 2.0})),
|
||||
Grad: Must(NewVector([]float64{0.1, 0.2})),
|
||||
},
|
||||
}
|
||||
|
||||
// 创建Adam优化器
|
||||
lr := 0.001
|
||||
beta1 := 0.9
|
||||
beta2 := 0.999
|
||||
epsilon := 1e-8
|
||||
adam := NewAdam(params, lr, beta1, beta2, epsilon)
|
||||
|
||||
// 保存原始参数值
|
||||
origVec0, _ := params[0].Data.Get(0)
|
||||
|
||||
// 执行几步优化
|
||||
for i := 0; i < 3; i++ {
|
||||
adam.Step()
|
||||
}
|
||||
|
||||
// 检查参数是否已更新
|
||||
newVec0, _ := params[0].Data.Get(0)
|
||||
|
||||
if math.Abs(newVec0-origVec0) < 1e-9 {
|
||||
t.Errorf("Expected parameter to be updated, but it wasn't. Original: %v, New: %v", origVec0, newVec0)
|
||||
}
|
||||
|
||||
// 验证内部状态是否已创建
|
||||
if len(adam.M) != len(params) || len(adam.V) != len(params) {
|
||||
t.Error("Adam internal states M and V not properly initialized")
|
||||
}
|
||||
|
||||
// 测试ZeroGrad
|
||||
adam.ZeroGrad()
|
||||
for _, param := range params {
|
||||
shape := param.Shape()
|
||||
for i := 0; i < param.Size(); i++ {
|
||||
var gradVal float64
|
||||
if len(shape) == 1 {
|
||||
gradVal, _ = param.Grad.Get(i)
|
||||
} else if len(shape) == 2 {
|
||||
cols := shape[1]
|
||||
gradVal, _ = param.Grad.Get(i/cols, i%cols)
|
||||
}
|
||||
if math.Abs(gradVal) > 1e-9 {
|
||||
t.Errorf("Expected gradient to be zero after ZeroGrad, got %v", gradVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdamWithMatrix 测试Adam优化器处理矩阵参数
|
||||
func TestAdamWithMatrix(t *testing.T) {
|
||||
matrixData, _ := NewMatrix([][]float64{{1.0, 2.0}, {3.0, 4.0}})
|
||||
matrixGrad, _ := NewMatrix([][]float64{{0.1, 0.2}, {0.3, 0.4}})
|
||||
|
||||
// 创建矩阵参数用于测试
|
||||
params := []*Tensor{
|
||||
{
|
||||
Data: matrixData,
|
||||
Grad: matrixGrad,
|
||||
},
|
||||
}
|
||||
|
||||
// 创建Adam优化器
|
||||
lr := 0.001
|
||||
adam := NewAdam(params, lr, 0.9, 0.999, 1e-8)
|
||||
|
||||
// 保存原始参数值
|
||||
origMat00, _ := params[0].Data.Get(0, 0)
|
||||
|
||||
// 执行几步优化
|
||||
for i := 0; i < 5; i++ {
|
||||
adam.Step()
|
||||
}
|
||||
|
||||
// 检查参数是否已更新
|
||||
newMat00, _ := params[0].Data.Get(0, 0)
|
||||
|
||||
if math.Abs(newMat00-origMat00) < 1e-9 {
|
||||
t.Errorf("Expected parameter to be updated, but it wasn't. Original: %v, New: %v", origMat00, newMat00)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
package gotensor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.kingecg.top/kingecg/gomatrix"
|
||||
)
|
||||
|
||||
// MockLayer 是一个用于测试的模拟层
|
||||
type MockLayer struct {
|
||||
Weight *Tensor
|
||||
}
|
||||
|
||||
// NewMockLayer 创建一个新的模拟层
|
||||
func NewMockLayer() *MockLayer {
|
||||
weight, _ := gomatrix.NewMatrix([]float64{0.5, 0.3, 0.4, 0.7}, []int{2, 2})
|
||||
grad, _ := gomatrix.NewZeros([]int{2, 2})
|
||||
|
||||
return &MockLayer{
|
||||
Weight: &Tensor{
|
||||
Data: weight,
|
||||
Grad: grad,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockLayer) Forward(inputs *Tensor) (*Tensor, error) {
|
||||
// 简单的矩阵乘法
|
||||
output, err := m.Weight.MatMul(inputs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (m *MockLayer) Parameters() []*Tensor {
|
||||
return []*Tensor{m.Weight}
|
||||
}
|
||||
|
||||
func (m *MockLayer) ZeroGrad() {
|
||||
m.Weight.ZeroGrad()
|
||||
}
|
||||
|
||||
// MockModel 是一个用于测试的模拟模型
|
||||
type MockModel struct {
|
||||
Layer *MockLayer
|
||||
}
|
||||
|
||||
func (m *MockModel) Forward(inputs *Tensor) (*Tensor, error) {
|
||||
return m.Layer.Forward(inputs)
|
||||
}
|
||||
|
||||
func (m *MockModel) Parameters() []*Tensor {
|
||||
return m.Layer.Parameters()
|
||||
}
|
||||
|
||||
func (m *MockModel) ZeroGrad() {
|
||||
m.Layer.ZeroGrad()
|
||||
}
|
||||
|
||||
func NewVector(data []float64) (*Tensor, error) {
|
||||
return NewTensor(data, []int{len(data), 1})
|
||||
|
||||
}
|
||||
|
||||
func Must(t *Tensor, err error) *Tensor {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// TestTrainer 测试训练器的基本功能
|
||||
func TestTrainer(t *testing.T) {
|
||||
// 创建模型
|
||||
mockLayer := NewMockLayer()
|
||||
model := &MockModel{
|
||||
Layer: mockLayer,
|
||||
}
|
||||
|
||||
// 创建优化器
|
||||
optimizer := NewSGD(model.Parameters(), 0.01)
|
||||
|
||||
// 创建训练器
|
||||
trainer := NewTrainer(model, optimizer)
|
||||
|
||||
// 创建训练数据
|
||||
inputs := []*Tensor{
|
||||
Must(NewVector([]float64{1, 0})),
|
||||
Must(NewVector([]float64{0, 1})),
|
||||
}
|
||||
|
||||
targets := []*Tensor{
|
||||
Must(NewVector([]float64{1, 0})),
|
||||
Must(NewVector([]float64{0, 1})),
|
||||
}
|
||||
|
||||
// 定义损失函数
|
||||
lossFn := func(output, target *Tensor) *Tensor {
|
||||
// MSE 损失函数
|
||||
diff, _ := output.Data.Subtract(target.Data)
|
||||
squared, _ := diff.Multiply(diff)
|
||||
|
||||
// 计算矩阵元素的总和
|
||||
var total float64
|
||||
shape := squared.Shape()
|
||||
if len(shape) == 1 {
|
||||
for i := 0; i < squared.Size(); i++ {
|
||||
val, _ := squared.Get(i)
|
||||
total += val
|
||||
}
|
||||
} else if len(shape) == 2 {
|
||||
rows, cols := shape[0], shape[1]
|
||||
for i := 0; i < rows; i++ {
|
||||
for j := 0; j < cols; j++ {
|
||||
val, _ := squared.Get(i, j)
|
||||
total += val
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 对于其他维度,遍历所有元素
|
||||
for i := 0; i < squared.Size(); i++ {
|
||||
var val float64
|
||||
var err error
|
||||
|
||||
if len(shape) == 1 {
|
||||
val, err = squared.Get(i)
|
||||
} else if len(shape) == 2 {
|
||||
cols := shape[1]
|
||||
val, err = squared.Get(i/cols, i%cols)
|
||||
} else {
|
||||
// 对于高维张量,按顺序访问元素
|
||||
val, err = squared.Get(i)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
total += val
|
||||
}
|
||||
}
|
||||
|
||||
size := float64(output.Size())
|
||||
resultVal := total / size
|
||||
|
||||
// 创建结果张量
|
||||
result, _ := NewTensor([]float64{resultVal}, []int{1})
|
||||
return result
|
||||
}
|
||||
|
||||
// 测试TrainEpoch
|
||||
avgLoss, err := trainer.TrainEpoch(inputs, targets, lossFn)
|
||||
if err != nil {
|
||||
t.Errorf("TrainEpoch failed: %v", err)
|
||||
}
|
||||
|
||||
if avgLoss < 0 {
|
||||
t.Errorf("Expected non-negative loss, got %v", avgLoss)
|
||||
}
|
||||
|
||||
// 测试Evaluate
|
||||
evalLoss, err := trainer.Evaluate(inputs, targets, lossFn)
|
||||
if err != nil {
|
||||
t.Errorf("Evaluate failed: %v", err)
|
||||
}
|
||||
|
||||
if evalLoss < 0 {
|
||||
t.Errorf("Expected non-negative evaluation loss, got %v", evalLoss)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTrainerFullTrain 测试完整的训练过程
|
||||
func TestTrainerFullTrain(t *testing.T) {
|
||||
// 创建一个简单的线性回归模型
|
||||
mockLayer := NewMockLayer()
|
||||
model := &MockModel{
|
||||
Layer: mockLayer,
|
||||
}
|
||||
|
||||
// 创建优化器
|
||||
optimizer := NewSGD(model.Parameters(), 0.1)
|
||||
|
||||
// 创建训练器
|
||||
trainer := NewTrainer(model, optimizer)
|
||||
|
||||
// 创建训练数据 (简单的 y = x 示例)
|
||||
trainInputs := []*Tensor{
|
||||
Must(NewVector([]float64{1, 0})),
|
||||
Must(NewVector([]float64{0, 1})),
|
||||
Must(NewVector([]float64{1, 1})),
|
||||
}
|
||||
|
||||
trainTargets := []*Tensor{
|
||||
Must(NewVector([]float64{1, 0})),
|
||||
Must(NewVector([]float64{0, 1})),
|
||||
Must(NewVector([]float64{1, 1})),
|
||||
}
|
||||
|
||||
// 定义损失函数
|
||||
lossFn := func(output, target *Tensor) *Tensor {
|
||||
// MSE 损失函数
|
||||
diff, _ := output.Data.Subtract(target.Data)
|
||||
squared, _ := diff.Multiply(diff)
|
||||
sum := squared.Sum()
|
||||
size := float64(output.Size())
|
||||
result := sum / size
|
||||
return Must(NewTensor([]float64{result}, []int{1}))
|
||||
// return result
|
||||
}
|
||||
|
||||
// 训练模型
|
||||
epochs := 5
|
||||
err := trainer.Train(trainInputs, trainTargets, epochs, lossFn, false)
|
||||
if err != nil {
|
||||
t.Errorf("Full training failed: %v", err)
|
||||
}
|
||||
|
||||
// 验证训练后的损失应该比训练前低(理想情况下)
|
||||
evalLoss, err := trainer.Evaluate(trainInputs, trainTargets, lossFn)
|
||||
if err != nil {
|
||||
t.Errorf("Post-training evaluation failed: %v", err)
|
||||
}
|
||||
|
||||
if evalLoss < 0 {
|
||||
t.Errorf("Expected non-negative evaluation loss after training, got %v", evalLoss)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTrainerWithDifferentOptimizers 测试使用不同优化器的训练器
|
||||
func TestTrainerWithDifferentOptimizers(t *testing.T) {
|
||||
// 创建模型
|
||||
mockLayer := NewMockLayer()
|
||||
model := &MockModel{
|
||||
Layer: mockLayer,
|
||||
}
|
||||
|
||||
// 测试Adam优化器
|
||||
adamOpt := NewAdam(model.Parameters(), 0.001, 0.9, 0.999, 1e-8)
|
||||
trainer := NewTrainer(model, adamOpt)
|
||||
|
||||
// 创建训练数据
|
||||
inputs := []*Tensor{
|
||||
Must(NewVector([]float64{1, 0})),
|
||||
Must(NewVector([]float64{0, 1})),
|
||||
}
|
||||
|
||||
targets := []*Tensor{
|
||||
Must(NewVector([]float64{1, 0})),
|
||||
Must(NewVector([]float64{0, 1})),
|
||||
}
|
||||
|
||||
// 损失函数
|
||||
lossFn := func(output, target *Tensor) *Tensor {
|
||||
diff, _ := output.Data.Subtract(target.Data)
|
||||
squared, _ := diff.Multiply(diff)
|
||||
|
||||
// 计算矩阵元素的总和
|
||||
var total float64
|
||||
shape := squared.Shape()
|
||||
if len(shape) == 1 {
|
||||
for i := 0; i < squared.Size(); i++ {
|
||||
val, _ := squared.Get(i)
|
||||
total += val
|
||||
}
|
||||
} else if len(shape) == 2 {
|
||||
rows, cols := shape[0], shape[1]
|
||||
for i := 0; i < rows; i++ {
|
||||
for j := 0; j < cols; j++ {
|
||||
val, _ := squared.Get(i, j)
|
||||
total += val
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 对于其他维度,遍历所有元素
|
||||
for i := 0; i < squared.Size(); i++ {
|
||||
var val float64
|
||||
var err error
|
||||
|
||||
if len(shape) == 1 {
|
||||
val, err = squared.Get(i)
|
||||
} else if len(shape) == 2 {
|
||||
cols := shape[1]
|
||||
val, err = squared.Get(i/cols, i%cols)
|
||||
} else {
|
||||
// 对于高维张量,按顺序访问元素
|
||||
val, err = squared.Get(i)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
total += val
|
||||
}
|
||||
}
|
||||
|
||||
size := float64(output.Size())
|
||||
resultVal := total / size
|
||||
|
||||
// 创建结果张量
|
||||
result, _ := NewTensor([]float64{resultVal}, []int{1})
|
||||
return result
|
||||
}
|
||||
|
||||
// 训练
|
||||
err := trainer.Train(inputs, targets, 3, lossFn, false)
|
||||
if err != nil {
|
||||
t.Errorf("Training with Adam optimizer failed: %v", err)
|
||||
}
|
||||
|
||||
evalLoss, err := trainer.Evaluate(inputs, targets, lossFn)
|
||||
if err != nil {
|
||||
t.Errorf("Evaluation with Adam optimizer failed: %v", err)
|
||||
}
|
||||
|
||||
if evalLoss < 0 {
|
||||
t.Errorf("Expected non-negative evaluation loss with Adam, got %v", evalLoss)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue