鸿蒙实战:EmotionClassifier 多模态情绪分类

前言(必读)

在这里插入图片描述

图:鸿蒙实战:EmotionClassifier 多模态情绪分类(第 56 篇) 运行效果截图(HarmonyOS NEXT)

情绪分类是 AI 推理流水线的最后一环,也是最关键的一环。它与前两个步骤不同——不再是单一输入的图像识别,而是多模态融合:同时接收 OCR 文字识别结果和手写 6 维特征向量,综合判断用户的情绪状态和人格类型。

本文详细讲解 EmotionClassifier 的设计、6 种人格类型系统、多模态融合策略以及调试模式的规则分类器。

EmotionClassifier 多模态融合架构

图:EmotionClassifier 多模态融合——文字语义 + 笔迹特征 → 6种人格类型分类

OcrResult
文字 + 置信度

轻量 BERT
文字语义理解

FeatureVector
6维笔迹特征

情绪分类器
6种人格类型

ANALYTICAL 分析型

CREATIVE 创意型

EMPATHETIC 共情型

LEADER 领导型

DETAIL 细节型

SOCIAL 社交型


一、EmotionClassifier 完整实现

1.1 类结构

// 文件:features/ai/EmotionClassifier.ets
import { hilog } from '@kit.PerformanceAnalysisKit'
import { OcrResult } from './OcrService'
import { FeatureScores } from './HandwritingFeatureExtractor'

const TAG = 'EmotionClassifier'

// 6 种人格类型定义
export const PERSONALITY_TYPES = [
  '温和理性型',
  '温柔守护型',
  '敏感细腻型',
  '紧绷专注型',
  '开朗外放型',
  '内敛深沉型'
] as const

export type PersonalityType = typeof PERSONALITY_TYPES[number]

export class EmotionClassifier {
  private isInitialized: boolean = false

  async init(): Promise<boolean> {
    try {
      // MindSpore Lite 真实调用
      // const modelBuffer = await this.loadModelFromRawfile('emotion_bert.ms')
      // const model = new Model()
      // await model.build(modelBuffer, 'ms', {})

      hilog.info(0x0000, TAG, '情绪分类器初始化成功(调试模式)')
      this.isInitialized = true
      return true
    } catch (error) {
      hilog.error(0x0000, TAG, '情绪分类器初始化失败: %{public}s', JSON.stringify(error))
      this.isInitialized = false
      return false
    }
  }

  async classify(
    ocrResult: OcrResult,
    featureScores: FeatureScores
  ): Promise<ClassificationResult> {
    if (!this.isInitialized) {
      throw new Error('情绪分类器未初始化')
    }

    try {
      hilog.info(0x0000, TAG, '开始多模态融合分类')

      // ===== 真实推理(蓝图) =====
      // const textEmbedding = await this.encodeText(ocrResult.text)
      // const featureVector = this.featuresToArray(featureScores)
      // const fusedInput = this.concat([textEmbedding, featureVector])
      // const outputs = await model.predict([fusedInput])
      // const result = this.decodeClassification(outputs[0])

      // ===== 调试模式(基于规则的分类) =====
      await new Promise(resolve => setTimeout(resolve, 400))
      const result = this.ruleBasedClassify(ocrResult, featureScores)

      hilog.info(0x0000, TAG, '分类完成: %{public}s', result.personalityType)
      return result
    } catch (error) {
      hilog.error(0x0000, TAG, '分类失败: %{public}s', JSON.stringify(error))
      throw error
    }
  }
}

1.2 分类结果接口

export interface ClassificationResult {
  personalityType: PersonalityType  // 人格类型(6 种之一)
  tags: string[]                    // 情绪标签数组(3 个)
  summary: string                   // AI 总结文案
  confidence: number                // 置信度 0-1
}

二、6 种人格类型系统

2.1 类型总览

类型 特点 典型分数画像 适合的鼓励方向
温和理性型 细腻克制、深思熟虑 能量 🟡⚪ · 情绪 🟢🟢 · 细节 🟢🟢 你有一颗通透的心
温柔守护型 温暖共情、关怀他人 社交 🟢🟢 · 情绪 🟢⚪ · 思维 🟡⚪ 你是身边人的暖阳
敏感细腻型 情感丰富、注重细节 情绪 🟢🟢 · 细节 🟢🟢 · 能量 🟡⚪ 你的细腻是天赋
紧绷专注型 目标感强、高度专注 思维 🟢🟢 · 细节 🟢⚪ · 能量 🟢⚪ 别忘了给自己松绑
开朗外放型 外向热情、行动力强 能量 🟢🟢 · 行动 🟢🟢 · 社交 🟡⚪ 你的活力有感染力
内敛深沉型 内省思考、情感含蓄 思维 🟢⚪ · 细节 🟢⚪ · 能量 🟡⚪ 你的深度是财富

