前言
在写书的时候,之所以事无巨细的讲问题的定义、思考的过程,因为,世界是不断变化的,如果不掌握变化背后的思维方法,仅仅刻舟求剑、缘木求鱼,是无法将问题彻底解决的。因此,我希望借助一些实际的案例,继续诠释梳理想表达的思维方法,借此解决一些大家阅读中的困惑。同时,随着技术的进步和 AI 的发展,书里的思维方法虽然可以复用,但如何复用?如何跟新的技术相结合?这也是本系列文章想要解决的第二个问题。欢迎大家留言,提出需要解答的问题和困惑,我尽量安排在后续文章中一并回答。
用户需求为何会个性化
在开始正式内容之前,先简要介绍一下用户需求为何会个性化以及从何时开始个性化的?首先,我们可以用类比的方法在另一个领域求证。比如,在购物领域,刚参加工作的时候,受限于信用卡额度和工资收入,想要选择一台个人笔记本电脑,大部分情况下只能选择廉价、沉重、屏幕模糊的入门配置,预算卡得越紧,选择的范围越窄。
随着不断成长,个人笔记本电脑带来的信息、学习环境等便利性,以复利的方式在体内积累,终于在一份大厂高薪 Offer 面前瓜熟蒂落。忽的发现,预算一下提高了数个档次,想着:是时候买个好笔记本电脑犒劳一下自己了。这时候,预算宽松,选择的范围变的宽泛。同时,宽泛的选择和需求刺激了供给端,电脑厂商们摩拳擦掌,什么游戏本、轻薄本、标压本、扩展坞外接显卡本……琳琅满目。选择的可能性增加,也就从没得选变成选择困难了。但是,这并不是需求个性化的开始,也不是个性化的根因,这只是个性化的可能,或者叫个性化的生长空间。
换个视角来探求一下个性化的根因。比如,我有两个孩子,老大刚出生的时候没奶吃,大部分时间在喝奶粉。老二刚出生的时候天天吃奶,很少喝奶粉。但是,我并不知道孩子是喜欢喝奶粉呢?还是喜欢喝母乳?做选择的是我和家人。孩子慢慢长大了,看到别的孩子踩水他们也踩水,看到别的孩子爬高上低的他们也跟着爬,甚至别的孩子在地上撒泼打滚,我俩娃也是一学就会。我就问他们:你自己觉得应该怎么对待水?对待楼梯扶手?对待地上的尘土?看别人做就学,没有自己的判断,岂不是很无趣?但孩子们并不在意。
随着一天天长大,老大上学了能自己读书之后,我发现他不好忽悠了,而且很有自己的想法。可见,随着心智的成熟和内化的知识、思想越来越多,孩子有了独立思考的能力和独立的人格。然而,随着社会教育程度的提高和人们素质的普遍提升,追星、哈日、哈韩等情况变的越来越少了,不会再那么强烈的跟风偶像、跟风潮流。更多的人,开始关注自己的内心,关注内心滋养出的独特的个体思维、个人风格。这时,人们慢慢的分道扬镳了,共性的东西逐渐减少、个性的东西逐渐增多,让每个个体宣誓着自己的——卓尔不群。
综上所述,当任何一个用户场景下,只要拥有个性化的生长空间:选择的可能性,同时拥有独立思维能力:选择的动机,用户在该场景下的需求就会离散化。无论这两个变量放大哪一个,都会使用户需求的离散化过程加剧。回到本文的主题,在 AIGC 的时代,富文本内容被 AI 大模型的:生文、生图能力极大的丰富,甚至可以说内容的供给是无限的,因此,选择内容的可能性是无限的。如何根据用户的个性化和无限的内容,找到其中布局样式的最佳信息表达方式?
研究和定义问题
如何根据用户的个性化和无限的内容,找到其中布局样式的最佳信息表达方式?在我的书《UI智能化与前端智能化》中有过详述,书中的方法主要还是以海量的方案、A/B Test 和个性化 UI 方案推荐为主。在书中有对端智能的一些介绍和展望,但因为当时并没有进行完整的实践,所以,没有将端智能的实现方法写在书里。采用 UI 方案推荐,最主要的是通过不断细化推荐的人群,来提供个性化 UI 的能力,但是,在云端进行模型训练有诸如:隐私、成本、实时性等问题。端智能,随着越来越多的小模型开源,越来越多的 NPU 在端侧部署和迭代,终将成为 AI 领域解决用户问题不可或缺的重要环节。因此,本文主要围绕 ONNX 在端上进行增量学习,来实现富文本布局样式的个性化。
第一步:通过示例研究富文本布局样式的问题
首先,假设有一篇图文混排的内容,标题12个汉字,作者和日期在一行,作者3个汉字,日期yyyy-mm-dd,文本长度587个汉字,图片高度200宽度300单位是像素,使用 TailwindCSS 控制和优化样式,并生成相应的骨架图来预览最终文章展示的效果。
<script>
const title = "这是一个12字的标题";
const author = "作者名";
const date = "2024-06-07";
const content = "这里是正文内容,共587个汉字。" + "这里是正文内容,共587个汉字。".repeat(29); // 模拟587个汉字
const imageUrl = "https://via.placeholder.com/300x200"; // 示例图片链接
</script>
<div class="max-w-4xl mx-auto p-4">
<!-- 文章标题 -->
<h1 class="text-2xl font-bold mb-2">{title}</h1>
<!-- 作者和日期 -->
<div class="flex justify-between text-gray-600 mb-4">
<span>{author}</span>
<span>{date}</span>
</div>
<!-- 图片 -->
<div class="mb-4">
<img src={imageUrl} alt="Article Image" class="w-full max-w-sm mx-auto" />
</div>
<!-- 正文内容 -->
<div class="text-justify text-gray-800">
{content}
</div>
</div>
<style>
.skeleton {
background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.3) 50%, rgba(255, 255, 255, 0) 100%);
background-size: 200% 100%;
animation: skeleton 1.5s infinite;
}
@keyframes skeleton {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
</style>
<div class="max-w-4xl mx-auto p-4 mt-10">
<!-- 骨架图 -->
<div class="skeleton h-8 w-3/4 mb-2"></div>
<div class="flex justify-between text-gray-600 mb-4">
<div class="skeleton h-6 w-20"></div>
<div class="skeleton h-6 w-32"></div>
</div>
<div class="skeleton h-52 w-full max-w-sm mx-auto mb-4"></div>
<div class="space-y-4">
<div class="skeleton h-6 w-full"></div>
<div class="skeleton h-6 w-full"></div>
<div class="skeleton h-6 w-full"></div>
<div class="skeleton h-6 w-full"></div>
<div class="skeleton h-6 w-full"></div>
<div class="skeleton h-6 w-full"></div>
<div class="skeleton h-6 w-full"></div>
<div class="skeleton h-6 w-full"></div>
<div class="skeleton h-6 w-full"></div>
<div class="skeleton h-6 w-full"></div>
<div class="skeleton h-6 w-full"></div>
</div>
</div>
通过对渲染结果的分析,可以发现,一个良好的富文本布局样式应该包含:
- 简化结构:
将内容分成更小的部分或模块,让每个模块专注于一个主题。这样可以避免信息过载。
使用颜色和间距来区分不同的部分或模块。 - 色彩搭配:
使用更少的颜色并保持一致。选择一个主要颜色和几个辅助颜色。
使用颜色区分不同的区域或主题,而不是使用多种不同的颜色,这样可以减少视觉疲劳。 - 字体和排版:
使用一致的字体样式和大小。标题、正文、注释等不同层次的内容可以使用不同的字体大小和样式,但保持一致性。
增加行间距和段间距,让内容更容易阅读。 - 图标和图片:
使用图标来表示不同的模块或内容,使其更直观。
确保所有的图标和图片风格一致,避免混乱。
内容优化的部分应该包含:
- 明确主题:
确保每个模块或部分都有一个明确的标题或主题。
确保每个主题下的内容都相关且有逻辑性。 - 减少文字量:
使用简洁明了的语言,避免冗长的句子。
使用要点或列表形式展示信息,便于快速浏览。 - 高亮关键点:
使用粗体、颜色或其他视觉效果高亮关键点或重要信息。 - 视觉层次:
使用不同的字体大小、颜色、图标等来创建视觉层次,使读者一眼就能抓住重点信息。
为了让实现更简洁,针对上述内容我进行了一定的抽象:信息密度、美学分数和信息展示权重。从这三个维度定义一个富文本内容的排版布局样式是否美观,就可以把这个主观的问题变得客观一些,用可量化的指标进行考察和分析。
第二步:对评估信息密度、美学分数和信息展示权重进行数学建模
1. 信息密度计算
信息密度可以通过计算每个块元素的字符数、单词数、图片数量等来量化。具体计算公式如下:
其中:
2. 美学分数计算
美学分数可以通过以下公式计算,该公式考虑了元素的对齐、间距、颜色对比度等因素:
其中:
- 对齐度:计算元素在水平和垂直方向上的对齐程度,值越高表示对齐越好。
- 间距均匀性:计算元素之间间距的标准差,值越低表示间距越均匀。
- 颜色对比度:计算相邻元素颜色的对比度,值越高表示对比越强。
3. 信息展示权重计算
信息展示权重基于元素的重要性和其在页面中的位置来计算。可以使用以下公式:
其中:
综合优化公式
综合考虑信息密度、美学分数和信息展示权重,可以使用以下综合优化公式:
其中:
示例
假设有一个图文混排布局,包括标题、作者和日期、正文和图片,具体如下:
- 标题:12个汉字
- 作者:3个汉字
- 日期:yyyy-mm-dd(10个字符)
- 正文:587个汉字
- 图片:宽度300px,高度200px
步骤
- 信息密度计算:
- 美学分数计算:对齐度:假设对齐度为0.8间距均匀性:假设间距均匀性为0.7颜色对比度:假设颜色对比度为0.9
- 信息展示权重计算:假设标题、作者、日期、正文和图片的重要性权重分别为0.3, 0.1, 0.1, 0.4, 0.1位置权重假设根据视觉优先级分别为0.4, 0.1, 0.1, 0.3, 0.1
- 综合评分计算:
通过这个模型,我们可以量化图文混排布局的综合评分,帮助优化信息密度、美学分数和信息展示权重。这个模型可以进一步调整权重系数和计算公式,以更好地适应具体应用场景和需求。
第三步:模型设计
为了设计一个基于 ONNX 的端侧训练模型,用于根据用户的评分进行后验机器学习,理解用户的个性化布局样式偏好,我们可以考虑以下步骤:
模型架构
输入层:
布局特征: 包含各种布局属性,例如字体大小、颜色、间距、对齐方式等。
上下文特征: 包含内容的类型、用户的历史偏好等。
隐藏层:
卷积层(Conv Layers): 处理图像特征,如果需要考虑图文混排中的图像部分。
全连接层(Fully Connected Layers): 处理所有的布局和上下文特征。
输出层:
评分预测: 预测用户对当前布局的评分。
数据收集
- 用户评分数据: 收集用户对不同布局样式的评分数据。
- 布局样式数据: 收集不同布局样式的特征数据。
- 用户上下文数据: 收集用户的偏好和历史数据。
特征提取
- 布局特征: 提取各种布局属性的数值化表示,例如颜色的RGB值、字体大小的数值等。
- 上下文特征: 提取用户的偏好和历史数据,并进行数值化表示。
数据预处理
- 归一化: 对数值型特征进行归一化处理。
- 分类编码: 对分类特征进行独热编码(One-Hot Encoding)或标签编码(Label Encoding)。
模型训练
- 初始训练: 在服务器端进行初始训练,生成初始模型权重。
- 端侧训练: 将模型部署到用户设备上,根据用户评分进行在线更新。
用户评分反馈
- 实时反馈: 用户在使用过程中对布局样式进行评分。
- 数据更新: 收集用户的评分数据和当前布局样式的特征。
在线训练
- 小批量更新: 使用小批量(mini-batch)SGD或其他优化算法,根据新收集的数据进行模型权重更新。
- 模型更新: 定期将更新后的模型权重同步回服务器,以便进行全局优化。
第四步:实现细节
特征处理(使用 PyTorch 示例)
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
# 模块化特征处理器
class FeatureProcessor:
def __init__(self):
self.embeddings = nn.ModuleDict({
'user_identity': nn.Embedding(num_embeddings=100, embedding_dim=10),
'time_of_day': nn.Embedding(num_embeddings=24, embedding_dim=5),
'location': nn.Embedding(num_embeddings=50, embedding_dim=8),
'device_type': nn.Embedding(num_embeddings=10, embedding_dim=3),
# 添加其他类别特征的Embedding...
})
def process(self, features):
processed_features = []
for feature_name, feature_value in features.items():
if feature_name in self.embeddings:
embedded_feature = self.embeddings[feature_name](torch.tensor([feature_value]))
processed_features.append(embedded_feature.squeeze(0))
else:
processed_features.append(torch.tensor([feature_value], dtype=torch.float32))
return torch.cat(processed_features)
# 创建示例数据集
class LayoutDataset(Dataset):
def __init__(self, data, feature_processor):
self.data = data
self.feature_processor = feature_processor
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
features = self.data[idx]['features']
label = self.data[idx]['label']
processed_features = self.feature_processor.process(features)
return processed_features, torch.tensor(label, dtype=torch.float32)
# 示例数据
data = [
{'features': {'user_identity': 5, 'time_of_day': 14, 'location': 3, 'device_type': 1, 'font_size': 12, 'color_value': 200}, 'label': 4.5},
{'features': {'user_identity': 7, 'time_of_day': 20, 'location': 5, 'device_type': 2, 'font_size': 13, 'color_value': 150}, 'label': 3.8},
# 更多数据...
]
# 创建特征处理器和数据集
feature_processor = FeatureProcessor()
dataset = LayoutDataset(data, feature_processor)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)
# 查看处理后的特征示例
for features, label in dataloader:
print('Processed Features:')
print(features)
print('Labels:')
print(label)
break # 仅打印一个batch的示例
模型定义
# 定义注意力机制
class Attention(nn.Module):
def __init__(self, input_dim):
super(Attention, self).__init__()
self.input_dim = input_dim
self.attention_weights = nn.Parameter(torch.randn(input_dim))
def forward(self, x):
attention_scores = torch.matmul(x, self.attention_weights)
# Ensure attention_scores has correct shape: (batch_size, num_features)
attention_weights = torch.softmax(attention_scores, dim=-1) # softmax along the last dimension
# Expand attention_weights to match the last dimension of x
attention_weights = attention_weights.unsqueeze(-1)
# Multiply x by attention_weights element-wise
x = x * attention_weights
return x
# 定义布局偏好模型
class LayoutPreferenceModel(nn.Module):
def __init__(self, input_dim):
super(LayoutPreferenceModel, self).__init__()
self.fc1 = nn.Linear(input_dim, 128)
self.fc2 = nn.Linear(128, 64)
self.fc3 = nn.Linear(64, 1)
self.attention = Attention(input_dim)
def forward(self, x):
x = self.attention(x)
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = self.fc3(x)
return x
# 初始化模型
input_dim = sum(embedding.embedding_dim for embedding in feature_processor.embeddings.values()) + 2 # 加上两个额外的数值特征
model = LayoutPreferenceModel(input_dim)
# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
模型训练
# 示例训练过程
def train(model, dataloader, criterion, optimizer, num_epochs=100):
model.train()
for epoch in range(num_epochs):
for batch in dataloader:
features, label = batch[0], batch[1] # 假设每个批次是一个列表,第一个元素是特征,第二个元素是标签
optimizer.zero_grad()
outputs = model(features)
outputs = outputs.squeeze() # 去除多余的维度,确保与标签形状匹配
loss = criterion(outputs, label)
loss.backward()
optimizer.step()
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
# 开始训练
train(model, dataloader, criterion, optimizer)
导出 ONNX 模型
import onnx
import onnxruntime as ort
# 导出模型为ONNX格式
dummy_input = torch.randn(1, input_dim)
torch.onnx.export(model,
dummy_input,
"layout.onnx", # 输出文件路径
verbose=True, # 是否打印详细信息
input_names=['input'], # 输入名称,通常是input
output_names=['output']) # 输出名称,通常是output
端侧增量学习(使用 ONNX Runtime)
import numpy as np
# 步骤2:使用 ONNX Runtime 进行增量学习
# 加载 ONNX 模型进行增量学习
def incremental_train_onnx(model_path, updated_data, feature_processor):
# 加载原始 ONNX 模型
original_model = onnx.load(model_path)
# 创建更新后的模型文件名
updated_model_path = "layout.onnx"
# 准备数据
features = [feature_processor.process(data['features']) for data in updated_data]
labels = [data['label'] for data in updated_data]
# 在端侧执行增量学习,更新模型的权重
for features_batch, label in zip(features, labels):
ort_inputs = {original_model.graph.input[0].name: features_batch.unsqueeze(0).detach().numpy()}
ort_labels = np.array([label], dtype=np.float32)
# 创建 ONNX SessionOptions
options = ort.SessionOptions()
options.log_severity_level = 3 # 设置详细日志级别
# 加载更新后的模型
ort_session = ort.InferenceSession(updated_model_path, options)
# 训练模型
ort_outputs = ort_session.run(None, ort_inputs)
# 计算损失(这里可以根据需要修改,这里简化为单个样本的 MSE 损失)
loss = np.mean(np.square(ort_outputs[0] - ort_labels))
# 打印训练日志
print(f'Loss: {loss:.4f}')
# 保存更新后的模型(这一步是可选的)
onnx.save_model(original_model, updated_model_path)
print(f"Updated model saved to {updated_model_path}")
# 创建特征处理器
feature_processor = FeatureProcessor()
# 新数据示例
updated_data = [
{'features': {'user_identity': 8, 'time_of_day': 10, 'location': 1, 'device_type': 3, 'font_size': 11, 'color_value': 180}, 'label': 4.2},
# 更多新数据...
]
# 开始增量学习
incremental_train_onnx("layout.onnx", updated_data, feature_processor)
总结
至此,我们实现一个基于 ONNX 的端侧训练模型,用于根据用户评分进行后验机器学习,理解用户的个性化布局样式偏好。这种方法不仅可以提高模型的个性化和准确性,还能通过端侧训练减轻服务器的负担,实现更高效的模型更新和部署。虽然,限于时间这只是一个示例代码,但代码我都在本机测试过,框架和逻辑上应该是正确的。通过上述内容的分享,我期望能把自己对如何通过算法模型做 UI 个性化这个事儿的思路讲清楚,如果大家有什么不明白的地方,或者有其它问题,都可以留言给我。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/82372.html