You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

cuDF处理姓名近似重复报错:apply编译失败与加速方案咨询

解决cuDF中Levenshtein距离近似重复检测的报错与性能优化问题

问题背景

在处理含30万行数据的people DataFrame时,需通过Levenshtein距离(距离≤1视为近似重复)识别姓名的重复/近似重复项,并输出每行对应的重复项索引列表。尝试用cuDF加速计算时,将DataFrame转为cuDF格式后调用apply函数触发ValueError: user defined function compilation failed,底层为Numba无法识别list类型的TypingError。

输入输出示例

输入

name
jack
john
jack
jackz

期望输出

nameduplicates
jack[0,2,3]
john[1]
jack[0,2,3]
jackz[0,2,3]

原代码

import pandas as pd
import cudf

people=pd.read_csv('people.csv')
df1 = people

def filter_rows(row: pd.Series) -> pd.Series:
    row["duplicates"] = []
    targets = cudf.Series(list(df1['name']))
    sr = cudf.Series((len(targets)*[row['name']]))
    dist=sr.str.edit_distance(targets=targets)
    dist=dist.where(dist<=1).dropna()
    row["duplicates"]=dist.index.to_arrow().to_pylist()
    return row

people_with_dup = (df1).apply(filter_rows, axis=1)

转为cuDF时添加代码:people=cudf.from_pandas(people),触发上述报错。

报错原因分析

  • cuDF的apply依赖Numba编译用户自定义函数(UDF),但Numba对Python原生list类型的GPU编译支持有限,无法正确推断其动态类型,直接导致TypingError。
  • 原UDF中混合使用CPU列表(如[]、len(targets)*[row['name']])与GPU的cuDF Series,频繁在CPU和GPU间传输数据,不仅触发类型推断失败,还会严重拖慢性能。

性能优化与报错修复方案

原代码采用逐行apply的方式,复杂度为O(n²),30万行数据会产生9e10次计算,完全不具备可行性。以下是两种高效的替代方案:

方案一:分组+批量编辑距离计算

通过先分组聚合唯一姓名,再计算唯一姓名间的编辑距离,最后映射回原表的方式减少计算量:

import cudf
from itertools import combinations

# 读取数据并保留原始索引
people = cudf.read_csv('people.csv')
people['idx'] = people.index

# 聚合每个唯一姓名对应的所有原始索引
unique_names = people.groupby('name')['idx'].agg(list).reset_index()

# 找出所有近似重复的姓名组
similar_groups = []
for name1, name2 in combinations(unique_names['name'], 2):
    # 计算两个姓名的编辑距离
    dist = cudf.Series([name1]).str.edit_distance(targets=cudf.Series([name2])).iloc[0]
    if dist <= 1:
        # 合并两组索引并去重
        group1 = unique_names[unique_names['name'] == name1]['idx'].iloc[0]
        group2 = unique_names[unique_names['name'] == name2]['idx'].iloc[0]
        merged_group = sorted(list(set(group1 + group2)))
        similar_groups.append((name1, merged_group))
        similar_groups.append((name2, merged_group))

# 补充无近似项的姓名
for _, row in unique_names.iterrows():
    if not any(g[0] == row['name'] for g in similar_groups):
        similar_groups.append((row['name'], row['idx']))

# 合并回原DataFrame
similar_df = cudf.DataFrame(similar_groups, columns=['name', 'duplicates'])
people_with_dup = people.merge(similar_df, on='name').drop('idx', axis=1)

方案二:FAISS GPU近似搜索(超大数据量首选)

将姓名转换为字符向量,用FAISS GPU索引快速定位近似项,再验证编辑距离:

import cudf
import faiss
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer

people = cudf.read_csv('people.csv')
names = people['name'].to_pandas().tolist()

# 将姓名转换为字符3-gram向量
vectorizer = CountVectorizer(analyzer='char', ngram_range=(3, 3))
name_vectors = vectorizer.fit_transform(names).toarray().astype(np.float32)

# 构建FAISS GPU索引
res = faiss.StandardGpuResources()
index = faiss.IndexFlatL2(name_vectors.shape[1])
gpu_index = faiss.index_cpu_to_gpu(res, 0, index)
gpu_index.add(name_vectors)

# 搜索每个姓名的近邻(top_k根据数据量调整)
k = min(100, len(names))
distances, indices = gpu_index.search(name_vectors, k)

# 验证编辑距离并收集有效索引
duplicates_list = []
for i in range(len(names)):
    current_name = names[i]
    valid_indices = []
    for idx in indices[i]:
        if cudf.Series([current_name]).str.edit_distance(targets=cudf.Series([names[idx]])).iloc[0] <= 1:
            valid_indices.append(idx)
    duplicates_list.append(sorted(list(set(valid_indices))))

# 合并到原表
people['duplicates'] = duplicates_list

性能提升关键点

  • 规避O(n²)复杂度:通过分组或近似搜索将计算量降至O(n)或O(n log n),适配30万行数据规模。
  • 充分利用GPU并行:cuDF的批量编辑距离计算、FAISS的GPU索引均为并行优化操作,远快于逐行处理。
  • 减少跨设备数据传输:避免在UDF中频繁将GPU数据拉取到CPU,消除性能瓶颈。

内容的提问来源于stack exchange,提问作者retroprojecteur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 10:52:53