Ziyu Li's Homepage

Back

CS189 Assignment1#

part2和part1其实差不多, 只是更深入的去实现一些机器学习的基本模型以及做一些图像增强的工作, 从算法难度上来说还不如part1里面那几个实现旋转变换的函数

Assignment Overview#

本部分代码位于fashion_pt_2.ipynb里面

加载MNIST数据集#

torchvision调用api即可

注意一般来说对于这种加载出来的数据集, 我们都要把他的特征列和label列转化成numpy数组, 然后再用这两个(也许不止两个)数组构造DataFrame

Loaded FashionMNIST with 60000 samples. Classes: {0: 'T-shirt/top', 1: 'Trouser', 2: 'Pullover', 3: 'Dress', 4: 'Coat', 5: 'Sandal', 6: 'Shirt', 7: 'Sneaker', 8: 'Bag', 9: 'Ankle boot'}
Classes: {0: 'T-shirt/top', 1: 'Trouser', 2: 'Pullover', 3: 'Dress', 4: 'Coat', 5: 'Sandal', 6: 'Shirt', 7: 'Sneaker', 8: 'Bag', 9: 'Ankle boot'}
Image shape: (28, 28)
Image dtype: float64
plaintext

训练一个2层MLP分类器#

这部分实验文档让我们直接copy part1当中problem 3c的代码, 因为他在这里的目的只是需要这个分类器为后面的代码做铺垫, 所以就不再重述代码了, 在part1的notes当中复制3c的代码过来即可

Problem 5a#

这里要求我们对数据集进行处理, 然后用scikit-learn训练一个线性回归来预测价格, 要用到的特征只有image本身

先读取一下prices表, 这是一个独立的表, 后面要和数据的DataFramejoin

prices = pd.read_csv("./data/FashionMNIST_prices.csv")
prices
python

prices切成两部分, 分别和prices[:train_size]prices[train_size:]join

prices = pd.read_csv("./data/FashionMNIST_prices.csv")
# TODO: Join the original `train_df` and `test_df` with the `prices` DataFrame
prices_train = train_df.copy()
prices_train['Price'] = prices['Price'][:48000].values
prices_test = test_df.copy()
prices_test['Price'] = prices['Price'][48000:].values
prices_train['Price'].hist().show()
prices_train.head()
python
label_plot

接下来创建并且拟合这个LinearRegression模型, 只有一点要注意, 就是这个X_train要从原始的DataFrame里面拿出列, 去to_numpy()然后再stack, Y_train因为那个price列每行只有一个数, 所以只要to_numpy()就好了

# TODO: Fit a linear regression model (price_model) on the training data with prices as the target variable
if (load_saved_models or IS_GRADING_ENV) and os.path.exists('price_model.joblib'):
    price_model = joblib.load('price_model.joblib')
    print("Model loaded from price_model.joblib")
else:
    price_model = LinearRegression()
    X_train=np.stack(prices_train['image'].to_numpy())
    Y_train=prices_train['Price'].to_numpy()
    price_model.fit(X=X_train, y=Y_train)

# TODO: Predict the prices of the test data and add them to the test_df
X_test=np.stack(prices_test['image'].to_numpy())
python

再调用model.predict()得到测试结果, 加到测试集的DataFrame上去

prices_test['price_prediction'] = price_model.predict(X_test)

if save_models:
    joblib.dump(price_model, 'price_model.joblib')
    print("Model saved to price_model.joblib")
python

Problem 5b#

numpy操作生成各种模型指标来评价模型在测试集上的表现, 要注意这里的变量基本上都是向量, 所以要想明白怎么用向量化操作

先看RMSE的计算

def compute_rmse(y_true, y_pred):
  length=len(y_true)
  rmse=np.sqrt(np.sum((y_true-y_pred)**2)/length)

  return rmse
python

向量的平方操作与除以标量是广播到每个分量上的, 最后的np.sum操作是对分量的聚合, 然后开根号

同理是MAE和R&2的计算

顺便解释一下R2R^2, 我感觉不像MSE和RMSE那么直观

