政安晨:【Keras机器学习示例演绎】(五十四)—— 使用神经决策森林进行分类

目录

 导言

数据集

设置

准备数据

定义数据集元数据

为训练和验证创建 tf_data.Dataset 对象

创建模型输入

输入特征编码

深度神经决策树

深度神经决策森林

实验 1:训练决策树模型

实验 2:训练森林模型


政安晨的个人主页:政安晨

欢迎 👍点赞✍评论⭐收藏

收录专栏: TensorFlow与Keras机器学习实战

希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正!

本文目标:如何为深度神经网络的端到端学习训练可微分决策树。

 导言

本示例提供了 P. Kontschieder 等人提出的用于结构化数据分类的深度神经决策林模型的实现。 它演示了如何建立一个随机可变的决策树模型,对其进行端到端训练,并将决策树与深度表示学习统一起来。

数据集

本示例使用加州大学欧文分校机器学习资料库提供的美国人口普查收入数据集。 该数据集包含 48,842 个实例,其中有 14 个输入特征(如年龄、工作级别、教育程度、职业等): 5 个数字特征和 9 个分类特征。

设置

import keras
from keras import layers
from keras.layers import StringLookup
from keras import opsfrom tensorflow import data as tf_data
import numpy as np
import pandas as pdimport math

准备数据

CSV_HEADER = ["age","workclass","fnlwgt","education","education_num","marital_status","occupation","relationship","race","gender","capital_gain","capital_loss","hours_per_week","native_country","income_bracket",
]train_data_url = ("https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data"
)
train_data = pd.read_csv(train_data_url, header=None, names=CSV_HEADER)test_data_url = ("https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test"
)
test_data = pd.read_csv(test_data_url, header=None, names=CSV_HEADER)print(f"Train dataset shape: {train_data.shape}")
print(f"Test dataset shape: {test_data.shape}")
Train dataset shape: (32561, 15)
Test dataset shape: (16282, 15)

删除第一条记录(因为它不是一个有效的数据示例)和类标签中的尾部 "点"。

test_data = test_data[1:]
test_data.income_bracket = test_data.income_bracket.apply(lambda value: value.replace(".", "")
)

我们将训练数据和测试数据分割成 CSV 文件存储在本地。

train_data_file = "train_data.csv"
test_data_file = "test_data.csv"train_data.to_csv(train_data_file, index=False, header=False)
test_data.to_csv(test_data_file, index=False, header=False)

定义数据集元数据

在此,我们定义了数据集的元数据,这些元数据将有助于读取、解析和编码输入特征。

# A list of the numerical feature names.
NUMERIC_FEATURE_NAMES = ["age","education_num","capital_gain","capital_loss","hours_per_week",
]
# A dictionary of the categorical features and their vocabulary.
CATEGORICAL_FEATURES_WITH_VOCABULARY = {"workclass": sorted(list(train_data["workclass"].unique())),"education": sorted(list(train_data["education"].unique())),"marital_status": sorted(list(train_data["marital_status"].unique())),"occupation": sorted(list(train_data["occupation"].unique())),"relationship": sorted(list(train_data["relationship"].unique())),"race": sorted(list(train_data["race"].unique())),"gender": sorted(list(train_data["gender"].unique())),"native_country": sorted(list(train_data["native_country"].unique())),
}
# A list of the columns to ignore from the dataset.
IGNORE_COLUMN_NAMES = ["fnlwgt"]
# A list of the categorical feature names.
CATEGORICAL_FEATURE_NAMES = list(CATEGORICAL_FEATURES_WITH_VOCABULARY.keys())
# A list of all the input features.
FEATURE_NAMES = NUMERIC_FEATURE_NAMES + CATEGORICAL_FEATURE_NAMES
# A list of column default values for each feature.
COLUMN_DEFAULTS = [[0.0] if feature_name in NUMERIC_FEATURE_NAMES + IGNORE_COLUMN_NAMES else ["NA"]for feature_name in CSV_HEADER
]
# The name of the target feature.
TARGET_FEATURE_NAME = "income_bracket"
# A list of the labels of the target features.
TARGET_LABELS = [" <=50K", " >50K"]

