Bert基础(八)--Bert实战之理解Bert微调

到目前为止,我们已经介绍了如何使用预训练的BERT模型。现在,我们将学习如何针对下游任务微调预训练的BERT模型。需要注意的是,微调并非需要我们从头开始训练BERT模型,而是使用预训练的BERT模型,并根据任务需要更新模型的权重。

在本节中,我们将学习如何为以下任务微调预训练的BERT模型。

  • 文本分类任务
  • 自然语言推理任务
  • 问答任务
  • 命名实体识别任务

1、 文本分类任务

首先,我们学习如何为文本分类任务微调预训练的BERT模型。比如我们要进行情感分析,目标是对一个句子是积极(正面情绪)还是消极(负面情绪)进行分类。假设我们有一个包含句子及其标签的数据集。

以句子I love Paris为例,我们首先对句子进行标记,在句首添加[CLS],在句尾添加[SEP]。然后,将这些标记输入预训练的BERT模型,并得到所有标记的嵌入。

接下来,我们只取[CLS]的嵌入,也就是 R C L S R_{CLS} RCLS,忽略所有其他标记的嵌入,因为[CLS]标记的嵌入包含整个句子的总特征。我们将 R C L S R_{CLS} RCLS送入一个分类器(使用softmax激活函数的前馈网络层),并训练分类器进行情感分析。

但这与我们在本节一开始看到的情况有什么不同呢?微调预训练的BERT模型与使用预训练的BERT模型作为特征提取器有何不同呢?

在前面,我们了解到,在提取句子的嵌入 R C L S R_{CLS} RCLS后,我们将 R C L S R_{CLS} RCLS送入一个分类器并训练其进行分类。同样,在微调过程中,我们将嵌入 R C L S R_{CLS} RCLS送入一个分类器,并训练它进行分类。

不同的是,对预训练的BERT模型进行微调时,模型的权重与分类器一同更新。但使用预训练的BERT模型作为特征提取器时,我们只更新分类器的权重,而不更新模型的权重。

在微调期间,可以通过以下两种方式调整权重。

  • 与分类器层一起更新预训练的BERT模型的权重。
  • 仅更新分类器层的权重,不更新预训练的BERT模型的权重。这类似于使用预训练的BERT模型作为特征提取器的情况。

下图显示了如何针对文本分类任务对预训练的BERT模型进行微调。

在这里插入图片描述
从图中可以看到,我们将标记送入预训练的BERT模型,得到所有标记的嵌入。然后将[CLS]标记的嵌入送入使用softmax激活函数的前馈网络层进行分类。

下面,我们将针对情感分析任务对预训练的BERT模型进行微调,以深入了解微调的工作原理。

1.1 导入依赖库

我们以使用IMDB数据集的情感分析任务为例来微调预训练的BERT模型。IMDB数据集由电影评论和情感标签(正面/负面)组成。

首先,安装必要的库。

!pip install nlp
!pip install Transformers

然后,导入必要的模块。

from transformers import BertForSequenceClassification, BertTokenizerFast,
Trainer, TrainingArguments
from nlp import load_dataset
import torch
import numpy as np

1.2 加载模型和数据集

使用nlp库下载并加载数据集。然后,检查数据类型。

from datasets import load_dataset
dataset = load_dataset("ethos", "binary", split='train')
type(dataset)

输出如下。

nlp.arrow_dataset.Dataset

接下来,将数据集分成训练集和测试集。

dataset = dataset.train_test_split(test_size=0.3)

打印数据集的内容。

print(dataset)
DatasetDict({
    train: Dataset({
        features: ['text', 'label'],
        num_rows: 798
    })
    test: Dataset({
        features: ['text', 'label'],
        num_rows: 200
    })
})

现在,创建训练集和测试集。

train_set = dataset['train']
test_set = dataset['test']

接下来,下载并加载预训练的BERT模型。在这个例子中,我们使用预训练的bert-base-uncased模型。由于要进行序列分类,因此我们使用BertForSequence-Classification类。

model = BertForSequenceClassification.from_pretrained('bert-base-uncased')

然后,下载并加载用于预训练bert-base-uncased模型的词元分析器。