R2=1i=1n(yiy^i)2i=1n(yiyˉ)2R^2 = 1 - \frac{\sum_{i=1}^{n} (y_i - \hat{y}_i)^2}{\sum_{i=1}^{n} (y_i - \bar{y})^2}

R2=1R^2 = 1

的时候, 说明预测值和真实值完全一致

R2=0R^2 = 0

的时候, 说明统计意义下预测的方式和均值预测一样

当介于0到1且慢慢增大的时候, 就说明(总的)预测值和真实值是越来越靠近的

后面就是垃圾时间, 用model.predict得到预测值, 计算一些指标并且打印, 然后画个图就行了

=== Training Metrics ===
RMSE: 118.72
MAE: 90.26
R²: -0.006
=== Test Metrics ===
RMSE: 121.58
MAE: 90.56
R²: -0.054
plaintext

数据上可以看出这个预测效果很差, 甚至不如均值预测

label_plot

图上更直观, 可以看到大部分预测点当中, Predicted Price都被远远低估了, 距离那根

PredictedPrice=1ActualPrice+0Predicted Price = 1 * Actual Price + 0

非常远, 和R2<0R^2 < 0的结果一致

Problem 5d#

分类错误和回归误差

之前我们是对这份数据做了分类的, 即用那个2层的MLP分类器去预测label, 现在我们要研究一下分类对/错的类和回归的价格误差之间的一些关系

先看一下经过分类/回归之后已有的数据:

prices_test
python
label_plot

先计算两个误差: price_predictionPrice的差值的绝对值以及差值绝对值与真实值的商

# TODO: Compare how accurate the model is at classifying the images vs how accurate it is at predicting the price of the images
# TODO: Calculate the average relative price error for correctly classified vs. misclassified images
prices_test['price_error']=np.abs(prices_test['price_prediction']-prices_test['Price'])
prices_test['price_error_relative']=prices_test['price_error']/prices_test['Price']
python

然后按预测label是否正确来分类, 在类内计算商误差的平均值, 这一步是为了看看分类对/错和回归误差之间有没有什么关系, 一个naive的想法是那些错误分类的点的回归误差的平均值应该要高一些

misclassified_price_error = prices_test[prices_test['correct']==False]['price_error_relative'].mean()
correctly_classified_price_error = prices_test[prices_test['correct']==True]['price_error_relative'].mean()


print(f"\nPrice prediction performance:")
print(f"Misclassified images - Avg relative price error: {misclassified_price_error:.3f}")
print(f"Correctly classified images - Avg relative price error: {correctly_classified_price_error:.3f}")
python
Price prediction performance:
Misclassified images - Avg relative price error: 2.585
Correctly classified images - Avg relative price error: 2.321
plaintext

实际上只有非常小的差别

Problem 6a#

我们将在一个新的数据集上应用我们训练好的model(2层MLP分类器), 首先要对新数据集的数据(both训练集和测试集)做标准化

X_test_secret = np.load("./data/secret_test_set/X_test.npy")
y_test_secret = np.load("./data/secret_test_set/y_test.npy")

X_test_secret_sc = scaler.transform(X_test_secret)

print(X_test_secret.shape)
print(y_test_secret.shape)
python

不记得了可以去前面看一下, 这个scaler变量是StandardScaler的实例, 他会自动学习均值和方差以便归一化

(2000, 784)
(2000,)
plaintext

接下来就是先把X和y装填进一个DataFrame里面, 然后调用model.predict得到预测值, 最后计算一些指标并且打印

# TODO: Create a dataframe with the secret test set and use the model to predict the labels
test_secret_df = pd.DataFrame()
test_secret_df['label'] = y_test_secret

test_secret_df['image'] = X_test_secret.tolist()
# test_secret_df['image'] = test_secret_df['image'].apply(lambda x: np.array(x).reshape(-1))

# TODO: Use the model to predict the labels and calculate the accuracy
test_secret_df['predicted_label'] = model.predict(X_test_secret_sc)

test_secret_df['correct'] = test_secret_df['predicted_label'] == test_secret_df['label']

print(f"Test accuracy: {test_df['correct'].mean():.3f}")
print(f"Secret Test accuracy: {test_secret_df['correct'].mean():.3f}")
python
Test accuracy: 0.886
Secret Test accuracy: 0.200
plaintext

