恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

机器学习课设实战:线性回归分类与手写数字识别的从零实现和调库对比

  • 首页
  • 资讯中心
  • /
  • 机器学习课设实战:线性回归分类与手写数字识别的从零实现和调库对比

相关资讯

Claude Code UI WebSocket 机制:一条消息的完整旅程,断线 3 秒自动重连 2026/9/12 1:53:55
go2rtc 接 GoPro 画面突然黑屏:3 条路径解决睡眠导致的流中断 2026/9/12 1:53:55
SGM66052国产电源芯片的系统级供电重构 2026/9/12 1:53:55

最新资讯

TaskMaster 任务永久删除指南:remove-task 命令的完整实战与源码解析
基于 HelloAgents 与 FastAPI 构建多智能体健康档案助手:体检报告解读、饮食推荐与 Reflect 反馈闭环
QGroundControl地面站与PX4配置:从校淮到仿真,手把手调稳无人机
AI即推GEO:构建动态意图-空间-行为耦合的智能推荐系统
Windows Server与SQL Server备份策略与实战指南
Foundry智能合约开发框架:高性能Solidity测试与部署实践

今日推荐

MATLAB仿生优化框架:长鼻浣熊算法多策略融合实现
【JAVA毕设源码分享】基于 JavaWeb 的校园一卡通管理系统的设计与实现 基于 JavaWeb 的校园卡业务管理系统(程序+文档+代码讲解+一条龙定制)
【JAVA毕设源码分享】基于 Java 的图书馆借阅管理平台的搭建与实现 基于 Java 的图书馆综合管理系统(程序+文档+代码讲解+一条龙定制)

本周热门

超人会飞不算本事:系统稳定依赖清晰规则与边界设计
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
基于CNN的调制信号识别:MATLAB实现时频图分类实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

机器学习课设实战:线性回归分类与手写数字识别的从零实现和调库对比

