Skip to content

Commit

Permalink
Merge pull request #1499 from VesnaT/svm_tests
Browse files Browse the repository at this point in the history
[ENH] Upgrade OWSvm unittests
  • Loading branch information
tomazc authored Sep 9, 2016
2 parents b0793ac + f882602 commit 46961b4
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 157 deletions.
65 changes: 33 additions & 32 deletions Orange/widgets/classify/owsvmclassification.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ class OWBaseSVM(OWBaseLearner):
#: kernel degree
degree = settings.Setting(3)
#: gamma
gamma = settings.Setting(1.0)
gamma = settings.Setting(0.0)
#: coef0 (adative constant)
coef0 = settings.Setting(0.0)

#: numerical tolerance
tol = settings.Setting(0.001)

_default_gamma = "auto"
kernels = (("Linear", "x⋅y"),
("Polynomial", "(g x⋅y + c)<sup>d</sup>"),
("RBF", "exp(-g|x-y|²)"),
Expand All @@ -50,6 +51,7 @@ def _add_kernel_box(self):
inbox = gui.vBox(spbox)
gamma = gui.doubleSpin(
inbox, self, "gamma", 0.0, 10.0, 0.01, label=" g: ", **common)
gamma.setSpecialValueText(self._default_gamma)
coef0 = gui.doubleSpin(
inbox, self, "coef0", 0.0, 10.0, 0.01, label=" c: ", **common)
degree = gui.doubleSpin(
Expand All @@ -66,7 +68,7 @@ def _add_kernel_box(self):
def _add_optimization_box(self):
self.optimization_box = gui.vBox(
self.controlArea, "Optimization Parameters")
gui.doubleSpin(
self.tol_spin = gui.doubleSpin(
self.optimization_box, self, "tol", 1e-6, 1.0, 1e-5,
label="Numerical tolerance:",
decimals=6, alignment=Qt.AlignRight, controlWidth=100,
Expand Down Expand Up @@ -94,17 +96,18 @@ def _on_kernel_changed(self):
self.settings_changed()

def _report_kernel_parameters(self, items):
gamma = self.gamma or self._default_gamma
if self.kernel_type == 0:
items["Kernel"] = "Linear"
elif self.kernel_type == 1:
items["Kernel"] = \
"Polynomial, ({g:.4} x⋅y + {c:.4})<sup>{d}</sup>".format(
g=self.gamma, c=self.coef0, d=self.degree)
g=gamma, c=self.coef0, d=self.degree)
elif self.kernel_type == 2:
items["Kernel"] = "RBF, exp(-{:.4}|x-y|²)".format(self.gamma)
items["Kernel"] = "RBF, exp(-{:.4}|x-y|²)".format(gamma)
else:
items["Kernel"] = "Sigmoid, tanh({g:.4} x⋅y + {c:.4})".format(
g=self.gamma, c=self.coef0)
g=gamma, c=self.coef0)

def update_model(self):
super().update_model()
Expand All @@ -126,7 +129,7 @@ class OWSVMClassification(OWBaseSVM):

outputs = [("Support vectors", Table)]

# 0: c_svc, 1: nu_svc
C_SVC, Nu_SVC = 0, 1
svmtype = settings.Setting(0)
C = settings.Setting(1.0)
nu = settings.Setting(0.5)
Expand All @@ -141,53 +144,51 @@ def _add_type_box(self):
self.controlArea, self, "svmtype", [], box="SVM Type",
orientation=form, callback=self.settings_changed)

form.addWidget(gui.appendRadioButton(box, "C-SVM", addToLayout=False),
0, 0, Qt.AlignLeft)
form.addWidget(QtGui.QLabel("Cost (C):"),
0, 1, Qt.AlignRight)
form.addWidget(gui.doubleSpin(box, self, "C", 1e-3, 1000.0, 0.1,
decimals=3, alignment=Qt.AlignRight,
controlWidth=80, addToLayout=False,
callback=self.settings_changed),
0, 2)

form.addWidget(gui.appendRadioButton(box, "ν-SVM", addToLayout=False),
1, 0, Qt.AlignLeft)
form.addWidget(QtGui.QLabel("Complexity (ν):"),
1, 1, Qt.AlignRight)
form.addWidget(gui.doubleSpin(box, self, "nu", 0.05, 1.0, 0.05,
decimals=2, alignment=Qt.AlignRight,
controlWidth=80, addToLayout=False,
callback=self.settings_changed),
1, 2)
self.c_radio = gui.appendRadioButton(box, "C-SVM", addToLayout=False)
self.nu_radio = gui.appendRadioButton(box, "ν-SVM", addToLayout=False)
self.c_spin = gui.doubleSpin(
box, self, "C", 1e-3, 1000.0, 0.1, decimals=3,
alignment=Qt.AlignRight, controlWidth=80, addToLayout=False,
callback=self.settings_changed)
self.nu_spin = gui.doubleSpin(
box, self, "nu", 0.05, 1.0, 0.05, decimals=2,
alignment=Qt.AlignRight, controlWidth=80, addToLayout=False,
callback=self.settings_changed)
form.addWidget(self.c_radio, 0, 0, Qt.AlignLeft)
form.addWidget(QtGui.QLabel("Cost (C):"), 0, 1, Qt.AlignRight)
form.addWidget(self.c_spin, 0, 2)
form.addWidget(self.nu_radio, 1, 0, Qt.AlignLeft)
form.addWidget(QtGui.QLabel("Complexity (ν):"), 1, 1, Qt.AlignRight)
form.addWidget(self.nu_spin, 1, 2)

def _add_optimization_box(self):
super()._add_optimization_box()
gui.spin(self.optimization_box, self, "max_iter", 50, 1e6, 50,
label="Iteration limit:", checked="limit_iter",
alignment=Qt.AlignRight, controlWidth=100,
callback=self.settings_changed)
self.max_iter_spin = gui.spin(
self.optimization_box, self, "max_iter", 50, 1e6, 50,
label="Iteration limit:", checked="limit_iter",
alignment=Qt.AlignRight, controlWidth=100,
callback=self.settings_changed)

def create_learner(self):
kernel = ["linear", "poly", "rbf", "sigmoid"][self.kernel_type]
common_args = dict(
kernel=kernel,
degree=self.degree,
gamma=self.gamma,
gamma=self.gamma or self._default_gamma,
coef0=self.coef0,
tol=self.tol,
max_iter=self.max_iter if self.limit_iter else -1,
probability=True,
preprocessors=self.preprocessors
)
if self.svmtype == 0:
if self.svmtype == OWSVMClassification.C_SVC:
return SVMLearner(C=self.C, **common_args)
else:
return NuSVMLearner(nu=self.nu, **common_args)

def get_learner_parameters(self):
items = OrderedDict()
if self.svmtype == 0:
if self.svmtype == OWSVMClassification.C_SVC:
items["SVM type"] = "C-SVM, C={}".format(self.C)
else:
items["SVM type"] = "ν-SVM, ν={}".format(self.nu)
Expand Down
163 changes: 69 additions & 94 deletions Orange/widgets/classify/tests/test_owsvmclassification.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,82 @@
# Test methods with long descriptive names can omit docstrings
# pylint: disable=missing-docstring
from PyQt4 import QtGui

from Orange.data import Table
from Orange.widgets.classify.owsvmclassification import OWSVMClassification
from Orange.widgets.tests.base import WidgetTest
from Orange.widgets.tests.base import (WidgetTest, DefaultParameterMapping,
ParameterMapping, WidgetLearnerTestMixin)


class TestOWSVMClassification(WidgetTest):
class TestOWSVMClassification(WidgetTest, WidgetLearnerTestMixin):
def setUp(self):
self.widget = self.create_widget(OWSVMClassification)
self.widget.spin_boxes = self.widget.findChildren(QtGui.QDoubleSpinBox)
# max iter spin
self.widget.spin_boxes.append(self.widget.findChildren(QtGui.QSpinBox)[0])
# max iter checkbox
self.widget.max_iter_check_box = self.widget.findChildren(QtGui.QCheckBox)[0]
self.spin_boxes = self.widget.spin_boxes
self.event_data = None

def test_kernel_equation_run(self):
""" Check if right text is written for specific kernel """
for i in range(0, 4):
if self.widget.kernel_box.buttons[i].isChecked():
self.assertEqual(self.widget.kernel_eq, self.widget.kernels[i][1])

def test_kernel_equation(self):
""" Check if right text is written for specific kernel after click """
for index in range(0, 4):
self.widget.kernel_box.buttons[index].click()
self.assertEqual(self.widget.kernel_eq, self.widget.kernels[index][1])

def test_kernel_display_run(self):
""" Check if right spinner box for selected kernel are visible after widget start """
for button_pos, value in ((0, [False, False, False]),
(1, [True, True, True]),
(2, [True, False, False]),
(3, [True, True, False])):
if self.widget.kernel_box.buttons[button_pos].isChecked():
self.assertEqual([not self.spin_boxes[i].box.isHidden() for i in range(2, 5)],
value)
break
self.widget = self.create_widget(OWSVMClassification,
stored_settings={"auto_apply": False})
self.init()
gamma_spin = self.widget._kernel_params[0]
values = [self.widget._default_gamma, gamma_spin.maximum()]

def test_kernel_display(self):
""" Check if right spinner box for selected kernel are visible after we select kernel """
for button_pos, value in ((0, [False, False, False]),
(1, [True, True, True]),
(2, [True, False, False]),
(3, [True, True, False])):
self.widget.kernel_box.buttons[button_pos].click()
self.widget.kernel_box.buttons[button_pos].isChecked()
self.assertEqual([not self.spin_boxes[i].box.isHidden() for i in range(2, 5)], value)
def getter():
value = gamma_spin.value()
return gamma_spin.specialValueText() \
if value == gamma_spin.minimum() else value

def test_optimization_box_visible(self):
""" Check if both spinner box is visible after starting widget """
self.assertEqual(self.spin_boxes[5].box.isHidden(), False)
self.assertEqual(self.spin_boxes[6].box.isHidden(), False)
def setter(value):
if value == gamma_spin.specialValueText():
gamma_spin.setValue(gamma_spin.minimum())
else:
gamma_spin.setValue(value)

def test_optimization_box_checked(self):
""" Check if spinner box for iteration limit is enabled or disabled """
for value in (True, False):
self.widget.max_iter_check_box.setChecked(value)
self.assertEqual(self.widget.max_iter_check_box.isChecked(), value)
self.assertEqual(self.spin_boxes[6].isEnabled(), value)
self.parameters = [
ParameterMapping("C", self.widget.c_spin),
ParameterMapping("gamma", self.widget._kernel_params[0],
values=values, setter=setter, getter=getter),
ParameterMapping("coef0", self.widget._kernel_params[1]),
ParameterMapping("degree", self.widget._kernel_params[2]),
ParameterMapping("tol", self.widget.tol_spin),
ParameterMapping("max_iter", self.widget.max_iter_spin[1])]

def test_type_button_checked(self):
""" Check if SVM type is selected after click """
self.widget.type_box.buttons[0].click()
self.assertEqual(self.widget.type_box.buttons[0].isChecked(), True)
self.widget.type_box.buttons[1].click()
self.assertEqual(self.widget.type_box.buttons[1].isChecked(), True)
def test_parameters_unchecked(self):
"""Check learner and model for various values of all parameters
when Iteration limit is not checked
"""
self.widget.max_iter_spin[0].setCheckState(False)
self.parameters[-1] = DefaultParameterMapping("max_iter", -1)
self.test_parameters()

def test_type_button_properties_visible(self):
""" Check if spinner box in SVM type are visible """
self.assertEqual(not self.spin_boxes[0].isHidden(), True)
self.assertEqual(not self.spin_boxes[1].isHidden(), True)
def test_parameters_svm_type(self):
"""Check learner and model for various values of all parameters
when NuSVM is chosen
"""
self.assertEqual(self.widget.svmtype, OWSVMClassification.C_SVC)
# setChecked(True) does not trigger callback event
self.widget.nu_radio.click()
self.assertEqual(self.widget.svmtype, OWSVMClassification.Nu_SVC)
self.parameters[0] = ParameterMapping("nu", self.widget.nu_spin)
self.test_parameters()

def test_data_before_apply(self):
""" Check if data are set """
self.widget.set_data(Table("iris")[:100])
self.widget.apply()
self.assertEqual(len(self.widget.data), 100)

def test_output_signal_learner(self):
""" Check if we have on output learner """
self.widget.kernel_box.buttons[0].click()
self.widget.set_data(Table("iris")[:100])
self.widget.apply()
self.assertNotEqual(self.widget.learner, None)
def test_kernel_equation(self):
"""Check if the right equation is written according to kernel """
for i in range(4):
if self.widget.kernel_box.buttons[i].isChecked():
self.assertEqual(self.widget.kernel_eq,
self.widget.kernels[i][1])
break
for i in range(4):
self.widget.kernel_box.buttons[i].click()
self.assertEqual(self.widget.kernel_eq, self.widget.kernels[i][1])

def test_output_params(self):
""" Check ouput params """
self.widget.kernel_box.buttons[0].click()
self.widget.set_data(Table("iris")[:100])
self.widget.max_iter_check_box.setChecked(True)
self.widget.apply()
self.widget.type_box.buttons[0].click()
params = self.widget.learner.params
self.assertEqual(params.get('C'), self.spin_boxes[0].value())
self.widget.type_box.buttons[1].click()
params = self.widget.learner.params
self.assertEqual(params.get('nu'), self.spin_boxes[1].value())
self.assertEqual(params.get('gamma'), self.spin_boxes[2].value())
self.assertEqual(params.get('coef0'), self.spin_boxes[3].value())
self.assertEqual(params.get('degree'), self.spin_boxes[4].value())
self.assertEqual(params.get('tol'), self.spin_boxes[5].value())
self.assertEqual(params.get('max_iter'), self.spin_boxes[6].value())
def test_kernel_spins(self):
"""Check if the right spins are visible according to kernel """
for i, hidden in enumerate([[True, True, True],
[False, False, False],
[False, True, True],
[False, False, True]]):
if self.widget.kernel_box.buttons[i].isChecked():
self.assertEqual([self.widget._kernel_params[j].box.isHidden()
for j in range(3)], hidden)
break
for i, hidden in enumerate([[True, True, True],
[False, False, False],
[False, True, True],
[False, False, True]]):
self.widget.kernel_box.buttons[i].click()
self.assertEqual([self.widget._kernel_params[j].box.isHidden()
for j in range(3)], hidden)
47 changes: 22 additions & 25 deletions Orange/widgets/regression/owsvmregression.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,38 +40,35 @@ def _add_type_box(self):
self.controlArea, self, "svrtype", [], box="SVR Type",
orientation=form)

