Skip to content

Commit

Permalink
增加卷积和池化
Browse files Browse the repository at this point in the history
  • Loading branch information
zhaoyi committed Jul 9, 2023
1 parent 139f15c commit 4da2d5e
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 2 deletions.
9 changes: 9 additions & 0 deletions erud/cg/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,4 +392,13 @@ def __str__ (self) :
p = p.bNextEdge

str += "\n"
return str

def tableTimespend(self) :
str = "\n"
str += "| code \t | fprop last \t | fprop total \t | bprop last \t | bprop total \t|\n"
for node in self.__nodes :
str += "| %s \t | %.3f \t | %.3f \t | %.3f \t | %.3f \t |" %(node.code, node.ftimespend, node.ftimetotal, node.btimespend, node.btimetotal)
str += "\n"

return str
49 changes: 47 additions & 2 deletions erud/cg/node.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import erud.cg.edge as edge
from erud.cg.payload import payload
import time

class ComputationNode :
#### 数据域
Expand All @@ -8,13 +9,39 @@ class ComputationNode :
#### 代码
__code : str = ''

#### debuger
# 前向传播上一次计算花费的时间
__ftimespend : int = 0
# 前向传播计算花费的总时间
__ftimetotal : int = 0
# 反向传播上一次计算花费的时间
__btimespend : int = 0
# 反向传播计算花费的总时间
__btimetotal : int = 0

@property
def data (self) :
return self.__data

@property
def code (self) :
return self.__code

@property
def ftimespend(self) :
return self.__ftimespend

@property
def ftimetotal(self) :
return self.__ftimetotal

@property
def btimespend(self) :
return self.__btimespend

@property
def btimetotal(self) :
return self.__btimetotal

#### 前向传播路径
fFirstEdge : "edge.ComputationEdge" = None
Expand All @@ -24,11 +51,29 @@ def code (self) :

# 计算前向传播
def fprop(self, *args) :
return self.__data.fprop(*args)
tic = time.process_time()

res = self.__data.fprop(*args)

toc = time.process_time()
spt = (toc - tic) * 1000
self.__ftimespend = spt
self.__ftimetotal += spt

return res

# 计算反向传播
def bprop(self, args) :
return self.__data.bprop(args)
tic = time.process_time()

res = self.__data.bprop(args)

toc = time.process_time()
spt = (toc - tic) * 1000
self.__btimespend = spt
self.__btimetotal += spt

return res

def __init__ (self, pl : payload = None, code : str = None) :
self.__data = pl
Expand Down
6 changes: 6 additions & 0 deletions erud/nous.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
from erud.opts.dropout import dropout
from erud.opts.batchnorm import batchnorm
from erud.opts.softmax_cross_entropy import softmax_cross_entropy
from erud.opts.conv2d import conv2d
from erud.opts.pooling_max_2d import pooling_max_2d

from erud.tensor.var import var
from erud.tensor.rest import rest
import numpy as np
Expand Down Expand Up @@ -45,6 +48,9 @@ class nous :
'tanh' : tanh,
'dropout' : dropout,
'batchnorm' : batchnorm,
'conv2d' : conv2d,
'pooling_max_2d' : pooling_max_2d,

'cross_entropy' : cross_entropy,
'softmax_cross_entropy' : softmax_cross_entropy,
'cost' : cost,
Expand Down
122 changes: 122 additions & 0 deletions erud/opts/conv2d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from erud.cg.payload import payload
import numpy as np

# 多样本二维图片带通道(四维数据)的卷积
class conv2d(payload) :

# 步长
__stride : int
# 扩展边距,通常用来填充零
__padding: int

# padding后的样本
__px : np.ndarray = None
# 卷积核
__w : np.ndarray = None
# 结果
__z : np.ndarray = None

def __init__(self, stride = 1, padding = 0) :
self.__stride = stride
self.__padding = padding

# 输入样本需固定为shape=(s, m1, n1, c1)
# s 为样本数
# m1 为样本宽度
# n1 为样本高度
# c1 为样本通道数
#
# 卷积核参数需固定为shape=(p, q, c1, c2)
# p 为卷积核宽度
# q 为卷积核高度,通常与宽度相等
# c1 为输入(样本)通道数
# c2 为输出通道数
#
# 卷积计算返回值固定为shape=(s, m2, n2, c2)
# s 为样本数,不变
# m2 为输出宽度,m2 = floor((m1 + 2 * padding - p) / stride + 1)
# m1 为输入宽度
# padding 为增加的边距,通常根据valid、same、full取不同的值
# p 为卷积核宽度
# stride 为步长
# n2 为输出高度,n2 = floor((n1 + 2 * padding - q) / stride + 1)
# n1 为输入高度
# padding 为增加的边距,通常根据valid、same、full取不同的值
# q 为卷积核高度
# stride 为步长
# c2 为输出通道数,或理解为三维卷积核的个数
def fprop(self, x : np.ndarray, w : np.ndarray) -> np.ndarray :
self.__w = w

(s, m1, n1, c1) = x.shape
(p, q, c1, c2) = w.shape
_padding = self.__padding
_stride = self.__stride

