AI大模型探索之路-训练篇9:大语言模型Transformer库-Pipeline组件实践

发布于:2024-05-05 ⋅ 阅读:(38) ⋅ 点赞:(0)

系列篇章💥

AI大模型探索之路-训练篇1:大语言模型微调基础认知
AI大模型探索之路-训练篇2:大语言模型预训练基础认知
AI大模型探索之路-训练篇3:大语言模型全景解读
AI大模型探索之路-训练篇4:大语言模型训练数据集概览
AI大模型探索之路-训练篇5:大语言模型预训练数据准备-词元化
AI大模型探索之路-训练篇6:大语言模型预训练数据准备-预处理
AI大模型探索之路-训练篇7:大语言模型Transformer库之HuggingFace介绍
AI大模型探索之路-训练篇8:大语言模型Transformer库-预训练流程编码体验



一、Pipeline的简介

在人工智能和机器学习领域,Pipeline是一种设计模式,它将多个处理步骤串联起来,形成一个有序的、自动化的工作流程。这一概念在自然语言处理(NLP)尤其重要,因为NLP任务通常涉及多个阶段,如文本清洗、特征提取、模型训练或预测等。Pipeline的设计旨在减少重复代码、提高代码的可维护性,并优化整个处理流程的效率。
官网API地址:https://huggingface.co/docs/transformers/main_classes/pipelines

二、Pipeline支持的任务类型

Pipeline能够支持多种机器学习任务,包括但不限于:
1)文本分类:将文本分配到预定义的类别。
2)情感分析:判断文本表达的情绪倾向。
3)机器翻译:将一种语言的文本转换为另一种语言。
4)问答系统:针对问题提供准确答案。
5)文本摘要:生成文本的简短摘要。
6)语言模型生成:基于语言模型生成新的文本内容。
在Python中,可以通过以下方式查询transformers库支持的所有任务类型:

from transformers.pipelines import SUPPORTED_TASKS

print(SUPPORTED_TASKS.items())

