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

【机器学习基础】机器学习入门核心算法:隐马尔可夫模型 (HMM)

在这里插入图片描述

机器学习入门核心算法:隐马尔可夫模型 (HMM)

  • 一、算法逻辑与核心思想
  • 二、算法原理与数学推导
      • 核心问题与算法推导
  • 三、模型评估
  • 四、应用案例
      • 1. 语音识别 (Speech Recognition)
      • 2. 自然语言处理 (Natural Language Processing - NLP)
      • 3. 手写体识别 (Handwriting Recognition)
      • 4. 金融时间序列分析 (Financial Time Series Analysis)
      • 5. 网络入侵检测 (Network Intrusion Detection)
  • 五、面试题示例
      • 1. 基础概念题:
      • 2. 模型与计算题:
      • 3. 算法实现题:
      • 4. 优缺点与应用题:
  • 六、详细的优缺点
  • 总结

一、算法逻辑与核心思想

隐马尔可夫模型是一种用于建模时序数据的概率图模型。其核心思想在于假设:

  1. 存在一个不可直接观测的隐藏状态序列:该序列遵循马尔可夫性质,即当前状态仅依赖于前一状态。
  2. 存在一个可观测的输出序列:每个观测值由其对应的隐藏状态生成。
  3. 状态转移和观测生成都是随机的:由概率分布控制。

逻辑流程:

  • 系统在 t=1 时刻处于某个隐藏状态 q₁(由初始状态概率分布决定)。
  • 隐藏状态 q₁ 根据观测概率分布生成一个观测值 o₁
  • 系统根据状态转移概率分布,从状态 q₁ 转移到下一个隐藏状态 q₂
  • 隐藏状态 q₂ 生成观测值 o₂
  • 此过程持续进行,产生隐藏状态序列 Q = (q₁, q₂, ..., q_T) 和对应的观测序列 O = (o₁, o₂, ..., o_T)

关键点:我们只能看到观测序列 O,而隐藏状态序列 Q 是未知的(“隐”的由来)。

二、算法原理与数学推导

一个标准的 HMM 由以下五元组参数 λ = (A, B, π) 定义:

  1. 状态集合:S = {s₁, s₂, ..., s_N},共有 N 个可能的隐藏状态。
  2. 观测集合:V = {v₁, v₂, ..., v_M},共有 M 个可能的观测符号。
  3. 状态转移概率矩阵:A = [aᵢⱼ],其中 aᵢⱼ = P(qₜ₊₁ = sⱼ | qₜ = sᵢ)。表示在时刻 t 处于状态 sᵢ 时,下一时刻 t+1 转移到状态 sⱼ 的概率。
    • 性质:∑_{j=1}^N aᵢⱼ = 1, ∀ i
  4. 观测概率矩阵(发射概率矩阵):B = [bⱼ(k)],其中 bⱼ(k) = P(oₜ = vₖ | qₜ = sⱼ)。表示在时刻 t 处于状态 sⱼ 时,生成观测符号 vₖ 的概率。
    • 性质:∑_{k=1}^M bⱼ(k) = 1, ∀ j
  5. 初始状态概率分布:π = [πᵢ],其中 πᵢ = P(q₁ = sᵢ)。表示在 t=1 时刻系统处于状态 sᵢ 的概率。
    • 性质:∑_{i=1}^N πᵢ = 1

核心问题与算法推导

