NLP中的对话机器人——问答机器人的应用场景

article/2025/9/20 3:43:22

引言

本文是七月在线《NLP中的对话机器人》的视频笔记,主要介绍FAQ问答型聊天机器人的实现。

FAQ问答机器人

FAQ就是一些常见问题与回答,比如https://letsencrypt.org/docs/faq/。

但是我们要做的不是一问一答形式的,而是类似stackoverflow那种一问多答,即包括用户提问、网友回答和最佳答案。有人提问,然后会有人在上面回复,每个问题可能有多个回答。

数据集仓库地址 : https://github.com/SophonPlus/ChineseNlpCorpus

数据集

我们先来了解下数据集。

image-20230112095843475

可以看到,有4个字段,其中标题和问题类似发帖时的标题和正文,问题可以为空。

剩下的是reply和is_best分别代表回复和是否为最佳答案。

从这种数据集我们可以想一下它的应用场景。

场景一

假设在提问者手动选择最佳答案之前,我们可以

  • 对多个回答进行排序,最相关的、最好的排在前面,不好的排在后面
  • 从这些回答中找出最佳回答。二分类任务

数据

我们这里用的数据是农行问答数据,下载地址: https://pan.baidu.com/s/1n-jT9SKkt6cwI_PjCd7i_g

模型

关于这种类似的任务,我们应该得到句子的向量表示,即句向量。可能说到句向量,大家第一时间想到的都是BERT来实现,但这里我们先用简单的模型来实现,简单的模型速度快,可以快速验证我们的思路。

关于问题和回答我们都需要一个句向量编码,我们可以采用Dual Encoder的架构,训练两个编码器,一个用于问题的编码,另一个用于回答的编码。这两个编码器是独立的。我们知道,编码器的选择一般有RNN、CNN和Transformer等。得到了问题和回答的句向量编码后,我们可以使用余弦相似度来计算问题和回答的匹配程度,也可以使用一个复杂一点的神经网络来计算匹配度。

每当拿到一个新的问题和数据,建议从最简单的模型开始,搭建出一个baseline,然后以这个baseline为基础开始调试自己的模型。因为简单的模型往往表现不会太差,容易调试,且模型的可解释性好。当我们确定自己的baseline没有问题之后可以开始尝试更复杂的模型,通过循序渐进地尝试。一般从一些简单的模型开始一步步叠加新的组件,直到效果令人满意位置。如果一开始就采用太复杂的模型,很多情况下我们就无法理解究竟模型中的哪些组件时重要的,哪些是不重要的。

实现

本节包含完整的代码,首先是需要引入的依赖:

from collections import Counter
import pickle
import osimport torch
import torch.nn as nn
import torch.nn.functional as Fimport numpy as np
import pandas as pdfrom tqdm import tqdm

DualEncoder

image-20230112161731161

Dual Encoder即两个独立的Encoder,这里分别计算问题和回答的句向量,最后通过余弦相似度计算它们之间的关联程度。

# DualEncoder
class DualEncoder(nn.Module):def __init__(self, encoder1, encoder2, type="cosine"):super(DualEncoder, self).__init__()self.encoder1 = encoder1self.encoder2 = encoder2if type != 'cosine':# 训练一个简单的神经网络来计算相似度self.linear = nn.Sequential(# 拼接encoder1和encoder2的输出(向量),转换成100维的表示nn.Linear(self.encoder1.hidden_size + self.encoder2.hidden_size, 100),# 经过ReLU激活nn.ReLU(),# 再转换成一个数值,表示相似程度nn.Linear(100, 1))def forward(self, x, x_mask, y, y_mask):x_rep = self.encoder1(x, x_mask)y_rep = self.encoder2(y, y_mask)return x_rep, y_rep

主要实现了前向传播方法。

Encoder

