feat: 添加模型定义、优化器和训练器功能

- 实现Model接口和Sequential序列模型,支持模型定义和前向传播
- 添加SGD和Adam优化器,支持参数更新和梯度清零
- 创建Trainer训练器,提供完整的训练和评估流程
- 实现模型保存和加载功能,支持参数序列化
- 更新README文档,添加模型训练示例和API文档
- 重构README中的功能特性和示例代码
```
This commit is contained in:
程广 2025-12-31 14:45:29 +08:00
parent 3e9e913dd4
commit fcc2e54144
4 changed files with 521 additions and 90 deletions

182
README.md
View File

@ -1,16 +1,19 @@
# gotensor
gotensor 是一个用 Go 语言编写的张量计算库,提供了基本的张量运算、自动微分和反向传播功能。该项目旨在为 Go 语言开发者提供一个高效、易用的张量计算工具
gotensor 是一个用 Go 语言实现的张量计算库,专注于为 Go 开发者提供高效的数值计算能力,支持自动微分和反向传播,适用于构建轻量级机器学习模型
## 功能特性
- 基本张量运算:加法、减法、乘法、矩阵乘法等
- 张量操作:数乘、转置等
- 自动微分和反向传播
- 激活函数Sigmoid、ReLU、Softmax等
- 卷积和池化操作Conv2D、MaxPool2D、AvgPool2D等
- 神经网络层Flatten、损失函数等
- 支持多种初始化方式:零张量、单位矩阵、随机张量等
- 基本张量运算:加法、减法、乘法、矩阵乘法
- 数乘、转置等张量操作
- 自动微分与反向传播机制
- 激活函数Sigmoid、ReLU、Softmax
- 卷积与池化操作Conv2D、MaxPool2D、AvgPool2D
- 神经网络层与损失函数Flatten、CrossEntropy、MSE
- 多种初始化方式:零张量、单位矩阵、随机张量
- 模型定义和训练支持
- 模型保存和加载
- 多种优化器SGD、Adam
## 安装
@ -29,107 +32,106 @@ import (
)
func main() {
// 创建两个2x2的张量
t1_data := []float64{1, 2, 3, 4}
t1_shape := []int{2, 2}
t1, err := gotensor.NewTensor(t1_data, t1_shape)
if err != nil {
panic(err)
}
t2_data := []float64{5, 6, 7, 8}
t2, err := gotensor.NewTensor(t2_data, t1_shape)
if err != nil {
panic(err)
}
// 执行加法运算
result, err := t1.Add(t2)
if err != nil {
panic(err)
}
fmt.Printf("结果:\n%s\n", result.String())
// 创建张量
tensor1, _ := gotensor.NewTensor([]float64{1, 2, 3}, []int{1, 3})
tensor2, _ := gotensor.NewTensor([]float64{4, 5, 6}, []int{3, 1})
// 执行矩阵乘法
result, _ := tensor1.MatMul(tensor2)
fmt.Println(result)
}
```
## 示例
## 模型训练示例
项目包含多个示例,展示如何使用 gotensor
```go
package main
- [基本运算示例](examples/basic_operations.go):展示基本的张量运算
- [自动微分示例](examples/autograd_example.go):演示自动微分和反向传播
- [线性回归示例](examples/linear_regression.go):使用 gotensor 实现简单的线性回归
- [CNN示例](examples/cnn_example.go):使用卷积、池化等操作构建简单的卷积神经网络
import (
"fmt"
"git.kingecg.top/kingecg/gotensor"
)
运行示例:
```bash
# 基本运算示例
go run examples/basic_operations.go
# 自动微分示例
go run examples/autograd_example.go
# 线性回归示例
go run examples/linear_regression.go
# CNN示例
go run examples/cnn_example.go
func main() {
// 创建模型(例如:简单的线性回归模型)
// 这里可以使用Sequential模型或者自定义模型
// 定义一些示例数据
input, _ := gotensor.NewTensor([]float64{1, 2, 3, 4}, []int{2, 2})
target, _ := gotensor.NewTensor([]float64{5, 6, 7, 8}, []int{2, 2})
// 创建模型参数
weights, _ := gotensor.NewTensor([]float64{0.5, 0.3, 0.2, 0.4}, []int{2, 2})
// 定义模型(这里简化为单个张量,实际中会是更复杂的结构)
// ...
// 定义优化器
optimizer := gotensor.NewSGD([]*gotensor.Tensor{weights}, 0.01)
// 创建训练器
trainer := gotensor.NewTrainer(nil, optimizer) // 需要传入实际模型
// 开始训练
// trainer.Train(trainInputs, trainTargets, epochs, lossFn, true)
fmt.Println("Training example")
}
```
## API 文档
### 创建张量
### 张量操作
- `NewTensor(data []float64, shape []int)` - 创建新的张量
- `NewZeros(shape []int)` - 创建零张量
- `NewOnes(shape []int)` - 创建全一张量
- `NewIdentity(size int)` - 创建单位矩阵
### 张量运算
- `Add(other *Tensor)` - 张量加法
- `Subtract(other *Tensor)` - 张量减法
- `Multiply(other *Tensor)` - 张量逐元素乘法
- `MatMul(other *Tensor)` - 矩阵乘法
- `Scale(factor float64)` - 数乘
### 激活函数
- `Sigmoid()` - Sigmoid激活函数
- `ReLU()` - ReLU激活函数
- `Softmax()` - Softmax激活函数
### 卷积和池化
- `Conv2D(kernel *Tensor, stride, padding int)` - 二维卷积操作
- `MaxPool2D(kernelSize, stride int)` - 二维最大池化
- `AvgPool2D(kernelSize, stride int)` - 二维平均池化
- `NewTensor(data []float64, shape []int)`: 创建新张量
- `Add(other *Tensor)`: 张量加法
- `Subtract(other *Tensor)`: 张量减法
- `Multiply(other *Tensor)`: 张量逐元素乘法
- `MatMul(other *Tensor)`: 矩阵乘法
- `Scale(factor float64)`: 张量数乘
- `Sigmoid()`: Sigmoid激活函数
- `ReLU()`: ReLU激活函数
- `Softmax()`: Softmax函数
- `Backward()`: 反向传播
### 神经网络层
- `Flatten()` - 将多维张量展平为一维
- `CrossEntropy(target *Tensor)` - 交叉熵损失函数
- `MeanSquaredError(target *Tensor)` - 均方误差损失函数
- `Flatten()`: 展平张量
- `CrossEntropy(target *Tensor)`: 交叉熵损失
- `MeanSquaredError(target *Tensor)`: 均方误差损失
### 其他方法
### 模型定义
- `ZeroGrad()` - 将梯度置零
- `Shape()` - 返回张量形状
- `Size()` - 返回张量大小
- `Get(indices ...int)` - 获取指定位置的值
- `Set(value float64, indices ...int)` - 设置指定位置的值
- `Backward()` - 执行反向传播
- `Sequential`: 序列模型
- `Model` 接口: 模型的基本接口
- `SaveModel(model Model, filepath string)`: 保存模型
- `LoadModel(model Model, filepath string)`: 加载模型
## 测试
### 优化器
运行测试:
- `SGD`: 随机梯度下降
- `Adam`: Adam优化算法
```bash
go test
```
### 训练器
- `Trainer`: 训练管理器
- `NewTrainer(model Model, optimizer Optimizer)`: 创建训练器
- `Train(...)`: 执行训练
- `Evaluate(...)`: 评估模型
## 示例
项目包含多个示例:
- `examples/basic_operation`: 基本张量运算示例
- `examples/autograd`: 自动微分示例
- `examples/linear_regression`: 线性回归示例
- `examples/cnn_example.go`: 卷积神经网络示例
## 贡献
欢迎提交 Issue 和 Pull Request 来帮助改进 gotensor
## 许可证
本项目使用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。
本项目使用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。

124
model.go Normal file
View File

@ -0,0 +1,124 @@
package gotensor
import (
"encoding/json"
"os"
)
// Model 模型接口定义
type Model interface {
Forward(inputs *Tensor) (*Tensor, error)
Parameters() []*Tensor // 获取模型所有参数
ZeroGrad() // 将所有参数的梯度清零
}
// Sequential 序列模型,按顺序执行层
type Sequential struct {
Layers []Layer
}
// Layer 接口定义
type Layer interface {
Forward(inputs *Tensor) (*Tensor, error)
Parameters() []*Tensor
ZeroGrad()
}
// Forward 实现前向传播
func (s *Sequential) Forward(inputs *Tensor) (*Tensor, error) {
output := inputs
var err error
for _, layer := range s.Layers {
output, err = layer.Forward(output)
if err != nil {
return nil, err
}
}
return output, nil
}
// Parameters 获取模型所有参数
func (s *Sequential) Parameters() []*Tensor {
var params []*Tensor
for _, layer := range s.Layers {
params = append(params, layer.Parameters()...)
}
return params
}
// ZeroGrad 将所有参数梯度清零
func (s *Sequential) ZeroGrad() {
for _, layer := range s.Layers {
layer.ZeroGrad()
}
}
// SaveModel 保存模型参数到文件
func SaveModel(model Model, filepath string) error {
params := model.Parameters()
paramsData := make([][]float64, len(params))
for i, param := range params {
shape := param.Shape()
size := param.Size()
data := make([]float64, size)
for idx := 0; idx < size; idx++ {
if len(shape) == 1 {
data[idx], _ = param.Data.Get(idx)
} else if len(shape) == 2 {
cols := shape[1]
data[idx], _ = param.Data.Get(idx/cols, idx%cols)
}
}
paramsData[i] = data
}
file, err := os.Create(filepath)
if err != nil {
return err
}
defer file.Close()
return json.NewEncoder(file).Encode(paramsData)
}
// LoadModel 从文件加载模型参数
func LoadModel(model Model, filepath string) error {
file, err := os.Open(filepath)
if err != nil {
return err
}
defer file.Close()
var paramsData [][]float64
err = json.NewDecoder(file).Decode(&paramsData)
if err != nil {
return err
}
params := model.Parameters()
if len(params) != len(paramsData) {
return nil // 参数数量不匹配,返回错误
}
for i, param := range params {
data := paramsData[i]
shape := param.Shape()
if len(shape) == 1 {
for idx, val := range data {
param.Data.Set(val, idx)
}
} else if len(shape) == 2 {
cols := shape[1]
for idx, val := range data {
param.Data.Set(val, idx/cols, idx%cols)
}
}
}
return nil
}

213
optimizer.go Normal file
View File

@ -0,0 +1,213 @@
package gotensor
import "math"
// Optimizer 优化器接口
type Optimizer interface {
Step() // 根据梯度更新参数
ZeroGrad() // 清空所有梯度
}
// SGD 随机梯度下降优化器
type SGD struct {
Parameters []*Tensor
LR float64 // 学习率
}
// NewSGD 创建一个新的SGD优化器
func NewSGD(parameters []*Tensor, lr float64) *SGD {
return &SGD{
Parameters: parameters,
LR: lr,
}
}
// Step 更新参数
func (s *SGD) Step() {
for _, param := range s.Parameters {
// 获取参数的梯度
grad := param.Grad
// 获取参数的形状
shape := param.Data.Shape()
// 更新参数: param = param - lr * grad
if len(shape) == 1 {
for i := 0; i < shape[0]; i++ {
paramVal, _ := param.Data.Get(i)
gradVal, _ := grad.Get(i)
newVal := paramVal - s.LR * gradVal
param.Data.Set(newVal, i)
}
} else if len(shape) == 2 {
rows, cols := shape[0], shape[1]
for i := 0; i < rows; i++ {
for j := 0; j < cols; j++ {
paramVal, _ := param.Data.Get(i, j)
gradVal, _ := grad.Get(i, j)
newVal := paramVal - s.LR * gradVal
param.Data.Set(newVal, i, j)
}
}
}
}
}
// ZeroGrad 清空所有梯度
func (s *SGD) ZeroGrad() {
for _, param := range s.Parameters {
param.ZeroGrad()
}
}
// Adam 优化器
type Adam struct {
Parameters []*Tensor
LR float64 // 学习率
Beta1 float64 // 一阶矩估计的指数衰减率
Beta2 float64 // 二阶矩估计的指数衰减率
Epsilon float64 // 防止除零的小常数
T int // 当前步数
// 一阶矩估计
M []map[string]*Tensor
// 二阶矩估计
V []map[string]*Tensor
}
// NewAdam 创建一个新的Adam优化器
func NewAdam(parameters []*Tensor, lr, beta1, beta2, epsilon float64) *Adam {
adam := &Adam{
Parameters: parameters,
LR: lr,
Beta1: beta1,
Beta2: beta2,
Epsilon: epsilon,
T: 0,
M: make([]map[string]*Tensor, len(parameters)),
V: make([]map[string]*Tensor, len(parameters)),
}
// 初始化M和V
for i := range parameters {
adam.M[i] = make(map[string]*Tensor)
adam.V[i] = make(map[string]*Tensor)
// 创建与参数形状相同的零张量
shape := parameters[i].Shape()
m, _ := NewZeros(shape)
v, _ := NewZeros(shape)
adam.M[i]["tensor"] = m
adam.V[i]["tensor"] = v
}
return adam
}
// Step 更新参数
func (a *Adam) Step() {
a.T++
for i, param := range a.Parameters {
grad := param.Grad
shape := param.Data.Shape()
// 更新一阶矩估计: m = beta1 * m + (1 - beta1) * grad
m := a.M[i]["tensor"]
newMData := make([]float64, param.Size())
if len(shape) == 1 {
for idx := 0; idx < shape[0]; idx++ {
mVal, _ := m.Data.Get(idx)
gradVal, _ := grad.Get(idx)
newMData[idx] = a.Beta1 * mVal + (1 - a.Beta1) * gradVal
}
} else if len(shape) == 2 {
rows, cols := shape[0], shape[1]
for r := 0; r < rows; r++ {
for c := 0; c < cols; c++ {
mVal, _ := m.Data.Get(r, c)
gradVal, _ := grad.Get(r, c)
newMData[r*cols+c] = a.Beta1 * mVal + (1 - a.Beta1) * gradVal
}
}
}
newM, _ := NewTensor(newMData, shape)
a.M[i]["tensor"] = newM
// 更新二阶矩估计: v = beta2 * v + (1 - beta2) * grad^2
v := a.V[i]["tensor"]
newVData := make([]float64, param.Size())
if len(shape) == 1 {
for idx := 0; idx < shape[0]; idx++ {
vVal, _ := v.Data.Get(idx)
gradVal, _ := grad.Get(idx)
newVData[idx] = a.Beta2 * vVal + (1 - a.Beta2) * gradVal * gradVal
}
} else if len(shape) == 2 {
rows, cols := shape[0], shape[1]
for r := 0; r < rows; r++ {
for c := 0; c < cols; c++ {
vVal, _ := v.Data.Get(r, c)
gradVal, _ := grad.Get(r, c)
newVData[r*cols+c] = a.Beta2 * vVal + (1 - a.Beta2) * gradVal * gradVal
}
}
}
newV, _ := NewTensor(newVData, shape)
a.V[i]["tensor"] = newV
// 计算偏差修正的一阶矩估计
mHatData := make([]float64, param.Size())
mHatShape := shape
for idx := 0; idx < param.Size(); idx++ {
mVal, _ := newM.Data.Get(idx)
mHatData[idx] = mVal / (1 - math.Pow(a.Beta1, float64(a.T)))
}
mHat, _ := NewTensor(mHatData, mHatShape)
// 计算偏差修正的二阶矩估计
vHatData := make([]float64, param.Size())
vHatShape := shape
for idx := 0; idx < param.Size(); idx++ {
vVal, _ := newV.Data.Get(idx)
vHatData[idx] = vVal / (1 - math.Pow(a.Beta2, float64(a.T)))
}
vHat, _ := NewTensor(vHatData, vHatShape)
// 更新参数: param = param - lr * m_hat / (sqrt(v_hat) + epsilon)
if len(shape) == 1 {
for idx := 0; idx < shape[0]; idx++ {
paramVal, _ := param.Data.Get(idx)
mHatVal, _ := mHat.Data.Get(idx)
vHatVal, _ := vHat.Data.Get(idx)
updateVal := a.LR * mHatVal / (math.Sqrt(vHatVal) + a.Epsilon)
newVal := paramVal - updateVal
param.Data.Set(newVal, idx)
}
} else if len(shape) == 2 {
rows, cols := shape[0], shape[1]
for r := 0; r < rows; r++ {
for c := 0; c < cols; c++ {
paramVal, _ := param.Data.Get(r, c)
mHatVal, _ := mHat.Data.Get(r, c)
vHatVal, _ := vHat.Data.Get(r, c)
updateVal := a.LR * mHatVal / (math.Sqrt(vHatVal) + a.Epsilon)
newVal := paramVal - updateVal
param.Data.Set(newVal, r, c)
}
}
}
}
}
// ZeroGrad 清空所有梯度
func (a *Adam) ZeroGrad() {
for _, param := range a.Parameters {
param.ZeroGrad()
}
}

92
trainer.go Normal file
View File

@ -0,0 +1,92 @@
package gotensor
import (
"fmt"
)
// Trainer 训练器结构,管理整个训练过程
type Trainer struct {
Model Model
Optimizer Optimizer
}
// NewTrainer 创建新的训练器
func NewTrainer(model Model, optimizer Optimizer) *Trainer {
return &Trainer{
Model: model,
Optimizer: optimizer,
}
}
// TrainEpoch 训练一个epoch
func (t *Trainer) TrainEpoch(inputs []*Tensor, targets []*Tensor, lossFn func(*Tensor, *Tensor) *Tensor) (float64, error) {
var totalLoss float64
for i := 0; i < len(inputs); i++ {
// 前向传播
output, err := t.Model.Forward(inputs[i])
if err != nil {
return 0, err
}
// 计算损失
loss := lossFn(output, targets[i])
lossVal, _ := loss.Data.Get(0)
totalLoss += lossVal
// 反向传播
loss.Backward()
// 更新参数
t.Optimizer.Step()
// 清空梯度
t.Optimizer.ZeroGrad()
}
avgLoss := totalLoss / float64(len(inputs))
return avgLoss, nil
}
// Train 完整的训练过程
func (t *Trainer) Train(
trainInputs []*Tensor,
trainTargets []*Tensor,
epochs int,
lossFn func(*Tensor, *Tensor) *Tensor,
verbose bool,
) error {
for epoch := 0; epoch < epochs; epoch++ {
avgLoss, err := t.TrainEpoch(trainInputs, trainTargets, lossFn)
if err != nil {
return err
}
if verbose {
fmt.Printf("Epoch [%d/%d], Loss: %.6f\n", epoch+1, epochs, avgLoss)
}
}
return nil
}
// Evaluate 评估模型性能
func (t *Trainer) Evaluate(testInputs []*Tensor, testTargets []*Tensor, lossFn func(*Tensor, *Tensor) *Tensor) (float64, error) {
var totalLoss float64
for i := 0; i < len(testInputs); i++ {
// 前向传播
output, err := t.Model.Forward(testInputs[i])
if err != nil {
return 0, err
}
// 计算损失
loss := lossFn(output, testTargets[i])
lossVal, _ := loss.Data.Get(0)
totalLoss += lossVal
}
avgLoss := totalLoss / float64(len(testInputs))
return avgLoss, nil
}