机器学习面试编程实战:5类核心问题与Python解决方案

1. 数据预处理:缺失值与异常值处理

数据预处理是机器学习项目中最耗时的环节,也是面试官重点考察的实战能力。在真实业务场景中,约60%的数据科学时间都花费在数据清洗和特征工程上。

Pandas高效处理缺失值技巧

import pandas as pd
import numpy as np

# 创建含缺失值的示例数据
data = {'年龄': [25, np.nan, 32, 28, 45, np.nan],
        '收入': [50000, 62000, np.nan, 58000, np.nan, 70000],
        '购买记录': [1, 0, 3, np.nan, 2, 1]}
df = pd.DataFrame(data)

# 缺失值分析
print("缺失值统计:\n", df.isnull().sum())

# 填充策略
df_filled = df.copy()
df_filled['年龄'] = df['年龄'].fillna(df['年龄'].median())  # 数值型用中位数
df_filled['收入'] = df['收入'].fillna(df['收入'].mean())    # 连续变量用均值
df_filled['购买记录'] = df['购买记录'].fillna(0)             # 离散变量用0填充

# 删除缺失率过高的样本
df_dropped = df.dropna(thresh=len(df.columns)-1)  # 保留至少n-1个特征的样本

常见陷阱与解决方案

  1. 数据泄露 :在填充缺失值时使用整个数据集统计量(如全局均值),会导致测试集信息污染

    • 正确做法:仅在训练集计算统计量,应用到验证/测试集
  2. 异常值检测的三种方法

    • IQR法: Q1 - 1.5*IQR Q3 + 1.5*IQR 之外的值
    • Z-score法:绝对值大于3的标准分数
    • 隔离森林:适用于高维数据

提示:在时间序列数据中,应采用前向填充(ffill)而非均值填充,以保持时间依赖性

2. 特征工程:编码与标准化

特征工程的质量直接决定模型性能上限。面试中常要求手写特征转换代码,考察对数据分布的理解。

分类变量编码对比

编码方式 适用场景 优缺点 Python实现
One-Hot 类别数量<10 维度爆炸,丢失类别关系 pd.get_dummies()
Target Encoding 高基数类别(>100个类别) 可能引入目标泄露 category_encoders
Embedding 深度学习模型 需要预训练或端到端学习 tf.keras.layers.Embedding

数值特征标准化代码

from sklearn.preprocessing import StandardScaler, MinMaxScaler

# 标准化(Z-score)
scaler = StandardScaler()
X_train_std = scaler.fit_transform(X_train)
X_test_std = scaler.transform(X_test)  # 注意:使用训练集的均值和方差

# 归一化(MinMax)
minmax_scaler = MinMaxScaler(feature_range=(0, 1))
X_train_minmax = minmax_scaler.fit_transform(X_train)

# 鲁棒缩放(适用于有异常值)
from sklearn.preprocessing import RobustScaler
robust_scaler = RobustScaler(quantile_range=(25, 75))
X_train_robust = robust_scaler.fit_transform(X_train)

高频面试问题

  • 什么情况下需要做特征缩放?(距离度量算法:KNN、SVM;梯度下降算法)
  • 如何处理数值特征的偏态分布?(对数变换、Box-Cox变换)
  • 为什么测试集要使用训练集的缩放参数?(避免数据泄露)

3. 基础算法实现:从零编写KNN

手写经典算法是考察编程能力和数学基础的常见方式。以下是KNN的Python实现及优化技巧:

import numpy as np
from collections import Counter
from sklearn.metrics import accuracy_score

class KNN:
    def __init__(self, k=5, distance_metric='euclidean'):
        self.k = k
        self.metric = distance_metric
    
    def _calculate_distance(self, x1, x2):
        if self.metric == 'euclidean':
            return np.sqrt(np.sum((x1 - x2)**2))
        elif self.metric == 'manhattan':
            return np.sum(np.abs(x1 - x2))
        else:
            raise ValueError("不支持的度量方式")
    
    def fit(self, X, y):
        self.X_train = X
        self.y_train = y
    
    def predict(self, X):
        predictions = []
        for x in X:
            # 计算距离并获取最近邻
            distances = [self._calculate_distance(x, x_train) 
                        for x_train in self.X_train]
            k_indices = np.argsort(distances)[:self.k]
            k_nearest_labels = [self.y_train[i] for i in k_indices]
            
            # 多数投票
            most_common = Counter(k_nearest_labels).most_common(1)
            predictions.append(most_common[0][0])
        return np.array(predictions)

# 测试示例
if __name__ == "__main__":
    from sklearn.datasets import load_iris
    from sklearn.model_selection import train_test_split
    
    iris = load_iris()
    X, y = iris.data, iris.target
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
    
    knn = KNN(k=3)
    knn.fit(X_train, y_train)
    preds = knn.predict(X_test)
    print(f"准确率: {accuracy_score(y_test, preds):.2f}")