m2 = np.floor((m1 + (2 * _padding) - p) / _stride + 1)
n2 = np.floor((n1 + (2 * _padding) - q) / _stride + 1)

z = np.zeros((s, m2, n2, c2))

# padding
px = np.pad(x, ((0, 0), (_padding, _padding), (_padding, _padding), (0, 0)), "constant")

for si in range(s) :
for m2i in range(m2) :
for n2i in range(n2) :
for c2i in range(c2) :
# 滑动窗口中的矩阵乘法
# 维度: (1, p, q, c1) * (p, q, c1, 1) -> sum -> (1, 1, 1, 1)
z[si, m2i, n2i, c2i] = np.sum( px[si, (_stride * m2i):(_stride * m2i + p), (_stride * n2i):(_stride * n2i + q), :] * w[:, :, :, c2i] )

self.__z = z
self.__px = px

return z

# 我很难跟你说清反向传播逻辑,因为我也写不出数学公式,只能写出工程代码
def bprop(self, dz : np.ndarray) -> list[np.ndarray] :
_px = self.__px
_z = self.__z
_w = self.__w

_stride = self.__stride
_padding = self.__padding

(s, m2, n2, c2) = _z.shape
(p, q, c1, c2) = _w.shape


# dx = np.zeros_like(_x)
# for si in range (s) :
# for m2i in range(m2) :
# for n2i in range(n2) :
# # 维度: (p, q, c1, c2) * (1, 1, 1, c2) -> sum(-1) -> (p, q, c1, 1)
# dx[si, m2i:(m2i + p), n2i:(n2i + q), :] += np.sum( _w[:, :, :, :] * dz[si, m2i, n2i, :], axis = -1)


# dw = np.zeros_like(_w)
# for c2i in range (c2) :
# for m2i in range(m2) :
# for n2i in range(n2) :
# # 维度: (s, p, q, c1) * (s, 1, 1, 1) -> sum(0) -> (1, p, q, c1)
# dw[:, :, :, c2i] += np.sum( _x[:, m2i:(m2i + p), n2i:(n2i + q), :] * dz[:, m2i, n2i, c2i], axis = 0 )


dpx = np.zeros_like(_px)
dw = np.zeros_like(_w)
for si in range (s) :
for m2i in range(m2) :
for n2i in range(n2) :
for c2i in range(c2) :
dpx[si, (m2i * _stride):(m2i * _stride + p), (n2i * _stride):(n2i * _stride + q), :] += (_w[:, :, :, c2i] * dz[si, m2i, n2i, c2i])
dw[:, :, :, c2i] += (_px[si, (m2i * _stride):(m2i * _stride + p), (n2i * _stride):(n2i * _stride + q), :] * dz[si, m2i, n2i, c2i])

dx = dpx[:, _padding : -_padding, _padding : -_padding, :]

return [dx, dw]





58 changes: 58 additions & 0 deletions erud/opts/pooling_max_2d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from erud.cg.payload import payload
import numpy as np

class pooling_max_2d(payload) :

__stride : int
__p : int
__q : int

__x : np.ndarray

def __init__(self, stride : int = 1, p : int = 1, q : int = 1) :
self.__stride = stride
self.__p = p
self.__q = q

def fprop(self, x : np.ndarray) -> np.ndarray :
self.__x = x

_stride = self.__stride
_p = self.__p
_q = self.__q
(s, m1, n1, c1) = x.shape


m2 = np.floor((m1 - _p) / _stride + 1)
n2 = np.floor((n1 - _q) / _stride + 1)

z = np.zeros((s, m2, n2, c1))

for si in range(s) :
for m2i in range(m2) :
for n2i in range(n2) :
for c1i in range(c1) :
z[si, m2i, n2i, c1i] = np.max(x[si, (_stride * m2i):(_stride * m2i + _p), (_stride * n2i):(_stride * n2i + _q), c1i])

return z

def bprop(self, dz : np.ndarray) -> list[np.ndarray] :
_x = self.__x
_stride = self.__stride
_p = self.__p
_q = self.__q

(s, m2, n2, c1) = dz.shape

dx = np.zeros_like(_x)

for si in range(s) :
for m2i in range(m2) :
for n2i in range(n2) :
for c1i in range(c1) :
_slice = _x[si, (m2i * _stride):(m2i * _stride + _p), (n2i * _stride):(n2i * _stride + _q), c1i]
dx[si, (m2i * _stride):(m2i * _stride + _p), (n2i * _stride):(n2i * _stride + _q), c1i] += (dz[si, m2i, n2i, c1i] * (np.max(_slice) == _slice))

return [dx]


2 changes: 2 additions & 0 deletions test/test_softmax_multiple_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ def test_multiple_classification () :
print("Cost after iteration {}: {}".format(i, g.getData('J')))
print("Cost after iteration {}: {}".format(num_iterations, g.getData('J')))

print(g.tableTimespend())

# 测试
gtest = erud.nous(
'''
Expand Down

0 comments on commit 4da2d5e

Please sign in to comment.