form.addWidget(gui.appendRadioButton(box, "ε-SVR", addToLayout=False),
0, 0, Qt.AlignLeft)
form.addWidget(QtGui.QLabel("Cost (C):"),
0, 1, Qt.AlignRight)
form.addWidget(gui.doubleSpin(box, self, "epsilon_C", 0.1, 512.0, 0.1,
decimals=2, addToLayout=False),
0, 2)
form.addWidget(QLabel("Loss epsilon (ε):"),
1, 1, Qt.AlignRight)
form.addWidget(gui.doubleSpin(box, self, "epsilon", 0.1, 512.0, 0.1,
decimals=2, addToLayout=False),
1, 2)
self.epsilon_radio = gui.appendRadioButton(box, "ε-SVR",
addToLayout=False)
self.epsilon_C_spin = gui.doubleSpin(box, self, "epsilon_C", 0.1, 512.0,
0.1, decimals=2, addToLayout=False)
self.epsilon_spin = gui.doubleSpin(box, self, "epsilon", 0.1, 512.0,
0.1, decimals=2, addToLayout=False)
form.addWidget(self.epsilon_radio, 0, 0, Qt.AlignLeft)
form.addWidget(QtGui.QLabel("Cost (C):"), 0, 1, Qt.AlignRight)
form.addWidget(self.epsilon_C_spin, 0, 2)
form.addWidget(QLabel("Loss epsilon (ε):"), 1, 1, Qt.AlignRight)
form.addWidget(self.epsilon_spin, 1, 2)

