基于神经网络的聚类分析

news2024/11/24 19:10:29

神经网络是一种非常有用的机器学习模型,具有无数的应用。今天,我们将分析一个数据集,看看我们是否可以通过应用无监督聚类技术来查找数据中的模式和隐藏分组,从而获得新的见解。

我们的目标是对复杂数据进行降维,以便我们可以创建无监督的、可解释的集群,如下所示:

图 1:在三维空间中编码的亚马逊手机数据,使用 K 均值聚类定义了八个聚类。

NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割

1、数据

我们将研究 2019 年从亚马逊获取的手机和客户评论。该数据集由两个文件组成。

第一个文件 items.csv 包含有关单个产品的数据。它包含以下列:产品 ASIN、产品品牌、产品标题、产品 URL、产品图片 URL、产品平均评分、产品评论页面 URL、产品总评论、产品价格和产品原价。

第二个文件 reviews.csv 包含有关单个评论的数据,可以使用产品 ASIN 列将其链接到 items.csv。reviews.csv 文件的列包含:产品 ASIN、评论者姓名、评论者评分(等级 1 到 5)、评论日期、有效客户、评论标题、评论内容和有用的反馈。

为简洁起见,我们将仅关注 items.csv 文件,但你可以想象在 reviews.csv 上执行非常相似的工作流程,然后可以使用它来分析项目集群与评论者集群。

感谢 Griko Nibras 整理这些数据并将其发布在 Kaggle 上!

2、软件

今天我们将使用 Python 3.7.9 以及机器学习包 TensorFlow。我们将使用 Keras API,这使得构建神经网络变得非常简单。

稍后我们将使用函数 plot_model 来可视化神经网络。在 Windows 10 上运行该函数可能会有问题(至少在撰写本文时)。如果你愿意,可以跳过运行该函数,它会将神经网络图的图像保存到磁盘,这对于可视化很有用,但在其他方面则没有必要。

如果你遇到问题,以下是我让它工作的步骤:

  • 使用 Python 3.7
  • 安装 Graphviz
  • 将 graphviz 添加到你的系统路径,对我来说: C:/Program Files (x86)/Graphviz 2.44.1/bin
  • 安装 python 包 pydot 和 graphviz
  • 将 graphviz 添加到 python 中的路径:
import os
os.environ["PATH"] += os.pathsep + 'C:/Program Files (x86)/Graphviz 2.44.1/bin' 
  • 重新启动你的 python IDE

经过上述操作,我可以用 plot_model 了,但如果你在 Windows 上无法让它工作,我建议你改用 Linux。

3、分析

我们将整个分析流程划分为6个步骤。

3.1 加载 Python 中的库

加载以下库(并安装任何缺少的库)。

import numpy as np  # numpy for math
import pandas       # for dataframes and csv files
import matplotlib.pyplot as plt  # for plotting
from matplotlib import animation  # animate 3D plots
from mpl_toolkits.mplot3d import Axes3D  # 3D plots

# Scikit learn
from sklearn.model_selection import train_test_split
from sklearn.cluster import KMeans
from sklearn import manifold

# TensorFlow and Keras
import tensorflow as tf
from tensorflow.keras.layers.experimental import preprocessing
from tensorflow import keras

3.2 在 Python 中加载数据

请注意,我已将数据保存到相对目录“./data/”,你需要根据存储数据的位置修改路径。

items_csv = pandas.read_csv("./data/items.csv")
items_csv.head(3) # look at top 3 items
items_csv.shape   # what is the size of the data?
>>> items_csv.head(3)
         asin     brand  ...  price originalPrice
0  B0000SX2UC       NaN  ...   0.00           0.0
1  B0009N5L7K  Motorola  ...  49.95           0.0
2  B000SKTZ0S  Motorola  ...  99.99           0.0
[3 rows x 10 columns]

items_csv.shape
(720, 10)

因此,你会看到我们有 720 行数据,每行包含 10 列信息(如预期)。在继续之前,我们将再做一步:删除所有有缺失数据的行。这是一个有争议的选择,因为我们可以使用插补来填充缺失值,或者在模型中以信息量的方式处理缺失值。为简单起见,我们将从数据集中删除所有有缺失数据的行:

items_features = items_csv.copy().dropna()

3.3 预处理数据

我们的原始数据需要进行转换才能与 Keras 很好地配合使用,因此我们需要对数据进行预处理。我们的一些列是数字,可以进行规范化,而其中一些是字符串(需要特殊处理)。我们将为模型创建一个类似于本教程的预处理层,这将使我们能够适当地使用每个特征列。

首先,我们删除不太可能有用的列:url、image、reviewUrl 和 asin:

# Let's remove these columns as not interesting for us
items_features.pop("url")
items_features.pop("image")
items_features.pop("reviewUrl")
p_labels = items_features.pop("asin")

现在我们将开始创建预处理层。我们为输入特征创建一个字典,其中包含匹配的数据类型:

inputs = {}
for name, column in items_features.items():
    dtype = column.dtype
    if dtype == object:
        dtype = tf.string
    else:
        dtype = tf.float32
    inputs[name] = tf.keras.Input(shape=(1,), name=name, dtype=dtype)

inputs
>>> inputs
{'brand': <tf.Tensor 'brand_3:0' shape=(None, 1) dtype=string>, 
 'title': <tf.Tensor 'title_3:0' shape=(None, 1) dtype=string>, 
 'rating': <tf.Tensor 'rating_3:0' shape=(None, 1) dtype=float32>, 
 'totalReviews': <tf.Tensor 'totalReviews_3:0' shape=(None, 1) dtype=float32>, 
 'price': <tf.Tensor 'price_3:0' shape=(None, 1) dtype=float32>, 
 'originalPrice': <tf.Tensor 'originalPrice_3:0' shape=(None, 1) dtype=float32>}

你可以看到,输入包含有关数据外观的信息。我们使用它来告诉 Keras 如何将神经网络层连接在一起。

接下来,我们分离出四个数字列,将它们连接成一个特征向量,然后进行规范化:

# deal with numeric features
numeric_inputs = {name: input for name, input in inputs.items() if input.dtype == tf.float32}

x = tf.keras.layers.Concatenate()(list(numeric_inputs.values()))
norm = preprocessing.Normalization()
norm.adapt(np.array(items_features[numeric_inputs.keys()]))
all_numeric_inputs = norm(x)
preprocessed_inputs = [all_numeric_inputs]

all_numeric_inputs
>>> all_numeric_inputs
<tf.Tensor 'normalization/Identity:0' shape=(None, 4) dtype=float32>

类似地,我们处理字符串特征(我们为每一列创建一个词汇表,并将它们编码为独热编码):

# deal with string features
for name, input in inputs.items():
    if input.dtype != tf.string:
        continue

    lookup = preprocessing.StringLookup(vocabulary=np.unique(items_features[name]))
    one_hot = preprocessing.CategoryEncoding(max_tokens=lookup.vocab_size())

    x = lookup(input)
    x = one_hot(x)
    preprocessed_inputs.append(x) # append preprocessed feature to features list
    
preprocessed_inputs
>>> preprocessed_inputs
[<tf.Tensor 'normalization/truediv:0' shape=(None, 4) dtype=float32>, 
 <tf.Tensor 'category_encoding_1/bincount/DenseBincount:0' shape=(None, 12) dtype=float32>, 
 <tf.Tensor 'category_encoding_2/bincount/DenseBincount:0' shape=(None, 716) dtype=float32>]

最后,我们将预处理后的输入连接成一个向量,并创建处理模型,稍后我们可以将其应用于我们的数据,作为神经网络的第一步:

preprocessed_inputs_cat = keras.layers.Concatenate()(preprocessed_inputs)
preprocessing_layer = tf.keras.Model(inputs, preprocessed_inputs_cat, name="ProcessData")

# this saves an image of the model, see note regarding plot_model issues
tf.keras.utils.plot_model(model=preprocessing_layer, rankdir="LR", dpi=130, show_shapes=True, to_file="processing.png")

在这里您可以看到我们的处理模型的结果,从每个感兴趣的列的输入开始,到单个特征向量结束:

图 2:数据预处理层的图形可视化。

我们将该层应用到两行数据,以查看预处理层的实际作用:

items_features_dict = {name: np.array(value) for name, value in items_features.items()}

