PyTorch语法

1.张量的创建

import torch
a=[1,2,3.]
print(type(a))

b=torch.tensor(a)
print(b)
print(type(b))
print(b.dtype)

import numpy as np
c=np.random.normal((2,3))
d=torch.tensor(c)
print(d)

e=torch.ones_like(d)
print(e)
f=torch.zeros_like(d)
print(f)
g=torch.rand_like(d)
print(g)

print(torch.rand((2,2)))
print(torch.randn([2,2]))
print(torch.rand([2,2,]).dtype)

h=torch.rand([2,2,])
print(h.dtype)
print(h.shape)
print(h.device)
print(torch.is_tensor(h))

i=torch.tensor(0)
print(torch.is_nonzero(i))
print('-'*34)
print(torch.numel(h))
print(torch.zeros([5,5]))
a=torch.zeros([5,5],dtype=torch.int32)
print(a)
print(torch.zeros([5,5]).dtype)
print(torch.ones_like(a))
print('-'*34)
print(torch.arange(5))
print(torch.arange(0,5,2))
print(torch.range(0,5))
print(torch.range(0,2).dtype)
print(torch.arange(0,5).dtype)
for i in torch.arange(5):
    print('epoch:',i)
print(torch.eye(3))
print(torch.ones_like(a)*5)
print(torch.full([2,2],2))
print(torch.full_like(a,2))

a=torch.rand([3,2,])
print(a)
b=torch.rand([3,2,])
print(b)
print(torch.cat([a,b],dim=0))

2.张量的运算API(1)

import torch
b=torch.rand([3,2])
print(b)
c,d=torch.chunk(b,chunks=2,dim=1)
print(c)
print(d)
print(torch.reshape(torch.reshape(b,[2,3]),[-1]))
print('-'*34)
src = torch.tensor([[1,2],
                    [3,4],
                    [5,6]])

index = torch.tensor([[0,2],
                      [1,0],
                      [2,1]])

'''out = torch.zeros_like(src)
out.scatter_(dim=0, index=index, src=src)
#src [i][j] 这个数,要搬到 out 的 第 index [i][j] 行、第 j 列
print(out)
'''
a=torch.arange(10).reshape(5,2)
#print(a)
#print(torch.split(a,[1,4]))

'''
print(a.shape)
print(torch.squeeze(torch.reshape(a,[1,1,5,2]),dim=0).shape)
'''
b=torch.rand(5,2)
print(torch.stack([a,b],dim=0).shape)    #torch.Size([2, 5, 2])
print(torch.stack([a,b],dim=1).shape)    #torch.Size([5, 2, 2])
print(torch.cat([a,b],dim=0).shape)      #torch.Size([10, 2])
print(torch.cat([a,b],dim=1).shape)     #torch.Size([5, 4])

3.张量的运算API(2)

import torch
a=torch.rand([3,2])
print(a)
#print(torch.take(a,torch.tensor([0,2,4])))
'''
print(torch.tile(a,dims=[1,2]))
print(torch.tile(a,dims=[2,1]))
'''
'''
print(torch.transpose(a,0,1))
print('-'*34)
print(torch.unbind(a,dim=0))
print(torch.unbind(a,dim=1))
print('-'*34)
print(torch.unsqueeze(a,dim=0).shape)
print(torch.unsqueeze(a,dim=1).shape)
print(torch.unsqueeze(a,dim=-1).shape)
'''
b=torch.zeros_like(a)
print(torch.where(a>0.5,a,b))

4.dataset的基本代码实现

from torch.utils.data import Dataset
from PIL import Image
import os
class MyDataset(Dataset):
    def __init__(self,root_dir,label_dir):
        self.root_dir=root_dir
        self.label_dir=label_dir
        self.path=os.path.join(self.root_dir,self.label_dir)
        self.img_path=os.listdir(self.path)


    def __getitem__(self,index):
       img_name=self.img_path[index]
       img_item_path=os.path.join(self.root_dir,self.label_dir,img_name)
       img=Image.open(img_item_path)
       label=self.label_dir
       return img,label

    def __len__(self):
        return len(self.img_path)

root_dir='dataset'
ants_label_dir='ants'
bees_label_dir='bees'
ants_dataset=MyDataset(root_dir,ants_label_dir)
bees_dataset=MyDataset(root_dir,bees_label_dir)

dataset=ants_dataset+bees_dataset

5.如何用 SummaryWriter 记录图像和标量

import numpy
numpy.bool8 = bool

from torch.utils.tensorboard import SummaryWriter
import numpy as np
from PIL import Image

