鸿蒙实战:EmotionClassifier 多模态情绪分类
鸿蒙实战:EmotionClassifier 多模态情绪分类
前言(必读)

图:鸿蒙实战:EmotionClassifier 多模态情绪分类(第 56 篇) 运行效果截图(HarmonyOS NEXT)
情绪分类是 AI 推理流水线的最后一环,也是最关键的一环。它与前两个步骤不同——不再是单一输入的图像识别,而是多模态融合:同时接收 OCR 文字识别结果和手写 6 维特征向量,综合判断用户的情绪状态和人格类型。
本文详细讲解 EmotionClassifier 的设计、6 种人格类型系统、多模态融合策略以及调试模式的规则分类器。

图:EmotionClassifier 多模态融合——文字语义 + 笔迹特征 → 6种人格类型分类
一、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 的集成
EmotionClassifier 在 AnalyzingPage 的第 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 实际项目开发经验整理,建议在动手开发前仔细阅读,可有效避免常见坑点。
在正式开发前,建议按以下步骤完成环境准备与前置检查:
- 版本确认:检查 DevEco Studio 与 SDK 版本,确保满足目标 API Level 要求
- 权限声明:在
module.json5的requestPermissions字段中提前声明所有需要的系统权限 - 设备能力检查:调用前验证设备是否支持目标能力(相机、NFC、传感器等)
- 异步封装:所有耗时操作(数据库、文件 I/O、网络请求)统一使用
async/await处理 - 资源释放:在组件
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 开发的问题!
相关资源:
更多推荐




所有评论(0)