2.2 人格类型的 UI 展示

人格类型在 ReportDetailPage 中展示:

// ReportDetailPage.ets 中的标签展示
Flex({ wrap: FlexWrap.Wrap }) {
  ForEach(this.result.tags, (tag: string) => {
    Text(tag)
      .fontSize(11)
      .fontColor(AppColors.PRIMARY_DEEP)
      .padding({ left: 11, right: 11, top: 5, bottom: 5 })
      .backgroundColor(AppColors.BG)
      .borderRadius(999)
      .margin({ right: 6, top: 6 })
  })
}

三、多模态融合策略

3.1 为什么需要多模态?

纯文本分析                 纯笔迹分析                多模态融合
────────────────      ────────────────       ────────────────
文字:               笔迹特征:               融合判断:
"今天心情不好"  →      energy: 78       →    开朗外放型 ❌
消极判断 → 悲伤       action: 82             (笔迹特征占主导)
                      情绪外放活跃
                      
                      更准确的理解:
                      用户心情不好但性格开朗,
                      这是一种"真实的低落"

单一模态的局限性

  • 纯文本分析:用户可能写「今天心情不好」,但实际只是随意记录,情绪并不差
  • 纯笔迹分析:笔迹特征反映的是人格底色而非当下瞬时情绪
  • 多模态融合:同时考虑「说了什么」+「怎么写」→ 更准确的判断

3.2 融合结构设计

┌─────────────┐    ┌──────────────┐
│ OCR 文本     │    │ 手写特征向量  │
│ "今天不错"   │    │ [62,68,55,   │
│              │    │  71,60,75]   │
└──────┬──────┘    └──────┬───────┘
       │                  │
       ▼                  ▼
┌──────────────┐  ┌──────────────┐
│ BERT Encoder │  │ FC Projector │
│ (Token IDs→  │  │ (6维→64维)   │
│  256维向量)  │  │              │
└──────┬──────┘  └──────┬───────┘
       │                  │
       └──────┬───────────┘
              │ concat
              ▼
      ┌──────────────┐
      │ Fusion Layer │  ← 256 + 64 = 320 维
      │  FC(320→128) │
      │  ReLU+Dropout│
      └──────┬───────┘
              │
              ▼
      ┌──────────────┐
      │ Classifier   │
      │  FC(128→6)   │
      │  Softmax     │
      └──────┬───────┘
              │
              ▼
      [0.02, 0.71, 0.08, 0.05, 0.11, 0.03]
        ↑温和   ↑温柔    ↑敏感  ↑紧绷  ↑开朗  ↑内敛

3.3 推理蓝图代码

// BERT 文本编码
private async encodeText(text: string): Promise<MSTensor> {
  // WordPiece 分词
  const tokens = this.wordPieceTokenize(text)
  // [CLS] + tokens + [SEP]
  const inputIds = [101, ...tokens, 102]
  
  // Padding 到固定长度
  const MAX_LEN = 128
  while (inputIds.length < MAX_LEN) {
    inputIds.push(0)
  }
  
  return MSTensor.createFromData(
    new Int32Array(inputIds.slice(0, MAX_LEN)).buffer,
    { shape: [1, MAX_LEN], dataType: DataType.INT32 }
  )
}

// 特征转向量
private featuresToArray(scores: FeatureScores): number[] {
  return [scores.energy, scores.thinking, scores.action,
          scores.emotion, scores.social, scores.detail]
}

// 多模态融合(向量拼接)
private concat(vectors: any[]): MSTensor {
  // 实际实现中,BERT 输出 [1, 256] + 特征投影 [1, 64]
  // 在模型图中由 Fusion Layer 完成,此处为概念示意
  throw new Error('由 MindSpore Lite 模型图中实现')
}

四、调试模式:基于规则的分类器

当 BERT 模型未就绪时,使用基于规则的分类器 (ruleBasedClassify),通过 6 维特征分数阈值判断人格类型:

