PyTorch翻译官网教程-NLP FROM SCRATCH: CLASSIFYING NAMES WITH A CHARACTER-LEVEL RNN

官网链接

NLP From Scratch: Classifying Names with a Character-Level RNN — PyTorch Tutorials 2.0.1+cu117 documentation

使用CHARACTER-LEVEL RNN 对名字分类

我们将建立和训练一个基本的字符级递归神经网络(RNN)来分类单词。本教程以及另外两个“from scratch”的自然语言处理(NLP)教程 NLP From Scratch: Generating Names with a Character-Level RNN NLP From Scratch: Translation with a Sequence to Sequence Network and Attention,演示如何预处理数据以建立NLP模型。特别是,这些教程没有使用torchtext的许多便利功能,因此您可以看到如何简单使用预处理模型NLP。

字符级RNN将单词作为一系列字符来读取 ,每一步输出一个预测和“隐藏状态”,将之前的隐藏状态输入到下一步。我们把最后的预测作为输出,即这个词属于哪个类。

具体来说,我们将训练来自18种语言的几千个姓氏,并根据拼写来预测一个名字来自哪种语言:

$ python predict.py Hinton
(-0.47) Scottish
(-1.52) English
(-3.57) Irish$ python predict.py Schmidhuber
(-0.19) German
(-2.48) Czech
(-2.68) Dutch

建议准备

在开始本教程之前,建议您安装PyTorch,并对Python编程语言和张量有基本的了解:

  • PyTorch 有关安装说明
  • Deep Learning with PyTorch: A 60 Minute Blitz 开始使用PyTorch并学习张量的基础知识
  • Learning PyTorch with Examples 使用概述
  • PyTorch for Former Torch Users 如果您是前Lua Torch用户

了解rnn及其工作原理也很有用:

  • The Unreasonable Effectiveness of Recurrent Neural Networks 展示了一些现实生活中的例子
  • Understanding LSTM Networks 是专门关于LSTMs的,但也有关于RNNs的信息

准备数据

从这里下载数据并将其解压缩到当前目录。here

data/names”目录下包含18个文本文件,文件名为“[Language].txt”。每个文件包含一堆名称,每行一个名称,大多数是罗马化的(但我们仍然需要从Unicode转换为ASCII)。

我们最终会得到一个包含每种语言名称列表的字典,{language: [names ...]}。通用变量“category”和“line”(在本例中表示语言和名称)用于以后的可扩展性。

from io import open
import glob
import osdef findFiles(path): return glob.glob(path)print(findFiles('data/names/*.txt'))import unicodedata
import stringall_letters = string.ascii_letters + " .,;'"
n_letters = len(all_letters)# Turn a Unicode string to plain ASCII, thanks to https://stackoverflow.com/a/518232/2809427
def unicodeToAscii(s):return ''.join(c for c in unicodedata.normalize('NFD', s)if unicodedata.category(c) != 'Mn'and c in all_letters)print(unicodeToAscii('Ślusàrski'))# Build the category_lines dictionary, a list of names per language
category_lines = {}
all_categories = []# Read a file and split into lines
def readLines(filename):lines = open(filename, encoding='utf-8').read().strip().split('\n')return [unicodeToAscii(line) for line in lines]for filename in findFiles('data/names/*.txt'):category = os.path.splitext(os.path.basename(filename))[0]all_categories.append(category)lines = readLines(filename)category_lines[category] = linesn_categories = len(all_categories)

输出

['data/names/Arabic.txt', 'data/names/Chinese.txt', 'data/names/Czech.txt', 'data/names/Dutch.txt', 'data/names/English.txt', 'data/names/French.txt', 'data/names/German.txt', 'data/names/Greek.txt', 'data/names/Irish.txt', 'data/names/Italian.txt', 'data/names/Japanese.txt', 'data/names/Korean.txt', 'data/names/Polish.txt', 'data/names/Portuguese.txt', 'data/names/Russian.txt', 'data/names/Scottish.txt', 'data/names/Spanish.txt', 'data/names/Vietnamese.txt']
Slusarski

现在我们有了category_lines,这是一个将每个类别(语言)映射到行(名称)列表的字典。我们还记录了all_categories(只是一个语言列表)和n_categories,以供以后参考。