# GRUEncoder
class GRUEncoder(nn.Module):def __init__(self, vocab_size, embed_size, hidden_size, dropout_p=0.1, avg_hidden=True, n_layers=1, bidirectional=True):super(GRUEncoder, self).__init__()self.hidden_size = hidden_sizeself.embed = nn.Embedding(vocab_size, embed_size)if bidirectional:# 大小除以2,使得拼接两个方向后大小不变hidden_size //= 2# 这种生成句子表征的建议使用bidirectional=Trueself.rnn = nn.GRU(embed_size, hidden_size, num_layers=n_layers, bidirectional=bidirectional,dropout=dropout_p)self.dropout = nn.Dropout(dropout_p)self.bidirectional = bidirectionalself.avg_hidden = avg_hiddendef forward(self, x, mask):x_embed = self.embed(x) # 先得到嵌入表示x_embed = self.dropout(x_embed) # 再经过dropoutseq_len = mask.sum(1) # 计算有效长度# 压缩批次内填充数据# 通过压缩填充加快训练效率,具体可参考文章: https://blog.csdn.net/yjw123456/article/details/118855324x_embed = torch.nn.utils.rnn.pack_padded_sequence(input=x_embed,lengths=seq_len.cpu(),batch_first=True,enforce_sorted=False)output, hidden = self.rnn(x_embed)# output (batch_size, seq_len, hidden_size)# hidden (num_directions * num_layers, batch_size, hidden_size)output, seq_len = torch.nn.utils.rnn.pad_packed_sequence(sequence=output,batch_first=True,padding_value=0,total_length=mask.shape[1])if self.avg_hidden:# 对RNN输出每个时刻的输出求均值# mask.unsqueeze(2) 使维度个数和output一致# hiden (batch_size, hidden_size)hidden = torch.sum(output * mask.unsqueeze(2), 1) / torch.sum(mask, 1, keepdim=True)else:if self.bidirectional:# 拼接两个方向上的输出# hidden[-2,:,:] (batch_size, hidden_size / 2)# hidden[-1,:,:] (batch_size, hidden_size / 2)# hidden (batch_size, hidden_size)hidden = torch.cat((hidden[-2,:,:], hidden[-1,:,:]),dim=1)else:# 取出最顶层(若num_layers > 1)的hiddenhidden = hidden[-1,:,:]# 需要保证各种情况下的hidden大小都是一致的# 经过一层dropouthidden = self.dropout(hidden)return hidden

这里采用GRU作为Encoder的实现,支持多种表征的获取,默认是平均每个时刻的输出。

词典

处理NLP任务基本上都需要一个词典:

# 构建分词器
class Tokenizer:def __init__(self, vocab):self.id2word = ["UNK"] + vocab # 保证未知词UNK的id为0self.word2id = {w:i for i,w in enumerate(vocab)}def text2id(self, text):# 对中文简单的按字拆分return [self.word2id.get(w, 0) for w in str(text)]def id2text(self, ids):return "".join([self.id2word[id] for id in ids])def __len__(self):return len(self.id2word)def create_tokenizer(texts, vocab_size):"""创建分词器,输入文本列表和词典大小"""all_vocab = ""for text in texts:all_vocab += str(text)vocab_count = Counter(all_vocab) # 按字拆分# 最频繁的vocab_size个单词vocab = vocab_count.most_common(vocab_size)# (char, count) 从中取出charvocab = [w[0] for w in vocab]return Tokenizer(vocab)def list2tensor(sents, tokenizer):"""将文本列表结合分词器转换为tensor"""res = []mask = []for sent in sents:res.append(tokenizer.text2id(sent))max_len = max([len(sent) for sent in res])# 按最大长度进行填充for i in range(len(res)):_mask = np.zeros((1, max_len)) # 中的元素0表示填充词,1表示非填充_mask[:,:len(res[i])] = 1 # 有效位元素置1res[i] = np.expand_dims(np.array(res[i] + [0] * (max_len - len(res[i]))), 0) # 增加一个维度mask.append(_mask)res = np.concatenate(res, axis=0) # 按维度0进行拼接mask = np.concatenate(mask, axis=0)# 分别转换为long类型和float类型的tensorres = torch.tensor(res).long()mask = torch.tensor(mask).float()return res, mask

这里的分词器结合了词典的功能,代码如上,我们可以通过text2id方法获取文本中每个字的ID。

加载数据集

数据集下载地址: https://pan.baidu.com/s/1n-jT9SKkt6cwI_PjCd7i_g

# 数据集位置
file_path = '../dataset/nonghangzhidao_filter.csv'
df = pd.read_csv(file_path)[["title", "reply", "is_best"]] # 只需要这三个字段
df.head()

image-20230112165912462

看一下开头那么几条数据,对数据长什么样的有一个基本的了解。

拆分数据集

由于数据集本身没有进行拆分,因此我们这里实现拆分数据集的代码:

