深度学习-第T11周——优化器对比实验

news2025/1/9 2:02:14

深度学习-第T11周——优化器对比实验

  • 深度学习-第T11周——优化器对比实验
    • 一、前言
    • 二、我的环境
    • 三、前期工作
      • 1、导入数据集
      • 2、查看图片数目
      • 3、查看数据
    • 四、数据预处理
      • 1、 加载数据
        • 1、设置图片格式
        • 2、划分训练集
        • 3、划分验证集
        • 4、查看标签
      • 2、数据可视化
      • 3、检查数据
      • 4、配置数据集
    • 五、搭建CNN网络
    • 六、训练模型
    • 七、模型评估
      • 1、Loss和Acc图
      • 2、模型评估
    • 八、总结

深度学习-第T11周——优化器对比实验

一、前言

  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊

二、我的环境

  • 电脑系统:Windows 10
  • 语言环境:Python 3.8.5
  • 编译器:colab在线编译
  • 深度学习环境:Tensorflow

三、前期工作

1、导入数据集

导入数据集,这里使用k同学的数据集,共17个分类。


import os, PIL, pathlib
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models
import matplotlib.pyplot as plt

data_dir = "/content/drive/MyDrive/Training_Camp_DL/TensorFlow/CNN/48-data"
print(type(data_dir))
data_dir = pathlib.Path(data_dir)
print(type(data_dir))

这段代码将字符串类型的 data_dir 转换为了 pathlib.Path 类型的对象。pathlib 是 Python3.4 中新增的模块,用于处理文件路径。
通过 Path 对象,可以方便地操作文件和目录,如创建、删除、移动、复制等。
在这里,我们使用 pathlib.Path() 函数将 data_dir 转换为路径对象,这样可以更加方便地进行文件路径的操作和读写等操作。

2、查看图片数目

image_count = len(list(data_dir.glob("*/*.jpg")))
print("图片数目:", image_count)

获取指定目录下所有子文件夹中 jpg 格式的文件数量,并将其存储在变量 image_count 中。

data_dir 是一个路径变量,表示需要计算的目标文件夹的路径。
glob() 方法可以返回匹配指定模式(通配符)的文件列表,该方法的参数 “/.jpg” 表示匹配所有子文件夹下以 .jpg 结尾的文件。

list() 方法将 glob() 方法返回的生成器转换为列表,方便进行数量统计。最后,len() 方法计算列表中元素的数量,就得到了指定目录下 jpg 格式文件的总数。

所以,这行代码的作用就是计算指定目录下 jpg 格式文件的数量。
在这里插入图片描述

3、查看数据

roses = list(data_dir.glob("Jennifer Lawrence/*.jpg"))
print(type(roses), roses)
PIL.Image.open(str(roses[0]))

在这里插入图片描述

四、数据预处理

1、 加载数据

1、设置图片格式

batch_size = 16
img_height = 336
img_width = 336

2、划分训练集

train_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split = 0.2,
    subset = 'training',
    seed = 12,
    image_size = (img_height, img_width),
    batch_size = batch_size
)

这行代码使用 TensorFlow 读取指定路径下的图片文件,并生成一个 tf.data.Dataset 对象,用于模型的训练和评估。

具体来说,tf.keras.preprocessing.image_dataset_from_directory() 函数从指定目录中读取图像数据,并自动对其进行标准化和预处理。该函数有以下参数:

directory:指定图像文件所在目录的路径;
seed:用于随机划分数据集的种子;
image_size:指定图像缩放后的尺寸;
batch_size:指定批次中图像的数量。
通过这些参数,函数将指定目录中的图像按照指定大小预处理后,随机划分为训练集和验证集。最终,生成的 tf.data.Dataset 对象包含了划分好的数据集,可以用于后续的模型训练和验证。

需要注意的是,这里的 img_height 和 img_width 变量应该提前定义,并且应该与实际图像的尺寸相对应。同时,batch_size 也应该根据硬件设备的性能合理调整,以充分利用 GPU/CPU 的计算资源。
在这里插入图片描述

3、划分验证集

val_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split = 0.2,
    subset = "validation",
    seed = 12,
    image_size = (img_height, img_width),
    batch_size = batch_size
)

