Skip to content

Commit 9eb4e27

Browse files
committed
init commit
0 parents  commit 9eb4e27

10 files changed

Lines changed: 545 additions & 0 deletions

File tree

infCNN/Core.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
class Op:
2+
def __init__(self, name):
3+
self.name = name
4+
pass
5+
6+
def __repr__(self):
7+
return 'Op:' + self.name
8+
9+
def forward(self, x):
10+
pass
11+
12+
def backward(self, grad_y):
13+
pass
14+
15+
def __call__(self, x):
16+
return self.forward(x)
17+
18+
19+
class Layer:
20+
def __init__(self, name):
21+
self.name = name
22+
self.param = None
23+
pass
24+
25+
def __repr__(self):
26+
return 'layer:' + self.name
27+
28+
def forward(self, x):
29+
pass
30+
31+
def backward(self, grad_y):
32+
pass
33+
34+
def __call__(self, x):
35+
return self.forward(x)
36+
37+
38+
39+

infCNN/LayerLib.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
from .Core import Layer
2+
from .im2col import im2col_indices
3+
import numpy as np
4+
5+
class Dense(Layer):
6+
7+
def __init__(self, n_in, n_out):
8+
super().__init__('dense_{}_{}'.format(n_in, n_out))
9+
self.n_in = n_in
10+
self.n_out = n_out
11+
self.K = np.random.randn(n_out, n_in)
12+
self.bias = np.zeros(n_out)
13+
14+
def forward(self, x):
15+
self.x = x
16+
w = self.K
17+
# the dense layer math: y = x*w
18+
y = w.dot(x.T)
19+
return y.T + self.bias.reshape((1, -1))
20+
21+
def load_from_torch(self, weights, bias):
22+
self.K = weights
23+
self.bias = bias
24+
25+
26+
class Conv2d(Layer):
27+
28+
def __init__(self, C_in, C_out, K_s, Stride):
29+
"""
30+
Params:
31+
in channels, out channles, kernel size, stride
32+
"""
33+
super().__init__("conv_{}_{}x{}x{}".format(C_out, C_in, K_s, K_s))
34+
self.c_in = C_in
35+
self.c_out = C_out
36+
self.k_s = K_s
37+
self.stride = Stride
38+
self.pad_size = int((K_s -1)/2)
39+
self.K = np.zeros((C_out, C_in, K_s, K_s))
40+
self.bias = np.zeros(C_out)
41+
42+
def forward(self, X):
43+
out = conv_forward(X, self.K, self.bias, stride=self.stride, padding=self.pad_size)
44+
return out
45+
46+
47+
def load_from_torch(self, weights, bias):
48+
self.K = weights
49+
self.bias = bias
50+
51+
52+
def conv_forward(X, W, b, stride=1, padding=1):
53+
cache = W, b, stride, padding
54+
n_filters, d_filter, h_filter, w_filter = W.shape
55+
n_x, d_x, h_x, w_x = X.shape
56+
h_out = (h_x - h_filter + 2 * padding) / stride + 1
57+
w_out = (w_x - w_filter + 2 * padding) / stride + 1
58+
59+
if not h_out.is_integer() or not w_out.is_integer():
60+
raise Exception('Invalid output dimension!')
61+
62+
h_out, w_out = int(h_out), int(w_out)
63+
64+
X_col = im2col_indices(X, h_filter, w_filter, padding=padding, stride=stride)
65+
W_col = W.reshape(n_filters, -1)
66+
67+
out = W_col @ X_col
68+
out = (out.T + b[:]).T
69+
out = out.reshape(n_filters, h_out, w_out, n_x)
70+
out = out.transpose(3, 0, 1, 2)
71+
72+
cache = (X, W, b, stride, padding, X_col)
73+
74+
return out# , cache
75+
76+
77+
78+
79+
if __name__ == "__main__":
80+
# data = np.arange(16).reshape((1, 1, 4, 4))
81+
data = np.random.randn(2, 3, 200, 200)
82+
83+
84+
import torch.nn as nn
85+
import torch
86+
from time import time
87+
88+
conv_torch = nn.Conv2d(3, 5, 3, 1, 1)
89+
conv = Conv2d(3, 5, 3, 1)
90+
# conv_torch.weight = nn.Parameter(torch.FloatTensor(np.arange(9).reshape(1, 1, 3, 3)))
91+
# conv_torch.bias = nn.Parameter(torch.FloatTensor(np.zeros(1)))
92+
w = conv_torch.weight.detach().numpy()
93+
b = conv_torch.bias.detach().numpy()
94+
95+
start = time()
96+
out_torch = conv_torch(torch.FloatTensor(data)).detach().numpy()
97+
print('torch time:', time() - start)
98+
99+
conv.load_from_torch(w, b)
100+
start = time()
101+
out = conv(data)
102+
print('acw time:', time() - start)
103+
104+
print('torch:', out_torch.shape)
105+
print('acw:', out.shape)
106+
print(np.sum(out-out_torch))
107+
108+
109+
data = np.random.randn(2, 10)
110+
111+
dense_torch = nn.Linear(10, 20)
112+
w = dense_torch.weight.detach().numpy()
113+
b = dense_torch.bias.detach().numpy()
114+
115+
dense = Dense(10, 20)
116+
117+
start = time()
118+
out_torch = dense_torch(torch.FloatTensor(data)).detach().numpy()
119+
print('torch time:', time() - start)
120+
121+
dense.load_from_torch(w, b)
122+
start = time()
123+
out = dense(data)
124+
print('acw time:', time() - start)
125+
print(np.sum(out-out_torch))