print(category_lines['Italian'][:5])

输出

['Abandonato', 'Abatangelo', 'Abatantuono', 'Abate', 'Abategiovanni']

把名字变成张量

现在我们已经组织好了所有的名字,我们需要把它们变成张量来使用它们。

为了表示单个字母,我们使用大小为<1 x n_letters> 的 “one-hot vector”。一个独热向量被0填充,除了当前字母所以处是1。例如:"b" = <0 1 0 0 0 ...>.

为了组成一个单词,我们将一堆这样的单词连接到一个二维矩阵中<line_length x 1 x n_letters>.

额外的1维度是因为PyTorch假设所有的东西都是分批的——我们在这里只是使用1的批大小。

import torch# Find letter index from all_letters, e.g. "a" = 0
def letterToIndex(letter):return all_letters.find(letter)# Just for demonstration, turn a letter into a <1 x n_letters> Tensor
def letterToTensor(letter):tensor = torch.zeros(1, n_letters)tensor[0][letterToIndex(letter)] = 1return tensor# Turn a line into a <line_length x 1 x n_letters>,
# or an array of one-hot letter vectors
def lineToTensor(line):tensor = torch.zeros(len(line), 1, n_letters)for li, letter in enumerate(line):tensor[li][0][letterToIndex(letter)] = 1return tensorprint(letterToTensor('J'))print(lineToTensor('Jones').size())

输出

tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0.]])
torch.Size([5, 1, 57])

创建网络

在autograd之前,在Torch中创建循环神经网络涉及到在几个时间步上克隆一层的参数。图层包含隐藏状态和梯度,现在完全由图形本身处理。这意味着你可以以一种非常“纯粹”的方式实现RNN,作为常规的前馈层。

这个RNN模块(主要是从the PyTorch for Torch users tutorial复制的)只有2个线性层,在输入和隐藏状态上操作,在输出之后有一个LogSoftmax层。

import torch.nn as nnclass RNN(nn.Module):def __init__(self, input_size, hidden_size, output_size):super(RNN, self).__init__()self.hidden_size = hidden_sizeself.i2h = nn.Linear(input_size + hidden_size, hidden_size)self.h2o = nn.Linear(hidden_size, output_size)self.softmax = nn.LogSoftmax(dim=1)def forward(self, input, hidden):combined = torch.cat((input, hidden), 1)hidden = self.i2h(combined)output = self.h2o(hidden)output = self.softmax(output)return output, hiddendef initHidden(self):return torch.zeros(1, self.hidden_size)n_hidden = 128
rnn = RNN(n_letters, n_hidden, n_categories)

为了运行这个网络的一个步骤,我们需要传递一个输入(在我们的例子中,是当前字母的张量)和一个先前的隐藏状态(我们一开始将其初始化为零)。我们将返回输出(每种语言的概率)和下一个隐藏状态(我们将其保留到下一步)。

input = letterToTensor('A')
hidden = torch.zeros(1, n_hidden)output, next_hidden = rnn(input, hidden)

为了提高效率,我们不想为每一步都创建一个新的张量,所以我们将使用lineToTensor而不是letterToTensor并使用切片。这可以通过预计算张量批次来进一步优化。

input = lineToTensor('Albert')
hidden = torch.zeros(1, n_hidden)output, next_hidden = rnn(input[0], hidden)
print(output)

输出

tensor([[-2.9083, -2.9270, -2.9167, -2.9590, -2.9108, -2.8332, -2.8906, -2.8325,-2.8521, -2.9279, -2.8452, -2.8754, -2.8565, -2.9733, -2.9201, -2.8233,-2.9298, -2.8624]], grad_fn=<LogSoftmaxBackward0>)

正如您所看到的,输出是一个<1 x n_categories> 张量,其中每个项目是该类别的可能性(越高越有可能)。

训练

训练准备

在开始训练之前,我们应该编写一些辅助函数。首先是解释网络的输出,我们知道这是每个类别的可能性。我们可以用Tensor.topk得到最大值的索引:

def categoryFromOutput(output):top_n, top_i = output.topk(1)category_i = top_i[0].item()return all_categories[category_i], category_iprint(categoryFromOutput(output))