这段代码和上一段代码类似,使用 TensorFlow 的 keras.preprocessing.image_dataset_from_directory() 函数从指定的目录中读取图像数据集,并将其划分为训练集和验证集。

其中,data_dir 指定数据集目录的路径,validation_split 表示从数据集中划分出多少比例的数据作为验证集,subset 参数指定为 “validation” 则表示从数据集的 20% 中选择作为验证集,其余 80% 作为训练集。seed 是一个随机种子,用于生成可重复的随机数。image_size 参数指定输出图像的大小,batch_size 表示每批次加载的图像数量。

该函数返回一个 tf.data.Dataset 对象,代表了整个数据集(包含训练集和验证集)。可以使用 train_ds 和 val_ds 两个对象分别表示训练集和验证集。

不过两段代码的 subset 参数值不同,一个是 “training”,一个是 “validation”。

因此,在含有交叉验证或者验证集的深度学习训练过程中,需要定义两个数据集对象 train_ds 和 val_ds。我们已经定义了包含训练集和验证集的数据集对象 train_ds,可以省略这段代码,无需重复定义 val_ds 对象。只要确保最终的训练过程中,两个数据集对象都能够被正确地使用即可。

如果你没有定义 val_ds 对象,可以使用这段代码来创建一个验证数据集对象,用于模型训练和评估,从而提高模型性能。

在这里插入图片描述

4、查看标签

class_names = train_ds.class_names
class_names

train_ds.class_names 是一个属性,它是通过数据集对象 train_ds 中的类别信息自动生成的一个包含类别名称的列表。

在创建数据集对象 train_ds 时,你可以通过 class_names 参数手动指定类别名称,也可以根据图像文件夹的目录结构自动推断出来。
例如,假设你有一个包含猫和狗两种类别的图像数据集,其中猫类别的图像存储在 “cat” 文件夹中,狗类别的图像存储在 “dog” 文件夹中,
那么当你使用 keras.preprocessing.image_dataset_from_directory() 函数加载数据集时,会自动将 “cat” 和 “dog” 文件夹作为两个不同的类别,
并将它们的名称存储在 train_ds.class_names 属性中。

执行该代码后,你就可以在控制台或者输出窗口中看到包含数据集中所有类别名称的列表。这些名称通常是按照字母顺序排列的

因此,train_ds.class_names 属性可以让你方便地查看数据集中所有的类别名称,以便后续的模型训练、预测和评估等任务。
如果你要对数据集进行多类别分类,则需要根据 train_ds.class_names 的元素个数设置输出层的神经元数量,并将每个类别与一个唯一的整数标签相关联。
在这里插入图片描述

2、数据可视化

plt.figure(figsize = (20, 10))

for images, labels in train_ds.take(1):
  for i in range(20):
    ax = plt.subplot(5, 10, i + 1)

    plt.imshow(images[i].numpy().astype("uint8"))
    plt.title(class_names[np.argmax(labels[i])]) #返回当前处理的图像所代表的类别的索引。

    plt.axis("off")

train_ds.take(1) 是一个方法调用,它返回一个数据集对象 train_ds 中的子集,其中包含了 take() 方法参数指定的数量的样本。
在这个例子中,take(1) 意味着我们从 train_ds 数据集中获取一批包含一个样本的数据块。

因此,for images, labels in train_ds.take(1): 的作用是遍历这个包含一个样本的数据块,并将其中的图像张量和标签张量依次赋值给变量 images 和 labels。具体来说,
它的执行过程如下:

从 train_ds 数据集中获取一批大小为 1 的数据块。
遍历这个数据块,每次获取一个图像张量和一个标签张量。
将当前图像张量赋值给变量 images,将当前标签张量赋值给变量 labels。
执行 for 循环中的代码块,即对当前图像张量和标签张量进行处理。

plt.imshow() 函数是 Matplotlib 库中用于显示图像的函数,它接受一个数组或张量作为输入,并在窗口中显示对应的图像。
在这个代码中,images[i] 表示从训练集中获取的第 i 个图像张量。由于 images 是一个包含多个图像的张量列表,因此使用 images[i] 可以获取其中的一个图像。

