| import numpy as np
|
|
|
|
|
| def randn(*shape):
|
|
|
| return np.random.randn(*shape) * np.sqrt(2.0 / (shape[0] * np.prod(shape[2:])))
|
|
|
| def randn_bias(*shape):
|
| return np.zeros(shape)
|
|
|
| class NumpyUNet:
|
| def __init__(self, in_channels=1, out_classes=2):
|
| """
|
| NumPy๋ก U-Net ๊ฐ์ค์น๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
|
| ์ฌ๊ธฐ์๋ 2-Level U-Net์ ํ๋์ฝ๋ฉํฉ๋๋ค. (์: 64 -> 128 -> 256(๋ฐ๋ฅ) -> 128 -> 64)
|
| """
|
| self.weights = {}
|
|
|
|
|
|
|
| self.weights['enc1_w1'] = randn(64, in_channels, 3, 3)
|
| self.weights['enc1_b1'] = randn_bias(64)
|
| self.weights['enc1_w2'] = randn(64, 64, 3, 3)
|
| self.weights['enc1_b2'] = randn_bias(64)
|
|
|
|
|
| self.weights['enc2_w1'] = randn(128, 64, 3, 3)
|
| self.weights['enc2_b1'] = randn_bias(128)
|
| self.weights['enc2_w2'] = randn(128, 128, 3, 3)
|
| self.weights['enc2_b2'] = randn_bias(128)
|
|
|
|
|
|
|
| self.weights['bottle_w1'] = randn(256, 128, 3, 3)
|
| self.weights['bottle_b1'] = randn_bias(256)
|
| self.weights['bottle_w2'] = randn(256, 256, 3, 3)
|
| self.weights['bottle_b2'] = randn_bias(256)
|
|
|
|
|
|
|
| self.weights['dec1_w1'] = randn(128, 384, 3, 3)
|
| self.weights['dec1_b1'] = randn_bias(128)
|
| self.weights['dec1_w2'] = randn(128, 128, 3, 3)
|
| self.weights['dec1_b2'] = randn_bias(128)
|
|
|
|
|
| self.weights['dec2_w1'] = randn(64, 192, 3, 3)
|
| self.weights['dec2_b1'] = randn_bias(64)
|
| self.weights['dec2_w2'] = randn(64, 64, 3, 3)
|
| self.weights['dec2_b2'] = randn_bias(64)
|
|
|
|
|
| self.weights['final_w'] = randn(out_classes, 64, 1, 1)
|
| self.weights['final_b'] = randn_bias(out_classes)
|
|
|
|
|
|
|
| def _relu(self, x):
|
| return np.maximum(0, x)
|
|
|
| def _conv2d(self, x, kernel, bias, padding=1):
|
| """
|
| NumPy๋ฅผ ์ฌ์ฉํ 'same' 2D ์ปจ๋ณผ๋ฃจ์
(stride=1)
|
| x: (In_C, H, W)
|
| kernel: (Out_C, In_C, K, K)
|
| bias: (Out_C,)
|
| """
|
| in_C, in_H, in_W = x.shape
|
| out_C, _, K, _ = kernel.shape
|
|
|
|
|
| padded_x = np.pad(x, ((0, 0), (padding, padding), (padding, padding)), 'constant')
|
|
|
|
|
| out_H, out_W = in_H, in_W
|
| output = np.zeros((out_C, out_H, out_W))
|
|
|
|
|
| for k in range(out_C):
|
| for i in range(out_H):
|
| for j in range(out_W):
|
|
|
| patch = padded_x[:, i:i+K, j:j+K]
|
|
|
| output[k, i, j] = np.sum(patch * kernel[k]) + bias[k]
|
| return output
|
|
|
| def _max_pool2d(self, x, pool_size=2):
|
| """ 2x2 Max Pooling """
|
| in_C, in_H, in_W = x.shape
|
| out_H = in_H // pool_size
|
| out_W = in_W // pool_size
|
| output = np.zeros((in_C, out_H, out_W))
|
|
|
| for c in range(in_C):
|
| for i in range(out_H):
|
| for j in range(out_W):
|
| patch = x[c, i*pool_size:(i+1)*pool_size, j*pool_size:(j+1)*pool_size]
|
| output[c, i, j] = np.max(patch)
|
| return output
|
|
|
| def _upsample2d(self, x, scale=2):
|
| """
|
| Transposed Conv ๋์ ๊ฐ๋จํ Nearest-neighbor ์
์ํ๋ง ๊ตฌํ
|
| """
|
|
|
| return x.repeat(scale, axis=1).repeat(scale, axis=2)
|
|
|
| def _conv_block(self, x, w1, b1, w2, b2):
|
| """ (3x3 Conv + ReLU) * 2ํ ๋ฐ๋ณต ๋ธ๋ก """
|
| x = self._conv2d(x, w1, b1, padding=1)
|
| x = self._relu(x)
|
| x = self._conv2d(x, w2, b2, padding=1)
|
| x = self._relu(x)
|
| return x
|
|
|
|
|
|
|
| def forward(self, x):
|
| """
|
| U-Net ์ํคํ
์ฒ๋ฅผ ๋ฐ๋ผ ์์ ํ๋ฅผ ์ํํฉ๋๋ค.
|
| x: (In_C, H, W)
|
| """
|
| w = self.weights
|
| skip_connections = []
|
|
|
| print(f"Input: \t\t{x.shape}")
|
|
|
|
|
|
|
| e1 = self._conv_block(x, w['enc1_w1'], w['enc1_b1'], w['enc1_w2'], w['enc1_b2'])
|
| p1 = self._max_pool2d(e1)
|
| skip_connections.append(e1)
|
| print(f"Encoder 1: \t{e1.shape} -> Pool: {p1.shape}")
|
|
|
|
|
| e2 = self._conv_block(p1, w['enc2_w1'], w['enc2_b1'], w['enc2_w2'], w['enc2_b2'])
|
| p2 = self._max_pool2d(e2)
|
| skip_connections.append(e2)
|
| print(f"Encoder 2: \t{e2.shape} -> Pool: {p2.shape}")
|
|
|
|
|
| b = self._conv_block(p2, w['bottle_w1'], w['bottle_b1'], w['bottle_w2'], w['bottle_b2'])
|
| print(f"Bottleneck: \t{b.shape}")
|
|
|
|
|
| skip_connections = skip_connections[::-1]
|
|
|
|
|
| u1 = self._upsample2d(b)
|
| s1 = skip_connections[0]
|
| c1 = np.concatenate((u1, s1), axis=0)
|
| d1 = self._conv_block(c1, w['dec1_w1'], w['dec1_b1'], w['dec1_w2'], w['dec1_b2'])
|
| print(f"Decoder 1: \tUp: {u1.shape} + Skip: {s1.shape} = Concat: {c1.shape} -> Block: {d1.shape}")
|
|
|
|
|
| u2 = self._upsample2d(d1)
|
| s2 = skip_connections[1]
|
| c2 = np.concatenate((u2, s2), axis=0)
|
| d2 = self._conv_block(c2, w['dec2_w1'], w['dec2_b1'], w['dec2_w2'], w['dec2_b2'])
|
| print(f"Decoder 2: \tUp: {u2.shape} + Skip: {s2.shape} = Concat: {c2.shape} -> Block: {d2.shape}")
|
|
|
|
|
|
|
| output = self._conv2d(d2, w['final_w'], w['final_b'], padding=0)
|
| print(f"Final 1x1 Conv: {output.shape}")
|
|
|
| return output
|
|
|
|
|
| if __name__ == "__main__":
|
|
|
|
|
| dummy_image = np.random.randn(1, 32, 32)
|
|
|
|
|
| model = NumpyUNet(in_channels=1, out_classes=2)
|
|
|
| print("--- U-Net Forward Pass Start ---")
|
|
|
|
|
| output_map = model.forward(dummy_image)
|
|
|
| print("--- U-Net Forward Pass End ---")
|
| print(f"\n์ต์ข
์
๋ ฅ ์ด๋ฏธ์ง Shape: {dummy_image.shape}")
|
| print(f"์ต์ข
์ถ๋ ฅ ๋งต Shape: {output_map.shape}")
|
|
|
|
|
| assert dummy_image.shape[1:] == output_map.shape[1:]
|
| assert output_map.shape[0] == 2 |