当前位置: 首页 > news >正文

构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化

一. 什么是随机森林

        随机森林是一种非常强大的机器学习算法,它属于集成学习中的Bagging方法。随机森林的基本单元是决策树,而森林就是由很多棵决策树组成的。简单的说随机森林就是由多个决策树组成的森林,每棵树都是一个独立的预测模型。通过集体投票或平均意见来做出最终决策,这样比单棵决策树更准确、更稳定。

        随机森林的核心思想是:通过构建多个决策树,并将它们的预测结果结合起来,从而获得比单个决策树更准确、更稳定的预测。

随机森林的重要性:

  • 它能够处理大规模的数据集,并且具有很高的准确性。
  • 它能够处理高维数据,并且不需要降维。
  • 它能够评估各个特征的重要性。
  • 它不容易过拟合,因为多棵树进行了平均。

二、决策树回顾

        决策树是随机森林的基本单元,前期文章《构建AI智能体:三十五、决策树的核心机制(一):刨根问底鸢尾花分类中的参数推理计算》,我们详细探讨了决策树的细节内容,如不清楚建议先了解,简单来说决策树是一种模拟人类决策过程的模型,它通过一系列的问题对数据进行分割,直到得到最终的决策。例如,我们定是否外出,我们会先看看天气如何:如果是晴天,在看看看气温,如果温度大于25°C ,那么我们考虑去游泳,如果温度不到25°C,那么我们可能考虑去爬山,如果天气是下雨的,那么我们就选择不出门,在家看书。

1. 决策树是什么

决策树就像我们做决策时的思考过程。比如决定是否外出:

今天天气如何?
├── 晴天 → 温度如何?
│   ├── 温度 > 25°C → 去游泳
│   └── 温度 ≤ 25°C → 去爬山
└── 下雨 → 在家看书

2. 简单的决策树示例

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.tree import DecisionTreeClassifier, plot_treeplt.rcParams['font.sans-serif'] = ['SimHei']  
plt.rcParams['axes.unicode_minus'] = False# 创建示例数据
X, y = make_classification(n_samples=100, n_features=2, n_redundant=0, n_informative=2, n_clusters_per_class=1, random_state=42)# 训练决策树
tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X, y)# 可视化决策树
plt.figure(figsize=(12, 8))
plot_tree(tree, filled=True, feature_names=['特征1', '特征2'], class_names=['类别0', '类别1'])
plt.title('决策树结构')
plt.show()

代码详解:

  • make_classification:生成分类数据集
  • DecisionTreeClassifier:决策树分类器
  • max_depth=3:限制树深度,防止过拟合
  • plot_tree:可视化决策树结构

输出结果:

3. 决策树如何做决策

        决策树通过递归分割数据来学习,其生成过程包括特征选择、树的构建和剪枝。在特征选择中,关键问题是:如何选择最佳分割点?通常使用信息增益、基尼指数等指标,其目标是选择能让数据变得"最纯净"的分割方式。

from collections import Counter
import math
import numpy as npdef calculate_entropy(labels):"""计算信息熵"""counter = Counter(labels)total = len(labels)entropy = 0.0for count in counter.values():probability = count / totalentropy -= probability * math.log2(probability)return entropydef calculate_information_gain(data, labels, feature_index, threshold):"""计算信息增益"""# 保证 labels 为 numpy 数组,便于布尔索引labels_np = np.asarray(labels)# 分割数据left_mask = data[:, feature_index] <= thresholdright_mask = ~left_maskleft_labels = labels_np[left_mask]right_labels = labels_np[right_mask]# 计算父节点熵parent_entropy = calculate_entropy(labels_np)# 计算子节点加权平均熵n_left, n_right, n_total = len(left_labels), len(right_labels), len(labels_np)children_entropy = (n_left / n_total) * calculate_entropy(left_labels) + \(n_right / n_total) * calculate_entropy(right_labels)# 信息增益 = 父节点熵 - 子节点加权平均熵return parent_entropy - children_entropy# 示例:计算不同分割点的信息增益
print("信息熵计算示例:")
sample_labels = [0, 0, 0, 1, 1, 1]
print(f"样本标签: {sample_labels}")
print(f"信息熵: {calculate_entropy(sample_labels):.4f}")# 测试不同分割
test_data = np.array([[1], [2], [3], [4], [5], [6]])
test_labels = [0, 0, 0, 1, 1, 1]for threshold in [2.5, 3.5, 4.5]:gain = calculate_information_gain(test_data, test_labels, 0, threshold)print(f"阈值 {threshold} 的信息增益: {gain:.4f}")

