Skip to content

Commit a7e3cbb

Browse files
authored
Support for Keras compatible classifiers (scikit-multilearn#138)
Implement wrapper for Keras scikit-compatible wraper.
1 parent 0735724 commit a7e3cbb

File tree

8 files changed

+393
-7
lines changed

8 files changed

+393
-7
lines changed

.travis.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,16 @@ install:
4242
- if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then docker run -e "MEKA_CLASSPATH=/opt/meka/lib/" -v "$TRAVIS_BUILD_DIR:/home/python-dev/repo" --name scikit_multilearn_dev_test_docker -d niedakh/scikit-multilearn-dev:latest; fi
4343
- if [[ "$TOXENV" == "linux_py2" ]]; then docker exec -it scikit_multilearn_dev_test_docker pip install -r /home/python-dev/repo/requirements-all.txt; fi
4444
- if [[ "$TOXENV" == "linux_py3" ]]; then docker exec -it scikit_multilearn_dev_test_docker pip3 install -r /home/python-dev/repo/requirements-all.txt; fi
45+
- if [[ "$TOXENV" == "linux_py2" ]]; then docker exec -it scikit_multilearn_dev_test_docker pip install -r /home/python-dev/repo/requirements/keras.txt; fi
46+
- if [[ "$TOXENV" == "linux_py3" ]]; then docker exec -it scikit_multilearn_dev_test_docker pip3 install -r /home/python-dev/repo/requirements/keras.txt; fi
4547
- if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then find $TRAVIS_BUILD_DIR -name \*.pyc -delete; fi
4648

4749
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd $TRAVIS_BUILD_DIR && pip install -r requirements-all.txt ; fi
50+
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd $TRAVIS_BUILD_DIR && pip install -r requirements/keras.txt; fi
4851
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd $TRAVIS_BUILD_DIR && pip3 install -r requirements-all.txt; fi
52+
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd $TRAVIS_BUILD_DIR && pip3 install https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-1.12.0-py3-none-any.whl; fi
53+
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd $TRAVIS_BUILD_DIR && pip3 install -r requirements/keras.txt; fi
54+
4955
notifications:
5056
slack:
5157
secure: aTCuexl+J0s+ll8sS+p65Atmr9hgOYg0Qkv1nOwb+2oVlJtIYpwLymYZdMymeMtcbK8nlCKGuZRj9jpowzLvSU5kmmmbUdCskEzLf8guE/TUN5iMcIdvKnlAxfJ/MBXw59bjIuF3saYmzpkYbaBvj150M/tXMkYMLSgqwFMQq6U=

appveyor.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,26 @@ environment:
2828
- PYTHON: "C:\\Python27.10"
2929
PYTHON_VERSION: "2.7.10"
3030
PYTHON_ARCH: "32"
31+
HAS_KERAS: "0"
3132

3233
- PYTHON: "C:\\Python27.10-x64"
3334
PYTHON_VERSION: "2.7.10"
3435
PYTHON_ARCH: "64"
36+
HAS_KERAS: "0"
3537

3638
# Pre-installed Python versions, which Appveyor may upgrade to
3739
# a later point release.
3840
# See: http://www.appveyor.com/docs/installed-software#python
3941

40-
- PYTHON: "C:\\Python34"
41-
PYTHON_VERSION: "3.4.x" # currently 3.4.3
42+
- PYTHON: "C:\\Python35"
43+
PYTHON_VERSION: "3.6.x" # currently 3.4.3
4244
PYTHON_ARCH: "32"
45+
HAS_KERAS: "0"
4346

44-
- PYTHON: "C:\\Python34-x64"
45-
PYTHON_VERSION: "3.4.x" # currently 3.4.3
47+
- PYTHON: "C:\\Python36-x64"
48+
PYTHON_VERSION: "3.6.x" # currently 3.4.3
4649
PYTHON_ARCH: "64"
50+
HAS_KERAS: "1"
4751

4852

4953
install:
@@ -90,6 +94,7 @@ install:
9094
# target Python version and architecture
9195
- "pip install -r requirements\\base.txt"
9296
- "pip install -r requirements\\meka.txt"
97+
- ps: If ($env:HAS_KERAS -eq "1") { & pip install -r requirements\\keras.txt }
9398
# - "pip install -r requirements\\gpl.txt"
9499
- "pip install -r requirements\\test.txt"
95100

docs/source/index.rst

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
<div class="section">
3131
<div class="row">
32-
<div class="col feature s12 m12 l6">
32+
<div class="col feature s12 m6 l4">
3333
<div class="card blue-grey darken-3">
3434
<div class="card-content white-text">
3535
<span class="card-title"><i class="fas fa-brain"></i> Lots of classifiers</span>
@@ -42,7 +42,7 @@
4242
</div>
4343
</div>
4444

45-
<div class="col feature s12 m6 l6">
45+
<div class="col feature s12 m6 l4">
4646
<div class="card blue-grey lighten-5">
4747
<div class="card-content black-text">
4848
<span class="card-title"><i class="fab fa-connectdevelop"></i> Label Relations</span>
@@ -107,14 +107,28 @@
107107
<div class="card-content white-text">
108108
<span class="card-title"><i class="fas fa-box-open"></i> MEKA wrapper</span>
109109
<p>Missing a particular classifier which exists in the Java MEKA and WEKA stack?
110-
Now you can use it like a native scikit classifier!.</p>
110+
Now you can use it like a native scikit classifier!</p>
111111
</div>
112112
<div class="card-action right-align">
113113
<a href="meka.html" class="waves-effect waves-light btn">Using MEKA</a>
114114
</div>
115115
</div>
116116
</div>
117117

118+
119+
<div class="col feature s12 m6 l4">
120+
<div class="card blue-grey lighten-5">
121+
<div class="card-content black-text">
122+
<span class="card-title"><b>K</b> Keras support</span>
123+
<p>Need a particular deep learning single/multi class classifier? You can now use it for multi-label
124+
problems with scikit-multilearn!</p>
125+
</div>
126+
<div class="card-action right-align">
127+
<a href="keras.html" class="waves-effect waves-light btn">Using Keras</a>
128+
</div>
129+
</div>
130+
</div>
131+
118132
<div class="col feature s12 m6 l4">
119133
<div class="card blue-grey lighten-5">
120134
<div class="card-content black-text">

docs/source/keras.ipynb

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 103,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"from skmultilearn.ext import Keras\n",
10+
"from keras.models import Sequential\n",
11+
"from keras.layers import Dense\n",
12+
"import numpy\n",
13+
"import sklearn.metrics as metrics\n",
14+
"from skmultilearn.dataset import load_dataset"
15+
]
16+
},
17+
{
18+
"cell_type": "code",
19+
"execution_count": null,
20+
"metadata": {},
21+
"outputs": [],
22+
"source": [
23+
"X_train, y_train, feature_names, label_names = load_dataset('emotions', 'train')\n",
24+
"X_test, y_test, _, _ = load_dataset('emotions', 'test')"
25+
]
26+
},
27+
{
28+
"cell_type": "markdown",
29+
"metadata": {},
30+
"source": [
31+
"# Single-class Keras classifier"
32+
]
33+
},
34+
{
35+
"cell_type": "markdown",
36+
"metadata": {},
37+
"source": [
38+
"We train a two-layer neural network using Keras and tensortflow as backend (feel free to use others), the network is fairly simple 12 x 8 RELU that finish with a sigmoid activator optimized via binary cross entropy. This is a case from the [Keras example page](https://keras.io/scikit-learn-api/). Note that the model creation function must create a model that accepts an input dimension and outpus a relevant output dimension. The Keras wrapper from scikit-multilearn will pass relevant dimensions upon fitting. "
39+
]
40+
},
41+
{
42+
"cell_type": "code",
43+
"execution_count": 105,
44+
"metadata": {},
45+
"outputs": [],
46+
"source": [
47+
"def create_model_single_class(input_dim, output_dim):\n",
48+
"\t# create model\n",
49+
"\tmodel = Sequential()\n",
50+
"\tmodel.add(Dense(12, input_dim=input_dim, activation='relu'))\n",
51+
"\tmodel.add(Dense(8, activation='relu'))\n",
52+
"\tmodel.add(Dense(output_dim, activation='sigmoid'))\n",
53+
"\t# Compile model\n",
54+
"\tmodel.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])\n",
55+
"\treturn model"
56+
]
57+
},
58+
{
59+
"cell_type": "markdown",
60+
"metadata": {},
61+
"source": [
62+
"Let's use it with a problem transformation method which converts multi-label classification problems to single-label single-class problems, ex. Binary Relevance which trains a classifier per label."
63+
]
64+
},
65+
{
66+
"cell_type": "code",
67+
"execution_count": 123,
68+
"metadata": {},
69+
"outputs": [],
70+
"source": [
71+
"from skmultilearn.problem_transform import BinaryRelevance"
72+
]
73+
},
74+
{
75+
"cell_type": "code",
76+
"execution_count": 126,
77+
"metadata": {},
78+
"outputs": [],
79+
"source": [
80+
"clf = BinaryRelevance(classifier=Keras(create_model_single_class), require_dense=[True,True])"
81+
]
82+
},
83+
{
84+
"cell_type": "code",
85+
"execution_count": 127,
86+
"metadata": {},
87+
"outputs": [
88+
{
89+
"data": {
90+
"text/plain": [
91+
"BinaryRelevance(classifier=<__main__.Keras object at 0x7f4d0b040978>,\n",
92+
" require_dense=[True, True])"
93+
]
94+
},
95+
"execution_count": 127,
96+
"metadata": {},
97+
"output_type": "execute_result"
98+
}
99+
],
100+
"source": [
101+
"clf.fit(X_train,y_train)"
102+
]
103+
},
104+
{
105+
"cell_type": "code",
106+
"execution_count": 130,
107+
"metadata": {},
108+
"outputs": [],
109+
"source": [
110+
"y_pred = clf.predict(X_test)"
111+
]
112+
},
113+
{
114+
"cell_type": "code",
115+
"execution_count": 133,
116+
"metadata": {},
117+
"outputs": [
118+
{
119+
"data": {
120+
"text/plain": [
121+
"0.25495049504950495"
122+
]
123+
},
124+
"execution_count": 133,
125+
"metadata": {},
126+
"output_type": "execute_result"
127+
}
128+
],
129+
"source": [
130+
"metrics.hamming_loss(y_test, y_pred)"
131+
]
132+
},
133+
{
134+
"cell_type": "markdown",
135+
"metadata": {},
136+
"source": [
137+
"# Single-class Keras classifier"
138+
]
139+
},
140+
{
141+
"cell_type": "markdown",
142+
"metadata": {},
143+
"source": [
144+
"We now train a multi-class neural network using Keras and tensortflow as backend (feel free to use others) optimized via categorical cross entropy. This is a case from the [Keras multi-class tutorial](https://machinelearningmastery.com/multi-class-classification-tutorial-keras-deep-learning-library/). Note again that the model creation function must create a model that accepts an input dimension and outpus a relevant output dimension. The Keras wrapper from scikit-multilearn will pass relevant dimensions upon fitting. We must also tel the Keras wrapper that this is a multi-class case. We use the Label Powerset multi-label to multi-class transformation approach, but this can also be used with all the advanced label space division methods available in scikit-multilearn."
145+
]
146+
},
147+
{
148+
"cell_type": "code",
149+
"execution_count": 136,
150+
"metadata": {},
151+
"outputs": [],
152+
"source": [
153+
"from skmultilearn.problem_transform import LabelPowerset"
154+
]
155+
},
156+
{
157+
"cell_type": "code",
158+
"execution_count": 137,
159+
"metadata": {},
160+
"outputs": [],
161+
"source": [
162+
"def create_model_multiclass(input_dim, output_dim):\n",
163+
"\t# create model\n",
164+
"\tmodel = Sequential()\n",
165+
"\tmodel.add(Dense(8, input_dim=input_dim, activation='relu'))\n",
166+
"\tmodel.add(Dense(output_dim, activation='softmax'))\n",
167+
"\t# Compile model\n",
168+
"\tmodel.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])\n",
169+
"\treturn model"
170+
]
171+
},
172+
{
173+
"cell_type": "code",
174+
"execution_count": 138,
175+
"metadata": {},
176+
"outputs": [],
177+
"source": [
178+
"clf = LabelPowerset(classifier=Keras(create_model_multiclass, multi_class=True), require_dense=[True,True])"
179+
]
180+
},
181+
{
182+
"cell_type": "code",
183+
"execution_count": 139,
184+
"metadata": {},
185+
"outputs": [
186+
{
187+
"data": {
188+
"text/plain": [
189+
"LabelPowerset(classifier=<__main__.Keras object at 0x7f4d09229be0>,\n",
190+
" require_dense=[True, True])"
191+
]
192+
},
193+
"execution_count": 139,
194+
"metadata": {},
195+
"output_type": "execute_result"
196+
}
197+
],
198+
"source": [
199+
"clf.fit(X_train,y_train)"
200+
]
201+
},
202+
{
203+
"cell_type": "code",
204+
"execution_count": 140,
205+
"metadata": {},
206+
"outputs": [],
207+
"source": [
208+
"y_pred = clf.predict(X_test)"
209+
]
210+
},
211+
{
212+
"cell_type": "code",
213+
"execution_count": 142,
214+
"metadata": {},
215+
"outputs": [
216+
{
217+
"data": {
218+
"text/plain": [
219+
"0.22277227722772278"
220+
]
221+
},
222+
"execution_count": 142,
223+
"metadata": {},
224+
"output_type": "execute_result"
225+
}
226+
],
227+
"source": [
228+
"metrics.accuracy_score(y_test, y_pred)"
229+
]
230+
},
231+
{
232+
"cell_type": "code",
233+
"execution_count": null,
234+
"metadata": {},
235+
"outputs": [],
236+
"source": []
237+
}
238+
],
239+
"metadata": {
240+
"kernelspec": {
241+
"display_name": "Python 3",
242+
"language": "python",
243+
"name": "python3"
244+
},
245+
"language_info": {
246+
"codemirror_mode": {
247+
"name": "ipython",
248+
"version": 3
249+
},
250+
"file_extension": ".py",
251+
"mimetype": "text/x-python",
252+
"name": "python",
253+
"nbconvert_exporter": "python",
254+
"pygments_lexer": "ipython3",
255+
"version": "3.6.6"
256+
}
257+
},
258+
"nbformat": 4,
259+
"nbformat_minor": 2
260+
}

requirements/keras.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
keras
2+
tensorflow

skmultilearn/ext/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,19 @@
1010
| :class:`~skmultilearn.ext.Meka` | Wrapper for the Multilabel Extension to WEKA - |
1111
| | `MEKA <http://meka.sf.net>`_ library |
1212
+--------------------------------------------+------------------------------------------------------------------+
13+
| :class:`~skmultilearn.ext.Keras` | Wrapper for the Python Deep Learning library - |
14+
| | `KERAS <http://https://keras.io/>`_ |
15+
+--------------------------------------------+------------------------------------------------------------------+
1316
| :func:`~skmultilearn.ext.download_meka` | Helper function for installing MEKA |
1417
+--------------------------------------------+------------------------------------------------------------------+
1518
1619
"""
1720

21+
import sys, platform
1822
from .meka import Meka, download_meka
1923

2024
__all__ = ["Meka", 'download_meka']
25+
26+
if not (sys.version_info[0] == 2 or platform.architecture()[0]=='32bit'):
27+
from .keras import Keras
28+
__all__ += ['Keras']

0 commit comments

Comments
 (0)