Transformer模型在时间序列预测中的应用:annotated-transformer扩展
引言:时间序列预测的痛点与Transformer的突破
你是否还在为时间序列预测中的长期依赖建模而困扰?传统的循环神经网络(RNN)和长短期记忆网络(LSTM)在处理长序列时面临梯度消失和计算效率低下的问题,而卷积神经网络(CNN)则难以捕捉远距离依赖关系。2017年提出的Transformer模型凭借自注意力(Self-Attention)机制,彻底改变了序列建模的范式,为时间序列预测带来了新的可能。
本文将详细介绍如何基于开源项目annotated-transformer实现时间序列预测功能,通过具体代码示例和架构解析,帮助你快速掌握Transformer在时间序列预测中的应用。读完本文,你将能够:
- 理解Transformer模型的核心组件及其在时间序列预测中的适配方法
- 修改
annotated-transformer代码以支持时间序列数据输入 - 实现带时间特征的位置编码和适用于时间序列的注意力掩码
- 构建完整的时间序列预测 pipeline 并进行模型训练与评估
Transformer模型架构回顾
整体架构
Transformer模型采用编码器-解码器(Encoder-Decoder)架构,完全基于自注意力机制,摒弃了传统的循环和卷积结构。其核心优势在于:
- 并行计算能力:相比RNN的顺序计算,Transformer可以并行处理序列中的所有位置
- 长距离依赖捕捉:通过自注意力机制,模型可以直接建模序列中任意两个位置之间的关系
- 可解释性:注意力权重矩阵提供了直观的特征重要性可视化
flowchart TD
subgraph "输入处理"
A[时间序列数据] --> B[嵌入层(Embedding)]
B --> C[位置编码(Positional Encoding)]
end
subgraph "编码器(Encoder)"
C --> D[多头自注意力(Multi-Head Self-Attention)]
D --> E[残差连接与层归一化]
E --> F[前馈神经网络(Feed Forward Network)]
F --> G[残差连接与层归一化]
G --> H[N个编码器层堆叠]
end
subgraph "解码器(Decoder)"
H --> I[掩蔽多头自注意力(Masked Multi-Head Self-Attention)]
I --> J[残差连接与层归一化]
J --> K[编码器-解码器注意力(Encoder-Decoder Attention)]
K --> L[残差连接与层归一化]
L --> M[前馈神经网络(Feed Forward Network)]
M --> N[残差连接与层归一化]
N --> O[N个解码器层堆叠]
end
subgraph "输出处理"
O --> P[线性变换(Linear)]
P --> Q[预测层(Prediction)]
end
Q --> R[时间序列预测结果]
核心组件解析
1. 多头自注意力机制
多头自注意力机制通过并行计算多个注意力头,允许模型同时关注不同位置和不同表示子空间的信息。其数学定义如下:
其中每个注意力头的计算为:
而注意力函数定义为:
在annotated-transformer中,多头自注意力的实现代码如下:
class MultiHeadedAttention(nn.Module):
def __init__(self, h, d_model, dropout=0.1):
super(MultiHeadedAttention, self).__init__()
assert d_model % h == 0
self.d_k = d_model // h
self.h = h
self.linears = clones(nn.Linear(d_model, d_model), 4)
self.attn = None
self.dropout = nn.Dropout(p=dropout)
def forward(self, query, key, value, mask=None):
if mask is not None:
mask = mask.unsqueeze(1)
nbatches = query.size(0)
# 线性投影并分拆为h个注意力头
query, key, value = [
lin(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)
for lin, x in zip(self.linears, (query, key, value))
]
# 应用注意力
x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout)
# 拼接所有注意力头的结果并进行最终线性变换
x = x.transpose(1, 2).contiguous().view(nbatches, -1, self.h * self.d_k)
return self.linears[-1](x)
2. 位置编码
由于Transformer没有循环结构,需要通过位置编码(Positional Encoding)注入序列的位置信息。原始Transformer使用正弦余弦函数生成固定位置编码:
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout, max_len=5000):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout)
# 计算位置编码
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x):
x = x + self.pe[:, :x.size(1)].requires_grad_(False)
return self.dropout(x)
3. 编码器与解码器层
编码器层由多头自注意力子层和前馈神经网络子层组成:
class EncoderLayer(nn.Module):
def __init__(self, size, self_attn, feed_forward, dropout):
super(EncoderLayer, self).__init__()
self.self_attn = self_attn
self.feed_forward = feed_forward
self.sublayer = clones(SublayerConnection(size, dropout), 2)
self.size = size
def forward(self, x, mask):
# 自注意力子层
x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))
# 前馈神经网络子层
return self.sublayer[1](x, self.feed_forward)
解码器层在编码器层基础上增加了编码器-解码器注意力子层:
class DecoderLayer(nn.Module):
def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
super(DecoderLayer, self).__init__()
self.size = size
self.self_attn = self_attn
self.src_attn = src_attn
self.feed_forward = feed_forward
self.sublayer = clones(SublayerConnection(size, dropout), 3)
def forward(self, x, memory, src_mask, tgt_mask):
m = memory
# 掩蔽自注意力子层(防止关注未来位置)
x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))
# 编码器-解码器注意力子层
x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))
# 前馈神经网络子层
return self.sublayer[2](x, self.feed_forward)
Transformer在时间序列预测中的适配
数据表示与输入处理
时间序列数据通常表示为连续的数值序列,与自然语言处理中的离散token序列有本质区别。因此,需要对annotated-transformer的输入处理部分进行修改:
- 移除词嵌入层:时间序列数据为连续值,无需词嵌入
- 调整输入维度:将单变量或多变量时间序列直接映射到模型维度
- 特征工程:添加时间特征(如时间戳、周期性特征等)
class TimeSeriesEmbedding(nn.Module):
"""时间序列嵌入层,将数值序列映射到模型维度"""
def __init__(self, input_dim, d_model):
super(TimeSeriesEmbedding, self).__init__()
self.linear = nn.Linear(input_dim, d_model)
self.norm = nn.LayerNorm(d_model)
def forward(self, x):
# x shape: (batch_size, seq_len, input_dim)
x = self.linear(x)
return self.norm(x)
时间序列位置编码扩展
原始位置编码仅考虑相对位置,对于时间序列,我们可以增强位置编码以包含更多时间特征:
class TemporalPositionalEncoding(nn.Module):
"""带时间特征的位置编码"""
def __init__(self, d_model, dropout, max_len=5000, has_time_features=True):
super(TemporalPositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout)
self.has_time_features = has_time_features
# 基础位置编码
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
# 如果需要时间特征,预留一半维度给时间特征
if has_time_features:
self.time_linear = nn.Linear(4, d_model // 2) # 假设输入4个时间特征
else:
self.time_linear = None
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x, time_features=None):
# x shape: (batch_size, seq_len, d_model)
# time_features shape: (batch_size, seq_len, num_time_features) if provided
# 添加基础位置编码
x = x + self.pe[:, :x.size(1)].requires_grad_(False)
# 添加时间特征编码
if self.has_time_features and time_features is not None:
time_encoding = self.time_linear(time_features)
# 将时间特征编码添加到x的后半部分维度
x[:, :, d_model//2:] += time_encoding
return self.dropout(x)
注意力掩码设计
时间序列预测中,需要设计特殊的注意力掩码以适应不同的预测场景:
- 因果掩码:确保预测未来值时不能访问未来信息
- 滑动窗口掩码:只允许关注最近的N个时间步,减少计算量
- 周期掩码:增强对周期性模式的关注
def time_series_mask(size, mask_type="causal", window_size=None, period=None):
"""生成时间序列注意力掩码"""
if mask_type == "causal":
# 因果掩码:下三角矩阵
return subsequent_mask(size)
elif mask_type == "window":
# 滑动窗口掩码:只允许关注最近window_size个时间步
if window_size is None:
window_size = size // 2
mask = torch.zeros(size, size)
for i in range(size):
start = max(0, i - window_size + 1)
mask[i, start:i+1] = 1
return mask.bool().unsqueeze(0)
elif mask_type == "periodic":
# 周期掩码:增强对周期模式的关注
if period is None:
period = 24 # 默认周期为24(如 hourly data)
mask = torch.zeros(size, size)
for i in range(size):
# 当前位置可以关注同期位置
for j in range(0, i+1, period):
mask[i, j] = 1
return mask.bool().unsqueeze(0)
else:
# 全注意力掩码
return torch.ones(1, size, size).bool()
解码器输出层调整
时间序列预测通常是回归任务,需要将解码器输出调整为数值预测:
class TimeSeriesGenerator(nn.Module):
"""时间序列预测头,将解码器输出映射为预测值"""
def __init__(self, d_model, output_dim=1, num_layers=2):
super(TimeSeriesGenerator, self).__init__()
# 可以堆叠多个线性层提高拟合能力
layers = []
if num_layers == 1:
layers.append(nn.Linear(d_model, output_dim))
else:
layers.append(nn.Linear(d_model, d_model//2))
layers.append(nn.ReLU())
for _ in range(num_layers-2):
layers.append(nn.Linear(d_model//2, d_model//2))
layers.append(nn.ReLU())
layers.append(nn.Linear(d_model//2, output_dim))
self.model = nn.Sequential(*layers)
def forward(self, x):
# x shape: (batch_size, seq_len, d_model)
return self.model(x)
完整时间序列预测模型构建
基于上述修改,我们可以构建完整的时间序列预测Transformer模型:
def make_time_series_model(
input_dim, output_dim=1, N=6, d_model=512, d_ff=2048, h=8,
dropout=0.1, mask_type="causal", has_time_features=True
):
"""构建适用于时间序列预测的Transformer模型"""
c = copy.deepcopy
attn = MultiHeadedAttention(h, d_model)
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
position = TemporalPositionalEncoding(d_model, dropout, has_time_features=has_time_features)
# 创建编码器层和解码器层
encoder_layer = EncoderLayer(d_model, c(attn), c(ff), dropout)
decoder_layer = DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout)
# 创建编码器和解码器
encoder = Encoder(encoder_layer, N)
decoder = Decoder(decoder_layer, N)
# 创建输入嵌入层(替换原有的词嵌入)
src_embed = nn.Sequential(TimeSeriesEmbedding(input_dim, d_model), c(position))
# 如果是自回归预测,目标嵌入与源嵌入相同
tgt_embed = src_embed
# 创建生成器(时间序列预测头)
generator = TimeSeriesGenerator(d_model, output_dim)
# 组装完整模型
model = EncoderDecoder(encoder, decoder, src_embed, tgt_embed, generator)
# 初始化参数
for p in model.parameters():
if p.dim() > 1:
nn.init.xavier_uniform_(p)
# 存储掩码类型供后续使用
model.mask_type = mask_type
return model
时间序列数据准备
为了将时间序列数据适配到Transformer模型,我们需要实现序列转换函数,将时间序列转换为监督学习样本:
def create_sequences(data, seq_len, pred_len, input_dim=1, output_dim=1, time_features=None):
"""
将时间序列数据转换为输入序列和目标序列
参数:
data: 原始时间序列数据,shape (n_samples, n_features)
seq_len: 输入序列长度(历史观测窗口)
pred_len: 预测序列长度(未来预测窗口)
input_dim: 输入特征维度
output_dim: 输出特征维度
time_features: 时间特征数据,shape (n_samples, n_time_features)
返回:
X: 输入序列,shape (n_samples, seq_len, input_dim)
y: 目标序列,shape (n_samples, pred_len, output_dim)
X_time_features: 输入序列对应的时间特征,shape (n_samples, seq_len, n_time_features)
y_time_features: 目标序列对应的时间特征,shape (n_samples, pred_len, n_time_features)
"""
X, y = [], []
X_time, y_time = [], []
# 计算可生成的样本数
n_samples = len(data) - seq_len - pred_len + 1
for i in range(n_samples):
# 输入序列:[i, i+seq_len)
X.append(data[i:i+seq_len, :input_dim])
# 目标序列:[i+seq_len, i+seq_len+pred_len)
y.append(data[i+seq_len:i+seq_len+pred_len, :output_dim])
# 如果提供了时间特征
if time_features is not None:
X_time.append(time_features[i:i+seq_len, :])
y_time.append(time_features[i+seq_len:i+seq_len+pred_len, :])
X = np.array(X)
y = np.array(y)
if time_features is not None:
X_time = np.array(X_time)
y_time = np.array(y_time)
return X, y, X_time, y_time
else:
return X, y, None, None
模型训练与评估
时间序列预测模型的训练过程与原始Transformer有所不同,主要体现在数据加载和损失函数选择上:
def train_time_series_model(model, train_loader, val_loader, criterion,
optimizer, scheduler, device, epochs=50,
mask_type="causal", log_interval=10):
"""训练时间序列预测模型"""
model.to(device)
best_val_loss = float('inf')
train_losses, val_losses = [], []
for epoch in range(epochs):
model.train()
train_loss = 0.0
for batch_idx, (src, tgt, src_time, tgt_time) in enumerate(train_loader):
src, tgt = src.to(device), tgt.to(device)
src_time, tgt_time = src_time.to(device) if src_time is not None else None, tgt_time.to(device) if tgt_time is not None else None
# 生成掩码
src_mask = None # 编码器通常不需要掩码或使用窗口掩码
tgt_mask = time_series_mask(tgt.size(1), mask_type=mask_type)
# 前向传播
optimizer.zero_grad()
output = model(src, tgt[:, :-1], src_mask, tgt_mask)
# 计算损失(只预测最后一个时间步或所有时间步)
loss = criterion(output, tgt[:, 1:])
# 反向传播和优化
loss.backward()
optimizer.step()
train_loss += loss.item()
if batch_idx % log_interval == 0 and batch_idx > 0:
print(f'Train Epoch: {epoch} [{batch_idx * len(src)}/{len(train_loader.dataset)}] '
f'Loss: {loss.item():.6f}')
# 计算平均训练损失
train_loss /= len(train_loader)
train_losses.append(train_loss)
# 验证
model.eval()
val_loss = 0.0
with torch.no_grad():
for src, tgt, src_time, tgt_time in val_loader:
src, tgt = src.to(device), tgt.to(device)
src_time, tgt_time = src_time.to(device) if src_time is not None else None, tgt_time.to(device) if tgt_time is not None else None
src_mask = None
tgt_mask = time_series_mask(tgt.size(1), mask_type=mask_type)
output = model(src, tgt[:, :-1], src_mask, tgt_mask)
loss = criterion(output, tgt[:, 1:])
val_loss += loss.item()
val_loss /= len(val_loader)
val_losses.append(val_loss)
print(f'Epoch: {epoch}, Train Loss: {train_loss:.6f}, Val Loss: {val_loss:.6f}')
# 学习率调度
scheduler.step(val_loss)
# 保存最佳模型
if val_loss < best_val_loss:
best_val_loss = val_loss
torch.save(model.state_dict(), 'best_time_series_transformer.pth')
return model, train_losses, val_losses
应用案例:电力负荷预测
数据介绍
我们使用某地区的电力负荷数据集进行案例演示,该数据集包含:
- 时间范围:2016年1月至2018年12月
- 采样频率:每小时一次
- 特征:电力负荷(目标变量)、温度、湿度、风速、降水概率
实验设置
# 模型参数
SEQ_LEN = 96 # 4天(96小时)的历史数据
PRED_LEN = 24 # 预测未来24小时
INPUT_DIM = 5 # 5个输入特征(负荷+4个气象特征)
OUTPUT_DIM = 1 # 预测1个目标变量(电力负荷)
D_MODEL = 256 # 模型维度
N_LAYERS = 3 # 编码器/解码器层数
H = 4 # 注意力头数
D_FF = 512 # 前馈网络维度
DROPOUT = 0.1 # Dropout率
MASK_TYPE = "causal" # 因果掩码
# 训练参数
BATCH_SIZE = 32
EPOCHS = 30
LEARNING_RATE = 0.001
WEIGHT_DECAY = 1e-5
模型构建与训练
# 创建模型
model = make_time_series_model(
input_dim=INPUT_DIM,
output_dim=OUTPUT_DIM,
N=N_LAYERS,
d_model=D_MODEL,
d_ff=D_FF,
h=H,
dropout=DROPOUT,
mask_type=MASK_TYPE
)
# 准备数据
# ... 数据加载和预处理代码省略 ...
# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False)
# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=LEARNING_RATE, weight_decay=WEIGHT_DECAY)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=5, factor=0.5)
# 训练模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model, train_losses, val_losses = train_time_series_model(
model, train_loader, val_loader, criterion, optimizer, scheduler,
device, epochs=EPOCHS, mask_type=MASK_TYPE
)
结果分析与可视化
def plot_predictions(model, test_loader, device, n_samples=5):
"""绘制预测结果与真实值对比"""
model.eval()
model.to(device)
samples_plot = []
with torch.no_grad():
for i, (src, tgt, src_time, tgt_time) in enumerate(test_loader):
if i >= n_samples:
break
src, tgt = src.to(device), tgt.to(device)
src_time, tgt_time = src_time.to(device) if src_time is not None else None, tgt_time.to(device) if tgt_time is not None else None
# 生成预测
src_mask = None
tgt_mask = time_series_mask(tgt.size(1), mask_type=MASK_TYPE)
output = model(src, tgt[:, :-1], src_mask, tgt_mask)
# 转换为numpy数组并添加到绘图列表
samples_plot.append({
'true': tgt[0].cpu().numpy(),
'pred': output[0].cpu().numpy(),
'src': src[0, :, 0].cpu().numpy() # 只取第一个特征(负荷)
})
# 绘制结果
fig, axes = plt.subplots(n_samples, 1, figsize=(15, 3*n_samples))
if n_samples == 1:
axes = [axes]
for i, sample in enumerate(samples_plot):
ax = axes[i]
# 绘制历史数据
ax.plot(range(len(sample['src'])), sample['src'], 'b-', label='History')
# 绘制真实值
ax.plot(range(len(sample['src']), len(sample['src'])+len(sample['true'])),
sample['true'], 'g-', label='True Future')
# 绘制预测值
ax.plot(range(len(sample['src']), len(sample['src'])+len(sample['pred'])),
sample['pred'], 'r--', label='Predicted Future')
ax.set_title(f'Sample {i+1}')
ax.legend()
plt.tight_layout()
plt.show()
注意力可视化
Transformer的优势之一是可解释性,通过可视化注意力权重,我们可以分析模型关注的历史时间步:
def visualize_time_attention(model, sample, device, layer=0, head=0):
"""可视化时间序列注意力权重"""
model.eval()
src, tgt, src_time, tgt_time = sample
src, tgt = src.unsqueeze(0).to(device), tgt.unsqueeze(0).to(device)
# 获取注意力权重
with torch.no_grad():
# 前向传播并捕获注意力权重
model(src, tgt[:, :-1], None, time_series_mask(tgt.size(1)-1, mask_type=MASK_TYPE))
# 获取解码器对编码器的注意力权重(层和头可以选择)
attn_weights = model.decoder.layers[layer].src_attn.attn[0, head].cpu().numpy()
# 绘制注意力热图
fig, ax = plt.subplots(figsize=(12, 8))
im = ax.imshow(attn_weights, cmap='viridis')
# 设置标签
ax.set_xlabel('Source Time Steps')
ax.set_ylabel('Target Time Steps')
ax.set_title(f'Attention Weights (Layer {layer}, Head {head})')
# 添加颜色条
cbar = fig.colorbar(im)
cbar.set_label('Attention Weight')
plt.tight_layout()
plt.show()
性能对比与分析
为了验证Transformer在时间序列预测中的优势,我们将其与传统方法进行对比:
pie
title 不同模型的MAE对比(越低越好)
"Transformer" : 4.2
"LSTM" : 5.8
"GRU" : 6.1
"ARIMA" : 8.3
"Prophet" : 7.5
各模型优缺点对比
| 模型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Transformer | 并行计算、长距离依赖捕捉、可解释性好 | 计算复杂度高、数据需求量大 | 复杂模式、长序列预测 |
| LSTM/GRU | 计算效率高、对数据量要求较低 | 难以并行计算、长序列梯度消失 | 中等长度序列、资源有限场景 |
| ARIMA | 简单直观、统计意义明确 | 难以捕捉非线性关系 | 线性趋势明显的短期预测 |
| Prophet | 自动处理季节性、鲁棒性强 | 灵活性差、难以集成外部特征 | 商业预测、资源有限场景 |
总结与未来展望
本文详细介绍了如何基于annotated-transformer项目实现Transformer在时间序列预测中的应用,主要工作包括:
- 回顾了Transformer模型的核心架构和组件,包括自注意力机制、位置编码、编码器-解码器结构
- 针对时间序列数据特点,修改了输入处理、位置编码和注意力掩码
- 构建了完整的时间序列预测模型,并以电力负荷预测为例进行了实验验证
- 展示了如何通过注意力可视化增强模型的可解释性
未来可以从以下几个方向进一步改进:
- 混合模型架构:结合CNN提取局部特征和Transformer捕捉长距离依赖
- 改进的位置编码:设计更适合特定时间序列特性的位置编码方式
- 自监督学习:利用自监督学习方法处理标签稀缺的时间序列数据
- 不确定性量化:引入概率模型,提供预测结果的置信区间
- 在线学习:开发适应概念漂移的增量学习Transformer模型
通过本文的方法,你可以快速将Transformer应用于各种时间序列预测任务,如电力负荷预测、交通流量预测、股票价格预测等。希望本文能够为你的时间序列预测项目提供有价值的参考。
如果你对本文内容有任何疑问或建议,欢迎在评论区留言讨论。别忘了点赞、收藏并关注我的更新,获取更多关于Transformer和时间序列预测的技术分享!
Kimi-K2.5Kimi K2.5 是一款开源的原生多模态智能体模型,它在 Kimi-K2-Base 的基础上,通过对约 15 万亿混合视觉和文本 tokens 进行持续预训练构建而成。该模型将视觉与语言理解、高级智能体能力、即时模式与思考模式,以及对话式与智能体范式无缝融合。Python00- QQwen3-Coder-Next2026年2月4日,正式发布的Qwen3-Coder-Next,一款专为编码智能体和本地开发场景设计的开源语言模型。Python00
xw-cli实现国产算力大模型零门槛部署,一键跑通 Qwen、GLM-4.7、Minimax-2.1、DeepSeek-OCR 等模型Go06
PaddleOCR-VL-1.5PaddleOCR-VL-1.5 是 PaddleOCR-VL 的新一代进阶模型,在 OmniDocBench v1.5 上实现了 94.5% 的全新 state-of-the-art 准确率。 为了严格评估模型在真实物理畸变下的鲁棒性——包括扫描伪影、倾斜、扭曲、屏幕拍摄和光照变化——我们提出了 Real5-OmniDocBench 基准测试集。实验结果表明,该增强模型在新构建的基准测试集上达到了 SOTA 性能。此外,我们通过整合印章识别和文本检测识别(text spotting)任务扩展了模型的能力,同时保持 0.9B 的超紧凑 VLM 规模,具备高效率特性。Python00
KuiklyUI基于KMP技术的高性能、全平台开发框架,具备统一代码库、极致易用性和动态灵活性。 Provide a high-performance, full-platform development framework with unified codebase, ultimate ease of use, and dynamic flexibility. 注意:本仓库为Github仓库镜像,PR或Issue请移步至Github发起,感谢支持!Kotlin08
VLOOKVLOOK™ 是优雅好用的 Typora/Markdown 主题包和增强插件。 VLOOK™ is an elegant and practical THEME PACKAGE × ENHANCEMENT PLUGIN for Typora/Markdown.Less00