NLP面试题总结
一 算法题
1. 字典树构建,用字典树做搜索/分词; 求平方根
字典树构建和搜索/分词
Trie节点类
class TrieNode:
def __init__(self):
self.children = {}
self.is_end_of_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end_of_word = True
def search(self, word):
node = self.root
for char in word:
if char not in node.children:
return False
node = node.children[char]
return node.is_end_of_word
def starts_with(self, prefix):
node = self.root
for char in prefix:
if char not in node.children:
return False
node = node.children[char]
return True
def tokenize(self, text):
node = self.root
start = 0
result = []
for i, char in enumerate(text):
if char in node.children:
node = node.children[char]
if node.is_end_of_word:
result.append(text[start:i+1])
start = i + 1
node = self.root
else:
node = self.root
if start < i:
result.append(text[start:i])
start = i
if start < len(text):
result.append(text[start:])
return result
使用示例
trie = Trie()
words = ["hello", "world", "this", "is", "a", "test"]
for word in words:
trie.insert(word)
print(trie.search("hello")) # True
print(trie.search("hell")) # False
print(trie.starts_with("he")) # True
text = "hellothisisatest"
tokens = trie.tokenize(text)
print(tokens) # ['hello', 'this', 'is', 'a', 'test']
求平方根
使用牛顿迭代法
def sqrt_newton(n, tolerance=1e-10):
if n < 0:
raise ValueError("Cannot compute square root of a negative number.")
if n == 0:
return 0
x = n
while True:
root = 0.5 * (x + (n / x))
if abs(root - x) < tolerance:
return root
x = root
# 使用示例
print(sqrt_newton(25)) # 5.0
print(sqrt_newton(2)) # 1.414213562373095
使用Python内置函数
import math
def sqrt_builtin(n):
if n < 0:
raise ValueError("Cannot compute square root of a negative number.")
return math.sqrt(n)
# 使用示例
print(sqrt_builtin(25)) # 5.0
print(sqrt_builtin(2)) # 1.4142135623730951
2. 编辑距离
def minDistance(word1, word2):
m, n = len(word1), len(word2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
dp[i][0] = i
for j in range(n + 1):
dp[0][j] = j
for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1)
return dp[m][n]
# 使用示例
print(minDistance("horse", "ros")) # 3
3. 正则表达式匹配
def isMatch(s, p):
m, n = len(s), len(p)
dp = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True
for j in range(1, n + 1):
if p[j - 1] == '*':
dp[0][j] = dp[0][j - 2]
for i in range(1, m + 1):
for j in range(1, n + 1):
if p[j - 1] in {s[i - 1], '.'}:
dp[i][j] = dp[i - 1][j - 1]
elif p[j - 1] == '*':
dp[i][j] = dp[i][j - 2] or (dp[i - 1][j] if p[j - 2] in {s[i - 1], '.'} else False)
return dp[m][n]
# 使用示例
print(isMatch("aab", "c*a*b")) # True
4. 寻找两个有序数组的中位数
def findMedianSortedArrays(nums1, nums2):
A, B = nums1, nums2
m, n = len(A), len(B)
if m > n:
A, B, m, n = B, A, n, m
imin, imax, half_len = 0, m, (m + n + 1) // 2
while imin <= imax:
i = (imin + imax) // 2
j = half_len - i
if i < m and B[j-1] > A[i]:
imin = i + 1
elif i > 0 and A[i-1] > B[j]:
imax = i - 1
else:
if i == 0: max_of_left = B[j-1]
elif j == 0: max_of_left = A[i-1]
else: max_of_left = max(A[i-1], B[j-1])
if (m + n) % 2 == 1:
return max_of_left
if i == m: min_of_right = B[j]
elif j == n: min_of_right = A[i]
else: min_of_right = min(A[i], B[j])
return (max_of_left + min_of_right) / 2.0
# 使用示例
print(findMedianSortedArrays([1, 3], [2])) # 2.0
5. 合并K个升序链表变体
from heapq import heappop, heappush
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def mergeKLists(lists):
heap = []
for i in range(len(lists)):
if lists[i]:
heappush(heap, (lists[i].val, i, lists[i]))
head = ListNode()
curr = head
while heap:
val, i, node = heappop(heap)
curr.next = ListNode(val)
curr = curr.next
if node.next:
heappush(heap, (node.next.val, i, node.next))
return head.next
# 使用示例
# 构建链表
list1 = ListNode(1, ListNode(4, ListNode(5)))
list2 = ListNode(1, ListNode(3, ListNode(4)))
list3 = ListNode(2, ListNode(6))
lists = [list1, list2, list3]
result = mergeKLists(lists)
while result:
print(result.val, end=" -> ")
result = result.next # 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> 5 -> 6 ->
6. 岛屿数量
def numIslands(grid):
if not grid:
return 0
def dfs(grid, r, c):
grid[r][c] = '0'
for x, y in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:
if 0 <= x < len(grid) and 0 <= y < len(grid[0]) and grid[x][y] == '1':
dfs(grid, x, y)
count = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == '1':
dfs(grid, r, c)
count += 1
return count
# 使用示例
grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
print(numIslands(grid)) # 1
7. 二叉树层次遍历
from collections import deque
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def levelOrder(root):
if not root:
return []
result, queue = [], deque([root])
while queue:
level_size = len(queue)
level_nodes = []
for _ in range(level_size):
node = queue.popleft()
level_nodes.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level_nodes)
return result
# 使用示例
root = TreeNode(3)
root.left = TreeNode(9)
root.right = TreeNode(20, TreeNode(15), TreeNode(7))
print(levelOrder(root)) # [[3], [9, 20], [15, 7]]
8. 二叉树最大路劲和
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def maxPathSum(root):
def helper(node):
nonlocal max_sum
if not node:
return 0
left = max(helper(node.left), 0)
right = max(helper(node.right), 0)
max_sum = max(max_sum, node.val + left + right)
return node.val + max(left, right)
max_sum = float('-inf')
helper(root)
return max_sum
# 使用示例
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
print(maxPathSum(root)) # 6
9. 数组中第K大的元素
import heapq
def findKthLargest(nums, k):
heap = nums[:k]
heapq.heapify(heap)
for num in nums[k:]:
if num > heap[0]:
heapq.heappushpop(heap, num)
return heap[0]
# 使用示例
print(findKthLargest([3,2,1,5,6,4], 2)) # 5
10. python解析json结构,正则表达式匹配(re)
Python 解析 JSON 结构
Python 提供了内置的 json
模块来处理 JSON 数据。以下是解析 JSON 的一些基本操作示例:
解析 JSON 字符串
import json
json_str = '{"name": "John", "age": 30, "city": "New York"}'
data = json.loads(json_str)
print(data)
print(data['name'])
print(data['age'])
print(data['city'])
解析 JSON 文件
import json
with open('data.json', 'r') as file:
data = json.load(file)
print(data)
将 Python 对象转换为 JSON 字符串
import json
data = {
"name": "John",
"age": 30,
"city": "New York"
}
json_str = json.dumps(data)
print(json_str)
将 Python 对象写入 JSON 文件
import json
data = {
"name": "John",
"age": 30,
"city": "New York"
}
with open('data.json', 'w') as file:
json.dump(data, file)
正则表达式匹配(re 模块)
Python 的 re
模块提供了对正则表达式的支持,可以用于字符串的模式匹配和替换等操作。
匹配字符串
import re
pattern = r'\d+'
string = 'There are 123 numbers in this 456 string.'
matches = re.findall(pattern, string)
print(matches) # ['123', '456']
使用 re.search
进行匹配
import re
pattern = r'\d+'
string = 'There are 123 numbers in this 456 string.'
match = re.search(pattern, string)
if match:
print(match.group()) # 123
使用 re.match
进行匹配
import re
pattern = r'\d+'
string = '123 numbers in this string.'
match = re.match(pattern, string)
if match:
print(match.group()) # 123
使用 re.sub
进行替换
import re
pattern = r'\d+'
string = 'There are 123 numbers in this 456 string.'
new_string = re.sub(pattern, '#', string)
print(new_string) # 'There are # numbers in this # string.'
使用 re.split
进行分割
import re
pattern = r'\d+'
string = 'There are 123 numbers in this 456 string.'
result = re.split(pattern, string)
print(result) # ['There are ', ' numbers in this ', ' string.']
编译正则表达式
import re
pattern = re.compile(r'\d+')
string = 'There are 123 numbers in this 456 string.'
matches = pattern.findall(string)
print(matches) # ['123', '456']
这些示例展示了如何使用 Python 的 json
模块解析 JSON 数据以及使用 re
模块进行正则表达式匹配和操作。
11. 最长子序列
最长递增子序列(Longest Increasing Subsequence, LIS)问题是一个经典的动态规划问题。其目的是在给定的序列中找到一个最长的子序列,使得该子序列中的元素按升序排列。
def length_of_lis(nums):
if not nums:
return 0
n = len(nums)
dp = [1] * n
for i in range(1, n):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
# 测试
nums = [10, 9, 2, 5, 3, 7, 101, 18]
print("最长递增子序列长度为:", length_of_lis(nums)) # 输出: 4 (子序列: [2, 3, 7, 101])
12. 找出和为给定目标值(target)的连续子数组
滑动窗口算法适用于找和为目标值的连续子数组。通过调整窗口的左右边界来尝试找到符合条件的子数组。
思路
- 使用两个指针(
start
和end
)来定义当前窗口的左右边界。 - 计算当前窗口的和。
- 如果当前窗口的和小于目标值,则扩展右边界(
end
)。 - 如果当前窗口的和大于目标值,则收缩左边界(
start
)。 - 如果当前窗口的和等于目标值,则记录结果并尝试找到下一个子数组。
def find_subarray_with_sum(nums, target):
start = 0
current_sum = 0
for end in range(len(nums)):
current_sum += nums[end]
while current_sum > target and start <= end:
current_sum -= nums[start]
start += 1
if current_sum == target:
return nums[start:end + 1]
return None
# 测试
nums = [1, 2, 3, 7, 5]
target = 12
result = find_subarray_with_sum(nums, target)
print("和为目标值的连续子数组:", result) # 输出: [5, 7]
13. 有一组无序数组,怎么取前10个最大的数
通过维护一个固定大小的最小堆(堆的大小为 10),可以在遍历数组时逐步更新这个堆,以便始终保持堆中的元素是当前数组中的前 10 个最大数。
import heapq
# 示例数组
arr = [5, 1, 9, 6, 8, 3, 7, 10, 4, 2, 11, 15, 14, 13, 12]
# 使用一个最小堆来维护前10个最大的数
min_heap = []
for num in arr:
if len(min_heap) < 10:
heapq.heappush(min_heap, num)
else:
if num > min_heap[0]:
heapq.heappushpop(min_heap, num)
# 最小堆中的元素就是前10个最大的数
top_10 = sorted(min_heap, reverse=True)
print(top_10)
二 八股文
1. deepspeed框架介绍
DeepSpeed 是微软开发的一个深度学习优化库,旨在使大规模模型训练更快、更高效。它为分布式训练和推理提供了强大的功能,特别适用于在大规模模型和数据集上进行训练。
主要特性
- 高效的大规模模型训练:
- 支持超过万亿参数的大规模模型训练。
- 通过混合精度训练、梯度累积和 ZeRO 优化器等技术来提升训练效率和降低显存占用。
- ZeRO 优化器:
- ZeRO (Zero Redundancy Optimizer) 是 DeepSpeed 的核心组件,分为多个阶段:
- Stage 1:优化器状态分片,将优化器状态分散到多个 GPU 上。
- Stage 2:梯度分片,将梯度分散到多个 GPU 上。
- Stage 3:参数分片,将模型参数分散到多个 GPU 上。
- 这些技术使得在多 GPU 环境下训练大模型成为可能,并显著降低显存使用。
- ZeRO (Zero Redundancy Optimizer) 是 DeepSpeed 的核心组件,分为多个阶段:
- 深度学习优化器:
- 提供了一系列优化器,包括 Adam、Lamb 等,通过优化计算效率和内存使用,使训练过程更加高效。
- 分布式训练支持:
- 通过数据并行、模型并行和流水线并行等多种策略,支持大规模分布式训练。
- 与 PyTorch 深度集成,简化了分布式训练的配置和管理。
- 混合精度训练:
- 支持 FP16 混合精度训练,通过降低计算精度来加速训练并减少显存占用。
- 内存优化技术:
- 通过内存优化技术,如内存压缩和分布式检查点,进一步减少显存占用,提高训练效率。
- 高效推理:
- 提供高效的推理优化技术,支持大规模模型的快速推理。
2. lora介绍,了解什么其他高效微调方法
LoRA(Low-Rank Adaptation)是一种高效的微调方法,旨在降低大模型在微调过程中的计算和存储开销。它通过将模型参数的微调限制在低秩子空间中,从而减少了需要更新的参数数量和计算量。
LoRA 的基本原理
LoRA 的核心思想是对神经网络中的权重矩阵进行低秩分解,并在微调过程中只更新分解后的低秩矩阵。这可以显著减少参数更新的开销。
具体来说,对于一个权重矩阵 W ∈ R d × d W \in \mathbb{R}^{d \times d} W∈Rd×d,LoRA 将其分解为两个低秩矩阵 A ∈ R d × r A \in \mathbb{R}^{d \times r} A∈Rd×r和 B ∈ R r × d B \in \mathbb{R}^{r \times d} B∈Rr×d,其中 r ≪ d r \ll d r≪d。微调过程中,只更新 A A A 和 B B B,而不是整个 W W W。
LoRA 的实现
以下是一个简化的 LoRA 实现示例:
import torch
import torch.nn as nn
class LoRALayer(nn.Module):
def __init__(self, original_layer, r):
super(LoRALayer, self).__init__()
self.original_layer = original_layer
self.r = r
self.A = nn.Parameter(torch.randn(original_layer.weight.size(0), r))
self.B = nn.Parameter(torch.randn(r, original_layer.weight.size(1)))
def forward(self, x):
return self.original_layer(x) + (x @ self.B.t()) @ self.A.t()
# 使用示例
original_layer = nn.Linear(128, 128)
lora_layer = LoRALayer(original_layer, r=16)
# 输入数据
x = torch.randn(32, 128)
output = lora_layer(x)
其他高效微调方法
除了 LoRA,还有一些其他高效的微调方法,主要包括:
-
Adapter Layers:
- 通过在预训练模型的层之间插入适配器层(Adapter Layers)来实现高效微调。适配器层通常包含少量参数,只在微调过程中更新。
- 适配器方法的一个典型实现是将小型瓶颈层插入到每个层的输出和输入之间。
class AdapterLayer(nn.Module): def __init__(self, hidden_size, bottleneck_size): super(AdapterLayer, self).__init__() self.down = nn.Linear(hidden_size, bottleneck_size) self.up = nn.Linear(bottleneck_size, hidden_size) self.activation = nn.ReLU() def forward(self, x): return x + self.up(self.activation(self.down(x))) # 使用示例 adapter_layer = AdapterLayer(hidden_size=768, bottleneck_size=64) x = torch.randn(32, 768) output = adapter_layer(x)
-
Prefix-Tuning:
- 在序列的开始部分添加一组可学习的前缀向量,在模型的每个层中应用这些前缀向量,以影响模型的输出。
- 这种方法特别适用于生成任务,如文本生成和翻译。
class PrefixTuning(nn.Module): def __init__(self, hidden_size, prefix_length): super(PrefixTuning, self).__init__() self.prefix = nn.Parameter(torch.randn(prefix_length, hidden_size)) def forward(self, x): prefix_repeated = self.prefix.unsqueeze(0).expand(x.size(0), -1, -1) return torch.cat([prefix_repeated, x], dim=1) # 使用示例 prefix_tuning = PrefixTuning(hidden_size=768, prefix_length=10) x = torch.randn(32, 100, 768) output = prefix_tuning(x)
-
Prompt-Tuning:
- 类似于 Prefix-Tuning,但在输入序列中插入可学习的提示(prompts),这些提示可以是输入序列的前缀、后缀或中间部分。
- Prompt-Tuning 在处理任务特定的微调时非常有效。
-
BitFit:
- BitFit(Bias-only Fine-tuning)方法只更新预训练模型中的偏置参数(bias parameters),而冻结所有其他参数。
- 这种方法极大地减少了需要更新的参数数量,同时在许多任务上仍能保持较高的性能。
class BitFit(nn.Module): def __init__(self, original_layer): super(BitFit, self).__init__() self.weight = original_layer.weight self.bias = nn.Parameter(original_layer.bias.clone().detach()) def forward(self, x): return nn.functional.linear(x, self.weight, self.bias) # 使用示例 original_layer = nn.Linear(128, 128) bitfit_layer = BitFit(original_layer) # 输入数据 x = torch.randn(32, 128) output = bitfit_layer(x)
总结
LoRA 和上述其他高效微调方法通过减少需要更新的参数数量和计算量,使得在大规模预训练模型上进行微调更加高效。这些方法在各种任务中表现良好,并且在资源受限的环境中尤为有用。
3. prompt tuning, instruct tuning, fine tuning差别
Prompt Tuning:通过可学习的提示向量引导模型生成特定输出,参数更新少,适合少样本学习和文本生成任务。
Instruct Tuning:通过明确的任务指令引导模型完成特定任务,适合多任务学习和任务指导。
Fine Tuning:通过全面或部分更新模型参数,使模型适应特定任务,适合特定任务的性能提升和迁移学习。
4. llama中per norm,rmsnorm的介绍,优劣,position embedding构造方法
在 LLaMA(Large Language Model Meta AI)及其他大型语言模型中,Per-Norm、RMSNorm 以及位置嵌入(Position Embedding)是常用的技术。以下是对它们的介绍、优劣分析以及位置嵌入的构造方法:
1. Per-Norm
介绍
Per-Norm(Per-Layer Normalization)是一种在每一层进行归一化的技术,主要用于加速训练并提高模型的稳定性。
实现方式
- 每层归一化:在每一层的输出上应用归一化操作,通常是对层的输出进行均值为0、方差为1的标准化。
- 参数化:可以有可学习的参数用于缩放和偏移。
优劣分析
- 优点:
- 稳定训练:通过每层归一化,可以减小梯度爆炸或消失的风险,提高训练稳定性。
- 加速收敛:归一化可以使模型更快地收敛,减少训练时间。
- 缺点:
- 计算开销:每一层都进行归一化操作会增加一定的计算开销。
- 不适用所有模型:在某些模型结构或任务中,效果可能不如其他归一化方法。
2. RMSNorm
介绍
RMSNorm(Root Mean Square Normalization)是一种变体的归一化方法,与LayerNorm(层归一化)相比,它使用均方根(RMS)来进行归一化。
实现方式
- 均方根归一化:计算输入张量的均方根值,并使用该值对输入进行归一化。
- 公式:设输入为 $ x$,则归一化后的输出为 $y = \frac{x}{\text{RMS}(x)} \cdot \gamma + \beta $,其中 $\gamma $ 和 $ \beta $ 是可学习的参数。
import torch
import torch.nn as nn
class RMSNorm(nn.Module):
def __init__(self, dim, eps=1e-8):
super().__init__()
self.eps = eps
self.scale = nn.Parameter(torch.ones(dim))
def forward(self, x):
rms = torch.sqrt(torch.mean(x ** 2, dim=-1, keepdim=True) + self.eps)
return x / rms * self.scale
优劣分析
- 优点:
- 计算效率高:RMSNorm 的计算量较少,比 LayerNorm 更加高效。
- 适用于深层模型:在深层模型中,RMSNorm 可以提供更稳定的训练过程。
- 缺点:
- 表达能力有限:相比 LayerNorm,RMSNorm 在某些复杂任务中的表现可能不如 LayerNorm。
3. Position Embedding 构造方法
位置嵌入(Position Embedding)是用于编码序列中元素位置的信息,以便模型能够理解序列顺序。常见的构造方法有以下几种:
a. 绝对位置编码(Absolute Position Embedding)
每个位置都有一个唯一的嵌入向量,通常通过查找表实现。
import torch.nn as nn
class AbsolutePositionEmbedding(nn.Module):
def __init__(self, max_len, embed_dim):
super().__init__()
self.position_embeddings = nn.Embedding(max_len, embed_dim)
def forward(self, x):
positions = torch.arange(x.size(1), device=x.device).unsqueeze(0)
return x + self.position_embeddings(positions)
b. 正弦-余弦位置编码(Sinusoidal Position Embedding)
通过正弦和余弦函数生成位置编码,通常用于Transformer模型。
import torch
def sinusoidal_position_encoding(max_len, embed_dim):
position = torch.arange(max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, embed_dim, 2) * -(torch.log(torch.tensor(10000.0)) / embed_dim))
pos_encoding = torch.zeros(max_len, embed_dim)
pos_encoding[:, 0::2] = torch.sin(position * div_term)
pos_encoding[:, 1::2] = torch.cos(position * div_term)
return pos_encoding.unsqueeze(0)
class SinusoidalPositionEmbedding(nn.Module):
def __init__(self, max_len, embed_dim):
super().__init__()
self.pos_encoding = sinusoidal_position_encoding(max_len, embed_dim)
def forward(self, x):
return x + self.pos_encoding[:, :x.size(1), :]
c. 相对位置编码(Relative Position Embedding)
编码相对位置,以捕捉元素之间的相对顺序关系。
import torch
import torch.nn as nn
class RelativePositionEmbedding(nn.Module):
def __init__(self, max_len, embed_dim):
super().__init__()
self.relative_positions = nn.Parameter(torch.randn(max_len, max_len, embed_dim))
def forward(self, x):
batch_size, seq_len, _ = x.size()
relative_positions = self.relative_positions[:seq_len, :seq_len]
return x + relative_positions.unsqueeze(0).repeat(batch_size, 1, 1, 1)
优劣分析
绝对位置编码
- 优点:
- 简单直接:实现和理解都比较简单。
- 性能好:在许多任务中表现良好。
- 缺点:
- 无法处理长序列:编码长度受限于预定义的最大长度。
正弦-余弦位置编码
- 优点:
- 无需训练参数:不需要额外的训练参数。
- 泛化能力强:对不同长度的序列有较好的泛化能力。
- 缺点:
- 相对复杂:实现起来相对复杂。
相对位置编码
- 优点:
- 捕捉相对顺序信息:能够更好地捕捉元素之间的相对顺序关系。
- 缺点:
- 计算复杂度高:计算相对位置嵌入的开销较大。
总的来说,选择哪种方法取决于具体的应用场景和需求。每种方法都有其优缺点,适用于不同的任务和模型结构。
4. prompt构造经验,怎样的prompt更好
设计有效的提示(prompt)对于提升语言模型(如 GPT)生成结果的质量至关重要。以下是一些构造高效提示的经验和技巧:
1. 明确任务目标
- 清晰性:提示应该明确指出任务的目标或期望的输出类型。避免模糊不清的描述。
- 具体性:具体地说明你想要模型做什么。例如,“请解释”比“解释”要明确得多。
2. 提供足够的上下文
- 背景信息:如果任务涉及特定领域或背景,确保提示中包含必要的背景信息。例如,在要求解释技术术语时,可以先提供相关的定义或示例。
- 示例:使用示例可以帮助模型理解预期的输出格式或风格。例如,提供一个问题和答案的示例,让模型知道你希望获得类似的答案。
3. 设定清晰的格式
- 格式化:如果希望模型生成特定格式的输出(如列表、表格或特定结构的文本),在提示中明确说明。例如,“请以列表形式列出……”或“请生成一段描述,包括以下要点……”。
- 标记:使用标记或符号来引导模型。例如,“请总结以下内容:”然后列出要总结的文本。
4. 使用开放性问题
- 引导深入回答:对于需要详细回答的问题,可以使用开放性问题而不是是非题。例如,“你如何看待……?”比“是否认为……?”更能引导模型生成详细的回答。
示例
1. 清晰和具体的任务
- 一般: “请告诉我关于深度学习的知识。”
- 优化后:“请简要解释什么是深度学习,并提供一个简单的应用示例。”
2. 提供背景信息和示例
- 一般:“请生成一个新闻标题。”
- 优化后:“请生成一个关于最新科技新闻的标题。示例:‘突破性科技进展:新型量子计算机的问世’。”
3. 格式化输出
- 一般:“列出一些编程语言。”
- 优化后:“请以列表形式列出五种流行的编程语言及其主要用途。”
通过这些策略,你可以构造出更高效、更准确的提示,从而提升模型生成结果的质量。
5. 了解哪些深度学习模型(MLP、CNN、RNN、Transformer),说一下各自的优缺点
深度学习模型在各种任务中发挥着重要作用,每种模型都有其独特的优势和局限性。以下是对主要深度学习模型(MLP、CNN、RNN、Transformer)的介绍,以及它们的优缺点:
1. MLP (Multilayer Perceptron)
概述
- 结构:由多个全连接层(也称为线性层)构成,通常包括输入层、多个隐藏层和输出层。
- 激活函数:常用的激活函数包括ReLU、Sigmoid、Tanh等。
优点
- 简单易懂:结构简单,易于理解和实现。
- 适用广泛:可以用于分类、回归等任务。
缺点
- 特征提取能力差:不擅长处理空间和时间序列数据,因为它没有专门的机制来捕捉数据中的空间结构或时间依赖。
- 计算成本高:对于大规模数据集和复杂任务,MLP可能需要较长的训练时间和大量的计算资源。
- 梯度消失和梯度爆炸:在训练深层网络时,可能会遇到梯度消失或梯度爆炸问题。
2. CNN (Convolutional Neural Network)
概述
- 结构:包括卷积层、池化层和全连接层。卷积层用于提取特征,池化层用于降维和减少计算复杂度。
- 激活函数:常用的激活函数包括ReLU。
优点
- 特征提取能力强:能够自动学习输入数据中的空间特征,特别适合处理图像数据。
- 参数共享:通过卷积操作减少了模型的参数数量,从而降低计算成本。
- 平移不变性:卷积操作使得网络能够识别图像中的局部特征,并对平移和变形具有一定的不变性。
缺点
- 对位置变化敏感:虽然卷积操作对平移有一定的不变性,但对更复杂的变化(如旋转、尺度变化)可能不够鲁棒。
- 计算资源需求高:对于高分辨率图像或深层网络,计算和存储需求较大。
3. RNN (Recurrent Neural Network)
概述
- 结构:通过递归连接来处理序列数据,能够捕捉时间序列中的依赖关系。
- 变体:包括LSTM(长短期记忆网络)和GRU(门控循环单元),用于解决标准RNN的梯度消失和梯度爆炸问题。
优点
- 时间序列建模:能够处理时间序列数据和具有时间依赖关系的任务,如语音识别和语言建模。
- 记忆能力:LSTM和GRU通过门控机制改进了对长期依赖关系的建模能力。
缺点
- 训练困难:标准RNN在处理长序列时容易遇到梯度消失和梯度爆炸问题。
- 计算效率低:训练时间较长,因为RNN需要逐步处理序列中的每一个时间步。
4. Transformer
概述
- 结构:基于自注意力机制(Self-Attention),由编码器和解码器组成。编码器用于处理输入序列,解码器用于生成输出序列。
- 变体:包括BERT(Bidirectional Encoder Representations from Transformers)、GPT(Generative Pre-trained Transformer)等。
优点
- 并行计算:由于自注意力机制不依赖于序列的顺序处理,可以实现高度的并行计算,从而加速训练过程。
- 长距离依赖建模:能够有效捕捉序列中长距离的依赖关系,比RNN更擅长处理长序列。
- 灵活性强:Transformer模型在NLP领域取得了显著的成功,广泛应用于各种任务,如文本生成、翻译和问答系统。
缺点
- 计算资源需求高:自注意力机制的计算复杂度为O(n^2),对于非常长的序列,计算和存储需求可能非常高。
- 模型规模大:大型Transformer模型(如GPT-3)需要大量的参数和计算资源,训练和推理成本较高。
总结
- MLP:适用于简单任务,结构简单但特征提取能力较差。
- CNN:在图像处理方面表现优异,能够有效提取空间特征,但对位置变化的鲁棒性有限。
- RNN:擅长处理序列数据和时间依赖问题,但训练复杂度高,容易遇到梯度问题。
- Transformer:能够处理长序列和复杂依赖关系,训练和推理效率高,但计算资源需求较大。
这些模型各有优缺点,选择合适的模型取决于具体的应用场景和需求。
6. RNN有哪些问题,如何缓解
循环神经网络(RNN)在处理序列数据时有许多优点,但也存在一些问题。以下是RNN面临的主要问题及其缓解方法:
1. 梯度消失和梯度爆炸
问题
- 梯度消失:在长序列的训练过程中,梯度可能会逐渐减小,导致模型难以学习长期依赖关系。
- 梯度爆炸:梯度在反向传播过程中可能会迅速增大,导致模型参数更新不稳定。
缓解方法
- 使用LSTM(长短期记忆网络):LSTM通过引入门控机制(输入门、遗忘门、输出门)来控制信息的流动,缓解梯度消失问题。
- 使用GRU(门控循环单元):GRU是LSTM的简化版本,也通过门控机制改进了对长期依赖关系的建模。
- 梯度裁剪(Gradient Clipping):在训练过程中,对梯度进行裁剪,以防止梯度爆炸。这可以通过限制梯度的最大值来实现。
2. 计算效率低
问题
- 逐步处理:RNN需要逐步处理序列中的每一个时间步,导致训练和推理速度较慢,难以并行计算。
缓解方法
- 使用Transformer:Transformer模型通过自注意力机制不依赖于序列的顺序处理,可以并行计算,显著提高训练和推理速度。
- 使用批处理(Batch Processing):在训练过程中,将序列拆分成多个小批次,利用GPU进行并行计算,提高效率。
3. 长期依赖问题
问题
- 长期依赖难以捕捉:RNN在处理很长的序列时,难以捕捉远距离的依赖关系。
缓解方法
- 使用LSTM或GRU:这些变体通过门控机制来有效处理长期依赖问题,能够记住较长时间跨度的信息。
- 使用卷积神经网络(CNN):在一些任务中,使用CNN进行特征提取并结合RNN处理序列信息,能够帮助捕捉长距离依赖。
4. 训练时间长
问题
- 训练复杂度高:由于RNN的逐步计算特性,训练时间可能较长,尤其是在长序列和大规模数据集上。
缓解方法
- 模型简化:使用更简单的模型或简化RNN的架构(如简化LSTM或GRU)。
- 使用高效的优化器:例如,Adam优化器可以加速训练过程并提高收敛速度。
5. 记忆效能有限
问题
- 记忆能力有限:即使使用LSTM和GRU,RNN在处理极长的序列时,仍然可能会遇到记忆能力不足的问题。
缓解方法
- 分层RNN:使用多层RNN或双向RNN来增强模型的记忆能力。
- Attention机制:结合注意力机制,可以显著提高对重要信息的关注,从而增强模型的记忆能力。
6. 训练数据依赖
问题
- 对训练数据的依赖:RNN在训练过程中对数据顺序和结构的依赖可能导致模型泛化能力有限。
缓解方法
- 数据增强:通过数据增强技术(如数据合成、噪声添加等)增加训练数据的多样性,从而提高模型的泛化能力。
- 正则化技术:如Dropout、L2正则化等,可以帮助模型更好地泛化到新数据。
通过这些方法,可以有效地缓解RNN在处理序列数据时面临的一些主要问题,提高模型的性能和效率。
7. 哈夫曼树
哈夫曼树利用频率较高的字符用较短的编码,频率较低的字符用较长的编码,从而达到压缩数据的目的。哈夫曼树的构建过程依赖于字符的频率,最终生成的哈夫曼编码具有前缀性质,即任何一个编码都不是另一个编码的前缀,从而确保了编码的唯一性和无二义性。
8. 动态规划算法思想
动态规划(Dynamic Programming, DP)是一种用于解决具有重叠子问题和最优子结构性质的问题的算法设计方法。其核心思想是通过将复杂问题分解为更小的子问题,保存子问题的解以避免重复计算,从而提高算法效率。动态规划广泛应用于各种优化问题,如最短路径、背包问题、最长公共子序列等。
动态规划其实就是,给定一个问题,我们把它拆成一个个子问题,直到子问题可以直接解决。然后呢,把子问题答案保存起来,以减少重复计算。再根据子问题答案反推,得出原问题解的一种方法。
9. word2vector的两种改进方法
Word2Vec是一种将词语表示为向量的方法,主要有两种训练方式:CBOW(Continuous Bag of Words)和Skip-gram。这两种方法本身在很多实际应用中表现出色,但也有一些改进方法可以进一步提升其性能和效果。
word2vec有两种改进方法,一种是基于Hierarchical Softmax的,另一种是基于Negative Sampling的。
10. 在语言模型算法中可不可以使用两层lstm
在语言模型算法中使用两层LSTM(Long Short-Term Memory)是非常常见且有效的。LSTM是一种特殊的递归神经网络(RNN),设计用于捕捉序列数据中的长程依赖关系。相比于传统的RNN,LSTM能够更好地处理长序列中的梯度消失和梯度爆炸问题。
11. robert和bert区别在哪
- 使用更丰富的数据(160GB数据)、更充分的训练(训练时间或迭代步数)、更大的batch_size。
- 使用动态的masking机制,每次向模型提供输入时动态地mask。具体是对训练数据进行10次拷贝,每个拷贝使用不同的mask。
- 支持更长的输入序列
- 移除NSP任务,RoBERTa实验证明移除NSP后效果比BERT好
12. jieba 分词的原理
(1)基于前缀词典实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图 (DAG)
(2)采用了动态规划查找最大概率路径, 找出基于词频的最大切分组合
(3)对于未登录词,采用了基于汉字成词能力的 HMM 模型,使用了 Viterbi 算法
13. word2vec的原理,怎么训练的
word2vec
是一种用于将词语映射到连续向量空间的技术,其目标是将词语表示为向量,使得语义上相似的词在向量空间中距离较近。word2vec
主要有两种训练模型:连续词袋模型(CBOW) 和 跳字模型(Skip-gram)
14. 介绍bert,GPT,以及其区别
BERT是一种基于Transformer的预训练模型,它的目标是通过双向语言模型预训练来学习上下文相关的词表示。在预训练过程中,BERT通过掩码语言模型(Masked Language Model,MLM)和下一句预测(Next Sentence Prediction,NSP)任务进行训练。
- 掩码语言模型(Masked Language Model,MLM):在输入序列中,BERT随机掩盖一些词语,然后要求模型预测这些被掩盖的词语。通过这个任务,BERT可以学习到在给定上下文的情况下,预测缺失词语的能力。这使得BERT能够理解词语的语义和上下文信息。具体来说,对于每个输入序列,BERT会随机选择一些词语进行掩码。通常,选择的词语占总词语数量的15%左右。
- 下一句预测(Next Sentence Prediction,NSP):在一些自然语言处理任务中,理解句子之间的关系是很重要的。为了让模型学习句子级别的关系,BERT使用了NSP任务。该任务要求模型判断两个句子是否是连续的,即一个句子是否是另一个句子的下一句。通过这个任务,BERT能够学习到句子级别的语义关系和推理能力。显式地建模文本对之间的逻辑关系。
GPT是一种基于Transformer的生成式预训练模型,其目标是通过自回归语言模型预训练来学习生成连贯文本的能力。GPT采用了自回归语言模型的预训练方式。在预训练过程中,GPT使用大规模的文本数据,并通过自回归的方式逐步生成下一个词语。模型根据已生成的上文预测下一个词语,通过最大似然估计来优化模型参数。这使得GPT能够学习到生成连贯、有逻辑性的文本的能力。
训练方式:
BERT:BERT使用了双向语言模型的训练策略。在输入序列中,BERT随机掩盖一些词语,并让模型预测这些被掩盖的词语。这种方式使BERT能够从上下文中学习词语的语义和语境信息。
GPT:GPT使用了自回归语言模型的训练方式。它通过让模型预测当前位置的词语来学习生成文本的能力。在预训练过程中,GPT逐步生成下一个词语,并优化参数以最大化下一个词语的概率。
上下文理解能力:
两种基于Transformer架构的预训练模型,它们在上下文理解能力和应用领域上有所不同。
BERT:由于BERT采用双向模型,通过预测被掩盖的词语和判断句子之间的关系。它可以从上下文中获取更丰富的信息,并具有较强的上下文理解能力。这使得BERT在词语级别的任务中表现出色,如命名实体识别、问答等。
GPT:GPT是一个单向模型,它只能依赖已生成的上文来预测下一个词语。在预训练过程中,GPT使用自回归语言模型进行训练,通过逐步生成下一个词语来学习生成连贯的文本。由于单向模型的限制,GPT在生成式任务中表现较好,如对话生成、文本生成等。GPT能够生成具有上下文连贯性和逻辑性的文本,因为它在生成每个词语时都能考虑之前已生成的上文。
15. bert及其变体介绍
- BERT:通过预测文本中被遮盖的词语和判断一个文本是否跟随另一个来进行预训练,前一个任务被称为遮盖语言建模 (Masked Language Modeling, MLM),后一个任务被称为下句预测 (Next Sentence Prediction, NSP);
- DistilBERT:尽管 BERT 性能优异,但它的模型大小使其难以部署在低延迟需求的环境中。 通过在预训练期间使用知识蒸馏 (knowledge distillation) 技术,DistilBERT 在内存占用减少 40%、计算速度提高 60% 的情况下,依然可以保持 97% 的性能;
- RoBERTa:BERT 之后的一项研究表明,通过修改预训练方案可以进一步提高性能。 RoBERTa 在更多的训练数据上,以更大的批次训练了更长的时间,并且放弃了 NSP 任务。与 BERT 模型相比,这些改变显著地提高了模型的性能;
- XLM:跨语言语言模型 (XLM) 探索了构建多语言模型的多个预训练目标,包括来自 GPT 的自回归语言建模和来自 BERT 的 MLM,还将 MLM 拓展到多语言输入,提出了翻译语言建模 (Translation Language Modeling, TLM)。XLM 在多个多语言 NLU 基准和翻译任务上都取得了最好的性能;
- XLM-RoBERTa:跟随 XLM 和 RoBERTa,XLM-RoBERTa (XLM-R) 通过升级训练数据来改进多语言预训练。其基于 Common Crawl 创建了一个 2.5 TB 的语料,然后运用 MLM 训练编码器,由于没有平行对照文本,因此移除了 XLM 的 TLM 目标。最终,该模型大幅超越了 XLM 和多语言 BERT 变体;
- ALBERT:ALBERT 通过三处变化使得 Encoder 架构更高效:首先将词嵌入维度与隐藏维度解耦以减少模型参数;其次所有模型层共享参数;最后将 NSP 任务替换为句子排序预测(判断句子顺序是否被交换)。这些变化使得可以用更少的参数训练更大的模型,并在 NLU 任务上取得了优异的性能;
- ELECTRA:MLM 在每个训练步骤中只有被遮盖掉词语的表示会得到更新。ELECTRA 使用了一种双模型方法来解决这个问题:第一个模型继续按标准 MLM 工作;第二个模型(鉴别器)则预测第一个模型的输出中哪些词语是被遮盖的,这使得训练效率提高了 30 倍。下游任务使用时,鉴别器也参与微调;
- DeBERTa:DeBERTa 模型引入了两处架构变化。首先将词语的内容与相对位置分离,使得自注意力层 (Self-Attention) 层可以更好地建模邻近词语对的依赖关系;此外在解码头的 softmax 层之前添加了绝对位置嵌入。DeBERTa 是第一个在 SuperGLUE 基准上击败人类的模型。
16. 介绍transformer
Transformer 模型通过自注意力机制和并行计算的优势,能够更好地处理长距离依赖关系,提高了模型的训练和推理效率。它在机器翻译、文本摘要、问答系统等多个 NLP 任务中取得了显著的性能提升。
Transformer 模型本质上都是预训练语言模型,大都采用自监督学习 (Self-supervised learning) 的方式在大量生语料上进行训练,也就是说,训练这些 Transformer 模型完全不需要人工标注数据。
模型本来是为翻译任务设计的,主要包括encoder和decoder两个模块。在训练过程中,encoder接受源语言的句子作为输入,decoder接受目标语言的翻译作为输入。在encoder中,由于翻译一个词语需要以来上下文,因此注意力层可以访问句子中所有词语;decoder是顺序的进行解码,在生成词语时,注意力层只能访问前面已经生成的单词。
纯encoder模型:适用于理解文本的输入语义的任务。
纯decoder模型:适用于生成式任务。
Encoder-Decoder 模型:适用于基于输入的生成任务。
17. python中字典的结构,是用啥实现的
实现:Python 字典主要通过哈希表实现,使用哈希函数将键映射到数组索引位置。
哈希冲突:使用链表法或开放地址法来处理哈希冲突。
动态调整:字典会根据需要动态调整大小,以保持高效性能。
顺序:从 Python 3.7 起,字典保持了键的插入顺序。
18. F1为什么是1
F1-score的取值范围在0到1之间,其中1表示模型完美的分类性能,0表示模型没有分类性能。 其中,精确率是指模型预测为正例的样本中真正为正例的比例,召回率是指所有真正的正例样本中被模型预测为正例的比例。
19. Transformer的多头是为什么
多头注意力机制的组成是有单个的self attention,由于self attention通过产生QKV矩阵来学习数据特征,那每一个self attention最终会产生一个维度上的输出特征,所以当使用多头注意力机制的时候,模型就可以学习到多维度的特征信息,这使得模型可以从多个维度更好的理解数据。
20. 自注意力的计算过程
除以 d k d_k dk , d k d_k dk 表示特征维度, 之所以要除以这个数,是为了矩阵点乘后的范围,确保softmax的梯度稳定性。
21. Bert里面为什么用layer normalization,而不用batch normalization,分别讲一下这两个啥意思。
因为它不依赖批次大小,能更好地处理变长序列,并在训练和推理时保持一致性。
Batch Normalization 是对这批样本的同一维度特征做归一化, Layer Normalization 是对这单个样本的所有维度特征做归一化。
区别:LN中同层神经元输入拥有相同的均值和方差,不同的输入样本有不同的均值和方差;BN中则针对不同神经元输入计算均值和方差,同一个batch中的输入拥有相同的均值和方差。所以,LN不依赖于batch的大小和输入sequence的长度,因此可以用于batchsize为1和RNN中sequence的normalize操作。
22. Bert里面为什么Q,K,V要用三个不同的矩阵,用一个不是也行吗。
请求和键值初始为不同的权重是为了解决可能输入句长与输出句长不一致的问题。并且假如QK维度一致,如果不用Q,直接拿K和K点乘的话,你会发现attention score 矩阵是一个对称矩阵。因为是同样一个矩阵,都投影到了同样一个空间,所以泛化能力很差。
23. Bert和transformer讲一下。
1 bert只有transformer的encode 结构 ,是生成语言模型
2 bert 加入了输入句子的 mask机制,在输入的时候会随机mask
3 模型接收两个句子作为输入,并且预测其中第二个句子是否在原始文档中也是后续句子 可以做对话机制的应答。
4 在训练 BERT 模型时,Masked LM 和 Next Sentence Prediction 是一起训练的,目标就是要最小化两种策略的组合损失函数。
24. AUC指标讲一下。
AUC:AUC是ROC曲线下面的面积,AUC可以解读为从所有正例中随机选取一个样本A,再从所有负例中随机选取一个样本B,分类器将A判为正例的概率比将B判为正例的概率大的可能性。AUC反映的是分类器对样本的排序能力。AUC越大,自然排序能力越好,即分类器将越多的正例排在负例之前。
25. 防止过拟合的方式。
降低模型复杂度
增加更多的训练数据:使用更大的数据集训练模型
数据增强
正则化:L1、L2、添加BN层
添加Dropout策略
Early Stopping
26. Adam讲一下。
Adam算法即自适应时刻估计方法(Adaptive Moment Estimation),能计算每个参数的自适应学习率。这个方法不仅存储了AdaDelta先前平方梯度的指数衰减平均值,而且保持了先前梯度M(t)的指数衰减平均值,这一点与动量类似。
Adam实际上就是将Momentum和RMSprop集合在一起,把一阶动量和二阶动量都使用起来了。
27. pytorch中的train,val模块
在 PyTorch 中,train
和 val
模块通常是指模型训练和验证过程中的两个重要环节。它们有助于评估模型的性能、避免过拟合,并确保模型在不同数据集上的表现一致。
训练(train):通过前向传播、计算损失、反向传播和优化来更新模型参数。
验证(val):通过在验证数据上评估模型性能,检查模型是否过拟合,并确保其在未见过的数据上的表现。