CMU10414-hw0

overview

hw0 简单回顾了机器学习的基础,看了 lec01-03 就可以开始完成。本人为 0基础 机器学习选手,花了不少时间熟悉矩阵运算和一些 python 的矩阵操作。hw0 是进入开发needle前的热身,并不算难。关于计算梯度等的求导过程,在 lec 中会简单过一遍,最后直接使用简化后的公式即可( Kotlin说这样hacky 一点,因为没有人会在实际的计算过程中使用严格的求导过程)。

环境

作业的检测平台使用 colab,作为非会员,经常会断线。每次断线重连可能需要重新配置环境:(插入代码块并运行)

  • 挂载google drive
    1
    2
    from google.colab import drive
    drive.mount('/content/drive')
  • 进入工作路径(假设drive中已经有文件夹)
    1
    %cd /content/drive/MyDrive/10714/hw0
  • 下载必要依赖:
    1
    2
    3
    !pip3 install --upgrade --no-deps git+https://github.com/dlsyscourse/mugrade.git
    !pip3 install pybind11
    !pip3 install numdifftools

add

1
2
def add(x, y):    
return x + y

parse_mnist

手动解析 mnist 数据集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def parse_mnist(image_filename, label_filename):
# load the image file
with gzip.open(image_filename, 'rb') as f:
magic, num, rows, cols = struct.unpack(">IIII", f.read(16))
images = np.frombuffer(f.read(), dtype=np.uint8).reshape(num, rows * cols)

# normalize and convert
images = images.astype(np.float32) / 255.0

with gzip.open(label_filename, 'rb') as f:
magic, num = struct.unpack(">II", f.read(8))
labels = np.frombuffer(f.read(), dtype=np.uint8)

return (images, labels)

softmax loss

1
2
def softmax_loss(Z, y):
return np.mean(np.log(np.sum(np.exp(Z), axis=1)) - Z[np.arange(Z.shape[0]), y])

我们需要熟悉一下numpy的一些基本操作:

  • np.exp对 ndarray 中每一个元素计算指数函数
  • np.sum对 ndarray 进行求和,axis=1代表对行求和
  • np.log对 ndarray 每个元素取自然对数
  • Z.shape[0]表示 Z 的行数,1为列数
  • np.arange(t)生成一个 0 到 t-1 的数组

softmax regression epoch

我们将X(m,n)、y(m) 按照 batch size 划分为多个 batch (m / batch_size 个),利用简化后的梯度计算公式来计算下降梯度

更新公式:
theta := theta - lr * grad
lr 为学习率,aka 步长

1
2
3
4
5
6
7
8
9
10
11
def softmax_regression_epoch(X, y, theta, lr=0.1, batch=100):
for i in range(0, X.shape[0], batch):
X_batch = X[i: i + batch]
y_batch = y[i: i + batch]
exp_X_theta = np.exp(X_batch @ theta)
Z = exp_X_theta / np.sum(exp_X_theta, axis=1, keepdims=True)
I_y = np.zeros((batch, theta.shape[1]))
I_y[np.arange(batch), y_batch] = 1
grad = X_batch.T @ (Z - I_y) / batch
theta -= lr * grad
#theta = theta - lr * grad

(离大谱了,有人可以想到 -== -的区别?)
注意在 python 中,后者相当于重新创建了一个新的 theta并修改,和我们预期的原地修改不一样!!

这里学到的 ndarray 操作:

  • X.T:得到 ndarray 的矩阵转置
  • keepdims=True:可以保留原来的维度,比如:
    1
    2
    3
    4
    5
    6
    # before
    ([1,2],
    [3,4])
    # after
    ([3,3],
    [7,7])
  • np.zeros():创造指定形状的零矩阵
  • @:该运算符可以用来进行矩阵乘法,注意区分*对于两个矩阵是每个相同位置的元素相乘

nn_epoch

利用反向传播的简化公式,更新参数 W1 W2(即神经网络的layer)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def nn_epoch(X, y, W1, W2, lr = 0.1, batch=100):
for i in range(0, X.shape[0], batch):
X_batch = X[i : i + batch]
y_batch = y[i : i + batch]
Z1 = np.maximum(0, (X_batch @ W1))

exp_Z1W2 = np.exp(Z1 @ W2)
Iy = np.zeros((batch, W2.shape[1]))
Iy[np.arange(batch), y_batch] = 1
G2 = exp_Z1W2 / np.sum(exp_Z1W2, axis=1, keepdims=True) - Iy
G1 = (Z1 > 0).astype(int) * (G2 @ W2.T)

grad_W1 = (X_batch.T @ G1) / batch
grad_W2 = (Z1.T @ G2) / batch