由于 imshow() 函数需要输入的数组或张量的类型是整型或浮点型,而从数据集中获取的图像张量通常是浮点型张量,因此需要将其转换为整型张量,以便进行显示。
这里使用了 .numpy().astype(“uint8”) 操作来将图像张量转换为整型张量(uint8 表示无符号8位整数),然后将结果传递给 plt.imshow() 函数进行显示。

因此,plt.imshow(images[i].numpy().astype(“uint8”)) 的作用是在 Matplotlib 窗口中显示训练集中的第 i 个图像。
你可以通过改变 i 的值来显示不同的图像,例如 i = 0 表示显示训练集中的第一张图像。

plt.axis(“off”) 是 Matplotlib 库中的一个函数调用,它用于控制图像显示时的坐标轴是否可见。
具体来说,当参数为 “off” 时,图像的坐标轴会被关闭,不会显示在图像周围。这个函数通常在 plt.imshow() 函数之后调用,以便在显示图像时去掉多余的细节信息,仅仅显示图像本身。

在这里插入图片描述

3、检查数据

for image_batch, labels_batch in train_ds:
  print(image_batch.shape)
  print(labels_batch.shape)
  break

在这里插入图片描述

4、配置数据集

##2.3、配置数据集
AUTOTUNE = tf.data.AUTOTUNE
"""
定义 AUTOTUNE 常量
这个常量的作用是指定 TensorFlow 数据管道读取数据时使用的线程个数,使得数据读取可以尽可能地并行化,提升数据读取效率。
具体来说,AUTOTUNE 的取值会根据系统资源和硬件配置等因素自动调节。"""

def train_preprocessing(image, label):
    return (image / 255.0, label)

"""这个函数的作用是对输入的图像数据进行预处理操作,其中 image 表示输入的原始图像数据,label 表示对应的标签信息。
函数体内的操作是把原始图像数据除以 255,使其数值归一化到 0 和 1 之间。
函数返回一个元组 (image / 255.0, label),其中第一个元素是经过处理后的图像数据,第二个元素是对应的标签信息。
"""

#归一化处理


train_ds = train_ds.cache().shuffle(1000).map(train_preprocessing).prefetch(buffer_size = AUTOTUNE)
val_ds = val_ds.cache().shuffle(1000).map(train_preprocessing).prefetch(buffer_size = AUTOTUNE)

AUTOTUNE 是 TensorFlow 的一个常量,它的值取决于当前硬件配置和运行环境。
它表示 TensorFlow 数据处理流程中可以自动选择最优化参数(例如 GPU 处理数量等)的范围,在不同的硬件配置下可能会有不同的取值。
在这个代码片段中,AUTOTUNE 的作用是为数据集的预处理过程提供了一个启发式的缓冲大小,以便更好地平衡内存使用和计算速度。

train_preprocessing 函数定义了对输入图像数据进行预处理的操作,其中 image 表示输入的原始图像数据,label 表示对应的标签信息。函数体内的操作是将原始图像数据除以 255,使其数值归一化到 0 和 1 之间。函数返回一个元组 (image / 255.0, label),其中第一个元素是经过处理后的图像数据,第二个元素是对应的标签信息。

接下来,训练数据集 train_ds 和验证数据集 val_ds 进行了一系列操作。首先使用 cache() 方法对数据集进行缓存,然后使用 shuffle(1000) 方法随机打乱数据集中的样本,接着使用 map(train_preprocessing) 方法对每个样本应用 train_preprocessing 函数进行预处理,最后使用 prefetch(buffer_size = AUTOTUNE) 方法预取数据以提高训练的效率。

五、搭建CNN网络

from tensorflow.keras.layers import BatchNormalization
#from tensorflow.keras.models import Model

def create_model(optimizer = 'adam'):
  
  vgg16_base_model = tf.keras.applications.vgg16.VGG16(weights = 'imagenet', include_top = False, input_shape = (img_width, img_height, 3), pooling = 'avg')

  for layer in vgg16_base_model.layers:
    layer.trainable = False

  X = vgg16_base_model.output

  X = Dense(170, activation = 'relu')(X)
  X = BatchNormalization()(X)
  X = Dropout(0.5)(X)

  output = Dense(len(class_names), activation = 'softmax')(X)
  vgg16_model = Model(inputs = vgg16_base_model.input, outputs = output)
  
  vgg16_model.compile(optimizer = optimizer,
            loss = 'sparse_categorical_crossentropy',
            metrics = ['accuracy'])
  return vgg16_model