# grab two samples
two_sample_dict = {name:values[1:3, ] for name, values in items_features_dict.items()}
two_sample_dict
>>> two_sample_dict
{'brand': array(['Motorola', 'Motorola'], dtype=object), 
 'title': array(['MOTOROLA C168i AT&T CINGULAR PREPAID GOPHONE CELL PHONE', 'Motorola i335 Cell Phone Boost Mobile'], dtype=object), 
 'rating': array([2.7, 3.3]), 
 'totalReviews': array([22, 21], dtype=int64), 
 'price': array([99.99,  0.  ]), 
 'originalPrice': array([0., 0.])}
# apply the preprocessing layer
two_sample_fitted = preprocessing_layer(two_sample_dict)

two_sample_fitted
>>> two_sample_fitted
<tf.Tensor: shape=(2, 732), dtype=float32, numpy=
array([[-1.4144895 , -0.50023943, -0.67571497, ...,  0.        ,
         0.        ,  0.        ],
       [-0.57760113, -0.50619656, -1.1760992 , ...,  0.        ,
         0.        ,  0.        ]], dtype=float32)>

太棒了!我们现在可以将输入数据处理成有用的张量。现在我们可以继续尝试在数据中找到自然聚类。

3.4 用于降维的自动编码器

我们的模型现在有一个预处理层,它准备原始数据以供使用。但是,一行数据(处理后)由多达 732 个特征表示。我们需要将数据的维度降低到不那么笨重的程度。为了完成这项任务,我们将使用一个简单的自编码器(autoencoder)。你可以在 keras 博客上阅读有关如何实现各种类型的自编码器的更多信息。

这是模型的自动编码器部分:

# This is the size of our input data
full_dim = two_sample_fitted.shape.as_list()[1]

# these are the downsampling/upsampling dimensions
encoding_dim1 = 128
encoding_dim2 = 16
encoding_dim3 = 3 # we will use these 3 dimensions for clustering

# This is our encoder input
encoder_input_data = keras.Input(shape=(full_dim,))

# the encoded representation of the input
encoded_layer1 = keras.layers.Dense(encoding_dim1, activation='relu')(encoder_input_data)
encoded_layer2 = keras.layers.Dense(encoding_dim2, activation='relu')(encoded_layer1)
# Note that encoded_layer3 is our 3 dimensional "clustered" layer, which we will later use for clustering
encoded_layer3 = keras.layers.Dense(encoding_dim3, activation='relu', name="ClusteringLayer")(encoded_layer2)

encoder_model = keras.Model(encoder_input_data, encoded_layer3)

# the reconstruction of the input
decoded_layer3 = keras.layers.Dense(encoding_dim2, activation='relu')(encoded_layer3)
decoded_layer2 = keras.layers.Dense(encoding_dim1, activation='relu')(decoded_layer3)
decoded_layer1 = keras.layers.Dense(full_dim, activation='sigmoid')(decoded_layer2)

# This model maps an input to its autoencoder reconstruction
autoencoder_model = keras.Model(encoder_input_data, outputs=decoded_layer1, name="Encoder")

# compile the model
autoencoder_model.compile(optimizer="RMSprop", loss=tf.keras.losses.mean_squared_error)
tf.keras.utils.plot_model(model=autoencoder_model, rankdir="LR", dpi=130, show_shapes=True, to_file="autoencoder.png")

该模型的自编码器层如下所示:

图 3:自编码器层的图形可视化。

3.5 训练模型

现在,我们需要训练模型;目前我们的模型什么都不做!所有模型权重都是随机的,并不是特别有用。

# process the inputs
p_items = preprocessing_layer(items_features_dict)

# split into training and testing sets (80/20 split)
train_data, test_data, train_labels, test_labels = train_test_split(p_items.numpy(), p_labels, train_size=0.8, random_state=5)

# fit the model using the training data
history = autoencoder_model.fit(train_data, train_data, epochs=1000, batch_size=256, shuffle=True, validation_data=(test_data, test_data))

我们来看看训练进度:

# Investigate training performance:
history_fig, (ax1, ax2) = plt.subplots(2, sharex=True)
history_fig.suptitle('Autoencoder Training Performance')
ax1.plot(range(0,1000), history.history['accuracy'], color='blue')
ax1.set(ylabel='Reconstruction Accuracy')
ax2.plot(range(0,1000), np.log10(history.history['loss']), color='blue')
ax2.plot(range(0,1000), np.log10(history.history['val_loss']), color='red', alpha=0.9)
ax2.set(ylabel='log_10(loss)', xlabel='Training Epoch')
history_fig

图 4:自动编码器模型训练历史。训练部分为蓝色,验证部分为红色。

你可以看到,我们从自动编码器获得了不错的重构效果(训练准确率 76%,验证准确率 65%)。这远非完美重构,还可以做更多工作来改进自动编码器(例如,特征特定编码、更大的编码维度等)。对于我们的目的而言,这已经足够好了。

因此,我们现在有一个编码器,它可以为我们的 732 维数据生成三维表示。它看起来是这样的:

图 5:在 3 维空间中编码的亚马逊手机数据

此时,你可能会想:“我为什么要这样做?这些是 3D 空间中的任意点,我该如何解释这种编码?”。好问题。假设你想知道产品评级如何影响其编码:

图 6:平均用户评分如何影响编码?

我希望你会同意,这出乎意料地容易解释,评分以可预测的方式从高到低分布。你可以制作类似的图表来可视化其他特征对编码的影响。

3.6 聚类

经过所有这些准备,我们终于可以尝试对数据进行聚类了。聚类的方法有很多。我们将使用 K-means 作为最简单的聚类方法之一。我们不仅对原始数据进行聚类,还使用数据的自动编码器表示,以便将问题的维数从 732 维一直降低到 3 维。

第一步,我们需要决定使用多少个聚类!我们将使用elbow方法,如这个 K-means 教程中所示:

encoded_items = encoder_model(p_items)

# choose number of clusters K:
Sum_of_squared_distances = []
K = range(1,30)
for k in K:
    km = KMeans(init='k-means++', n_clusters=k, n_init=10)
    km.fit(encoded_items)
    Sum_of_squared_distances.append(km.inertia_)

plt.plot(K, Sum_of_squared_distances, 'bx-')
plt.vlines(ymin=0, ymax=150000, x=8, colors='red')
plt.text(x=8.2, y=130000, s="optimal K=8")
plt.xlabel('Number of Clusters K')
plt.ylabel('Sum of squared distances')
plt.title('Elbow Method For Optimal K')
plt.show()

图 7:用于在 K 均值中选择最佳 K 的肘部图

从肘形图来看,K 介于 5 和 10 之间比较合适。我们以 K=8 作为最佳选择。

在这里,我们使用 8 个聚类来拟合 K 均值:

kmeans = KMeans(init='k-means++', n_clusters=8, n_init=10)
kmeans.fit(encoded_items)
P = kmeans.predict(encoded_items)

我们可以在三维表示中轻松地看到这些聚类:

# visualize the clusters:
encoded_fig = plt.figure()
ax = Axes3D(encoded_fig)
p = ax.scatter(encoded_items[:,0], encoded_items[:,1], encoded_items[:,2], c=P, marker="o", picker=True, cmap="rainbow")
plt.colorbar(p, shrink=0.5)
plt.show()

angle = 3
ani = animation.FuncAnimation(encoded_fig, rotate, frames=np.arange(0, 360, angle), interval=50)
ani.save('kmeans_fig.gif', writer=animation.PillowWriter(fps=12))

图 8:在三维空间中编码的亚马逊手机数据,使用 K 均值聚类定义了 8 个聚类。

聚类看起来基本合理,但是聚类之间显然存在一些差异,而且 K 均值无法清晰地识别出视觉上清晰的月牙形聚类。对此,可能应该使用更复杂的聚类算法。基于密度的聚类将是我的下一个选择!

4、结论

我们只是触及了该数据集可以做什么的表面,并且有许多不同的技术可以执行聚类(包括监督和无监督)。可以进行额外的工作,分析聚类本身的可解释性(例如,基于聚类的品牌、评级、价格等直方图)。我希望本指南能让你了解如何将这些想法应用于自己的数据!


原文链接:神经网络聚类分析 - BimAnt

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

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