W1 -= lr * grad_W1
W2 -= lr * grad_W2

ReLU函数:ReLU(x) = max(0, x),是这里的非线性因素

softmax regression epoch in cpp

要求我们使用 cpp 实现 Softmax 回归,小批量梯度下降,和上面的一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void softmax_regression_epoch_cpp(const float *X, const unsigned char *y,
float *theta, size_t m, size_t n, size_t k,
float lr, size_t batch)
{
for (size_t b = 0; b < m; b += batch) {
// for each batch:

// calculate Z - I_y (after normalize)
std::vector<std::vector<float>> Z(batch, std::vector<float>(k, 0));
//float Z[batch][k] = {};
size_t z_row, z_col;
for (z_row = 0; z_row < batch; z_row++) {
float sum = 0; // row-wise sum
for (z_col = 0; z_col < k; z_col++) {
float z_element = 0;
for (size_t i = 0; i < n; i++) {
z_element += X[(b+z_row)*n + i] * theta[i*k + z_col];
}
z_element = std::exp(z_element);
Z[z_row][z_col] = z_element;
sum += z_element;
}

// normalize and minus e_y
for (z_col = 0; z_col < k; z_col++) {
Z[z_row][z_col] /= sum;
if (z_col == y[b+z_row])
Z[z_row][z_col] -= 1;
}
}

// calculate grad: X.T @ (Z - I_y) / batch
// and update in place
size_t g_row, g_col;
for (g_row = 0; g_row < n; g_row++) {
for (g_col = 0; g_col < k; g_col++) {
for (size_t i = 0; i < batch; i++) {
theta[g_row*k + g_col] -= X[(b+i)*n + g_row] * Z[i][g_col] * lr / batch;
}

}
}
}
}

result

使用 mnist作为训练数据,平台为 colab

python 版本的 softmax regression classifier

Epoch Train Loss Train Err Test Loss Test Err
0 0.35134 0.10182 0.33588 0.09400
1 0.32142 0.09268 0.31086 0.08730
2 0.30802 0.08795 0.30097 0.08550
3 0.29987 0.08532 0.29558 0.08370
4 0.29415 0.08323 0.29215 0.08230
5 0.28981 0.08182 0.28973 0.08090
6 0.28633 0.08085 0.28793 0.08080
7 0.28345 0.07997 0.28651 0.08040
8 0.28100 0.07923 0.28537 0.08010
9 0.27887 0.07847 0.28442 0.07970

python 版本的神经网络:可以看到Test Err显然低于前者,说明我们引入的非线性因素,的确显著提高了分辨的正确率。

Epoch Train Loss Train Err Test Loss Test Err
0 0.15324 0.04697 0.16305 0.04920
1 0.09854 0.02923 0.11604 0.03660
2 0.07434 0.02167 0.09790 0.03160
3 0.05947 0.01713 0.08773 0.02890
4 0.04826 0.01355 0.08048 0.02610
5 0.04048 0.01093 0.07690 0.02390
6 0.03476 0.00895 0.07435 0.02320
7 0.03026 0.00777 0.07256 0.02290
8 0.02664 0.00650 0.07137 0.02200
9 0.02355 0.00560 0.07004 0.02130
10 0.02099 0.00463 0.06890 0.02080
11 0.01908 0.00392 0.06852 0.02070
12 0.01720 0.00322 0.06801 0.02070
13 0.01561 0.00275 0.06746 0.02110
14 0.01422 0.00238 0.06691 0.02070
15 0.01297 0.00212 0.06644 0.02040
16 0.01188 0.00178 0.06617 0.02030
17 0.01085 0.00142 0.06560 0.01980
18 0.01005 0.00122 0.06527 0.01900
19 0.00921 0.00105 0.06492 0.01900

c++版本的 softmax regression classifier

Epoch Train Loss Train Err Test Loss Test Err
0 0.35134 0.10182 0.33588 0.09400
1 0.32142 0.09270 0.31086 0.08730
2 0.30803 0.08795 0.30097 0.08550
3 0.29987 0.08532 0.29559 0.08370
4 0.29415 0.08323 0.29215 0.08230
5 0.28981 0.08182 0.28973 0.08090
6 0.28633 0.08083 0.28793 0.08080
7 0.28345 0.07997 0.28651 0.08040
8 0.28100 0.07925 0.28537 0.08010
9 0.27887 0.07847 0.28442 0.07970

move on !


CMU10414-hw0
https://pactheman123.github.io/2025/01/25/CMU10414-hw0/
作者
Xiaopac
发布于
2025年1月25日
许可协议