dict_items([('audio-classification', {'impl': <class 'transformers.pipelines.audio_classification.AudioClassificationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForAudioClassification'>,), 'default': {'model': {'pt': ('superb/wav2vec2-base-superb-ks', '372e048')}}, 'type': 'audio'}), ('automatic-speech-recognition', {'impl': <class 'transformers.pipelines.automatic_speech_recognition.AutomaticSpeechRecognitionPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForCTC'>, <class 'transformers.models.auto.modeling_auto.AutoModelForSpeechSeq2Seq'>), 'default': {'model': {'pt': ('facebook/wav2vec2-base-960h', '55bb623')}}, 'type': 'multimodal'}), ('text-to-audio', {'impl': <class 'transformers.pipelines.text_to_audio.TextToAudioPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForTextToWaveform'>, <class 'transformers.models.auto.modeling_auto.AutoModelForTextToSpectrogram'>), 'default': {'model': {'pt': ('suno/bark-small', '645cfba')}}, 'type': 'text'}), ('feature-extraction', {'impl': <class 'transformers.pipelines.feature_extraction.FeatureExtractionPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModel'>,), 'default': {'model': {'pt': ('distilbert-base-cased', '935ac13'), 'tf': ('distilbert-base-cased', '935ac13')}}, 'type': 'multimodal'}), ('text-classification', {'impl': <class 'transformers.pipelines.text_classification.TextClassificationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForSequenceClassification'>,), 'default': {'model': {'pt': ('distilbert-base-uncased-finetuned-sst-2-english', 'af0f99b'), 'tf': ('distilbert-base-uncased-finetuned-sst-2-english', 'af0f99b')}}, 'type': 'text'}), ('token-classification', {'impl': <class 'transformers.pipelines.token_classification.TokenClassificationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForTokenClassification'>,), 'default': {'model': {'pt': ('dbmdz/bert-large-cased-finetuned-conll03-english', 'f2482bf'), 'tf': ('dbmdz/bert-large-cased-finetuned-conll03-english', 'f2482bf')}}, 'type': 'text'}), ('question-answering', {'impl': <class 'transformers.pipelines.question_answering.QuestionAnsweringPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForQuestionAnswering'>,), 'default': {'model': {'pt': ('distilbert-base-cased-distilled-squad', '626af31'), 'tf': ('distilbert-base-cased-distilled-squad', '626af31')}}, 'type': 'text'}), ('table-question-answering', {'impl': <class 'transformers.pipelines.table_question_answering.TableQuestionAnsweringPipeline'>, 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForTableQuestionAnswering'>,), 'tf': (), 'default': {'model': {'pt': ('google/tapas-base-finetuned-wtq', '69ceee2'), 'tf': ('google/tapas-base-finetuned-wtq', '69ceee2')}}, 'type': 'text'}), ('visual-question-answering', {'impl': <class 'transformers.pipelines.visual_question_answering.VisualQuestionAnsweringPipeline'>, 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForVisualQuestionAnswering'>,), 'tf': (), 'default': {'model': {'pt': ('dandelin/vilt-b32-finetuned-vqa', '4355f59')}}, 'type': 'multimodal'}), ('document-question-answering', {'impl': <class 'transformers.pipelines.document_question_answering.DocumentQuestionAnsweringPipeline'>, 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForDocumentQuestionAnswering'>,), 'tf': (), 'default': {'model': {'pt': ('impira/layoutlm-document-qa', '52e01b3')}}, 'type': 'multimodal'}), ('fill-mask', {'impl': <class 'transformers.pipelines.fill_mask.FillMaskPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForMaskedLM'>,), 'default': {'model': {'pt': ('distilroberta-base', 'ec58a5b'), 'tf': ('distilroberta-base', 'ec58a5b')}}, 'type': 'text'}), ('summarization', {'impl': <class 'transformers.pipelines.text2text_generation.SummarizationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForSeq2SeqLM'>,), 'default': {'model': {'pt': ('sshleifer/distilbart-cnn-12-6', 'a4f8f3e'), 'tf': ('t5-small', 'd769bba')}}, 'type': 'text'}), ('translation', {'impl': <class 'transformers.pipelines.text2text_generation.TranslationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForSeq2SeqLM'>,), 'default': {('en', 'fr'): {'model': {'pt': ('t5-base', '686f1db'), 'tf': ('t5-base', '686f1db')}}, ('en', 'de'): {'model': {'pt': ('t5-base', '686f1db'), 'tf': ('t5-base', '686f1db')}}, ('en', 'ro'): {'model': {'pt': ('t5-base', '686f1db'), 'tf': ('t5-base', '686f1db')}}}, 'type': 'text'}), ('text2text-generation', {'impl': <class 'transformers.pipelines.text2text_generation.Text2TextGenerationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForSeq2SeqLM'>,), 'default': {'model': {'pt': ('t5-base', '686f1db'), 'tf': ('t5-base', '686f1db')}}, 'type': 'text'}), ('text-generation', {'impl': <class 'transformers.pipelines.text_generation.TextGenerationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForCausalLM'>,), 'default': {'model': {'pt': ('gpt2', '6c0e608'), 'tf': ('gpt2', '6c0e608')}}, 'type': 'text'}), ('zero-shot-classification', {'impl': <class 'transformers.pipelines.zero_shot_classification.ZeroShotClassificationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForSequenceClassification'>,), 'default': {'model': {'pt': ('facebook/bart-large-mnli', 'c626438'), 'tf': ('roberta-large-mnli', '130fb28')}, 'config': {'pt': ('facebook/bart-large-mnli', 'c626438'), 'tf': ('roberta-large-mnli', '130fb28')}}, 'type': 'text'}), ('zero-shot-image-classification', {'impl': <class 'transformers.pipelines.zero_shot_image_classification.ZeroShotImageClassificationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForZeroShotImageClassification'>,), 'default': {'model': {'pt': ('openai/clip-vit-base-patch32', 'f4881ba'), 'tf': ('openai/clip-vit-base-patch32', 'f4881ba')}}, 'type': 'multimodal'}), ('zero-shot-audio-classification', {'impl': <class 'transformers.pipelines.zero_shot_audio_classification.ZeroShotAudioClassificationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModel'>,), 'default': {'model': {'pt': ('laion/clap-htsat-fused', '973b6e5')}}, 'type': 'multimodal'}), ('conversational', {'impl': <class 'transformers.pipelines.conversational.ConversationalPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForSeq2SeqLM'>, <class 'transformers.models.auto.modeling_auto.AutoModelForCausalLM'>), 'default': {'model': {'pt': ('microsoft/DialoGPT-medium', '8bada3b'), 'tf': ('microsoft/DialoGPT-medium', '8bada3b')}}, 'type': 'text'}), ('image-classification', {'impl': <class 'transformers.pipelines.image_classification.ImageClassificationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForImageClassification'>,), 'default': {'model': {'pt': ('google/vit-base-patch16-224', '5dca96d'), 'tf': ('google/vit-base-patch16-224', '5dca96d')}}, 'type': 'image'}), ('image-segmentation', {'impl': <class 'transformers.pipelines.image_segmentation.ImageSegmentationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForImageSegmentation'>, <class 'transformers.models.auto.modeling_auto.AutoModelForSemanticSegmentation'>), 'default': {'model': {'pt': ('facebook/detr-resnet-50-panoptic', 'fc15262')}}, 'type': 'multimodal'}), ('image-to-text', {'impl': <class 'transformers.pipelines.image_to_text.ImageToTextPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForVision2Seq'>,), 'default': {'model': {'pt': ('ydshieh/vit-gpt2-coco-en', '65636df'), 'tf': ('ydshieh/vit-gpt2-coco-en', '65636df')}}, 'type': 'multimodal'}), ('object-detection', {'impl': <class 'transformers.pipelines.object_detection.ObjectDetectionPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForObjectDetection'>,), 'default': {'model': {'pt': ('facebook/detr-resnet-50', '2729413')}}, 'type': 'multimodal'}), ('zero-shot-object-detection', {'impl': <class 'transformers.pipelines.zero_shot_object_detection.ZeroShotObjectDetectionPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForZeroShotObjectDetection'>,), 'default': {'model': {'pt': ('google/owlvit-base-patch32', '17740e1')}}, 'type': 'multimodal'}), ('depth-estimation', {'impl': <class 'transformers.pipelines.depth_estimation.DepthEstimationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForDepthEstimation'>,), 'default': {'model': {'pt': ('Intel/dpt-large', 'e93beec')}}, 'type': 'image'}), ('video-classification', {'impl': <class 'transformers.pipelines.video_classification.VideoClassificationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForVideoClassification'>,), 'default': {'model': {'pt': ('MCG-NJU/videomae-base-finetuned-kinetics', '4800870')}}, 'type': 'video'}), ('mask-generation', {'impl': <class 'transformers.pipelines.mask_generation.MaskGenerationPipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForMaskGeneration'>,), 'default': {'model': {'pt': ('facebook/sam-vit-huge', '997b15')}}, 'type': 'multimodal'}), ('image-to-image', {'impl': <class 'transformers.pipelines.image_to_image.ImageToImagePipeline'>, 'tf': (), 'pt': (<class 'transformers.models.auto.modeling_auto.AutoModelForImageToImage'>,), 'default': {'model': {'pt': ('caidas/swin2SR-classical-sr-x2-64', '4aaedcb')}}, 'type': 'image'})])

三、Pipeline的使用

使用Pipeline通常涉及以下几个步骤:
1)定义步骤:明确每个处理步骤和它们之间的顺序。
2)配置组件:为每个步骤选择合适的模型或算法,并配置必要的参数。
3)组装Pipeline:将各个步骤按照既定的顺序组装成一个完整的Pipeline。
4)训练模型:如果Pipeline中包含模型训练的步骤,需要提供训练数据并执行训练。
5)执行Pipeline:输入待处理的数据,Pipeline将自动按顺序执行每个步骤。
6)评估和调整:根据Pipeline的输出结果进行评估,并根据需要调整步骤或参数。

