用Python3.6复现吴恩达机器学习课后作业:从环境配置到实战避坑
当你在Coursera上完成吴恩达教授的机器学习课程后,是否曾对着那些Octave作业感到无从下手?作为一门经典的入门课程,其教学价值毋庸置疑,但原作业使用的Octave语言在当今Python主导的机器学习生态中显得有些格格不入。本文将带你用Python3.6完整复现所有编程练习,不仅还原算法本质,更融入现代Python数据科学生态的最佳实践。
1. 环境搭建与工具链配置
复现作业的第一步是搭建一个与课程内容匹配但又能适应现代Python开发的环境。不同于简单地安装Anaconda,我们需要精心配置工具链以确保与课程算法的兼容性。
核心工具栈选择:
- Python 3.6.x(与课程数学运算保持最佳兼容)
- NumPy 1.19.5(最后一个全面支持Python3.6的稳定版本)
- Matplotlib 3.3.4(兼容性最佳的绘图库版本)
- SciPy 1.5.4(科学计算基础库)
# 创建专用虚拟环境 conda create -n ml-ng python=3.6.13 conda activate ml-ng # 安装核心依赖(指定版本避免冲突) pip install numpy==1.19.5 matplotlib==3.3.4 scipy==1.5.4 pandas==1.1.5注意:避免使用Python3.7+版本,某些课程中的矩阵运算在更高版本NumPy中的行为会有微妙差异
对于IDE的选择,推荐VS Code配合Jupyter插件,既能交互式调试又支持完整项目开发。特别建议安装Python Extension Pack和Jupyter插件,配置如下工作区设置:
{ "python.pythonPath": "ml-ng/bin/python", "jupyter.notebookFileRoot": "${workspaceFolder}", "python.linting.pylintEnabled": true }2. 线性回归作业的Python实现与优化
课程的第一个编程作业是单变量线性回归,原Octave代码大量依赖矩阵操作。我们用NumPy重写时需要注意几点关键差异:
关键实现对比:
| Octave操作 | Python等效实现 | 注意事项 |
|---|---|---|
theta' * X | theta.T @ X | NumPy中@是矩阵乘法运算符 |
sum((X*theta-y).^2) | np.sum((X @ theta - y)**2) | 平方运算需显式使用** |
pinv(X'*X)*X'*y | np.linalg.pinv(X.T @ X) @ X.T @ y | 正规方程的实现差异 |
完整的多变量线性回归实现示例:
def compute_cost(X, y, theta): m = len(y) predictions = X @ theta sq_errors = (predictions - y)**2 J = 1/(2*m) * np.sum(sq_errors) return J def gradient_descent(X, y, theta, alpha, iterations): m = len(y) cost_history = [] for _ in range(iterations): error = X @ theta - y theta = theta - (alpha/m) * (X.T @ error) cost_history.append(compute_cost(X, y, theta)) return theta, cost_history常见报错与解决:
- 维度不匹配错误:Octave自动广播规则与NumPy不同,务必显式检查
X.shape和theta.shape - 收敛问题:Python中需要更小的学习率(尝试0.01而非Octave的0.1)
- 可视化差异:Matplotlib默认样式与Octave不同,需手动设置
plt.style.use('seaborn')
3. 逻辑回归与正则化的现代实现
逻辑回归作业涉及更复杂的优化算法。原课程使用fminunc,而Python生态中有更高效的选择:
优化算法对比选择:
| 方法 | 实现方案 | 适用场景 |
|---|---|---|
| 原生梯度下降 | 手动实现 | 教学理解 |
| scipy.optimize | minimize(method='BFGS') | 平衡精度与速度 |
| scipy.optimize | minimize(method='TNC') | 约束优化问题 |
正则化逻辑回归的核心实现技巧:
def sigmoid(z): return 1 / (1 + np.exp(-z)) def cost_function_reg(theta, X, y, lambda_): m = len(y) h = sigmoid(X @ theta) reg_term = (lambda_/(2*m)) * np.sum(theta[1:]**2) J = (-1/m) * (y.T @ np.log(h) + (1-y).T @ np.log(1-h)) + reg_term return J def gradient_reg(theta, X, y, lambda_): m = len(y) h = sigmoid(X @ theta) grad = (1/m) * X.T @ (h - y) grad[1:] += (lambda_/m) * theta[1:] # 不正则化theta0 return grad提示:使用
scipy.optimize.minimize时,将jac=True传入以使用自定义梯度函数可大幅提升收敛速度
特征映射的Python优化技巧: 课程中的多项式特征生成在Octave中效率较低,改用sklearn.preprocessing.PolynomialFeatures即使不借助GPU也能获得百倍加速:
from sklearn.preprocessing import PolynomialFeatures def map_feature(X1, X2, degree=6): poly = PolynomialFeatures(degree) return poly.fit_transform(np.column_stack((X1, X2)))4. 神经网络与反向传播的工程化实现
第三周的神经网络作业是最具挑战性的部分。原Octave实现暴露了底层细节,但在Python中我们可以采用更工程化的方法:
权重初始化策略对比:
| 方法 | 实现代码 | 优点 |
|---|---|---|
| 随机初始化 | theta = 0.12 * (2 * np.random.rand(size) - 1) | 课程原始方法 |
| He初始化 | theta = np.random.randn(size) * np.sqrt(2/n_features) | 更适合ReLU |
| Xavier初始化 | theta = np.random.randn(size) * np.sqrt(1/n_features) | 适合sigmoid/tanh |
向量化实现反向传播的关键步骤:
def backprop(params, input_size, hidden_size, num_labels, X, y, lambda_): # 解压参数 theta1 = params[:hidden_size*(input_size+1)].reshape(hidden_size, input_size+1) theta2 = params[hidden_size*(input_size+1):].reshape(num_labels, hidden_size+1) m = X.shape[0] Delta1 = np.zeros_like(theta1) Delta2 = np.zeros_like(theta2) # 前向传播 a1 = np.hstack([np.ones((m,1)), X]) z2 = a1 @ theta1.T a2 = np.hstack([np.ones((m,1)), sigmoid(z2)]) z3 = a2 @ theta2.T h = sigmoid(z3) # 计算误差 y_matrix = np.eye(num_labels)[y-1] delta3 = h - y_matrix delta2 = delta3 @ theta2[:,1:] * sigmoid_gradient(z2) # 累积梯度 Delta2 = delta3.T @ a2 Delta1 = delta2.T @ a1 # 正则化 theta1_reg = np.hstack([np.zeros((theta1.shape[0],1)), theta1[:,1:]]) theta2_reg = np.hstack([np.zeros((theta2.shape[0],1)), theta2[:,1:]]) grad1 = Delta1/m + (lambda_/m)*theta1_reg grad2 = Delta2/m + (lambda_/m)*theta2_reg return np.concatenate([grad1.ravel(), grad2.ravel()])调试技巧:
- 使用
np.gradient检查数值梯度与解析梯度的一致性 - 可视化各层权重分布发现初始化问题
- 对成本函数曲线添加滑动平均平滑处理(
pd.Series(costs).rolling(50).mean())
5. 支持向量机与K-means的现代替代方案
虽然课程使用SVM和K-means作为无监督学习的示例,但Python生态提供了更强大的实现:
scikit-learn优化实现对比:
| 课程算法 | sklearn等效类 | 关键参数优化 |
|---|---|---|
| SVM | sklearn.svm.SVC(kernel='linear', C=1) | 调整C值控制正则化强度 |
| K-means | sklearn.cluster.KMeans(n_clusters=3) | 使用kmeans++初始化 |
高斯核函数的工程实现技巧:
def gaussian_kernel(x1, x2, sigma): return np.exp(-np.sum((x1 - x2)**2) / (2 * sigma**2)) # 向量化版本用于计算Gram矩阵 def gaussian_kernel_matrix(X1, X2, sigma): pairwise_dists = np.sum(X1**2, axis=1)[:, np.newaxis] + np.sum(X2**2, axis=1) - 2 * X1 @ X2.T return np.exp(-pairwise_dists / (2 * sigma**2))性能优化技巧:
- 对大数据集使用
sklearn.metrics.pairwise_distances_argmin_min加速K-means分配 - 对SVM使用
SVC(kernel='precomputed')配合预先计算的核矩阵 - 利用
joblib并行化交叉验证过程
6. 异常检测与推荐系统的生产级实现
课程最后两章的作业可以延伸出现代数据流水线的最佳实践:
异常检测的改进方案:
from sklearn.covariance import EllipticEnvelope def multivariate_gaussian(X, mu, sigma2): k = len(mu) X_centered = X - mu sigma2_diag = np.diag(sigma2) denominator = np.sqrt((2*np.pi)**k * np.linalg.det(sigma2_diag)) exponent = -0.5 * np.sum(X_centered @ np.linalg.pinv(sigma2_diag) * X_centered, axis=1) return np.exp(exponent) / denominator # 使用鲁棒协方差估计 clf = EllipticEnvelope(contamination=0.02) clf.fit(X_train) y_pred = clf.predict(X_test)推荐系统的增量学习实现:
from surprise import Dataset, KNNBasic from surprise.model_selection import train_test_split def train_recommender(ratings): reader = Reader(rating_scale=(1, 5)) data = Dataset.load_from_df(ratings[['user', 'item', 'rating']], reader) trainset, testset = train_test_split(data, test_size=0.25) sim_options = { 'name': 'cosine', 'user_based': False # 基于物品的协同过滤 } algo = KNNBasic(k=40, min_k=5, sim_options=sim_options) algo.fit(trainset) return algo在实际项目中,这些算法需要配合特征工程流水线和超参数优化框架(如Optuna)才能发挥最大效用。