Skip to main content

4 posts tagged with "Machine Learning"

View All Tags

Model Explorer是一个强大的图形可视化工具,帮助人们理解、调试和优化机器学习模型。 它专注于以直观的分层格式可视化大型图形,但也适用于较小的模型。

图形可视化在机器学习(ML)开发过程中起着至关重要的作用。 模型的视觉表示帮助研究人员和工程师调试转换和量化问题,识别性能瓶颈,找到优化模式(例如,操作融合),并深入了解模型架构。

这些实践对许多类型的模型都很有用,特别是在将模型部署到资源有限的设备(如手机和浏览器)时。 然而,现代ML和AI模型(例如Transformersdiffusers)的规模和复杂性不断增加,这给现有的可视化工具带来了重大挑战。

大规模基于Transformer的模型通常会使传统的图形可视化工具不堪重负,导致渲染失败或视觉复杂度难以管理。 此外,缺乏清晰的视觉层次结构使人们难以理解节点之间的关系。

为了解决这些限制,我们推出了Model Explorer

这是一种新颖的图形可视化解决方案,可以顺畅处理大型模型并可视化分层信息,如函数名称和作用域。

Model Explorer支持多种图形格式,包括JAX、PyTorch、TensorFlow和TensorFlow Lite使用的格式。

最初作为谷歌研究人员和工程师的实用工具开发,Model Explorer现在作为我们谷歌AI Edge产品系列的一部分向公众开放。

Model Explorer在简化将大型模型部署到设备平台方面特别有效,其中可视化转换、量化和优化数据尤为有用。

我们描述了Model Explorer如何将用于3D游戏和动画制作的图形技术(例如实例化渲染和多通道有符号距离场(MSDF))相结合, 并将其应用于ML图形渲染。 我们展示了Model Explorer为开发大型模型提供了三种用例:理解模型架构、调试转换错误以及调试性能和数字问题。

Model Explorer以流畅的方式可视化大型模型并提供分层信息

大型图形可视化中的挑战

将大型模型图形可视化存在两个主要技术挑战。 首先,布局算法难以随着图形规模的增长而扩展:随着节点数量的增加,它们的计算复杂性急剧上升,导致布局阶段明显减慢,甚至偶尔完全失败。 即使成功的布局也往往过于密集和复杂,降低了可解释性。

其次,大多数现有的模型可视化工具使用基于可缩放矢量图形的渲染,这种渲染不适用于渲染大量对象。 缩放和滚动操作变得迟缓和无响应,使可视化工具无法使用。

受Tensorboard图形可视化器中分层布局概念的启发,我们开发了一个库,从像TensorFlow、PyTorch和JAX这样的主流创作框架中提取分层信息。 然后,我们构建了一个交互式系统,从最顶层开始可视化节点,允许用户通过逐层展开或折叠层来逐步浏览图形。 这使用户能够按需检查图形内部结构和连接。

由于布局算法是基于每层进行操作的,Model Explorer在用户选择打开一层时计算布局, 这种方法避免了在初始图形加载期间对所有节点进行不必要的计算,并显著改善了大型模型的性能。

我们通过在WebGL和three.js中实现GPU加速图形渲染来解决第二个挑战。 因此,我们实现了每秒60帧(FPS)的流畅用户体验,即屏幕每秒显示60幅图像, 即使包含数万个节点的图形也能创建平滑和逼真的交互和动画效果。 此外,我们利用实例化渲染技术在场景中同时渲染对象的多个副本。 下面的示例展示了一个包含50,000个节点和5,000条边的图形(为演示目的随机生成)如何在2019年款Macbook Pro上的集成GPU上以60 FPS的流畅度呈现。 为了提高视觉连续性,我们为层导航添加了平滑动画,帮助用户保持对模型结构位置的理解。

Model Explorer以60 FPS流畅地呈现了一个包含50,000个节点和5,000条边的图形(为演示目的随机生成)

理解模型架构

通过基于层的视图和导航复杂结构的能力,大型模型变得更容易理解。 例如,在下面的MobileBert模型中,很明显自注意力掩码和嵌入被馈送到一个Transformer层中。嵌入层的扩展视图显示了不同类型嵌入之间的关系。总共有近2,000个节点,没有这些分层信息,这个模型几乎是不可能理解的。