可以看到,我们使用了BertTokenizerFast类创建词元分析器,而不是使用BertTokenizer。与BertTokenizer相比,BertTokenizerFast类有很多优点。我们将在后面了解这方面的内容。

tokenizer = BertTokenizerFast.from_pretrained('bert-base-uncased')

现在,我们已经加载了数据集和模型,可以开始对数据集进行预处理了。

1.3 预处理数据集

我们仍然以句子I love Paris为例,使用词元分析器对数据集进行快速预处理。

首先,对例句进行标记,在句首添加[CLS]标记,在句尾添加[SEP]标记,如下所示。

tokens = [ [CLS], I, love, Paris, [SEP] ]

接下来,将标记映射到唯一的输入ID(标记ID)。假设输入ID如下所示。

input_ids = [101, 1045, 2293, 3000, 102]

然后,添加分段ID(标记类型ID)。假设输入中有两个句子,分段ID可以用来区分这两个句子。第1句中的所有标记被映射为0,第2句中的所有标记被映射为1。在这里,我们只有一个句子,因此所有的标记都会被映射为0,如下所示。

token_type_ids = [0, 0, 0, 0, 0]

现在创建注意力掩码。我们知道注意力掩码是用来区分实际标记和[PAD]标记的,它把所有实际标记映射为1,把[PAD]标记映射为0。假设标记长度为5,因为标记列表已经有5个标记,所以不必添加[PAD]标记。在本例中,注意力掩码如下所示。

attention_mask = [1, 1, 1, 1, 1]

不过,我们无须手动执行上述所有步骤,词元分析器会为我们完成这些步骤。我们只需将例句传递给词元分析器,如下所示。

tokenizer('I love Paris')

上面的代码将返回以下内容。可以看到,输入句已被标记,并被映射到input_ids、token_type_ids和attention_mask。

{
'input_ids': [101, 1045, 2293, 3000, 102],
'token_type_ids': [0, 0, 0, 0, 0],
'attention_mask': [1, 1, 1, 1, 1]
}

通过词元分析器,还可以输入任意数量的句子,并动态地进行补长或填充。要实现动态补长或填充,需要将padding设置为True,同时设置最大序列长度。假设输入3个句子,并将最大序列长度max_length设置为5,如下所示。

tokenizer(['I love Paris', 'birds fly', 'snow fall'], padding = True,max_length = 5)

上面的代码将返回以下内容。可以看到,所有的句子都被映射到input_ids、token_type_ids和attention_mask。第2句和第3句只有两个标记,加上[CLS]和[SEP]后,有4个标记。由于将padding设置为True,并将max_length设置为5,因此在第2句和第3句中添加了一个额外的[PAD]标记。这就是在第2句和第3句的注意力掩码中出现0的原因。

{
'input_ids': [[101, 1045, 2293, 3000, 102], [101, 5055, 4875, 102, 0],
[101, 4586, 2991, 102, 0]],
'token_type_ids': [[0, 0, 0, 0, 0], [1, 1, 1, 1, 1], [0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1], [1, 1, 1, 1, 0], [1, 1, 1, 1, 0]]
}

有了词元分析器,我们可以轻松地预处理数据集。我们定义了一个名为preprocess的函数来处理数据集,如下所示。

def preprocess(data):
    return tokenizer(data['text'], padding = True, truncation = True)

使用preprocess函数对训练集和测试集进行预处理。

train_set = train_set.map(preprocess, batched = True,
                          batch_size = len(train_set))
test_set = test_set.map(preprocess, batched = True, batch_size = len(test_set))

接下来,使用set_format函数,选择数据集中需要的列及其对应的格式,如下所示。

train_set.set_format('torch',
                      columns = ['input_ids', 'attention_mask', 'label'])
test_set.set_format('Torch',
                     columns = ['input_ids', 'attention_mask', 'label'])

1.4 训练模型

首先,定义批量大小和迭代次数。

batch_size = 8
epochs = 2

然后,确定预热步骤和权重衰减。

warmup_steps = 500
weight_decay = 0.01

接着,设置训练参数。

