321 lines
6.9 KiB
Go
321 lines
6.9 KiB
Go
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 any](t *T, err error) *T {
|
|
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)
|
|
}
|
|
}
|