可以看到这个分类器在原来的测试集(其实应该叫验证集)上的正确率还可以, 但是真正在未知的测试集上的正确率非常低, 只有20%

Problem 6b#

在验证集/测试集上分label看正确率

test_df.groupby('label')['correct'].mean()
python
label
Ankle boot     0.956775
Bag            0.943191
Coat           0.836106
Dress          0.882601
Pullover       0.824066
Sandal         0.963666
Shirt          0.692939
Sneaker        0.943054
T-shirt/top    0.842762
Trouser        0.974569
Name: correct, dtype: float64
plaintext
test_secret_df.groupby('label')['correct'].mean()
python
label
Ankle boot     0.212963
Bag            0.513089
Coat           0.161290
Dress          0.130653
Pullover       0.098655
Sandal         0.265403
Shirt          0.297980
Sneaker        0.142857
T-shirt/top    0.107143
Trouser        0.070707
Name: correct, dtype: float64
plaintext

画个直方图

label_plot

Problem 6c#

调用sklearn.confusion_matrix得到混淆矩阵

# TODO: plot a confusion matrix for the secret test set
y_true=test_secret_df['label']
y_pred=test_secret_df['predicted_label']
conf_matrix = confusion_matrix(y_true,y_pred)

conf_matrix
python
array([[ 46,  89,   2,   1,   7,  12,  17,   5,  27,  10],
       [  8,  98,   6,   3,   7,  17,  26,   2,  23,   1],
       [  1,  73,  30,   1,  13,   9,  47,   0,  10,   2],
       [  1,  49,   8,  26,  12,  28,  34,  13,  24,   4],
       [  1, 111,  13,   5,  22,   3,  53,   0,  11,   4],
       [  2,  27,   1,   7,   9,  56,  29,  14,  57,   9],
       [  0,  78,   9,   3,  13,   9,  59,   0,  25,   2],
       [ 10,  21,   2,  12,  16,  33,   9,  30,  67,  10],
       [  1,  69,   2,   2,  18,  12,  45,   1,  18,   0],
       [  0,  24,   7,  23,  16,  51,  43,   8,  12,  14]])
plaintext

混淆矩阵C(i,j)C(i,j)表示真实label为ii, 预测label为jj的样本个数

Problem 6d#

首先利用给出的show_inages函数画出一些错误分类的例子

# Predict labels for the secret test set
predictions_secret = model.predict(X_test_secret_sc)

# Identify misclassified examples
incorrect_secret = predictions_secret != y_test_secret

# Generate labels for misclassified examples with true and predicted labels
labels = [f"True: {true_label}<br>Pred: {pred_label}"
          for true_label, pred_label in zip(y_test_secret[incorrect_secret], predictions_secret[incorrect_secret])]

# Display misclassified images with their true and predicted labels
show_images(X_test_secret[incorrect_secret], max_images=5, ncols=5, labels=labels, reshape=True)
python
label_plot

图中可以看出测试集上所有的图片都旋转了, 也许因为这个导致分类器误判了(因为训练数据当中并没有旋转了的图片), 我们有以下三个方案来解决这个问题

  • 把旋转了的图片加入训练集, 让模型学习额外的特征
  • 把测试集的图片转回去, 重新预测
  • Test Time Augmentation(TTA) 在测试时尝试多种增强图像的策略并且做出多个预测, 然后组合成最终的预测结果(和量化里的多路因子信号组合成最终信号差不多)

Problem 7a#

把训练数据当中的每一张图去rotate, 并记下旋转的角度, 得到一系列新的训练数据

记得要flatten一下就好

label_plot

Problem 7b#

用新得到的训练数据去训练一个新的模型, 注意标签还是和原来一样的, 因为数据旋转并不改变label

Rotated model saved to disk.
Training accuracy (training on rotated images): 1.000
Test accuracy (original): 0.886
Test accuracy (training on rotated images): 0.689
plaintext
label_plot

Problem 8a#