1、Pipeline的创建与使用方式

使用Hugging Face的transformers库中的pipeline功能来创建一个文本分类模型的接口,并对其进行使用

##导入pipeline
from transformers import pipeline
## 指定任务类型,再指定模型,创建基于指定模型的Pipeline
# https://huggingface.co/models
# roberta-base-finetuned-dianping-chinese:是专门为中文评论情感分析预训练和微调过的模型。
pipe = pipeline("text-classification", model="uer/roberta-base-finetuned-dianping-chinese")
pipe("我觉得不太行!")

输出

[{'label': 'negative (stars 1, 2 and 3)', 'score': 0.9735506772994995}]

代码中模型将输入的句子分类为负面情感,并且给出了相应的置信度(score)。在这个例子中,模型非常有信心(置信度约为0.974)认为这条评论是负面的,对应的是1星到3星的评价。

2、预先加载模型,再创建Pipeline

from transformers import AutoTokenizer, AutoModelForSequenceClassification

# 这种方式,必须同时指定model和tokenizer
model = AutoModelForSequenceClassification.from_pretrained("uer/roberta-base-finetuned-dianping-chinese")
tokenizer = AutoTokenizer.from_pretrained("uer/roberta-base-finetuned-dianping-chinese")
pipe = pipeline("text-classification", model=model, tokenizer=tokenizer)
# 测试
pipe("今天天气很晴朗,处处好风光!")

输出

[{'label': 'positive (stars 4 and 5)', 'score': 0.9193938374519348}]