model1 = create_model(optimizer = tf.keras.optimizers.Adam())
model2 = create_model(optimizer = tf.keras.optimizers.SGD())
model2.summary()

首先,从 tensorflow.keras.layers 模块导入了 BatchNormalization 层。注释掉的那行代码导入了 Model 类,但在代码中并没有使用到。

接下来,定义了一个名为 create_model 的函数,该函数接受一个参数 optimizer(默认为 ‘adam’)。函数内部首先使用 tf.keras.applications.vgg16.VGG16 加载了预训练的 VGG16 模型,其中 weights = ‘imagenet’ 表示使用 ImageNet 数据集的预训练权重,include_top = False 表示不包含顶层的全连接层,input_shape 设置输入图像的形状,pooling = ‘avg’ 表示使用平均池化。

然后,对 VGG16 模型的所有层进行循环遍历,将每一层的 trainable 属性设置为 False,即冻结预训练参数,使它们在训练过程中保持不变。

接下来,将 VGG16 模型的输出作为输入,并经过一系列的神经网络层进行特征提取和分类。首先是全连接层 Dense(170, activation = ‘relu’),然后是批归一化层 BatchNormalization(),最后是丢弃层 Dropout(0.5),用于防止过拟合。

最后一层是输出层 Dense(len(class_names), activation = ‘softmax’),其中 len(class_names) 是类别的数量,根据具体情况进行设置,激活函数使用 softmax。

接下来,使用 Model 函数创建了一个新的模型 vgg16_model,指定了输入和输出。这里的 vgg16_base_model.input 表示 VGG16 模型的输入,output 表示上述神经网络层的输出。

然后,使用 compile 方法编译了模型,指定了优化器、损失函数和评估指标。优化器根据传入的参数进行选择,如果没有指定,默认为 Adam 优化器。损失函数使用稀疏分类交叉熵,评估指标为准确率。

最后,函数返回创建的模型。

接下来,通过调用 create_model 函数分别创建了两个模型 model1 和 model2,分别使用了 Adam 优化器和 SGD 优化器。最后调用 model2.summary() 打印了 model2 的模型摘要信息。

在这里插入图片描述

六、训练模型

NO_EPOCHS = 50
history_model1 = model1.fit(train_ds, epochs = NO_EPOCHS, verbose = 1, validation_data = val_ds)
history_model2 = model2.fit(train_ds, epochs = NO_EPOCHS, verbose = 1, validation_data = val_ds)

使用 fit 方法训练了 model1 和 model2 模型,并将训练过程中的指标记录在 history_model1 和 history_model2 中。
参数 train_ds 是训练数据集,epochs 指定训练轮数,verbose 控制是否打印训练过程中的日志信息(这里设置为 1,表示打印日志),validation_data 是验证数据集,用于在每个训练轮结束后评估模型性能。
在这里插入图片描述

七、模型评估

1、Loss和Acc图

from matplotlib.ticker import MultipleLocator
plt.rcParams['savefig.dpi'] = 300 #图片像素
plt.rcParams['figure.dpi'] = 300 #分辨率

acc1 = history_model1.history['accuracy']
acc2 = history_model2.history['accuracy']
val_acc1 = history_model1.history['val_accuracy']
val_acc2 = history_model2.history['val_accuracy']

loss1 = history_model1.history['loss']
loss2 = history_model2.history['loss']
val_loss1 = history_model1.history['val_loss']
val_loss2 = history_model2.history['val_loss']

epochs_range = range(len(acc1))
plt.figure(figsize = (16, 4))
plt.subplot(1, 2, 1)

