From 190b3d50d3c1da1273d218b995a32973e8394df4 Mon Sep 17 00:00:00 2001 From: SnowyWind Date: Sun, 2 May 2021 23:43:40 +0800 Subject: [PATCH] try again --- .../submission/18307130074/img/basic_test.png | Bin 0 -> 23528 bytes .../img/get_torch_initialization.png | Bin 0 -> 23162 bytes .../submission/18307130074/img/mini_batch.png | Bin 0 -> 23039 bytes .../submission/18307130074/img/research.png | Bin 0 -> 18194 bytes .../submission/18307130074/numpy_fnn.py | 171 ++++++++ .../submission/18307130074/numpy_mnist.py | 83 ++++ assignment-2/submission/18307130074/readme.md | 371 ++++++++++++++++++ .../submission/18307130074/tester_demo.py | 183 +++++++++ .../submission/18307130074/torch_mnist.py | 73 ++++ assignment-2/submission/18307130074/utils.py | 71 ++++ 10 files changed, 952 insertions(+) create mode 100644 assignment-2/submission/18307130074/img/basic_test.png create mode 100644 assignment-2/submission/18307130074/img/get_torch_initialization.png create mode 100644 assignment-2/submission/18307130074/img/mini_batch.png create mode 100644 assignment-2/submission/18307130074/img/research.png create mode 100644 assignment-2/submission/18307130074/numpy_fnn.py create mode 100644 assignment-2/submission/18307130074/numpy_mnist.py create mode 100644 assignment-2/submission/18307130074/readme.md create mode 100644 assignment-2/submission/18307130074/tester_demo.py create mode 100644 assignment-2/submission/18307130074/torch_mnist.py create mode 100644 assignment-2/submission/18307130074/utils.py diff --git a/assignment-2/submission/18307130074/img/basic_test.png b/assignment-2/submission/18307130074/img/basic_test.png new file mode 100644 index 0000000000000000000000000000000000000000..09a77bfd30f6ba7bf950b8413637d9fe163ebe53 GIT binary patch literal 23528 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%0D81YJ_llBY;?%eZIy8B`1stZ|c;g|Hg# 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

5c>2+9Mcskrq4qlU5f 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 literal 0 HcmV?d00001 diff --git a/assignment-2/submission/18307130074/img/research.png b/assignment-2/submission/18307130074/img/research.png new file mode 100644 index 0000000000000000000000000000000000000000..77f8f897fd02871f3a93a3636bc374c0232b6341 GIT binary patch literal 18194 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 AFaQ7m literal 0 HcmV?d00001 diff --git a/assignment-2/submission/18307130074/numpy_fnn.py b/assignment-2/submission/18307130074/numpy_fnn.py new file mode 100644 index 0000000..1182068 --- /dev/null +++ b/assignment-2/submission/18307130074/numpy_fnn.py @@ -0,0 +1,171 @@ +import numpy as np + + +class NumpyOp: + + def __init__(self): + self.memory = {} + self.epsilon = 1e-12 + + +class Matmul(NumpyOp): + + def forward(self, x, W): + """ + x: shape(N, d) + w: shape(d, d') + """ + self.memory['x'] = x + self.memory['W'] = W + h = np.matmul(x, W) + return h + + def backward(self, grad_y): + """ + grad_y: shape(N, d') + """ + + grad_x = np.matmul(grad_y, self.memory['W'].T) + grad_W = np.matmul(self.memory['x'].T, grad_y) + + return grad_x, grad_W + + +class Relu(NumpyOp): + + def forward(self, x): + self.memory['x'] = x + return np.where(x > 0, x, np.zeros_like(x)) + + def backward(self, grad_y): + """ + grad_y: same shape as x + """ + + grad_x = np.where(self.memory['x'] > 0, 1, 0) * grad_y + + return grad_x + + +class Log(NumpyOp): + + def forward(self, x): + """ + x: shape(N, c) + """ + + out = np.log(x + self.epsilon) + self.memory['x'] = x + + return out + + def backward(self, grad_y): + """ + grad_y: same shape as x + """ + + grad_x = np.reciprocal(self.memory['x'] + self.epsilon) * grad_y + + return grad_x + + +class Softmax(NumpyOp): + """ + softmax over last dimension + """ + + def forward(self, x): + """ + x: shape(N, c) + """ + + r = np.exp(x) + s = np.sum(r, axis=1).reshape(-1, 1) + out = (r / s).astype('float64') + self.memory['out'] = out + + return out + + def backward(self, grad_y): + """ + grad_y: same shape as x + """ + + out = self.memory['out'] + Matrix = [] + for i in range(out.shape[0]): + row = out[i] + Jacob = np.diag(row) - np.outer(row, row) + Matrix.append(Jacob) + Matrix = np.array(Matrix) + grad_x = np.squeeze(np.matmul(grad_y[:,np.newaxis,:], Matrix), axis=1) + + return grad_x + + +class NumpyLoss: + + def __init__(self): + self.target = None + + def get_loss(self, pred, target): + self.target = target + return (-pred * target).sum(axis=1).mean() + + def backward(self): + return -self.target / self.target.shape[0] + + +class NumpyModel: + def __init__(self): + self.W1 = np.random.normal(size=(28 * 28, 256)) + self.W2 = np.random.normal(size=(256, 64)) + self.W3 = np.random.normal(size=(64, 10)) + + # 以下算子会在 forward 和 backward 中使用 + self.matmul_1 = Matmul() + self.relu_1 = Relu() + self.matmul_2 = Matmul() + self.relu_2 = Relu() + self.matmul_3 = Matmul() + self.softmax = Softmax() + self.log = Log() + + # 以下变量需要在 backward 中更新。 softmax_grad, log_grad 等为算子反向传播的梯度( loss 关于算子输入的偏导) + self.x1_grad, self.W1_grad = None, None + self.relu_1_grad = None + self.x2_grad, self.W2_grad = None, None + self.relu_2_grad = None + self.x3_grad, self.W3_grad = None, None + self.softmax_grad = None + self.log_grad = None + + def forward(self, x): + x = x.reshape(-1, 28 * 28) + + z1 = self.matmul_1.forward(x, self.W1) + x2 = self.relu_1.forward(z1) + z2 = self.matmul_2.forward(x2, self.W2) + x3 = self.relu_2.forward(z2) + z3 = self.matmul_3.forward(x3, self.W3) + out = self.softmax.forward(z3) + x = self.log.forward(out) + + return x + + def backward(self, y): + + self.log_grad = self.log.backward(y) + self.softmax_grad = self.softmax.backward(self.log_grad) + self.x3_grad, self.W3_grad = self.matmul_3.backward(self.softmax_grad) + self.relu_2_grad = self.relu_2.backward(self.x3_grad) + self.x2_grad, self.W2_grad = self.matmul_2.backward(self.relu_2_grad) + self.relu_1_grad = self.relu_1.backward(self.x2_grad) + self.x1_grad, self.W1_grad = self.matmul_1.backward(self.relu_1_grad) + + pass + + def optimize(self, learning_rate): + self.W1 -= learning_rate * self.W1_grad + self.W2 -= learning_rate * self.W2_grad + self.W3 -= learning_rate * self.W3_grad diff --git a/assignment-2/submission/18307130074/numpy_mnist.py b/assignment-2/submission/18307130074/numpy_mnist.py new file mode 100644 index 0000000..689df44 --- /dev/null +++ b/assignment-2/submission/18307130074/numpy_mnist.py @@ -0,0 +1,83 @@ +import numpy as np +from numpy_fnn import NumpyModel, NumpyLoss + +import os +os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" + +# from utils import download_mnist, batch, mini_batch, get_torch_initialization, plot_curve, one_hot +from utils import download_mnist, batch, plot_curve, one_hot + +def mini_batch(dataset, batch_size=128): + + data = [] + label = [] + + for each in dataset: + data.append(np.array(each[0])) + label.append(each[1]) + + label = np.array(label) + data = np.array(data) + + num = data.shape[0] + i = np.arange(num) + np.random.shuffle(i) + + label_ = label[i] + data_ = data[i] + + res = [] + for id in range(num // batch_size): + batch_data = data_[id * batch_size: (id + 1) * batch_size] + batch_label = label_[id * batch_size: (id + 1) * batch_size] + res.append((batch_data, batch_label)) + + return res + + +def get_torch_initialization(): + + def parameters(in_features, out_features, param = 5**0.5): + bound = (6 / (1 + param * param) / in_features ) ** 0.5 + return np.random.uniform(-bound, bound, (in_features, out_features)) + + W1 = parameters(28 * 28, 256) + W2 = parameters(256, 64) + W3 = parameters(64, 10) + return W1, W2, W3 + + +def numpy_run(): + train_dataset, test_dataset = download_mnist() + + model = NumpyModel() + numpy_loss = NumpyLoss() + model.W1, model.W2, model.W3 = get_torch_initialization() + + train_loss = [] + + epoch_number = 3 + learning_rate = 0.1 + + for epoch in range(epoch_number): + + for x, y in mini_batch(train_dataset): + y = one_hot(y) + + y_pred = model.forward(x) + loss = numpy_loss.get_loss(y_pred, y) + + model.backward(numpy_loss.backward()) + model.optimize(learning_rate) + + train_loss.append(loss.item()) + + x, y = batch(test_dataset)[0] + accuracy = np.mean((model.forward(x).argmax(axis=1) == y)) + print('[{}] Accuracy: {:.4f}'.format(epoch, accuracy)) + + plot_curve(train_loss) + + +if __name__ == "__main__": + numpy_run() diff --git a/assignment-2/submission/18307130074/readme.md b/assignment-2/submission/18307130074/readme.md new file mode 100644 index 0000000..71c3f6f --- /dev/null +++ b/assignment-2/submission/18307130074/readme.md @@ -0,0 +1,371 @@ +# Assignment2:FNN + +18307130074 姜博天(选题1) + +## 1. numpy_fnn.py算子反向传播推导 + +### 1. Matmul + +首先正向计算时有矩阵 x(N * d') * W(d' * d) = h(N * d) + +假设反向传播中输入的梯度为y(N * d) + +则有 +$$ +y_{ij} = \frac{\partial Loss}{\partial h_{ij}} +$$ +计算W的梯度 +$$ +\begin{aligned} + \frac{\partial Loss}{\partial W_{pq}} + &=\sum_{i \leqslant N\\ j\leqslant d} \frac{\partial Loss}{\partial h_{ij}} \times \frac{\partial h_{ij}}{\partial W_{pq}}\\\\ + &=\sum_{i \leqslant N} \ y_{iq} \times \frac{\partial h_{iq}}{\partial W_{pq}}\\\\ + &=\sum_{i \leqslant N} \ y_{iq} \times \ x_{ip}\\\\ + &=\sum_{i \leqslant N} \ x_{pi}^T \times y_{iq} +\end{aligned} +$$ +所以有 +$$ +\frac{\partial Loss}{\partial W} = x^T \times y +$$ +同理可得P的梯度 +$$ +\frac{\partial Loss}{\partial x} = y \times W^T +$$ + +### 2. RELU + +正向计算有RELU(x(N * d)) = out(N * d) +$$ +out = \begin{cases} + 0& where\ x \leq 0 \\\\ + x& where\ x > 0 + \end{cases} +$$ +计算x的梯度 +$$ +\begin{aligned} + \frac{\partial Loss}{\partial x_{ij}} + &=\frac{\partial Loss}{\partial out_{ij}} \times \frac{d_{out_{ij}}}{d_{x_{ij}}}\\\\ + &=y \times \frac{d_{out_{ij}}}{d_{x_{ij}}} +\end{aligned} +$$ +其中 +$$ +\frac{d_{out_{ij}}}{d_{x_{ij}}} = \begin{cases} + 0& x_{ij} \leq 0 \\\\ + 1& x_{ij} > 0 + \end{cases} +$$ +所以 +$$ +\frac{\partial Loss}{\partial x} = y \times x'\\\ +其中x' = \begin{cases} + 0& where\ x \leq 0 \\\\ + 1& where\ x > 0 + \end{cases} +$$ + + +### 3. Log + +正向计算有Log(x(N * d)) = out(N * d) +$$ +out_{ij} = log\ x_{ij} +$$ +计算x的梯度 +$$ +\begin{aligned} + \frac{\partial Loss}{\partial x_{ij}} + &=\frac{\partial Loss}{\partial out_{ij}} \times \frac{d_{out_{ij}}}{d_{x_{ij}}}\\\\ + &=y \times \frac{d_{out_{ij}}}{d_{x_{ij}}} +\end{aligned} +$$ +其中 +$$ +\frac{d_{out_{ij}}}{d_{x_{ij}}} = \frac {1}{x_{ij}} +$$ +所以 +$$ +\frac{\partial Loss}{\partial x} = y \times x'\\\ +其中x' = \frac{1}{x} +$$ + + +### 4. Softmax + +正向计算有Softmax(x(N * d)) = out(N * d) +$$ +out_{ij} = \frac{e^{ij}}{\sum_{k=1}^d e^{x_{ik}}} +$$ +可见每一行的out只与所在行有关,所以我们不妨先只考虑一行,然后推广到整个矩阵。 +$$ +\begin{aligned} + \frac{\partial Loss}{\partial x_i} + &=\sum_{j=1}^d \frac{\partial Loss}{\partial out_j} \times \frac{\partial out_j}{\partial x_i}\\\\ + &=y \times \frac{\partial out_i}{\partial x_i} +\end{aligned} +$$ +并且有 +$$ +\frac{\partial out_i}{\partial x_j} = + \begin{cases} + out_i \times (1 - out_i) & i = j\\\\ + -out_i \times out_j & i \neq j + \end{cases} +$$ + + + +$$ +softmax([x_1, x_2, ...,x_d]) = [out_1, out_2, ..., out_d]\\\\ +$$ + + + +$$ +\frac{\partial Loss}{\partial x} = [\frac{\partial Loss}{\partial out_1}, \frac{\partial Loss}{\partial out_2},...,\frac{\partial Loss}{\partial out_d}] \times +\begin{matrix} +\frac{\partial out_1}{\partial x_1} & \frac{\partial out_1}{\partial x_2} & ... & \frac{\partial out_1}{\partial x_d}\\\\ +\frac{\partial out_2}{\partial x_1} & \frac{\partial out_2}{\partial x_2} & ... & \frac{\partial out_2}{\partial x_d}\\\\ +... & ... & ... & ...\\\\ +\frac{\partial out_d}{\partial x_1} & \frac{\partial out_d}{\partial x_2} & ... & \frac{\partial out_d}{\partial x_d}\\\\ +\end{matrix} +$$ +可以看到[1 * d]维的向量需要乘一个[d * d]的矩阵才可得到梯度 + +那么对于[N * d]维的矩阵则可以通过添加维看成一个[N * 1 * d]的多维矩阵,需要乘一个[N * d * d]的矩阵才能得到梯度,然而得到的矩阵也为[N * 1 * d],需要通过numpy压缩成[N * d]维的矩阵作为结果返回 + +代码如下: + +```python +out = self.memory['out'] +Matrix = [] +for i in range(out.shape[0]): + row = out[i] + Jacob = np.diag(row) - np.outer(row, row) + Matrix.append(Jacob) +Matrix = np.array(Matrix) +grad_x = np.squeeze(np.matmul(grad_y[:,np.newaxis,:], Matrix), axis=1) + +return grad_x +``` + + + +## 2. 模型训练与测试 + +### 1. 利用utils.py中已经实现的函数来做测试 + +基础测试 + +| epoch | accuracy | +| ----- | -------- | +| 0 | 0.9409 | +| 1 | 0.9653 | +| 2 | 0.9694 | + +![basic_test](img/basic_test.png) + +**考虑更改epoch数量和learning_rate做对比实验** + +| epoch | accuracy with learning_rate = 0.05 | accuracy with learning_rate = 0.1 | accuracy with learning_rate = 0.2 | +| ----- | ---------------------------------- | --------------------------------- | --------------------------------- | +| 0 | 0.9266 | 0.9468 | 0.9602 | +| 1 | 0.9482 | 0.9638 | 0.9708 | +| 2 | 0.9571 | 0.9701 | 0.9752 | +| 4 | 0.9692 | 0.9755 | 0.9785 | +| 9 | 0.9787 | 0.9792 | 0.9811 | +| 14 | 0.9793 | 0.9811 | 0.9835 | +| 19 | 0.9811 | 0.9811 | 0.9835 | +| 29 | 0.9820 | 0.9806 | 0.9838 | +| 39 | 0.9823 | 0.9809 | 0.9832 | +| 49 | 0.9822 | 0.9809 | 0.9835 | +| 69 | 0.9826 | 0.9810 | 0.9836 | +| 99 | 0.9826 | 0.9811 | 0.9836 | + +由于数据量较大,这里只展示部分数据。实际上不同的learning rate以及其对应的最高accuracy和accuracy所对应的epoch如下图所示。 + +| learning_rate | epoch | accuracy | +| ------------- | ----- | -------- | +| 0.05 | 44 | 0.9827 | +| 0.1 | 23 | 0.9815 | +| 0.2 | 16 | 0.9839 | + +可以看到随着learning_rate的提升,第一次得到最大accuracy的epoch数逐渐降低,前几次的accuracy也较高。此外,可以发现无论learning_rate为何值,accuracy都会存在持续的抖动现象(甚至epoch数还未超过10就已经开始抖动)。 + +**下面对epoch、learning_rate的选取进行探究** + +如果epoch选取过大会导致浪费gpu时间且会导致过拟合,如果选取过小又有可能使得结果并非最优。所以在实验开始之前,给epoch和learning_rate选取合适的值是一件非常有意义的事情。查阅资料后发现,learning_rate最优的选取方式并非是一个定值,而是一个随着epoch变化的函数,所以初始learning_rate可以设置较大,随后每隔几个epoch减半是一种比较好的实现方式。而epoch的选取则没有较为固定的方法,一般是观察loss的变化,选取loss最小时的epoch值,一般选取10左右。下面是一组epoch=10,每隔2个epoch则learning_rate减半,learning_rate初始设为0.2的一组实验数据。 + +| epoch | accuracy | +| ----- | -------- | +| 0 | 0.9573 | +| 1 | 0.9694 | +| 2 | 0.9759 | +| 3 | 0.9791 | +| 4 | 0.9789 | +| 5 | 0.9789 | +| 6 | 0.9809 | +| 7 | 0.9812 | +| 8 | 0.9807 | +| 9 | 0.9812 | + +![research](img/research.png) + +与上文所做的固定learning_rate的结果相比之下,效果有稍微的提升,不过不是很明显。 + +### 2. mini_batch的复现 + +可以看到mini_batch函数中只有简单的一行 + +```python +def mini_batch(dataset, batch_size=128, numpy=False): + return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True) +``` + +那么去查阅torch.utils.data的源码中DataLoader类可以看到该函数的作用是给定batch_size和dataset,将数据集打乱并分割成batch_size大小的一个个小数据集。知道了原理之后就可以很简单的利用numpy.shuffle来进行打乱,并进行很简单的数据处理实现该函数。 + +```python +def mini_batch(dataset, batch_size=128): + + data = [] + label = [] + + for each in dataset: + data.append(np.array(each[0])) + label.append(each[1]) + + label = np.array(label) + data = np.array(data) + + num = data.shape[0] + i = np.arange(num) + np.random.shuffle(i) + + label_ = label[i] + data_ = data[i] + + res = [] + for id in range(num // batch_size): + batch_data = data_[id * batch_size: (id + 1) * batch_size] + batch_label = label_[id * batch_size: (id + 1) * batch_size] + res.append((batch_data, batch_label)) + + return res +``` + +### 3.利用新版本的mini_batch进行基础测试 + +| epoch | accuracy | +| ----- | -------- | +| 0 | 0.9476 | +| 1 | 0.9640 | +| 2 | 0.9715 | + +可以看到和utils.py中的mini_batch效果相近 + +![mini_batch](img/mini_batch.png) + +## 3. Pytorch权重初始化 + +首先去查阅torch.nn.linear的源码,如下所示 + +```python +def __init__(self, in_features: int, out_features: int, bias: bool = True) -> None: + super(Linear, self).__init__() + self.in_features = in_features + self.out_features = out_features + self.weight = Parameter(torch.Tensor(out_features, in_features)) + if bias: + self.bias = Parameter(torch.Tensor(out_features)) + else: + self.register_parameter('bias', None) + self.reset_parameters() + +def reset_parameters(self) -> None: + init.kaiming_uniform_(self.weight, a=math.sqrt(5)) + if self.bias is not None: + fan_in, _ = init._calculate_fan_in_and_fan_out(self.weight) + bound = 1 / math.sqrt(fan_in) + init.uniform_(self.bias, -bound, bound) +``` + +可以看到在定义self.bias和self.weight之后进行了reset_parameters的操作,而在这个函数中关键的一步为init.kaiming_uniform_ + +去查看该函数的源码,如下所示 + +```python +def kaiming_uniform_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu'): + fan = _calculate_correct_fan(tensor, mode) + gain = calculate_gain(nonlinearity, a) + std = gain / math.sqrt(fan) + bound = math.sqrt(3.0) * std + with torch.no_grad(): + return tensor.uniform_(-bound, bound) +``` + +calculate_gain的源码,如下所示 + +```python +def calculate_gain(nonlinearity, param=None): + linear_fns = ['linear', 'conv1d', 'conv2d', 'conv3d', 'conv_transpose1d', 'conv_transpose2d', 'conv_transpose3d'] + if nonlinearity in linear_fns or nonlinearity == 'sigmoid': + return 1 + elif nonlinearity == 'tanh': + return 5.0 / 3 + elif nonlinearity == 'relu': + return math.sqrt(2.0) + elif nonlinearity == 'leaky_relu': + if param is None: + negative_slope = 0.01 + elif not isinstance(param, bool) and isinstance(param, int) or isinstance(param, float): + negative_slope = param + else: + raise ValueError("negative_slope {} not a valid number".format(param)) + return math.sqrt(2.0 / (1 + negative_slope ** 2)) + elif nonlinearity == 'selu': + return 3.0 / 4 + else: + raise ValueError("Unsupported nonlinearity {}".format(nonlinearity)) +``` + +| 函数 | gain效果 | +| ---------- | -------------------------- | +| ReLU | sqrt(2) | +| Leaky_ReLU | sqrt(2 / (1 + param ** 2)) | + +由于默认为Leaky_ReLU,所以 +$$ +bound = \sqrt[]{\frac{2}{1 + param^2}} \times \sqrt[]{\frac{3}{fan\_in}} +$$ +而且可以看到默认的param = math.sqrt(5),所以很容易就可以写出代码,如下所示 + +```python +def get_torch_initialization(): + + def parameters(in_features, out_features, param = 5**0.5): + bound = (6 / (1 + param * param) / in_features ) ** 0.5 + return np.random.uniform(-bound, bound, (in_features, out_features)) + + W1 = parameters(28 * 28, 256) + W2 = parameters(256, 64) + W3 = parameters(64, 10) + return W1, W2, W3 +``` + +如上为kaiming分布中的均匀分布,也是pytorch初始化参数的做法 + +其实kaiming分布中还有一种正态分布,和均匀分布稍有不同,将值的求法稍稍改动,如下所示 +$$ +std = \sqrt[]{\frac{2}{fan\_in \times (1 + param^2)}} +$$ +采用复现的get_torch_initialization进行基础测试,结果如下 + +| epoch | accuracy | +| ----- | -------- | +| 0 | 0.9421 | +| 1 | 0.9658 | +| 2 | 0.9742 | + +![get_torch_initialization](.\img\get_torch_initialization.png) \ No newline at end of file diff --git a/assignment-2/submission/18307130074/tester_demo.py b/assignment-2/submission/18307130074/tester_demo.py new file mode 100644 index 0000000..e63107a --- /dev/null +++ b/assignment-2/submission/18307130074/tester_demo.py @@ -0,0 +1,183 @@ +import numpy as np +import torch +from torch import matmul as torch_matmul, relu as torch_relu, softmax as torch_softmax, log as torch_log + +from numpy_fnn import Matmul, Relu, Softmax, Log, NumpyModel, NumpyLoss +from torch_mnist import TorchModel +from utils import get_torch_initialization, one_hot + +err_epsilon = 1e-6 +err_p = 0.4 + +import os +os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" +def check_result(numpy_result, torch_result=None): + if isinstance(numpy_result, list) and torch_result is None: + flag = True + for (n, t) in numpy_result: + flag = flag and check_result(n, t) + return flag + # print((torch.from_numpy(numpy_result) - torch_result).abs().mean().item()) + T = (torch_result * torch.from_numpy(numpy_result) < 0).sum().item() + direction = T / torch_result.numel() < err_p + return direction and ((torch.from_numpy(numpy_result) - torch_result).abs().mean() < err_epsilon).item() + + +def case_1(): + x = np.random.normal(size=[5, 6]) + W = np.random.normal(size=[6, 4]) + + numpy_matmul = Matmul() + numpy_out = numpy_matmul.forward(x, W) + numpy_x_grad, numpy_W_grad = numpy_matmul.backward(np.ones_like(numpy_out)) + + torch_x = torch.from_numpy(x).clone().requires_grad_() + torch_W = torch.from_numpy(W).clone().requires_grad_() + + torch_out = torch_matmul(torch_x, torch_W) + torch_out.sum().backward() + + return check_result([ + (numpy_out, torch_out), + (numpy_x_grad, torch_x.grad), + (numpy_W_grad, torch_W.grad) + ]) + + +def case_2(): + x = np.random.normal(size=[5, 6]) + + numpy_relu = Relu() + numpy_out = numpy_relu.forward(x) + numpy_x_grad = numpy_relu.backward(np.ones_like(numpy_out)) + + torch_x = torch.from_numpy(x).clone().requires_grad_() + + torch_out = torch_relu(torch_x) + torch_out.sum().backward() + + return check_result([ + (numpy_out, torch_out), + (numpy_x_grad, torch_x.grad), + ]) + + +def case_3(): + x = np.random.uniform(low=0.0, high=1.0, size=[3, 4]) + + numpy_log = Log() + numpy_out = numpy_log.forward(x) + numpy_x_grad = numpy_log.backward(np.ones_like(numpy_out)) + + torch_x = torch.from_numpy(x).clone().requires_grad_() + + torch_out = torch_log(torch_x) + torch_out.sum().backward() + + return check_result([ + (numpy_out, torch_out), + + (numpy_x_grad, torch_x.grad), + ]) + + +def case_4(): + x = np.random.normal(size=[4, 5]) + + numpy_softmax = Softmax() + numpy_out = numpy_softmax.forward(x) + + torch_x = torch.from_numpy(x).clone().requires_grad_() + + torch_out = torch_softmax(torch_x, 1) + + return check_result(numpy_out, torch_out) + + +def case_5(): + x = np.random.normal(size=[20, 25]) + + numpy_softmax = Softmax() + numpy_out = numpy_softmax.forward(x) + numpy_x_grad = numpy_softmax.backward(np.ones_like(numpy_out)) + + torch_x = torch.from_numpy(x).clone().requires_grad_() + + torch_out = torch_softmax(torch_x, 1) + torch_out.sum().backward() + + return check_result([ + (numpy_out, torch_out), + (numpy_x_grad, torch_x.grad), + ]) + + +def test_model(): + try: + numpy_loss = NumpyLoss() + numpy_model = NumpyModel() + torch_model = TorchModel() + torch_model.W1.data, torch_model.W2.data, torch_model.W3.data = get_torch_initialization(numpy=False) + numpy_model.W1 = torch_model.W1.detach().clone().numpy() + numpy_model.W2 = torch_model.W2.detach().clone().numpy() + numpy_model.W3 = torch_model.W3.detach().clone().numpy() + + x = torch.randn((10000, 28, 28)) + y = torch.tensor([1, 2, 3, 4, 5, 6, 7, 8, 9, 0] * 1000) + + y = one_hot(y, numpy=False) + x2 = x.numpy() + y_pred = torch_model.forward(x) + loss = (-y_pred * y).sum(dim=1).mean() + loss.backward() + + y_pred_numpy = numpy_model.forward(x2) + numpy_loss.get_loss(y_pred_numpy, y.numpy()) + + check_flag_1 = check_result(y_pred_numpy, y_pred) + print("+ {:12} {}/{}".format("forward", 10 * check_flag_1, 10)) + except: + print("[Runtime Error in forward]") + print("+ {:12} {}/{}".format("forward", 0, 10)) + return 0 + + try: + + numpy_model.backward(numpy_loss.backward()) + + check_flag_2 = [ + check_result(numpy_model.log_grad, torch_model.log_input.grad), + check_result(numpy_model.softmax_grad, torch_model.softmax_input.grad), + check_result(numpy_model.W3_grad, torch_model.W3.grad), + check_result(numpy_model.W2_grad, torch_model.W2.grad), + check_result(numpy_model.W1_grad, torch_model.W1.grad) + ] + check_flag_2 = sum(check_flag_2) >= 4 + print("+ {:12} {}/{}".format("backward", 20 * check_flag_2, 20)) + except: + print("[Runtime Error in backward]") + print("+ {:12} {}/{}".format("backward", 0, 20)) + check_flag_2 = False + + return 10 * check_flag_1 + 20 * check_flag_2 + + +if __name__ == "__main__": + testcases = [ + ["matmul", case_1, 5], + ["relu", case_2, 5], + ["log", case_3, 5], + ["softmax_1", case_4, 5], + ["softmax_2", case_5, 10], + ] + score = 0 + for case in testcases: + try: + res = case[2] if case[1]() else 0 + except: + print("[Runtime Error in {}]".format(case[0])) + res = 0 + score += res + print("+ {:12} {}/{}".format(case[0], res, case[2])) + score += test_model() + print("{:14} {}/60".format("FINAL SCORE", score)) diff --git a/assignment-2/submission/18307130074/torch_mnist.py b/assignment-2/submission/18307130074/torch_mnist.py new file mode 100644 index 0000000..6d3e214 --- /dev/null +++ b/assignment-2/submission/18307130074/torch_mnist.py @@ -0,0 +1,73 @@ +import torch +from utils import mini_batch, batch, download_mnist, get_torch_initialization, one_hot, plot_curve + + +class TorchModel: + + def __init__(self): + self.W1 = torch.randn((28 * 28, 256), requires_grad=True) + self.W2 = torch.randn((256, 64), requires_grad=True) + self.W3 = torch.randn((64, 10), requires_grad=True) + self.softmax_input = None + self.log_input = None + + def forward(self, x): + x = x.reshape(-1, 28 * 28) + x = torch.relu(torch.matmul(x, self.W1)) + x = torch.relu(torch.matmul(x, self.W2)) + x = torch.matmul(x, self.W3) + + self.softmax_input = x + self.softmax_input.retain_grad() + + x = torch.softmax(x, 1) + + self.log_input = x + self.log_input.retain_grad() + + x = torch.log(x) + + return x + + def optimize(self, learning_rate): + with torch.no_grad(): + self.W1 -= learning_rate * self.W1.grad + self.W2 -= learning_rate * self.W2.grad + self.W3 -= learning_rate * self.W3.grad + + self.W1.grad = None + self.W2.grad = None + self.W3.grad = None + + +def torch_run(): + train_dataset, test_dataset = download_mnist() + + model = TorchModel() + model.W1.data, model.W2.data, model.W3.data = get_torch_initialization(numpy=False) + + train_loss = [] + + epoch_number = 3 + learning_rate = 0.1 + + for epoch in range(epoch_number): + for x, y in mini_batch(train_dataset, numpy=False): + y = one_hot(y, numpy=False) + + y_pred = model.forward(x) + loss = (-y_pred * y).sum(dim=1).mean() + loss.backward() + model.optimize(learning_rate) + + train_loss.append(loss.item()) + + x, y = batch(test_dataset, numpy=False)[0] + accuracy = model.forward(x).argmax(dim=1).eq(y).float().mean().item() + print('[{}] Accuracy: {:.4f}'.format(epoch, accuracy)) + + plot_curve(train_loss) + + +if __name__ == "__main__": + torch_run() diff --git a/assignment-2/submission/18307130074/utils.py b/assignment-2/submission/18307130074/utils.py new file mode 100644 index 0000000..709220c --- /dev/null +++ b/assignment-2/submission/18307130074/utils.py @@ -0,0 +1,71 @@ +import torch +import numpy as np +from matplotlib import pyplot as plt + + +def plot_curve(data): + plt.plot(range(len(data)), data, color='blue') + plt.legend(['loss_value'], loc='upper right') + plt.xlabel('step') + plt.ylabel('value') + plt.show() + + +def download_mnist(): + from torchvision import datasets, transforms + + transform = transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize(mean=(0.1307,), std=(0.3081,)) + ]) + + train_dataset = datasets.MNIST(root="./data/", transform=transform, train=True, download=True) + test_dataset = datasets.MNIST(root="./data/", transform=transform, train=False, download=True) + + return train_dataset, test_dataset + + +def one_hot(y, numpy=True): + if numpy: + y_ = np.zeros((y.shape[0], 10)) + y_[np.arange(y.shape[0], dtype=np.int32), y] = 1 + return y_ + else: + y_ = torch.zeros((y.shape[0], 10)) + y_[torch.arange(y.shape[0], dtype=torch.long), y] = 1 + return y_ + + +def batch(dataset, numpy=True): + data = [] + label = [] + for each in dataset: + data.append(each[0]) + label.append(each[1]) + data = torch.stack(data) + label = torch.LongTensor(label) + if numpy: + return [(data.numpy(), label.numpy())] + else: + return [(data, label)] + + +def mini_batch(dataset, batch_size=128, numpy=False): + return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True) + + +def get_torch_initialization(numpy=True): + fc1 = torch.nn.Linear(28 * 28, 256) + fc2 = torch.nn.Linear(256, 64) + fc3 = torch.nn.Linear(64, 10) + + if numpy: + W1 = fc1.weight.T.detach().clone().numpy() + W2 = fc2.weight.T.detach().clone().numpy() + W3 = fc3.weight.T.detach().clone().numpy() + else: + W1 = fc1.weight.T.detach().clone().data + W2 = fc2.weight.T.detach().clone().data + W3 = fc3.weight.T.detach().clone().data + + return W1, W2, W3 -- Gitee