文章目錄
- 開發前言
- 開發理論
- 圖解理論
- 數據類型
- 數學函數
- 數據節點統一抽象
- 變量數據節點
- 常量數據節點
- 單目運算封裝
- 雙目運算封裝
- 算子節點統一抽象
- 基礎算子
- 加法算子
- 減法算子
- 乘法算子
- 除法算子
- 指數算子
- 對數算子
- 正切算子
- 正弦算子
- 余弦算子
- 數據流圖
- 正向傳播
- 反向傳播
- 正向訓練
- 反向訓練
- 運行示例
- 開發總結
開發前言
正向傳播是指從神經網絡的輸入層開始,通過逐層計算和傳遞,將輸入數據一直傳遞到輸出層。在每一層中,通過對輸入數據進行加權求和并應用激活函數,得到該層的輸出。這個過程可以看作是將輸入數據在網絡中前進(向前傳播),直至得到模型的預測結果。
反向傳播是指根據模型的預測結果和實際標簽之間的差異,從輸出層向輸入層反向計算梯度,并利用梯度來更新網絡參數。
這篇博客我將使用Go語言實現正向傳播和反向傳播,幫助你理解其底層的運轉規律
項目代碼使用純粹的Go語言標準庫實現,不借用任何其它第三方庫。用輪子是生活,造輪子是信仰。
我是醉墨居士,我們現在開始吧🤗
開發理論
一個數學函數,由一系列數據和一系列運算方式構成,我們將數據對應為數據節點,將運算方式對應為算子節點,這樣我們就可以將數學函數轉化為由一系列數據節點和一系列算子節點組成的數據流圖
正向傳遞數據流圖,不斷運算數據,就是正向傳播的過程
反向傳遞數據流圖,不斷累加梯度,就是反向傳播的過程
圖解理論
我畫了兩張圖來表示函數3 * pow(x, 2) + 2 * x + 1的正向傳播和反向傳播的過程
正向傳播圖解
反向傳播圖解
數據類型
data/type.go
package datatype Type interface {~int | ~int32 | ~int64 |~uint | ~uint32 | ~uint64 |~float32 | ~float64
}
數學函數
math/math.go
package mathimport ("dl/node"stmath "math"
)func Pow[T node.Type](a, b T) T {return T(stmath.Pow(float64(a), float64(b)))
}func Ln[T node.Type](a T) T {return T(stmath.Log(float64(a)))
}func Tan[T node.Type](a T) T {return T(stmath.Tan(float64(a)))
}func Sin[T node.Type](a T) T {return T(stmath.Sin(float64(a)))
}func Cos[T node.Type](a T) T {return T(stmath.Cos(float64(a)))
}
數據節點統一抽象
fm/datanode.go
type DataNode[T data.Type] interface {Data()TSetData(T)Grad()TsetGrad(T)preNode() CalNode[T]backNodes() *[]CalNode[T]fm()FlowMap[T]Add(DataNode[T]) DataNode[T]Sub(DataNode[T]) DataNode[T]Mul(DataNode[T]) DataNode[T]Div(DataNode[T]) DataNode[T]Pow(DataNode[T]) DataNode[T]Ln() DataNode[T]Tan() DataNode[T]Sin() DataNode[T]Cos() DataNode[T]
}
變量數據節點
package fmimport "dl/data"type varDataNode[T data.Type] struct {data Tgrad Tprenode CalNode[T]backnodes []CalNode[T]flowmap FlowMap[T]
}func (n *varDataNode[T]) Data() T {return n.data
}func (n *varDataNode[T]) SetData(i T) {n.data = i
}func (n *varDataNode[T]) Grad() T {return n.grad
}func (n *varDataNode[T]) setGrad(i T) {n.grad = i
}func (n *varDataNode[T]) preNode() CalNode[T] {return n.prenode
}func (n *varDataNode[T]) backNodes() *[]CalNode[T] {return &n.backnodes
}func (n *varDataNode[T]) fm() FlowMap[T] {return n.flowmap
}func (n *varDataNode[T]) Add(node DataNode[T]) DataNode[T] {return calTwo(newAdd[T](), n, node)
}func (n *varDataNode[T]) Sub(node DataNode[T]) DataNode[T] {return calTwo(newSub[T](), n, node)
}func (n *varDataNode[T]) Mul(node DataNode[T]) DataNode[T] {return calTwo(newMul[T](), n, node)
}func (n *varDataNode[T]) Div(node DataNode[T]) DataNode[T] {return calTwo(newDiv[T](), n, node)
}func (n *varDataNode[T]) Pow(node DataNode[T]) DataNode[T] {return calTwo(newPow[T](), n, node)
}func (n *varDataNode[T]) Ln() DataNode[T] {return calOne(newLn[T](), n)
}func (n *varDataNode[T]) Tan() DataNode[T] {return calOne(newTan[T](), n)
}func (n *varDataNode[T]) Sin() DataNode[T] {return calOne(newSin[T](), n)
}func (n *varDataNode[T]) Cos() DataNode[T] {return calOne(newCos[T](), n)
}
常量數據節點
type constDataNode[T data.Type] struct {data Tprenode CalNode[T]backnodes []CalNode[T]flowmap FlowMap[T]
}func (n *constDataNode[T]) Data() T {return n.data
}func (n *constDataNode[T]) SetData(i T) {n.data = i
}
func (n *constDataNode[T]) Grad() T {return 0
}func (n *constDataNode[T]) setGrad(T) {}func (n *constDataNode[T]) preNode() CalNode[T] {return n.prenode
}func (n *constDataNode[T]) backNodes() *[]CalNode[T] {return &n.backnodes
}func (n *constDataNode[T]) fm() FlowMap[T] {return n.flowmap
}func (n *constDataNode[T]) Add(node DataNode[T]) DataNode[T] {return calTwo(newAdd[T](), n, node)
}func (n *constDataNode[T]) Sub(node DataNode[T]) DataNode[T] {return calTwo(newSub[T](), n, node)
}func (n *constDataNode[T]) Mul(node DataNode[T]) DataNode[T] {return calTwo(newMul[T](), n, node)
}func (n *constDataNode[T]) Div(node DataNode[T]) DataNode[T] {return calTwo(newDiv[T](), n, node)
}func (n *constDataNode[T]) Pow(node DataNode[T]) DataNode[T] {return calTwo(newPow[T](), n, node)
}func (n *constDataNode[T]) Ln() DataNode[T] {return calOne(newLn[T](), n)
}func (n *constDataNode[T]) Tan() DataNode[T] {return calOne(newTan[T](), n)
}func (n *constDataNode[T]) Sin() DataNode[T] {return calOne(newSin[T](), n)
}func (n *constDataNode[T]) Cos() DataNode[T] {return calOne(newCos[T](), n)
}
單目運算封裝
func calOne[T data.Type](operation CalNode[T], a DataNode[T]) DataNode[T] {*a.fm().calnodes = append(*a.fm().calnodes, operation)*a.backNodes() = append(*a.backNodes(), operation)res := &varDataNode[T]{prenode: operation,flowmap: a.fm(),}*a.fm().datanodes = append(*a.fm().datanodes, res)operation.CalNode().PreNodes = []DataNode[T]{a}operation.CalNode().BackNode = resreturn res
}
雙目運算封裝
func calTwo[T data.Type] (operation CalNode[T], a, b DataNode[T]) DataNode[T] {if a.fm() != b.fm() {return nil}*a.fm().calnodes = append(*a.fm().calnodes, operation)*a.backNodes() = append(*a.backNodes(), operation)*b.backNodes() = append(*b.backNodes(), operation)res := &varDataNode[T]{prenode: operation,flowmap: a.fm(),}*a.fm().datanodes = append(*a.fm().datanodes, res)operation.CalNode().PreNodes = []DataNode[T]{a, b}operation.CalNode().BackNode = resreturn res
}
算子節點統一抽象
fm/calnode.go
type CalNode[T data.Type] interface {CalNode() *BaseCalNode[T]Forward()Backward()
}
基礎算子
type BaseCalNode[T data.Type] struct {PreNodes []DataNode[T]BackNode DataNode[T]
}
加法算子
type AddNode[T data.Type] BaseCalNode[T]func newAdd[T data.Type]() CalNode[T] {basenode := &BaseCalNode[T]{}return (*AddNode[T])(basenode)
}func (n *AddNode[T]) CalNode() *BaseCalNode[T] {return (*BaseCalNode[T])(n)
}func (n *AddNode[T]) Forward() {n.BackNode.SetData(n.CalNode().PreNodes[0].Data() + n.CalNode().PreNodes[1].Data())
}func (n *AddNode[T]) Backward() {// selfgrad + backgradgrad0 := n.PreNodes[0].Grad() + n.BackNode.Grad()// selfgrad + backgradgrad1 := n.PreNodes[1].Grad() + n.BackNode.Grad()n.PreNodes[0].setGrad(grad0)n.PreNodes[1].setGrad(grad1)
}
減法算子
type SubNode[T data.Type] BaseCalNode[T]func newSub[T data.Type]() CalNode[T] {basenode := &BaseCalNode[T]{}return (*SubNode[T])(basenode)
}func (n *SubNode[T]) CalNode() *BaseCalNode[T] {return (*BaseCalNode[T])(n)
}func (n *SubNode[T]) Forward() {n.BackNode.SetData(n.CalNode().PreNodes[0].Data() - n.CalNode().PreNodes[1].Data())
}func (n *SubNode[T]) Backward() {// selfgrad + backgradgrad0 := n.PreNodes[0].Grad() + n.BackNode.Grad()// selfgrad - backgradgrad1 := n.PreNodes[1].Grad() - n.BackNode.Grad()n.PreNodes[0].setGrad(grad0)n.PreNodes[1].setGrad(grad1)
}
乘法算子
type MulNode[T data.Type] BaseCalNode[T]func newMul[T data.Type]() CalNode[T] {basenode := &BaseCalNode[T]{}return (*MulNode[T])(basenode)
}func (n *MulNode[T]) CalNode() *BaseCalNode[T] {return (*BaseCalNode[T])(n)
}func (n *MulNode[T]) Forward() {n.BackNode.SetData(n.CalNode().PreNodes[0].Data() * n.CalNode().PreNodes[1].Data())
}func (n *MulNode[T]) Backward() {a := n.PreNodes[0].Data()b := n.PreNodes[1].Data()backgrad := n.BackNode.Grad()// selfgrad + (backgrad * b)grad0 := n.PreNodes[0].Grad() + (backgrad * b)// selfgrad + (backgrad * a)grad1 := n.PreNodes[1].Grad() + (backgrad * a)n.PreNodes[0].setGrad(grad0)n.PreNodes[1].setGrad(grad1)
}
除法算子
type DivNode[T data.Type] BaseCalNode[T]func newDiv[T data.Type]() CalNode[T] {basenode := &BaseCalNode[T]{}return (*DivNode[T])(basenode)
}func (n *DivNode[T]) CalNode() *BaseCalNode[T] {return (*BaseCalNode[T])(n)
}func (n *DivNode[T]) Forward() {n.BackNode.SetData(n.CalNode().PreNodes[0].Data() / n.CalNode().PreNodes[1].Data())
}func (n *DivNode[T]) Backward() {a := n.PreNodes[0].Data()b := n.PreNodes[1].Data()backgrad := n.BackNode.Grad()// selfgrad + (backgrad / b)grad0 := n.PreNodes[0].Grad() + (backgrad / b)// selfgrad - (backgrad * a / pow(b, 2))grad1 := n.PreNodes[1].Grad() - (backgrad * a / math.Pow(b, 2))n.PreNodes[0].setGrad(grad0)n.PreNodes[1].setGrad(grad1)
}
指數算子
type PowNode[T data.Type] BaseCalNode[T]func newPow[T data.Type]() CalNode[T] {basenode := &BaseCalNode[T]{}return (*PowNode[T])(basenode)
}func (n *PowNode[T]) CalNode() *BaseCalNode[T] {return (*BaseCalNode[T])(n)
}func (n *PowNode[T]) Forward() {n.BackNode.SetData(math.Pow(n.CalNode().PreNodes[0].Data(), n.CalNode().PreNodes[1].Data()))
}func (n *PowNode[T]) Backward() {a := n.PreNodes[0].Data()b := n.PreNodes[1].Data()backgrad := n.BackNode.Grad()// selfgrad + (backgrad * b * pow(a, b-1))grad0 := n.PreNodes[0].Grad() + (backgrad * b * math.Pow(a, b-1))// selfgrad + (backgrad * pow(a, b) * ln(a))grad1 := n.PreNodes[1].Grad() + (backgrad * math.Pow(a, b) * math.Ln(a))n.PreNodes[0].setGrad(grad0)n.PreNodes[1].setGrad(grad1)
}
對數算子
type LnNode[T data.Type] BaseCalNode[T]func newLn[T data.Type]() CalNode[T] {basenode := &BaseCalNode[T]{}return (*LnNode[T])(basenode)
}func (n *LnNode[T]) CalNode() *BaseCalNode[T] {return (*BaseCalNode[T])(n)
}func (n *LnNode[T]) Forward() {n.BackNode.SetData(math.Ln(n.CalNode().PreNodes[0].Data()))
}func (n *LnNode[T]) Backward() {a := n.PreNodes[0].Data()backgrad := n.BackNode.Grad()// selfgrad + (backgrad / a)grad0 := n.PreNodes[0].Grad() + (backgrad / a)n.PreNodes[0].setGrad(grad0)
}
正切算子
type TanNode[T data.Type] BaseCalNode[T]func newTan[T data.Type]() CalNode[T] {basenode := &BaseCalNode[T]{}return (*TanNode[T])(basenode)
}func (n *TanNode[T]) CalNode() *BaseCalNode[T] {return (*BaseCalNode[T])(n)
}func (n *TanNode[T]) Forward() {n.BackNode.SetData(math.Tan(n.CalNode().PreNodes[0].Data()))
}func (n *TanNode[T]) Backward() {a := n.PreNodes[0].Data()backgrad := n.BackNode.Grad()// selfgrad + (backgrad / pow(cos(a), 2))grad0 := n.PreNodes[0].Grad() + (backgrad / math.Pow(math.Cos(a), 2))n.PreNodes[0].setGrad(grad0)
}
正弦算子
type SinNode[T data.Type] BaseCalNode[T]func newSin[T data.Type]() CalNode[T] {basenode := &BaseCalNode[T]{}return (*SinNode[T])(basenode)
}func (n *SinNode[T]) CalNode() *BaseCalNode[T] {return (*BaseCalNode[T])(n)
}func (n *SinNode[T]) Forward() {n.BackNode.SetData(math.Sin(n.CalNode().PreNodes[0].Data()))
}func (n *SinNode[T]) Backward() {a := n.PreNodes[0].Data()backgrad := n.BackNode.Grad()// selfgrad + (backgrad * cos(a))grad0 := n.PreNodes[0].Grad() + (backgrad * math.Cos(a))n.PreNodes[0].setGrad(grad0)
}
余弦算子
type CosNode[T data.Type] BaseCalNode[T]func newCos[T data.Type]() CalNode[T] {basenode := &BaseCalNode[T]{}return (*CosNode[T])(basenode)
}func (n *CosNode[T]) CalNode() *BaseCalNode[T] {return (*BaseCalNode[T])(n)
}func (n *CosNode[T]) Forward() {n.BackNode.SetData(math.Cos(n.CalNode().PreNodes[0].Data()))
}func (n *CosNode[T]) Backward() {a := n.PreNodes[0].Data()backgrad := n.BackNode.Grad()// selfgrad - (backgrad * sin(a))grad0 := n.PreNodes[0].Grad() - (backgrad * math.Sin(a))n.PreNodes[0].setGrad(grad0)
}
數據流圖
fm/flowmap.go
type FlowMap[T data.Type] struct {calnodes *[]CalNode[T]datanodes *[]DataNode[T]
}func NewFlowMap[T data.Type]() *FlowMap[T] {calnodes := make([]CalNode[T], 0)datanods := make([]DataNode[T], 0)return &FlowMap[T]{calnodes: &calnodes,datanodes: &datanods,}
}func (m FlowMap[T]) NewData() DataNode[T] {node := &varDataNode[T]{backnodes: make([]CalNode[T], 0),flowmap: m,}*m.datanodes = append(*m.datanodes, node)return node
}func (m FlowMap[T]) NewConstData(i T) DataNode[T] {return &constDataNode[T]{backnodes: make([]CalNode[T], 0),data: i,flowmap: m,}
}
正向傳播
func (m FlowMap[T]) Forward() {n := len(*m.calnodes)for i := 0; i < n; i++ {(*m.calnodes)[i].Forward()}
}
反向傳播
func (m FlowMap[T]) Backward() {for i := len(*m.datanodes) - 1; i >= 0; i-- {(*m.datanodes)[i].setGrad(0)}n := len(*m.calnodes)-1(*m.calnodes)[n].CalNode().BackNode.setGrad(1)for i := n; i >= 0; i-- {(*m.calnodes)[i].Backward()}
}
正向訓練
func (m FlowMap[T]) ForwardWalk(step T) {for i := len(*m.datanodes) - 1; i >= 0; i-- {(*m.datanodes)[i].SetData((*m.datanodes)[i].Data() + (*m.datanodes)[i].Grad() * step)}
}
反向訓練
func (m FlowMap[T]) BackwardWalk(step T) {for i := len(*m.datanodes) - 1; i >= 0; i-- {(*m.datanodes)[i].SetData((*m.datanodes)[i].Data() - (*m.datanodes)[i].Grad() * step)}
}
運行示例
我們的運行示例使用理論圖解的那個例子: 3 * pow(x, 2) + 2 * x + 1
// 3 * pow(x, 2) + 2 * x + 1
func main() {m := fm.NewFlowMap[float64]()x := m.NewData()three := m.NewConstData(3)two := m.NewConstData(2)one := m.NewConstData(1)res := three.Mul(x.Pow(two)).Add(two.Mul(x)).Add(one)x.SetData(2)m.Forward()m.Backward()// data = 3 * pow(2, 2) + 2 * 2 + 1 = 17// grad = 2 + 6 * x = 14fmt.Println("x=2 -> ", "res.data =", res.Data(), ",", "x.grad =", x.Grad())x.SetData(3)m.Forward()m.Backward()// data = 3 * pow(3, 2) + 2 * 3 + 1 = 34// grad = 2 + 6 * x = 20fmt.Println("x=3 -> ", "res.data =", res.Data(), ",", "x.grad =", x.Grad())x.SetData(4)m.Forward()m.Backward()// data = 3 * pow(4, 2) + 2 * 4 + 1 = 57// grad = 2 + 6 * x = 26fmt.Println("x=4 -> ", "res.data =", res.Data(), ",", "x.grad =", x.Grad())
}
運行結果
開發總結
恭喜你,我們一起使用Go語言完成了深度學習的正向傳播和反向傳播,希望這個項目能讓你有所收獲😊
我的特色就是用最簡單的方式幫助你學會最硬核的知識,一起加油吧??
我是醉墨居士,之前這個賬號改了好幾次名稱,從此之后這個賬號的名稱大概率不會再變動😜
如果有什么錯誤,請你評論區或者私信我指出,讓我們一起進步??
請你多多關注我,開發這個項目,并且整理總結,花費了很多的精力,博客熱度越高,更新速度越快😎