这个问题逻辑比较复杂, 首先我们刚刚得到了一些旋转的数据, 也就是(图片, 旋转角度), 我们可以用这些数据去训练一个简单的神经网络回归模型(MLPRegressor), 让他学习根据图片的状态预测旋转的角度, 然后在把测试集图片输入这个模型去得到测试集的旋转角度, 这样就可以逆向旋转回来了

训练阶段:
┌────────────────┐     ┌────────────────┐     ┌────────────────────┐
│  图片 + 角度    │ ──→ │  MLPRegressor  │ ──→ │  模型学会预测角度   │
│  (有标签的)     │     │  回归模型       │     │  image → rotation  │
└────────────────┘     └────────────────┘     └────────────────────┘

预测阶段:
┌────────────────┐     ┌────────────────┐     ┌────────────────────┐
│  测试集图片     │ ──→ │  预测旋转角度   │ ──→ │  逆时针转回来      │
│  (被旋转过的)   │     │  比如 30°      │     │  30° → 0°         │
└────────────────┘     └────────────────┘     └────────────────────┘
plaintext

迭代一百次就够了, 只是一个简单的神经网络而已

label_plot

Problem 8b#

现在来检测一下这个角度回归模型的效果

利用X_test这个数据集, 先把他旋转并且记下旋转的角度y_test_rotations, 然后用角度回归模型来预测这个旋转角度, 和y_test_rotations计算MSE和RMSE就知道模型的效果了

Test MSE: 3748.14
Test RMSE: 61.22 degrees

Sample predictions:
True rotation: 263.5°, Predicted: 196.7°
True rotation: 188.1°, Predicted: 154.8°
True rotation: 32.8°, Predicted: 32.1°
True rotation: 337.8°, Predicted: 330.9°
True rotation: 295.0°, Predicted: 328.0°
------------------------------
------------------------------
Test MSE (angle):  3748.14
Test RMSE:        61.22°
------------------------------
------------------------------
plaintext

Plot出来

label_plot

可以看到预测的效果还是不错的

画个例子, 输出原始图像, 旋转后的图像, 以及根据角度模型输出逆向旋转后的图像

label_plot

Problem 8c#

先预测一下测试集旋转的角度, 再逆向旋转

# TODO: Scale the secret test set and use model_rotation_regression to unrotate the images in the secret test set
X_test_secret_scaled = scaler.transform(X_test_secret)
y_pred_angles = model_rotation_regression.predict(X_test_secret_scaled)
X_test_unrotated  = np.array([
    rotate_image_by_angle(X_test_secret[i],-y_pred_angles[i]) 
    for i in range(len(X_test_secret))
])
X_test_unrotated_sc = scaler.transform(X_test_unrotated)
python

接着对这个逆向旋转的数据去预测label, 注意这个逆向旋转之后的图和之前的是一一对应的, 本质上还是在预测之前的label, 逆向旋转只是个增强而已

# TODO: Make new predictions using the original MLPClassifier model and check which images are correctly classified
test_secret_df["unrotated_prediction"] = model.predict(X_test_unrotated)
test_secret_df["unrotated_correct"] = test_secret_df["unrotated_prediction"] == test_secret_df['label']

print(f"Test accuracy: {test_secret_df.correct.mean():.3f}")
print(f"Unrotated Test accuracy: {test_secret_df.unrotated_correct.mean():.3f}")
python
Test accuracy: 0.200
Unrotated Test accuracy: 0.243
plaintext

可以看到有增强

接下来我们对比一下这三种方式的绩效

-原始baseline(直接跑测试集) -先用旋转过的数据去训练模型, 然后再跑测试集合 -训练角度预测模型, 把原数据逆向旋转之后再跑

label_plot

Problem 9#

利用TTA的方式, 把每一个图像增强(旋转)得到很多个, 然后对他们预测取一个平均的概率得到结果

代码看起来很长, 但其实都是流水线, 核心就是一张图片变多张图片, 产生多个概率向量, 求平均在argmax即可

Accuracy: 0.341
plaintext
UC Berkeley CS189 Assignment 1(Part 2)
https://astro-pure.js.org/blog/cs189_assignment1_part2
Author Ziyu(Albert) Li 李子煜
Published at February 1, 2026
Comment seems to stuck. Try to refresh?✨