4.1 核心分类逻辑

/**
 * 基于规则的分类器(调试模式)
 * 实际项目应替换为 BERT 模型推理
 */
private ruleBasedClassify(
  ocrResult: OcrResult,
  featureScores: FeatureScores
): ClassificationResult {
  const { energy, thinking, action, emotion, social, detail } = featureScores

  let personalityType: PersonalityType = '温和理性型'
  let tags: string[] = []

  // 判断规则(优先级从高到低)
  if (emotion > 70 && detail > 70) {
    // 情绪丰富 + 注重细节 → 敏感细腻型
    personalityType = '敏感细腻型'
    tags = ['💛 平静偏暖', `情绪 ${emotion}`, '细腻克制']

  } else if (energy > 70 && action > 70) {
    // 能量充沛 + 行动力强 → 开朗外放型
    personalityType = '开朗外放型'
    tags = ['🧡 活力充沛', `能量 ${energy}`, '行动力强']

  } else if (thinking > 75 && detail > 70) {
    // 思维缜密 + 注重细节 → 紧绷专注型
    personalityType = '紧绷专注型'
    tags = ['💙 理性专注', `思维 ${thinking}`, '注重细节']

  } else if (social > 70 && emotion > 60) {
    // 社交活跃 + 情绪稳定 → 温柔守护型
    personalityType = '温柔守护型'
    tags = ['💚 温柔体贴', `社交 ${social}`, '共情力强']

  } else if (energy < 50 && emotion < 50) {
    // 能量偏低 + 情绪内敛 → 内敛深沉型
    personalityType = '内敛深沉型'
    tags = ['💜 内向深沉', `能量 ${energy}`, '思虑较多']

  } else {
    // 默认 → 温和理性型
    personalityType = '温和理性型'
    tags = ['💛 平静偏暖', `能量 ${energy}`, '细腻克制']
  }

  return {
    personalityType,
    tags,
    summary: this.generateSummary(personalityType, ocrResult, featureScores),
    confidence: 0.87
  }
}

4.2 决策流程图

              featureScores 输入
                    │
                    ▼
              emotion > 70   │   energy > 70   │   thinking > 75
              detail > 70    │   action > 70   │   detail > 70
                    │               │               │
                    ▼               ▼               ▼
              敏感细腻型         开朗外放型       紧绷专注型
                    │               │               │
                    └───────┬───────┴───────┬───────┘
                            │               
                            ▼
              social > 70   │   energy < 50  │   默认
              emotion > 60  │   emotion < 50 │
                    │               │               │
                    ▼               ▼               ▼
              温柔守护型         内敛深沉型       温和理性型

4.3 规则边界的平滑处理

为防止分数在边界附近频繁跳变,引入滞后缓冲:

private ruleBasedWithHysteresis(
  ocrResult: OcrResult,
  featureScores: FeatureScores,
  previousType?: PersonalityType
): ClassificationResult {
  const HYSTERESIS_MARGIN = 5  // 滞后缓冲值
  
  const result = this.ruleBasedClassify(ocrResult, featureScores)
  
  // 如果与上一次结果不同,检查是否足够偏离边界
  if (previousType && result.personalityType !== previousType) {
    // 计算当前结果的关键判定维度
    const { emotion, energy, action, thinking, detail, social } = featureScores
    
    let isStableTransition = true
    switch (result.personalityType) {
      case '敏感细腻型':
        isStableTransition = emotion > 75 && detail > 75
        break
      case '开朗外放型':
        isStableTransition = energy > 75 && action > 75
        break
      case '紧绷专注型':
        isStableTransition = thinking > 80 && detail > 75
        break
      // ... 其他类型的缓冲检查
    }
    
    // 边界抖动时保留上一次结果
    if (!isStableTransition) {
      return this.ruleBasedClassify(ocrResult, {
        ...featureScores,
        emotion: previousType === '敏感细腻型' ? 72 : featureScores.emotion
      })
    }
  }
  
  return result
}

五、AI 总结文案模板系统

六种人格类型各有对应的 AI 生成文案,在 generateSummary 方法中维护:

private generateSummary(
  type: PersonalityType,
  ocrResult: OcrResult,
  scores: FeatureScores
): string {
  const templates: Record<PersonalityType, string> = {
    '温和理性型': `TA 是一个细腻、克制的人,此刻偏向平静但温柔,倾向深思熟虑而非冲动表达。笔迹展现出较好的逻辑思维,情绪状态稳定。`,
    
    '温柔守护型': `TA 内心温暖、富有共情力,字迹圆润柔和,展现出对他人的关怀与守护倾向。情绪状态偏向积极、安定。`,
    
    '敏感细腻型': `TA 对细节非常敏感,笔迹精细、曲线较多,内心世界丰富且情感细腻。此刻情绪可能有些波动,但整体偏向温和。`,
    
    '紧绷专注型': `TA 此刻处于高度专注状态,笔画力度较大、字形规整,展现出强烈的目标感。可能在处理重要事务,压力略大但可控。`,
    
    '开朗外放型': `TA 性格外向开朗,笔迹舒展、充满活力,字迹较大且倾斜度明显。此刻情绪积极向上,能量充沛。`,
    
    '内敛深沉型': `TA 内敛、思虑较多,笔迹紧凑且偏小,情绪表达含蓄。此刻可能有些疲惫或低沉,需要适当的放松与关怀。`
  }

  // 动态增强:根据具体分数追加个性化内容
  let summary = templates[type]
  
  if (scores.energy > 70) {
    summary += ' 当前精力充沛,适合进行创造性工作或户外活动。'
  } else if (scores.energy < 40) {
    summary += ' 当前能量偏低,建议适当休息,给自己一些放松的时间。'
  }

  return summary
}

文案模板的设计原则

原则 说明 示例
非评判性 不使用「好/坏」等评价性词汇 不说「你的字写得太差了」
具体化 结合具体特征描述 「笔画力度较大」而非「你很用力」
正向引导 结尾给出温和的建议方向 「需要适当的放松与关怀」
个性化 根据实际分数动态调整 能量高分和低分追加不同文案
去心理学化 避免心理诊断式的表达 写「偏好独处」而非「你有社交障碍」

六、置信度计算

6.1 调试模式的置信度

调试模式下,置信度基于「规则匹配的清晰度」计算:

private calcDebugConfidence(featureScores: FeatureScores): number {
  const { energy, thinking, action, emotion, social, detail } = featureScores
  
  // 计算所有判定维度的"极值程度"
  const extremeDims = [
    Math.abs(emotion - 50) / 50,    // 情绪极端度
    Math.abs(energy - 50) / 50,     // 能量极端度
    Math.abs(detail - 50) / 50,     // 细节极端度
    Math.abs(thinking - 50) / 50,   // 思维极端度
    Math.abs(action - 50) / 50,     // 行动极端度
    Math.abs(social - 50) / 50      // 社交极端度
  ]
  
  // 取 Top-2 维度均值作为置信度
  const sorted = extremeDims.sort((a, b) => b - a)
  const confidence = (sorted[0] + sorted[1]) / 2
  
  // 映射到 0.75-0.95 区间
  return Math.round((0.75 + confidence * 0.20) * 100) / 100
}

6.2 置信度展示

ReportDetailPage 显示:

Text(`置信度 ${this.moodConfidence}%`)
  .fontSize(11)
  .fontColor(AppColors.TEXT_3)

七、与 AnalyzingPage 的集成

EmotionClassifierAnalyzingPage 的第 3-4 步被触发(调试模式用模拟数据替代真实调用):

// 在 AnalyzingPage 中模拟分类结果的生成
private generateMockResult(source: string, archiveId: number): AnalysisResultData {
  const templates = [
    {
      personality_type: '温和理性型',
      energy_score: 62, emotion_score: 71, thinking_score: 68,
      action_score: 55, social_score: 60, detail_score: 75,
      tags: ['💛 平静偏暖', '能量 62', '细腻克制'],
      summary: 'TA 是一个细腻、克制的人...'
    },
    // ... 另 4 套模板
  ]
  
  // 稳定的多样化选择
  const seed = source.length + archiveId * 7 + new Date().getMinutes()
  return templates[Math.abs(seed) % templates.length]
}

八、多模态融合的局限性

局限 说明 缓解方案
书写场景影响 匆忙写 vs 认真写特征差异大 在分析中提示「书写条件的影响」
工具影响 钢笔/触控笔/手指书写特征不同 训练数据包含多种书写工具
文化差异 不同文字系统笔迹特征不同 中文字迹单独训练,多语种模型
时效性 情绪随时间变化,一次分析不全面 鼓励多次记录查看趋势
过度解读 用户可能将结果视为心理学诊断 明确标注「仅供娱乐参考」