为训练和验证创建 tf_data.Dataset 对象

我们创建了一个输入函数来读取和解析文件,并将特征和标签转换为 tf_data.Dataset 用于训练和验证。 我们还通过将目标标签映射到索引对输入进行预处理。

target_label_lookup = StringLookup(vocabulary=TARGET_LABELS, mask_token=None, num_oov_indices=0
)lookup_dict = {}
for feature_name in CATEGORICAL_FEATURE_NAMES:vocabulary = CATEGORICAL_FEATURES_WITH_VOCABULARY[feature_name]# Create a lookup to convert a string values to an integer indices.# Since we are not using a mask token, nor expecting any out of vocabulary# (oov) token, we set mask_token to None and num_oov_indices to 0.lookup = StringLookup(vocabulary=vocabulary, mask_token=None, num_oov_indices=0)lookup_dict[feature_name] = lookupdef encode_categorical(batch_x, batch_y):for feature_name in CATEGORICAL_FEATURE_NAMES:batch_x[feature_name] = lookup_dict[feature_name](batch_x[feature_name])return batch_x, batch_ydef get_dataset_from_csv(csv_file_path, shuffle=False, batch_size=128):dataset = (tf_data.experimental.make_csv_dataset(csv_file_path,batch_size=batch_size,column_names=CSV_HEADER,column_defaults=COLUMN_DEFAULTS,label_name=TARGET_FEATURE_NAME,num_epochs=1,header=False,na_value="?",shuffle=shuffle,).map(lambda features, target: (features, target_label_lookup(target))).map(encode_categorical))return dataset.cache()

创建模型输入

def create_model_inputs():inputs = {}for feature_name in FEATURE_NAMES:if feature_name in NUMERIC_FEATURE_NAMES:inputs[feature_name] = layers.Input(name=feature_name, shape=(), dtype="float32")else:inputs[feature_name] = layers.Input(name=feature_name, shape=(), dtype="int32")return inputs

输入特征编码

def encode_inputs(inputs):encoded_features = []for feature_name in inputs:if feature_name in CATEGORICAL_FEATURE_NAMES:vocabulary = CATEGORICAL_FEATURES_WITH_VOCABULARY[feature_name]# Create a lookup to convert a string values to an integer indices.# Since we are not using a mask token, nor expecting any out of vocabulary# (oov) token, we set mask_token to None and num_oov_indices to 0.value_index = inputs[feature_name]embedding_dims = int(math.sqrt(lookup.vocabulary_size()))# Create an embedding layer with the specified dimensions.embedding = layers.Embedding(input_dim=lookup.vocabulary_size(), output_dim=embedding_dims)# Convert the index values to embedding representations.encoded_feature = embedding(value_index)else:# Use the numerical features as-is.encoded_feature = inputs[feature_name]if inputs[feature_name].shape[-1] is None:encoded_feature = keras.ops.expand_dims(encoded_feature, -1)encoded_features.append(encoded_feature)encoded_features = layers.concatenate(encoded_features)return encoded_features

深度神经决策树

神经决策树模型有两组权重需要学习。 第一组是 pi,代表树叶中类别的概率分布。 第二组是路由层 decision_fn 的权重,代表前往每个树叶的概率。 该模型的前向传递工作原理如下:

该模型希望将输入特征作为一个单一的向量,对批次中某个实例的所有特征进行编码。 该向量可以由应用于图像的卷积神经网络(CNN)生成,也可以由应用于结构化数据特征的密集变换生成。

模型首先应用已用特征掩码随机选择要使用的输入特征子集。

然后,模型通过在树的各个层级迭代执行随机路由,计算输入实例到达树叶的概率(mu)。

最后,将到达树叶的概率与树叶上的类概率相结合,生成最终输出。

