Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7accbfa

Browse files
committed
Merge pull request scikit-learn#3239 from larsmans/faster-poly-features
compute poly features directly and allow interaction features only
2 parents 9f468f3 + de42d69 commit 7accbfa

File tree

3 files changed

+79
-22
lines changed

3 files changed

+79
-22
lines changed

doc/modules/linear_model.rst

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ performance.
795795

796796
.. _polynomial_regression:
797797

798-
Polynomial Regression: Extending Linear Models with Basis Functions
798+
Polynomial regression: extending linear models with basis functions
799799
===================================================================
800800

801801
.. currentmodule:: sklearn.preprocessing
@@ -842,7 +842,7 @@ polynomial features of varying degrees:
842842

843843
This figure is created using the :class:`PolynomialFeatures` preprocessor.
844844
This preprocessor transforms an input data matrix into a new data matrix
845-
of a given degree. It can be used as follows:
845+
of a given degree. It can be used as follows::
846846

847847
>>> from sklearn.preprocessing import PolynomialFeatures
848848
>>> import numpy as np
@@ -863,7 +863,7 @@ any linear model.
863863

864864
This sort of preprocessing can be streamlined with the
865865
:ref:`Pipeline <pipeline>` tools. A single object representing a simple
866-
polynomial regression can be created and used as follows:
866+
polynomial regression can be created and used as follows::
867867

868868
>>> from sklearn.preprocessing import PolynomialFeatures
869869
>>> from sklearn.linear_model import LinearRegression
@@ -879,3 +879,28 @@ polynomial regression can be created and used as follows:
879879

880880
The linear model trained on polynomial features is able to exactly recover
881881
the input polynomial coefficients.
882+
883+
In some cases it's not necessary to include higher powers of any single feature,
884+
but only the so-called *interaction features*
885+
that multiply together at most :math:`d` distinct features.
886+
These can be gotten from :class:`PolynomialFeatures` with the setting
887+
``interaction_only=True``.
888+
889+
For example, when dealing with boolean features,
890+
:math:`x_i^n = x_i` for all :math:`n` and is therefore useless;
891+
but :math:`x_i x_j` represents the conjunction of two booleans.
892+
This way, we can solve the XOR problem with a linear classifier::
893+
894+
>>> from sklearn.linear_model import Perceptron
895+
>>> from sklearn.preprocessing import PolynomialFeatures
896+
>>> X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
897+
>>> y = X[:, 0] ^ X[:, 1]
898+
>>> X = PolynomialFeatures(interaction_only=True).fit_transform(X)
899+
>>> X
900+
array([[1, 0, 0, 0],
901+
[1, 0, 1, 0],
902+
[1, 1, 0, 0],
903+
[1, 1, 1, 1]])
904+
>>> clf = Perceptron(fit_intercept=False, n_iter=10).fit(X, y)
905+
>>> clf.score(X, y)
906+
1.0

sklearn/preprocessing/data.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
# Andreas Mueller <[email protected]>
55
# License: BSD 3 clause
66

7+
from itertools import chain, combinations
78
import numbers
8-
import warnings
9-
import itertools
109

1110
import numpy as np
1211
from scipy import sparse
@@ -20,13 +19,15 @@
2019
from ..utils import safe_asarray
2120
from ..utils import warn_if_not_float
2221
from ..utils.extmath import row_norms
22+
from ..utils.fixes import combinations_with_replacement as combinations_w_r
2323
from ..utils.sparsefuncs_fast import inplace_csr_row_normalize_l1
2424
from ..utils.sparsefuncs_fast import inplace_csr_row_normalize_l2
2525
from ..utils.sparsefuncs import inplace_column_scale
2626
from ..utils.sparsefuncs import mean_variance_axis0
2727

2828
zip = six.moves.zip
2929
map = six.moves.map
30+
range = six.moves.range
3031