HMM 主要解决三个基本问题,对应三个经典算法:

  1. 评估问题 (Evaluation Problem):给定模型 λ = (A, B, π) 和观测序列 O = (o₁, o₂, ..., o_T),计算该观测序列出现的概率 P(O | λ)

    • 目的:判断模型产生给定观测序列的可能性,用于模型选择、异常检测等。
    • 算法:前向算法 (Forward Algorithm)
      • 定义前向概率:αₜ(i) = P(o₁, o₂, ..., oₜ, qₜ = sᵢ | λ)。表示在时刻 t,观测到前 t 个符号 (o₁, ..., oₜ) 且当前状态为 sᵢ 的联合概率。
      • 初始化 (t=1): α₁(i) = πᵢ * bᵢ(o₁), i = 1, 2, ..., N
      • 递归 (t = 2, 3, ..., T):
        α t ( j ) = [ ∑ i = 1 N α t − 1 ( i ) ⋅ a i j ] ⋅ b j ( o t ) , j = 1 , 2 , … , N \alpha_{t}(j) = \left[ \sum_{i=1}^{N} \alpha_{t-1}(i) \cdot a_{ij} \right] \cdot b_{j}(o_{t}), \quad j = 1, 2, \ldots, N αt(j)=[i=1Nαt1(i)aij]bj(ot),j=1,2,,N
      • 终止:
        P ( O ∣ λ ) = ∑ i = 1 N α T ( i ) P(O | \lambda) = \sum_{i=1}^{N} \alpha_{T}(i) P(Oλ)=i=1NαT(i)
      • 原理:利用动态规划,避免直接枚举所有可能的状态路径 (O(N^T) 复杂度),将复杂度降低到 O(N²T)αₜ(j) 的计算依赖于前一个时刻 t-1 所有状态 i 的前向概率 αₜ₋₁(i) 转移到状态 j 的概率 aᵢⱼ,再乘以状态 j 生成当前观测 oₜ 的概率 bⱼ(oₜ)
  2. 解码问题 (Decoding Problem):给定模型 λ = (A, B, π) 和观测序列 O = (o₁, o₂, ..., o_T),寻找最有可能(概率最大)产生该观测序列的隐藏状态序列 Q* = (q₁*, q₂*, ..., q_T*)

    • 目的:揭示观测数据背后最可能的状态序列,如词性标注、语音识别中的音素序列。
    • 算法:维特比算法 (Viterbi Algorithm)
      • 定义维特比概率:δₜ(i) = max_{q₁, q₂, ..., qₜ₋₁} P(q₁, q₂, ..., qₜ₋₁, qₜ = sᵢ, o₁, o₂, ..., oₜ | λ)。表示在时刻 t,沿着一条路径到达状态 sᵢ 并观测到 (o₁, ..., oₜ) 的最大概率。
      • 定义回溯指针:ψₜ(j) 用于记录在时刻 t 到达状态 sⱼ 的最优路径中,前一个时刻 t-1 的状态。
      • 初始化 (t=1):
        δ₁(i) = πᵢ * bᵢ(o₁), i = 1, 2, ..., N
        ψ₁(i) = 0 (无前驱状态)
      • 递归 (t = 2, 3, ..., T):
        δ t ( j ) = max ⁡ 1 ≤ i ≤ N [ δ t − 1 ( i ) ⋅ a i j ] ⋅ b j ( o t ) , j = 1 , 2 , … , N \delta_{t}(j) = \max_{1 \leq i \leq N} \left[ \delta_{t-1}(i) \cdot a_{ij} \right] \cdot b_{j}(o_{t}), \quad j = 1, 2, \ldots, N δt(j)=1iNmax[δt1(i)aij]bj(ot),j=1,2,,N
        ψ t ( j ) = arg ⁡ max ⁡ 1 ≤ i ≤ N [ δ t − 1 ( i ) ⋅ a i j ] , j = 1 , 2 , … , N \psi_{t}(j) = \arg\max_{1 \leq i \leq N} \left[ \delta_{t-1}(i) \cdot a_{ij} \right], \quad j = 1, 2, \ldots, N ψt(j)=arg1iNmax[δt1(i)aij],j=1,2,,N
      • 终止:
        P ∗ = max ⁡ 1 ≤ i ≤ N δ T ( i ) P^{*} = \max_{1 \leq i \leq N} \delta_{T}(i) P=1iNmaxδT(i)
        q T ∗ = arg ⁡ max ⁡ 1 ≤ i ≤ N δ T ( i ) q_{T}^{*} = \arg\max_{1 \leq i \leq N} \delta_{T}(i) qT=arg1iNmaxδT(i)
      • 路径回溯 (t = T-1, T-2, ..., 1):
        qₜ* = ψₜ₊₁(qₜ₊₁*)
      • 原理:同样是动态规划,但与前向算法的关键区别在于使用 max 操作代替 sum 操作。δₜ(j) 记录的是到达 (t, sⱼ) 的单条最优路径的概率,ψₜ(j) 记录这条最优路径是从哪个前驱状态 i 转移过来的。
  3. 学习问题 (Learning Problem):给定观测序列 O = (o₁, o₂, ..., o_T)(有时可能需要多个观测序列),估计模型参数 λ = (A, B, π),使得该模型产生该观测序列的概率 P(O | λ) 最大。

    • 目的:从观测数据中学习模型,是 HMM 应用的关键步骤。
    • 算法:Baum-Welch 算法 (前向-后向算法) - 一种期望最大化 (EM) 算法。
      • 定义后向概率:βₜ(i) = P(oₜ₊₁, oₜ₊₂, ..., o_T | qₜ = sᵢ, λ)。表示在时刻 t 处于状态 sᵢ 的条件下,观测到从 t+1T 的剩余观测序列的概率。
        • 初始化 (t=T): β_T(i) = 1, i = 1, 2, ..., N (约定空序列概率为1)
        • 递归 (t = T-1, T-2, ..., 1):
          β t ( i ) = ∑ j = 1 N a i j ⋅ b j ( o t + 1 ) ⋅ β t + 1 ( j ) , i = 1 , 2 , … , N \beta_{t}(i) = \sum_{j=1}^{N} a_{ij} \cdot b_{j}(o_{t+1}) \cdot \beta_{t+1}(j), \quad i = 1, 2, \ldots, N βt(i)=j=1Naijbj(ot+1)βt+1(j),i=1,2,,N
      • 定义两个关键概率:
        • ξₜ(i, j) = P(qₜ = sᵢ, qₜ₊₁ = sⱼ | O, λ):给定模型和整个观测序列,时刻 t 处于状态 sᵢ 且时刻 t+1 处于状态 sⱼ 的概率。
          ξ t ( i , j ) = α t ( i ) ⋅ a i j ⋅ b j ( o t + 1 ) ⋅ β t + 1 ( j ) P ( O ∣ λ ) = α t ( i ) ⋅ a i j ⋅ b j ( o t + 1 ) ⋅ β t + 1 ( j ) ∑ i = 1 N ∑ j = 1 N α t ( i ) ⋅ a i j ⋅ b j ( o t + 1 ) ⋅ β t + 1 ( j ) \xi_{t}(i, j) = \frac{\alpha_{t}(i) \cdot a_{ij} \cdot b_{j}(o_{t+1}) \cdot \beta_{t+1}(j)}{P(O | \lambda)} = \frac{\alpha_{t}(i) \cdot a_{ij} \cdot b_{j}(o_{t+1}) \cdot \beta_{t+1}(j)}{\sum_{i=1}^{N} \sum_{j=1}^{N} \alpha_{t}(i) \cdot a_{ij} \cdot b_{j}(o_{t+1}) \cdot \beta_{t+1}(j)} ξt(i,j)=P(Oλ)αt(i)aijbj(ot+1)βt+1(j)=i=1Nj=1Nαt(i)aijbj(ot+1)βt+1(j)αt(i)aijbj(ot+1)βt+1(j)
        • γₜ(i) = P(qₜ = sᵢ | O, λ):给定模型和整个观测序列,时刻 t 处于状态 sᵢ 的概率。它是 ξₜ(i, j)j 的求和:
          γ t ( i ) = ∑ j = 1 N ξ t ( i , j ) \gamma_{t}(i) = \sum_{j=1}^{N} \xi_{t}(i, j) γt(i)=j=1Nξt(i,j)
      • Baum-Welch 重估公式 (M步):利用 ξₜ(i, j)γₜ(i) 计算新的模型参数 λ̄ = (Ā, B̄, π̄)
        • 初始状态概率:
          π ˉ i = γ 1 ( i ) \bar{\pi}_{i} = \gamma_{1}(i) πˉi=γ1(i)
        • 状态转移概率:
          a ˉ i j = ∑ t = 1 T − 1 ξ t ( i , j ) ∑ t = 1 T − 1 γ t ( i ) \bar{a}_{ij} = \frac{\sum_{t=1}^{T-1} \xi_{t}(i, j)}{\sum_{t=1}^{T-1} \gamma_{t}(i)} aˉij=t=1T1γt(i)t=1T1ξt(i,j) (分子:从 i 转移到 j 的期望次数;分母:处于 i 的期望次数)
        • 观测概率:
          b ˉ j ( k ) = ∑ t = 1 T γ t ( j ) ⋅ 1 ( o t = v k ) ∑ t = 1 T γ t ( j ) \bar{b}_{j}(k) = \frac{\sum_{t=1}^{T} \gamma_{t}(j) \cdot \mathbf{1}(o_{t} = v_{k})}{\sum_{t=1}^{T} \gamma_{t}(j)} bˉj(k)=t=1Tγt(j)t=1Tγt(j)1(ot=vk) (分子:在状态 j 观测到符号 vₖ 的期望次数;分母:处于状态 j 的期望次数;𝟏(·) 是指示函数,当 oₜ = vₖ 时为1,否则为0)
      • 原理:Baum-Welch 算法是 EM 算法在 HMM 上的具体实现。
        • E步:利用当前参数 λ 和观测序列 O,计算期望统计量 ξₜ(i, j)γₜ(i)(需要前向和后向算法)。
        • M步:基于 E 步计算出的期望统计量,最大化似然函数,更新参数 λ̄
        • 迭代执行 E 步和 M 步,直到参数收敛(P(O | λ) 增长很小或达到最大迭代次数)。该算法保证 P(O | λ) 在每次迭代中非递减,最终收敛到局部极大值点。