form.addWidget(gui.appendRadioButton(box, "ν-SVR", addToLayout=False),
2, 0, Qt.AlignLeft)
form.addWidget(QLabel("Cost (C):"),
2, 1, Qt.AlignRight)
form.addWidget(gui.doubleSpin(box, self, "nu_C", 0.1, 512.0, 0.1,
decimals=2, addToLayout=False),
2, 2)
form.addWidget(QLabel("Complexity bound (ν):"),
3, 1, Qt.AlignRight)
form.addWidget(gui.doubleSpin(box, self, "nu", 0.05, 1.0, 0.05,
decimals=2, addToLayout=False),
3, 2)
self.nu_radio = gui.appendRadioButton(box, "ν-SVR", addToLayout=False)
self.nu_C_spin = gui.doubleSpin(box, self, "nu_C", 0.1, 512.0, 0.1,
decimals=2, addToLayout=False)
self.nu_spin = gui.doubleSpin(box, self, "nu", 0.05, 1.0, 0.05,
decimals=2, addToLayout=False)
form.addWidget(self.nu_radio, 2, 0, Qt.AlignLeft)
form.addWidget(QLabel("Cost (C):"), 2, 1, Qt.AlignRight)
form.addWidget(self.nu_C_spin, 2, 2)
form.addWidget(QLabel("Complexity bound (ν):"), 3, 1, Qt.AlignRight)
form.addWidget(self.nu_spin, 3, 2)

def create_learner(self):
kernel = ["linear", "poly", "rbf", "sigmoid"][self.kernel_type]
common_args = dict(
kernel=kernel,
degree=self.degree,
gamma=self.gamma,
gamma=self.gamma if self.gamma else self._default_gamma,
coef0=self.coef0,
tol=self.tol,
preprocessors=self.preprocessors
Expand Down
Loading

0 comments on commit 46961b4

Please sign in to comment.