具有自注意力掩码、嵌入和Transformer层等层信息,有助于理解模型架构](https://storage.googleapis.com/gweb-research2023-media/images/model-explorer-layer-info-img.original.png)

调试转换错误

在部署到特定硬件(如手机或笔记本电脑)之前,ML模型必须经过转换过程:例如将PyTorch模型转换为Tensorflow Lite模型。 然而,在转换过程中往往会丢失有关转换的信息。

为了比较多个图形,Model Explorer提供了并排比较模式。 下面的示例突出显示了在从PyTorch到TensorFlow Lite的转换过程中一个层内的子图(即torch.nn.modules.linear.Linear_attn)的变化。 比较层的输入和输出的形状和数据类型信息可以帮助突出转换错误。

比较PyTorch模型与TensorFlow Lite模型结构

调试性能和数字准确性

另一个Model Explorer的功能是能够在图上叠加每个节点的数据, 允许用户使用数据中的值对节点进行排序、搜索和样式化。

与分层视图相结合,我们用户研究中的参与者表示,这有助于他们快速缩小图中某个区域的性能或数值问题, 如果没有这种可视化,这些见解将会更加难以捉摸。 下面的示例显示了量化 TFLite 模型的均方误差与其浮点数对照模型的情况。

使用 Model Explorer,用户能够快速确定质量下降发生在图的底部附近,并调整他们的量化方法。

通过每个节点数据叠加,用户可以快速识别模型中的性能或数字问题。

安装和启动Model Explorer

  1. 安装
pip install model-explorer
  1. 启动
model-explorer
  1. 打开浏览器,输入http://localhost:8080,即可查看模型可视化。

  2. 拖拽或者点击上传模型文件,即可查看模型结构。

结论

Model Explorer引入了一种强大的新方式,可以在几乎任何规模的模型中检查架构并调试问题, 而不会影响用户体验或呈现性能。

它以清晰的方式呈现模型结构,使用层和分组来提高理解,并融入调试特性和层级洞察力,以支持模型分析。 Model Explorer现在已公开。请访问Model Explorer网站 以获取安装说明,并提供反馈。

参考资料

鱼雪

CandleRust 的极简 ML 框架,重点关注性能(包括 GPU 支持)和易用性。 今天我们来使用Candle完成一个深度学习的Hello World案例:手写数字识别。 我们使用最简单的线性模型来训练一个自己的手写数字识别模型,作为Candle框架的 最简单入门案例。

环境

  • Rust: 1.75.0-nightly
  • candle-core: 0.3.0
  • candle-nn: 0.3.0
  • candle-datasets: 0.3.0
tip

candle-nn当前版本中依赖了Rust nightly

Cargo.toml内容如下

  1. rand: 随机数
  2. anyhow: 处理异常
  3. clap: 解析命令行参数
[package]
name = "linear_mnist"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
candle-core = { git = "https://github.com/huggingface/candle.git", version = "0.3.0" }
candle-nn = { git = "https://github.com/huggingface/candle.git", version = "0.3.0" }
rand = "0.8.5"
anyhow = "1"
clap = { version = "4.4.4", features = ["derive"] }
candle-datasets = { git = "https://github.com/huggingface/candle.git", version = "0.3.0" }

创建项目并安装Candle相关模块

  1. 使用cargo new创建linear_mnist项目
  2. 进入项目目录
  3. 安装candle三个模块
    • candle-core
    • candle-nn
    • candle-datasets
  4. 安装其他依赖库
    • rand
    • anyhow
    • clap

具体操作如下:

cargo new linear_mnist
cd linear_mnist

cargo add --git https://github.com/huggingface/candle.git candle-core
cargo add --git https://github.com/huggingface/candle.git candle-nn
cargo add --git https://github.com/huggingface/candle.git candle-datasets

代码

导入相关依赖

  1. 导入clap::Parser解析命令行参数
  2. 导入candle_core的相关依赖
    • Device: 数据计算时放置的设备
    • Result: 处理异常
    • Tensor: 张量数据类型
    • D: 是一个enum,包含Minus1Minus2
    • DType: 数据类型enum结构,包含支持的数据类型
  3. 导入candle-nn的相关依赖
    • loss: 损失函数相关操作
    • ops: 函数操作,如log_softmax
    • Linear: 线性模型
    • Module: 由于Linear的依赖
    • Optimizer: 优化器
    • VarBuilder: 构建变量
    • VarMap: 用于存储模型变量
use clap::{ Parser };
use candle_core::{ Device, Result, Tensor, D, DType };
use candle_nn::{ loss, ops, Linear, Module, Optimizer, VarBuilder, VarMap };

定义相关配置

  1. 定义图像维度数量和标签数量的常量
  2. 定义命令行参数解析,并添加指令宏#[derive(Parser)],可以使用clap::Parser解析命令行参数
    • learning_rate: 学习率
    • epochs: 模型训练迭代次数
    • save_model_path: 训练好的模型保存路径
    • load_model_path: 加载预训练模型路径
    • local_mnist: 本地MNIST数据集目录
  3. 定义训练参数结构体TrainingArgs
  4. 定义线性模型结构体LinearModel

具体代码如下:

const IMAGE_DIM: usize = 784;
const LABELS: usize = 10;

#[derive(Parser)]
struct Args {
#[arg(long)]
learning_rate: Option<f64>,

#[arg(long, default_value_t = 10)]
epochs: usize,

#[arg(long)]
save_model_path: Option<String>,

#[arg(long)]
load_model_path: Option<String>,

#[arg(long)]
local_mnist: Option<String>,
}

struct TrainingArgs {
learning_rate: f64,
load_path: Option<String>,
save_path: Option<String>,
epochs: usize,
}

struct LinearModel {
linear: Linear,
}

定义模型

  1. 定义Model trait
  2. LinearModel实现Model trait
    • new: 初始化模型
    • forward: 模型结构,前向传播
  3. linear_z是具体创建Linear模型
    • 创建模型张量变量并调用candle-nn::Linear创建线性模型返回

具体代码如下:

trait Model: Sized {
fn new(vs: VarBuilder) -> Result<Self>;
fn forward(&self, xs: &Tensor) -> Result<Tensor>;
}

impl Model for LinearModel {
fn new(vs: VarBuilder) -> Result<Self> {
let linear: Linear = linear_z(IMAGE_DIM, LABELS, vs)?;
Ok(Self { linear })
}

fn forward(&self, xs: &Tensor) -> Result<Tensor> {
self.linear.forward(xs)
}
}

fn linear_z(in_dim: usize, out_dim: usize, vs: VarBuilder) -> Result<Linear> {
let ws: Tensor = vs.get_with_hints((out_dim, in_dim), "weight", candle_nn::init::ZERO)?;
let bs: Tensor = vs.get_with_hints(out_dim, "bias", candle_nn::init::ZERO)?;
Ok(Linear::new(ws, Some(bs)))
}

定义模型训练函数

  1. 输入参数
    • m: 数据集
    • args: 训练参数TrainingArgs
  2. 获取或设置模型运算的设备Device::Cpu
  3. 从数据集m中获取训练数据和标签,测试数据和标签
  4. 创建varmap用来存储模型参数
  5. 创建vs变量构造,存储模型参数,并将其传入到Model::new
  6. 如果命令行传入load_model_path,则会加载预训练模型
  7. 创建优化器SGD
  8. 根据epochs迭代训练模型
    • 前向传播得到logits
    • 计算概率log_softmax
    • 计算损失函数值
    • 反向传播sgd.backward_step()
    • 输入测试数据得到测试数据准确率test_accuracy
    • 每个epoch花费的时间epoch_duration
  9. 如果命令传入save_model_path,则会保存模型参数
    • 确保存放模型的目录已经建立

具体代码如下:

fn train<M: Model>(
m: candle_datasets::vision::Dataset,
args: &TrainingArgs) -> anyhow::Result<()> {

let dev = Device::Cpu;

let train_labels = m.train_labels;
let train_images = m.train_images.to_device(&dev)?;
let train_labels = train_labels.to_dtype(DType::U32)?.to_device(&dev)?;
let test_images = m.test_images.to_device(&dev)?;
let test_labels = m.test_labels.to_dtype(DType::U32)?.to_device(&dev)?;

let mut varmap = VarMap::new();
let vs = VarBuilder::from_varmap(&varmap, DType::F32, &dev);
let model = M::new(vs.clone())?;

// Load Pre-trained Model Parameters
if let Some(load_path) = &args.load_path {
println!("Loading model from {}", load_path);
let _ = varmap.load(load_path);
}

// Create Optimizer
let mut sgd = candle_nn::SGD::new(varmap.all_vars(), args.learning_rate)?;

// Iterate training model
for epoch in 1..=args.epochs {
let start_time = std::time::Instant::now();
let logits = model.forward(&train_images)?;
let log_sm = ops::log_softmax(&logits, D::Minus1)?;
let loss = loss::nll(&log_sm, &train_labels)?;

sgd.backward_step(&loss)?;

let test_logits = model.forward(&test_images)?;
let sum_ok = test_logits
.argmax(D::Minus1)?
.eq(&test_labels)?
.to_dtype(DType::F32)?
.sum_all()?
.to_scalar::<f32>()?;
let test_accuracy = sum_ok / test_labels.dims1()? as f32;
let end_time = std::time::Instant::now();
let epoch_duration = end_time.duration_since(start_time);
println!("Epoch: {epoch:4} Train Loss: {:8.5} Test Acc: {:5.2}% Epoch duration: {:.2} second.",
loss.to_scalar::<f32>()?, test_accuracy * 100., epoch_duration.as_secs_f64());
}

// Save Model Parameters
if let Some(save_path) = &args.save_path {
println!("Saving trained weight in {save_path}");
varmap.save(save_path)?
}
Ok(())
}

main函数

  1. 解析命令行参数Args
  2. 根据local_mnist命令行参数指定的目录加载MNIST数据集
  3. 设置学习率
  4. 创建模型训练参数TrainingArgs类型变量training_args并填充设置好的参数
  5. 调用模型训练函数train::<LinearModel>(m, &training_args),传入数据集模型训练参数
fn main() ->anyhow::Result<()> {
let args: Args = Args::parse();
let m: candle_datasets::vision::Dataset = if let Some(directory) = args.local_mnist {
candle_datasets::vision::mnist::load_dir(directory)?
} else {
candle_datasets::vision::mnist::load()?
};

println!("Train Images: {:?}", m.train_images.shape());
println!("Train Labels: {:?}", m.train_labels.shape());
println!("Test Images: {:?}", m.test_images.shape());
println!("Test Labels: {:?}", m.test_labels.shape());

let default_learning_rate: f64 = 0.1;

let training_args = TrainingArgs {
epochs: args.epochs,
learning_rate: args.learning_rate.unwrap_or(default_learning_rate),
load_path: args.load_model_path,
save_path: args.save_model_path,
};

train::<LinearModel>(m, &training_args)
}

训练

  1. 如果saved_model不存在,则需要先创建该目录

  2. 目录结构如下

linear_mnist
├── Cargo.lock
├── Cargo.toml
├── dataset
│   ├── t10k-images-idx3-ubyte
│   ├── t10k-labels-idx1-ubyte
│   ├── train-images-idx3-ubyte
│   └── train-labels-idx1-ubyte
├── saved_model
│   └── minst.safetensors
└── src
└── main.rs
  1. 训练并保存模型参数

HuggingFace Candle 训练手写数字识别

  1. 加载预训练模型继续训练

HuggingFace Candle加载预训练模型

  1. 完整代码地址

Candel Linear Model Training MNIST Classification on Github

参考代码

Candle MNIST Training

鱼雪

CLIP(Contrastive Language-Image Pre-training)是由OpenAI开发的一个深度学习模型,用于处理图像和文本之间的语义关系。CLIP通过对图像和文本进行联合训练,学习到了一个通用的表示空间,使得相似的图像和文本在这个空间中距离较近,而不相似的则距离较远。

监督学习的局限性

由图像分类模型生成的现成特征已被用于图像检索等其他任务中。

但是,这些特征的通用性不强,

因为分类模型是为识别一组固定的类别而训练的。

要在这组类别中添加任何新类别

都需要为这一新类别收集额外的标注图像,

然后重新训练模型。

这是一个耗时且昂贵的过程。

能否利用自监督学习技术来解决这一问题?

能否利用图像说明来生成更好的图像表征并避免标注成本? 也就是说,能否使用自然语言作为监督来学习视觉感知?

主要贡献

作者提出了一项预训练任务(CLIP = Contrastive Language Pre-training),

  • 即预测哪张图片配哪句标题,以便从头开始学习SOTA图像表征。

为此,他们创建了一个从互联网上收集的包含4亿个(图片、文本)配对的数据集:

  • 这种预先训练好的模型可以不费吹灰之力地应用到大多数任务中
  • 而且不需要任何特定数据集的训练,就能与完全有监督的基线模型相媲美。

背景

CLIP从监督图像描述(supervised image captioning):

  • 每张图片(image)都有相应的标题(caption),
  • 用来训练一个模型,
  • 预测相应图片标题中的准确词语。

这是一项艰巨的任务,因为一幅图像可以有多种不同的描述方式,但仍能表达相同的含义。

Contrastive Pre-training(对比预训练)

对比预训练:

  • 考虑一批N个图像(images)及其相应的N个标题(captions)。
  • 有了这些,我们可以再批次中创建NxN可能的(图像、文本)配对。
  • 现在,任务是预测批次中的N个真实对

为此,CLIP通过联合训练:

  • 图像编码器文本编码器
  • 学习多模态嵌入空间
  • 图像编码器产生特征向量I;
  • 类似地,文本编码器产生一个特征向量T;
  1. 对于N个实数对,我们希望最大化IT之间的余弦相似度
  2. 对于$N^2-N$不正确的配对,我们希望最小化IT之间的余弦相似度

对比预训练


|----------| |---------------| |----------|
| Image | ---> | Image Encoder | ---> | I Vector | ---------------|
|----------| |---------------| |----------| V
|---------------|
| |
| |
| |
|----------| |---------------| |----------| | |
| Text | ---> | Text Encoder | ---> | T Vector |------> | |
|----------| |---------------| |----------| |---------------|

图片来源于论文

零样本预测

考虑图像分类任务, 预测时,对于单张图像,图像编码器将生成一个特征向量$I_1$ 为了识别图像类别, 文本编码器会嵌入目标数据集的类别名称, 生成N个特征向量$T_1,T_2,......$,以此类推。 N表示目标数据集中的类别数

Create dataset classifier from label text
|-------|
| plane |
| car | |-----------------------| |--------------|
| dog | -----> | A photo of a {object} | ----------------> | Text Encoder |
| bird | |-----------------------| |--------------|
| cat | |
|-------| |
|
Use for zero-shot prediction V
|-------| |---------------| |-----| |-----|-----|-----|-----|-----|
| Image | -----> | Image Encoder | -----> | I_1 | | T_1 | T_2 | T_3 | ... | T_N |
|-------| |---------------| |-----| |-----|-----|-----|-----|-----|
| |
| .* |
|-----------------------------------|
|
V
|--------|--------|--------|-----|--------|
|I_1.*T_1|I_1.*T_2|I_1.*T_3| ... |I_1.*T_N|
|--------|--------|--------|-----|--------|
|
V
|--------------------|
| A photo of a `dog` |
|--------------------|

图片来源于论文

模型细节

对于图像编码器,作者评估了两种不同的架构:

  • ResNet-50
  • ViT

ResNet-50

他们使用了改进的ResNet-D架构,

并进行了抗锯齿矩阵-2模糊池化(anti-aliased rect-2 blur pooling)处理。

他们还用Transformer-style attention pooling mechanism替换了global average pooling layer

ViT(Vision Transformer)

作者在Transformer之前

组合patchposition embedding进行了额外的归一化处理( use an additional layer normalization to the combined patch and position embedding),

并使用了略有不同的初始化方案。

对于文本编码器

使用本文中描述的具有:

  • 6300万个参数(12层 512-wide)
  • 8个注意力头的Transformer

训练

作者训练的模型包括:

  • 5个ResNet

    • ResNet-50
    • ResNet-101
    • 3个EfficientNet风格的ResNet模型
  • 3个ViT

    • ViT-B/32
    • ViT-B/16
    • ViT-L/14
  • The models are trained:

    • 32 epochs
    • using Adam optimizer with decoupled weight decay regularization
    • decay the learning rate using a cosine schedule
    • used a very large minibatch size of 32,768

提示工程的效果(Effect of Prompt Engineering)

图像分类数据集标注了与类名相对应的label IDs

由于CLIP模式在文本为一个完整句子的情况下进行训练的,

因此作者发现使用提示模版(prompt template)A photo of a {label}

是与图像相关联的文本的良好默认设置。

我们可以看到在36各分类数据集中,使用提示工程的分类准确率提高了5个百分点。

图片来源于论文

Zero-shot CLIP vs. Linear Probe

在27个数据集中的16个数据集上:

  • zero-shot CLIP分类器的表现优于基于ResNet-50特征的监督线性分类器。
  • 不过,在大多数数据集上,CLIP的性能仍低于最新水平

图片来源于论文

Limitations(局限性)

  1. CLIP以下任务上性能不佳
    • counting object in an image
    • finding the distance to the nearest object object in an image
  2. 在MNIST等分布外数据集上的表现非常差
    • 在数字OCR上性能很好
    • 但在识别MNIST手写数字方面却不好(准确率88%)
  3. 使用CLIP进行few-shot learning会导致性能不佳
    • from zero-shot to few-shot learning时,性能会出现反直觉的下降
  4. 由于CLIP是根据互联网上查询的文本图像对进行训练的
    • 因此它将会学习许多社会偏见

References

Notes on CLIP: Connecting Text and Images

CLIP paper

鱼雪

CandleRust 的极简 ML 框架,重点关注性能(包括 GPU 支持)和易用性。尝试我们的在线演示: whisperLLaMA2T5yoloSegment Anything

模块

Candle项目包括一些crates,如下:

  • candle-book: candle相关的文档
  • candle-core: 核心功能库,核心操作,设备,Tensor结构定义等。
  • candle-nn: 神经网络,构建真实模型的工具
  • candle-examples: 在实际环境中使用库的示例
  • candle-datasets: 数据集和数据加载
  • candle-transformers: Transformer相关实现工具
  • candle-flash-attn: Flash Attention v2实现
  • candle-kernels: CUDA加速实现
  • candle-pyo3: Rust提供的Python接口
  • candle-wasm-examples: Rust WASM示例

其它有用的库:

  • candle-lora: 提供了符合官方peft实现的LoRA实现

特点

  • 语法简单(看起来像PyTorch)
    • 支持模型训练
    • 支持用于自定义操作运算
  • 后端
    • 优化的CPU后端,具有针对x86的可选MKL支持和针对MacAccelerate支持
    • CUDA后端可以再GPU上高效运行,通过NCCL运行多GPU分配
    • WASM支持,在浏览器中运行模型
  • 包含的模型
    • 语言模型
      • LLaMA v1 and v2
      • FaIcon
      • StarCoder
      • Phi v1.5
      • T5
      • Bert
    • Whisper(多语言支持)
    • Stable Diffusion v1.5, v2.1, XL v1.0
    • Wurstchen v2
    • 计算机视觉
      • DINOv2
      • EfficientNet
      • yolo-v3
      • yolo-v8
      • Segmeng-Anything(SAM)
  • 文件格式
    • 加载模型支持的格式如下:
      • safetensors
      • npz
      • ggml
      • PyTorch files
  • 无服务部署
    • 小型且快速的部署
  • 使用llama.cpp量化类型的量化支持

基本用法介绍

  1. 创建张量

    Tensor::new(&[[1f32, 2.], [3., 4.]], &Device::Cpu)?
    Tensor::zeros((2, 2), DType::F32, &Device::Cpu)?
  2. 张量索引

    tensor.i((.., ..4))?
  3. 张量重塑

    tensor.reshape((2, 2))?
  4. 张量矩阵乘法

    a.matmul(&b)?
  5. 张量数据移动到特定设备

    tensor.to_device(&Device::new_cuda(0)?)?
  6. 更改张量数据类型

    tensor.to_dtype(&Device::F16)?
  7. 张量算术运算

    &a + &b
  8. 保存模型

    candle::safetensors::save(&HashMap::from([("A", A)]), "model.safetensors")?
  9. 加载模型

    candle::safetensors::load("model.safetensors", &device)
鱼雪