np.random.seed(42) # 设定随机种子可以防止每次的训练/测试集数据不一样
# 拆分训练/测试集
def shuffle_and_split_data(data, test_ratio):shuffled_indices = np.random.permutation(len(data))test_set_size = int(len(data) * test_ratio)test_indices = shuffled_indices[:test_set_size] # 前test_set_size作为测试集train_indices = shuffled_indices[test_set_size:] # 剩下的作为训练集return data.iloc[train_indices], data.iloc[test_indices]# 20%的数据作为测试集
train_set, test_set = shuffle_and_split_data(df, 0.2)print(len(train_set), len(test_set))
(31876, 7968)

在拆分数据集的同时进行了洗牌操作,打散数据。

这里设置了随机种子,方式每次运行的训练集、测试集中的数据都不一样。

将文本转换为张量

首先我们取出文本内容:

texts = list(train_set["title"]) + list(train_set["reply"]) # 取出文本内容

查看前10条:

print(texts[:10])
['有没有什么借款的口子?','农行信用卡办哪一张好','窝的银行卡必须要办理转账才能收到钱吗?','请问成都兴百惠公司贷款不成功不收费是真的吗?','得到死者老婆的身份证,还得到死者的户口本和火化证能取走死者银行卡里的钱吗','借个爱奇艺vip会员,只用1天','微信怎么能有微','农村60岁拿钱的,在外地农行能交钱吗','信而富为什么不下款了','大众金融为什么还完贷款,不给办解压绿本']

下面我们创建词典,并基于词典将文本转换为张量:

tokenizer = create_tokenizer(texts, 5000)
print(len(tokenizer)) 
3383

词典中的单词(字)个数为3383。调用上面实现的list2tensor函数:

sents = list(train_set["title"][:3])
print(list2tensor(sents, tokenizer))
(tensor([[  15,  211,   15,  238,   97,   52,    4,    2,  488,  233,  125,    0,0,    0,    0,    0,    0,    0,    0],[   9,    1,   12,   13,   18,   62,  248,   39,  379,  178,    0,    0,0,    0,    0,    0,    0,    0,    0],[2231,    2,    7,    1,   18,  373,  442,   71,   62,   34,   64,   21,493,   63,   81,   41,  206,  100,  125]]),tensor([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0.,0.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0.,0.],[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,1.]]))

可以看到,同时返回了两个tensor,一个是文本对应的ID列表,另一个是mask,表示对应位置的单词是否为填充单词。填充单词用0表示,非填充单词用1表示。

这样做的好处是,可以在计算损失时乘以这个mask,就可以忽略0处(填充词)的损失;通过mask.sum()就可以知道句子的有效长度。

编写训练代码

数据和模型都准备好了,下一步是编写训练代码:

# 编写训练代码
def train(df, model, loss_fn, optimizer, device, tokenizer, loss_function, batch_size):# 设成训练模型,让dropout生效model.train() df = df.sample(frac=1) # 每次训练时shuffle数据# 分批处理for i in range(0, df.shape[0], batch_size): # 得到批次数据batch_df = df.iloc[i:i+batch_size]title = list(batch_df["title"])reply = list(batch_df["reply"])# 构建目标tensor(1或0)target = torch.tensor(batch_df["is_best"].to_numpy()).float()if loss_function == "cosine":# 为了符合CosineEmbeddingLoss的要求,将0替换成-1target[target == 0] = -1 x, x_mask = list2tensor(title, tokenizer)y, y_mask = list2tensor(reply, tokenizer)# 都切换到同一设备(cpu/gpu)x, x_mask, y, y_mask, target = x.to(device), x_mask.to(device), y.to(device), y_mask.to(device), target.to(device)# 计算x和y的表征x_rep, y_rep = model(x, x_mask, y, y_mask)# 根据需要使用不同的损失if loss_function == "cosine":loss = loss_fn(x_rep, y_rep, target)else:# 拼接x_pre和y_rep,并传入linearlogits = model.linear(torch.cat([x_rep, y_rep], 1)) loss = loss_fn(logits, target)optimizer.zero_grad()loss.backward()# 梯度裁剪nn.utils.clip_grad_norm_(model.parameters(), 1.0)optimizer.step()if loss_function == "cosine":sim = F.cosine_similarity(x_rep, y_rep)# 余弦相似度范围在[-1,1]之间,因此这里使用0作为分界sim[sim < 0] = -1sim[sim >= 0] = 1else:sim = model.linear(torch.cat([x_rep, y_rep], 1))# sim = torch.sigmoid(logits) 可以不用sigmoidsim[sim < 0] = 0sim[sim >= 0] = 1sim = sim.view(-1)target = target.view(-1)# 计算准确率num_corrects = torch.sum(sim == target).item()total_counts = target.shape[0]print(f"accuracy:{num_corrects / total_counts}")return num_corrects / total_counts