training_args = TrainingArguments(
    output_dir = './results',
    num_train_epochs = epochs,
    per_device_train_batch_size = batch_size,
    per_device_eval_batch_size = batch_size,
    warmup_steps = warmup_steps,
    weight_decay = weight_decay,
    evaluate_during_training = True,
    logging_dir = './logs',
)

最后,定义训练函数。

trainer = Trainer(
    model = model,
    args = training_args,
    train_dataset = train_set,
    eval_dataset = test_set
)

现在,开始训练模型。

trainer.train()

在这里插入图片描述

训练结束后,可以使用evaluate函数来评估模型。

trainer.evaluate()

以上代码的输出如下。

在这里插入图片描述

以这种方式,我们就可以针对文本分类任务对预训练的BERT模型进行微调。

2、自然语言推理任务

现在,我们学习如何为自然语言推理任务微调BERT模型。在自然语言推理任务中,模型的目标是确定在给定前提下,一个假设是必然的(真)、矛盾的(假),还是未定的(中性)。

在下图所示的样本数据集中,有几个前提和假设,并有标签表明假设是真、是假,还是中性。

在这里插入图片描述
模型的目标是确定一个句子对(前提−假设对)是真、是假,还是中性。我们以一个前提−假设对为例来了解如何做到这一点。

  • 前提:He is playing(他在玩)
  • 假设:He is sleeping(他在睡觉)

首先,对句子对进行标记,在第1句的开头添加[CLS]标记,在每句的结尾添加[SEP]标记,如下所示。

tokens = [ [CLS], He, is, playing, [SEP], He, is, sleeping, [SEP] ]

现在,将这些标记送入预训练的BERT模型,得到每个标记的嵌入。我们已经知道[CLS]标记的特征就是整个句子对的特征。

因此,将[CLS]标记的特征 R C L S R_{CLS} RCLS送入分类器(使用softmax激活函数的前馈网络层)。分类器将返回该句子对是真、是假,以及是中性的概率,如图所示。在最初的迭代中,结果会不准确,但经过多次迭代后,结果会逐渐准确。

在这里插入图片描述

3、 问答任务

在本节中,我们将学习如何为问答任务微调BERT模型。在问答任务中,针对一个问题,模型会返回一个答案。

我们的目标是让模型针对给定问题返回正确的答案。BERT模型的输入是一个问题和一个段落,也就是说,需要向BERT输入一个问题和一个含有答案的段落。BERT必须从该段落中提取答案。因此,从本质上讲,BERT必须返回包含答案的文本段。让我们通过下面这个问题−段落对示例来理解。

  • 问题:“什么是免疫系统?”
  • 段落:“免疫系统是一个由生物体内许多生物结构和过程组成的系统,它能保护人们免受疾病的侵害。为了正常运作,免疫系统必须检测各种各样的制剂,即所谓的病原体,从病毒到寄生虫,并将它们与有机体自身的健康组织区分开来。”

BERT必须从该段落中提取出一个答案,也就是包含答案的文本段。因此,它应该返回如下信息。

答案:“一个由生物体内许多生物结构和过程组成的系统,它能保护人们免受疾病的侵害。”

如何微调BERT模型来完成这项任务?要做到这一点,模型必须了解给定段落中包含答案的文本段的起始索引和结束索引。以“什么是免疫系统”这个问题为例,如果模型理解这个问题的答案是从索引5(“一”)开始,在索引39(“害”)结束,那么可以得到如下答案。

段落:“免疫系统是一个由生物体内许多生物结构和过程组成的系统,它能保护人们免受疾病的侵害。为了正常运作,免疫系统必须检测各种各样的制剂,即所谓的病原体,从病毒到寄生虫,并将它们与有机体自身的健康组织区分开来。”

如何找到包含答案的文本段的起始索引和结束索引呢?我们如果能够得到该段落中每个标记是答案的起始标记和结束标记的概率,那么就可以很容易地提取答案。但如何才能实现这一点?这里需要引入两个向量,称为起始向量S和结束向量E。起始向量和结束向量的值将通过训练习得。

首先,计算该段落中每个标记是答案的起始标记的概率。

为了计算这个概率,对于每个标记 i i i,计算标记特征 R i R_i Ri和起始向量S之间的点积。然后,将softmax函数应用于点积 S ⋅ R i S·R_i SRi,得到概率。计算公式如下所示。