writer=SummaryWriter('logs')
image_path='dataset/ants/0013035.jpg'
img_PIL=Image.open(image_path)
img_array=np.array(img_PIL)
print(type(img_array))
print(img_array.shape)

writer.add_image("test",img_array,1,dataformats='HWC')
#y=2x
for i in range(100):
    writer.add_scalar('y=2x', 2*i, i)

writer.close()

6.使用transforms.ToTensor()将PIL图像转换为Tensor

from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
img_path="dataset/ants/0013035.jpg"
img_PIL=Image.open(img_path)

writer=SummaryWriter('logs')

#1.使用transforms.ToTensor()将PIL图像转换为Tensor
transform=transforms.ToTensor()
img_tensor=transform(img_PIL)

writer.add_image('Tensor_img',img_tensor)
writer.close()

7.transform的常见图像预处理操作

from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms

img=Image.open("dataset/1E6B8C5DB9722D99D028F39265867A60.png")
print(img)
writer=SummaryWriter('logs')

# ToTensor
trans_totensor=transforms.ToTensor()
img_tensor=trans_totensor(img)
writer.add_image('ToTensor',img_tensor)

# Normalize
'''
将输入张量 img_tensor 的每个通道减去均值 mean,再除以标准差 std。
公式:output = (input - mean) / std'''
print(img_tensor[0][0][0])
transform_norm=transforms.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])
img_norm=transform_norm(img_tensor)
print(img_norm[0][0][0])
writer.add_image('Normalize',img_norm,2)

#Resize
print(img.size)
#将输入图像 img(PIL 格式)缩放到 512×512 像素,得到 img_resize(依然是 PIL 图像)
transform_resize=transforms.Resize((512,512))
#img.PIL->resize->img_resize PIL
img_resize=transform_resize(img)
#img_resize PIL->to_tensor->img_resize_tensor Tensor
img_resize=trans_totensor(img_resize)
writer.add_image('Resize',img_resize,0)
print(img_resize)

#Compose
#Resize(512) 表示将图像的短边缩放到 512 像素,长边等比例缩放(保持宽高比)。
transforms_resize_2=transforms.Resize(512)
#PIL->PIL->Tensor
transform_compose=transforms.Compose([transform_resize,trans_totensor])
#先执行 transform_resize(对输入图像做尺寸调整),再执行 trans_totensor(将 PIL 图像转为 PyTorch 张量)
img_resize_2=transform_compose(img)
writer.add_image('Compose',img_resize_2,1)

#RandomCrop
#从同一张原始图像中随机裁剪出 10 个不同的 256×256 子图,并将它们全部转换为 PyTorch 张量。
transform_randomcrop=transforms.RandomCrop(256)
trans_compose_2=transforms.Compose([transform_randomcrop,trans_totensor])
#组合两个操作:先随机裁剪,再将裁剪后的 PIL 图像转换为张量(形状 (C, 256, 256),值域 [0,1])
for i in range(10):
    img_crop=trans_compose_2(img)
    writer.add_image('RandomCrop',img_crop,i)

writer.close()

8.transform与dataset的结合

import torchvision
from torch.utils.tensorboard import SummaryWriter

dataset_transform=torchvision.transforms.Compose([
    torchvision.transforms.ToTensor()
])
train_set=torchvision.datasets.CIFAR10(root='./data', train=True, transform=dataset_transform, download=True)
test_set=torchvision.datasets.CIFAR10(root='./data', train=False, transform=dataset_transform, download=True)
'''
print(train_set[0])
print(train_set.classes)

img,target=train_set[0]
print(img)
print(target)
print(train_set.classes[target])
img.show()
'''
#print(test_set[0])
writer=SummaryWriter('p10')
for i in range(10):
    img,target=test_set[i]
    writer.add_image('test_set',img,i)

writer.close()

9.dataloader

import torchvision

from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter


test_data=torchvision.datasets.CIFAR10(root="./data", train=False, transform=torchvision.transforms.ToTensor())

test_dataloader=DataLoader(test_data, batch_size=64, shuffle=False, num_workers=0, drop_last=True)

img,target=test_data[0]
print(img.shape)
print(target)

writer=SummaryWriter(log_dir="./dataloader")
for epoch in range(2):
    step=0
    for data in test_dataloader:
        imgs, targets=data
        #print(imgs.shape)
        #print(targets)

        writer.add_images("Epoch: {}".format(epoch), imgs, step)
        step+=1
writer.close()

10.神经网络的基本骨架

import torch
from torch import nn


class MyModule(nn.Module):
    def __init__(self):
        super().__init__()
    def forward(self,input):
        output=input+1
        return output

