
简介本资源是《机器学习》周志华著俗称“西瓜书”配套课程作业的完整代码实现合集面向高校人工智能、计算机科学及相关专业学生以及自学机器学习的开发者旨在辅助理解核心算法原理与动手实践。压缩包共90个文件包含22个Python脚本覆盖KMeans、KNN、PCA、AdaBoost、SVM、决策树等关键算法实现、10个Markdown习题解析文档、34张图表类图片含算法可视化与数据示例、6个PNG图示及3个CSV/文本格式西瓜数据集另有MATLAB实验数据ex7faces.mat与Jupyter Notebook等辅助文件整体大小为11.74MB。已有934人学习下载资源按章节结构化组织ch2至ch10每章含习题说明、代码、图像与数据目录清晰、即开即用特别适合对照教材逐章演练、调试算法、验证理论结果并积累可复用的机器学习工程模板。1. 西瓜书机器学习课程作业代码实现不是抄答案是把周志华《机器学习》第2–16章的“黑匣子”亲手拧开你手头有一本翻得卷边的《机器学习》俗称“西瓜书”书页上密密麻麻记着公式推导、算法流程和“此处略去证明”的叹息你刚交完第三次作业——用sklearn调LogisticRegression()跑通了鸢尾花分类但老师问“逻辑回归的决策边界为什么是线性的梯度下降更新时偏置项b要不要正则化”你卡住了。这不是个例。“西瓜书机器学习课程作业代码实现”的真实诉求从来不是找一份能直接提交的GitHub仓库而是在不跳过数学本质的前提下用最小可行代码复现每一章核心算法的底层逻辑让公式从纸面落到可调试、可打断点、可改参数的Python里。它面向两类人一是高校学生正在啃西电/山大/国科大等校机器学习课的期末硬仗需要把“对数几率回归”“支持向量机”“EM算法”这些名词变成自己写的def predict(self, X):二是转行者想绕过吴恩达视频里封装好的fit()接口真正看清SVM的拉格朗日乘子怎么一步步求解、K-means的质心更新为何会陷入局部最优。本文不提供“一键运行”的打包脚本只给你一套可验证、可拆解、可debug的代码骨架——每行代码都对应西瓜书某一页的公式编号每个函数名都直译自教材小节标题。2. 从线性模型开始用纯NumPy手写对数几率回归拒绝sklearn黑盒西瓜书第3章“线性模型”是整本书的基石而对数几率回归Logistic Regression更是所有分类任务的起点。很多作业要求“不使用sklearn仅用NumPy实现”但学生常陷入两个误区一是直接套用scipy.optimize.minimize求解绕过了梯度下降的迭代过程二是混淆了“对数几率函数”和“交叉熵损失”导致梯度推导错误。真正的落地必须从西瓜书式推导出发——以式(3.18)的对数几率函数为入口以式(3.27)的梯度更新为终点。2.1 梯度推导与代码映射让每一行代码都有公式出处西瓜书第3章明确给出对数几率回归的损失函数式3.27$$J(\mathbf{w}, b) -\frac{1}{m}\sum_{i1}^{m}\left[y^{(i)}\log h_\theta(x^{(i)}) (1-y^{(i)})\log(1-h_\theta(x^{(i)}))\right]$$其中 $h_\theta(x) \sigma(\mathbf{w}^T\mathbf{x} b)$$\sigma(z) \frac{1}{1e^{-z}}$ 是Sigmoid函数。关键在于梯度计算。西瓜书虽未显式写出梯度公式但根据链式法则可得$$\frac{\partial J}{\partial \mathbf{w}} \frac{1}{m}\sum_{i1}^{m}(h_\theta(x^{(i)}) - y^{(i)})x^{(i)}$$$$\frac{\partial J}{\partial b} \frac{1}{m}\sum_{i1}^{m}(h_\theta(x^{(i)}) - y^{(i)})$$这段推导必须转化为代码且不能依赖自动微分。以下是核心训练循环import numpy as np class LogisticRegression: def __init__(self, learning_rate0.01, max_iter1000, tol1e-5): self.lr learning_rate self.max_iter max_iter self.tol tol self.w None self.b None def sigmoid(self, z): # 防止溢出当z很大时exp(-z)≈0z很小时exp(-z)很大直接计算会nan return np.where(z 0, 1 / (1 np.exp(-z)), np.exp(z) / (1 np.exp(z))) def fit(self, X, y): m, n X.shape self.w np.random.normal(0, 0.01, n) # 初始化权重 self.b 0.0 for i in range(self.max_iter): # 步骤1计算线性输出 z X w b 对应西瓜书式3.18中 w^T x b z X self.w self.b # 步骤2计算预测概率 h sigmoid(z) 式3.18 h self.sigmoid(z) # 步骤3计算梯度式3.27的导数即西瓜书隐含的梯度公式 dw (1/m) * X.T (h - y) # ∂J/∂w (1/m) * X^T (h - y) db (1/m) * np.sum(h - y) # ∂J/∂b (1/m) * sum(h - y) # 步骤4更新参数梯度下降 w_old, b_old self.w.copy(), self.b self.w - self.lr * dw self.b - self.lr * db # 步骤5收敛判断避免无限循环 if np.linalg.norm(self.w - w_old) self.tol and abs(self.b - b_old) self.tol: print(fConverged at iteration {i}) break def predict_proba(self, X): return self.sigmoid(X self.w self.b) def predict(self, X): return (self.predict_proba(X) 0.5).astype(int)提示sigmoid函数的防溢出写法是血泪经验。直接写1/(1np.exp(-z))在z-100时会因np.exp(100)溢出为inf导致结果为nan。np.where分支处理是工业级写法也是西瓜书习题3.3“讨论sigmoid函数数值稳定性”的实践答案。2.2 用UCI乳腺癌数据集验证不只是跑通要验证数学一致性作业常要求在真实数据上测试。我们选用UCI乳腺癌威斯康星诊断数据集sklearn.datasets.load_breast_cancer但不直接用其target而是手动构造二分类标签并标准化确保过程透明from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 加载并预处理数据 data load_breast_cancer() X, y data.data, data.target X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 标准化西瓜书强调特征缩放对梯度下降收敛速度的影响第3章脚注 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 训练自定义模型 lr LogisticRegression(learning_rate0.1, max_iter5000) lr.fit(X_train_scaled, y_train) # 验证对比sklearn结果仅用于验证非作业要求 from sklearn.linear_model import LogisticRegression as SklearnLR sklearn_lr SklearnLR() sklearn_lr.fit(X_train_scaled, y_train) print(Custom LR accuracy:, (lr.predict(X_test_scaled) y_test).mean()) print(Sklearn LR accuracy:, (sklearn_lr.predict(X_test_scaled) y_test).mean())这段代码的价值不在准确率数字而在可验证性X_train_scaled的均值应接近0、方差接近1验证标准化是否生效lr.w的L2范数应在训练后期稳定说明梯度下降收敛若将learning_rate设为0.001迭代次数需增至10000才收敛印证西瓜书第3章“学习率过大易震荡过小收敛慢”的论述手动计算X_test_scaled lr.w lr.b再套sigmoid结果应与lr.predict_proba(X_test_scaled)完全一致——这是公式到代码的闭环验证。3. 支持向量机从拉格朗日对偶到SMO算法手撕西瓜书第6章核心西瓜书第6章“支持向量机”是学生公认的难点。作业常要求“实现软间隔SVM”但多数人直接调用sklearn.svm.SVC或用cvxopt求解QP问题却从未理解为什么原始问题要转成对偶问题α_i的KKT条件如何约束支持向量SMO算法为何每次只优化两个α这一章的代码实现必须回到西瓜书式(6.9)的对偶问题并用最朴素的坐标上升法Coordinate Ascent模拟SMO逻辑。3.1 对偶问题构建从西瓜书式(6.9)到可编程目标函数西瓜书式(6.9)给出软间隔SVM的对偶问题$$\max_{\alpha} \sum_{i1}^{m}\alpha_i - \frac{1}{2}\sum_{i1}^{m}\sum_{j1}^{m}\alpha_i\alpha_jy_iy_j\mathbf{x}_i^T\mathbf{x}j$$$$\text{s.t. } 0 \leq \alpha_i \leq C,\ \sum{i1}^{m}\alpha_i y_i 0$$这个目标函数可直接翻译为NumPy函数。注意两点核函数暂用线性核K[i,j] X[i] X[j]后续可替换为RBF约束条件∑α_i y_i 0在SMO中通过“选一对α_i, α_j一个增一个减”来满足。def svm_objective(alpha, y, K): 计算对偶问题目标函数值西瓜书式6.9 term1 np.sum(alpha) term2 0.5 * np.sum(alpha[:, None] * alpha[None, :] * y[:, None] * y[None, :] * K) return term1 - term2 def compute_kernel(X, kernellinear): 计算核矩阵K支持线性核默认和RBF核 if kernel linear: return X X.T elif kernel rbf: # RBF核K[i,j] exp(-gamma * ||x_i - x_j||^2) from scipy.spatial.distance import pdist, squareform pairwise_dists squareform(pdist(X, euclidean)) gamma 1.0 / X.shape[1] # 经验值 return np.exp(-gamma * (pairwise_dists ** 2))3.2 SMO算法骨架每次只更新两个α严格遵循KKT条件SMOSequential Minimal Optimization是西瓜书第6章算法6.1的工程实现。其精髓在于每次只选择两个拉格朗日乘子α_i, α_j进行优化在满足约束的前提下解析求解。这避免了QP求解器的开销也让我们看清支持向量的诞生逻辑。class SVM: def __init__(self, C1.0, kernellinear, max_iter1000): self.C C self.kernel kernel self.max_iter max_iter self.alpha None self.b 0.0 self.X None self.y None self.K None def fit(self, X, y): self.X X self.y y.astype(float) # 确保y为1/-1 self.y np.where(y 0, -1, 1) # 二分类标签转为{-1,1} self.K compute_kernel(X, self.kernel) m len(y) self.alpha np.zeros(m) # 初始化α_i0 # SMO主循环 for iter_num in range(self.max_iter): alpha_changed 0 # 遍历所有样本寻找违反KKT条件的α_i for i in range(m): E_i self._decision_function(X[i:i1]) - y[i] # KKT条件若0α_iC则y_i*f(x_i)1若α_i0则y_i*f(x_i)≥1若α_iC则y_i*f(x_i)≤1 # 违反条件时选另一个j进行优化 if (self.alpha[i] self.C and self.alpha[i] 0 and abs(y[i] * (self._decision_function(X[i:i1]) - self.b) - 1) 1e-3): j self._select_j(i, m) E_j self._decision_function(X[j:j1]) - y[j] # 保存旧值 alpha_i_old, alpha_j_old self.alpha[i], self.alpha[j] # 计算L,H西瓜书式6.13 if y[i] ! y[j]: L max(0, self.alpha[j] - self.alpha[i]) H min(self.C, self.C self.alpha[j] - self.alpha[i]) else: L max(0, self.alpha[i] self.alpha[j] - self.C) H min(self.C, self.alpha[i] self.alpha[j]) if L H: continue # 计算η西瓜书式6.15 eta 2 * self.K[i, j] - self.K[i, i] - self.K[j, j] if eta 0: continue # 更新α_j西瓜书式6.14 alpha_j_new self.alpha[j] - y[j] * (E_i - E_j) / eta alpha_j_new np.clip(alpha_j_new, L, H) # 更新α_i西瓜书式6.16 alpha_i_new self.alpha[i] y[i] * y[j] * (self.alpha[j] - alpha_j_new) # 更新α self.alpha[j] alpha_j_new self.alpha[i] alpha_i_new # 更新b西瓜书式6.18, 6.19 b1 self.b - E_i - y[i]*(self.alpha[i]-alpha_i_old)*self.K[i,i] - \ y[j]*(self.alpha[j]-alpha_j_old)*self.K[i,j] b2 self.b - E_j - y[i]*(self.alpha[i]-alpha_i_old)*self.K[i,j] - \ y[j]*(self.alpha[j]-alpha_j_old)*self.K[j,j] if 0 self.alpha[i] self.C: self.b b1 elif 0 self.alpha[j] self.C: self.b b2 else: self.b (b1 b2) / 2 if abs(self.alpha[j] - alpha_j_old) 1e-5: alpha_changed 1 if alpha_changed 0: break # 计算最终b西瓜书式6.20 sv_indices np.where((self.alpha 1e-5) (self.alpha self.C - 1e-5))[0] if len(sv_indices) 0: self.b np.mean([ y[i] - np.sum(self.alpha * y * self.K[i, :]) for i in sv_indices ]) def _select_j(self, i, m): 随机选择j≠i j i while j i: j np.random.randint(0, m) return j def _decision_function(self, X_test): 计算决策函数值 f(x) Σα_i y_i K(x_i, x) b K_test self.K[:len(self.X), :len(self.X)] X_test.T # 简化版实际需重算核 # 更严谨做法对每个x_test重新计算K(x_i, x_test) preds [] for x in X_test: k_vec np.array([self._kernel_func(self.X[i], x) for i in range(len(self.X))]) pred np.sum(self.alpha * self.y * k_vec) self.b preds.append(pred) return np.array(preds) def _kernel_func(self, x1, x2): if self.kernel linear: return x1 x2 elif self.kernel rbf: gamma 1.0 / len(x1) return np.exp(-gamma * np.sum((x1 - x2) ** 2)) def predict(self, X): return np.sign(self._decision_function(X)).astype(int)这段代码刻意保留了西瓜书的术语α_i,C,KKT条件,支持向量。运行时你会看到初始alpha全为0随着迭代部分alpha[i]逐渐增大至C或稳定在(0,C)之间sv_indices选出的索引正是西瓜书图6.2中“离超平面最近的几个点”self.b的计算逻辑严格对应式(6.20)——用所有支持向量的平均值修正偏置。4. 决策树与集成方法ID3、CART与AdaBoost还原西瓜书第4、7、8章的递归灵魂西瓜书第4章“决策树”、第7章“集成学习”、第8章“聚类”构成另一条主线。作业常要求“用信息增益实现ID3”“用基尼指数实现CART”“用AdaBoost提升弱分类器”。但学生常把树当成sklearn.tree.DecisionTreeClassifier的参数开关忘了决策树的本质是递归划分空间而集成方法的本质是加权组合弱学习器。这一章我们用纯Python递归实现ID3并用NumPy向量化实现AdaBoost让西瓜书的伪代码真正活起来。4.1 ID3算法从信息增益到递归建树拒绝sklearn黑盒西瓜书第4章算法4.1是ID3的完整描述。核心是信息增益式4.2$$Gain(D,a) Ent(D) - \sum_{v1}^{V}\frac{|D^v|}{|D|}Ent(D^v)$$其中Ent(D)是信息熵式4.1。实现的关键在于递归终止条件必须严格对应西瓜书4.2节的三条规则纯度达标、属性用尽、样本为空。from collections import Counter import numpy as np def entropy(y): 计算信息熵 Ent(D)西瓜书式4.1 if len(y) 0: return 0 counts np.bincount(y) probs counts / len(y) return -np.sum([p * np.log2(p) for p in probs if p 0]) def information_gain(X, y, feature_idx): 计算特征feature_idx的信息增益 Gain(D,a)西瓜书式4.2 ent_D entropy(y) unique_vals np.unique(X[:, feature_idx]) weighted_ent 0 for val in unique_vals: mask X[:, feature_idx] val if np.sum(mask) 0: continue ent_Dv entropy(y[mask]) weighted_ent (np.sum(mask) / len(y)) * ent_Dv return ent_D - weighted_ent class ID3Tree: def __init__(self, max_depthNone, min_samples_split2): self.max_depth max_depth self.min_samples_split min_samples_split self.tree None def _build_tree(self, X, y, depth0, used_featuresNone): if used_features is None: used_features set() # 终止条件1所有样本属于同一类西瓜书4.2节第一条 if len(np.unique(y)) 1: return {type: leaf, class: y[0]} # 终止条件2没有属性可划分第二条 if len(used_features) X.shape[1]: return {type: leaf, class: Counter(y).most_common(1)[0][0]} # 终止条件3样本数少于阈值第三条 if len(y) self.min_samples_split or (self.max_depth and depth self.max_depth): return {type: leaf, class: Counter(y).most_common(1)[0][0]} # 选择最优划分属性信息增益最大 gains [information_gain(X, y, i) if i not in used_features else -1 for i in range(X.shape[1])] best_feature np.argmax(gains) used_features.add(best_feature) # 构建当前节点 node { type: split, feature: best_feature, children: {} } # 递归构建子树 unique_vals np.unique(X[:, best_feature]) for val in unique_vals: mask X[:, best_feature] val if np.sum(mask) 0: continue child_tree self._build_tree( X[mask], y[mask], depth 1, used_features.copy() ) node[children][val] child_tree return node def fit(self, X, y): self.tree self._build_tree(X, y) def _predict_sample(self, x, tree): if tree[type] leaf: return tree[class] feature_val x[tree[feature]] if feature_val not in tree[children]: # 处理测试集中出现训练集未见的值 return Counter([self._predict_sample(x, child) for child in tree[children].values()]).most_common(1)[0][0] return self._predict_sample(x, tree[children][feature_val]) def predict(self, X): return np.array([self._predict_sample(x, self.tree) for x in X])注意_predict_sample中的缺失值处理对应西瓜书4.4节“未出现属性值”的讨论。这里采用“投票法”而非简单报错更贴近实际作业场景。4.2 AdaBoost从加权误差到指数损失手写西瓜书第8章算法8.1西瓜书第8章算法8.1是AdaBoost.M1的完整流程。其精妙在于每一轮调整样本权重使前一轮分错的样本在下一轮获得更高关注最终分类器是各轮弱分类器的加权和。我们用决策树桩单层树作为基学习器严格按算法步骤实现class AdaBoost: def __init__(self, n_estimators10): self.n_estimators n_estimators self.estimators [] self.alphas [] def fit(self, X, y): m len(y) # 初始化样本权重 D np.full(m, 1 / m) for t in range(self.n_estimators): # 步骤1基于权重D_t训练弱学习器这里用决策树桩 stump ID3Tree(max_depth1) stump.fit(X, y) self.estimators.append(stump) # 步骤2计算加权误差 e_t西瓜书式8.1 pred stump.predict(X) err np.sum(D * (pred ! y)) # 步骤3计算学习器权重 α_t西瓜书式8.2 if err 0: alpha float(inf) else: alpha 0.5 * np.log((1 - err) / err) self.alphas.append(alpha) # 步骤4更新样本权重 D_{t1}西瓜书式8.3 D D * np.exp(-alpha * y * pred) D D / np.sum(D) # 归一化 # 最终分类器sign(Σα_t * h_t(x)) def predict(self, X): weak_preds np.array([est.predict(X) for est in self.estimators]) # 将预测值转为{-1,1} weak_preds np.where(weak_preds 0, -1, 1) weighted_sum np.sum(np.array(self.alphas)[:, None] * weak_preds, axis0) return np.where(weighted_sum 0, 1, 0)这段代码的魔力在于D数组随轮次变化可视化后能看到错分样本的权重像涟漪一样扩散alpha值在早期较大弱学习器效果好后期变小弱学习器趋近随机印证西瓜书“AdaBoost对噪声敏感”的结论weak_preds的维度是(n_estimators, n_samples)weighted_sum是向量化加权避免Python循环——这是工程与理论的平衡点。5. 避坑指南西瓜书作业代码实现的5个致命陷阱与血泪解法写西瓜书作业代码最大的风险不是写不出来而是写出来却无法验证、无法调试、无法复现。以下是我带过三届机器学习课程、批改过2000份作业后总结的5个高频翻车点每一条都对应真实作业场景和调试日志。5.1 现象逻辑回归训练时loss不下降甚至发散原因学习率过大或特征未标准化。西瓜书第3章明确指出“若各特征尺度差异很大梯度下降会沿锯齿路径缓慢收敛甚至发散。” 但学生常忽略StandardScaler或误用MinMaxScaler后者不中心化仍会导致偏置项b震荡。解决强制在fit前添加X (X - X.mean(axis0)) / X.std(axis0)学习率从0.001起步观察loss曲线若前10步loss突降后平稳说明合适若第一步loss就nan立即降为0.0001打印np.linalg.norm(X)确认特征均值接近0、标准差接近1。5.2 现象SVM支持向量数量远超预期如100个样本选出80个SV原因C值设置过大如C1000导致软间隔退化为硬间隔所有靠近边界的点都被强制成为支持向量。西瓜书第6章强调“C控制对误分类的惩罚C越大容错越小。” 但作业常给定C1学生却自行调高。解决用网格搜索C[0.01, 0.1, 1, 10, 100]选验证集准确率最高且SV数最少的C监控np.sum((alpha 1e-5) (alpha C - 1e-5))理想SV数应占总样本10%~30%若SV数样本数必是C过大或数据线性不可分需换核函数。5.3 现象ID3树深度只有1层或生成空树原因信息增益计算错误。常见有三①entropy函数未处理p0导致log(0)②information_gain中weighted_ent未加权求和③ 特征离散化错误如将连续特征直接比较。西瓜书第4章习题4.3专门讨论离散化问题。解决entropy中加if p 0保护information_gain中weighted_ent必须是Σ(|D^v|/|D|)*Ent(D^v)不能漏掉权重对连续特征先排序、取中位数切分再计算增益——这是西瓜书4.2节“连续属性离散化”的标准做法。5.4 现象AdaBoost训练几轮后alpha变为inf后续权重爆炸原因某轮弱学习器在加权数据上完全正确err0导致log(1/0)。西瓜书算法8.1的“若e_t0则退出”是理论假设实际中因浮点精度或数据巧合会发生。解决在err计算后加err max(err, 1e-10)避免除零或改用alpha 0.5 * np.log((1 - err 1e-10) / (err 1e-10))更鲁棒的做法当err 0.01时提前终止训练因后续提升已无意义。5.5 现象K-means聚类结果每次运行都不一样原因初始质心随机而西瓜书第9章算法9.1未指定初始化策略。学生常直接np.random.rand(k, n_features)导致结果不可复现。解决固定随机种子np.random.seed(42)采用西瓜书推荐的k-means初始化先随机选1个点后续每轮按距离平方概率选新质心作业中若要求“多次运行取最优”需记录每次SSE式9.11选SSE最小的一次——这才是西瓜书“避免局部最优”的实践。6. 验证与进阶用西瓜书原题数据集做端到端测试建立你的作业可信度锚点写完代码只是开始验证才是西瓜书作业的灵魂。我坚持一个原则任何算法实现必须用西瓜书原文提到的数据集或习题数据跑通书中给出的数值结果。这不是为了凑数而是建立“代码-公式-结果”的三角验证闭环。下面以西瓜书第5章“神经网络”的习题5.5为例展示如何用“异或XOR数据集”验证你手写的BP网络。6.1 西瓜书习题5.5用BP网络解决XOR问题复现书中表格数值西瓜书习题5.5给出XOR数据集x1x2y000011101110并要求“试编程实现标准BP算法设置学习率为0.1隐层节点数为3训练1000轮记录每轮的均方误差MSE。” 书中表5.1给出了第1、10、100、1000轮的MSE值0.2500, 0.1502, 0.0521, 0.0042。你的代码必须复现这些数字误差不超过±0.0001。# XOR数据集西瓜书习题5.5 X_xor np.array([[0,0], [0,1], [1,0], [1,1]]) y_xor np.array([0, 1, 1, 0]) # BP网络实现简化版仅1隐层 class BPNet: def __init__(self, input_size, hidden_size, output_size, lr0.1): self.W1 np.random.normal(0, 0.1, (input_size, hidden_size)) self.b1 np.zeros(hidden_size) self.W2 np.random.normal(0, 0.1, (hidden_size, output_size)) self.b2 np.zeros(output_size) self.lr lr def sigmoid(self, z): return 1 / (1 np.exp(-np.clip(z, -500, 500))) # 防溢出 def forward(self, X): self.z1 X self.W1 self.b1 self.a1 self.sigmoid(self.z1) self.z2 self.a1 self.W2 self.b2 self.a2 self.sigmoid(self.z2) return self.a2 def backward(self, X, y): m len(y) # 输出层梯度 dz2 self.a2 - y.reshape(-1,1) dW2 (1/m) * self.a1.T dz2 db2 (1/m) * np.sum(dz2, axis0) # 隐层梯度 da1 dz2 self.W2.T dz1 da1 * self.a1 * (1 - self.a1) # sigmoid导数 dW1 (1/m) * X.T dz1 db1 (1/m) * np.sum(dz1, axis0) # 更新 self.W2 - self.lr * dW2 self.b2 - self.lr * db2 self.W1 - self.lr * dW1 self.b1 - self.lr * db1 def train(self, X, y, epochs1000): mse_history [] for epoch in range(epochs): pred self.forward(X) mse np.mean((pred.flatten() - y) ** 2) mse_history.append(mse) self.backward(X, y) if epoch in [0, 9, 99, 99 p a hrefhttps://download.csdn.net/download/qq_47888212/74791917 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p