iOS基于MNN的本地化个性化推荐模型实现

iOS基于MNN的本地化个性化推荐模型实现

一、项目目标

使用训练一个简化的个性化排序模型,并将其转换为TensorFlow Lite格式,最终转换为MNN格式,并实现在iOS设备上运行。

应用场景模拟

根据用户特征、商品特征和上下文信息进行个性化排序,实现基于本地化部署的个性化推荐能力。

二、准备工作

1. 安装TensorFlow 和 NumPy

pip3 install tensorflow numpy

验证是否安装成功

# 验证是否安装成功
python3 -c "import tensorflow as tf; print('✅ TensorFlow安装成功!'); print('版本:', tf.__version__); print('GPU支持:', len(tf.config.list_physical_devices('GPU')) > 0)"

# 安装成功则会有如下提示信息
✅ TensorFlow安装成功!
版本: 2.16.2
GPU支持: True

2. 下载MNN源码,生成MNNConvert转换工具

⚠️ 避坑提示:MNN提供的workbench与本地的MNN版本存在匹配兼容性问题,导致不能将.tflite转为.mnn。建议直接编译MNN源码,生成MNNConvert工具,通过命令行转换。

2.1 下载MNN源码

下载最新的release版本:https://github.com/alibaba/MNN.git
目前最新版本为3.3.0

2.2 通过CMake,生成MNNConvert工具(用于将.tflite转为.mnn)

1)安装CMake

brew install cmake

2)执行cmake命令

cmake -DMNN_BUILD_CONVERTER=ON -DCMAKE_BUILD_TYPE=Release ..
make mnnconvert -j4

3)编译后的mnnconvert工具在build目录下
image

4)通过MNNConvert可将.tflite转为.mnn

mnnconvert --framework TFLITE --modelFile ultra_simple_ranking_model.tflite --MNNModel ultra_simple_ranking_model.mnn --bizCode tflite_convert

三、生成神经网络模型

1. 根据需求设计一个简化的神经网络

三个输入分别为用户特征(user_features)、商品特征(item_features)和上下文特征(context_features)

# 输入层 - 使用固定维度
user_input = keras.Input(shape=(10,), name='user_features')
item_input = keras.Input(shape=(10,), name='item_features') 
context_input = keras.Input(shape=(5,), name='context_features')

# 连接所有输入
combined = layers.Concatenate(name='combined_features')([user_input, item_input, context_input])

# 超简单的全连接层 - 避免任何复杂操作
hidden = layers.Dense(16, activation='relu', name='hidden_layer')(combined)

# 输出层
output = layers.Dense(1, activation='sigmoid', name='ranking_score')(hidden)

用户特征的10个维度:

  • [0] 年龄(归一化) → 0.25 (25岁)
  • [1] 性别 → 1.0 (男) 或 0.0 (女)
  • [2] 收入水平 → 0.6 (中等收入)
  • [3] 购买力 → 0.8 (购买力强)
  • [4] 品牌偏好 → 0.3 (偏爱性价比)
  • [5] 价格敏感度 → 0.7 (价格敏感)
  • [6] 活跃度 → 0.9 (经常购物)
  • [7] 地域特征 → 0.4 (二线城市)
  • [8] 历史行为模式 → 0.6 (理性消费)
  • [9] 社交影响力 → 0.2 (不太关注社交)

商品特征的10个维度:

  • [0] 价格档次 → 0.7 (中高端)
  • [1] 品牌知名度 → 0.9 (知名品牌)
  • [2] 用户评分 → 0.85 (4.2/5星)
  • [3] 销量热度 → 0.6 (中等销量)
  • [4] 功能复杂度 → 0.4 (功能简单)
  • [5] 外观设计 → 0.8 (设计精美)
  • [6] 性价比 → 0.7 (性价比高)
  • [7] 类目属性 → 0.3 (电子产品)
  • [8] 新品程度 → 0.2 (老款产品)
  • [9] 促销力度 → 0.5 (有一定优惠)