p i = e S ⋅ R i ∑ j e S ⋅ R i p_i = \frac{e^{S·R_i}}{\sum_je^{S·R_i}} pi=jeSRieSRi

接下来,选择其中具有最高概率的标记,并将其索引值作为起始索引。

以同样的方式,计算该段落中每个标记是答案的结束标记的概率。为了计算这个概率,为每个标记 i i i计算标记特征 R i R_i Ri和结束向量E之间的点积。然后,将softmax函数应用于点积 E ⋅ R i E·R_i ERi,得到概率。计算公式如下所示。

p i = e E ⋅ R i ∑ j e E ⋅ R i p_i = \frac{e^{E·R_i}}{\sum_je^{E·R_i}} pi=jeERieERi

接着,选择其中具有最高概率的标记,并将其索引值作为结束索引。现在,我们就可以使用起始索引和结束索引选择包含答案的文本段了。

如图所示,首先对问题−段落对进行标记,然后将这些标记送入预训练的BERT模型。该模型返回所有标记的嵌入(标记特征)。 R i R_i Ri是问题中的标记的嵌入, R i 、 R_i^、 Ri是段落中的标记的嵌入。

在这里插入图片描述
在得到嵌入后,开始分别计算嵌入与起始向量和结束向量的点积,并使用softmax函数获得段落中每个标记是起始词和结束词的概率。

上图展现了如何计算段落中每个标记是起始词和结束词的概率。下面,我们使用概率最高的起始索引和结束索引来选择包含答案的文本段。为了更好地理解这一点,下面我们将学习如何在问答任务中使用微调后的BERT模型。

3.1 用微调后的BERT模型执行问答任务

首先,导入必要的库模块。

from transformers import BertForQuestionAnswering, BertTokenizer

然后,下载并加载该模型。我们使用的是bert-large-uncased-whole-word-masking-fine-tuned-squad模型,该模型基于斯坦福问答数据集(SQuAD)微调而得。

model = BertForQuestionAnswering.from_pretrained('bert-large-uncased-whole-word-masking-fine-tuned-squad')

接下来,下载并加载词元分析器。

tokenizer = BertTokenizer.from_pretrained('bert-large-uncased-whole-word-masking-fine-tuned-squad')

3.2 对模型输入进行预处理

首先,定义BERT的输入。输入的问题和段落文本如下所示。

question = "What is the immune system?"
paragraph = "The immune system is a system of many biological structures
and processes within an organism that protects against disease. To function
properly, an immune system must detect a wide variety of agents, known as
pathogens, from viruses to parasitic worms, and distinguish them from the
organism's own healthy tissue."

接着,在问题的开头添加[CLS]标记,在问题和段落的结尾添加[SEP]标记。

question = '[CLS]' + question + '[SEP]'
paragraph = paragraph + '[SEP]'

然后,标记问题和段落。

question_tokens = tokenizer.tokenize(question)
paragraph_tokens = tokenizer.tokenize(paragraph)

合并问题标记和段落标记,并将其转换为input_ids。

tokens = question_tokens + paragraph_tokens
input_ids = tokenizer.convert_tokens_to_ids(tokens)

设置segment_ids。对于问题的所有标记,将segment_ids设置为0;对于段落的所有标记,将segment_ids设置为1。

segment_ids = [0] * len(question_tokens)
segment_ids = [1] * len(paragraph_tokens)

把input_ids和segment_ids转换成张量。

input_ids = torch.tensor([input_ids])
segment_ids = torch.tensor([segment_ids])

我们已经处理了输入,现在将它送入模型以获得答案。

3.3 获得答案

把input_ids和segment_ids送入模型。模型将返回所有标记的起始分数和结束分数。

start_scores, end_scores = model(input_ids, token_type_ids = segment_ids)

这时,我们需要选择start_index和end_index,前者是具有最高起始分数的标记的索引,后者是具有最高结束分数的标记的索引。

start_index = torch.argmax(start_scores)
end_index = torch.argmax(end_scores)

然后,打印起始索引和结束索引之间的文本段作为答案。