相关文章

基于R语言复杂数据回归与混合效应模型【多水平/分层/嵌套】技术与代码

回归分析是科学研究特别是生态学领域科学研究和数据分析十分重要的统计工具&#xff0c;可以回答众多科学问题&#xff0c;如环境因素对物种、种群、群落及生态系统或气候变化的影响&#xff1b;物种属性和系统发育对物种分布&#xff08;多度&#xff09;的影响等。纵观涉及数…

PyTorch计算机视觉之Vision Transformer 整体结构

【图书推荐】《PyTorch深度学习与计算机视觉实践》-CSDN博客 Vision Transformer&#xff08;ViT&#xff09;模型是最新提出将注意力机制应用在图像分类的模型。Vision Transformer算法会将整幅图像拆分成小图像块&#xff0c;然后把这些小图像块的线性映射序列作为注意力模块…

昇思25天学习打卡营第29天 | 基于MindSpore通过GPT实现情感分类

基于MindSpore框架通过GPT模型实现情感分类展示了从项目设置、数据预处理到模型训练和评估的详细步骤&#xff0c;提供了一个完整的案例来理解如何在自然语言处理任务中实现情感分析。 首先&#xff0c;环境配置是任何机器学习项目的起点。项目通过安装特定版本的MindSpore和相…

基于机器学习的旅游景区评论情感分析算法设计与实现

1 绪论 1.1 背景与意义 1.1.1 背景 旅游业是全球范围内一个快速发展的行业&#xff0c;旅游景区作为旅游业的核心组成部分&#xff0c;对于吸引游客和提升旅游体验起着重要作用。随着社交媒体和在线评论平台的普及&#xff0c;越来越多的游客在网上分享他们对旅游景区的评论…

[路由器]IP-MAC的绑定与取消

背景&#xff1a;当公司的网络不想与外部人员进行共享&#xff0c;可以在路由器页面配置IP-MAC的绑定&#xff0c;让公司内部人员的手机和电脑的mac&#xff0c;才能接入到公司。第一步&#xff1a;在ARP防护中&#xff0c;启动IP-MAC绑定选项&#xff0c;必须启动仅允许IP-MAC…

OpenAI发布“最具性价比”模型 GPT-4o mini,GPT-3.5 Turbo 已成过去式

GPT-4o mini 相较于 GPT 3.5 在多个方面实现了显著的性能提升&#xff1a; 得分率提升&#xff1a;GPT-4o mini 在 MMLU&#xff08;一个涉及多种语言理解任务的基准测试&#xff09;上的得分率为 82%&#xff0c;优于 GPT-4&#xff0c;并且明显高于 GPT-3.5 2。 成本效益&am…

U盘数据危机:应对文件或目录损坏无法读取的全面解析

一、U盘数据损坏的困境与挑战 U盘&#xff0c;作为我们日常生活中不可或缺的便携存储设备&#xff0c;承载着大量的工作文档、学习资料及珍贵回忆。然而&#xff0c;当U盘中的文件或目录突然无法读取&#xff0c;甚至提示损坏时&#xff0c;我们往往会陷入焦急与无助之中。这种…

Modbus转BACnet/IP网关快速对接Modbus协议设备与BA系统

摘要 在智能建筑和工业自动化领域&#xff0c;Modbus和BACnet/IP协议的集成应用越来越普遍。BA&#xff08;Building Automation&#xff0c;楼宇自动化&#xff09;系统作为现代建筑的核心&#xff0c;需要高效地处理来自不同协议的设备数据&#xff0c;负责监控和管理建筑内…

华清数据结构day5 24-7-22

1>使用栈&#xff0c;完成进制转换输入&#xff1a;一个整数&#xff0c;进制数输出&#xff1a;该数的对应的进制数 seqstack.h #ifndef SEQSTACK_H #define SEQSTACK_H #define MAX 10 #include"myhead.h" typedef int datatype;typedef struct {datatype *d…

ReadAgent,一款具有要点记忆的人工智能阅读代理

人工智能咨询培训老师叶梓 转载标明出处 现有的大模型&#xff08;LLMs&#xff09;在处理长文本时受限于固定的最大上下文长度&#xff0c;并且当输入文本越来越长时&#xff0c;性能往往会下降&#xff0c;即使在没有超出明确上下文窗口的情况下&#xff0c;LLMs 的性能也会随…