算法优化方向

  1. 空间分割 :使用KD树或球树加速近邻搜索,将复杂度从O(n)降到O(log n)
  2. 距离加权 :给更近的邻居更高投票权重
  3. 降维处理 :对高维数据先用PCA降维

4. 模型评估:从混淆矩阵到F1分数

准确率在不平衡数据中会失真,面试官常要求手写更全面的评估指标计算。

多分类评估指标实现

def multiclass_confusion_matrix(y_true, y_pred, classes):
    """生成多分类混淆矩阵"""
    n_classes = len(classes)
    matrix = np.zeros((n_classes, n_classes), dtype=int)
    
    for true, pred in zip(y_true, y_pred):
        matrix[true][pred] += 1
    
    return matrix

def precision_recall_f1(conf_matrix):
    """计算每个类别的精确率、召回率和F1"""
    metrics = {}
    for i in range(conf_matrix.shape[0]):
        tp = conf_matrix[i,i]
        fp = conf_matrix[:,i].sum() - tp
        fn = conf_matrix[i,:].sum() - tp
        
        precision = tp / (tp + fp) if (tp + fp) > 0 else 0
        recall = tp / (tp + fn) if (tp + fn) > 0 else 0
        f1 = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
        
        metrics[f'Class_{i}'] = {
            'Precision': precision,
            'Recall': recall,
            'F1': f1
        }
    return metrics

# 示例使用
y_true = [0, 1, 2, 0, 1, 2]
y_pred = [0, 2, 1, 0, 0, 1]
conf_mat = multiclass_confusion_matrix(y_true, y_pred, classes=[0,1,2])
print("混淆矩阵:\n", conf_mat)
print("\n评估指标:\n", precision_recall_f1(conf_mat))

关键概念解析

  • 宏平均 vs 微平均 :宏平均平等看待每个类别,微平均平等看待每个样本
  • ROC-AUC :真正例率(TPR) vs 假正例率(FPR)的曲线下面积
  • PR曲线 :精确率-召回率曲线,特别适用于不平衡数据

5. 交叉验证与超参数调优

正确的验证策略能防止过拟合,以下是带早停机制的交叉验证实现:

from sklearn.model_selection import KFold
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score

def cross_val_with_early_stop(X, y, model, n_splits=5, early_stop_rounds=3):
    kf = KFold(n_splits=n_splits)
    best_scores = []
    
    for train_idx, val_idx in kf.split(X):
        X_train, X_val = X[train_idx], X[val_idx]
        y_train, y_val = y[train_idx], y[val_idx]
        
        model.fit(X_train, y_train)
        val_preds = model.predict(X_val)
        current_score = f1_score(y_val, val_preds, average='macro')
        
        # 早停机制
        if len(best_scores) > 0 and current_score < max(best_scores):
            early_stop_rounds -= 1
            if early_stop_rounds == 0:
                print("早停触发")
                break
        
        best_scores.append(current_score)
    
    return np.mean(best_scores)

# 使用示例
X, y = np.random.rand(100, 10), np.random.randint(0, 2, 100)
model = RandomForestClassifier(n_estimators=100)
mean_score = cross_val_with_early_stop(X, y, model)
print(f"平均F1分数: {mean_score:.4f}")

超参数搜索技巧

  1. 网格搜索 :适用于小参数空间

    from sklearn.model_selection import GridSearchCV
    param_grid = {'n_estimators': [50, 100, 200],
                  'max_depth': [None, 5, 10]}
    grid_search = GridSearchCV(estimator=model, param_grid=param_grid, cv=5)
    grid_search.fit(X_train, y_train)
    
  2. 贝叶斯优化 :适用于昂贵评估的大参数空间

    from skopt import BayesSearchCV
    opt = BayesSearchCV(
        model,
        {'n_estimators': (50, 200),
         'max_depth': (1, 50)},
        n_iter=32,
        cv=5
    )
    opt.fit(X_train, y_train)
    
  3. 学习曲线分析 :判断增加数据还是调整模型复杂度

6. 面试实战技巧与避坑指南

代码白板书写规范

  1. 先写函数签名和docstring
  2. 处理边界条件(空输入、极端值)
  3. 添加时间/空间复杂度分析
  4. 准备测试用例(正常、异常场景)

高频陷阱问题

  • 如何优化KNN在大数据集的性能?
  • 当特征数量远大于样本数量时如何处理?
  • 线上模型效果下降的可能原因有哪些?

典型错误案例

# 错误:在整体数据上做标准化后再划分训练测试集
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)  # 数据泄露!
X_train, X_test = train_test_split(X_scaled, test_size=0.2)

# 正确做法
X_train, X_test = train_test_split(X, test_size=0.2)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

性能优化技巧

  • 使用 numba 加速数值计算
  • 对大数据集使用近似最近邻算法(Annoy、Faiss)
  • 利用多核并行化特征处理(joblib库)
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