推理过程:

  • 计算当前节点的混乱程度(信息熵)
  • 尝试不同的分割方式
  • 选择信息增益最大的分割方式
  • 递归应用到每个子节点

输出结果:

信息熵计算示例:
样本标签: [0, 0, 0, 1, 1, 1]
信息熵: 1.0000
阈值 2.5 的信息增益: 0.4591
阈值 3.5 的信息增益: 1.0000
阈值 4.5 的信息增益: 0.4591

决策树涉及的相关概念参考:

  • 信息熵、信息增益:《信息论完全指南:从基础概念到在大模型中的实际应用》
  • 最佳分割点、基尼指数:《决策树的核心机制(二):抽丝剥茧简化专业术语推理最佳分裂点》

三、集成学习回顾

        集成学习的核心思想在于群体智慧,其“从一棵树到一片森林”的构建范式,形象地揭示了这一理念:通过构建大量存在差异的决策树,形成一个规模庞大的模型集合(森林)。在预测时,这片“森林”并非依赖其中任何单一树木的判断,而是通过集体投票或平均来做出最终决策,从而有效规避单棵树的个体偏见与不稳定性,汇聚为更强大、更稳健的预测能力。

        集成学习通过组合多个学习器来获得比单一学习器更好的性能。随机森林属于集成学习中的Bagging方法。

Bagging的基本思想是:

  • 从原始数据集中有放回地随机抽取多个样本子集(bootstrap样本)。
  • 在每个样本子集上训练一个基学习器(比如决策树)。
  • 将这些基学习器的预测结果进行组合(分类问题使用投票,回归问题使用平均)。

        随机森林在Bagging的基础上又进了一步:不仅在数据上进行随机抽样,而且在训练每棵树时,对特征也进行随机选择。这样进一步增加了树的多样性,从而提升模型的泛化能力。详细内容可参考《六十八、集成学习:从三个臭皮匠到AI集体智慧的深度解析》。

四、随机森林原理解析

1. 随机森林的核心思想

随机森林通过两个随机性来增强多样性:

1.1 Bootstrap采样:每棵树用不同的训练子集

        Bootstrap采样是一种统计学方法,它的核心思想是“以小见大,通过模拟逼近真实”,即在只有一份样本的情况下,通过对原始数据集进行有放回的随机抽样,这个过程重复多次,从而得到多个Bootstrap样本。然后,我们可以基于这些Bootstrap样本估计统计量(如均值、方差、中位数等)的抽样分布。这种方法特别适用于小样本数据集,能够有效估计统计量的分布和不确定性。

Bootstrap采样的步骤:

  • 1. 从原始数据集中随机抽取一个样本,并记录。
  • 2. 将该样本放回原始数据集,使得下次抽样时该样本仍有可能被抽到。
  • 3. 重复步骤1和2,直到抽取的样本数量达到原始数据集的样本数n。这样就得到了一个Bootstrap样本。
  • 4. 重复上述过程B次(例如B=1000),得到B个Bootstrap样本。

        每个Bootstrap样本与原始数据集的大小相同,但由于是有放回抽样,每个Bootstrap样本中有些样本会出现多次,而有些样本则不会出现。

1.2 特征随机选择:每棵树分裂时只考虑部分特征

        特征随机选择是指在模型训练过程中,随机地选取一部分特征来进行学习,而不是每次都使用全部的特征。在模型训练的每个步骤中,随机限制可用的特征,迫使模型学习更鲁棒、更多样的规则。

目标:通过降低模型间的相关性来减少方差,防止过拟合。

