diff --git a/README.md b/README.md index 7d6b63e..70cd87c 100644 --- a/README.md +++ b/README.md @@ -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) 文件。 \ No newline at end of file +本项目使用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。 diff --git a/model.go b/model.go new file mode 100644 index 0000000..b11cec1 --- /dev/null +++ b/model.go @@ -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(¶msData) + 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 +} diff --git a/optimizer.go b/optimizer.go new file mode 100644 index 0000000..15451ad --- /dev/null +++ b/optimizer.go @@ -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() + } +} \ No newline at end of file diff --git a/trainer.go b/trainer.go new file mode 100644 index 0000000..7f6cd21 --- /dev/null +++ b/trainer.go @@ -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 +} \ No newline at end of file