三、模型评估

评估训练好的 HMM 性能是应用的关键环节,常用方法包括:

  1. 似然值 (Likelihood):使用 前向算法 计算测试观测序列 O_test 在模型 λ 下的概率 P(O_test | λ)。值越大,说明模型描述该序列的能力越强。常用于比较不同模型或同一模型的不同超参数(如状态数)对特定序列的拟合程度。
  2. 困惑度 (Perplexity):常用于语言模型等序列生成任务,是似然值的几何平均的倒数,信息论中衡量概率模型预测能力的指标。对于一个长度为 T 的测试序列:
    Perplexity ( O test ∣ λ ) = 1 P ( O test ∣ λ ) T \text{Perplexity}(O_{\text{test}} | \lambda) = \sqrt[T]{\frac{1}{P(O_{\text{test}} | \lambda)}} Perplexity(Otestλ)=TP(Otestλ)1
    困惑度越低,表示模型对序列的预测越确定(越好)。
  3. 分类准确率 (Classification Accuracy):如果 HMM 用于分类任务(例如,每个类别对应一个 HMM),将测试序列输入所有模型,选择产生最高似然值 P(O_test | λ_c) 的类别 c 作为预测结果。计算预测正确的测试序列比例即为分类准确率。
  4. 序列标注准确率 (Labeling Accuracy):
    • 状态级准确率 (State Accuracy):使用 维特比算法 解码出最可能的状态序列 Q*,与真实状态序列 Q_true 比较,计算状态正确预测的比例。
    • 序列级准确率 (Sequence Accuracy):计算整个状态序列预测正确的比例(要求序列中所有状态都预测正确)。通常比状态级准确率低。
  5. 交叉验证 (Cross-Validation):尤其当训练数据有限时,将数据分成 k 折,轮流用 k-1 折训练模型,在剩余 1 折上评估(使用似然或准确率),最后取平均评估结果。有助于更可靠地估计模型泛化能力。
  6. 模型选择准则 (AIC/BIC):在模型复杂度(如状态数 N)不同时,仅比较似然值会偏向选择更复杂的模型。赤池信息准则 (AIC) 和贝叶斯信息准则 (BIC) 在似然值基础上加入了对模型参数数量的惩罚:
    • AIC = -2 * log(P(O | λ)) + 2 * k
    • BIC = -2 * log(P(O | λ)) + k * log(T)
      (其中 k 是模型参数数量,T 是观测序列长度)选择 AIC 或 BIC 值较小的模型,能在拟合优度和模型复杂度之间取得平衡。