发布时间:2026/9/12 1:58:56
机器学习课设实战:线性回归分类与手写数字识别的从零实现和调库对比 简介面向机器学习课程结课大课设的完整资源包聚焦线性回归分类与手写数字识别两大基础主题适合初学机器学习、需要完成课程设计或巩固建模流程的学生。压缩包内共11个文件包括7个Python脚本和4个MNIST手写数字数据集文件idx格式整体约11MB。其中线性回归分类部分同时提供自设计实现与调用sklearn库的高斯函数回归、幂函数回归等对比版本手写数字识别部分覆盖原始像素建模、带标签数据训练及KMeans调库方案便于从原理到工具逐层理解。已有646人学习下载资源自带完整数据集省去单独下载MNIST的麻烦适合对照两种实现方式梳理数据预处理、模型训练、评估和调参思路也能为课程答辩和实验报告提供参考。1. 这门课设真正要交的不是模型而是两套程序的对比很多人在学期末拿到机器学习课设题目线性回归分类、手写数字识别时第一反应是找现成仓库把代码一放跑通就算完。但你仔细看标题里括号那句设计程序和调库程序都有就知道这门课设的考核点根本不是模型的准确率而是你能不能分别用从零实现和调现成库这两条路径解决同一个问题并且说清楚两者之间的差距。设计程序要求你从随机初始化、梯度下降、反向传播这些底层步骤写起调库程序则要求你用 sklearn 或 PyTorch 快速构建一个可用的方案。对于打算应付过去的人这文能帮你在三天内把两条路都走通对于想真正弄懂机器学习流程的人这里讲的边界和参数思想能让你答辩时不露怯。我会顺着课设最常见的拆分方式来讲先用线性回归做二分类看你手写梯度下降和调 sklearn 差多少再上手写数字识别手写一个两层 BP 神经网络对比 PyTorch 里的 CNN。中间穿插数据预处理、损失函数、调参与可视化这些课设报告里必须有的环节最后给你答辩前必须验证的几个点。2. 线性回归分类从最小二乘到梯度下降的手写实现与调库对照2.1 为什么课设里线性回归分类不等于分类器线性回归本质是拟合连续值输出但课设题目里把线性回归分类放在一起实际场景通常有两种一种是用线性回归拟合 0/1 类别标签然后在预测时加一个阈值比如输出大于 0.5 判为正类另一种就是直接用逻辑回归线性部分接一个 sigmoid 函数。前者是线性回归做分类的朴素直译后者的数学推导里保留着线性假设的核心所以很多课设报告干脆把逻辑回归也归在线性回归分类一节。你需要清楚两个关键差异线性回归直接拟合标签损失是均方误差梯度对离群点非常敏感逻辑回归通过 sigmoid 压缩输出损失用交叉熵梯度更平稳。课设里如果只给线性回归分类这个说法我一般建议两份代码都写手写部分就用线性回归加阈值因为实现简单、原理好讲调库部分用逻辑回归因为 sklearn 里没有线性回归分类器这种现成命名你可以在报告里注明逻辑回归是线性回归在分类任务上的扩展。答辩时这个问题几乎是必问的。再往下说无论选哪种核心都是线性假设输入特征与输出关系近似一条直线或者高维超平面。手写实现的意义不是造轮子而是让你亲眼看到损失函数怎么收敛、梯度方向怎么更新、偏置项从哪来。调库实现的意义则是让你理解封装好的 API 背后替你做了什么——标准化、正则化、迭代终止条件。2.2 手写线性回归分类纯 NumPy 梯度下降2.2.1 数据准备与模型假设我用 sklearn 自带的乳腺癌数据集做演示它适合做二分类特征维数不多能直观看到线性模型的效果。设计程序的核心代码放在一个模块里方便和调库程序共享数据加载逻辑。import numpy as np from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split data load_breast_cancer() X data.data y data.target.astype(float) # 0/1 标签 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) # 标准化对手写梯度下降非常重要 mean X_train.mean(axis0) std X_train.std(axis0) X_train (X_train - mean) / std X_test (X_test - mean) / std标准化这里不能省。线性回归的梯度与特征尺度成正比如果不做标准化学习率只能取很小值否则会震荡标准化以后所有特征均值为 0、方差为 1梯度方向更接近球形等高线收敛速度快很多。train_test_split 里的 random_state 固定下来是为了让手写程序和调库程序在同一份数据上比较。2.2.2 训练函数与损失计算class LinearRegressionClassifier: def __init__(self, lr0.01, epochs1000, threshold0.5): self.lr lr self.epochs epochs self.threshold threshold self.w None self.b None def fit(self, X, y): m, n X.shape self.w np.random.randn(n) * 0.01 self.b 0.0 for epoch in range(self.epochs): pred X self.w self.b loss np.mean((pred - y) ** 2) # 梯度的向量化表达 grad_w (2 / m) * X.T (pred - y) grad_b (2 / m) * np.sum(pred - y) self.w - self.lr * grad_w self.b - self.lr * grad_b if epoch % 200 0: print(fepoch {epoch}, loss {loss:.4f}) def predict(self, X): return (X self.w self.b self.threshold).astype(int) model LinearRegressionClassifier(lr0.1, epochs2000) model.fit(X_train, y_train) acc np.mean(model.predict(X_test) y_test) print(手工线性回归分类准确率:, acc)代码逻辑很简单每次迭代计算预测值与真实标签的差值然后分别对 w 和 b 求梯度用学习率更新参数。注意这里损失函数用了均方误差梯度除以 m 是为了让梯度值不随样本数变大而变大。grad_w是关键X.T (pred - y)是特征矩阵转置乘残差形状正好是 (n,)也就是每个特征的梯度。实际课设中经常有人把转置写反导致维度对不上可以用X.T 残差这个固定句式记忆。调学习率时要注意lr0.1 在标准化后的数据上通常没问题如果 loss 交叉震荡就降到 0.01如果 loss 下降太慢可以尝试 0.5 并观察是否发散。epochs 不宜死磕固定值最好在循环里加一个终止条件——当连续 20 次迭代 loss 变化小于 1e-5 就停止省时间。2.3 调库版本用 sklearn 一行完成from sklearn.linear_model import LinearRegression, LogisticRegression # 线性回归 阈值 lr_clf LinearRegression() lr_clf.fit(X_train, y_train) pred_linear (lr_clf.predict(X_test) 0.5).astype(int) acc_linear np.mean(pred_linear y_test) # 逻辑回归更推荐作为本题的调库程序 lgr LogisticRegression(max_iter1000, C1.0) lgr.fit(X_train, y_train) acc_lgr lgr.score(X_test, y_test) print(sklearn LinearRegression 阈值:, acc_linear) print(sklearn LogisticRegression:, acc_lgr)sklearn 的 LinearRegression 用的是最小二乘解析解没有迭代也没有学习率它内部通过 SVD 分解直接求闭式解。这个调库程序和手写程序放在一起能说明一件重要的事解析解和梯度下降解的差距不在结果而在路径。你的手写代码能展示优化过程和学习率的影响调库代码能展示当最优解可以直接算出来时迭代是否必要。另一个建议是同时调用 LogisticRegression因为它在分类任务里的实际表现明显更好把它作为调库程序你报告里的对比就有三层手写线性回归、sklearn 线性回归、sklearn 逻辑回归这样内容量足够支撑三千字的报告。需要注意C1.0是正则化强度的倒数C 越小正则化越强。在特征数不多且标准化良好的数据上C 等于 1.0 是默认值效果稳定。如果训练集准确率高于测试集 3 个点以上把 C 降到 0.1 试试可以缓解过拟合。2.4 参数怎么调学习率、迭代次数、特征缩放手写线性回归分类里你真正能调的超参数只有三个学习率 lr、迭代次数 epochs、阈值 threshold。学习率的常见范围是 0.001 到 1.0指数级尝试不要用 0.3 到 0.4 这种线性猜测。判断标准是 loss 曲线如果前几百次迭代 loss 持续下降说明 lr 合适如果 loss 发散立刻调小 lr如果 loss 下降缓慢可以调大 lr 但要注意震荡。迭代次数不是越多越好。你可以观察最后 10% 迭代的 loss 变化如果小于 1e-5说明模型已经收敛再训也没有用。阈值 threshold 按理说不需要调对于二分类 0.5 是天然的但如果你的类别不平衡比如正类样本只占 10%手写程序的输出平均值会偏向负类这时把阈值调到训练集正样本比例附近准确率会提升。这种调法体现了分类阈值是可优化决策边界的思想答辩时提一句很加分。特征缩放是手写线性回归的生命线。除了标准化到均值为 0、方差为 1还可以用 min-max 缩放到 [0,1] 区间。两者的差别是标准化保留了分布形状对离群点不敏感min-max 会把离群点压缩到边界。对于线性回归这两种都行但 sklearn 的线性回归接口不接受 NaN所以缩放前要确认数据没有缺失值。3. 手写数字识别从零实现 BP 神经网络与 PyTorch 调库之间的差距3.1 MNIST 数据集加载与预处理手写数字识别最经典的基准是 MNIST28×28 的灰度图每张像素值范围 0 到 255共 10 个类别。课设里一般要求训练集 60000 张、测试集 10000 张。但很多人加载数据直接用 torchvision 或 keras 的下载接口这没问题不过手写程序部分需要的是纯 NumPy 数据格式所以我建议统一用 torchvision 下载后转成 ndarray或者直接加载 scikit-learn 自带的fetch_openml版本。后者对教育场景更友好不需要碰深度学习框架。from sklearn.datasets import fetch_openml import numpy as np mnist fetch_openml(mnist_784, version1, as_frameFalse) X mnist.data.astype(np.float32) / 255.0 y mnist.target.astype(np.int32) # 划分训练/测试集前 60000 张训练后 10000 张测试 X_train, y_train X[:60000], y[:60000] X_test, y_test X[60000:], y[60000:]as_frameFalse直接返回 ndarray省去转换除以 255 是必需的归一化操作否则手写 BP 的梯度极容易爆炸。这个数据集里每张图被拉成 784 维向量但标签还是整型而手写神经网络的输出层需要 one-hot 编码def one_hot(labels, num_classes10): eye np.eye(num_classes) return eye[labels] y_train_oh one_hot(y_train) y_test_oh one_hot(y_test)np.eye(num_classes)[labels]是构造 one-hot 的快速写法不需要写循环。3.2 设计程序手写两层神经网络NumPy 实现3.2.1 前向传播与反向传播的实现课设里的设计程序部分我不想直接给一个三层以上的复杂网络因为反向传播的链式法则在层数多了之后很难手动查错。这里实现一个隐藏层 64 个神经元、输出层 10 神经元的全连接网络激活函数用 ReLU输出层用 softmax 加交叉熵损失这就是 Mini-Batch 梯度下降的核心结构。class SimpleNN: def __init__(self, input_size784, hidden_size64, output_size10, lr0.1): self.lr lr self.W1 np.random.randn(input_size, hidden_size) * np.sqrt(1.0 / input_size) self.b1 np.zeros(hidden_size) self.W2 np.random.randn(hidden_size, output_size) * np.sqrt(1.0 / hidden_size) self.b2 np.zeros(output_size) def forward(self, X): self.z1 X self.W1 self.b1 self.a1 np.maximum(0, self.z1) # ReLU 激活 self.z2 self.a1 self.W2 self.b2 exp_z np.exp(self.z2 - np.max(self.z2, axis1, keepdimsTrue)) self.a2 exp_z / exp_z.sum(axis1, keepdimsTrue) # softmax return self.a2 def backward(self, X, y_oh): m X.shape[0] pred self.a2 dz2 pred - y_oh # softmax 交叉熵的简化梯度 dW2 self.a1.T dz2 / m db2 dz2.mean(axis0) da1 dz2 self.W2.T dz1 da1 * (self.z1 0) # ReLU 的导数 dW1 X.T dz1 / m db1 dz1.mean(axis0) self.W2 - self.lr * dW2 self.b2 - self.lr * db2 self.W1 - self.lr * dW1 self.b1 - self.lr * db1初始化权重的尺度乘以sqrt(1/input_size)是 Xavier 初始化的简化版本防止前向传播时神经元输出方差过大或者过小。dz2 pred - y_oh是这段代码里最值得记住的结论——如果你使用 softmax 输出和交叉熵损失反向传播到输出层之前的梯度恰好等于预测值减真值不需要手算 log 的导数。ReLU 的导数在正向大于零时是 1否则是 0代码里(self.z1 0)生成的就是一个掩码矩阵。3.2.2 训练循环与准确率计算def train(model, X, y_oh, epochs20, batch_size64): n X.shape[0] for epoch in range(epochs): indices np.random.permutation(n) for start in range(0, n, batch_size): batch_idx indices[start:start batch_size] model.forward(X[batch_idx]) model.backward(X[batch_idx], y_oh[batch_idx]) pred model.forward(X) acc np.mean(np.argmax(pred, axis1) np.argmax(y_oh, axis1)) if epoch % 5 0: print(fepoch {epoch}, train acc {acc:.4f}) model SimpleNN(lr0.3, hidden_size64) train(model, X_train, y_train_oh, epochs20) test_pred model.forward(X_test) print(手写 BP 测试准确率:, np.mean(np.argmax(test_pred, axis1) y_test))训练循环里每个 epoch 都打乱样本顺序这是 Mini-Batch 梯度下降能否跳出局部鞍点的关键。batch_size 取多少合适课设场景下 64 是稳妥选择——太小比如 1梯度噪声大收敛慢太大比如 512每个 epoch 更新次数少也慢。lr 取 0.3 是配合 ReLU 初始化和数据归一化的经验值你可以尝试 0.1 到 1.0。这个手写网络在 MNIST 测试集上通常能到 0.90 到 0.93 的准确率跑 20 轮在普通笔记本上大约需要一到两分钟。如果你发现准确率只有 0.8先检查标准化像素没除以 255 的话梯度会以 784 的规模累积等于没调。别忘了在验证集上算准确率时不再做反向传播只是 forward。argmax把每个样本的输出向量变成预测类别和真实标签比较就算出准确率。3.3 调库程序PyTorch 实现一个简单 CNN调库部分如果继续用全连接网络和手写 BP 的差别只是框架不够有说服力。我一般建议直接用 PyTorch 搭一个小型 CNN因为卷积层能展示调库让我们可以轻松使用高级结构这个观点。import torch import torch.nn as nn class CNN(nn.Module): def __init__(self): super().__init__() self.conv nn.Sequential( nn.Conv2d(1, 32, kernel_size3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(), nn.MaxPool2d(2) ) self.fc nn.Sequential( nn.Linear(64 * 7 * 7, 128), nn.ReLU(), nn.Linear(128, 10) ) def forward(self, x): x self.conv(x) x x.view(x.size(0), -1) x self.fc(x) return x def train_pytorch(model, train_loader, epochs5): criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) model.train() for epoch in range(epochs): total_loss 0 for images, labels in train_loader: optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() print(fepoch {epoch}, loss {total_loss / len(train_loader):.4f})PyTorch 代码的核心是 DataLoader 和优化器。CrossEntropyLoss内部封装了 softmax 和负对数似然所以你不需要在模型 forward 里手动加 softmaxAdam优化器自带学习率自适应不需要手动调整学习率衰减策略。训练一个 epoch 只需遍历 DataLoader每批次数据送到模型前向算 loss 后反向传播优化器更新参数。这个代码在 MNIST 上训练 5 个 epoch测试准确率可以轻松到达 0.99而手写 BP 到 0.93 就已经触顶。注意输入形状nn.Conv2d期望输入是(batch, channel, height, weight)所以加载数据时要把(batch, 28, 28)reshape 成(batch, 1, 28, 28)。如果你用 DataLoader 默认的 MNIST 数据集它已经自动把尺寸转成 4 维张量了。3.4 手写BP和调库CNN的边界在哪两个程序的差距体现在三个地方参数量、运算结构和训练技巧。手写 BP 的参数是 784×64 64×10 50760 个而硬编码的两层卷积 CNN 参数约 5 万个左右其实相差不大。真正决定准确率差异的是结构CNN 通过卷积的局部感受野和参数共享天然编码了 像素邻近关系 的先验知识全连接网络每个像素都是独立特征要求大量数据才能学到空间不变性。调库程序除了框架还带来了批归一化、Dropout、数据增强这些现成组件。你可以在 PyTorch 里加两个批归一化层nn.BatchNorm2d然后和手写程序对比结论会更有说服力同样的迭代次数加了归一化的模型收敛更快测试准确率更高。答辩时你不需要背诵论文只需要说清楚一点调库是让框架替我们处理了底层梯度计算但模型结构的选择、损失函数的设计、超参数调整仍然需要我们决策。4. 两套程序的工程化组装数据共享、结果对比与可视化4.1 工程目录怎么安排课设提交通常要一个压缩包里面包含源码、数据、报告。我习惯按功能分模块避免老师打开就是一个 Jupyter Notebook 跑到底。推荐目录结构如下. ├── data/ # 原始数据脚本会自动下载或加载 ├── models/ │ ├── manual/ │ │ ├── linear_regression.py │ │ ├── bp_network.py │ │ └── train_bp.py │ └── library/ │ ├── sklearn_classifiers.py │ └── pytorch_cnn.py ├── utils/ │ ├── load_data.py # 统一的数据加载与预处理 │ └── metrics.py # 准确率、混淆矩阵、绘图 ├── results/ # 生成的图表和模型参数 └── report.md这个结构的好处是手写程序和调库程序之间存在清晰的隔离但数据预处理共享。load_data.py应当包含 MNIST 的两种加载方式sklearn 和 torchvision并统一输出 NumPy 数组metrics.py里放plot_confusion_matrix和plot_loss_curve两个函数避免在两个程序里重复写。4.2 统一的数据加载与评估指标无论手写还是调库都要保证训练集和测试集划分一致。MNIST 是官方划分好的但为了防作弊很多学校会要求自定义划分。我提供一个简单可靠的划分逻辑from sklearn.model_selection import StratifiedShuffleSplit def split_train_test(X, y, test_ratio0.2, save_pathresults/split_indices.npy): split StratifiedShuffleSplit(n_splits1, test_sizetest_ratio, random_state42) train_idx, test_idx next(split.split(X, y)) np.save(save_path, np.vstack([train_idx, test_idx])) return train_idx, test_idxStratifiedShuffleSplit会保证训练集和测试集里每个数字的比例接近原始分布比随机切分更公平。保存索引到 npy 文件可以保证手写程序和调库程序用同一批索引结果才具备可比性。评估指标用准确率只是基本要求建议加一个混淆矩阵尤其是手写数字识别里 9 和 4、3 和 8 这种易混淆数字混淆矩阵能直观看出模型在哪类样本上犯错。4.3 对比表格从准确率、训练时间、代码量看差异程序模型结构准确率约训练时间约代码行数约手写线性回归分类线性层 阈值0.94乳腺癌1s40sklearn 逻辑回归线性层0.961s8手写 BP 网络784-64-100.921-2 分钟90PyTorch CNN卷积全连接0.992-3 分钟60这个表格的明确信息有两个同一类任务中手写与调库的准确率差距大约 2 到 7 个百分点差距主要来自模型结构而非实现方式代码行数并不直接反映复杂度sklearn 的 8 行封装了矩阵分解、优化、交叉验证等等机制而手写 BP 的 90 行是纯计算逻辑。写报告时你应该把训练时间、准确率、代码行数都标注上但更重要的是在表格后追加一段分析——为什么手写 BP 用时和 CNN 差不多准确率却低因为 CNN 用卷积减少了参数冗余同时在同样参数规模下有了归纳偏置。这个分析段比表格本身更能体现你做了思考。可视化方面至少要有三个图线性回归的 loss 下降曲线、手写 BP 的训练准确率曲线、CNN 在测试集上的混淆矩阵。loss 曲线用 matplotlib 画X 轴是 epochY 轴是 loss注意手写 BP 的 loss 是每个 epoch 的整体平均画出来应该是平滑下界的曲线如果有剧烈抖动说明学习率太大。5. 答辩前必须会回答的四个问题和三个验证技巧5.1 老师必问为什么手写的收敛慢这个问题通常是答辩顺序里第一个出现的。你不需要回避手写程序慢可以从三方面回答一是手写网络只用全连接没有卷积和池化所以输入图像的两维空间结构没有被利用二是手写用了固定学习率而 PyTorch 的 Adam 会自适应调整每个参数的更新步长三是手写代码没有批归一化特征分布在每层传导后会剧烈变化导致收敛需要更多 epoch。如果你用了 Mini-Batch还可以补充说已经通过打乱样本顺序缓解了梯度噪声但用一个合适的 batch size 本身就是超参数搜索的工作。5.2 用学习曲线判断欠拟合与过拟合在你的训练循环里记录每个 epoch 的训练集准确率和验证集准确率然后画在同一张图上。如果训练集准确率一直高于验证集且两者差距变大说明过拟合解决办法是加正则化或减小模型容量如果两条曲线都低且平行说明欠拟合需要增加迭代次数或调整初始化。手写 BP 里最简单的正则化是 L2 权重衰减在backward里把dW加上self.lr * self.W的一部分比如self.W2 - self.lr * (dW2 0.001 * self.W2)。调库版本里 PyTorch 的 Adam 优化器带了weight_decay参数设成1e-4即可。5.3 输出权重可视化验证模型真的学到了特征线性回归分类里你可以把model.w按特征维度排序找出权重绝对值最大的前 10 个特征打印出这些特征的名字。在乳腺癌数据集里这些特征通常对应最差面积、凹陷点等医学指标。BP 神经网络里可以把第一层权重矩阵的第 i 行 reshape 成 28×28 图像并显示你会看到类似这种数字的笔画方向的模式——某些行看起来像 1 的形状某些像 0 的空心轮廓。这是证明你的手写模型不是随机拟合的最好方式答辩时展示一张 64 个小格子的权重图比任何文字都有说服力。最后提一个重要验证技巧把调库程序和手写程序的特征图放在一起对比你会直观看到 CNN 第一层卷积核学习到的是边缘和角点检测器而 BP 学习到的是全局的笔画分布。这个对比既能展示你对两种模型的理解也正好呼应了课设题目落的设计程序和调库程序都有的初心——你要做的就是清楚地把它们各自的长处和局限说出来而不是让两个程序互相掩盖。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号