输出

('Scottish', 15)

我们还需要一种快速获取训练示例(名称及其语言)的方法:

import randomdef randomChoice(l):return l[random.randint(0, len(l) - 1)]def randomTrainingExample():category = randomChoice(all_categories)line = randomChoice(category_lines[category])category_tensor = torch.tensor([all_categories.index(category)], dtype=torch.long)line_tensor = lineToTensor(line)return category, line, category_tensor, line_tensorfor i in range(10):category, line, category_tensor, line_tensor = randomTrainingExample()print('category =', category, '/ line =', line)

输出

category = Chinese / line = Hou
category = Scottish / line = Mckay
category = Arabic / line = Cham
category = Russian / line = V'Yurkov
category = Irish / line = O'Keeffe
category = French / line = Belrose
category = Spanish / line = Silva
category = Japanese / line = Fuchida
category = Greek / line = Tsahalis
category = Korean / line = Chang

训练网络

现在训练这个网络所需要做的就是给它看一堆例子,让它猜测,然后告诉它是否错了。

对于损失函数nn.NLLLoss是合适的,因为RNN的最后一层是nn.LogSoftmax.

criterion = nn.NLLLoss()

每个训练循环将:

  • 创建输入张量和目标张量
  • 创建一个零初始隐藏状态
  • 读取每个字母
    • 为下一个字母保存隐藏状态
  • 将最终输出与目标进行比较
  • 反向传播
  • 返回输出和损失
learning_rate = 0.005 # If you set this too high, it might explode. If too low, it might not learndef train(category_tensor, line_tensor):hidden = rnn.initHidden()rnn.zero_grad()for i in range(line_tensor.size()[0]):output, hidden = rnn(line_tensor[i], hidden)loss = criterion(output, category_tensor)loss.backward()# Add parameters' gradients to their values, multiplied by learning ratefor p in rnn.parameters():p.data.add_(p.grad.data, alpha=-learning_rate)return output, loss.item()

现在我们只需要用一堆例子来运行它。由于train函数返回输出和损失,我们可以打印它的猜测并跟踪损失以便绘制。由于有1000个示例,我们只打印每个print_every示例,并取损失的平均值。

import time
import mathn_iters = 100000
print_every = 5000
plot_every = 1000# Keep track of losses for plotting
current_loss = 0
all_losses = []def timeSince(since):now = time.time()s = now - sincem = math.floor(s / 60)s -= m * 60return '%dm %ds' % (m, s)start = time.time()for iter in range(1, n_iters + 1):category, line, category_tensor, line_tensor = randomTrainingExample()output, loss = train(category_tensor, line_tensor)current_loss += loss# Print ``iter`` number, loss, name and guessif iter % print_every == 0:guess, guess_i = categoryFromOutput(output)correct = '✓' if guess == category else '✗ (%s)' % categoryprint('%d %d%% (%s) %.4f %s / %s %s' % (iter, iter / n_iters * 100, timeSince(start), loss, line, guess, correct))# Add current loss avg to list of lossesif iter % plot_every == 0:all_losses.append(current_loss / plot_every)current_loss = 0

输出

5000 5% (0m 33s) 2.6379 Horigome / Japanese ✓
10000 10% (1m 5s) 2.0172 Miazga / Japanese ✗ (Polish)
15000 15% (1m 39s) 0.2680 Yukhvidov / Russian ✓
20000 20% (2m 12s) 1.8239 Mclaughlin / Irish ✗ (Scottish)
25000 25% (2m 45s) 0.6978 Banh / Vietnamese ✓
30000 30% (3m 18s) 1.7433 Machado / Japanese ✗ (Portuguese)
35000 35% (3m 51s) 0.0340 Fotopoulos / Greek ✓
40000 40% (4m 23s) 1.4637 Quirke / Irish ✓
45000 45% (4m 57s) 1.9018 Reier / French ✗ (German)
50000 50% (5m 30s) 0.9174 Hou / Chinese ✓
55000 55% (6m 2s) 1.0506 Duan / Vietnamese ✗ (Chinese)
60000 60% (6m 35s) 0.9617 Giang / Vietnamese ✓
65000 65% (7m 9s) 2.4557 Cober / German ✗ (Czech)
70000 70% (7m 42s) 0.8502 Mateus / Portuguese ✓
75000 75% (8m 14s) 0.2750 Hamilton / Scottish ✓
80000 80% (8m 47s) 0.7515 Maessen / Dutch ✓
85000 85% (9m 20s) 0.0912 Gan / Chinese ✓
90000 90% (9m 53s) 0.1190 Bellomi / Italian ✓
95000 95% (10m 26s) 0.0137 Vozgov / Russian ✓
100000 100% (10m 59s) 0.7808 Tong / Vietnamese ✓