plt.plot(epochs_range, acc1, label = 'Training Accuracy-Adam')
plt.plot(epochs_range, acc2, label = 'Training Accuracy-SGD')
plt.plot(epochs_range, val_acc1, label = 'Validation Accuracy-Adam')
plt.plot(epochs_range, val_acc2, label = 'Validation Accuracy-SGD')
plt.legend(loc = 'lower right')
plt.title('Training and Validation Accuracy')
ax = plt.gca()
ax.xaxis.set_major_locator(MultipleLocator(1))

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss1, label = 'Training Loss-Adam')
plt.plot(epochs_range, loss2, label = 'Training Loss-SGD')
plt.plot(epochs_range, val_loss1, label = 'Validation Loss-Adam')
plt.plot(epochs_range, val_loss2, label = 'Validation Loss-SGD')
plt.legend(loc = 'upper right')
plt.title('Training and Validation Loss')
ax = plt.gca()
ax.xaxis.set_major_locator(MultipleLocator(1))

plt.show()

在这里插入图片描述

2、模型评估

def test_accuracy_report(model):
  score = model.evaluate(val_ds, verbose = 0)
  print('Loss function; %s, accuracy:' % score[0], score[1])

print('model1:')
test_accuracy_report(model1)
print('model2:')
test_accuracy_report(model2)
'''

在这里插入图片描述

八、总结

通过本次实验,学会了进行对比实验,可以比较不同优化器在训练过程中的性能表现,可视化训练过程的损失曲线和准确率等指标。这是一项非常重要的技能,在研究论文中,我们可以通过这些优化方法可以提高工作量。

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

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

相关文章

6月份读书学习好文记录

看看CHATGPT在最近几个月的发展趋势 https://blog.csdn.net/csdnnews/article/details/130878125?spm1000.2115.3001.5927 这是属于 AI 开发者的好时代,有什么理由不多去做一些尝试呢。 北大教授陈钟谈 AI 未来:逼近 AGI、融进元宇宙,开源…

06-浏览器渲染原理

什么是渲染? render,HTML字符串 --渲染--> 像素信息 URL地址是一个字符串,HTML、css、js都在里面 可以把渲染想象成一个函数,上代码: function render (html) {/* 第一行第二行*/return pixels; } 渲染时间点 …

【深入浅出 Spring Security(十二)】使用第三方(Github)授权登录

使用第三方(Github)授权登录 一、OAuth2 简单概述二、OAuth2 四种授权模式之授权码模式三、Github 授权登录准备工作创建 Spring Boot 项目Vue 测试代码测试效果 (Github授权登录的具体操作在目录第三“章”) 一、OAuth2 简单概述…

Spring Boot 优雅集成 Spring Security 5.7(安全框架)与 JWT(双令牌机制)

Spring Boot 集成 Spring Security (安全框架) 本章节将介绍 Spring Boot 集成 Spring Security 5.7(安全框架)。 🤖 Spring Boot 2.x 实践案例(代码仓库) 介绍 Spring Security 是一个能够为基…

【CSDN创作纪念日】——博客小梦的“256”鸭~

博客小梦的创作纪念日😎 前言🙌与CSDN的相遇浑水摸鱼的日常CSDN上的小小收获收获了 一群热爱编程,热爱创作的CSDN挚友创作上的小荣誉 憧憬未来 总结撒花💞 😎博客昵称:博客小梦 😊最喜欢的座右铭…

【Spring】— Spring MVC简单数据绑定(一)

目录 Spring MVC数据绑定1.数据绑定概述2.简单数据绑定2.1 绑定默认数据类型2.2 绑定简单数据类型 Spring MVC数据绑定 1.数据绑定概述 在执行程序时,Spring MVC根据客户端请求参数的不同将请求消息中的信息以一定的方式转换并绑定到控制器类的方法参数中。这种将…

基于VMD-LSTM-IOWA-RBF的碳排放混合预测研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

第J3-1周:DenseNet算法 实现乳腺癌识别

目录 一、理论基础1.前言2.设计理念3.网络结构4.与其他算法效果对比 二、 前期准备1. 设置GPU2. 导入数据3. 划分数据集 三、搭建网络模型1. DenseLayer模块2. DenseBlock模块3. Transition模块4. 构建DenseNet5. 构建densenet121 四、 训练模型1. 编写训练函数2. 编写测试函数…

I/O多路复用+高性能网络模式

前言: 本篇文章将介绍客户端-服务端之间从最简单的Socket模型到I/O多路复用的模式演变过程,并介绍Reactor和Proactor两种高性能网络模式 文章内容摘自:小林Coding I/O多路复用高性能网络模式 . 传统Socket模型传统Socket模型的性能瓶颈多进程…

【Java基础学习打卡12】Java入门程序

目录 前言一、Java程序开发运行流程二、Java程序源代码编写三、Java程序源代码编译四、Java程序运行五、Java入门程序问题总结 前言 本文首先介绍Java程序开发运行基础流程,然后先进行程序源代码编写,然后对Java程序代码进行编译,最后要运行…

Python学习笔记(1)--环境搭建,开发工具PyCharm 安装及初步使用

传送门>B站黑马python入门教程 目录 1.Python环境安装搭建安装python基础包验证安装文件 2.hello world3.开发工具PyCharm 安装及初步使用安装基础设置 1.Python环境安装搭建 安装python基础包 首先,打开python 官网 https://www.python.org/ 下载windows版 下载后进行安装 …

Triton教程 --- 解耦后端和模型

Triton教程 — 解耦后端和模型 Triton系列教程: 快速开始利用Triton部署你自己的模型Triton架构模型仓库存储代理模型设置优化动态批处理速率限制器模型管理自定义算子 解耦后端和模型 Triton 可以支持为一个请求发送多个响应或为一个请求发送零个响应的后端和模型。 解耦的…

论文笔记--Prompt Consistency for Zero-Shot Task Generalization

论文笔记--Prompt Consistency for Zero-Shot Task Generalization 1. 文章简介2. 文章概括3 文章重点技术3.1 Prompt-based zero-shot task generalization3.2 Prompt Consistency Training3.3 如何防止遗忘和退化? 4. 文章亮点5. 原文传送门 1. 文章简介 标题&am…

【numpy模块上}——数据分析01

目录索引 介绍:用处与特点:构成:导包:创建数组: numpy常用方法:常用属性查看:*获取秩的大小:**获取数组形状:**获取元素个数:**获取元素类型:**获…

行为型设计模式10-解释器模式

🧑‍💻作者:猫十二懿 ❤️‍🔥账号:CSDN 、掘金 、个人博客 、Github 🎉公众号:猫十二懿 解释器模式 1、解释器模式介绍 解释器模式(Interpreter Pattern)是一种行为设…

Kafka系列之:对源连接器的的Exactly-Once支持

Kafka系列之:对源连接器的的Exactly-Once支持 一、背景二、目标三、公共接口四、连接器 API 扩展五、REST API验证六、新指标七、计划变更八、任务计数记录九、重新平衡的准备十、源任务启动十一、领导者访问配置主题十二、用于隔离事务生产者的管理 API十三、解决了…

论文阅读 - SegFormer

文章目录 1 概述2 模型说明2.1 总体结构2.2 Hierarchical Transformer Encoder2.3 Lightweight All-MLP Decoder 3 SegFormer和SETR的比较参考资料 1 概述 图像分割任务和图像分类任务是非常相关的,前者是像素级别的分类,后者是图像级别的分类。基于分类…

不到3000块,搭建IT人的实验平台!性能可媲美服务器!

作为IT从业者,特别是运维这个岗位,没有自己的实验平台真的特别难受,那么如何搭建自己的实验平台呢?这是我最近思考并付诸实践的一个事情,最终找到了自己觉得比较可以的方案。 01 我的需求是什么? 大内存容量…

TypeScript——类(class)

ES6 中类的用法 下面我们先回顾一下 ES6 中类的用法,更详细的介绍可以参考 ECMAScript 6 入门 - Class 属性和方法 使用 class 定义类,使用 constructor 定义构造函数。 通过new生成新实例的时候,会自动调用构造函数。 class Person{con…

leetcode877. 石子游戏(动态规划-java)

石子游戏 leetcode877. 石子游戏题目描述暴力递归代码演示 动态规划 动态规划专题: leetcode877. 石子游戏 来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/stone-game 题目描述 Alice 和 Bob 用几堆石子在做游戏。一共有…