3132
__all__ = [
3233
'Binarizer',
@@ -389,7 +390,7 @@ def inverse_transform(self, X, copy=None):
389390

390391

391392
class PolynomialFeatures(BaseEstimator, TransformerMixin):
392-
"""Generate polynomial (interaction) features.
393+
"""Generate polynomial and interaction features.
393394
394395
Generate a new feature matrix consisting of all polynomial combinations
395396
of the features with degree less than or equal to the specified degree.
@@ -400,7 +401,11 @@ class PolynomialFeatures(BaseEstimator, TransformerMixin):
400401
----------
401402
degree : integer
402403
The degree of the polynomial features. Default = 2.
403-
include_bias : integer
404+
interaction_only : boolean, default = False
405+
If true, only interaction features are produced: features that are
406+
products of at most ``degree`` *distinct* input features (so not
407+
``x[1] ** 2``, ``x[0] * x[2] ** 3``, etc.).
408+
include_bias : boolean
404409
If True (default), then include a bias column, the feature in which
405410
all polynomial powers are zero (i.e. a column of ones - acts as an
406411
intercept term in a linear model).
@@ -417,6 +422,11 @@ class PolynomialFeatures(BaseEstimator, TransformerMixin):
417422
array([[ 1, 0, 1, 0, 0, 1],
418423
[ 1, 2, 3, 4, 6, 9],
419424
[ 1, 4, 5, 16, 20, 25]])
425+
>>> poly = PolynomialFeatures(interaction_only=True)
426+
>>> poly.fit_transform(X)
427+
array([[ 1, 0, 1, 0],
428+
[ 1, 2, 3, 6],
429+
[ 1, 4, 5, 20]])
420430
421431
Attributes
422432
----------
@@ -427,36 +437,34 @@ class PolynomialFeatures(BaseEstimator, TransformerMixin):
427437
Notes
428438
-----
429439
Be aware that the number of features in the output array scales
430-
exponentially in the number of features of the input array, so this
431-
is not suitable for higher-dimensional data.
440+
polynomially in the number of features of the input array, and
441+
exponentially in the degree. High degrees can cause overfitting.
432442
433443
See :ref:`examples/plot_polynomial_regression.py
434444
<example_plot_polynomial_regression.py>`
435445
"""
436-
def __init__(self, degree=2, include_bias=True):
446+
def __init__(self, degree=2, interaction_only=False, include_bias=True):
437447
self.degree = degree
448+
self.interaction_only = interaction_only
438449
self.include_bias = include_bias
439450

440451
@staticmethod
441-
def _power_matrix(n_features, degree, include_bias):
452+
def _power_matrix(n_features, degree, interaction_only, include_bias):
442453
"""Compute the matrix of polynomial powers"""
443-
# Find permutations/combinations which add to degree or less
444-
deg_min = 0 if include_bias else 1
445-
powers = itertools.product(*(range(degree + 1)
446-
for i in range(n_features)))
447-
powers = np.array([c for c in powers if deg_min <= sum(c) <= degree])
448-
449-
# sort so that the order of the powers makes sense
450-
i = np.lexsort(np.vstack([powers.T, powers.sum(1)]))
451-
return powers[i]
454+
comb = (combinations if interaction_only else combinations_w_r)
455+
start = int(not include_bias)
456+
combn = chain.from_iterable(comb(range(n_features), i)
457+
for i in range(start, degree + 1))
458+
powers = np.vstack(np.bincount(c, minlength=n_features) for c in combn)
459+
return powers
452460

453461
def fit(self, X, y=None):
454462
"""
455463
Compute the polynomial feature combinations
456464
"""
457465
n_samples, n_features = array2d(X).shape
458-
self.powers_ = self._power_matrix(n_features,
459-
self.degree,
466+
self.powers_ = self._power_matrix(n_features, self.degree,
467+
self.interaction_only,
460468
self.include_bias)
461469
return self
462470

sklearn/utils/fixes.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,27 @@ def sparse_min_max(X, axis):
172172
# numpy.argpartition was introduced in v 1.8.0
173173
def argpartition(a, kth, axis=-1, kind='introselect', order=None):
174174
return np.argsort(a, axis=axis, order=order)
175+
176+
177+
try:
178+
from itertools import combinations_with_replacement
179+
except ImportError:
180+
# Backport of itertools.combinations_with_replacement for Python 2.6,
181+
# from Python 3.4 documentation (http://tinyurl.com/comb-w-r), copyright
182+
# Python Software Foundation (https://docs.python.org/3/license.html)
183+
def combinations_with_replacement(iterable, r):
184+
# combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC
185+
pool = tuple(iterable)
186+
n = len(pool)
187+
if not n and r:
188+
return
189+
indices = [0] * r
190+
yield tuple(pool[i] for i in indices)
191+
while True:
192+
for i in reversed(range(r)):
193+
if indices[i] != n - 1:
194+
break
195+
else:
196+
return
197+
indices[i:] = [indices[i] + 1] * (r - i)
198+
yield tuple(pool[i] for i in indices)

0 commit comments

Comments
 (0)