绘制结果

绘制all_losses的历史损失图显示了网络的学习情况:

import matplotlib.pyplot as plt
import matplotlib.ticker as tickerplt.figure()
plt.plot(all_losses)

输出

[<matplotlib.lines.Line2D object at 0x7f16606095a0>]

评估结果

为了了解网络在不同类别上的表现如何,我们将创建一个混淆矩阵,表示网络猜测(列)的每种语言(行)。为了计算混淆矩阵,使用evaluate(),在网络中运行一堆样本,这与 train() 去掉反向传播相同。

# Keep track of correct guesses in a confusion matrix
confusion = torch.zeros(n_categories, n_categories)
n_confusion = 10000# Just return an output given a line
def evaluate(line_tensor):hidden = rnn.initHidden()for i in range(line_tensor.size()[0]):output, hidden = rnn(line_tensor[i], hidden)return output# Go through a bunch of examples and record which are correctly guessed
for i in range(n_confusion):category, line, category_tensor, line_tensor = randomTrainingExample()output = evaluate(line_tensor)guess, guess_i = categoryFromOutput(output)category_i = all_categories.index(category)confusion[category_i][guess_i] += 1# Normalize by dividing every row by its sum
for i in range(n_categories):confusion[i] = confusion[i] / confusion[i].sum()# Set up plot
fig = plt.figure()
ax = fig.add_subplot(111)
cax = ax.matshow(confusion.numpy())
fig.colorbar(cax)# Set up axes
ax.set_xticklabels([''] + all_categories, rotation=90)
ax.set_yticklabels([''] + all_categories)# Force label at every tick
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(ticker.MultipleLocator(1))# sphinx_gallery_thumbnail_number = 2
plt.show()

输出

/var/lib/jenkins/workspace/intermediate_source/char_rnn_classification_tutorial.py:445: UserWarning:FixedFormatter should only be used together with FixedLocator/var/lib/jenkins/workspace/intermediate_source/char_rnn_classification_tutorial.py:446: UserWarning:FixedFormatter should only be used together with FixedLocator

你可以从主轴上挑出亮点,显示它猜错了哪些语言,例如中文猜错了韩语,西班牙语猜错了意大利语。它似乎在希腊语上表现得很好,而在英语上表现得很差(可能是因为与其他语言重叠)。

运行用户输入

def predict(input_line, n_predictions=3):print('\n> %s' % input_line)with torch.no_grad():output = evaluate(lineToTensor(input_line))# Get top N categoriestopv, topi = output.topk(n_predictions, 1, True)predictions = []for i in range(n_predictions):value = topv[0][i].item()category_index = topi[0][i].item()print('(%.2f) %s' % (value, all_categories[category_index]))predictions.append([value, all_categories[category_index]])predict('Dovesky')
predict('Jackson')
predict('Satoshi')

输出

> Dovesky
(-0.57) Czech
(-0.97) Russian
(-3.43) English> Jackson
(-1.02) Scottish
(-1.49) Russian
(-1.96) English> Satoshi
(-0.42) Japanese
(-1.70) Polish
(-2.74) Italian

in the Practical PyTorch repo中脚本的最终版本将上述代码拆分为几个文件:

  • data.py (加载文件)
  • model.py (定义 RNN)
  • train.py (执行训练)
  • predict.py (运行带有命令行参数的predict() )
  • server.py (使用bottle.py作为JSON API提供预测)

运行train.py来训练和保存网络。

运行predict.py并输入一个名称来查看预测:

$ python predict.py Hazaki
(-0.42) Japanese
(-1.39) Polish
(-3.51) Czech

