生成式聊天机器人 -- 基于Pytorch + Global Attention + 双向 GRU 实现的SeqToSeq模型 -- 下

生成式聊天机器人 -- 基于Pytorch + Global Attention + 双向 GRU 实现的SeqToSeq模型 -- 下

  • 训练
    • Masked 损失
    • 单次训练过程
    • 迭代训练过程
  • 测试
    • 贪心解码(Greedy decoding)算法
    • 实现对话函数
  • 训练和测试模型
  • 完整代码


生成式聊天机器人 – 基于Pytorch + Global Attention + 双向 GRU 实现的SeqToSeq模型 – 上


训练

Masked 损失

encoder 和 decoder 的 forward 函数实现之后,我们就需要计算loss。seq2seq有两个RNN,Encoder RNN是没有直接定义损失函数的,它是通过影响Decoder从而影响最终的输出以及loss。

Decoder输出一个序列,前面我们介绍的是Decoder在预测时的过程,它的长度是不固定的,只有遇到EOS才结束。给定一个问答句对,我们可以把问题输入Encoder,然后用Decoder得到一个输出序列,但是这个输出序列和”真实”的答案长度并不相同。而且即使长度相同并且语义相似,也很难直接知道预测的答案和真实的答案是否类似。

那么我们怎么计算loss呢?

比如输入是”What is your name?”,训练数据中的答案是”I am LiLi”。假设模型有两种预测:”I am fine”和”My name is LiLi”。从语义上显然第二种答案更好,但是如果字面上比较的话可能第一种更好。

但是让机器知道”I am LiLi”和”My name is LiLi”的语义很接近这是非常困难的,所以实际上我们通常还是通过字面上进行比较我们会限制Decoder的输出,使得Decoder的输出长度和”真实”答案一样,然后逐个时刻比较。Decoder输出的是每个词的概率分布,因此可以使用交叉熵损失函数。但是这里还有一个问题,因为是一个batch的数据里有一些是padding的,因此这些位置的预测是没有必要计算loss的,因此我们需要使用前面的mask矩阵把对应位置的loss去掉,我们可以通过下面的函数来实现计算Masked的loss。

# 带掩码的负对数似然损失函数
# 一次求出一个Time step一批词的预测损失 
# inp(batch_size,7826),词汇表总共7826个词
# target(batch_size,),给出当前批次中各个批当前Time step正确输出词在词汇表中的索引ID
# mask(batch_size,),给出当前批次中各个批当前Time step对应的词是否为padding填充词
def maskNLLLoss(inp, target, mask):# 计算实际的词的个数,因为padding是0,非padding是1,因此sum就可以得到词的个数nTotal = mask.sum()# 计算交叉熵损失,返回结果维度: (batch_size,) crossEntropy = -torch.log(torch.gather(inp, 1, target.view(-1, 1)).squeeze(1))# 通过mask矩阵筛选出非padding词的预测损失,然后求和算平均损失# 此处计算得到的平均损失是某个Time Step输入的一批词的平均损失loss = crossEntropy.masked_select(mask).mean()loss = loss.to(device)return loss, nTotal.item()

这段代码有两个函数比较难理解,下面来重点讲解一下,首先是gather函数:

为了实现交叉熵这里使用了gather函数,这是一种比较底层的实现方法,更简便的方法应该使用CrossEntropyLoss或者NLLLoss,其中CrossEntropy等价与LogSoftmax+NLLLoss

交叉熵的定义为: H ( p , q ) = − ∑ p ( x ) l o g q ( x ) H(p,q)=−∑p(x)logq(x) H(p,q)=p(x)logq(x)。其中p和q是两个随机变量的概率分布,这里是离散的随机变量,如果是连续的需要把求和变成积分。在我们这里p是真实的分布,也就是one-hot的,而q是模型预测的softmax的输出。因为p是one-hot的,所以只需要计算真实分类对应的那个值。

比如假设一个5分类的问题,当前正确分类是 2 2 2(下标从 0 0 0~ 4 4 4),而模型的预测是 ( 0.1 , 0.1 , 0.4 , 0.2 , 0.2 ) (0.1,0.1,0.4,0.2,0.2) (0.1,0.1,0.4,0.2,0.2),则 H = − l o g ( 0.4 ) H=-log(0.4) H=log(0.4)。用交叉熵作为分类的 L o s s Loss Loss是比较合理的,正确的分类是2,那么模型在下标为2的地方预测的概率 q 2 q_{2} q2越大,则 − l o g q 2 −logq_{2} logq2越小,也就是 l o s s loss loss越小。

假设inp是:

0.3 0.2 0.4 0.1
0.2 0.1 0.4 0.3

也就是batch=2,而分类数(词典大小)是4,inp是模型预测的分类概率。 而target = [2,3] ,表示第一个样本的正确分类是第三个类别(概率是0.4),第二个样本的正确分类是第四个类别(概率是0.3)。因此我们需要计算的是 − l o g ( 0.4 ) − l o g ( 0.3 ) -log(0.4) - log(0.3) log(0.4)log(0.3)。怎么不用for循环求出来呢?我们可以使用torch.gather函数首先把0.4和0.3选出来:

inp = torch.tensor([[0.3, 0.2, 0.4, 0.1], [0.2, 0.1, 0.4, 0.3]])
target = torch.tensor([2, 3])
selected = torch.gather(inp, 1, target.view(-1, 1))
print(selected)

输出:

tensor([[ 0.4000],[ 0.3000]])

关于masked_select函数,我们来看一个例子:

>>> x = torch.randn(3, 4)
>>> x
tensor([[ 0.3552, -2.3825, -0.8297,  0.3477],[-1.2035,  1.2252,  0.5002,  0.6248],[ 0.1307, -2.0608,  0.1244,  2.0139]])
>>> mask = x.ge(0.5)
>>> mask
tensor([[ 0,  0,  0,  0],[ 0,  1,  1,  1],[ 0,  0,  0,  1]], dtype=torch.uint8)
>>> torch.masked_select(x, mask)
tensor([ 1.2252,  0.5002,  0.6248,  2.0139])

它要求mask和被mask的tensor的shape是一样的,然后从crossEntropy选出mask值为1的那些值。输出的维度会减1。


单次训练过程

函数train实现一个batch数据的训练。前面我们提到过,在训练的时候我们会限制Decoder的输出,使得Decoder的输出长度和”真实”答案一样长。但是我们在训练的时候如果让Decoder自行输出,那么收敛可能会比较慢,因为Decoder在t时刻的输入来自t-1时刻的输出。

如果前面预测错了,那么后面很可能都会错下去。另外一种方法叫做teacher forcing,它不管模型在t-1时刻做什么预测都把t-1时刻的正确答案作为t时刻的输入。但是如果只用teacher forcing也有问题,因为真实的Decoder是没有老师来帮它纠正错误的。所以比较好的方法是追加一个teacher_forcing_ratio参数随机的来确定本次训练是否teacher forcing。

另外使用到的一个技巧是梯度裁剪(gradient clipping) 。这个技巧通常是为了防止梯度爆炸(exploding gradient),它把参数限制在一个范围之内,从而可以避免梯度的梯度过大或者出现NaN等问题。注意:虽然它的名字叫梯度裁剪,但实际它是对模型的参数进行裁剪,它把整个参数看成一个向量,如果这个向量的模大于max_norm,那么就把这个向量除以一个值使得模等于max_norm,因此也等价于把这个向量投影到半径为max_norm的球上。它的效果如下图所示。

在这里插入图片描述
单次具体训练过程为:

  1. 把整个batch的输入传入encoder
  2. 把decoder的输入设置为特殊的,初始隐状态设置为encoder最后时刻的隐状态
  3. decoder每次处理一个时刻的forward计算
  4. 如果是teacher forcing,把上个时刻的"正确的"词作为当前输入,否则用上一个时刻的输出作为当前时刻的输入
  5. 计算loss
  6. 反向计算梯度
  7. 对梯度进行裁剪
  8. 更新模型(包括encoder和decoder)参数

注意,PyTorch的RNN模块(RNN, LSTM, GRU)也可以当成普通的非循环的网络来使用。在Encoder部分,我们是直接把所有时刻的数据都传入RNN,让它一次计算出所有的结果,但是在Decoder的时候(非teacher forcing)后一个时刻的输入来自前一个时刻的输出,因此无法一次计算。

# input_var维度:(max_length,batch_size)
# target_var维度: (max_length,batch_size)
# lengths维度: (batch_size)
# mask维度: (max_length,batch_size)
def train(input_variable, lengths, target_variable, mask, max_target_len, encoder, decoder, embedding,encoder_optimizer, decoder_optimizer, batch_size, clip, max_length=MAX_LENGTH):# 梯度清空encoder_optimizer.zero_grad()decoder_optimizer.zero_grad()# 设置device,从而支持GPU,当然如果没有GPU也能工作。input_variable = input_variable.to(device)lengths = lengths.to(device)target_variable = target_variable.to(device)mask = mask.to(device)# 初始化变量loss = 0print_losses = []n_totals = 0# encoder的Forward计算 --- 输入数据维度: (max_len,batch_size) , (batch_size)encoder_outputs, encoder_hidden = encoder(input_variable, lengths)# Decoder的初始输入是SOS,我们需要构造(1, batch)的输入,表示第一个时刻batch个输入。decoder_input = torch.LongTensor([[SOS_token for _ in range(batch_size)]])decoder_input = decoder_input.to(device)# 注意:Encoder是双向的,而Decoder是单向的,因此从下往上取n_layers个decoder_hidden = encoder_hidden[:decoder.n_layers]# 确定是否teacher forcinguse_teacher_forcing = True if random.random() < teacher_forcing_ratio else False# 一次处理一个时刻if use_teacher_forcing:for t in range(max_target_len):decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden, encoder_outputs)# Teacher forcing: 下一个时刻的输入是当前正确答案decoder_input = target_variable[t].view(1, -1)# 计算累计的lossmask_loss, nTotal = maskNLLLoss(decoder_output, target_variable[t], mask[t])loss += mask_lossprint_losses.append(mask_loss.item() * nTotal)n_totals += nTotalelse:for t in range(max_target_len):decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden, encoder_outputs)# 不是teacher forcing: 下一个时刻的输入是当前模型预测概率最高的值_, topi = decoder_output.topk(1)decoder_input = torch.LongTensor([[topi[i][0] for i in range(batch_size)]])decoder_input = decoder_input.to(device)# 计算累计的lossmask_loss, nTotal = maskNLLLoss(decoder_output, target_variable[t], mask[t])loss += mask_lossprint_losses.append(mask_loss.item() * nTotal)n_totals += nTotal# 反向计算loss.backward()# 对encoder和decoder进行梯度裁剪_ = torch.nn.utils.clip_grad_norm_(encoder.parameters(), clip)_ = torch.nn.utils.clip_grad_norm_(decoder.parameters(), clip)# 更新参数encoder_optimizer.step()decoder_optimizer.step()return sum(print_losses) / n_totals

迭代训练过程

最后是把前面的代码组合起来进行训练。函数trainIters用于进行n_iterations次minibatch的训练。

值得注意的是我们定期会保存模型,我们会保存一个tar包,包括encoder和decoder的state_dicts(参数),优化器(optimizers)的state_dicts, loss和迭代次数。这样保存模型的好处是从中恢复后我们既可以进行预测也可以进行训练(因为有优化器的参数和迭代的次数)。

def trainIters(model_name, voc, pairs, encoder, decoder, encoder_optimizer, decoder_optimizer,embedding, encoder_n_layers, decoder_n_layers, save_dir, n_iteration, batch_size,print_every, save_every, clip, corpus_name, loadFilename):# 随机选择n_iteration个batch的数据(pair)# 一次性生成n_iteration轮迭代需要的全部数据 (n_iteration,batch)# batch 表示当前轮迭代需要的大小为batch_size的数据training_batches = [batch2TrainData(voc, [random.choice(pairs) for _ in range(batch_size)])for _ in range(n_iteration)]# 初始化print('Initializing ...')start_iteration = 1print_loss = 0if loadFilename:start_iteration = checkpoint['iteration'] + 1# 训练print("Training...")for iteration in range(start_iteration, n_iteration + 1):training_batch = training_batches[iteration - 1]# input_var维度:(max_length,batch_size)# target_var维度: (max_length,batch_size)# lengths维度: (batch_size)# mask维度: (max_length,batch_size)input_variable, lengths, target_variable, mask, max_target_len = training_batch# 训练一个batch的数据loss = train(input_variable, lengths, target_variable, mask, max_target_len, encoder,decoder, embedding, encoder_optimizer, decoder_optimizer, batch_size, clip)print_loss += loss# 进度if iteration % print_every == 0:print_loss_avg = print_loss / print_everyprint("Iteration: {}; Percent complete: {:.1f}%; Average loss: {:.4f}".format(iteration, iteration / n_iteration * 100, print_loss_avg))print_loss = 0# 保存checkpointif (iteration % save_every == 0):directory = os.path.join(save_dir, model_name, corpus_name, '{}-{}_{}'.format(encoder_n_layers, decoder_n_layers, hidden_size))if not os.path.exists(directory):os.makedirs(directory)torch.save({'iteration': iteration,'en': encoder.state_dict(),'de': decoder.state_dict(),'en_opt': encoder_optimizer.state_dict(),'de_opt': decoder_optimizer.state_dict(),'loss': loss,'voc_dict': voc.__dict__,'embedding': embedding.state_dict()}, os.path.join(directory, '{}_{}.tar'.format(iteration, 'checkpoint')))

测试

模型训练完成之后,我们需要测试它的效果。最简单直接的方法就是和chatbot来聊天。因此我们需要用Decoder来生成一个响应。

贪心解码(Greedy decoding)算法

最简单的解码算法是贪心算法,也就是每次都选择概率最高的那个词,然后把这个词作为下一个时刻的输入,直到遇到EOS结束解码或者达到一个最大长度。但是贪心算法不一定能得到最优解,因为某个答案可能开始的几个词的概率并不太高,但是后来概率会很大。因此除了贪心算法,我们通常也可以使用Beam-Search算法,也就是每个时刻保留概率最高的Top K个结果,然后下一个时刻尝试把这K个结果输入(当然需要能恢复RNN的状态),然后再从中选择概率最高的K个。

为了实现贪心解码算法,我们定义一个GreedySearchDecoder类。这个类的forwar的方法需要传入一个输入序列(input_seq),其shape是(input_seq length, 1), 输入长度input_length和最大输出长度max_length。过程如下:

  1. 把输入传给Encoder,得到所有时刻的输出和最后一个时刻的隐状态。
  2. 把Encoder最后时刻的隐状态作为Decoder的初始状态。
  3. Decoder的第一时刻输入初始化为SOS。
  4. 定义保存解码结果的tensor。
  5. 循环直到最大解码长度。
    1. 把当前输入传入Decoder。
    2. 得到概率最大的词以及概率。
    3. 把这个词和概率保存下来。
    4. 把当前输出的词作为下一个时刻的输入。
    5. 返回所有的词和概率。
import torch
from torch import nn
from Trainer import device, decoder, encoder
from utils import SOS_token, normalizeString, indexesFromSentence, MAX_LENGTH, vocclass GreedySearchDecoder(nn.Module):def __init__(self, encoder, decoder):super(GreedySearchDecoder, self).__init__()self.encoder = encoderself.decoder = decoder# input_seq维度: (max_len,batch_size)def forward(self, input_seq, input_length, max_length):# Encoder的Forward计算encoder_outputs, encoder_hidden = self.encoder(input_seq, input_length)# 把Encoder最后时刻的隐状态作为Decoder的初始值decoder_hidden = encoder_hidden[:decoder.n_layers]# 因为我们的函数都是要求(time,batch),因此即使只有一个数据,也要做出二维的。# Decoder的初始输入是SOSdecoder_input = torch.ones(1, 1, device=device, dtype=torch.long) * SOS_token# 用于保存解码结果的tensorall_tokens = torch.zeros([0], device=device, dtype=torch.long)all_scores = torch.zeros([0], device=device)# 循环,这里只使用长度限制,后面处理的时候把EOS去掉了。for _ in range(max_length):# Decoder forward一步decoder_output, decoder_hidden = self.decoder(decoder_input, decoder_hidden,encoder_outputs)# decoder_outputs是(batch=1, vob_size)# 使用max返回概率最大的词和得分decoder_scores, decoder_input = torch.max(decoder_output, dim=1)# 把解码结果保存到all_tokens和all_scores里all_tokens = torch.cat((all_tokens, decoder_input), dim=0)all_scores = torch.cat((all_scores, decoder_scores), dim=0)# decoder_input是当前时刻输出的词的ID,这是个一维的向量,因为max会减少一维。# 但是decoder要求有一个batch维度,因此用unsqueeze增加batch维度。decoder_input = torch.unsqueeze(decoder_input, 0)# 返回所有的词和得分。return all_tokens, all_scores

实现对话函数

解码方法完成后,我们写一个函数来测试从终端输入一个句子然后来看看chatbot的回复。我们需要用前面的函数来把句子分词,然后变成ID传入解码器,得到输出的ID后再转换成文字。我们会实现一个evaluate函数,由它来完成这些工作。

我们需要把一个句子变成输入需要的格式——shape为(max_length , batch),即使只有一个输入也需要增加一个batch维度。我们首先把句子分词,然后变成ID的序列,然后转置成合适的格式。此外我们还需要创建一个名为lengths的tensor,虽然只有一个,来表示输入的实际长度。接着我们构造类GreedySearchDecoder的实例searcher,然后用searcher来进行解码得到输出的ID,最后我们把这些ID变成词并且去掉EOS之后的内容。

另外一个evaluateInput函数作为chatbot的用户接口,当运行它的时候,它会首先提示用户输入一个句子,然后使用evaluate来生成回复。然后继续对话直到用户输入”q”或者”quit”。如果用户输入的词不在词典里,我们会输出错误信息(当然还有一种办法是忽略这些词)然后提示用户重新输入。

def evaluate(encoder, decoder, searcher, voc, sentence, max_length=MAX_LENGTH):# 把输入的一个batch句子变成idindexes_batch = [indexesFromSentence(voc, sentence)]# 创建lengths tensorlengths = torch.tensor([len(indexes) for indexes in indexes_batch])# 转置 -- (max_len,batch_size)input_batch = torch.LongTensor(indexes_batch).transpose(0, 1)# 放到合适的设备上(比如GPU)input_batch = input_batch.to(device)lengths = lengths.to(device)# 用searcher解码tokens, scores = searcher(input_batch, lengths, max_length)# ID变成词。decoded_words = [voc.index2word[token.item()] for token in tokens]return decoded_wordsdef evaluateInput(encoder, decoder, searcher, voc):input_sentence = ''while(1):try:# 得到用户终端的输入input_sentence = input()# 是否退出if input_sentence == 'q' or input_sentence == 'quit': break# 句子归一化input_sentence = normalizeString(input_sentence)# 生成响应Evaluate sentenceoutput_words = evaluate(encoder, decoder, searcher, voc, input_sentence)# 去掉EOS后面的内容words = []for word in output_words:if word == 'EOS':breakelif word != 'PAD':words.append(word)print('Bot:', ' '.join(words))except KeyError:print("Error: Encountered unknown word.")

训练和测试模型

不论是我们是训练模型还是测试对话,我们都需要初始化encoder和decoder模型参数。在下面的代码,我们从头开始训练模型或者从某个checkpoint加载模型。读者可以尝试不同的超参数配置来进行调优。

USE_CUDA = torch.cuda.is_available()
device = torch.device("cuda" if USE_CUDA else "cpu")model_name = 'cb_model'
attn_model = 'dot'
# attn_model = 'general'
# attn_model = 'concat'
hidden_size = 500
encoder_n_layers = 2
decoder_n_layers = 2
dropout = 0.1
batch_size = 64# 从哪个checkpoint恢复,如果是None,那么从头开始训练。
#loadFilename = "data/save/cb_model/cornell movie-dialogs corpus/2-2_500/4000_checkpoint.tar"
loadFilename = None
checkpoint_iter = 4000# 如果loadFilename不空,则从中加载模型
if loadFilename:# 如果训练和加载是一条机器,那么直接加载checkpoint = torch.load(loadFilename)# 否则比如checkpoint是在GPU上得到的,但是我们现在又用CPU来训练或者测试,那么注释掉下面的代码# checkpoint = torch.load(loadFilename, map_location=torch.device('cpu'))encoder_sd = checkpoint['en']decoder_sd = checkpoint['de']encoder_optimizer_sd = checkpoint['en_opt']decoder_optimizer_sd = checkpoint['de_opt']embedding_sd = checkpoint['embedding']voc.__dict__ = checkpoint['voc_dict']print('Building encoder and decoder ...')
# 初始化word embedding
embedding = nn.Embedding(voc.num_words, hidden_size)
if loadFilename:embedding.load_state_dict(embedding_sd)
# 初始化encoder和decoder模型
encoder = EncoderRNN(hidden_size, embedding, encoder_n_layers, dropout)
decoder = GlobalAttnDecoderRNN(attn_model, embedding, hidden_size, voc.num_words,decoder_n_layers, dropout)
if loadFilename:encoder.load_state_dict(encoder_sd)decoder.load_state_dict(decoder_sd)
# 使用合适的设备
encoder = encoder.to(device)
decoder = decoder.to(device)
print('Models built and ready to go!')

训练前,我们需要设置一些训练的超参数。初始化优化器,最后调用函数trainIters进行训练。

# 配置训练的超参数和优化器
clip = 50.0
teacher_forcing_ratio = 1.0
learning_rate = 0.0001
decoder_learning_ratio = 5.0
n_iteration = 4000
print_every = 1
save_every = 500# 设置进入训练模式,从而开启dropout
encoder.train()
decoder.train()# 初始化优化器
print('Building optimizers ...')
encoder_optimizer = optim.Adam(encoder.parameters(), lr=learning_rate)
decoder_optimizer = optim.Adam(decoder.parameters(), lr=learning_rate * decoder_learning_ratio)
if loadFilename:encoder_optimizer.load_state_dict(encoder_optimizer_sd)decoder_optimizer.load_state_dict(decoder_optimizer_sd)# 开始训练
print("Starting Training!")
trainIters(model_name, voc, pairs, encoder, decoder, encoder_optimizer, decoder_optimizer,embedding, encoder_n_layers, decoder_n_layers, save_dir, n_iteration, batch_size,print_every, save_every, clip, corpus_name, loadFilename)

训练完毕后,我们使用下面的代码进行测试。

# 进入eval模式,从而去掉dropout。 
encoder.eval()
decoder.eval()# 构造searcher对象 
searcher = GreedySearchDecoder(encoder, decoder)# 测试
evaluateInput(encoder, decoder, searcher, voc)

下面是测试的一些例子:

> hello
Bot: hello .
> what's your name?
Bot: jacob .
> I am sorry.
Bot: you re not .
> where are you from?
Bot: southern .
> q

完整代码

码云仓库链接

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

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

相关文章

《ARM64体系结构编程与实践》学习笔记(四)

MMU内存管理 1.MMU内存管理&#xff08;armv8.6手册的D5章节&#xff09;&#xff0c;MMU包含快表TLB&#xff0c;TLB是对页表的部分缓存&#xff0c;页表是存放在内存里面的。 AArch64仅仅支持Long Descriptor的页表格式&#xff0c;AArch32支持两种页表格式Armv7-A Short De…

如何在Vscode中接入Deepseek

一、获取Deepseek APIKEY 首先&#xff0c;登录Deepseek官网的开放平台&#xff1a;DeepSeek 选择API开放平台&#xff0c;然后登录Deepseek后台。 点击左侧菜单栏“API keys”&#xff0c;并创建API key。 需要注意的是&#xff0c;生成API key复制保存到本地&#xff0c;丢失…

Docker 部署 MinIO | 国内阿里镜像

一、导读 Minio 是个基于 Golang 编写的开源对象存储套件&#xff0c;基于Apache License v2.0开源协议&#xff0c;虽然轻量&#xff0c;却拥有着不错的性能。它兼容亚马逊S3云存储服务接口。可以很简单的和其他应用结合使用&#xff0c;例如 NodeJS、Redis、MySQL等。 二、…

DeepSeek-R1 32B Windows+docker本地部署

最近国产大模型DeepSeek兴起&#xff0c;本地部署了一套deepseek同时集成Open WebUI界面,给大家出一期教程。 软件&#xff1a;Ollama、docker、Open WebUI 一、用Ollama下载模型 首先我们需要安装Ollama&#xff0c;它可以在本地运行和管理大模型。 到Ollama官网 https://ol…

TCP服务器与客户端搭建

一、思维导图 二、给代码添加链表 【server.c】 #include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <fcntl.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.…

python爬虫--简单登录

1&#xff0c;使用flask框架搭建一个简易网站 后端代码app.py from flask import Flask, render_template, request, redirect, url_for, sessionapp Flask(__name__) app.secret_key 123456789 # 用于加密会话数据# 模拟用户数据库 users {user1: {password: password1}…

ESXi Host Client创建ubuntu虚拟机教程及NVIDIA显卡驱动安装

参考文章 VMware虚拟机显卡直通记录 AIGC 实战&#xff08;环境篇&#xff09; - EXSI 8.0 Debian安装RTX3060显卡驱动 重点介绍 client版本是7.0.3 注意&#xff1a;下图中不要选择BIOS 按照两个链接中的方法进行操作&#xff0c;以及本章节的上面几个图片的配置之后&a…

Maven入门核心知识点总结

Maven 1. POM&#xff08;Project Object Model&#xff09;2. 坐标&#xff08;Coordinates&#xff09;3. 依赖管理&#xff08;Dependency Management&#xff09;4. 常用五个生命周期&#xff08;Life Circle&#xff09;5. Maven 仓库&#xff08;Maven Repository&#x…

测试中的第一性原理:回归本质的质量思维革命

在软件工程领域&#xff0c;测试活动常被惯性思维和经验主义所主导——测试用例库无限膨胀、自动化脚本维护成本居高不下、测试策略与业务目标渐行渐远。要突破这种困境&#xff0c;第一性原理&#xff08;First Principles Thinking&#xff09;提供了独特的解题视角&#xff…

Rust语言进阶之标准输入: stdin用法实例(一百零五)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

SpringBoot速成(七)注册实战P2-P4

1.创建 数据库创建 依赖引入 <!-- mybatis起步依赖--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency> <…

Spring Boot接入Deep Seek的API

1&#xff0c;首先进入deepseek的官网&#xff1a;DeepSeek | 深度求索&#xff0c;单击右上角的API开放平台。 2&#xff0c;单击API keys&#xff0c;创建一个API&#xff0c;创建完成务必复制&#xff01;&#xff01;不然关掉之后会看不看api key&#xff01;&#xff01;&…

【C++学习篇】C++11第二期学习

目录 1. 可变参数模板 1.1 基本语法及原理 1.2 包扩展 1.3empalce系列接⼝ 2. lamba 2.1 lambda的语法表达式 2.2 捕捉列表 2.3 lamba的原理 1. 可变参数模板 1.1 基本语法及原理 1. C11⽀持可变参数模板&#xff0c;也就是说⽀持可变数量参数的函数模板和类模板&…

开放式TCP/IP通信

一、1200和1200之间的开放式TCP/IP通讯 第一步&#xff1a;组态1214CPU&#xff0c;勾选时钟存储器 第二步&#xff1a;防护与安全里面连接机制勾选允许PUT/GET访问 第三步&#xff1a;添加PLC 第四步&#xff1a;点击网络试图&#xff0c;选中网口&#xff0c;把两个PLC连接起…

迁移学习 Transfer Learning

迁移学习&#xff08;Transfer Learning&#xff09;是什么&#xff1f; 迁移学习是一种机器学习方法&#xff0c;它的核心思想是利用已有模型的知识来帮助新的任务或数据集进行学习&#xff0c;从而减少训练数据的需求、加快训练速度&#xff0c;并提升模型性能。 &#x1f…

爬虫技巧汇总

一、UA大列表 USER_AGENT_LIST 是一个包含多个用户代理字符串的列表&#xff0c;用于模拟不同浏览器和设备的请求。以下是一些常见的用户代理字符串&#xff1a; USER_AGENT_LIST [Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; Hot Lingo 2.0),Mozilla…

我们来学人工智能 -- 将Ollama已下载的模型从C盘迁出

题记 未配置OLLAMA_MODELS系统变量导致模型下载到了C盘 迁移步骤 退出ollama 配置OLLAMA_MODELS系统变量 OLLAMA_MODELS&#xff1a;D:\ollama\models 直接将C盘下的models目录剪切到指定目录 检查 cmd命令窗口退出重新打开

Redis 集群原理、主从复制和哨兵模式的详细讲解

引言&#xff1a;本文记录了博主在学习Redis的过程中的原理&#xff0c;了解为什么使用与怎么样使用 Redis 集群&#xff0c;在使用 Redis 集群时出现的主从复制和哨兵模式的相关知识。本文并不涉及Redis安装。 文章目录 一、简单介绍什么是 Redis二、为什么要使用 Redis 集群三…

Java数据结构 | TreeMap 和 TreeSet

TreeMap 和 TreeSet 1. 搜索树1.1 概念1.2 搜索树的查找、插入、删除思路及代码1.2.1 查找1.2.2 插入1.2.3 删除&#xff08;难点&#xff09; 1.3 二叉搜索树的性能分析 2. Map 和 Set2.1 Map 接口2.1.1 Map.Entry<K,V>2.1.2 Map的常用方法 2.2 Set 接口2.2.1 Set 的常用…

智能理解 PPT 内容,快速生成讲解视频

当我们想根据一版 PPT 制作出相对应的解锁视频时&#xff0c;从撰写解锁词&#xff0c;录制音频到剪辑视频&#xff0c;每一个环节都需要投入大量的时间和精力&#xff0c;本方案将依托于阿里云函数计算 FC 和百炼模型服务&#xff0c;实现从 PPT 到视频的全自动转换&#xff0…