Java查询ES报错 I/O 异常解决方法: Request cannot be executed; I/O reactor status: STOPPED

问题 ES Request cannot be executed; I/O reactor status: STOPPED 报错解决 在使用ES和SpringBoot进行数据检索时&#xff0c;在接口中第一次搜索正常。第二次在搜索时在控制台就会输出Request cannot be executed; I/O reactor status: STOPPED错误 原因 本文错误是因为在使…

高清视频,无损音频,LDR6023——打造极致视听与高效充电的双重享受!

Type-C PD&#xff08;Power Delivery&#xff09;芯片是一种支持USB Type-C接口规范的电源管理单元&#xff0c;其主要功能包括&#xff1a; 快速充电&#xff1a;Type-C PD芯片支持高功率传输&#xff0c;能够提供更快的充电速度&#xff0c;使电子设备在短时间内充满电&…

自然语言处理之RNN实现情感分类

前言 IMDB数据集经过分词处理后需要进行额外的预处理&#xff0c;包括将Token转换为index id&#xff0c;并统一文本序列长度。使用MindSpore.dataset接口进行预处理操作&#xff0c;包括text.Lookup和PadEnd接口。此外&#xff0c;还需要将label数据转换为float32格式。 模型…

.NET下支持多框架的托盘功能NotifyIconEx(WPF / WinForms / Avalonia / WinUI / MAUI / Wice)

支持 WPF / WinForms / Avalonia / WinUI / MAUI / Wice 应用。 先看效果&#xff1a; using NotifyIconEx;var notifyIcon new NotifyIcon() {Text "NotifyIcon",Icon Icon.ExtractAssociatedIcon(Process.GetCurrentProcess().MainModule?.FileName!)! }; not…

Kafka Producer之事务性

文章目录 1. 跨会话幂等性失效2. 开启事务3. 事务流程原理 事务性可以防止跨会话幂等性失效&#xff0c;同时也可以保证单个生产者的指定数据&#xff0c;要么全部成功要么全部失败&#xff0c;不限分区。不可以多个生产者共用相同的事务ID。 1. 跨会话幂等性失效 幂等性开启…

redis的学习(二):常见数据结构及其方法

简介 redis常见的数据结构和他们的常用方法 redis的数据结构 redis是一个key-value的nosql&#xff0c;key一般是字符串&#xff0c;value有很多的类型。 j基本类型&#xff1a; stringhashlistsetsortedSet 特殊类型&#xff1a; GEOBitMapHyperLog key的结构 可以使用…

VScode连接虚拟机运行Python文件的方法

声明&#xff1a;本文使用Linux发行版本为rocky_9.4 目录 1. 在rocky_9.4最小安装的系统中&#xff0c;默认是没有tar工具的&#xff0c;因此&#xff0c;要先下载tar工具 2. 在安装好的vscode中下载ssh远程插件工具 3. 然后连接虚拟机 4. 查看python是否已经安装 5. 下载…

Maven的核心概念

Maven的核心概念 —2020年06月11日 什么是Maven Maven是一款服务于Java平台的自动化构建工具。 约定的目录结构 目录结构&#xff1a; 根目录&#xff1a;工程名src目录&#xff1a;源码pom.xml文件&#xff1a;Maven工程的核心配置文件main目录&#xff1a;存放主程序tes…

Zabbix监控系统:zabbix服务部署+基于Proxy分布式部署+zabbix主动与被动监控模式

一、Zabbix概述 1.1 简介 zabbix 是一个基于 Web 界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。 zabbix 能监视各种网络参数&#xff0c;保证服务器系统的安全运营&#xff0c;提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。 zabbix…

三、GPIO按键读取

在上一篇文章中&#xff0c;我们详细讲解了GPIO的写函数。万事万物都具有一定的相对性&#xff0c;GPIO的操作也不例外。既然有写操作&#xff0c;那么必然也有读操作。有了上一篇文章的基础&#xff0c;理解本篇内容将会更加容易。 一、这篇文章能了解什么 本篇文章将基于上一…