代码中模型对输入句子的分类结果。模型预测该句子是正面情感的,并且给出了一个高分(约0.919),这表明模型对于这个预测非常有信心。标签’positive (stars 4 and 5)'意味着根据模型的判断,这条评论相当于4星或5星的评价。

3、CPU和GPU推理对比

我们可以对比看一下CPU和GPU推理的效率。
1)使用CPU推理
也可以在CPU上进行推理训练,默认就是使用CPU

pipe.model.device
#输出,默认使用CPU  device(type='cpu')
import torch
import time
times = []
for i in range(100):
    torch.cuda.synchronize()
    start = time.time()
    pipe("今天天气很晴朗,处处好风光!")
    torch.cuda.synchronize()
    end = time.time()
    times.append(end - start)
print(sum(times) / 100)

输出耗时
0.015748867988586424

2)使用GPU进行推理
我们可以对比发现,在GPU下进行推理效率会比CPU 高很多

pipe = pipeline("text-classification", model="uer/roberta-base-finetuned-dianping-chinese", device=0)
pipe.model.device

输出
device(type=‘cuda’, index=0)

import torch
import time
times = []
for i in range(100):
    torch.cuda.synchronize()
    start = time.time()
    pipe("我觉得不太行!")
    torch.cuda.synchronize()
    end = time.time()
    times.append(end - start)
print(sum(times) / 100)

输出耗时
0.0058620524406433104

4、确定Pipeline参数的使用

使用了Hugging Face的transformers库中的pipeline功能来创建一个问答系统(Question Answering, QA)的接口,并对其进行使用。

#创建了一个专门用于问答任务的Pipeline对象qa_pipe。问答任务是指系统根据给定的问题和上下文,找出并返回答案的任务。
#"question-answering":指定了要执行的任务类型为问答。
#model="uer/roberta-base-chinese-extractive-qa":指定了使用的模型,这个模型是专门为中文的抽取式问答(extractive QA)任务预训练过的模型。
qa_pipe = pipeline("question-answering", model="uer/roberta-base-chinese-extractive-qa")
qa_pipe

输出,查看pipe的类型
<transformers.pipelines.question_answering.QuestionAnsweringPipeline at 0x1f1eb647880>

效果测试

# question="云南的省会是哪里?":提供了要回答的问题。
# context="云南的省会是昆明":提供了回答问题所需的上下文信息。
# max_answer_len=2:指定了答案的最大长度。在这个例子中,设置为2意味着模型将尝试返回长度为2的最短可能答案。
qa_pipe(question="云南的省会是哪里?", context="云南的省会是昆明", max_answer_len=2)

输出:
{‘score’: 0.876051127910614, ‘start’: 6, ‘end’: 8, ‘answer’: ‘昆明’}

结果参数说明:
‘score’:模型对找到的答案的置信度分数。在这个例子中,分数比较高,表明模型对这个答案是很有信心。
‘start’:答案在给定上下文中的起始索引。
‘end’:答案在给定上下文中的结束索引。
‘answer’:模型找到的答案,这里是根据索引从上下文中提取的文本片段。

四、Pipeline背后的工作

Pipeline背后自动化了从数据预处理到模型预测的整个流程,减少了很多手动操作。比如:
1)分词处理、生成模型输入
2)将模型输入给到模型,进行模型推理
3)归一化:获取原始分数、再归一化处理
4)后处理:将模型输出转换为可读的格式。

使用一个预训练的文本分类模型来对给定的文本进行分类,并输出预测的标签

1、导入依赖,创建模型

#这行代码尝试从transformers库中导入所有公共对象。通常,这种导入方式不推荐使用,因为它可能会导入大量不相关的对象,造成命名空间的混乱。
from transformers import *
import torch # PyTorch是一个开源的机器学习库,广泛用于深度学习。

# 加载与预训练模型相对应的分词器
tokenizer = AutoTokenizer.from_pretrained("uer/roberta-base-finetuned-dianping-chinese")
# 加载预训练的文本分类模型。
model = AutoModelForSequenceClassification.from_pretrained("uer/roberta-base-finetuned-dianping-chinese")

2、分词处理、生成模型输入

#定义了要进行分类的输入文本
input_text = "今天天气好晴朗,处处好风光!"
#使用分词器将输入文本转换为模型所需的格式,并指定返回的张量类型为PyTorch张量("pt"代表PyTorch)
#这里的inputs变量将包含分词器处理后的结果,它是一个字典,包含分词器生成的编码张量、注意力掩码(attention mask)等信息
inputs = tokenizer(input_text, return_tensors="pt")
# 打印输出inputs变量,
Inputs