infCNN/OpLib.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from .Core import Op
2+
from .im2col import im2col_indices
3+
import numpy as np
4+
5+
6+
class ReLU(Op):
7+
8+
def __init__(self):
9+
super().__init__('ReLU')
10+
11+
def forward(self, x):
12+
self.x = x
13+
return (x>0) * x
14+
15+
16+
class Flatten(Op):
17+
18+
def __init__(self):
19+
super().__init__('Flatten')
20+
21+
def forward(self, x):
22+
n = x.shape[0]
23+
self.x = x
24+
return x.reshape((n, -1))
25+
26+
27+
class Sigmoid(Op):
28+
29+
def __init__(self):
30+
super().__init__('Sigmoid')
31+
32+
def forward(self, x):
33+
self.x = x
34+
return 1/(1 + np.exp(-x))
35+
36+
class Softmax(Op):
37+
def __init__(self):
38+
super().__init__('Sofmax')
39+
40+
def forward(self, X, axis=-1):
41+
eX = np.exp((X.T - np.max(X, axis=axis)).T)
42+
return (eX.T / eX.sum(axis=axis)).T
43+
44+
45+
46+
47+
class MaxPool(Op):
48+
49+
def __init__(self, size=2, stride=2):
50+
super().__init__('MaxPool_{}x{}s{}'.format(size, size, stride))
51+
self.size = size
52+
self.stride = stride
53+
54+
def _pool_forward(self, X, pool_fun, size=2, stride=2):
55+
n, d, h, w = X.shape
56+
h_out = (h - size) / stride + 1
57+
w_out = (w - size) / stride + 1
58+
59+
if not w_out.is_integer() or not h_out.is_integer():
60+
raise Exception('Invalid output dimension!')
61+
62+
h_out, w_out = int(h_out), int(w_out)
63+
64+
X_reshaped = X.reshape(n * d, 1, h, w)
65+
X_col = im2col_indices(X_reshaped, size, size, padding=0, stride=stride)
66+
67+
out, pool_cache = pool_fun(X_col)
68+
69+
out = out.reshape(h_out, w_out, n, d)
70+
out = out.transpose(2, 3, 0, 1)
71+
72+
cache = (X, size, stride, X_col, pool_cache)
73+
74+
return out# , cache
75+
76+
def maxpool(self, X_col):
77+
max_idx = np.argmax(X_col, axis=0)
78+
out = X_col[max_idx, range(max_idx.size)]
79+
return out, max_idx
80+
81+
def forward(self, x):
82+
return self._pool_forward(x, self.maxpool, self.size, self.stride)
83+
84+

infCNN/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .LayerLib import *
2+
from .OpLib import *

infCNN/im2col.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import numpy as np
2+
3+
4+
def get_im2col_indices(x_shape, field_height, field_width, padding=1, stride=1):
5+
# First figure out what the size of the output should be
6+
N, C, H, W = x_shape
7+
assert (H + 2 * padding - field_height) % stride == 0
8+
assert (W + 2 * padding - field_height) % stride == 0
9+
out_height = int((H + 2 * padding - field_height) / stride + 1)
10+
out_width = int((W + 2 * padding - field_width) / stride + 1)
11+
12+
i0 = np.repeat(np.arange(field_height), field_width)
13+
i0 = np.tile(i0, C)
14+
i1 = stride * np.repeat(np.arange(out_height), out_width)
15+
j0 = np.tile(np.arange(field_width), field_height * C)
16+
j1 = stride * np.tile(np.arange(out_width), out_height)
17+
i = i0.reshape(-1, 1) + i1.reshape(1, -1)
18+
j = j0.reshape(-1, 1) + j1.reshape(1, -1)
19+
20+
k = np.repeat(np.arange(C), field_height * field_width).reshape(-1, 1)
21+
22+
return (k.astype(int), i.astype(int), j.astype(int))
23+
24+
25+
def im2col_indices(x, field_height, field_width, padding=1, stride=1):
26+
""" An implementation of im2col based on some fancy indexing """
27+
# Zero-pad the input
28+
p = padding
29+
x_padded = np.pad(x, ((0, 0), (0, 0), (p, p), (p, p)), mode='constant')
30+
31+
k, i, j = get_im2col_indices(x.shape, field_height, field_width, padding, stride)
32+
33+
cols = x_padded[:, k, i, j]
34+
C = x.shape[1]
35+
cols = cols.transpose(1, 2, 0).reshape(field_height * field_width * C, -1)
36+
return cols
37+
38+
39+
def col2im_indices(cols, x_shape, field_height=3, field_width=3, padding=1,
40+
stride=1):
41+
""" An implementation of col2im based on fancy indexing and np.add.at """
42+
N, C, H, W = x_shape
43+
H_padded, W_padded = H + 2 * padding, W + 2 * padding
44+
x_padded = np.zeros((N, C, H_padded, W_padded), dtype=cols.dtype)
45+
k, i, j = get_im2col_indices(x_shape, field_height, field_width, padding, stride)
46+
cols_reshaped = cols.reshape(C * field_height * field_width, -1, N)
47+
cols_reshaped = cols_reshaped.transpose(2, 0, 1)
48+
np.add.at(x_padded, (slice(None), k, i, j), cols_reshaped)
49+
if padding == 0:
50+
return x_padded
51+
return x_padded[:, :, padding:-padding, padding:-padding]