class NeuralDecisionTree(keras.Model):def __init__(self, depth, num_features, used_features_rate, num_classes):super().__init__()self.depth = depthself.num_leaves = 2**depthself.num_classes = num_classes# Create a mask for the randomly selected features.num_used_features = int(num_features * used_features_rate)one_hot = np.eye(num_features)sampled_feature_indices = np.random.choice(np.arange(num_features), num_used_features, replace=False)self.used_features_mask = ops.convert_to_tensor(one_hot[sampled_feature_indices], dtype="float32")# Initialize the weights of the classes in leaves.self.pi = self.add_weight(initializer="random_normal",shape=[self.num_leaves, self.num_classes],dtype="float32",trainable=True,)# Initialize the stochastic routing layer.self.decision_fn = layers.Dense(units=self.num_leaves, activation="sigmoid", name="decision")def call(self, features):batch_size = ops.shape(features)[0]# Apply the feature mask to the input features.features = ops.matmul(features, ops.transpose(self.used_features_mask))  # [batch_size, num_used_features]# Compute the routing probabilities.decisions = ops.expand_dims(self.decision_fn(features), axis=2)  # [batch_size, num_leaves, 1]# Concatenate the routing probabilities with their complements.decisions = layers.concatenate([decisions, 1 - decisions], axis=2)  # [batch_size, num_leaves, 2]mu = ops.ones([batch_size, 1, 1])begin_idx = 1end_idx = 2# Traverse the tree in breadth-first order.for level in range(self.depth):mu = ops.reshape(mu, [batch_size, -1, 1])  # [batch_size, 2 ** level, 1]mu = ops.tile(mu, (1, 1, 2))  # [batch_size, 2 ** level, 2]level_decisions = decisions[:, begin_idx:end_idx, :]  # [batch_size, 2 ** level, 2]mu = mu * level_decisions  # [batch_size, 2**level, 2]begin_idx = end_idxend_idx = begin_idx + 2 ** (level + 1)mu = ops.reshape(mu, [batch_size, self.num_leaves])  # [batch_size, num_leaves]probabilities = keras.activations.softmax(self.pi)  # [num_leaves, num_classes]outputs = ops.matmul(mu, probabilities)  # [batch_size, num_classes]return outputs

深度神经决策森林

神经决策森林模型由一组同时训练的神经决策树组成。 森林模型的输出是各树的平均输出。

class NeuralDecisionForest(keras.Model):def __init__(self, num_trees, depth, num_features, used_features_rate, num_classes):super().__init__()self.ensemble = []# Initialize the ensemble by adding NeuralDecisionTree instances.# Each tree will have its own randomly selected input features to use.for _ in range(num_trees):self.ensemble.append(NeuralDecisionTree(depth, num_features, used_features_rate, num_classes))def call(self, inputs):# Initialize the outputs: a [batch_size, num_classes] matrix of zeros.batch_size = ops.shape(inputs)[0]outputs = ops.zeros([batch_size, num_classes])# Aggregate the outputs of trees in the ensemble.for tree in self.ensemble:outputs += tree(inputs)# Divide the outputs by the ensemble size to get the average.outputs /= len(self.ensemble)return outputs

最后,让我们来设置训练和评估模型的代码。

learning_rate = 0.01
batch_size = 265
num_epochs = 10def run_experiment(model):model.compile(optimizer=keras.optimizers.Adam(learning_rate=learning_rate),loss=keras.losses.SparseCategoricalCrossentropy(),metrics=[keras.metrics.SparseCategoricalAccuracy()],)print("Start training the model...")train_dataset = get_dataset_from_csv(train_data_file, shuffle=True, batch_size=batch_size)model.fit(train_dataset, epochs=num_epochs)print("Model training finished")print("Evaluating the model on the test data...")test_dataset = get_dataset_from_csv(test_data_file, batch_size=batch_size)_, accuracy = model.evaluate(test_dataset)print(f"Test accuracy: {round(accuracy * 100, 2)}%")

实验 1:训练决策树模型

在本实验中,我们使用所有输入特征训练一个神经决策树模型。