这里根据不同的设置使用了不同的损失函数,我们后面介绍。

定义参数

# 定义参数loss_function = "cosine"
batch_size  = 64
output_dir  = "./models"
num_epochs  = 10
vocab_size  = 5000
hidden_size = 300
embed_size  = 600

我们可以根据需要调整这里的参数。

开始训练

# 构建两个Encoder
title_encoder = GRUEncoder(len(tokenizer), embed_size, hidden_size)
reply_encoder = GRUEncoder(len(tokenizer), embed_size, hidden_size)
# 传入DualEncoder模型
model = DualEncoder(title_encoder, reply_encoder, type=loss_function)
# 设置特定的损失函数
if loss_function == "cosine":loss_fn = nn.CosineEmbeddingLoss()
else:loss_fn = nn.BCEWithLogitsLoss()
# Adam优化器
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
# 有GPU就用GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 
model = model.to(device)if not os.path.exists(output_dir):os.makedirs(output_dir)# 分词器(词典)可以重复使用,为了不出问题,在推理时要使用和训练时同样的分词器(词典)
pickle.dump(tokenizer, open(os.path.join(output_dir,"tokenizer.pickle"), "wb"))
for epoch in tqdm(range(num_epochs)):train(train_set, model, loss_fn, optimizer, device, tokenizer, loss_function, batch_size)

可以看到,这里有两种损失函数,分别是CosineEmbeddingLossBCEWithLogitsLoss

我们分别来看看。

CosineEmbeddingLoss

官方文档: https://pytorch.org/docs/stable/generated/torch.nn.CosineEmbeddingLoss.html

image-20230112171945684

用于衡量两个向量是相似还是不相似的。输入主要为input1( x 1 x_1 x1)、input2( x 2 x_2 x2)、target( y y y)。

这里要求target也就是 y y y取值-11,取值1代表正例。

对于正例来说,我们希望它们的余弦距离(1-cosine相似度)尽可能小,余弦相似度取值范围是[-1,1],越接近1表示越相似,在向量中余弦相似度-1表示两个向量方向相反。所以 1 − cos ⁡ ( x 1 , x 2 ) 1-\cos(x_1,x_2) 1cos(x1,x2)当相似度为 1 1 1时,损失为 0 0 0,否则相似度越小损失越大;

对于负例来说,我们希望它们的cosine相似度(1-余弦距离)尽可能小,这里让损失最小为 0 0 0,所以取了一个max,由于余弦相似度取值的限制,margin也只能取[-1,1]之间。文档建议使用 0.5 0.5 0.5,默认为零。当为 0.5 0.5 0.5时,实际上余弦相似度表示没有很相似,减去这个值,可以认为损失为 0 0 0。这个取值也可以尝试设置一下,看看会带来怎样的影响。

BCEWithLogitsLoss

因为我们要判断是否为最佳答案,这可以看成是一个二分类问题,正例为 1 1 1,负例为 0 0 0,因此也可以使用交叉熵来作为损失函数。

这里不再赘述,感兴趣都可以参考https://blog.csdn.net/yjw123456/article/details/121734499

了解完损失函数之后,我们来看下模型的表现:

10%|█         | 1/10 [00:56<08:28, 56.52s/it]accuracy:0.7520%|██        | 2/10 [01:54<07:37, 57.19s/it]accuracy:0.7530%|███       | 3/10 [02:50<06:37, 56.74s/it]accuracy:1.040%|████      | 4/10 [03:45<05:36, 56.08s/it]accuracy:1.050%|█████     | 5/10 [04:41<04:40, 56.03s/it]accuracy:1.060%|██████    | 6/10 [05:37<03:43, 55.93s/it]accuracy:0.7570%|███████   | 7/10 [06:32<02:47, 55.77s/it]accuracy:0.580%|████████  | 8/10 [07:30<01:52, 56.41s/it]accuracy:1.090%|█████████ | 9/10 [08:26<00:56, 56.22s/it]accuracy:1.0
100%|██████████| 10/10 [09:22<00:00, 56.21s/it]accuracy:1.0