四、应用案例

HMM 因其强大的时序建模能力,在众多领域有广泛应用:

1. 语音识别 (Speech Recognition)

  • 经典应用。每个单词或音素 (phoneme) 通常建模为一个 HMM。
    • 状态:表示发音过程中的不同阶段(如音素的开始、中间、结束)。
    • 观测:从语音信号中提取的短时频谱特征向量(如 MFCC)。
    • 训练 (学习):使用大量标注了音素/单词的语音数据训练 HMM 参数。
    • 解码 (识别):给定一段语音的观测特征序列,使用 Viterbi 算法在所有候选单词/句子的 HMM 组合中找到最可能产生该观测序列的状态序列(即对应的单词/句子序列)。

2. 自然语言处理 (Natural Language Processing - NLP)

  • 词性标注 (Part-of-Speech Tagging):
    • 状态:词性标签(名词、动词、形容词等)。
    • 观测:句子中的单词序列。
    • 模型学习词性之间的转移概率 (A) 和特定词性生成特定单词的概率 (B)。给定一个句子(单词序列),Viterbi 算法解码出最可能的词性标签序列。
    • 命名实体识别 (Named Entity Recognition - NER):类似词性标注,状态代表实体类别(人名、地名、组织名、非实体)。
  • 基因查找 / CpG岛检测 (Bioinformatics):
    • 状态:代表基因组序列的不同功能区域(例如,编码区、非编码区、CpG岛、非CpG岛)。CpG岛是富含CpG二核苷酸的区域,常与基因调控相关。
    • 观测:DNA序列中的核苷酸(A, C, G, T)。
    • HMM 学习不同状态下生成各种核苷酸的概率。给定一段DNA序列,Viterbi算法可以预测其最可能的功能区域划分(如识别出CpG岛的位置)。

