【专辑】AI大模型应用开发入门-拥抱Hugging Face与Transformers生态 - 基于BERT文本分类模型微调

大家好,我是java1234_小锋老师,最近更新《AI大模型应用开发入门-拥抱Hugging Face与Transformers生态》专辑,感谢大家支持。

本课程主要介绍和讲解Hugging Face和Transformers,包括加载预训练模型,自定义数据集,模型推理,模型微调,模型性能评估等。是AI大模型应用开发的入门必备知识。

基于BERT文本分类模型微调

模型微调简介

模型微调 (Model Fine-Tuning) 是迁移学习中的一种核心技术,指在一个已经预训练好的大型通用模型(称为基座模型 )的基础上,使用特定领域或任务的数据集进行额外的、有针对性的训练,使模型适应新任务的过程。

通俗比喻:

  • 预训练:让一个学生(模型)读完整个图书馆的书,获得广泛的通识知识。

  • 微调:然后让这个学生专攻某一个学科(例如医学或法律),学习这个领域的专业书籍和案例,从而成为该领域的专家。

为什么需要微调?(优势)

  1. 降低数据需求:从头训练一个大模型(如LLM或CV大模型)需要海量数据和巨大的算力。微调只需相对少量的高质量专业数据即可,成本极低。

  2. 节省计算资源和时间:预训练模型已经学会了通用的特征表示(如语言规则、图像纹理),微调只需在此基础上"微调"参数,训练速度快得多。

  3. 提升特定任务性能:在目标领域(如医疗问答、法律文书分析、特定风格的绘画)上,微调后的模型性能远优于直接使用通用模型。

  4. 避免灾难性遗忘:与从头学习相比,微调是一种温和的调整,能在保持模型通用能力的同时,增强其专业能力。

基于BERT模型微调训练

1,自定义数据集

首先我们准备下训练和测试数据。

8千多条的训练集数据,2千多条的测试集数据。

我们之所以要自定义数据集,是因为需要去适配训练模型需要的数据格式。

自定义数据集参考代码:

复制代码
from datasets import load_dataset
from torch.utils.data import Dataset


# 自定义数据集
class MyDataset(Dataset):

    def __init__(self, split):
        # 加载训练集
        train_dataset = load_dataset(path="csv", data_files="../weibo_senti_8k_train.csv")
        # 加载测试集
        test_dataset = load_dataset(path="csv", data_files="../weibo_senti_2k_test.csv")
        if split == 'train':
            self.data = train_dataset['train']
        elif split == 'test':
            self.data = test_dataset['train']

    # 获取数据集大小
    def __len__(self):
        return len(self.data)

    # 获取数据集的某个元素
    def __getitem__(self, index):
        sentence = self.data[index]['sentence']
        label = self.data[index]['label']
        return sentence, label


if __name__ == '__main__':
    train_dataset = MyDataset('train')
    test_dataset = MyDataset('test')
    print(train_dataset[0])
    print(test_dataset[0])

运行结果:

2,对训练输入文本进行编码

对传入的数据进行训练之前,我们需要对数据进行编码。

我们通过分词器的batch_encode_plus方法进行批量编码;

实例代码:

复制代码
from transformers import BertTokenizer

# 加载分词器
tokenizer = BertTokenizer.from_pretrained('../Bert-base-chinese')

# 准备测试文本
sents = ['床前明月光,疑似地上霜,举头望明月,低头思故乡', '今天天气不错', '很开心']

# 批量编码句子
out = tokenizer.batch_encode_plus(
    batch_text_or_text_pairs=sents,  # 输入的文本
    add_special_tokens=True,  # 添加特殊标记
    max_length=10,  # 最大长度
    padding='max_length',  # 填充
    truncation=True,  # 截断
    return_tensors='pt',  # 返回pytorch张量
    return_token_type_ids=True,  # 返回token_type_ids  区分不同句子或段落的类型标识
    return_attention_mask=True,  # 返回attention_mask  标记有效token位置的掩码
    return_special_tokens_mask=True  # 返回special_tokens_mask 标识特殊token(如[CLS]、[SEP])的位置掩码
)
print(out)
for k, v in out.items():
    print(k, v)