num_trees = 10
depth = 10
used_features_rate = 1.0
num_classes = len(TARGET_LABELS)def create_tree_model():inputs = create_model_inputs()features = encode_inputs(inputs)features = layers.BatchNormalization()(features)num_features = features.shape[1]tree = NeuralDecisionTree(depth, num_features, used_features_rate, num_classes)outputs = tree(features)model = keras.Model(inputs=inputs, outputs=outputs)return modeltree_model = create_tree_model()
run_experiment(tree_model)
Start training the model...
Epoch 1/10123/123 ━━━━━━━━━━━━━━━━━━━━ 5s 26ms/step - loss: 0.5308 - sparse_categorical_accuracy: 0.8150
Epoch 2/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - loss: 0.3476 - sparse_categorical_accuracy: 0.8429
Epoch 3/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - loss: 0.3312 - sparse_categorical_accuracy: 0.8478
Epoch 4/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - loss: 0.3247 - sparse_categorical_accuracy: 0.8495
Epoch 5/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step - loss: 0.3202 - sparse_categorical_accuracy: 0.8512
Epoch 6/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - loss: 0.3158 - sparse_categorical_accuracy: 0.8536
Epoch 7/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - loss: 0.3116 - sparse_categorical_accuracy: 0.8572
Epoch 8/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - loss: 0.3071 - sparse_categorical_accuracy: 0.8608
Epoch 9/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 11ms/step - loss: 0.3026 - sparse_categorical_accuracy: 0.8630
Epoch 10/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step - loss: 0.2975 - sparse_categorical_accuracy: 0.8653
Model training finished
Evaluating the model on the test data...62/62 ━━━━━━━━━━━━━━━━━━━━ 1s 13ms/step - loss: 0.3279 - sparse_categorical_accuracy: 0.8463
Test accuracy: 85.08%

实验 2:训练森林模型

在本实验中,我们使用 num_trees 树训练神经决策森林,每棵树随机使用 50%的输入特征。 通过设置 used_features_rate 变量,可以控制每棵树使用的特征数量。 此外,与之前的实验相比,我们将深度设置为 5,而不是 10。

num_trees = 25
depth = 5
used_features_rate = 0.5def create_forest_model():inputs = create_model_inputs()features = encode_inputs(inputs)features = layers.BatchNormalization()(features)num_features = features.shape[1]forest_model = NeuralDecisionForest(num_trees, depth, num_features, used_features_rate, num_classes)outputs = forest_model(features)model = keras.Model(inputs=inputs, outputs=outputs)return modelforest_model = create_forest_model()run_experiment(forest_model)
Start training the model...
Epoch 1/10123/123 ━━━━━━━━━━━━━━━━━━━━ 47s 202ms/step - loss: 0.5469 - sparse_categorical_accuracy: 0.7915
Epoch 2/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step - loss: 0.3459 - sparse_categorical_accuracy: 0.8494
Epoch 3/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step - loss: 0.3268 - sparse_categorical_accuracy: 0.8523
Epoch 4/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step - loss: 0.3195 - sparse_categorical_accuracy: 0.8524
Epoch 5/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step - loss: 0.3149 - sparse_categorical_accuracy: 0.8539
Epoch 6/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step - loss: 0.3112 - sparse_categorical_accuracy: 0.8556
Epoch 7/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step - loss: 0.3079 - sparse_categorical_accuracy: 0.8566
Epoch 8/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.3050 - sparse_categorical_accuracy: 0.8582
Epoch 9/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.3021 - sparse_categorical_accuracy: 0.8595
Epoch 10/10123/123 ━━━━━━━━━━━━━━━━━━━━ 1s 9ms/step - loss: 0.2992 - sparse_categorical_accuracy: 0.8617
Model training finished
Evaluating the model on the test data...62/62 ━━━━━━━━━━━━━━━━━━━━ 5s 39ms/step - loss: 0.3145 - sparse_categorical_accuracy: 0.8503
Test accuracy: 85.55%

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/44650.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python | Leetcode Python题解之第228题汇总区间