输出结果

{'input_ids': tensor([[ 101,  791, 1921, 1921, 3698, 1962, 3252, 3306, 8024, 1905, 1905, 1962,
         7599, 1045, 8013,  102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}

3、模型推理

#将 **inputs(即经过分词器处理的输入文本)作为参数传递给模型进行推理。

res = model(**inputs)
#打印结果
Res

SequenceClassifierOutput(loss=None, logits=tensor([[-1.0786, 0.9897]], grad_fn=), hidden_states=None, attentions=None)

4、获取原始分数、再归一化处理

##获取了模型的输出logits。Logits是模型在每个可能的类别上的得分或概率,还没有经过概率归一化处理。
logits = res.logits
## 使用PyTorch中的torch.softmax函数对logits进行softmax操作。softmax函数将每个logit值转换为在[0, 1]范围内的概率值,使得所有类别的概率之和为1
## dim=-1表示在最后一个维度上进行softmax操作。在情感分类任务中,通常最后一个维度是类别维度
logits = torch.softmax(logits, dim=-1)
Logits

5、查看概率最高的类别索引

#使用argmax找到logits中概率最高的类别索引,并通过.item()将其转换为一个普通的Python整数。
pred = torch.argmax(logits).item()
#打印结果
Pred

输出: 1

6、查看标签映射

#访问模型配置中的id2label映射,这是一个从索引到标签的映射
model.config.id2label	
#输出如下
{0: 'negative (stars 1, 2 and 3)', 1: 'positive (stars 4 and 5)'}

索引0对应标签’negative (stars 1, 2 and 3)‘,表示负面情感或低星级评价。
索引1对应标签’positive (stars 4 and 5)’,表示正面情感或高星级评价。

7、根据我们得到的索引类别,从标签映射中取出结果

#使用预测得到的类别索引pred从id2label映射中获取对应的标签。
result = model.config.id2label.get(pred)
#打印标签结果
result

输出如下:
‘positive (stars 4 and 5)’
这意味着模型预测输入文本表达的是正面情感,并且根据标签的含义,这相当于4星或5星的高分评价

8、Pipeline使用完整代码样例如下

from transformers import AutoTokenizer, AutoModelForSequenceClassification

# 加载预训练的Tokenizer和Model
tokenizer = AutoTokenizer.from_pretrained("uer/roberta-base-finetuned-dianping-chinese")
model = AutoModelForSequenceClassification.from_pretrained("uer/roberta-base-finetuned-dianping-chinese")

# 对输入文本进行编码
encoded_input = tokenizer("今天天气好晴朗,处处好风光!", return_tensors="pt")

# 模型推理
res = model(**encoded_input)

# 获取并处理logits
logits = res.logits
probabilities = torch.nn.functional.softmax(logits, dim=-1)

# 获取最可能的类别
pred = torch.argmax(probabilities).item()

五、使用Pipeline优化

Pipeline可以极大地简化模型预测的代码。以下是使用Pipeline重写先前示例的优化版本:

from transformers import pipeline

# 创建Pipeline,指定任务类型、模型和分词器
pipe = pipeline("text-classification", model=model, tokenizer=tokenizer, device=0)

# 执行预测
optimized_result = pipe("今天天气好晴朗,处处好风光!")
print(optimized_result)

接下来我们采用Pipeline对我们前面篇章《AI大模型探索之路-训练篇8:大语言模型Transformer库-预训练流程编码体验》中的样例进行优化;主要优化改造“步骤## 8:模型预测”中的相关代码

from transformers import pipeline
#model.config.id2label = id2label
pipe = pipeline("text-classification", model=model, tokenizer=tokenizer, device=0)
#调用执行
pipe(sen)	
#输出
[{'label': 'LABEL_1', 'score': 0.9772003293037415}]

结果会发现,在pipe的使用下,我们只需要一两行代码就搞定了,非常的丝滑。

总结

Pipeline是Hugging Face transformers库中一个功能强大的工具,它允许研究人员和开发者以一种简洁、高效的方式处理复杂的NLP任务。通过自动化常用的NLP流程,Pipeline不仅提高了性能,还使得代码更加易于编写和维护。正确地选择和使用Pipeline对于任何希望利用Transformer模型的NLP项目来说都是至关重要的。

在这里插入图片描述

🎯🔖更多专栏系列文章:AIGC-AI大模型探索之路

如果文章内容对您有所触动,别忘了点赞、⭐关注,收藏!加入我,让我们携手同行AI的探索之旅,一起开启智能时代的大门!


网站公告

今日签到

点亮在社区的每一天
去签到