# 解码文本数据
for i in range(len(sents)):
    print(sents[i] + "--编码后:", tokenizer.decode(out['input_ids'][i]))

运行输出:

复制代码
{'input_ids': tensor([[ 101, 2414, 1184, 3209, 3299, 1045, 8024, 4542,  849,  102],
        [ 101,  791, 1921, 1921, 3698,  679, 7231,  102,    0,    0],
        [ 101, 2523, 2458, 2552,  102,    0,    0,    0,    0,    0]]), 'token_type_ids': 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]]), 'special_tokens_mask': tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 1, 1, 1],
        [1, 0, 0, 0, 1, 1, 1, 1, 1, 1]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
        [1, 1, 1, 1, 1, 0, 0, 0, 0, 0]])}
input_ids tensor([[ 101, 2414, 1184, 3209, 3299, 1045, 8024, 4542,  849,  102],
        [ 101,  791, 1921, 1921, 3698,  679, 7231,  102,    0,    0],
        [ 101, 2523, 2458, 2552,  102,    0,    0,    0,    0,    0]])
token_type_ids 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]])
special_tokens_mask tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 1, 1, 1],
        [1, 0, 0, 0, 1, 1, 1, 1, 1, 1]])
attention_mask tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
        [1, 1, 1, 1, 1, 0, 0, 0, 0, 0]])
床前明月光,疑似地上霜,举头望明月,低头思故乡--编码后: [CLS] 床 前 明 月 光 , 疑 似 [SEP]
今天天气不错--编码后: [CLS] 今 天 天 气 不 错 [SEP] [PAD] [PAD]
很开心--编码后: [CLS] 很 开 心 [SEP] [PAD] [PAD] [PAD] [PAD] [PAD]
3,定义增量模型,也就是下游任务

我们定义增量模型,主要是在Bert模型最后加一个全连接层,实现二分类任务。进行前向传播的时候,我们要使用torch.no_grad()冻结Bert模型参数,不需计算梯度,获取最后一层Bert隐藏层输出,调用自定义的全连接层,进行增量模型训练。

示例代码:

复制代码
# 使用设备(GPU/CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)

# 加载预训练模型
pretrained_model = BertModel.from_pretrained('./Bert-base-chinese').to(device)