题目&#xff1a; 题解&#xff1a; class Solution:def summaryRanges(self, nums: List[int]) -> List[str]:def f(i: int, j: int) -> str:return str(nums[i]) if i j else f{nums[i]}->{nums[j]}i 0n len(nums)ans []while i < n:j iwhile j 1 < n …

Codeforces Round #956 (Div. 2) and ByteRace 2024 E. I Love Balls(概率期望)

题目 思路来源 官方题解 题解 特殊球不会改变普通球的顺序&#xff0c;所以都是alice拿一半里较多的部分 n-k1一半向上取整就是(n-k2)/2&#xff0c;同理n-k个一般向上取整(n-k1)/2 每个特殊球独立地来看&#xff0c;在每个空隙的概率相同 所以分别统计特殊球和非特殊球的…

LLM+Agent技术

&#x1f4a1; Agent可以理解为某种能自主理解、规划决策、执行复杂任务的智能体。Agent 是让 LLM 具备目标实现的能力&#xff0c;并通过自我激励循环来实现这个目标。它可以是并行的&#xff08;同时使用多个提示&#xff0c;试图解决同一个目标&#xff09;和单向的&#xf…

14-63 剑和诗人37 - 分布式系统中的数据访问设计

​​ 在分布式系统中,跨服务和数据库提供统一、可靠的数据访问至关重要,但又极具挑战性。微服务和数据库的拓扑结构为分布、缓存、复制和同步带来了复杂性。 让我们探索有助于解决这些复杂性并简化构建强大、高性能分布式系统的常见数据访问模式。 概述 我们将通过示例介绍…

AC修炼计划(AtCoder Regular Contest 180) A~C

A - ABA and BAB A - ABA and BAB (atcoder.jp) 这道题我一开始想复杂了&#xff0c;一直在想怎么dp&#xff0c;没注意到其实是个很简单的规律题。 我们可以发现我们住需要统计一下类似ABABA这样不同字母相互交替的所有子段的长度&#xff0c;而每个字段的的情况有&#xff…

利用 AI 解放双手:把“贾维斯”带进现实 | 开源专题 No.64

Significant-Gravitas/AutoGPT Stars: 160k License: MIT AutoGPT 是开源 AI 代理生态系统的核心工具包。 提供构建、测试和委托 AI 代理的工具。AutoGPT 处于 AI 创新前沿&#xff0c;提供文档、贡献指南以及快速开始创建自己的代理。包含强大的组件如 Forge 和 Benchmark&…

【教程】Hexo 部署到 Github Page 后,自定义域名失效的问题

目录 前言&问题描述解决方案细节 前言&问题描述 近期给 Github Page 上托管的静态网站映射了自定义域名&#xff08;aiproducthome.top&#xff09;&#xff0c;之后发现每次更新并部署 hexo 到 Github Page &#xff08;hexo d&#xff09;后就会出现自定义域名失效的…

前端如何去看蓝湖

首先加入团队&#xff0c;在内容中我们可以看到点击图片&#xff0c;右边出现的图 包含了像素甚至有代码&#xff0c;我们可以参考这个代码。 那么在使用之前我们需要调整好像素&#xff0c;例如我们的像素宽为375&#xff0c;不用去管高&#xff0c;然后这个宽度我们可以去自…

QT——Excel实现自绘区域选择边框

文章目录 一、自绘区域边框1.1、效果展示2.2、问题整理2.2.1、重绘单元格选择区2.2.2、选择区域的大小 一、自绘区域边框 1.1、效果展示 单选 多选 2.2、问题整理 2.2.1、重绘单元格选择区 误区: 继承QStyledItemDelegate重写paint,测试发现只能在单元格内绘制。 通过继…

图鸟UI框架在uni-app多端应用开发中的实践与应用

摘要&#xff1a; 随着移动互联网的蓬勃发展&#xff0c;跨平台应用开发已成为行业趋势。本文将探讨图鸟UI框架如何在uni-app开发环境下助力开发者高效构建多端应用&#xff0c;并通过具体案例展示其在实际项目中的应用效果。 一、引言 在移动应用开发领域&#xff0c;跨平台…

Java | Leetcode Java题解之第228题汇总区间

题目&#xff1a; 题解&#xff1a; class Solution {public List<String> summaryRanges(int[] nums) {List<String> ans new ArrayList<>();for (int i 0, j, n nums.length; i < n; i j 1) {j i;while (j 1 < n && nums[j 1] num…

分享一个项目模板electron+vue+ts+vite

分享一个项目模板electronvuetsvite GitHub - xiugou798/electron-vue-ts-vite-template: electron-vue-ts-vite-templateelectron-vue-ts-vite-template. Contribute to xiugou798/electron-vue-ts-vite-template development by creating an account on GitHub.https://gith…

弱电工程质量保修期是多久?

弱电工程是电力工程的一个分类&#xff0c;弱电可以向人们提供照明用电和空调用电&#xff0c;为人们的生活带来了极大的便利。弱电工程作为一类工程项目存在质量保证问题&#xff0c;在施工完成后需要进行质量检修&#xff0c;施工队应该向业主提供一定的质量保修期&#xff0…

LLM大模型应用中的安全对齐的简单理解

LLM大模型应用中的安全对齐的简单理解 随着人工智能技术的不断发展&#xff0c;大规模语言模型&#xff08;如GPT-4&#xff09;的应用越来越广泛。为了保证这些大模型在实际应用中的性能和安全性&#xff0c;安全对齐&#xff08;Safe Alignment&#xff09;成为一个重要的概…

实验-ENSP实现防火墙区域策略与用户管理

目录 实验拓扑 自己搭建拓扑 实验要求 实验步骤 整通总公司内网 sw3配置vlan 防火墙配置IP 配置安全策略&#xff08;DMZ区内的服务器&#xff0c;办公区仅能在办公时间内&#xff08;9: 00- 18:00)可以访问&#xff0c;生产区的设备全天可以访问&#xff09; 配置nat策…

Android 性能优化之内存优化

文章目录 Android 性能优化之内存优化内存问题内存抖动内存泄露内存溢出 检测工具Memory ProfilerMemory AnalyzerLeakCanary 内存管理机制JavaAndroid 解决内存抖动问题模拟问题代码使用Memory Profiler工具检测优化技巧 内存泄露问题模拟问题代码使用LeakCanary工具检测优化技…

顺序结构 ( 四 ) —— 标准数据类型 【互三互三】

序 C语言提供了丰富的数据类型&#xff0c;本节介绍几种基本的数据类型&#xff1a;整型、实型、字符型。它们都是系统定义的简单数据类型&#xff0c;称为标准数据类型。 整型&#xff08;integer&#xff09; 在C语言中&#xff0c;整型类型标识符为int。根据整型变量的取值范…

dify-api的Dockerfile分析

一.dify-api的Dockerfile文件 dify-api的Dockerfile文件如下所示&#xff1a; # base image FROM python:3.10-slim-bookworm AS baseLABEL maintainer"takatostgmail.com"# install packages FROM base as packagesRUN apt-get update \&& apt-get install…

nginx安装配置视频频服务器-windows

编译安装nginx 1、安装perl 安装地址: https://strawberryperl.com&#xff0c;选择msi安装程序即可 2、安装sed for windows 下载地址&#xff1a;https://sourceforge.net/projects/gnuwin32/files/sed/&#xff0c;执行安装程序结束后&#xff0c;将安装包bin目录配置到…

【Adobe】动作捕获和动画制作软件Character Animator

Adobe Character Animator 是一款由Adobe公司出品的动作捕获和动画制作软件&#xff0c;旨在帮助用户直观地制作2D&#xff08;二维&#xff09;人物动画、实时动画&#xff0c;并发布动画。这款软件功能强大、操作简单&#xff0c;非常适合动画制作者、直播主以及社交媒体内容…