Julia中高效生成多项式组合特征矩阵的方法求助
在Julia里想要实现类似Python scikit-learn PolynomialFeatures的高效多项式特征生成,其实有不少现成的优质工具,也可以对你的手动实现做针对性优化,下面分两种情况给你说明:
一、用现成库函数(优先推荐)
自己手动写循环很容易因为内存分配和低效操作拖慢速度,Julia生态里已经有成熟的工具可以直接用,性能和易用性都有保障:
1. MLJModels.jl 的 PolynomialFeatures
MLJModels是Julia机器学习生态里的工具集,它的PolynomialFeatures和你熟悉的scikit-learn版本功能几乎一致,而且经过优化,处理大数据量时速度会快很多。
举个实际使用的例子:
using MLJModels, DataFrames # 构造你的输入数据框 df = DataFrame(x = [1, 2], y = [3, 4]) # 初始化转换器:设置阶数为2,不包含偏置项(对应Python里的include_bias=False) poly_transformer = PolynomialFeatures(degree=2, include_bias=false) # 拟合转换器并转换数据 # 可以直接传入DataFrame或者矩阵,这里用矩阵演示 X = Matrix(df) X_poly = transform(fit!(machine(poly_transformer, X)), X) # 如果需要获取特征名称,直接调用feature_names方法即可 feature_names(poly_transformer)
运行后得到的X_poly就是你需要的特征矩阵,特征名称也会自动生成(比如x, y, x², x*y, y²),完全符合你的需求。
2. StatsModels.jl 的公式语法
如果你习惯用公式来定义特征,StatsModels的@formula可以非常灵活地生成多项式特征,尤其适合和后续的建模流程结合:
using StatsModels, DataFrames df = DataFrame(x = [1, 2], y = [3, 4]) # 用公式定义二阶多项式特征,包含原始特征、平方项和交叉项 f = @formula(0 ~ x + y + x^2 + y^2 + x&y) # 生成特征矩阵,去掉第一列的截距项(对应不包含偏置) X_poly = modelmatrix(f, df)[:, 2:end]
这种方式不需要额外的转换器,直接通过公式就能生成想要的特征,用起来很顺手。
二、优化你的手动实现
如果因为某些特殊需求必须自己写函数,你的原函数慢的核心问题是循环里反复用hcat——每次hcat都会重新分配内存,数据量大的时候开销会爆炸。可以通过以下方式优化:
1. 预分配内存+广播操作
先算出最终特征矩阵的大小,一次性分配好内存,再用广播填充数据,避免反复的内存分配:
function poly_opt(data, vars, deg) mat = Matrix(data[:, vars]) n, s = size(mat) if deg == 1 return mat, vars elseif deg == 2 # 计算总特征数:原始s个 + s*(s+1)/2个二阶项(平方+交叉) total_features = s + s*(s+1)÷2 # 预分配结果矩阵,和原始矩阵类型一致 p = similar(mat, n, total_features) # 先填充原始特征 p[:, 1:s] = mat varnm = String[] append!(varnm, string.(vars)) idx = s + 1 for i in 1:s for j in i:s # 用广播计算列,比手动循环快很多 p[:, idx] = mat[:, i] .* mat[:, j] # 生成特征名称 if i == j push!(varnm, "$(vars[i])²") else push!(varnm, "$(vars[i])*$(vars[j])") end idx += 1 end end return p, varnm else error("目前仅实现了1阶和2阶的情况,如需更高阶可以扩展") end end
这个版本去掉了所有不必要的内存分配,用Julia擅长的广播操作替代了低效循环,性能会比你的原函数提升很多。
2. 通用高阶版本(支持任意阶数)
如果需要支持更高阶的多项式,可以用迭代器生成所有指数组合,再批量计算:
using Iterators function poly_general(data, vars, deg) mat = Matrix(data[:, vars]) n, s = size(mat) # 生成所有满足指数和≥1且≤deg的非负整数组合(排除全零的偏置项) exponents = filter(x -> sum(x) ≥ 1 && sum(x) ≤ deg, collect(Iterators.product(fill(0:deg, s)...))) exponents = collect.(exponents) # 预分配结果矩阵 p = similar(mat, n, length(exponents)) varnm = String[] for (k, exp) in enumerate(exponents) # 计算当前特征列:各特征的指数幂相乘 col = ones(eltype(mat), n) for (i, e) in enumerate(exp) e > 0 && (col .*= mat[:, i] .^ e) end p[:, k] = col # 生成特征名称 parts = String[] for (i, e) in enumerate(exp) if e == 1 push!(parts, string(vars[i])) elseif e > 1 push!(parts, "$(vars[i])^$e") end end push!(varnm, join(parts, "*")) end return p, varnm end
这个版本可以处理任意阶数的多项式特征,而且同样通过预分配内存和广播操作保证了效率。
内容的提问来源于stack exchange,提问作者delight