net.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import numpy as np
2+
from scipy.io import loadmat
3+
# infCNN layer
4+
from infCNN import Conv2d, Dense
5+
# infCNN op
6+
from infCNN import ReLU, Flatten, MaxPool, Softmax
7+
from time import time
8+
9+
# op
10+
relu = ReLU()
11+
flatten = Flatten()
12+
maxpool = MaxPool()
13+
softmax = Softmax()
14+
15+
class LeNet():
16+
def __init__(self):
17+
self.conv1 = Conv2d(1, 6, 5, 1)
18+
self.conv2 = Conv2d(6, 16, 5, 1)
19+
self.fc1 = Dense(784, 120)
20+
self.fc2 = Dense(120, 84)
21+
self.fc3 = Dense(84, 10)
22+
23+
self.weights = [self.conv1, self.conv2, self.fc1, self.fc2, self.fc3]
24+
25+
def forward(self, x):
26+
out = relu(self.conv1(x))
27+
# print(out.shape)
28+
out = maxpool(out)
29+
# print(out.shape)
30+
out = relu(self.conv2(out))
31+
# print(out.shape)
32+
out = maxpool(out)
33+
# print(out.shape)
34+
out = flatten(out)
35+
# print(out.shape)
36+
out = relu(self.fc1(out))
37+
# print(out.shape)
38+
out = relu(self.fc2(out))
39+
# print(out.shape)
40+
out = self.fc3(out)
41+
# print(out.shape)
42+
return softmax(out)
43+
44+
def __call__(self, x):
45+
return self.forward(x)
46+
47+
def load_mat(self, fn):
48+
data = loadmat(fn)
49+
w_len = len(data)-4
50+
print('load num of weights:', w_len)
51+
# minus 4 to skip the header
52+
for i in range(w_len//2+1):
53+
w = data[str(2*i)]
54+
b = data[str(2*i+1)]
55+
print(i, w.shape, b.shape)
56+
self.weights[i].load_from_torch(data[str(2*i)], data[str(2*i+1)][0, :])
57+
print('load done!')
58+
59+
if __name__ == "__main__":
60+
net = LeNet()
61+
data = np.ones((1, 1, 28, 28))
62+
net.load_mat('train/lenet.mat')
63+
start = time()
64+
print(net(data))
65+
print('infCNN LeNet time:', time()-start)
66+

readme.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
## infCNN
2+
A pure numpy-based inference framework for CNN. The infCNN supports the inference of model trained on pytorch.
3+
4+
In general, all the elements used in the inference of CNN are divided into ```op``` and ```layer```. ```op``` contains no trainable weights such as "relu, sigmoid, softmax, maxpool, flatten", ```layer``` contains trainable weights such as "conv2d, dense". The ```op``` and ```layer``` implemented are very few now.
5+
6+
#### Inference
7+
8+
Example is shown in ```net.py```, which shows the inference of a LeNet CNN. Weights converted from pytorch model are loaded here. To be noted that ```net.py``` should align with ```train/lenet.py``` since they should have the same model. Just replacing ```nn.Conv2d/Linear``` with ```inferCNN.Conv2d/Dense``` will work.
9+
10+
#### Training on pytorch
11+
12+
The training on pytorch is regular, which can be seen in ```train/``` folder. The weights of CNN are exported to ```.mat``` file.
13+
14+
15+
16+
#### References
17+
18+
* https://github.com/wiseodd/hipsternet
19+
* https://github.com/pytorch/examples/blob/master/mnist/main.py
20+
21+
22+
23+
24+
25+

train/lenet.mat

422 KB
Binary file not shown.

0 commit comments

Comments
 (0)