# 定义下游任务(增量模型)
class DownStreamModel(torch.nn.Module):

    def __init__(self):
        super(DownStreamModel, self).__init__()
        # 下游加一个全连接层,实现二分类任务
        self.fc = torch.nn.Linear(768, 2)

    def forward(self, input_ids, attention_mask, token_type_ids):
        with torch.no_grad():  # 冻结Bert模型参数,不需计算梯度
            # 获取最后一层隐藏层输出
            output = pretrained_model(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
        # 增量模型参与训练
        out = self.fc(output.last_hidden_state[:, 0, :])
        return out

output.last_hidden_state[:, 0, :] 是用来获取模型输出的隐藏状态(通常是进入模型的最后一层隐藏状态)的一个特定切片。让我们逐个参数进行详细解析:

1. output

output 通常是通过对输入数据(如文本、图像等)运行某个深度学习模型(例如 BERT、GPT、Transformers 等)后返回的对象,它包含多个属性,其中一个重要的属性是 last_hidden_state

2. last_hidden_state

last_hidden_state 是模型最后一层的隐藏状态,它的形状通常是 (batch_size, sequence_length, hidden_size),具体的含义如下:

  • batch_size:处理的样本数量。在一次前向传播中,模型可以并行处理多个输入样本。

  • sequence_length:输入序列的长度。对于处理文本来说,它表示词汇的数量,可以是句子中词的个数。

  • hidden_size:每个隐藏状态的维度,代表模型的输出特征维数。通常在模型架构中定义,例如对于 BERT-base 隐藏大小为 768。

3. [:, 0, :]

这里的切片操作用来访问 last_hidden_state 的一部分,具体解释如下:

  • : :表示选择所有的样本。由于 batch_size 是第一个维度,所以把 : 放在这个位置表示选取当前批次的所有样本。

  • 0 :表示选择第一个时刻的隐藏状态。在 NLP 中,特别是像 BERT 这样的模型中,第一位置的隐藏状态通常对应于 [CLS] token(分类 token),它是用来进行句子级别的任务(例如分类)的。

  • : :表示选择所有的特征维度(hidden_size)。这意味着我们提取每个样本的第一个位置(即 [CLS] token)对应的全部隐藏状态特征。

综合

因此,output.last_hidden_state[:, 0, :] 表示提取所有输入样本的 [CLS] token 的隐藏状态向量,这通常用于下游任务,如文本分类、问答、情感分析等,因为 [CLS] token 的表示通常是整段文本的语义聚合。

例子

如果有一个输入批次大小为 4,并且每个输入序列的隐藏状态大小为 768,那么 output.last_hidden_state[:, 0, :] 的返回结果将是一个形状为 (4, 768) 的张量,其中每一行对应一个输入样本的 [CLS] token 的隐藏状态。

4,训练模型

前面我们已经定义好了数据集,以及文本编码处理,包括增量模型定义。接下来我们来进行增量模型微调训练。

实例代码:

复制代码
import torch
from datasets import load_dataset
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel


# 自定义数据集
class MyDataset(Dataset):

    def __init__(self, split):
        # 加载训练集
        train_dataset = load_dataset(path="csv", data_files="./weibo_senti_8k_train.csv")
        # 加载测试集
        test_dataset = load_dataset(path="csv", data_files="./weibo_senti_2k_test.csv")
        if split == 'train':
            self.data = train_dataset['train']
        elif split == 'test':
            self.data = test_dataset['train']

    # 获取数据集大小
    def __len__(self):
        return len(self.data)

    # 获取数据集的某个元素
    def __getitem__(self, index):
        sentence = self.data[index]['sentence']
        label = self.data[index]['label']
        return sentence, label


# 加载分词器
tokenizer = BertTokenizer.from_pretrained('./Bert-base-chinese')

# 使用设备(GPU/CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)

# 加载预训练模型
pretrained_model = BertModel.from_pretrained('./Bert-base-chinese').to(device)


# 定义下游任务(增量模型)
class DownStreamModel(torch.nn.Module):

    def __init__(self):
        super(DownStreamModel, self).__init__()
        # 下游加一个全连接层,实现二分类任务
        self.fc = torch.nn.Linear(768, 2)

    def forward(self, input_ids, attention_mask, token_type_ids):
        with torch.no_grad():  # 冻结Bert模型参数,不需计算梯度
            # 获取最后一层隐藏层输出
            output = pretrained_model(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
        # 增量模型参与训练
        out = self.fc(output.last_hidden_state[:, 0, :])
        return out


# 对传入数据进行编码
def collate_fn(data):
    sents = [i[0] for i in data]
    labels = [i[1] for i in data]
    # 编码
    # 批量编码句子
    out = tokenizer.batch_encode_plus(
        batch_text_or_text_pairs=sents,  # 输入的文本
        add_special_tokens=True,  # 添加特殊标记
        max_length=256,  # 最大长度
        padding='max_length',  # 填充
        truncation=True,  # 截断
        return_tensors='pt',  # 返回pytorch张量
        return_token_type_ids=True,  # 返回token_type_ids  区分不同句子或段落的类型标识
        return_attention_mask=True,  # 返回attention_mask  标记有效token位置的掩码
        return_special_tokens_mask=True  # 返回special_tokens_mask 标识特殊token(如[CLS]、[SEP])的位置掩码
    )
    return out['input_ids'], out['attention_mask'], out['token_type_ids'], torch.tensor(labels)


# 创建数据集
train_dataset = MyDataset('train')  # 训练集
train_loader = DataLoader(
    dataset=train_dataset,  # 数据集
    batch_size=200,  # 批次大小
    shuffle=True,  # 是否打乱数据
    drop_last=True,  # 丢弃最后一个批次数据
    collate_fn=collate_fn  # 对加载的数据进行编码
)

test_dataset = MyDataset('test')  # 测试集
test_loader = DataLoader(
    dataset=test_dataset,  # 数据集
    batch_size=200,  # 批次大小
    shuffle=True,  # 是否打乱数据
    drop_last=True,  # 丢弃最后一个批次数据
    collate_fn=collate_fn  # 对加载的数据进行编码
)

if __name__ == '__main__':
    model = DownStreamModel().to(device)  # 创建模型
    optimizer = torch.optim.AdamW(model.parameters())  # 优化器
    criterion = torch.nn.CrossEntropyLoss()  # 定义损失函数
    best_val_acc = 0  # 保存最好的准确率
    EPOCH = 3  # 训练轮数
    for epoch in range(EPOCH):  # 训练轮数
        for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(train_loader):  # 批次数据
            out = model(input_ids=input_ids.to(device), attention_mask=attention_mask.to(device),
                        token_type_ids=token_type_ids.to(device))  # 模型输出
            loss = criterion(out, labels.to(device))  # 计算损失
            optimizer.zero_grad()  # 清空梯度
            loss.backward()  # 反向传播
            optimizer.step()  # 优化器更新参数
            # 每隔5个批次输出训练结果
            if i % 5 == 0:
                out = out.argmax(dim=1)  # 获取预测结果
                acc = (out == labels.to(device)).sum().item() / len(labels)  # 计算准确率
                print("EPOCH:{}--第{}批次--损失:{}--准确率:{}".format(epoch + 1, i + 1, loss.item(), acc))

运行结果:

复制代码
cuda
EPOCH:1--第1批次--损失:0.5008983016014099--准确率:0.78
EPOCH:1--第6批次--损失:0.5052810907363892--准确率:0.75
EPOCH:1--第11批次--损失:0.4850313067436218--准确率:0.755
EPOCH:1--第16批次--损失:0.4301462471485138--准确率:0.83
EPOCH:1--第21批次--损失:0.39388778805732727--准确率:0.85
EPOCH:1--第26批次--损失:0.3695535361766815--准确率:0.855
EPOCH:1--第31批次--损失:0.35825812816619873--准确率:0.855
EPOCH:1--第36批次--损失:0.3288692533969879--准确率:0.875
EPOCH:2--第1批次--损失:0.31738394498825073--准确率:0.885
EPOCH:2--第6批次--损失:0.3121739625930786--准确率:0.87
EPOCH:2--第11批次--损失:0.30510687828063965--准确率:0.895
EPOCH:2--第16批次--损失:0.305753618478775--准确率:0.865
EPOCH:2--第21批次--损失:0.24456100165843964--准确率:0.92
EPOCH:2--第26批次--损失:0.2233615517616272--准确率:0.93
EPOCH:2--第31批次--损失:0.2816208302974701--准确率:0.89
EPOCH:2--第36批次--损失:0.24931633472442627--准确率:0.915
EPOCH:3--第1批次--损失:0.3053100109100342--准确率:0.885
EPOCH:3--第6批次--损失:0.2515011727809906--准确率:0.9
EPOCH:3--第11批次--损失:0.24241474270820618--准确率:0.915
EPOCH:3--第16批次--损失:0.2211739420890808--准确率:0.925
EPOCH:3--第21批次--损失:0.24276195466518402--准确率:0.91
EPOCH:3--第26批次--损失:0.27010777592658997--准确率:0.895
EPOCH:3--第31批次--损失:0.24304606020450592--准确率:0.9
EPOCH:3--第36批次--损失:0.26476508378982544--准确率:0.905
5,评估模型

模型训练好之后,我们要对模型进行性能评估,以及保存最优模型参数。

示例代码:

复制代码
import torch
from datasets import load_dataset
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel


# 自定义数据集
class MyDataset(Dataset):

    def __init__(self, split):
        # 加载训练集
        train_dataset = load_dataset(path="csv", data_files="./weibo_senti_8k_train.csv")
        # 加载测试集
        test_dataset = load_dataset(path="csv", data_files="./weibo_senti_2k_test.csv")
        if split == 'train':
            self.data = train_dataset['train']
        elif split == 'test':
            self.data = test_dataset['train']

    # 获取数据集大小
    def __len__(self):
        return len(self.data)

    # 获取数据集的某个元素
    def __getitem__(self, index):
        sentence = self.data[index]['sentence']
        label = self.data[index]['label']
        return sentence, label


# 加载分词器
tokenizer = BertTokenizer.from_pretrained('./Bert-base-chinese')

# 使用设备(GPU/CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)

# 加载预训练模型
pretrained_model = BertModel.from_pretrained('./Bert-base-chinese').to(device)


# 定义下游任务(增量模型)
class DownStreamModel(torch.nn.Module):

    def __init__(self):
        super(DownStreamModel, self).__init__()
        # 下游加一个全连接层,实现二分类任务
        self.fc = torch.nn.Linear(768, 2)

    def forward(self, input_ids, attention_mask, token_type_ids):
        with torch.no_grad():  # 冻结Bert模型参数,不需计算梯度
            # 获取最后一层隐藏层输出
            output = pretrained_model(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
        # 增量模型参与训练
        out = self.fc(output.last_hidden_state[:, 0, :])
        return out


# 对传入数据进行编码
def collate_fn(data):
    sents = [i[0] for i in data]
    labels = [i[1] for i in data]
    # 编码
    # 批量编码句子
    out = tokenizer.batch_encode_plus(
        batch_text_or_text_pairs=sents,  # 输入的文本
        add_special_tokens=True,  # 添加特殊标记
        max_length=256,  # 最大长度
        padding='max_length',  # 填充
        truncation=True,  # 截断
        return_tensors='pt',  # 返回pytorch张量
        return_token_type_ids=True,  # 返回token_type_ids  区分不同句子或段落的类型标识
        return_attention_mask=True,  # 返回attention_mask  标记有效token位置的掩码
        return_special_tokens_mask=True  # 返回special_tokens_mask 标识特殊token(如[CLS]、[SEP])的位置掩码
    )
    return out['input_ids'], out['attention_mask'], out['token_type_ids'], torch.tensor(labels)


# 创建数据集
train_dataset = MyDataset('train')  # 训练集
train_loader = DataLoader(
    dataset=train_dataset,  # 数据集
    batch_size=200,  # 批次大小
    shuffle=True,  # 是否打乱数据
    drop_last=True,  # 丢弃最后一个批次数据
    collate_fn=collate_fn  # 对加载的数据进行编码
)

test_dataset = MyDataset('test')  # 测试集
test_loader = DataLoader(
    dataset=test_dataset,  # 数据集
    batch_size=200,  # 批次大小
    shuffle=True,  # 是否打乱数据
    drop_last=True,  # 丢弃最后一个批次数据
    collate_fn=collate_fn  # 对加载的数据进行编码
)

if __name__ == '__main__':
    model = DownStreamModel().to(device)  # 创建模型
    optimizer = torch.optim.AdamW(model.parameters())  # 优化器
    criterion = torch.nn.CrossEntropyLoss()  # 定义损失函数
    best_val_acc = 0  # 保存最好的准确率
    EPOCH = 3  # 训练轮数
    for epoch in range(EPOCH):  # 训练轮数
        for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(train_loader):  # 批次数据
            out = model(input_ids=input_ids.to(device), attention_mask=attention_mask.to(device),
                        token_type_ids=token_type_ids.to(device))  # 模型输出
            loss = criterion(out, labels.to(device))  # 计算损失
            optimizer.zero_grad()  # 清空梯度
            loss.backward()  # 反向传播
            optimizer.step()  # 优化器更新参数
            # 每隔5个批次输出训练结果
            if i % 5 == 0:
                out = out.argmax(dim=1)  # 获取预测结果
                acc = (out == labels.to(device)).sum().item() / len(labels)  # 计算准确率
                print("EPOCH:{}--第{}批次--损失:{}--准确率:{}".format(epoch + 1, i + 1, loss.item(), acc))
    # 验证模型
    model.eval()  # 评估模式 Dropout关闭
    with torch.no_grad():  # 评估模式,不需要计算梯度
        for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(test_loader):
            out = model(input_ids=input_ids.to(device), attention_mask=attention_mask.to(device),
                        token_type_ids=token_type_ids.to(device))
            out = out.argmax(dim=1)
            acc = (out == labels.to(device)).sum().item() / len(labels)
            if acc > best_val_acc:
                best_val_acc = acc
                torch.save(model.state_dict(), "best_model.pth")
                print(f"测试集准确率EPOCH:{epoch}-第{i}批次:模型保存,准确率:{acc}")
        # 保存最后一轮模型
        torch.save(model.state_dict(), "last_model.pth")
        print(f"最后一轮模型保存,:准确率:{acc}")

运行结果:

复制代码
cuda
EPOCH:1--第1批次--损失:0.6369971632957458--准确率:0.695
EPOCH:1--第6批次--损失:0.6052521467208862--准确率:0.685
EPOCH:1--第11批次--损失:0.5286625623703003--准确率:0.77
EPOCH:1--第16批次--损失:0.4174182415008545--准确率:0.83
EPOCH:1--第21批次--损失:0.41344115138053894--准确率:0.81
EPOCH:1--第26批次--损失:0.3910037875175476--准确率:0.87
EPOCH:1--第31批次--损失:0.3680213987827301--准确率:0.85
EPOCH:1--第36批次--损失:0.3817676901817322--准确率:0.855
EPOCH:2--第1批次--损失:0.3963841199874878--准确率:0.84
EPOCH:2--第6批次--损失:0.3516421914100647--准确率:0.85
EPOCH:2--第11批次--损失:0.330654114484787--准确率:0.845
EPOCH:2--第16批次--损失:0.3345922529697418--准确率:0.865
EPOCH:2--第21批次--损失:0.3087370693683624--准确率:0.885
EPOCH:2--第26批次--损失:0.25769323110580444--准确率:0.92
EPOCH:2--第31批次--损失:0.2792946696281433--准确率:0.885
EPOCH:2--第36批次--损失:0.29899129271507263--准确率:0.905
EPOCH:3--第1批次--损失:0.3139827847480774--准确率:0.855
EPOCH:3--第6批次--损失:0.27809959650039673--准确率:0.895
EPOCH:3--第11批次--损失:0.2725857198238373--准确率:0.885
EPOCH:3--第16批次--损失:0.30161210894584656--准确率:0.885
EPOCH:3--第21批次--损失:0.26055067777633667--准确率:0.925
EPOCH:3--第26批次--损失:0.22951321303844452--准确率:0.895
EPOCH:3--第31批次--损失:0.2995443642139435--准确率:0.87
EPOCH:3--第36批次--损失:0.3246515691280365--准确率:0.87
测试集准确率EPOCH:2-第0批次:模型保存,准确率:0.975
最后一轮模型保存,:准确率:0.955
相关推荐
NAGNIP1 天前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab1 天前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab1 天前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP1 天前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年1 天前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼1 天前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS1 天前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区1 天前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈1 天前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang1 天前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx