chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,595 @@
|
||||
# Sebastian Raschka, 2015 (http://sebastianraschka.com)
|
||||
# Python Machine Learning - Code Examples
|
||||
#
|
||||
# Chapter 7 - Combining Different Models for Ensemble Learning
|
||||
#
|
||||
# S. Raschka. Python Machine Learning. Packt Publishing Ltd., 2015.
|
||||
# GitHub Repo: https://github.com/rasbt/python-machine-learning-book
|
||||
#
|
||||
# License: MIT
|
||||
# https://github.com/rasbt/python-machine-learning-book/blob/master/LICENSE.txt
|
||||
|
||||
|
||||
import math
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import operator
|
||||
from scipy.misc import comb
|
||||
import matplotlib.pyplot as plt
|
||||
from sklearn.base import BaseEstimator
|
||||
from sklearn.base import ClassifierMixin
|
||||
from sklearn.preprocessing import LabelEncoder
|
||||
from sklearn.externals import six
|
||||
from sklearn.base import clone
|
||||
from sklearn.pipeline import _name_estimators
|
||||
from sklearn import datasets
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
from sklearn.preprocessing import LabelEncoder
|
||||
from sklearn.linear_model import LogisticRegression
|
||||
from sklearn.tree import DecisionTreeClassifier
|
||||
from sklearn.neighbors import KNeighborsClassifier
|
||||
from sklearn.pipeline import Pipeline
|
||||
from sklearn.metrics import roc_curve
|
||||
from sklearn.metrics import auc
|
||||
from sklearn.metrics import accuracy_score
|
||||
from sklearn.ensemble import BaggingClassifier
|
||||
from sklearn.ensemble import AdaBoostClassifier
|
||||
from itertools import product
|
||||
|
||||
# Added version check for recent scikit-learn 0.18 checks
|
||||
from distutils.version import LooseVersion as Version
|
||||
from sklearn import __version__ as sklearn_version
|
||||
if Version(sklearn_version) < '0.18':
|
||||
from sklearn.cross_validation import train_test_split
|
||||
from sklearn.cross_validation import cross_val_score
|
||||
from sklearn.cross_validation import GridSearchCV
|
||||
else:
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.model_selection import cross_val_score
|
||||
from sklearn.model_selection import GridSearchCV
|
||||
|
||||
#############################################################################
|
||||
print(50 * '=')
|
||||
print('Section: Learning with ensembles')
|
||||
print(50 * '-')
|
||||
|
||||
|
||||
def ensemble_error(n_classifier, error):
|
||||
k_start = math.ceil(n_classifier / 2.0)
|
||||
probs = [comb(n_classifier, k) * error**k * (1 - error)**(n_classifier - k)
|
||||
for k in range(k_start, n_classifier + 1)]
|
||||
return sum(probs)
|
||||
|
||||
print('Ensemble error', ensemble_error(n_classifier=11, error=0.25))
|
||||
|
||||
error_range = np.arange(0.0, 1.01, 0.01)
|
||||
ens_errors = [ensemble_error(n_classifier=11, error=error)
|
||||
for error in error_range]
|
||||
|
||||
plt.plot(error_range,
|
||||
ens_errors,
|
||||
label='Ensemble error',
|
||||
linewidth=2)
|
||||
|
||||
plt.plot(error_range,
|
||||
error_range,
|
||||
linestyle='--',
|
||||
label='Base error',
|
||||
linewidth=2)
|
||||
|
||||
plt.xlabel('Base error')
|
||||
plt.ylabel('Base/Ensemble error')
|
||||
plt.legend(loc='upper left')
|
||||
plt.grid()
|
||||
# plt.tight_layout()
|
||||
# plt.savefig('./figures/ensemble_err.png', dpi=300)
|
||||
plt.show()
|
||||
|
||||
|
||||
#############################################################################
|
||||
print(50 * '=')
|
||||
print('Section: Implementing a simple majority vote classifier')
|
||||
print(50 * '-')
|
||||
|
||||
np.argmax(np.bincount([0, 0, 1],
|
||||
weights=[0.2, 0.2, 0.6]))
|
||||
|
||||
ex = np.array([[0.9, 0.1],
|
||||
[0.8, 0.2],
|
||||
[0.4, 0.6]])
|
||||
|
||||
p = np.average(ex,
|
||||
axis=0,
|
||||
weights=[0.2, 0.2, 0.6])
|
||||
print('Averaged prediction', p)
|
||||
print('np.argmax(p): ', np.argmax(p))
|
||||
|
||||
|
||||
class MajorityVoteClassifier(BaseEstimator,
|
||||
ClassifierMixin):
|
||||
""" A majority vote ensemble classifier
|
||||
|
||||
Parameters
|
||||
----------
|
||||
classifiers : array-like, shape = [n_classifiers]
|
||||
Different classifiers for the ensemble
|
||||
|
||||
vote : str, {'classlabel', 'probability'} (default='label')
|
||||
If 'classlabel' the prediction is based on the argmax of
|
||||
class labels. Else if 'probability', the argmax of
|
||||
the sum of probabilities is used to predict the class label
|
||||
(recommended for calibrated classifiers).
|
||||
|
||||
weights : array-like, shape = [n_classifiers], optional (default=None)
|
||||
If a list of `int` or `float` values are provided, the classifiers
|
||||
are weighted by importance; Uses uniform weights if `weights=None`.
|
||||
|
||||
"""
|
||||
def __init__(self, classifiers, vote='classlabel', weights=None):
|
||||
|
||||
self.classifiers = classifiers
|
||||
self.named_classifiers = {key: value for key, value
|
||||
in _name_estimators(classifiers)}
|
||||
self.vote = vote
|
||||
self.weights = weights
|
||||
|
||||
def fit(self, X, y):
|
||||
""" Fit classifiers.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : {array-like, sparse matrix}, shape = [n_samples, n_features]
|
||||
Matrix of training samples.
|
||||
|
||||
y : array-like, shape = [n_samples]
|
||||
Vector of target class labels.
|
||||
|
||||
Returns
|
||||
-------
|
||||
self : object
|
||||
|
||||
"""
|
||||
if self.vote not in ('probability', 'classlabel'):
|
||||
raise ValueError("vote must be 'probability' or 'classlabel'"
|
||||
"; got (vote=%r)"
|
||||
% self.vote)
|
||||
|
||||
if self.weights and len(self.weights) != len(self.classifiers):
|
||||
raise ValueError('Number of classifiers and weights must be equal'
|
||||
'; got %d weights, %d classifiers'
|
||||
% (len(self.weights), len(self.classifiers)))
|
||||
|
||||
# Use LabelEncoder to ensure class labels start with 0, which
|
||||
# is important for np.argmax call in self.predict
|
||||
self.lablenc_ = LabelEncoder()
|
||||
self.lablenc_.fit(y)
|
||||
self.classes_ = self.lablenc_.classes_
|
||||
self.classifiers_ = []
|
||||
for clf in self.classifiers:
|
||||
fitted_clf = clone(clf).fit(X, self.lablenc_.transform(y))
|
||||
self.classifiers_.append(fitted_clf)
|
||||
return self
|
||||
|
||||
def predict(self, X):
|
||||
""" Predict class labels for X.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : {array-like, sparse matrix}, shape = [n_samples, n_features]
|
||||
Matrix of training samples.
|
||||
|
||||
Returns
|
||||
----------
|
||||
maj_vote : array-like, shape = [n_samples]
|
||||
Predicted class labels.
|
||||
|
||||
"""
|
||||
if self.vote == 'probability':
|
||||
maj_vote = np.argmax(self.predict_proba(X), axis=1)
|
||||
else: # 'classlabel' vote
|
||||
|
||||
# Collect results from clf.predict calls
|
||||
predictions = np.asarray([clf.predict(X)
|
||||
for clf in self.classifiers_]).T
|
||||
|
||||
maj_vote = np.apply_along_axis(
|
||||
lambda x:
|
||||
np.argmax(np.bincount(x,
|
||||
weights=self.weights)),
|
||||
axis=1,
|
||||
arr=predictions)
|
||||
maj_vote = self.lablenc_.inverse_transform(maj_vote)
|
||||
return maj_vote
|
||||
|
||||
def predict_proba(self, X):
|
||||
""" Predict class probabilities for X.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : {array-like, sparse matrix}, shape = [n_samples, n_features]
|
||||
Training vectors, where n_samples is the number of samples and
|
||||
n_features is the number of features.
|
||||
|
||||
Returns
|
||||
----------
|
||||
avg_proba : array-like, shape = [n_samples, n_classes]
|
||||
Weighted average probability for each class per sample.
|
||||
|
||||
"""
|
||||
probas = np.asarray([clf.predict_proba(X)
|
||||
for clf in self.classifiers_])
|
||||
avg_proba = np.average(probas, axis=0, weights=self.weights)
|
||||
return avg_proba
|
||||
|
||||
def get_params(self, deep=True):
|
||||
""" Get classifier parameter names for GridSearch"""
|
||||
if not deep:
|
||||
return super(MajorityVoteClassifier, self).get_params(deep=False)
|
||||
else:
|
||||
out = self.named_classifiers.copy()
|
||||
for name, step in six.iteritems(self.named_classifiers):
|
||||
for key, value in six.iteritems(step.get_params(deep=True)):
|
||||
out['%s__%s' % (name, key)] = value
|
||||
return out
|
||||
|
||||
|
||||
#############################################################################
|
||||
print(50 * '=')
|
||||
print('Section: Combining different algorithms for'
|
||||
' classification with majority vote')
|
||||
print(50 * '-')
|
||||
|
||||
iris = datasets.load_iris()
|
||||
X, y = iris.data[50:, [1, 2]], iris.target[50:]
|
||||
le = LabelEncoder()
|
||||
y = le.fit_transform(y)
|
||||
|
||||
X_train, X_test, y_train, y_test =\
|
||||
train_test_split(X, y,
|
||||
test_size=0.5,
|
||||
random_state=1)
|
||||
|
||||
clf1 = LogisticRegression(penalty='l2',
|
||||
C=0.001,
|
||||
random_state=0)
|
||||
|
||||
clf2 = DecisionTreeClassifier(max_depth=1,
|
||||
criterion='entropy',
|
||||
random_state=0)
|
||||
|
||||
clf3 = KNeighborsClassifier(n_neighbors=1,
|
||||
p=2,
|
||||
metric='minkowski')
|
||||
|
||||
pipe1 = Pipeline([['sc', StandardScaler()],
|
||||
['clf', clf1]])
|
||||
pipe3 = Pipeline([['sc', StandardScaler()],
|
||||
['clf', clf3]])
|
||||
|
||||
clf_labels = ['Logistic Regression', 'Decision Tree', 'KNN']
|
||||
|
||||
print('10-fold cross validation:\n')
|
||||
for clf, label in zip([pipe1, clf2, pipe3], clf_labels):
|
||||
scores = cross_val_score(estimator=clf,
|
||||
X=X_train,
|
||||
y=y_train,
|
||||
cv=10,
|
||||
scoring='roc_auc')
|
||||
print("ROC AUC: %0.2f (+/- %0.2f) [%s]"
|
||||
% (scores.mean(), scores.std(), label))
|
||||
|
||||
|
||||
mv_clf = MajorityVoteClassifier(classifiers=[pipe1, clf2, pipe3])
|
||||
|
||||
clf_labels += ['Majority Voting']
|
||||
all_clf = [pipe1, clf2, pipe3, mv_clf]
|
||||
|
||||
for clf, label in zip(all_clf, clf_labels):
|
||||
scores = cross_val_score(estimator=clf,
|
||||
X=X_train,
|
||||
y=y_train,
|
||||
cv=10,
|
||||
scoring='roc_auc')
|
||||
print("ROC AUC: %0.2f (+/- %0.2f) [%s]"
|
||||
% (scores.mean(), scores.std(), label))
|
||||
|
||||
|
||||
#############################################################################
|
||||
print(50 * '=')
|
||||
print('Section: Evaluating and tuning the ensemble classifier')
|
||||
print(50 * '-')
|
||||
|
||||
|
||||
colors = ['black', 'orange', 'blue', 'green']
|
||||
linestyles = [':', '--', '-.', '-']
|
||||
for clf, label, clr, ls \
|
||||
in zip(all_clf,
|
||||
clf_labels, colors, linestyles):
|
||||
|
||||
# assuming the label of the positive class is 1
|
||||
y_pred = clf.fit(X_train,
|
||||
y_train).predict_proba(X_test)[:, 1]
|
||||
fpr, tpr, thresholds = roc_curve(y_true=y_test,
|
||||
y_score=y_pred)
|
||||
roc_auc = auc(x=fpr, y=tpr)
|
||||
plt.plot(fpr, tpr,
|
||||
color=clr,
|
||||
linestyle=ls,
|
||||
label='%s (auc = %0.2f)' % (label, roc_auc))
|
||||
|
||||
plt.legend(loc='lower right')
|
||||
plt.plot([0, 1], [0, 1],
|
||||
linestyle='--',
|
||||
color='gray',
|
||||
linewidth=2)
|
||||
|
||||
plt.xlim([-0.1, 1.1])
|
||||
plt.ylim([-0.1, 1.1])
|
||||
plt.grid()
|
||||
plt.xlabel('False Positive Rate')
|
||||
plt.ylabel('True Positive Rate')
|
||||
|
||||
# plt.tight_layout()
|
||||
# plt.savefig('./figures/roc.png', dpi=300)
|
||||
plt.show()
|
||||
|
||||
|
||||
sc = StandardScaler()
|
||||
X_train_std = sc.fit_transform(X_train)
|
||||
|
||||
|
||||
all_clf = [pipe1, clf2, pipe3, mv_clf]
|
||||
|
||||
x_min = X_train_std[:, 0].min() - 1
|
||||
x_max = X_train_std[:, 0].max() + 1
|
||||
y_min = X_train_std[:, 1].min() - 1
|
||||
y_max = X_train_std[:, 1].max() + 1
|
||||
|
||||
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
|
||||
np.arange(y_min, y_max, 0.1))
|
||||
|
||||
f, axarr = plt.subplots(nrows=2, ncols=2,
|
||||
sharex='col',
|
||||
sharey='row',
|
||||
figsize=(7, 5))
|
||||
|
||||
for idx, clf, tt in zip(product([0, 1], [0, 1]),
|
||||
all_clf, clf_labels):
|
||||
clf.fit(X_train_std, y_train)
|
||||
|
||||
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
|
||||
Z = Z.reshape(xx.shape)
|
||||
|
||||
axarr[idx[0], idx[1]].contourf(xx, yy, Z, alpha=0.3)
|
||||
|
||||
axarr[idx[0], idx[1]].scatter(X_train_std[y_train == 0, 0],
|
||||
X_train_std[y_train == 0, 1],
|
||||
c='blue',
|
||||
marker='^',
|
||||
s=50)
|
||||
|
||||
axarr[idx[0], idx[1]].scatter(X_train_std[y_train == 1, 0],
|
||||
X_train_std[y_train == 1, 1],
|
||||
c='red',
|
||||
marker='o',
|
||||
s=50)
|
||||
|
||||
axarr[idx[0], idx[1]].set_title(tt)
|
||||
|
||||
plt.text(-3.5, -4.5,
|
||||
s='Sepal width [standardized]',
|
||||
ha='center', va='center', fontsize=12)
|
||||
plt.text(-10.5, 4.5,
|
||||
s='Petal length [standardized]',
|
||||
ha='center', va='center',
|
||||
fontsize=12, rotation=90)
|
||||
|
||||
# plt.tight_layout()
|
||||
# plt.savefig('./figures/voting_panel', bbox_inches='tight', dpi=300)
|
||||
plt.show()
|
||||
|
||||
print(mv_clf.get_params())
|
||||
|
||||
params = {'decisiontreeclassifier__max_depth': [1, 2],
|
||||
'pipeline-1__clf__C': [0.001, 0.1, 100.0]}
|
||||
|
||||
grid = GridSearchCV(estimator=mv_clf,
|
||||
param_grid=params,
|
||||
cv=10,
|
||||
scoring='roc_auc')
|
||||
grid.fit(X_train, y_train)
|
||||
|
||||
if Version(sklearn_version) < '0.18':
|
||||
for params, mean_score, scores in grid.grid_scores_:
|
||||
print("%0.3f +/- %0.2f %r"
|
||||
% (mean_score, scores.std() / 2.0, params))
|
||||
|
||||
else:
|
||||
cv_keys = ('mean_test_score', 'std_test_score', 'params')
|
||||
|
||||
for r, _ in enumerate(grid.cv_results_['mean_test_score']):
|
||||
print("%0.3f +/- %0.2f %r"
|
||||
% (grid.cv_results_[cv_keys[0]][r],
|
||||
grid.cv_results_[cv_keys[1]][r] / 2.0,
|
||||
grid.cv_results_[cv_keys[2]][r]))
|
||||
|
||||
print('Best parameters: %s' % grid.best_params_)
|
||||
print('Accuracy: %.2f' % grid.best_score_)
|
||||
|
||||
|
||||
#############################################################################
|
||||
print(50 * '=')
|
||||
print('Section: Bagging -- Building an ensemble of'
|
||||
'classifiers from bootstrap samples')
|
||||
print(50 * '-')
|
||||
|
||||
df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/'
|
||||
'machine-learning-databases/wine/wine.data',
|
||||
header=None)
|
||||
|
||||
df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash',
|
||||
'Alcalinity of ash', 'Magnesium', 'Total phenols',
|
||||
'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins',
|
||||
'Color intensity', 'Hue', 'OD280/OD315 of diluted wines',
|
||||
'Proline']
|
||||
|
||||
# drop 1 class
|
||||
df_wine = df_wine[df_wine['Class label'] != 1]
|
||||
|
||||
y = df_wine['Class label'].values
|
||||
X = df_wine[['Alcohol', 'Hue']].values
|
||||
|
||||
|
||||
le = LabelEncoder()
|
||||
y = le.fit_transform(y)
|
||||
|
||||
X_train, X_test, y_train, y_test =\
|
||||
train_test_split(X, y,
|
||||
test_size=0.40,
|
||||
random_state=1)
|
||||
|
||||
tree = DecisionTreeClassifier(criterion='entropy',
|
||||
max_depth=None,
|
||||
random_state=1)
|
||||
|
||||
bag = BaggingClassifier(base_estimator=tree,
|
||||
n_estimators=500,
|
||||
max_samples=1.0,
|
||||
max_features=1.0,
|
||||
bootstrap=True,
|
||||
bootstrap_features=False,
|
||||
n_jobs=1,
|
||||
random_state=1)
|
||||
|
||||
tree = tree.fit(X_train, y_train)
|
||||
y_train_pred = tree.predict(X_train)
|
||||
y_test_pred = tree.predict(X_test)
|
||||
|
||||
tree_train = accuracy_score(y_train, y_train_pred)
|
||||
tree_test = accuracy_score(y_test, y_test_pred)
|
||||
print('Decision tree train/test accuracies %.3f/%.3f'
|
||||
% (tree_train, tree_test))
|
||||
|
||||
bag = bag.fit(X_train, y_train)
|
||||
y_train_pred = bag.predict(X_train)
|
||||
y_test_pred = bag.predict(X_test)
|
||||
|
||||
bag_train = accuracy_score(y_train, y_train_pred)
|
||||
bag_test = accuracy_score(y_test, y_test_pred)
|
||||
print('Bagging train/test accuracies %.3f/%.3f'
|
||||
% (bag_train, bag_test))
|
||||
|
||||
|
||||
x_min = X_train[:, 0].min() - 1
|
||||
x_max = X_train[:, 0].max() + 1
|
||||
y_min = X_train[:, 1].min() - 1
|
||||
y_max = X_train[:, 1].max() + 1
|
||||
|
||||
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
|
||||
np.arange(y_min, y_max, 0.1))
|
||||
|
||||
f, axarr = plt.subplots(nrows=1, ncols=2,
|
||||
sharex='col',
|
||||
sharey='row',
|
||||
figsize=(8, 3))
|
||||
|
||||
|
||||
for idx, clf, tt in zip([0, 1],
|
||||
[tree, bag],
|
||||
['Decision Tree', 'Bagging']):
|
||||
clf.fit(X_train, y_train)
|
||||
|
||||
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
|
||||
Z = Z.reshape(xx.shape)
|
||||
|
||||
axarr[idx].contourf(xx, yy, Z, alpha=0.3)
|
||||
axarr[idx].scatter(X_train[y_train == 0, 0],
|
||||
X_train[y_train == 0, 1],
|
||||
c='blue', marker='^')
|
||||
|
||||
axarr[idx].scatter(X_train[y_train == 1, 0],
|
||||
X_train[y_train == 1, 1],
|
||||
c='red', marker='o')
|
||||
|
||||
axarr[idx].set_title(tt)
|
||||
|
||||
axarr[0].set_ylabel('Alcohol', fontsize=12)
|
||||
plt.text(10.2, -1.2,
|
||||
s='Hue',
|
||||
ha='center', va='center', fontsize=12)
|
||||
|
||||
# plt.tight_layout()
|
||||
# plt.savefig('./figures/bagging_region.png',
|
||||
# dpi=300,
|
||||
# bbox_inches='tight')
|
||||
plt.show()
|
||||
|
||||
|
||||
#############################################################################
|
||||
print(50 * '=')
|
||||
print('Section: Leveraging weak learners via adaptive boosting')
|
||||
print(50 * '-')
|
||||
|
||||
tree = DecisionTreeClassifier(criterion='entropy',
|
||||
max_depth=1,
|
||||
random_state=0)
|
||||
|
||||
ada = AdaBoostClassifier(base_estimator=tree,
|
||||
n_estimators=500,
|
||||
learning_rate=0.1,
|
||||
random_state=0)
|
||||
|
||||
tree = tree.fit(X_train, y_train)
|
||||
y_train_pred = tree.predict(X_train)
|
||||
y_test_pred = tree.predict(X_test)
|
||||
|
||||
tree_train = accuracy_score(y_train, y_train_pred)
|
||||
tree_test = accuracy_score(y_test, y_test_pred)
|
||||
print('Decision tree train/test accuracies %.3f/%.3f'
|
||||
% (tree_train, tree_test))
|
||||
|
||||
ada = ada.fit(X_train, y_train)
|
||||
y_train_pred = ada.predict(X_train)
|
||||
y_test_pred = ada.predict(X_test)
|
||||
|
||||
ada_train = accuracy_score(y_train, y_train_pred)
|
||||
ada_test = accuracy_score(y_test, y_test_pred)
|
||||
print('AdaBoost train/test accuracies %.3f/%.3f'
|
||||
% (ada_train, ada_test))
|
||||
|
||||
|
||||
x_min, x_max = X_train[:, 0].min() - 1, X_train[:, 0].max() + 1
|
||||
y_min, y_max = X_train[:, 1].min() - 1, X_train[:, 1].max() + 1
|
||||
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
|
||||
np.arange(y_min, y_max, 0.1))
|
||||
|
||||
f, axarr = plt.subplots(1, 2, sharex='col', sharey='row', figsize=(8, 3))
|
||||
|
||||
|
||||
for idx, clf, tt in zip([0, 1],
|
||||
[tree, ada],
|
||||
['Decision Tree', 'AdaBoost']):
|
||||
clf.fit(X_train, y_train)
|
||||
|
||||
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
|
||||
Z = Z.reshape(xx.shape)
|
||||
|
||||
axarr[idx].contourf(xx, yy, Z, alpha=0.3)
|
||||
axarr[idx].scatter(X_train[y_train == 0, 0],
|
||||
X_train[y_train == 0, 1],
|
||||
c='blue', marker='^')
|
||||
axarr[idx].scatter(X_train[y_train == 1, 0],
|
||||
X_train[y_train == 1, 1],
|
||||
c='red', marker='o')
|
||||
axarr[idx].set_title(tt)
|
||||
|
||||
axarr[0].set_ylabel('Alcohol', fontsize=12)
|
||||
plt.text(10.2, -1.2,
|
||||
s='Hue',
|
||||
ha='center', va='center', fontsize=12)
|
||||
|
||||
# plt.tight_layout()
|
||||
# plt.savefig('./figures/adaboost_region.png',
|
||||
# dpi=300,
|
||||
# bbox_inches='tight')
|
||||
plt.show()
|
||||
Reference in New Issue
Block a user