mynodule=MyModule()
x=torch.tensor(1.0)
y=mynodule(x)
print(y)

11.卷积操作

import torch
import torch.nn.functional as F

input=torch.tensor([
    [1,2,0,3,1],
    [0,1,2,3,1],
    [1,2,1,0,0],
    [5,2,3,1,1],
    [2,1,0,1,1],])
kernel=torch.tensor([
    [1,2,1],
    [0,1,0],
    [2,1,0],])

input=torch.reshape(input,(1,1,5,5))
kernel=torch.reshape(kernel,(1,1,3,3))

print(input.shape)
print(kernel.shape)

output=F.conv2d(input,kernel,stride=1)
print(output)

output2=F.conv2d(input,kernel,stride=2)
print(output2)

output3=F.conv2d(input,kernel,stride=1,padding=1)
print(output3)

12.神经网络——卷积层

conv1: Conv2d(3, 32, kernel_size=(5,5), stride=(1,1), padding=(2,2))

Conv2d:二维卷积层,用于提取图像空间特征

  • 3输入通道数,对应 RGB 图像由红、绿、蓝3 个单通道灰度图叠加而成,所以输入通道固定为 3。卷积层会同时对这 3 个通道做卷积运算,融合色彩与纹理信息。
  • 32输出通道数,该层输出 32 组卷积特征图,该层使用了 32 个不同的 5×5 卷积核
  • kernel_size=(5,5)卷积核大小,5×5 的方形卷积窗口
  • stride=(1,1)步长,卷积核每次在宽、高方向各移动 1 个像素
  • padding=(2,2)填充,在图像四周补 2 圈 0 像素,作用:保证卷积后特征图宽高不变

计算:卷积核(小权重矩阵)和图像对应位置逐元素相乘,再全部求和,得到一个输出点。

import  torch
import  torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from urllib3.filepost import writer

dataset=torchvision.datasets.CIFAR10("./data",train=False,transform=torchvision.transforms.ToTensor(),download=True)

dataloader=DataLoader(dataset,batch_size=64)

class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1=torch.nn.Conv2d(3,6,kernel_size=3,stride=1,padding=0)
    def forward(self,x):
        x=self.conv1(x)
        return x

net=Net()

writer=SummaryWriter("logs")
step=0
for data in dataloader:
    imgs,targets=data
    output=net(imgs)
    print(imgs.shape)
    print(output.shape)
    #torch.Size([64, 3, 32, 32])
    writer.add_images("input",imgs,step)
    #torch.Size([64, 6, 30, 30])->torch.Size([xx, 3, 30, 30])
    
    output=torch.reshape(output,(-1,3,30,30))
    writer.add_images("output",output,step)
    
    step+=1

13.神经网络——最大池化

pool1: MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)

MaxPool2d:最大池化层,下采样、降维、简化特征、减少计算量

  • kernel_size=2:池化窗口大小 2×2
  • stride=2:步长 2,窗口不重叠
  • padding=0:不补零
  • dilation=1:空洞卷积系数,普通池化固定为 1,池化核元素紧挨着,无空隙
  • ceil_mode=False:向下取整计算输出尺寸
import torch
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from urllib3.filepost import writer

