ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

AIMLInterviews 机器学习编码面试指南:从零实现经典算法的完整备战手册

AIMLInterviews 机器学习编码面试指南:从零实现经典算法的完整备战手册 示例工程教程人工智能【免费下载链接】AIMLInterviewsThis repo is meant to serve as a guide for Machine Learning/AI technical interviews.项目地址https://gitcode.com/gh_mirrors/ma/AIMLInterviews点击查看免费下载本指南以 AIMLInterviews 仓库第 2 章 ML/数据编码 为主体系统讲解 ML 编码面试的考察形式、难度体系、12 道最高优先级题目及对应的权威 NumPy 参考实现。读完本文你将掌握数值稳定 softmax、梯度下降回归、kNN、k-means、决策树、PCA、卷积、注意力、ROC-AUC、蓄水池抽样、TF-IDF 等核心算法的从零实现要点以及面试中必须主动说明的设计取舍与边界情况。一、ML 编码面试在考什么ML 编码面试的形式因公司而异一部分公司要求候选人从零实现经典机器学习算法另一部分则考察实用的 Python 与 PyTorch 能力例如张量操作、数据预处理、指标计算和训练循环。无论哪种形式面试官通常都会围绕五个维度评估候选人正确性算法逻辑是否符合数学定义能否处理异常输入数值稳定性softmax、sigmoid、损失函数等是否在极端输入下保持有限值代码质量命名、结构、可读性与向量化程度边界情况处理空输入、平票、常量特征、非法标签、分母为零等复杂度时间与空间复杂度的讨论尤其是成对矩阵计算的开销。此外候选人解释设计取舍的能力也是重要的隐形考察点——面试官往往更关注你如何分析问题而非你是否背下了答案。二、如何使用本章学习路径与验证方法原文档给出了清晰的四步使用建议本仓库也据此配套了完整可运行的代码资产以权威参考实现为主将 solutions/ml_algorithms.py 作为核心面试题的权威、可执行 NumPy 参考实现。该文件覆盖 12 道优先级题目的全部函数并配有统一的输入校验辅助函数。用测试验证理解运行 solutions/test_ml_algorithms.py 验证实现并借此学习有价值的边界情况。测试文件基于unittest编写覆盖了数值稳定性、恢复真实参数、类别分离、空簇、掩码注意力等典型场景。以 notebook 作为补充探索材料notebooks 目录下提供较早的教学 notebook线性回归、逻辑回归、kNN、k-means、决策树、卷积、softmax、SVM、感知机、前馈网络等。原文档特别提醒其中一些 notebook 早于权威解法可能不够完整应作为理解推导过程的辅助而非最终标准。脱稿练习在不看参考实现的前提下写出每个重点题目然后与权威实现对比正确性、复杂度和边界情况处理。在仓库根目录运行参考测试原文档给出的命令uv run --with numpy python src/MLC/solutions/test_ml_algorithms.py该命令使用uv临时创建带 numpy 的隔离环境并执行全部单测无需预先手动安装依赖。中文版对应路径为cn/src/MLC/solutions/test_ml_algorithms.py本文后续引用均以中文目录cn/为准。全部测试通过即代表你的环境与参考实现一致。三、难度与公司标签体系如何解读题目信号所有编程题均使用 LeetCode 风格的难度标签这是本仓库的编辑判断并非对所有参考来源的绝对评级简单Easy通常只涉及一个核心操作、状态较少预计 15 分钟左右完成中等Medium包含多个步骤或较复杂的边界情况完整可靠的面试实现通常需要 20–35 分钟困难Hard通常需要 40–60 分钟以上涉及多个组件协作、高级调试或系统层面的权衡。关于标签的校准与边界原文档明确了三条规则面试准备时应正确理解能精确匹配的题目会参考 TorchLeet 与 Deep-ML 进行难度校准没有匹配来源的题目则按同一套标准评定。若参考来源存在分歧以本仓库题目的范围和边界条件要求为准。公司标签仅表示历史面试准备信号只有参考来源把某家公司与同一道实现题明确关联时才会添加并不保证该公司当前仍会考察此题。仓库只使用元数据事实题目与公司的历史关联不复制第三方题目描述或解答。因此难度与公司标签应视为值得优先准备的信号而非必考题清单。四、优先级最高的 12 道 ML 编码题下面这组题目结合了仍然常见的经典问题以及 AI/ML 面试中越来越常被要求掌握的现代基础能力注意力、TF-IDF、蓄水池抽样等。本节逐题结合源码函数签名、实现细节与测试用例展开讲解。4.1 数值稳定的 softmax 与交叉熵简单Apple, Meta, Google, Amazon权威解法softmax、cross_entropy_from_logits。源码实现的第一步是最大值平移这是数值稳定性的核心shifted values - values.max(axis-1, keepdimsTrue) exponentials np.exp(shifted) return exponentials / exponentials.sum(axis-1, keepdimsTrue)最大值平移减去每行的最大值使指数运算的输入上限为 0避免大 logits 直接溢出为infkeepdims 广播max与sum均使用keepdimsTrue保证沿最后一维归约后的 shape 可与原张量正确广播shape 约束softmax要求 logits 至少一维标量输入直接抛出ValueError。交叉熵在数值上被实现为 log-sum-exp 形式而非先算概率再取 log从而避免下溢log_probabilities shifted - np.log(np.exp(shifted).sum(axis1, keepdimsTrue)) return float(-log_probabilities[np.arange(scores.shape[0]), labels].mean())函数同时执行三类输入校验这些正是面试官期待的类别索引校验输入必须是[N, C]的 logits 与[N]的整数标签否则抛ValueError标签 dtype 必须为整数类型np.issubdtype(labels.dtype, np.integer)否则抛TypeError标签值必须落在[0, C)区间内越界抛ValueError。测试验证对应 test_ml_algorithms.py 中test_softmax_and_cross_entropy_are_stable使用[[1000, 1001], [-1000, -999]]这类极端 logits断言概率行和为 1 且交叉熵为有限值——直接检验数值稳定性。面试要点先讲清楚为什么要做最大值平移exp(1000)会溢出再讲交叉熵为什么要用 log-sum-exp概率过小取 log 会下溢为-inf最后说明 shape 约定[N, C]与[N]。4.2 使用梯度下降的线性回归中等权威解法linear_regression_gradient_descent配套 notebook 为 Linear regression。weights np.zeros(x.shape[1], dtypenp.float64) bias 0.0 for _ in range(steps): errors x weights bias - y weights - learning_rate * (2.0 / x.shape[0]) * (x.T errors) bias - learning_rate * 2.0 * float(errors.mean())从源码可以看到三个关键设计向量化梯度x.T errors一次性完成所有特征的梯度计算不出现任何 Python 循环偏置项独立更新bias 与 weights 分开维护其梯度为误差均值MSE 缩放系数2.0 / n均方误差对参数的梯度天然带2/n因子源码显式写出避免数值与约定不一致。测试验证test_linear_regression_recovers_line对y 3x - 2的数据训练 2000 步断言权重收敛到[3.0]误差 1e-3 内、bias 收敛到-2.0——直接验证收敛性。面试要点说明 MSE 的梯度推导、为什么学习率要随数据规模缩放、以及 batch 梯度下降在大数据下的局限可扩展到 mini-batch / SGD。4.3 使用梯度下降的逻辑回归困难Google, Meta, Amazon权威解法logistic_regression_gradient_descent配套 notebook 为 Logistic regression。逻辑回归的难点在于sigmoid 的数值稳定性。源码中的_sigmoid采用分段计算result[positive] 1.0 / (1.0 np.exp(-values[positive])) exponentials np.exp(values[~positive]) result[~positive] exponentials / (1.0 exponentials)对于x 0的输入用1 / (1 exp(-x))对于x 0的输入则用exp(x) / (1 exp(x))。这样exp(-x)或exp(x)永远不会出现正的大指数溢出保证 sigmoid 全程有限。梯度更新本身非常简洁——这正是稳定的 sigmoid 二元交叉熵梯度的组合probabilities _sigmoid(x weights bias) errors probabilities - y weights - learning_rate * (x.T errors) / x.shape[0] bias - learning_rate * float(errors.mean())注意probabilities - y恰好是二元交叉熵损失的梯度sigmoid 交叉熵的梯度奇迹般退化为残差形式这是面试中的加分讲解点。此外函数会先校验targets必须全为 0/1np.all(np.isin(y, (0.0, 1.0)))否则抛ValueError。测试验证test_logistic_regression_separates_simple_data对线性可分的两簇数据训练 1000 步后用阈值logits 0判定类别断言预测与真实标签完全一致——验证了阈值这个输出边界。面试要点对比线性回归与逻辑回归的梯度形式差异说明分类决策阈值默认 0.5可随业务代价调整解释为什么不直接对概率取 log 计算交叉熵。4.4 k 最近邻中等Uber, LinkedIn, Meta权威解法knn_predict配套 notebook 为 k-NN。kNN 的实现要点是向量化的成对平方欧氏距离distances ((query_x[:, None, :] - train_x[None, :, :]) ** 2).sum(axis2)利用 NumPy 广播将[M, D]的查询集与[N, D]的训练集扩展为[M, N, D]的差值张量一步得到[M, N]距离矩阵——这就是面试中需要讨论的成对矩阵计算开销时间与空间均为 O(M·N·D)。top-k 选择使用np.argpartition部分选择算法比全排序更快neighbor_indices np.argpartition(distances, kthk - 1, axis1)[:, :k]平票处理对每个查询的 k 个邻居用np.unique(..., return_countsTrue)统计类别频次取np.argmax(counts)对应的类别。源码默认取出现次数最多的类别中排序靠前者这是一种简单确定的平票策略。输入校验k必须满足1 k 训练样本数训练与查询特征宽度必须一致。测试test_knn用k1验证最近邻归属正确。面试要点讨论距离度量的选择欧氏 vs 曼哈顿 vs 余弦、k 的取值影响、以及大数据集下 kNN 的推理成本问题可用 KD-Tree / 近似最近邻缓解。4.5 k-means 聚类中等Uber, LinkedIn, Google, Amazon权威解法kmeans配套 notebook 为 k-means。源码实现了 Lloyd 算法签名暴露了完整参数面kmeans(features, k, *, max_iterations100, tolerance1e-4, seed0)确定性初始化使用np.random.default_rng(seed)从数据中不放回随机抽取 k 个样本作为初始中心保证可复现向量化分配squared_distances.argmin(axis1)一步完成所有样本的最近中心指派收敛判定当新旧中心矩阵的 Frobenius 范数差 tolerance时提前终止最多迭代max_iterations次。空簇处理本题最值得讲的边界情况若某个簇没有成员源码会把该中心重新播种到当前最未被良好表示的样本即离其指派中心最远的点farthest_index int(np.argmax(nearest_distance)) new_centers[cluster_index] x[farthest_index]每次重新播种后将该点距离置为-1.0保证多个空簇不会重复选中同一点。最终返回(centers, labels)。测试验证test_kmeans对[0, 0.2]与[10, 10.2]两组数据聚类断言收敛后的中心为[0.1, 10.1]且产生两个簇——同时覆盖了初始化、收敛与空簇场景。面试要点讨论初始化敏感性k-means 的动机、k的选择肘部法则、以及对离群点的鲁棒性问题。4.6 决策树划分中等权威解法gini_impurity、best_gini_split配套 notebook 为 Decision tree。Gini 不纯度的计算简洁直观_, counts np.unique(values, return_countsTrue) probabilities counts / len(values) return float(1.0 - np.sum(probabilities**2))空标签数组返回0.0纯节点一维校验失败抛ValueError。最优划分best_gini_split的核心是候选阈值构造对每个特征取唯一值排序后用相邻值的均值作为候选阈值unique_values np.unique(x[:, feature_index]) thresholds (unique_values[:-1] unique_values[1:]) / 2.0对每个阈值按left x[:, feature_index] threshold划分跳过所有样本都在同一侧的无效划分计算加权不纯度weighted_impurity left.mean() * gini_impurity(y[left]) (~left).mean() * gini_impurity(y[~left])返回(特征索引, 阈值, 加权不纯度)三元组若无法找到有效划分则返回None——这就是停止条件的体现。测试验证test_best_gini_split对[0,1,2,3]特征、[0,0,1,1]标签断言最优划分为特征 0、阈值 1.5、加权不纯度 0.0完全分割。面试要点对比 Gini 与信息增益熵讨论候选阈值为何取相邻值中点说明树生长的停止条件最大深度、最小样本数、纯节点。4.7 主成分分析中等权威解法principal_component_analysis。centered x - x.mean(axis0, keepdimsTrue) _, singular_values, right_vectors np.linalg.svd(centered, full_matricesFalse) components right_vectors[:n_components] transformed centered components.T explained_variance singular_values[:n_components] ** 2 / denominator实现要点清晰对应题目要求中心化减去特征均值keepdimsTrue保证广播正确SVD 而非显式特征分解对中心化矩阵直接做奇异值分解数值上比先算协方差再特征分解更稳定主成分排序SVD 返回的奇异值天然按降序排列取前n_components个右奇异向量即为主成分方差解释解释方差 奇异值平方除以(n - 1)这正是协方差矩阵的特征值。约束为1 n_components min(行数, 特征数)。测试test_pca断言投影后均值近似为 0、shape 正确、解释方差为正——验证了中心化与方差语义。面试要点说明 SVD 与特征分解的关系、为什么中心化是前提、如何用解释方差比例选择成分数。4.8 二维卷积中等权威解法conv2d_valid配套 notebook 为 Convolution。源码实现的是单通道 valid 卷积严格说是互相关输出 shape 公式一目了然output_height 1 (x.shape[0] - weights.shape[0]) // stride output_width 1 (x.shape[1] - weights.shape[1]) // stride双层循环滑动窗口逐位置计算np.sum(window * weights)。值得在面试中主动澄清的概念互相关 vs 卷积源码做的是window * weights逐元素相乘求和即互相关cross-correlation严格数学定义的卷积需要先把 kernel 沿两个轴翻转。由于深度学习实践中卷积层普遍指互相关二者在面试中必须显式说明步幅与输出 shapestride必须为正kernel 必须能放进 image否则抛ValueErrorvalid 与 same 的区别本题只要求 valid 模式不 padding面试可延伸讨论 padding 对输出 shape 的影响。测试验证test_valid_convolution3×3 图像与[[1,0],[0,-1]]核在 stride1 下输出-4.0与手算一致。面试要点解释输出 shape 公式推导对比互相关与卷积讨论向量化如as_strided或 im2col的加速思路。4.9 缩放点积注意力中等权威解法scaled_dot_product_attention。作为现代 Transformer 的基础原语这道题已成为 ML 编码面试的常客。源码实现scores q k.T / math.sqrt(q.shape[1]) if mask is not None: scores np.where(allowed, scores, -np.inf) weights softmax(scores) return weights v, weights关键设计shape 约定Q/K 的宽度d_k必须一致K/V 的序列长度必须一致否则抛ValueError缩放因子1/sqrt(d_k)防止点积随维度增大而方差膨胀、softmax 退化为 one-hotmask 在 softmax 之前被 mask 的位置置为-inf经过 softmax 后权重为 0——这正是在稳定 softmax 前做 mask的要求同时校验 mask shape 为[num_queries, num_keys]并保证每个 query 至少能 attend 到一个 key返回值同时返回输出与注意力权重weights便于面试讲解与可视化。测试验证test_masked_attention掩码[[True, False]]强制注意力只关注第一个 key断言输出为第一个 value 且权重为[1.0, 0.0]。面试要点解释缩放因子的数学动机点积方差为d_k说明 causal mask 与 padding mask 的区别可延伸讨论多头注意力与 KV cache。4.10 二分类指标与 ROC-AUC中等权威解法binary_classification_metrics、roc_auc。指标函数基于混淆矩阵的四个计数TP/TN/FP/FN并通过_safe_divide处理分母为零def _safe_divide(numerator, denominator): return float(numerator / denominator) if denominator else 0.0这对应题目要求的分母为零边界情况当没有正例或没有预测正类时precision/recall/F1 约定为 0.0而不是抛除以零异常。返回accuracy、precision、recall、f1四个值的字典。ROC-AUC的实现采用平均秩average ranks方法而非积分 ROC 曲线order np.argsort(predictions, kindmergesort) # 对相同分数的样本赋予平均秩tie handling average_rank ((start 1) end) / 2.0 auc (positive_rank_sum - positive_count * (positive_count 1) / 2.0) / (positive_count * negative_count)要点平票处理使用mergesort稳定排序并显式计算并列分数的平均秩——这正是题目强调的ties排序解释AUC 的统计意义是随机正样本得分高于随机负样本的概率秩和公式直接从定义推导类别不平衡要求正负样本都至少存在一个否则抛ValueError避免 0/0。测试test_metrics_and_auc断言precision 2/3、recall 1.0、完全排序数据的 AUC 1.0。面试要点说明为什么精确率在类别不平衡时比准确率更有信息量解释 AUC 的随机排序概率含义讨论 PR-AUC 在稀有正类场景下的优势。4.11 蓄水池抽样中等权威解法reservoir_sample。for index, item in enumerate(items): if index k: reservoir.append(item) continue replacement_index generator.randint(0, index) if replacement_index k: reservoir[replacement_index] item这个经典算法只需一遍扫描未知流长度无需提前知道数据总量逐个消费迭代器即可均匀概率第index个元素以k / (index 1)的概率进入蓄水池通过randint(0, index) k实现且进入后每个旧元素被替换的概率经过精心设计保证最终每个元素以k / N的概率被选中O(k) 内存全程只保留 k 个元素与流长度无关可复现性使用random.Random(seed)局部生成器而非全局 RNG这是可测试性的关键。测试test_reservoir_sample验证从range(100)中抽样 10 个长度正确、元素唯一且全部来自源集合。面试要点现场推导概率不变式归纳法讨论与random.sample需要整体加载的差异延伸到大文件或流式数据的场景。4.12 TF-IDF中等权威解法tfidf。源码实现了一个基础版 TF-IDF 矩阵构建全部以向量化矩阵承载inverse_document_frequency np.log((1.0 len(documents)) / (1.0 document_frequency)) 1.0 return matrix * inverse_document_frequency, vocabularytoken 计数小写化 空白切分document.lower().split()词表按字典序排序TF 归一化每个文档的 token 频次除以该文档 token 总数count / len(document)得到归一化词频平滑 IDF使用1 df与1 N的平滑公式再加 1避免对未出现词的除零与零权重——这是sklearn风格的平滑变体稀疏缩放虽然源码用稠密矩阵存储但matrix * idf的逐元素乘放缩本质上对应了稀疏表示下的高效计算模式面试可讨论如何迁移到稀疏格式。返回(tfidf矩阵, 词表)二元组空文档集返回空矩阵与空词表。测试test_tfidf断言矩阵 shape 为(2, 3)、词表有序、red 的权重为正。面试要点解释 IDF 的平滑动机讨论与CountVectorizer/TfidfVectorizer的异同说明为何需要词表vocabulary这一输出以便复用。五、权威参考实现的通用设计原则通读 ml_algorithms.py 可以发现12 道题共享同一套工程规范这些规范本身就是面试中代码质量维度的样板统一的输入校验辅助函数_as_feature_matrix强制输入为非空[N, D]矩阵且仅含有限值_validate_supervised_inputs进一步约束 targets 为每行一个有限值。所有公共函数复用这两处校验避免重复代码。显式 dtype 提升所有数值计算统一转换为np.float64保证跨平台数值行为一致。全部使用 keyword-only 超参数如*, learning_rate0.05, steps1_000防止位置参数误传同时给出实用默认值学习率 0.05/0.1、迭代 1000 步、k3、seed0 等。确定性优先kmeans与reservoir_sample均接受seed参数并使用局部生成器与 测试文件 的断言配合保证可复现。错误信息可读每个ValueError都描述具体约束如 k must be between 1 and the number of training rows便于面试中快速定位契约。六、其他经典算法延伸练习清单这些题适合作为延伸练习尤其适合目标团队业务方向与下列主题相关时难度均为本仓库编辑评定线性 SVM 与 hinge lossnotebook——中等Medium感知机学习规则notebook——简单Easy前馈神经网络与反向传播notebook——困难Hard指标与 loss 的多分类或多标签扩展——中等Medium用于文本分类的朴素贝叶斯——中等Medium用于推荐系统的矩阵分解——中等Medium梯度提升解释训练循环并实现一个简单的残差拟合步骤——困难Hard延伸题的共同模式是从权威实现中抽取核心思想如 hinge loss 的次梯度、感知机的错误驱动更新、BP 的链式法则再结合 notebook 理解推导过程。七、数据与采样问题容易被忽视的基础功数据工程类问题在面试中同样高频出现且大多围绕泄漏这一核心风险在不发生数据泄漏的前提下实现训练集/验证集/测试集划分——简单Easy只使用训练集统计量做特征标准化——简单Easy以一致方式处理缺失值和未见过的类别——中等Medium实现均匀抽样、分层抽样、加权抽样与蓄水池抽样——中等Medium构建 mini-batch并对变长序列做 padding——中等Medium正确聚合按样本加权的 loss 与流式指标——中等Medium这些问题的共性答案可概括为划分在先、统计量只在训练集上拟合、验证/测试阶段复用冻结后的预处理状态。其中蓄水池抽样已在前文给出权威实现可作为抽样类问题的起点。八、面试中应该说明什么六条实战法则原文档给出的六条建议是贯穿所有题目的表达框架编码前先说明契约明确输入 shape、dtype、前提假设和期望输出。本仓库所有权威函数的校验逻辑shape/dtype/值域就是契约的落地样板。先正确后优化先写出正确的基线版本再对瓶颈部分做向量化或优化。例如 kNN 可以先写双重循环再优化为广播距离矩阵。讨论复杂度包括成对矩阵的计算开销kNN 的 O(M·N·D) 距离矩阵、注意力分数矩阵等。处理边界情况数值稳定性、空输入、平票、常量特征和非法标签——每道权威实现都以异常或约定值显式处理了这些场景。写测试为正常情况编写小测试并至少覆盖一个失败或边界情况。测试文件 中每个用例都体现了正常 边界的组合模式。说明规模化差异解释如果面对大规模数据集、GPU、分布式训练或生产级库如 scikit-learn、PyTorch实现会如何变化。九、进阶方向PyTorch ML 编码现代 ML 编码面试越来越多地考察实用 PyTorch 能力而不仅是从零实现算法。本仓库的 PyTorch ML 编码题指南 与本章无缝衔接包含一场 60 分钟的模拟面试覆盖张量、预处理、指标、训练/评估循环和调试面向 ML 工作流的 Python 工具题与高质量代码题张量操作、数据集、batch 处理、设备管理和 autograd训练、优化、混合精度、checkpoint 保存与恢复以及可复现性测试、调试、部署和高级 PyTorch 问题编码挑战与简明参考答案。建议的进阶路径是先用本章的 NumPy 实现吃透算法本质再迁移到 PyTorch 的向量化写法如normalize_rows、FeatureStandardizer、训练/评估循环最后用 测试文件 同款的边界用例思维检验 PyTorch 版本的正确性。总结ML 编码面试的备战核心可以浓缩为一句话用可运行、可测试、边界完备的实现证明你真正理解了算法。AIMLInterviews 仓库通过 权威解法、单元测试 与 教学 notebook 三位一体的资产把 12 道优先级题目和 7 道延伸题落到了可执行层面。建议按脱稿实现 → 对照源码 → 运行测试 → 口头讲解复杂度与边界的循环反复训练并结合 PyTorch ML 编码指南 补齐现代 ML 工作流的实操能力。赞分享示例工程教程人工智能【免费下载链接】AIMLInterviewsThis repo is meant to serve as a guide for Machine Learning/AI technical interviews.项目地址https://gitcode.com/gh_mirrors/ma/AIMLInterviews点击查看免费下载相关推荐从零实现KNN用NumPy手撕机器学习经典算法从零实现KNN用NumPy手撕机器学习经典算法 你是否在学习机器学习时遇到这些痛点调用Scikit learn接口如同封装操作无法理解底层原理理论知识晦示例工程机器学习教程经典机器学习算法实现从理论到代码的完整转换经典机器学习算法实现从理论到代码的完整转换 本文深入探讨了机器学习中五种核心算法的理论与实践实现包括线性回归、逻辑回归、KNN、K means聚类、决策树与示例工程机器学习深度学习教程机器学习实战指南AiLearning中的经典算法实现机器学习实战指南AiLearning中的经典算法实现 本文详细解析了AiLearning项目中四种核心机器学习算法的原理与实现KNN近邻算法通过距离度量和K文档教程人工智能机器学习深度学习NLP数据分析上一篇Komodo命令补全功能提升CLI操作效率下一篇SolidWorks Keygen使用详解如何生成有效的序列号和许可证密钥创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表