可以看到,后面的几次准确率为1,但是要注意,这仅仅是在训练集上的结果,准确率为1,也有可能是过拟合了。因此,我们需要在测试集上进行验证。下面添加相关代码。

# 编写在测试集上评估的代码
# 编写训练代码
def evaluate(df, model, loss_fn, device, tokenizer, loss_function, batch_size):# 设成训练模型,让dropout失效model.eval() df = df.sample(frac=1) # 每次shuffle数据# 分批处理for i in range(0, df.shape[0], batch_size): batch_df = df.iloc[i:i+batch_size]title = list(batch_df["title"])reply = list(batch_df["reply"])# 构建目标tensortarget = torch.tensor(batch_df["is_best"].to_numpy()).float()if loss_function == "cosine":target[target == 0] = -1 # 符合CosineEmbeddingLoss的要求x, x_mask = list2tensor(title, tokenizer)y, y_mask = list2tensor(reply, tokenizer)# 都切换到同一设备(cpu/gpu)x, x_mask, y, y_mask, target = x.to(device), x_mask.to(device), y.to(device), y_mask.to(device), target.to(device)# 不需要计算梯度with torch.no_grad():# 计算x和y的表征x_rep, y_rep = model(x, x_mask, y, y_mask)if loss_function == "cosine":loss = loss_fn(x_rep, y_rep, target)sim = F.cosine_similarity(x_rep, y_rep)# 余弦相似度范围在[-1,1]之间,因此这里使用0作为分界sim[sim < 0] = -1sim[sim >= 0] = 1else:logits = model.linear(torch.cat([x_rep, y_rep], 1))loss = loss_fn(logits, target)# sim = torch.sigmoid(logits) 可以不用sigmoidsim = logitssim[sim < 0] = 0sim[sim >= 0] = 1sim = sim.view(-1)target = target.view(-1)# 计算准确率num_corrects = torch.sum(sim == target).item()total_counts = target.shape[0]print(f"test accuracy:{num_corrects / total_counts}, loss:{loss.item()}")return num_corrects / total_counts

和训练时差不多,但有几点要注意,首先model.eval() 让dropout失效,其次torch.no_grad()让推理时不计算梯度。

最后,改写下训练时的代码,每轮训练完成后在测试集上进行评估,保存测试集上准确率最好的模型。

# 构建两个Encoder
title_encoder = GRUEncoder(len(tokenizer), embed_size, hidden_size)
reply_encoder = GRUEncoder(len(tokenizer), embed_size, hidden_size)
# 传入DualEncoder模型
model = DualEncoder(title_encoder, reply_encoder, type=loss_function)
# 设置特定的损失函数
if loss_function == "cosine":loss_fn = nn.CosineEmbeddingLoss()
else:loss_fn = nn.BCEWithLogitsLoss()
# Adam优化器
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
# 有GPU就用GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 
model = model.to(device)if not os.path.exists(output_dir):os.makedirs(output_dir)# 分词器(词典)可以重复使用,为了不出问题,在推理时要使用和训练时同样的分词器(词典)best_acc = 0pickle.dump(tokenizer, open(os.path.join(output_dir,"tokenizer.pickle"), "wb"))
for epoch in tqdm(range(num_epochs)):train(train_set, model, loss_fn, optimizer, device, tokenizer, loss_function, batch_size)acc = evaluate(test_set, model, loss_fn, device, tokenizer, loss_function, batch_size)if acc > best_acc:best_acc = accprint("saving best model")torch.save(model.state_dict(), os.path.join(output_dir, "model.pth"))
 0%|          | 0/10 [00:00<?, ?it/s]train accuracy:0.75, loss:0.2164661139249801610%|█         | 1/10 [01:02<09:23, 62.66s/it]test accuracy:0.84375, loss:0.13674256205558777