print(' '.join(tokens[start_index:end_index+1]))

以上代码的输出如下。

a system of many biological structures and processes within an organism
that protects against disease

4、 命名实体识别任务

在命名实体识别任务中,我们的目标是将命名实体划分到预设的类别中。例如在句子Jeremy lives in Paris中,Jeremy应被归类为人,而Paris应被归类为地点。

现在,让我们看看如何微调预训练的BERT模型以执行命名实体识别任务。首先,对句子进行标记,在句首添加[CLS]标记,在句尾添加[SEP]标记。然后,将这些标记送入预训练的BERT模型,获得每个标记的特征。接下来,将这些标记特征送入一个分类器(使用softmax激活函数的前馈网络层)。最后,分类器返回每个命名实体所对应的类别,如图所示。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于单片机的太阳能充电系统设计

摘要:本文所设计的太阳能充电系统主要由以下几个模块组成:STC89C52 主控模块、TP4056 充电电路、电压AD 采集模块、LCD1602 液晶显示模块和太阳能充电电池等组成。此太阳能充电器制作简单,性价比高,性能稳定。 关键词:LCD1602;太阳能充电系统;ADC0832 太阳能充电系统的充…

【C++】基础:STL容器库

😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍STL容器库。 学其所用,用其所学。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下次更新不迷路&#x1f95…

vue动态渲染本地路径图片不显示的解决方案,v-fro 渲染本地图片路径不显示

1、第一种解决方法 如果直接使用本地路径渲染是渲染不出来的&#xff0c;因为这种情况下渲染时会发送网络请求加你的本地地址所以渲染不出来。 这样怎么能找到路径&#xff1f;解决方案如下 // 渲染正常渲染即可 <div v-for"(item, index) in imgPath" :key&quo…

【WEEK4】 【DAY4】AJAX第一部分【中文版】

2024.3.21 Thursday 目录 8.AJAX8.1.简介8.2.伪造ajax8.2.1.新建module&#xff1a;springmvc-06-ajax8.2.2.添加web支持&#xff0c;导入pom依赖8.2.2.1.修改web.xml8.2.2.2.新建jsp文件夹 8.2.3.新建applicationContext.xml8.2.4.新建AjaxController.java8.2.5.配置Tomcat8.…

【MATLAB源码-第9期】基于matlab的DQPSK的误码率BER和误符号率SER仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 DQPSK信号的解调与2DPSK信号的解调类似&#xff0c;也有两种方法&#xff0c;分别是极性比较法和相位比较法 极性比较法。其原理方框图如下图所示。由于DQPSK信号可以看做是两路2DPSK信号的合成&#xff0c;解 调时也可以分别…

Python环境下5种TE过程(Tennessee Eastman Process)故障检测方法

田纳西-伊斯特曼过程&#xff0c;也被称作TE过程&#xff0c;主要模拟美国田纳西州一家名为伊斯曼的化工公司的化工过程。TE过程是一个高度复杂、非线性和多变量的过程&#xff0c;涉及到多个阶段和多个控制参数&#xff0c;其中某些参数会相互影响&#xff0c;因此&#xff0c…

读研究生的时候早恋被导师发现了怎么办?

研究生早恋这事&#xff0c;危险重重&#xff0c;比走钢丝还紧张&#xff0c;这根本就是走在科研生涯的悬崖边上&#xff0c;随时都有掉下去的危险。 让我们来详细分析一下为什么你应该立刻收心&#xff0c;专注于你的实验和论文&#xff0c;而不是浪费时间在所谓的“早恋”上。…

[2023] 14届

1.日期统计 题意 1.日期统计 - 蓝桥云课 (lanqiao.cn) 思路 用dfs扫 对每一个位进行限制 花了一个小时 注意把答案枚举出来 对应一下看到底对不对 code #include<iostream> #include<cstdio> #include<stack> #include<vector> #include<al…

电脑桌面记事本便签软件,记事本软件哪个好用

正在电脑前忙碌工作&#xff0c;突然想起今晚有个重要的会议&#xff0c;或者是明天有一个重要的任务需要完成&#xff0c;但是手头的工作又无法让你离开电脑&#xff0c;这时候&#xff0c;你多么希望有一个便捷的电脑桌面记事本便签软件&#xff0c;可以让你快速记录下这些重…