input=torch.tensor([
    [1,2,0,3,1],
    [0,1,2,3,1],
    [1,2,1,0,0],
    [5,2,3,1,1],
    [2,1,0,1,1]
])
dataset=torchvision.datasets.CIFAR10("../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)
dataloader=DataLoader(dataset,batch_size=64)


class MaxPool(torch.nn.Module):
    def __init__(self):
        super(MaxPool, self).__init__()
        self.pool=torch.nn.MaxPool2d(kernel_size=3,ceil_mode=False)
    def forward(self,x):
        x=self.pool(x)
        return x
model=MaxPool()
writer=SummaryWriter('logs')

step=0
for data in dataloader:
    imgs,targets=data
    writer.add_image("input",imgs,step)
    output=model(imgs)
    writer.add_image("output",output,step)
    step+=1
writer.close()

14.神经网络——非线性激活

import torch
import torchvision
from torch import nn
from torch.nn import Sigmoid, ReLU
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

input=torch.tensor([
    [1,-0.5],
    [-1,3]])
output=torch.reshape(input,(-1,1,2,2))

dataset=torchvision.datasets.CIFAR10("../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)
dataloader=DataLoader(dataset,batch_size=64)

class Relu(torch.nn.Module):
    def __init__(self):
        super(Relu, self).__init__()
        self.relu=ReLU()
        self.sigmoid1=Sigmoid()

    def forward(self,input):
        output=self.sigmoid1(input)
        return output
model=Relu()
writer=SummaryWriter('logs')
step=0
for data in dataloader:
    imgs,targets=data
    writer.add_images("input",imgs,global_step= step)
    output=model(imgs)
    writer.add_images("output",output,global_step=step)
    step+=1
writer.close()

15.神经网络——线性层

linear1: Linear(in_features=1024, out_features=64, bias=True)

Linear:全连接层,把二维特征图展平为一维向量做特征融合

  • in_features=1024输入神经元总数 说明:经过 3 次池化后,特征图展平一共 1024 个数值
  • out_features=64:输出 64 个神经元
  • bias=True:启用偏置项(网络默认开启,提升拟合能力)让网络学习能力更强,能适配更多样本、提升拟合效果。
import torch
import torchvision
from torch.utils.data import DataLoader

dataset=torchvision.datasets.CIFAR10("../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)
dataloader=DataLoader(dataset,batch_size=64)  # 

class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.linear=torch.nn.Linear(196608,10)
    def forward(self,input):
        output=self.linear(input)
        return output
net=Net()

for data in dataloader:
    imgs,targets=data
    print(imgs.shape)
    output=torch.flatten(imgs)
    print(output.shape)
    output=net(output)
    print(output.shape)

线性层负责对输入进行可学习的加权组合与维度变换,是网络表达能力的“骨架”;而非线性层负责引入弯曲能力,二者相辅相成,缺一不可。

16.Sequential的使用

按顺序把网络层串成一整条流水线,数据从上到下依次经过每一层,不用手动逐层调用。代码简洁、结构清晰,适合一层接一层线性串联的网络。

import torch
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.tensorboard import SummaryWriter
from urllib3.filepost import writer


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.model1=nn.Sequential(
            Conv2d(3, 32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(32, 32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(32, 64, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Flatten(),
            Linear(1024,64),
            Linear(64, 10)
        )
    def forward(self,x):
        x=self.model1(x)
        return x
net=Net()
print(net)
input=torch.ones(64,3,32,32)
output=net(input)
print(output.shape)

writer=SummaryWriter('logs_seq')
writer.add_graph(net,input)
writer.close()

17.损失函数

import torch
from torch.nn import L1Loss
from torch import nn

inputs=torch.tensor([1,2,3],dtype=torch.float32)
targets=torch.tensor([1,2,5],dtype=torch.float32)

inputs=torch.reshape(inputs,[1,1,1,3])
targets=torch.reshape(targets,[1,1,1,3])

loss=L1Loss()#L1Loss:平均绝对误差,算差值绝对值
output=loss(inputs,targets)

loss_mse=nn.MSELoss ()#均方误差(MSE),算差值平方
output_mse=loss_mse(inputs,targets)

print(output)
print(output_mse)

x=torch.tensor([0.1,0.2,0.3])
y=torch.tensor([1])
x=torch.reshape(x,[1,3])
loss_cross=nn.CrossEntropyLoss()
output_cross=loss_cross(x,y)
print(output_cross)
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader

dataset=torchvision.datasets.CIFAR10("../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)
dataloader=DataLoader(dataset,batch_size=1)

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.model1=nn.Sequential(
            Conv2d(3, 32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(32, 32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(32, 64, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Flatten(),
            Linear(1024,64),
            Linear(64, 10)
        )
    def forward(self,x):
        x=self.model1(x)
        return x

loss=nn.CrossEntropyLoss()#交叉熵,专用于分类任务
Net=Net()
for data in dataloader:
    imgs,targets=data
    outputs=Net(imgs)
    result_loss=loss(outputs,targets)
    print(result_loss)

18.优化器

import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader

dataset=torchvision.datasets.CIFAR10("../data",train=False,transform=torchvision.transforms.ToTensor(),download=True)
dataloader=DataLoader(dataset,batch_size=1)

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.model1=nn.Sequential(
            Conv2d(3, 32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(32, 32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(32, 64, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Flatten(),
            Linear(1024,64),
            Linear(64, 10)
        )
    def forward(self,x):
        x=self.model1(x)
        return x

loss=nn.CrossEntropyLoss()
Net=Net()
optim=torch.optim.SGD(Net.parameters(),lr=0.01)
for epoch in range(20):
    running_loss=0.0
    for data in dataloader:
        imgs,targets=data
        outputs=Net(imgs)
        result_loss=loss(outputs,targets)
        optim.zero_grad()#每次循环清零梯度
        result_loss.backward()#反向传播计算梯度
        optim.step()#根据梯度优化相关参数
        running_loss+=result_loss
    print(f"第{epoch}轮训练损失为:{running_loss}")

19.完整的模型训练套路

import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

from model import *

train_data=torchvision.datasets.CIFAR10(root="./data", train=True, transform=torchvision.transforms.ToTensor(), download=True)
test_data=torchvision.datasets.CIFAR10(root="./data", train=False, transform=torchvision.transforms.ToTensor(), download=True)

train_data_size=len(train_data)
test_data_size=len(test_data)
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))

#利用dataloader加载数据集
train_dataloader=DataLoader(train_data,batch_size=64)
test_dataloader=DataLoader(test_data,batch_size=64)

#创建网络模型
net=Net()

#损失函数
loss_fn=nn.CrossEntropyLoss()

#优化器
learning_rate=1e-2
optimizer=torch.optim.SGD(net.parameters(),lr=learning_rate)
#记录训练次数
total_train_step=0
#测试次数
total_test_step=0
#训练的轮数
epoch=10

writer=SummaryWriter("logs_train")

for i in range(epoch):
    print("第{}轮训练开始".format(i+1))
    net.train()
    for data in train_dataloader:
        images,targets=data
        output=net(images)
        loss=loss_fn(output,targets)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_train_step+=1
        if total_train_step%100==0:
            print("训练次数:{},loss:{}".format(total_train_step,loss.item()))
            writer.add_scalar("train_loss",loss.item(),total_train_step)#add_scalar 就是专门用来向 TensorBoard 记录单个数值曲线的方法。


    #测试步骤
    net.eval()
    total_test_loss=0
    total_accuracy=0
    with torch.no_grad():
        for data in test_dataloader:
            images,targets=data
            output=net(images)
            loss=loss_fn(output,targets)
            total_test_loss+=loss.item()
            accuracy=(output.argmax(1)==targets).sum()
            total_accuracy+=accuracy.item()
    print("整体测试集上的loss:{}".format(total_test_loss))
    print("整体测试集上的accuracy:{}".format(total_accuracy/test_data_size))
    writer.add_scalar("test_loss",total_test_loss,total_test_step)
    writer.add_scalar("test_accuracy",total_accuracy/test_data_size,total_test_step)
    total_test_step+=1

    torch.save(net,"net_{}.pth".format(i+1))#第 1 个参数:要保存的对象(模型 / 参数 / 字典)第 2 个参数:保存路径 / 文件名
    #第二种保存模型的方式 torch.save(net.state_dict(),"net_params_{}.pth".format(i+1))
    print("模型已保存")

writer.close()

20.现有网络模型的使用,修改,保存

import torchvision
from torch import nn

'''
split="train"
加载训练集,用来给模型训练、更新权重。
split="val"
加载验证集,训练中途用来评估模型效果、调参数。
split="test"
加载测试集,训练全部结束后,做最终效果评测。
'''
#train_data=torchvision.datasets.ImageNet("./data_image",split="train",transform=torchvision.transforms.ToTensor(),download=True)

vgg16_false=torchvision.models.vgg16(pretrained=False)
# 不加载预训练权重,随机初始化参数
vgg16_true=torchvision.models.vgg16(pretrained=True)
# 加载ImageNet预训练权重(官方训练好的参数)

vgg16_true.classifier.add_module('add_linear',nn.Linear(1000,10))
print(vgg16_true)

vgg16_false.classifier[6]=nn.Linear(4096,10)
print(vgg16_false)

import torch
import torchvision

vgg16=torchvision.models.vgg16(pretrained=False)
#保存方式1,模型结构+模型参数
torch.save(vgg16,"vgg16_method1.pth")
#保存方式2,模型参数
torch.save(vgg16.state_dict(),"vgg16_method2.pth")

利用GPU训练

21.模型验证套路

import torch
import torchvision
from PIL import Image
from torch import nn

from model import Net

imag_path="dataset/1E6B8C5DB9722D99D028F39265867A60.png"
image=Image.open(imag_path)
print(image)

transform=torchvision.transforms.Compose(
    [torchvision.transforms.Resize((32,32)),
     torchvision.transforms.ToTensor()
    ])
image=transform(image)
print(image.shape)

model=Net()
state_dict = torch.load("net_params_1.pth")
model.load_state_dict(state_dict)
image=torch.reshape(image,[1,3,32,32])
model.eval()
with torch.no_grad():
    output=model(image)#关闭后能大幅减少显存占用。
print(output)
print(output.argmax(1))
Logo

汇聚全球AI编程工具,助力开发者即刻编程。