3. 手写体识别 (Handwriting Recognition)

  • 处理随时间或空间(从左到右)变化的笔迹轨迹。
    • 状态:可能表示字符、字符的部分或书写笔划。
    • 观测:笔的坐标、速度、方向或从图像中提取的特征。

4. 金融时间序列分析 (Financial Time Series Analysis)

  • 对股票价格、汇率等建模。
    • 状态:代表市场的隐含“状态”或“模式”(如“牛市”、“熊市”、“震荡市”)。
    • 观测:价格变动、交易量、技术指标等。
    • 可用于:状态识别(当前市场处于什么模式?)、预测(基于当前状态预测未来走势概率)、异常检测(观测序列概率极低可能表示异常事件)。

5. 网络入侵检测 (Network Intrusion Detection)

  • 建模正常的网络流量模式。
    • 状态:表示不同的网络活动类型或连接状态。
    • 观测:网络数据包特征(源/目的 IP、端口、协议、包大小、时间间隔等)。
    • 训练 HMM 学习正常流量模式。当新的流量序列 OP(O | λ_normal) 低于某个阈值时,触发入侵警报。

五、面试题示例

1. 基础概念题:

  • 什么是隐马尔可夫模型 (HMM)?它的两个基本假设是什么?核心五元组参数是什么?
  • 解释 HMM 的三个基本问题及其对应的经典算法。
  • 前向算法和 Viterbi 算法的核心思想是什么?它们的主要区别在哪里?(提示:sum vs max
  • Baum-Welch 算法属于什么类型的算法?简述其 E 步和 M 步主要做了什么。

2. 模型与计算题:

  • 场景:假设有一个非常简单的天气 HMM。
    • 隐藏状态:S = {Sunny, Rainy}
    • 观测:V = {Walk, Shop, Clean}(根据天气进行的活动)
    • 已知模型参数 λ
      • π = [0.6 (Sunny), 0.4 (Rainy)]
      • A = [[0.7, 0.3], [0.4, 0.6]] (行:当前状态 Sunny/Rainy;列:下一状态 Sunny/Rainy)
      • B = [[0.1, 0.4, 0.5], [0.6, 0.3, 0.1]] (行:状态 Sunny/Rainy;列:观测 Walk/Shop/Clean)
    • 问题 1 (评估):给定观测序列 O = (Walk, Shop, Clean),计算 P(O | λ)。要求写出前向概率 αₜ(i) 的计算过程。
    • 问题 2 (解码):对于同一个观测序列 O = (Walk, Shop, Clean),找出最可能的天气状态序列 Q*。要求写出维特比概率 δₜ(i) 和回溯指针 ψₜ(i) 的计算过程。
    • 问题 3 (学习):假设我们只有观测序列 O = (Walk, Shop, Clean),但没有状态序列和初始参数。简述如何使用 Baum-Welch 算法来估计参数 λ(说明需要计算的中间量 α, β, γ, ξ 和重估公式)。

3. 算法实现题:

  • 请用伪代码或熟悉的编程语言描述 Viterbi 算法的实现。
  • 如何高效地计算 Baum-Welch 算法中的 ξₜ(i, j)γₜ(i)

4. 优缺点与应用题:

  • 分析 HMM 的主要优点和局限性。
  • 为什么 HMM 在语音识别中曾经非常成功?现在是否仍然主流?它面临哪些挑战?
  • 除了提到的例子,还能列举 HMM 的其他应用场景吗?

六、详细的优缺点

特性优点缺点
建模能力专门为时序数据建模设计,结构直观。马尔可夫假设限制:状态仅依赖前一状态,难以捕捉长距离依赖关系。
能够有效处理观测与状态非确定性关联的问题。状态独立性假设:观测仅依赖当前状态,忽略了相邻观测之间的直接关联。
作为生成式模型,能模拟数据生成过程,生成新的序列。参数形式限制:观测概率 B 通常是离散或特定连续分布(如高斯混合),灵活性受限。
算法效率动态规划算法(前向、后向、Viterbi)高效,时间复杂度 O(N²T)训练复杂度:Baum-Wch 算法是迭代的 EM 过程,计算 ξ, γ 开销大,训练可能较慢,尤其状态/序列多时。
理论基础基于坚实的概率论和统计学基础。局部最优:Baum-Welch 只能收敛到局部极大值,结果依赖于初始参数选择。
可解释性状态和参数通常具有一定的物理或语义含义(如语音中的音素、NLP中的词性)。状态数选择:最优隐藏状态数 N 通常需要经验或通过模型选择准则(AIC/BIC)确定,非平凡问题。
应用广度在语音识别、NLP、生物信息学等序列标注、识别、分析领域历史悠久且成功。处理变长输入:虽然天然处理序列,但模型结构(状态数)通常是固定的,对复杂变长模式建模能力有限。
与深度学习对比训练数据量要求相对较低,参数较少,有时在小数据集上表现更好。表示学习能力弱:缺乏深度学习(如 RNN, LSTM, Transformer)强大的自动特征学习和层次化表示能力。
难以处理高维/复杂观测:如图像、视频等,需要复杂的特征工程。而深度学习擅长处理原始或高维数据。

总结

HMM 是时序数据分析的经典且强大的概率模型。其核心优势在于清晰的时序建模框架、高效的动态规划推理算法(解决评估、解码问题)以及基于 EM 的参数学习算法(解决学习问题)。在语音识别、自然语言处理(词性标注、命名实体识别)、生物信息学(基因序列分析)等领域取得了巨大成功。

然而,HMM 的严格假设(马尔可夫性、观测独立性)限制了其捕捉复杂依赖关系的能力。参数形式(离散或简单连续分布)的局限性和 Baum-Welch 易陷入局部最优也是其弱点。最重要的是,随着深度学习,特别是循环神经网络(RNN、LSTM、GRU)和 Transformer 的崛起,它们在处理复杂时序数据、自动特征学习、捕捉长距离依赖方面展现出更强大的能力,逐渐取代 HMM 成为许多领域(尤其是语音识别和 NLP)的主流技术。尽管如此,理解 HMM 的原理、算法和思想,仍然是学习时序建模和概率图模型的重要基础。它在计算效率、可解释性和小数据场景下仍有其应用价值。

相关文章:

  • # Python 语音助手本地的ollama实现
  • Byte(字节)和 k(通常指 kilobit 或 kilobyte)是两种不同的单位,它们的区别和联系
  • 网络协议DHCP
  • Centos7升级openssl
  • Flutter3.22适配运行鸿蒙系统问题记录
  • 数据结构- 10种常见树:二叉树、平衡二叉树、完全二叉树
  • 《全面解析鸿蒙相关概念:鸿蒙、开源鸿蒙、鸿蒙 Next 有何区别》
  • Java SE Cloneable接口和深/浅拷贝
  • 聊一聊 C# NativeAOT 多平台下的函数导出
  • day10机器学习的全流程
  • Python入门手册:模块和包的导入与使用
  • 基于SpringBoot开发一个MCP Server
  • 社区造数服务接入MCP|得物技术
  • JavaScript 中 this 指向全解析:从基础到 Vue 应用
  • C语言 文件操作(2)
  • Nodejs+http-server 使用 http-server 快速搭建本地图片访问服务
  • 不同坐标系下的 面积微元
  • 越南跨境电商免税政策遇冷?工商会为何踩下“刹车”
  • 8086 处理器寄存器超详细解析:从原理到实战
  • BEV和OCC学习-1:数据集以及评估指标
  • 如何在网站搜关键字/广州网络推广公司排名
  • 在哪里找给公司做网站优化的人/seo网站推广优化就找微源优化
  • 淘宝 网站建设教程视频/怎么自己弄一个平台
  • 电子商务网站建设实验总结/国际局势最新消息今天
  • 怎样做网站静态/负面口碑营销案例
  • 旅游网站做模板素材/国内最新新闻大事