上下文特征的5个维度:

  • [0] 时间特征 → 0.8 (晚上8点,购物高峰)
  • [1] 季节特征 → 0.3 (春季)
  • [2] 设备类型 → 1.0 (手机端)
  • [3] 网络状况 → 0.9 (WiFi,网速快)
  • [4] 搜索意图强度 → 0.7 (明确想买)

1.2 具体实现生成模型Python代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
超简化个性化排序模型 - 完全兼容TensorFlow Lite
"""

import os
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import json

# 设置随机种子确保结果可重现
tf.random.set_seed(42)
np.random.seed(42)

print("🚀 创建超简化兼容模型...")
print(f"TensorFlow版本: {tf.__version__}")

def create_ultra_simple_model():
    """创建超简化的个性化排序模型"""
    print("🏗️ 创建超简化模型架构...")
    
    # 定义输入层 - 使用固定维度
    user_input = keras.Input(shape=(10,), name='user_features')
    item_input = keras.Input(shape=(10,), name='item_features') 
    context_input = keras.Input(shape=(5,), name='context_features')
    
    # 连接所有输入
    combined = layers.Concatenate(name='combined_features')([user_input, item_input, context_input])
    
    # 超简单的全连接层 - 避免任何复杂操作
    hidden = layers.Dense(16, activation='relu', name='hidden_layer')(combined)
    
    # 输出层
    output = layers.Dense(1, activation='sigmoid', name='ranking_score')(hidden)
    
    # 创建模型
    model = keras.Model(
        inputs=[user_input, item_input, context_input],
        outputs=output,
        name='ultra_simple_ranking_model'
    )
    
    return model

def generate_simple_data(num_samples=1000):
    """生成简单的训练数据"""
    print(f"📊 生成 {num_samples} 个训练样本...")
    
    # 生成随机特征
    user_features = np.random.randn(num_samples, 10).astype(np.float32)
    item_features = np.random.randn(num_samples, 10).astype(np.float32) 
    context_features = np.random.randn(num_samples, 5).astype(np.float32)
    
    # 生成简单的标签 - 基于特征和的简单规则
    user_score = np.mean(user_features, axis=1)
    item_score = np.mean(item_features, axis=1)
    context_score = np.mean(context_features, axis=1)
    
    # 组合得分并转换为0-1标签
    combined_score = user_score + item_score + context_score
    labels = (combined_score > np.median(combined_score)).astype(np.float32)
    
    return {
        'user_features': user_features,
        'item_features': item_features, 
        'context_features': context_features
    }, labels

def main():
    print("🚀 开始创建超简化个性化排序模型")
    
    # 创建模型
    model = create_ultra_simple_model()
    
    # 显示模型架构
    print("🏗️ 模型架构:")
    model.summary()
    
    # 编译模型 - 使用最基础的配置
    model.compile(
        optimizer='adam',
        loss='binary_crossentropy',
        metrics=['accuracy']
    )
    
    # 生成训练数据
    X_train, y_train = generate_simple_data(5000)
    X_val, y_val = generate_simple_data(1000)
    
    print(f"训练数据形状: User={X_train['user_features'].shape}, Item={X_train['item_features'].shape}, Context={X_train['context_features'].shape}")
    print(f"标签分布: 正样本={np.sum(y_train)}, 负样本={len(y_train) - np.sum(y_train)}")
    
    # 训练模型
    print("🎯 开始训练...")
    history = model.fit(
        [X_train['user_features'], X_train['item_features'], X_train['context_features']],
        y_train,
        validation_data=([X_val['user_features'], X_val['item_features'], X_val['context_features']], y_val),
        epochs=5,
        batch_size=64,
        verbose=1
    )
    
    # 保存模型
    model_path = 'ultra_simple_ranking_model.keras'
    model.save(model_path)
    print(f"✅ 模型已保存到: {model_path}")
    
    # 保存配置信息
    config = {
        "model_name": "ultra_simple_ranking_model",
        "input_shapes": {
            "user_features": [10],
            "item_features": [10], 
            "context_features": [5]
        },
        "output_shape": [1],
        "total_params": model.count_params(),
        "architecture": "ultra_simple_dense_only"
    }
    
    with open('ultra_simple_config.json', 'w', encoding='utf-8') as f:
        json.dump(config, f, indent=2, ensure_ascii=False)
    
    print("✅ 配置文件已保存到: ultra_simple_config.json")
    
    # 测试模型推理
    print("🧪 测试模型推理...")
    test_user = np.random.randn(1, 10).astype(np.float32)
    test_item = np.random.randn(1, 10).astype(np.float32)
    test_context = np.random.randn(1, 5).astype(np.float32)
    
    prediction = model.predict([test_user, test_item, test_context])
    print(f"测试预测结果: {prediction[0][0]:.4f}")
    
    print("🎉 超简化模型创建完成!")
    return model_path

if __name__ == "__main__":
    main()

架构特点:

  • 总参数量:433个 (1.69KB)
  • 输入维度:25维 (10+10+5)
  • 输出:单一排序得分 (0-1)

1.3 训练数据生成

def generate_simple_data(num_samples=1000):
    # 生成随机特征
    user_features = np.random.randn(num_samples, 10).astype(np.float32)
    item_features = np.random.randn(num_samples, 10).astype(np.float32) 
    context_features = np.random.randn(num_samples, 5).astype(np.float32)
    
    # 基于特征生成标签
    user_score = np.mean(user_features, axis=1)
    item_score = np.mean(item_features, axis=1)
    context_score = np.mean(context_features, axis=1)
    
    combined_score = user_score + item_score + context_score
    labels = (combined_score > np.median(combined_score)).astype(np.float32)
    
    return features, labels

1.4 训练过程

训练配置:

  • 优化器:Adam
  • 损失函数:binary_crossentropy
  • 评估指标:accuracy
  • 训练轮数:5 epochs
  • 批次大小:64

执行命令Python文件,开始训练

python3 create_ultra_simple_model.py

image

1.5 训练结果

Epoch 1/5: accuracy: 0.5428 - val_accuracy: 0.7460
Epoch 2/5: accuracy: 0.7955 - val_accuracy: 0.8730
Epoch 3/5: accuracy: 0.9057 - val_accuracy: 0.9320
Epoch 4/5: accuracy: 0.9485 - val_accuracy: 0.9610
Epoch 5/5: accuracy: 0.9699 - val_accuracy: 0.9730

深度学习模型训练参数详解

accuracy 和 val_accuracy 是机器学习模型训练过程中的两个核心评估指标:

  • accuracy(训练准确率)

    • 定义:模型在训练集上的预测正确率
    • 计算方式:正确预测数量 ÷ 训练样本总数
    • 作用:衡量模型对训练数据的学习程度
  • val_accuracy(验证准确率)

    • 定义:模型在验证集上的预测正确率
    • 计算方式:正确预测数量 ÷ 验证样本总数
    • 作用:衡量模型的泛化能力(对新数据的预测能力)

1.6 输出TensorFlow文件

生成文件:ultra_simple_ranking_model.tflite

1.7 .tflite 转为 .mnn

.tflite可以通过MNNConvert转为.mnn(生成的.mnn文件比.tflite文件要大)

mnnconvert --framework TFLITE --modelFile ultra_simple_ranking_model.tflite --MNNModel ultra_simple_ranking_model.mnn --bizCode tflite_convert

生成文件:ultra_simple_ranking_model.mnn

四、在iOS工程里验证

1. 创建iOS工程并通过CocoaPods引用MNN框架

target 'MNNDemo' do
  pod 'MNN'
end

2. 编写iOS测试用例

#import <Foundation/Foundation.h>
#import <MNN/Interpreter.hpp>
#import <MNN/Tensor.hpp>
#import <iostream>
#import <vector>

@implementation SimpleRankingModelTest

+ (void)testUltraSimpleModel {
    NSLog(@"🚀 开始测试超简化个性化排序模型");
    
    // 1. 获取模型文件路径
    NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"ultra_simple_ranking_model" ofType:@"mnn"];
    if (!modelPath) {
        NSLog(@"❌ 模型文件未找到: ultra_simple_ranking_model.mnn");
        return;
    }
    
    NSLog(@"📂 模型文件路径: %@", modelPath);
    
    // 2. 创建MNN解释器
    std::shared_ptr<MNN::Interpreter> interpreter(MNN::Interpreter::createFromFile([modelPath UTF8String]));
    if (!interpreter) {
        NSLog(@"❌ 无法创建MNN解释器");
        return;
    }
    
    NSLog(@"✅ MNN解释器创建成功");
    
    // 3. 创建会话配置
    MNN::ScheduleConfig config;
    config.type = MNN_FORWARD_CPU;
    config.numThread = 1;
    
    // 4. 创建会话
    MNN::Session* session = interpreter->createSession(config);
    if (!session) {
        NSLog(@"❌ 无法创建MNN会话");
        return;
    }
    
    NSLog(@"✅ MNN会话创建成功");
    
    // 5. 获取输入张量
    MNN::Tensor* userInput = interpreter->getSessionInput(session, "user_features");
    MNN::Tensor* itemInput = interpreter->getSessionInput(session, "item_features");
    MNN::Tensor* contextInput = interpreter->getSessionInput(session, "context_features");
    
    if (!userInput || !itemInput || !contextInput) {
        NSLog(@"❌ 无法获取输入张量");
        interpreter->releaseSession(session);
        return;
    }
    
    NSLog(@"✅ 成功获取所有输入张量");
    NSLog(@"📊 用户特征张量形状: [%d, %d]", userInput->shape()[0], userInput->shape()[1]);
    NSLog(@"📊 物品特征张量形状: [%d, %d]", itemInput->shape()[0], itemInput->shape()[1]);
    NSLog(@"📊 上下文特征张量形状: [%d, %d]", contextInput->shape()[0], contextInput->shape()[1]);
    
    // 6. 准备测试数据
    std::vector<float> userData(10);
    std::vector<float> itemData(10);
    std::vector<float> contextData(5);
    
    // 填充随机测试数据
    for (int i = 0; i < 10; i++) {
        userData[i] = (float)(rand() % 100) / 100.0f;
        itemData[i] = (float)(rand() % 100) / 100.0f;
    }
    for (int i = 0; i < 5; i++) {
        contextData[i] = (float)(rand() % 100) / 100.0f;
    }
    
    // 7. 设置输入数据
    memcpy(userInput->host<float>(), userData.data(), userData.size() * sizeof(float));
    memcpy(itemInput->host<float>(), itemData.data(), itemData.size() * sizeof(float));
    memcpy(contextInput->host<float>(), contextData.data(), contextData.size() * sizeof(float));
    
    NSLog(@"✅ 输入数据设置完成");
    
    // 8. 运行推理
    interpreter->runSession(session);
    NSLog(@"✅ 模型推理完成");
    
    // 9. 获取输出结果
    MNN::Tensor* output = interpreter->getSessionOutput(session, NULL);
    if (!output) {
        NSLog(@"❌ 无法获取输出张量");
        interpreter->releaseSession(session);
        return;
    }
    
    float* outputData = output->host<float>();
    float rankingScore = outputData[0];
    
    NSLog(@"🎯 排序得分: %.4f", rankingScore);
    NSLog(@"✅ 超简化模型测试完成!");
    
    // 10. 清理资源
    interpreter->releaseSession(session);
}

+ (void)testModelInference {
    NSLog(@"🧪 开始批量推理测试");
    
    // 获取模型文件路径
    NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"ultra_simple_ranking_model" ofType:@"mnn"];
    if (!modelPath) {
        NSLog(@"❌ 模型文件未找到");
        return;
    }
    
    // 创建解释器和会话
    std::shared_ptr<MNN::Interpreter> interpreter(MNN::Interpreter::createFromFile([modelPath UTF8String]));
    MNN::ScheduleConfig config;
    config.type = MNN_FORWARD_CPU;
    config.numThread = 1;
    MNN::Session* session = interpreter->createSession(config);
    
    // 获取输入张量
    MNN::Tensor* userInput = interpreter->getSessionInput(session, "user_features");
    MNN::Tensor* itemInput = interpreter->getSessionInput(session, "item_features");
    MNN::Tensor* contextInput = interpreter->getSessionInput(session, "context_features");
    
    // 测试多组数据
    NSLog(@"📊 测试多组推理数据:");
    
    for (int test = 0; test < 5; test++) {
        // 生成测试数据
        std::vector<float> userData(10);
        std::vector<float> itemData(10);
        std::vector<float> contextData(5);
        
        for (int i = 0; i < 10; i++) {
            userData[i] = (float)(rand() % 200 - 100) / 100.0f; // -1.0 到 1.0
            itemData[i] = (float)(rand() % 200 - 100) / 100.0f;
        }
        for (int i = 0; i < 5; i++) {
            contextData[i] = (float)(rand() % 200 - 100) / 100.0f;
        }
        
        // 设置输入
        memcpy(userInput->host<float>(), userData.data(), userData.size() * sizeof(float));
        memcpy(itemInput->host<float>(), itemData.data(), itemData.size() * sizeof(float));
        memcpy(contextInput->host<float>(), contextData.data(), contextData.size() * sizeof(float));
        
        // 推理
        interpreter->runSession(session);
        
        // 获取结果
        MNN::Tensor* output = interpreter->getSessionOutput(session, NULL);
        float rankingScore = output->host<float>()[0];
        
        NSLog(@"  测试 %d: 排序得分 = %.4f", test + 1, rankingScore);
    }
    
    NSLog(@"✅ 批量推理测试完成");
    
    // 清理资源
    interpreter->releaseSession(session);
}

@end

主要步骤

1. 模型加载

从Bundle中获取ultra_simple_ranking_model.mnn文件路径

2. 解释器创建

使用MNN框架创建模型解释器

MNN::Interpreter::createFromFile(const char* file);

3. 会话配置

配置CPU推理,单线程执行

MNN::ScheduleConfig config;
config.type = MNN_FORWARD_CPU;
config.numThread = 1;

4. 会话创建

建立MNN推理会话

interpreter->createSession(config);

5. 张量获取

获取三个输入张量:用户特征(10维)、物品特征(10维)、上下文特征(5维)

MNN::Tensor* userInput = interpreter->getSessionInput(session, "user_features");
MNN::Tensor* itemInput = interpreter->getSessionInput(session, "item_features");
MNN::Tensor* contextInput = interpreter->getSessionInput(session, "context_features");

6. 数据准备

生成随机测试数据填充输入向量

std::vector<float> userData(10);
std::vector<float> itemData(10);
std::vector<float> contextData(5);

7. 数据设置

将测试数据复制到输入张量

memcpy(userInput->host<float>(), userData.data(), userData.size() * sizeof(float));
memcpy(itemInput->host<float>(), itemData.data(), itemData.size() * sizeof(float));
memcpy(contextInput->host<float>(), contextData.data(), contextData.size() * sizeof(float));

8. 模型推理

执行推理计算

interpreter->runSession(session);

9. 结果获取

提取输出张量中的排序得分(0-1范围)

MNN::Tensor* output = interpreter->getSessionOutput(session, NULL);

10. 资源清理

释放会话和相关资源

interpreter->releaseSession(session);

3. 单测运行结果

image

4. 测试结论

5组测试数据中,第4组推荐排序值最高,为0.8547,模型推理结果认为这个商品78.05%适合这个用户

五、总结

本项目成功实现了从Python模型训练到iOS端部署的完整流程:

  1. 模型训练:使用TensorFlow训练超简化神经网络,达到97.3%准确率
  2. 格式转换:TensorFlow → TFLite → MNN,模型大小仅6.6KB
  3. iOS集成:通过MNN框架实现毫秒级推理
  4. 功能验证:多组测试数据验证个性化排序效果

该方案为移动端本地化AI推荐提供了轻量级、高效的解决方案。

posted @ 2025-11-23 21:43  ITRyan  阅读(30)  评论(0)    收藏  举报