saving best model
train accuracy:0.75, loss:0.2725315690040588420%|██        | 2/10 [02:01<08:04, 60.60s/it]test accuracy:0.78125, loss:0.11578086018562317
train accuracy:1.0, loss:0.0731103867292404230%|███       | 3/10 [03:00<06:57, 59.59s/it]test accuracy:0.84375, loss:0.15137901902198792
train accuracy:0.75, loss:0.2228523194789886540%|████      | 4/10 [03:59<05:55, 59.31s/it]test accuracy:0.75, loss:0.252815842628479
train accuracy:1.0, loss:0.2226412743330001850%|█████     | 5/10 [04:58<04:57, 59.43s/it]test accuracy:0.75, loss:0.21923129260540009
train accuracy:1.0, loss:0.0572931915521621760%|██████    | 6/10 [05:57<03:56, 59.14s/it]test accuracy:0.9375, loss:0.09478427469730377
saving best model
train accuracy:0.75, loss:0.3161787092685699570%|███████   | 7/10 [06:56<02:57, 59.01s/it]test accuracy:0.9375, loss:0.07146801054477692
train accuracy:1.0, loss:0.04639099538326263480%|████████  | 8/10 [07:55<01:58, 59.01s/it]test accuracy:0.71875, loss:0.26382946968078613
train accuracy:1.0, loss:0.02517604827880859490%|█████████ | 9/10 [08:54<00:59, 59.23s/it]test accuracy:0.6875, loss:0.22439222037792206
train accuracy:0.75, loss:0.39026355743408203
100%|██████████| 10/10 [09:53<00:00, 59.37s/it]test accuracy:0.8125, loss:0.20696812868118286

看起来还不错,最好的时候在测试集上的准确率为93.75%

参考

  1. 七月在线《NLP中的对话机器人》
  2. PyTorch官网

http://chatgpt.dhexx.cn/article/4g8gNrhj.shtml

相关文章

机器人是如何实现对话的?

对话机器人作为近年来愈发普遍的产品&#xff0c;以各种各样的形态出现我们的生活中&#xff1a;电话客服、文本客服、超市里的导购机器人等&#xff1b;那这些机器人如何实现和我们人类的智能对话的呢&#xff1f;本文将用显浅易懂的文字讲述机器人的对话原理与产品设计要点。…

Python3从零开始搭建一个语音对话机器人

从零开始搭建一个语音对话机器人 目录 01-初心缘由 02-准备工作 03-语音机器人的搭建思路 04-语音生成音频文件 05-音频文件转文字STT 06-与图灵机器人对话 07-文字转语音 08-语音对话机器人的完整代码 09-结束语 10-有问必答 01-初心缘由 最近在研究语音识别方向&a…

对话机器人

【居然审核不通过……内容低俗&#xff0c;这么高大上的内容&#xff0c;哪里低俗了……】 前面写了一系列的 微信机器人&#xff0c;但还没涉及到自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;。今天把这坑填上。本文将基于 Seq2Seq 模型和Little Y…

关于对话机器人,你需要了解这些技术

对话系统(对话机器人)本质上是通过机器学习和人工智能等技术让机器理解人的语言。它包含了诸多学科方法的融合使用,是人工智能领域的一个技术集中演练营。图1给出了对话系统开发中涉及到的主要技术。 对话系统技能进阶之路 图1给出的诸多对话系统相关技术,从哪些渠道可以…

对话机器人(一)——对话机器人基础知识

对话机器人基础 一、对话机器人分类 1. 知识领域 a. 面向限定领域 只能聊设定好的固定主题。若用户用无关领域挑战机器人&#xff0c;机器人用安全话术回复或结束对话。 b. 面向开放领域 用户不需要有明确的目的或意图。 c. 面向常用问题集 通过检索知识库来回答问题&a…

Typora无法在applist里找到

添加一个desktop文件即可&#xff0c;记得加上%U才能在应用列表里看见

php安装失败,phpcms安装失败怎么办

phpcms安装失败怎么办&#xff1f; 最新版的phpcmsV9安装报错解决 具体报错信息如下&#xff1a;Web-server: Apache PHP版本: PHP/5.2.14 Mysql版本: MySQL 客户端版本: 5.0.90 适用版本: v9 更新日期: phpcms_v9.2.2_UTF8 编码版本: UTF-8 浏览器: maxthon 复现步骤: 正在准备…

android应用程序列表,List列表应用程序-小知识 #103

文章摘要&#xff1a; 1、从设计模式的角度浅谈List列表应用程序开发。 2、列表应用程序开发三要素。控件、数据、适配器。 一、综述&#xff1a; 1、Android中&#xff0c;使用ListView配合Adapter来展示数据列表的例子随处可见。但在实际应用场景中&#xff0c;数据源类型、V…

推荐系统中的Embedding应用