核心思想与作用:

  • 打破模型的惯性:如果模型在每一轮训练(例如决策树的每一个节点分裂)中都考虑所有特征,那么那些强度最高、最显著的特征会始终被选中。这会导致所有生成的树或模型都非常相似,即模型之间具有高度的相关性。高度相关的模型集成起来,方差减少的效果有限。
  • 引入随机性,降低方差:通过强制模型只关注特征的一个随机子集,我们迫使它去探索那些在全局看来可能不那么显著,但在特定局部情境下非常有用的特征。这相当于给模型增加了“扰动”。
  • 提升泛化能力:由于模型不再过度依赖少数几个强特征,它学到的规则会更加多样和鲁棒,从而在面对新数据时表现更好。
  • 提高训练效率:在每一个节点上,只需要在特征的一个子集中寻找最佳分裂点,计算量会显著降低。

在随机森林中的工作流程:

  • 1. 从原始数据集中使用Bootstrap抽样方法,有放回地抽取多个样本子集。
  • 2. 为每一个样本子集训练一棵决策树。
  • 3. 在训练每棵树的每一个节点进行分裂时:
    • 不再从所有 M 个特征中选择最佳分裂特征。
    • 而是首先从所有特征中随机选取一个特征子集(假设大小为 m,通常 m = sqrt(M) 或 log2(M))。
    • 然后仅在这个小的随机特征子集中寻找最佳分裂特征和分裂点。
  • 4. 重复这个过程,直到树生长到预定的深度或满足停止条件。
  • 5. 将森林中所有树的预测结果进行集成(分类问题用投票,回归问题用平均)。

特征随机的优势:

        集成的“群体智慧”要发挥作用,其基础是个体模型必须具备多样性。如果所有模型都一样,那集成毫无意义。特征随机选择正是为了刻意地创造模型之间的多样性,多样性决定集成效果。

  • 没有特征随机选择:所有树都会在全局最优的几个特征上分裂,导致树的结构非常相似。模型方差降低有限。
  • 有特征随机选择:每棵树可能在不同的特征子集上分裂,从而生长出结构迥异的树。这些多样化的模型从不同角度学习数据,它们的错误互不相关,集成后能显著降低整体模型的方差,提升泛化性能。

2. 随机森林的流程图

​随机森林的构建过程:

  • 1. 从原始训练集中使用Bootstrap方法随机有放回地抽取n个样本,作为一棵树的训练集。
  • 2. 对于每棵树,从所有特征中随机选择m个特征(m通常远小于总特征数),然后从这m个特征中选择最佳分割点来分裂节点。
  • 3. 每棵树都尽可能生长,不进行剪枝(或者使用最小剪枝)。
  • 4. 重复步骤1-3,直到生成指定数量的树。

3. Bootstrap采样详解

import numpy as npdef bootstrap_sampling(data, labels, n_trees=3):"""Bootstrap采样实现"""n_samples = len(data)bootstrap_datasets = []for i in range(n_trees):# 有放回随机采样indices = np.random.choice(n_samples, n_samples, replace=True)bootstrap_data = data[indices]bootstrap_labels = labels[indices]bootstrap_datasets.append((bootstrap_data, bootstrap_labels))print(f"树{i+1}的采样结果:")print(f"  采样索引: {indices}")print(f"  唯一样本数: {len(np.unique(indices))}/{n_samples}")print()return bootstrap_datasets# 示例数据
simple_data = np.array([[1, 2], [3, 4], [5, 6], [7, 8]])
simple_labels = np.array([0, 1, 0, 1])bootstrap_datasets = bootstrap_sampling(simple_data, simple_labels)

输出结果:

树1的采样结果:
  采样索引: [0 1 1 3]
  唯一样本数: 3/4

树2的采样结果:
  采样索引: [3 2 0 1]
  唯一样本数: 4/4

树3的采样结果:
  采样索引: [2 3 3 0]
  唯一样本数: 3/4

关键概念:有放回抽样

  • 代码中的 replace=True 表示抽样后会把样本放回,同一个样本可能被多次抽中
  • 从4个样本中抽取4次,可能产生重复样本

采样过程分析:

  • 第一次采样(树1):
    • 第一次随机生成的索引是:[0, 1, 1, 3]
  • 采样结果分析:
    • 索引0: 选择第1个样本 [1, 2],标签 0
    • 索引1: 选择第2个样本 [3, 4],标签 1
    • 索引1: 再次选择第2个样本 [3, 4],标签 1(重复!)
    • 索引3: 选择第4个样本 [7, 8],标签 1
  • 唯一样本数:3/4
    • 实际使用了4个样本位置,但只有3个不同的原始样本
    • 样本 [3, 4] 被使用了2次
    • 样本 [5, 6] 完全没有被选中
  • 第二次采样(树2):
    • 第二次随机生成的索引是:[3, 2, 0, 1]
  • 采样结果分析:
    • 所有4个不同的样本都被选中一次
    • 没有重复样本
  • 唯一样本数:4/4 - 这是可能的,但不常见
  • 第三次采样(树3):
    • 第三次随机生成的索引是:[2, 3, 3, 0]
  • 采样结果分析:
    • 索引2: 选择第3个样本 [5, 6],标签 0
    • 索引3: 选择第4个样本 [7, 8],标签 1
    • 索引3: 再次选择第4个样本 [7, 8],标签 1(重复!)
    • 索引0: 选择第1个样本 [1, 2],标签 0
  • 唯一样本数:3/4
    • 样本 [7, 8] 被使用了2次
    • 样本 [3, 4] 没有被选中

4. 特征随机选择

import numpy as npdef feature_sampling(features, max_features='sqrt'):"""特征随机选择"""n_features = len(features)if max_features == 'sqrt':n_select = int(np.sqrt(n_features))elif max_features == 'log2':n_select = int(np.log2(n_features))else:n_select = max_featuresselected_indices = np.random.choice(n_features, n_select, replace=False)selected_features = [features[i] for i in selected_indices]return selected_indices, selected_features# 示例
features = ['年龄', '工资', '学历', '工作经验', '城市', '性别', '婚姻状况']
print("所有特征:", features)selected_indices, selected_features = feature_sampling(features, 'sqrt')
print(f"随机选择的特征索引: {selected_indices}")
print(f"随机选择的特征: {selected_features}")

输出结果:

所有特征: ['年龄', '工资', '学历', '工作经验', '城市', '性别', '婚姻状况']
随机选择的特征索引: [2 5]
随机选择的特征: ['学历', '性别']

特征选择策略:

  • 总特征数:7个
  • 使用 'sqrt' 策略:n_select = int(np.sqrt(7)) ≈ int(2.645) = 2 ,每次随机选择 2个特征
  • 使用 'log2' 策略:n_select = int(np.log2(7)) ≈ int(2.807) = 2 ,每次随机选择 2个特征

五、随机森林的超参数

        超参数是机器学习模型在训练开始前需要设定的配置参数,它们不是从数据中学习得到的,而是用来控制学习过程的指导参数,通俗的理解,想象一下,我们在骑自行车时,需要先进行一些调整,比如座椅高度、把手位置和轮胎气压。这些设置不是我们在骑行过程中自动学会的,而是我们在开始前手动调整的。超参数就像这些设置,它们是机器学习模型在训练开始前,由我们人工设定的参数,用来控制模型如何学习。

        建议看本小节务必先了解超参数基本原理:《六十七、超参数如何影响大模型?通俗讲解原理、作用与实战示例》

随机森林有一些重要的超参数,例如:

  • n_estimators:森林中树的数量。
  • max_features:每棵树分裂时考虑的最大特征数。
  • max_depth:树的最大深度。
  • min_samples_split:内部节点再划分所需最小样本数。
  • min_samples_leaf:叶子节点最少样本数。

调整这些超参数可以优化模型性能。

1. 示例代码

from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import classification_report, confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
# 加载乳腺癌数据集
data = load_breast_cancer()
X, y = data.data, data.target
feature_names = data.feature_namesprint("数据集信息:")
print(f"特征数量: {X.shape[1]}")
print(f"样本数量: {X.shape[0]}")
print(f"类别分布: {np.bincount(y)}")# 划分训练测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)# 创建随机森林
rf = RandomForestClassifier(n_estimators=100,      # 树的数量max_depth=10,          # 最大深度min_samples_split=2,   # 内部节点再划分所需最小样本数min_samples_leaf=1,    # 叶节点最少样本数max_features='sqrt',   # 特征选择方式random_state=42,n_jobs=-1             # 使用所有CPU核心
)# 训练模型
rf.fit(X_train, y_train)# 预测
y_pred = rf.predict(X_test)
y_pred_proba = rf.predict_proba(X_test)# 评估模型
print("\n=== 模型评估 ===")
print(f"训练集准确率: {rf.score(X_train, y_train):.4f}")
print(f"测试集准确率: {rf.score(X_test, y_test):.4f}")# 交叉验证
cv_scores = cross_val_score(rf, X, y, cv=5)
print(f"交叉验证准确率: {cv_scores.mean():.4f} (±{cv_scores.std() * 2:.4f})")# 详细分类报告
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=data.target_names))# 混淆矩阵
plt.figure(figsize=(8, 6))
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=data.target_names, yticklabels=data.target_names)
plt.title('混淆矩阵')
plt.xlabel('预测标签')
plt.ylabel('真实标签')
plt.show()# 定义参数网格
param_grid = {'n_estimators': [50, 100, 200],'max_depth': [5, 10, 15, None],'min_samples_split': [2, 5, 10],'min_samples_leaf': [1, 2, 4],'max_features': ['sqrt', 'log2']
}# 网格搜索
grid_search = GridSearchCV(RandomForestClassifier(random_state=42),param_grid,cv=5,scoring='accuracy',n_jobs=-1,verbose=1
)# 简化版参数调优
simple_param_grid = {'n_estimators': [50, 100],'max_depth': [5, 10]
}simple_search = GridSearchCV(RandomForestClassifier(random_state=42),simple_param_grid,cv=3,scoring='accuracy',n_jobs=-1
)simple_search.fit(X_train, y_train)print("简化调优最佳参数:", simple_search.best_params_)
print("简化调优最佳分数:", simple_search.best_score_)

2. 代码分析

2.1 参数调优的核心:GridSearchCV

网格搜索是一种穷举式的参数调优方法:

  • 定义参数的候选值范围
  • 尝试所有可能的参数组合
  • 通过交叉验证评估每个组合的性能
  • 选择最佳性能的参数组合

代码中的参数网格:

simple_param_grid = {'n_estimators': [50, 100],      # 树的数量'max_depth': [5, 10],      # 树的最大深度
}
  • 参数组合数:2 × 2 = 4种组合
  • 总训练次数:4 × 3折 = 12次模型训练

参数的意义:

  • n_estimators(树的数量)
    • 值小:训练快,但可能欠拟合
    • 值大:效果更好,但计算成本高
    • 平衡点:通常100-200之间
  • max_depth(树的最大深度)
    • 值小:树简单,防止过拟合,但可能欠拟合
    • 值大/None:树复杂,可能过拟合
    • 经验值:5-15之间

3. 输出结果

数据集信息:
特征数量: 30       
样本数量: 569      
类别分布: [212 357]

=== 模型评估 ===
训练集准确率: 1.0000
测试集准确率: 0.9708
交叉验证准确率: 0.9561 (±0.0457)

分类报告:
              precision    recall  f1-score   support

   malignant       0.98      0.94      0.96        63
      benign       0.96      0.99      0.98       108

    accuracy                           0.97       171
   macro avg       0.97      0.96      0.97       171
weighted avg       0.97      0.97      0.97       171

简化调优最佳参数: {'max_depth': 10, 'n_estimators': 50}
简化调优最佳分数: 0.9622920938710413

4. 运行流程

5. 结果说明

分类报告:
              precision    recall  f1-score   support

   malignant       0.98      0.94      0.96        63
      benign       0.96      0.99      0.98       108

    accuracy                               0.97       171
   macro avg       0.97      0.96      0.97       171
weighted avg       0.97      0.97      0.97       171

简化调优最佳参数: {'max_depth': 10, 'n_estimators': 50}
简化调优最佳分数: 0.9622920938710413

这表示:

  • 在测试的参数组合中,max_depth=10和n_estimators=100表现最好
  • 通过3折交叉验证的平均准确率为96.5%

5.1 分类报告详解

数据集信息:

  • 类别1: malignant(恶性) - 63个样本
  • 类别2: benign(良性) - 108个样本
  • 总样本数: 171个测试样本
  • 准确率: 0.97 (97%)

核心指标详解:

Precision(精确率/查准率)

  • 问题:当模型预测为某个类别时,有多大概率是正确的?
  • 计算公式:Precision = TP / (TP + FP)
  • malignant: 0.98 - 当模型预测为恶性时,98%的情况是正确的
  • benign: 0.96 - 当模型预测为良性时,96%的情况是正确的

Recall(召回率/查全率)

  • 问题:实际为某个类别的样本中,模型能找出多少?
  • 计算公式:Recall = TP / (TP + FN)
  • malignant: 0.94 - 实际为恶性的样本中,模型找出了94%
  • benign: 0.99 - 实际为良性的样本中,模型找出了99%

F1-Score(F1分数)

  • Precision和Recall的调和平均数
  • 计算公式:F1 = 2 × (Precision × Recall) / (Precision + Recall)
  • 综合平衡精确率和召回率
  • malignant: 0.96
  • benign: 0.98

混淆矩阵推理:

根据这些指标,我们可以推断出混淆矩阵:

  • 实际恶性(63) → 预测恶性: 59个, 预测良性: 4个
  • 实际良性(108) → 预测良性: 107个, 预测恶性: 1个

计算验证:

  • malignant recall = 59/63 ≈ 0.94 ✓
  • benign recall = 107/108 ≈ 0.99 ✓
  • malignant precision = 59/(59+1) = 59/60 ≈ 0.98 ✓
  • benign precision = 107/(107+4) = 107/111 ≈ 0.96 ✓

平均值说明:

macro avg(宏平均)

  • precision:(0.98 + 0.96)/2 = 0.97  
  • recall:(0.94 + 0.99)/2 = 0.965 
  • 特点:平等看待每个类别,不考虑样本数量差异

weighted avg(加权平均)

  • precision:(0.98*63 + 0.96*108)/171 ≈ 0.97  
  • recall:(0.94*63 + 0.99*108)/171 ≈ 0.97  
  • 特点:按样本数量加权,更反映整体性能

六、总结

        随机森林是一种强大的集成学习算法,其核心思想是通过构建众多决策树,并以集体投票或平均的方式作出最终预测,从而显著提升模型的泛化能力和鲁棒性。

        该算法的成功关键在于引入两大随机性:在数据层面使用Bootstrap采样为每棵树构建略有差异的训练集,同时在特征层面进行随机选择,这种机制确保了树与树之间的差异性,有效避免了过拟合。

        与许多复杂模型相比,随机森林具有显著的工程化优势,它无需复杂的特征进行归一化缩放,能原生处理混合数据类型,并且其训练过程高度并行化。这些特性使其在工业界的大规模数据场景下,成为一个非常可靠、易于部署和维护的基石模型。

http://www.dtcms.com/a/512593.html

相关文章:

  • 好用的外贸网站深圳网络推广培训学校
  • 第5章—STM32工程创建
  • 网站建设公司宣传标语用百度地图 做gis网站
  • c 还可以做网站微信推广是什么意思
  • 代码随想录 112.路径总和
  • 51单片机基础-定时器中断
  • xtuoj 两个数
  • Android Studio新手开发第二十六天
  • 中国平安网站建设成都网站建设易维达好
  • 继保:对于线路两侧的电流互感器型号系数选取
  • Redis分布式集群:从分区算法到扩容实战
  • AI大模型:(二)1.6 DeepSeek-OCR部署尝鲜
  • 在昇腾NPU上跑Llama大模型:从零开始的真实测试之旅
  • 直播类网站开发wordpress 图片自动分页
  • JADX下载和安装图解教程(附安装包)
  • 矽塔 SA8203 2.5A可调过流保护 输入耐压36V 过压/过流保护芯片
  • 网站开发饼图样式wordpress 如何登陆地址
  • 工业相机 “即插即用” vs 采集卡依赖
  • wordpress手机视频播放器免费seo营销软件
  • 【系统分析师】预测试卷一:论文及写作要点(包括对应素材和论文案例)
  • 私有云不私有?Nextcloud+cpolar让文件随身走
  • 诺基亚官方网站wordpress站点进入时弹窗
  • 网站建设运营合作合同外链代发工具
  • 重庆江北网站建设18款app软件免费下载百度
  • wordpress 多语言 站点动态ip做网站影响seo吗
  • 河南建设网站官网怎么用微信官方网站做二维码
  • JSTS ,JSXTSX的区别与联系(前端react第一篇)
  • 电子商务网站建设以什么为核心公司网站能自己做吗
  • 股指期货与股票的区别是什么?
  • 天津网站建设学习温州建站模板搭建