运行server.py 并访问http://localhost:5533/Yourname以获得预测的JSON输出。

练习

尝试使用不同的数据集 -> 类别,例如:

  • 任何单词->语言
  • 名字->性别
  • 角色名称->作家
  • 页面标题 -> 博客或社交新闻网站子版块

使用一个更大的和/或更好的形状网络,可以获得更好的结果

  • 添加更多线性图层
  • 试试 nn.LSTM nn.GRU 网络层
  • 将这些RNNs组合成一个更高级的网络

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

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

相关文章

学习笔记整理-DOM-01-基础知识

一、DOM基本概念 1. DOM基本概念 DOM是JS操控HTML和CSS的桥梁。DOM是JS操作HTML变得优雅。 DOM(Document Object Model&#xff0c;文档对象模型)是JavaScript操作HTML文档的接口&#xff0c;使文档操作变得非常优雅、简便。DOM最大的特点就是将文档表示为节点树。 节点的node…

【数据结构与算法】十大经典排序算法-选择排序

&#x1f31f;个人博客&#xff1a;www.hellocode.top &#x1f3f0;Java知识导航&#xff1a;Java-Navigate &#x1f525;CSDN&#xff1a;HelloCode. &#x1f31e;知乎&#xff1a;HelloCode &#x1f334;掘金&#xff1a;HelloCode ⚡如有问题&#xff0c;欢迎指正&#…

opencv基础55-获取轮廓的特征值及示例

轮廓自身的一些属性特征及轮廓所包围对象的特征对于描述图像具有重要意义。本节介绍几个轮廓自身的属性特征及轮廓所包围对象的特征。 宽高比 可以使用宽高比&#xff08;AspectRation&#xff09;来描述轮廓&#xff0c;例如矩形轮廓的宽高比为&#xff1a; 宽高比 宽度&am…

Vue3使用vue-print-nb插件调起打印功能

一、效果图 二、使用方式 安装插件 //Vue2.0版本安装方法 npm install vue-print-nb --save yarn add vue-print-nb//Vue3.0版本安装方法&#xff1a; npm install vue3-print-nb --save yarn add vue3-print-nb在全局引用 import Print from vue-print-nb Vue.use(Print)打…

Stable Diffusion WebUI 从零基础到入门

本文主要介绍Stable Diffusion WebUI的实际操作方法&#xff0c;涵盖prompt推导、lora模型、vae模型和controlNet应用等内容&#xff0c;并给出了可操作的文生图、图生图实战示例。适合对Stable Diffusion感兴趣&#xff0c;但又对Stable Diffusion WebUI使用感到困惑的同学&am…

序列模型和循环网络

Sequence Modeling and Recurrent Networks Sequence modeling tasks 在以往的模型中&#xff0c;各个输入之间是独立分布的 x ( i ) x^{(i)} x(i) 之间是相互独立的&#xff0c;同样输出 y ( i ) y^{(i)} y(i)之间也是相互独立的。 但是在序列模型中&#xff0c;输入输出是…

STM32基于CubeIDE和HAL库 基础入门学习笔记:功能驱动与应用

文章目录&#xff1a; 一&#xff1a;LED与按键驱动程序 main.c 1.闪灯 led.h led.c 2.按键控制LED亮灭 key.h key.c 二&#xff1a;蜂鸣器与继电器驱动程序 main.c 1.蜂鸣器 buzzer.h buzzer.c delay.h delay.c 2.继电器 relay.h relay.c 三&#xff1…

“MongoDB基础知识【超详细】

"探索MongoDB的无边之境&#xff1a;沉浸式数据库之旅" 欢迎来到MongoDB的精彩世界&#xff01;在这个博客中&#xff0c;我们将带您进入一个充满创新和无限潜力的数据库领域。无论您是开发者、数据工程师还是技术爱好者&#xff0c;MongoDB都将为您带来一场令人心动…

PLY模型格式详解【3D】

本文介绍PLY 多边形文件格式&#xff0c;这是一种用于存储被描述为多边形集合的图形对象。 PLY文件格式的目标是提供一种简单且易于实现但通用的格式足以适用于各种模型。 PLY有两种子格式&#xff1a;易于入门的 ASCII 表示形式和用于紧凑存储和快速保存和加载的二进制格式。 …

搭建 Python 环境 | Python、PyCharm

计算机 计算机能完成的工作&#xff1a; 算术运算逻辑判断数据存储网络通信…更多的更复杂的任务 以下这些都可以称为 “计算机”&#xff1a; 一台计算机主要由以下这几个重要的组件构成 CPU 中央处理器&#xff1a;大脑&#xff0c;算术运算&#xff0c;逻辑判断 存储器&…

Redis——常见数据结构与单线程模型

Redis中的数据结构 Redis中所有的数据都是基于key&#xff0c;value实现的&#xff0c;这里的数据结构指的是value有不同的类型。 当前版本Redis支持10种数据类型&#xff0c;下面介绍常用的五种数据类型 底层编码 Redis在实现上述数据结构时&#xff0c;会在源码有特定的…

Docker数据卷容器

1.数据卷容器介绍 即使数据卷容器c3挂掉也不会影响c1和c2通信。 2.配置数据卷容器 创建启动c3数据卷容器&#xff0c;使用-v参数设置数据卷。volume为目录&#xff0c;这种方式数据卷目录就不用写了&#xff0c;直接写宿主机目录。 创建c1、c2容器&#xff0c;使用–volum…

三星霸主地位“无可撼动“,DRAM内存市场份额创近 9 年新低仍第一

三星电子在DRAM市场的竞争地位一直备受关注。据报告显示&#xff0c;除了市场份额下降外&#xff0c;三星电子在上半年的销售额也出现了下滑。这主要是由于全球消费电子产品需求下滑&#xff0c;导致三星电子的芯片需求减少。 存储芯片业务所在的设备解决方案部门的营收和利润也…

24届近3年上海电力大学自动化考研院校分析

今天给大家带来的是上海电力大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、上海电力大学 学校简介 上海电力大学&#xff08;Shanghai University of Electric Power&#xff09;&#xff0c;位于上海市&#xff0c;是中央与上海市共建、以上海市管理为主的全日…

经典人体模型SMPL介绍(一)

SMPL是马普所提出的经典人体模型&#xff0c;目前已成为姿态估计、人体重建等领域必不可少的基础先验。SMPL基于蒙皮和BlendShape实现&#xff0c;从数千个三维人体扫描结果得来&#xff0c;后通过PCA统计学习得来。 论文&#xff1a;SMPL: A Skinned Multi-Person Linear Mode…

基于Python的HTTP代理爬虫开发初探

前言 随着互联网的发展&#xff0c;爬虫技术已经成为了信息采集、数据分析的重要手段。然而在进行爬虫开发的过程中&#xff0c;由于个人或机构的目的不同&#xff0c;也会面临一些访问限制或者防护措施。这时候&#xff0c;使用HTTP代理爬虫可以有效地解决这些问题&#xff0…

普华永道踩坑MOVEit漏洞,泄露银行8万名储户的信息

8月14日&#xff0c;波多黎各自治区最大的银行——人民银行向缅因州司法部长提交了一份客户信息泄露报告。该报告指出&#xff0c;由于供应商普华永道使用的MOVEit软件存在安全漏洞&#xff0c;导致银行82217名储户的个人信息被泄露。 目前&#xff0c;波多黎各人民银行已经陆续…

javaScript:数组方法(增删/提取类/截取/操作方法等)

目录 一.数组的增删方法 1.push()数组末尾添加元素 解释 代码 运行截图 2.unshift()向数组的头部添加数组 解释 代码 运行截图 3.pop()数组的尾部删除一个元素 解释 代码 运行截图 4.shift()数组的头部删除一个元素 解释 代码 运行截图 5. splice()任意位…

使用 Visual Studio Code 调试 CMake 脚本

之前被引入到 Visual Studio 中的 CMake 调试器&#xff0c;现已在 Visual Studio Code 中可用。 也就是说&#xff0c;现在你可以通过在 VS Code 中安装 CMake 工具扩展&#xff0c;来调试你的 CMakeLists.txt 脚本了。是不是很棒? 背景知识 Visual C 开发团队和 CMake 的维…