文章目录 1. Word2Vec1.1 Skip-gram 2. Airbnb中的Embedding2.1 用在相似推荐中的List Embedding2.1.1 优化一&#xff1a;Booked Listing as Global Context2.1.2 优化二&#xff1a;Adapting Training for Congregated Search2.1.3 冷启动问题2.1.4 效果评估 2.2 用在搜索推荐…

APP设备数据的特征衍生与模型应用

在信贷风控领域众多维度的数据源中&#xff0c;APP设备数据对于策略规则的开发、模型变量的筛选有着重要的贡献&#xff0c;理由是在当今电子信息化时代&#xff0c;APP数据可以较全面地反映出用户的个人习惯、日常行为等综合信息。因此&#xff0c;金融机构在开展个人信贷产品…

风控建模十二:数据淘金——如何从APP数据中挖掘出有效变量

风控建模十二&#xff1a;数据淘金——如何从APP数据中挖掘出有效变量 1、常识知识2、个例分析3、分布排查 智能手机的诞生改变了人类的生活方式&#xff0c;智能手机所承载的功能日臻完善、强大&#xff0c;人们在衣、食、住、行、工作、生活中面临的方方面面问题&#xff0c;…

2021-03-07 大数据课程笔记 day46

R星校长 机器学习06【机器学习】 主要内容 理解推荐系统处理数据流程。python 文件预处理 Hive 数据。dubbo 服务使用。 学习目标 第一节 推荐系统-数据处理流程 推荐系统数据处理首先是将 Hive 中的用户 app 历史下载表与 app 浏览信息表按照设备 id 进行关联&#xff0c…

java手机应用安装目录_如何获得Android手机的软件安装列表

Android的PackageManager类用于检索目前安装在设备上的应用软件包的信息。你可以通过调用getpackagemanager()得到PackageManager类的一个实例。对查询和操作安装包和相关的权限提供了方法&#xff0c;在下面这个Android的例子中&#xff0c;我们得到了在Android安装的应用程序…

这些信贷数据埋点中不得不知的埋点知识

国庆七天假&#xff0c;就这样飞快结束&#xff0c;似乎感觉还没休息够&#xff0c;再来一个七天都不觉得多多。 经过多年来移动互联网的普及&#xff0c;众多APP已采集到亿级乃至数十亿级别用户在设备端、通话、短信、地址等强变量的数据&#xff0c;伴随着近年来信贷行业高速…

新浪微博开发(五)AppList界面

这是客户端开发部分很重要的一个类&#xff0c;但是在开发之前需要用到有关GridView的知识。 若要临时充充电&#xff0c;请移步&#xff1a;GridView(九宫图)的使用介绍。 下面是AppList类的代码&#xff1a; /* * 用来显示、管理自己的微博账号&#xff0c;包括新浪微博账号…

使用react+redux+react-redux+react-router+axios+scss技术栈从0到1开发一个applist应用

先看效果图 github地址 github仓库 在线访问 初始化项目 #创建项目 create-react-app applist #如果没有安装create-react-app的话&#xff0c;先安装 npm install -g create-react-app 目录结构改造 |--config |--node_modules |--public |--scripts |--src|-----api //…

【无标题】https://e-cloudstore.com/ec/api/applist/index.html#/

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

AppList数据处理

本文参考: 风控数据—手机App数据挖掘实践思路 引言 作为移动互联网时代的主要载体,智能手机逐渐成为人们日常生活中不可或缺的一部分,改变着人们的生活习惯。比如,可以用“饿了么”点外卖,“支付宝”可以用来种树,“抖音”可以用来上厕所......强大的App给我们的生活带来…

Faststone capture注册码

转载整理 很好用的图片编辑软件&#xff01; 企业版序列号&#xff1a; name&#xff1a;bluman serial/序列号/注册码&#xff1a;VPISCJULXUFGDDXYAUYF FastStone Capture 注册码 序列号&#xff1a; name/用户名&#xff1a;TEAM JiOO key/注册码&#xff1a;CPCWXRVCZW30H…

FastStone Capture(超级强大的截图、屏幕录制软件)

FastStone Capture是一款体积极其小、功能强悍的屏幕捕捉软件&#xff0c;还有强大的图片编辑、视频录制编辑功能&#xff0c;能够完全满足你截屏、处理图片的要求。FastStone Capture &#xff08;FSCapture&#xff09; 是经典好用的屏幕截图软件&#xff0c;还具有图像编辑和…