九、模型训练的扩展考虑

9.1 多模态训练数据标注要求

数据样本格式:
{
  "image_path": "/data/writing/001.jpg",
  "ocr_text": "今天心情不错",
  "feature_scores": {
    "energy": 62, "thinking": 68, "action": 55,
    "emotion": 71, "social": 60, "detail": 75
  },
  "personality_type": "温和理性型",    // 人工标注
  "confidence": 0.92                   // 标注者自信度
}

9.2 模型评估指标

指标 计算方式 目标值
Top-1 Accuracy 预测类型与标注一致的比例 ≥ 70%
Macro F1 Score 各类别 F1 的宏观平均 ≥ 0.65
Confusion Matrix 各类别的误分类模式 相邻类型<20%
Calibration Error 置信度与准确率的偏差 < 0.1

📖 参考:多模态融合最新研究综述 | 轻量 BERT 模型


十、最佳实践总结

// EmotionClassifier 使用模板
// 1. 初始化(应用启动时执行一次)
const classifier = new EmotionClassifier()
await classifier.init()

// 2. 分类调用(每次分析时执行)
const result = await classifier.classify(ocrResult, featureScores)

// 3. 结果使用
console.log(`人格类型: ${result.personalityType}`)
console.log(`标签: ${result.tags.join(', ')}`)
console.log(`总结: ${result.summary}`)
console.log(`置信度: ${result.confidence * 100}%`)
实践要点 建议
初始化时机 应用启动后即初始化,避免分析时等待
结果缓存 同一次书写可缓存分类结果,避免重复推理
用户反馈 提供「不符」反馈通道,持续改进模型
合规性 明确标注「AI 生成内容,仅供参考」
多模态权重 OCR 置信度低时降低文本模态权重

十一、注意事项与常见问题

11.1 开发注意事项

说明: 以下注意事项基于 HarmonyOS NEXT 实际项目开发经验整理,建议在动手开发前仔细阅读,可有效避免常见坑点。

在正式开发前,建议按以下步骤完成环境准备与前置检查:

  1. 版本确认:检查 DevEco Studio 与 SDK 版本,确保满足目标 API Level 要求
  2. 权限声明:在 module.json5requestPermissions 字段中提前声明所有需要的系统权限
  3. 设备能力检查:调用前验证设备是否支持目标能力(相机、NFC、传感器等)
  4. 异步封装:所有耗时操作(数据库、文件 I/O、网络请求)统一使用 async/await 处理
  5. 资源释放:在组件 aboutToDisappear() 生命周期钩子中及时释放系统资源,防止内存泄漏

11.2 常见错误与解决方案

常见问题快速排查表:

问题类型 排查方向 参考方法
应用崩溃 查看 hilog 错误日志 hilog.error(TAG, "...", e.message)
状态丢失 检查 AppStorage 键名拼写 统一使用常量管理键名
动画不流畅 避免在 animateTo 回调中执行 I/O 动画与数据操作分离

提示: 建议在 DevEco Studio 中开启 ArkTS Lint 静态检查,大部分编译期问题可在开发阶段发现。

总结

本文详细讲解了 EmotionClassifier 多模态情绪分类服务的完整设计:

  • 多模态融合:同时接收 OCR 文本和手写特征,综合判断情绪状态
  • 6 种人格类型:温和理性 / 温柔守护 / 敏感细腻 / 紧绷专注 / 开朗外放 / 内敛深沉
  • 规则分类器:调试模式下基于阈值判断,支持边界滞后缓冲
  • 文案模板系统:6 套基础文案 + 动态分数追加,非评判性语言风格
  • 置信度计算:基于特征清晰度的动态置信度
  • 融合架构:BERT 文本编码 + 特征投影 → 拼接 → 分类器

至此,AI 推理流水线的三个核心服务已全部讲解完毕。下一篇我们将探讨 从 rawfile 加载 AI 模型资源 的主题,讲解鸿蒙资源管理系统中模型文件的最佳实践。


📌 收藏提示:如果您觉得本系列对您有帮助,欢迎点赞 + 关注,也欢迎在评论区交流鸿蒙端侧 AI 开发的问题!


相关资源:

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