Python时间

UTC ~ 北京时间 【差8小时】 格式化日期时间为字符串:strftime 时间戳-1970.1.1到现在的秒数:time.time() AttributeError: partially initialized module ‘datetime’ has no attribute ‘fromtimestamp’ (most likely due to a circular import) 改正&#xff1a;文件名和…

【C++】隐藏的this指针

文章目录 1.this指针的引出2.this指针的特性 1.this指针的引出 我们通过日期类来学习this指针&#xff0c;首先我们先定义一个日期类。 class Date { public:void Display(){cout << _year << "-" << _month << "-" << _d…

1688商品API接口数据采集助力电商ERP

1688商品API接口数据采集可以帮助电商供应链ERP系统搭建&#xff0c;提供所需的商品信息和数据。 通过使用1688商品API接口&#xff0c;可以直接从1688网站上抓取商品信息&#xff0c;包括商品名称、价格、库存、属性等。这样&#xff0c;可以省去手动复制粘贴的步骤&#xff…

矩阵的归一化技术

矩阵的归一化&#xff08;Normalization&#xff09;是将矩阵中的元素缩放到一个特定的范围或者标准&#xff0c;使得在进行比较、评估或计算时能够保持数值稳定性和可比性。这个过程在数据预处理、机器学习、图像处理等领域中非常重要。归一化有助于改善算法的收敛速度和精度&…

elasticsearch 教程(一)程序建立索引

elasticsearch 教程&#xff08;一&#xff09;程序建立索引 从elasticsearch 8.x开始&#xff0c;除了通过kibana建立索引之外&#xff0c;还可以在Java程序定义索引。待程序运行时&#xff0c;会先检测是否建立索引&#xff0c;如果已建立索引&#xff0c;即使程序中定义的索…

百能云板开启1-6层陶瓷pcb板定制服务

普通PCB通常是由铜箔和基板粘合而成&#xff0c;而基板材质大多数为玻璃纤维&#xff08;FR-4&#xff09;&#xff0c;酚醛树脂&#xff08;FR-3&#xff09;等材质&#xff0c;粘合剂通常是酚醛、环氧等。在PCB加工过程中由于热应力、化学因素、生产工艺不当等原因&#xff0…

哈希冲突解决的几种方式

目录 哈希冲突 哈希冲突-避免方式1-哈希函数的设计 1. 直接定制法--(常用) 2. 除留余数法--(常用) 3. 平方取中法--(了解) 哈希冲突-避免方式2-负载因子调节 哈希冲突-解决方式1-闭散列 1.线性探测 2.二次探测 哈希冲突-解决方式2-开散列(哈希桶) 哈希冲突 在上文中…

【Java程序设计】【C00383】基于(JavaWeb)Springboot的水产养殖系统(有论文)

【C00383】基于&#xff08;JavaWeb&#xff09;Springboot的水产养殖系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#xff0c…

SketchUp草图大师模型网:哪家更值得信赖?

草图大师模型网是一个提供模型下载和分享的平台&#xff0c;用户可以在上面找到大量的SU模型&#xff0c;并学习一些草图大师的使用技巧。那么&#xff0c;SketchUp草图大师模型网哪家更值得信赖呢?下面将从多个角度进行比较和分析。 首先&#xff0c;我们要看看草图大师模型网…

python关于字符串基础学习

字符串 python字符串是不可改变的 Python不支持单字符类型&#xff0c;单字符也是作为一个字符串使用的。 字符串编码 python3直接支持Unicode,可以表示世界上任何书面语言的字符 python3的字符默认就是16位Unicode编码&#xff0c;ASCII是Unicode的子集 使用内置函数 ord()…

2.4 如何运行Python程序

如何运行Python程序&#xff1f; Python是一种解释型的脚本编程语言&#xff0c;这样的编程语言一般支持两种代码运行方式&#xff1a; 1) 交互式编程 在命令行窗口中直接输入代码&#xff0c;按下回车键就可以运行代码&#xff0c;并立即看到输出结果&#xff1b;执行完一行…
最新文章