0
# Utilities and Visualization
1
2
Utility functions for model interpretation, configuration management, visualization, and distributed communication. These tools help understand model behavior, manage XGBoost settings, create visual insights, and coordinate distributed training.
3
4
## Capabilities
5
6
### Model Visualization
7
8
Comprehensive visualization tools for understanding model behavior, feature importance, and decision tree structure. These functions integrate with matplotlib and graphviz for publication-quality plots.
9
10
```python { .api }
11
def plot_importance(booster, ax=None, height=0.2, xlim=None, ylim=None,
12
title='Feature importance', xlabel='F score',
13
ylabel='Features', fmap='', importance_type='weight',
14
max_num_features=None, grid=True, show_values=True,
15
values_format='{v}', **kwargs):
16
"""
17
Plot feature importance based on fitted trees.
18
19
Parameters:
20
- booster: Trained XGBoost model (Booster)
21
- ax: Matplotlib axes object (matplotlib.axes.Axes, optional)
22
- height: Bar height for horizontal bar plot (float)
23
- xlim: X-axis limits as (xmin, xmax) (tuple, optional)
24
- ylim: Y-axis limits as (ymin, ymax) (tuple, optional)
25
- title: Plot title (str)
26
- xlabel: X-axis label (str)
27
- ylabel: Y-axis label (str)
28
- fmap: Feature map file path (str)
29
- importance_type: Type of importance to plot (str)
30
Options: 'weight', 'gain', 'cover', 'total_gain', 'total_cover'
31
- max_num_features: Maximum number of top features to display (int, optional)
32
- grid: Whether to show grid lines (bool)
33
- show_values: Whether to show importance values on bars (bool)
34
- values_format: Format string for importance values (str)
35
- **kwargs: Additional arguments passed to matplotlib.pyplot.barh
36
37
Returns: matplotlib.axes.Axes - The plot axes object
38
"""
39
40
def plot_tree(booster, fmap='', num_trees=0, rankdir=None, ax=None,
41
tree_idx=0, show_info=None, precision=None, **kwargs):
42
"""
43
Plot specified tree using matplotlib.
44
45
Parameters:
46
- booster: Trained XGBoost model (Booster)
47
- fmap: Feature map file path (str)
48
- num_trees: Tree index to plot (deprecated, use tree_idx) (int)
49
- rankdir: Direction of plot layout ('UT', 'LR', 'BT', 'RL') (str, optional)
50
- ax: Matplotlib axes object (matplotlib.axes.Axes, optional)
51
- tree_idx: Index of tree to plot (int)
52
- show_info: Information to show in nodes (list of str, optional)
53
Options include: 'split', 'gain', 'cover', 'weight'
54
- precision: Number of decimal places for floating point values (int, optional)
55
- **kwargs: Additional arguments for graphviz layout
56
57
Returns: matplotlib.axes.Axes - The plot axes object
58
"""
59
60
def to_graphviz(booster, fmap='', num_trees=0, rankdir=None,
61
yes_color=None, no_color=None, condition_node_params=None,
62
leaf_node_params=None, **kwargs):
63
"""
64
Convert specified tree to graphviz instance for advanced visualization.
65
66
Parameters:
67
- booster: Trained XGBoost model (Booster)
68
- fmap: Feature map file path (str)
69
- num_trees: Tree index (deprecated, use tree_idx in kwargs) (int)
70
- rankdir: Direction of tree layout ('UT', 'LR', 'BT', 'RL') (str, optional)
71
- yes_color: Color for 'yes' edges (str, optional)
72
- no_color: Color for 'no' edges (str, optional)
73
- condition_node_params: Parameters for condition nodes (dict, optional)
74
- leaf_node_params: Parameters for leaf nodes (dict, optional)
75
- **kwargs: Additional parameters including:
76
- tree_idx: Index of tree to visualize (int)
77
- with_stats: Whether to include node statistics (bool)
78
79
Returns: graphviz.Source - Graphviz source object for rendering
80
"""
81
```
82
83
### Configuration Management
84
85
Global configuration management for XGBoost behavior, including device selection, verbosity levels, and algorithm parameters that affect all XGBoost operations.
86
87
```python { .api }
88
def set_config(**new_config):
89
"""
90
Set global XGBoost configuration parameters.
91
92
Parameters:
93
- **new_config: Configuration parameters as keyword arguments
94
Common parameters:
95
- verbosity: Global verbosity level (int, 0-3)
96
0=silent, 1=warning, 2=info, 3=debug
97
- use_rmm: Whether to use RMM memory allocator (bool)
98
- nthread: Global number of threads (int)
99
- device: Global device setting ('cpu', 'cuda', 'gpu') (str)
100
101
Example configurations:
102
set_config(verbosity=2, device='cpu', nthread=4)
103
set_config(use_rmm=True) # For GPU memory management
104
"""
105
106
def get_config():
107
"""
108
Get current global XGBoost configuration values.
109
110
Returns: dict - Dictionary containing all current configuration parameters
111
Keys include: 'verbosity', 'use_rmm', 'nthread', 'device', etc.
112
"""
113
114
def config_context(**new_config):
115
"""
116
Context manager for temporary XGBoost configuration changes.
117
118
Parameters:
119
- **new_config: Temporary configuration parameters
120
121
Returns: Context manager that restores previous config on exit
122
123
Usage:
124
with config_context(verbosity=0, device='cpu'):
125
# XGBoost operations with temporary config
126
model = xgb.train(params, dtrain, num_boost_round=100)
127
# Previous configuration restored automatically
128
"""
129
```
130
131
### Collective Communication
132
133
Low-level distributed communication primitives for custom distributed training setups. These functions enable coordination between multiple workers in distributed environments.
134
135
```python { .api }
136
import xgboost.collective as collective
137
138
def collective.init(config):
139
"""
140
Initialize collective communication library.
141
142
Parameters:
143
- config: Configuration for collective communication (collective.Config)
144
"""
145
146
def collective.finalize():
147
"""Finalize collective communication and clean up resources."""
148
149
def collective.get_rank():
150
"""
151
Get rank (ID) of current process in distributed setup.
152
153
Returns: int - Process rank (0-based indexing)
154
"""
155
156
def collective.get_world_size():
157
"""
158
Get total number of workers in distributed setup.
159
160
Returns: int - Total number of processes
161
"""
162
163
def collective.is_distributed():
164
"""
165
Check if running in distributed mode.
166
167
Returns: bool - True if distributed, False if single process
168
"""
169
170
def collective.communicator_print(msg):
171
"""
172
Print message from the communicator with rank information.
173
174
Parameters:
175
- msg: Message to print (str)
176
"""
177
178
def collective.get_processor_name():
179
"""
180
Get name of the processor/node.
181
182
Returns: str - Processor/hostname identifier
183
"""
184
185
def collective.broadcast(data, root):
186
"""
187
Broadcast object from root process to all other processes.
188
189
Parameters:
190
- data: Data to broadcast (any serializable object)
191
- root: Rank of root process (int)
192
193
Returns: object - Broadcasted data (same on all processes)
194
"""
195
196
def collective.allreduce(data, op):
197
"""
198
Perform allreduce operation across all processes.
199
200
Parameters:
201
- data: Data to reduce (numeric array-like)
202
- op: Reduction operation (collective.Op)
203
Options: Op.MAX, Op.MIN, Op.SUM, Op.BITWISE_AND, Op.BITWISE_OR, Op.BITWISE_XOR
204
205
Returns: object - Reduced result (same on all processes)
206
"""
207
208
def collective.signal_error(msg):
209
"""
210
Signal error condition to terminate all processes.
211
212
Parameters:
213
- msg: Error message (str)
214
"""
215
216
class collective.Config:
217
def __init__(self, *, retry=3, timeout=300, tracker_host_ip=None,
218
tracker_port=0, tracker_timeout=30):
219
"""
220
Configuration for collective communication.
221
222
Parameters:
223
- retry: Number of connection retries (int)
224
- timeout: Communication timeout in seconds (int)
225
- tracker_host_ip: IP address of tracker (str, optional)
226
- tracker_port: Port number for tracker (int)
227
- tracker_timeout: Tracker connection timeout (int)
228
"""
229
230
class collective.CommunicatorContext:
231
def __init__(self, **kwargs):
232
"""
233
Context manager for collective communicator setup and cleanup.
234
235
Parameters:
236
- **kwargs: Arguments passed to collective.init()
237
"""
238
239
class collective.Op:
240
"""Enumeration of reduction operations for allreduce."""
241
MAX = 0 # Maximum value
242
MIN = 1 # Minimum value
243
SUM = 2 # Sum of values
244
BITWISE_AND = 3 # Bitwise AND
245
BITWISE_OR = 4 # Bitwise OR
246
BITWISE_XOR = 5 # Bitwise XOR
247
```
248
249
### Distributed Coordination
250
251
High-level coordination utilities for distributed training setups, including worker synchronization and fault tolerance.
252
253
```python { .api }
254
from xgboost.tracker import RabitTracker
255
256
class RabitTracker:
257
def __init__(self, n_workers, host_ip=None, port=0, *, sortby='process',
258
timeout=3600):
259
"""
260
Tracker for collective communication coordination between workers.
261
262
Parameters:
263
- n_workers: Number of worker processes (int)
264
- host_ip: Host IP address for tracker (str, optional)
265
If None, uses local machine IP
266
- port: Port number for tracker (int, 0 for auto-assignment)
267
- sortby: Method for sorting workers ('process' or 'ip') (str)
268
- timeout: Maximum time to wait for workers (int, seconds)
269
"""
270
271
def start(self):
272
"""
273
Start the tracker server.
274
275
Returns: dict - Connection information including:
276
- 'host_ip': Tracker IP address
277
- 'port': Tracker port number
278
"""
279
280
def wait_for(self, timeout=None):
281
"""
282
Wait for all workers to connect and complete training.
283
284
Parameters:
285
- timeout: Maximum wait time in seconds (int, optional)
286
If None, uses the timeout from __init__
287
288
Returns: bool - True if all workers completed successfully
289
"""
290
291
def worker_args(self):
292
"""
293
Get environment arguments for worker processes.
294
295
Returns: dict - Environment variables for workers including:
296
- 'DMLC_TRACKER_URI': Tracker URI
297
- 'DMLC_TRACKER_PORT': Tracker port
298
- 'DMLC_TASK_ID': Task ID (set per worker)
299
"""
300
301
def build_info():
302
"""
303
Get build information for XGBoost installation.
304
305
Returns: dict - Build configuration including:
306
- 'USE_CUDA': Whether CUDA support is compiled
307
- 'USE_NCCL': Whether NCCL support is available
308
- 'COMPILER': Compiler used for building
309
- 'BUILD_WITH_SHARED_PTR': Shared pointer support
310
- And other compilation flags
311
"""
312
```
313
314
## Usage Examples
315
316
### Feature Importance Visualization
317
318
```python
319
import xgboost as xgb
320
import matplotlib.pyplot as plt
321
from sklearn.datasets import make_classification
322
323
# Create and train model
324
X, y = make_classification(n_samples=1000, n_features=20, n_informative=10,
325
random_state=42)
326
feature_names = [f'feature_{i}' for i in range(20)]
327
328
dtrain = xgb.DMatrix(X, label=y, feature_names=feature_names)
329
params = {'objective': 'binary:logistic', 'max_depth': 6, 'learning_rate': 0.1}
330
model = xgb.train(params, dtrain, num_boost_round=100)
331
332
# Plot feature importance with different metrics
333
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
334
335
# Weight-based importance (frequency of splits)
336
xgb.plot_importance(model, ax=axes[0,0], importance_type='weight',
337
max_num_features=10, title='Importance by Weight')
338
339
# Gain-based importance (average gain of splits)
340
xgb.plot_importance(model, ax=axes[0,1], importance_type='gain',
341
max_num_features=10, title='Importance by Gain')
342
343
# Cover-based importance (average coverage of splits)
344
xgb.plot_importance(model, ax=axes[1,0], importance_type='cover',
345
max_num_features=10, title='Importance by Cover')
346
347
# Total gain importance
348
xgb.plot_importance(model, ax=axes[1,1], importance_type='total_gain',
349
max_num_features=10, title='Importance by Total Gain')
350
351
plt.tight_layout()
352
plt.show()
353
354
# Customized importance plot
355
plt.figure(figsize=(10, 8))
356
xgb.plot_importance(model,
357
height=0.5,
358
importance_type='gain',
359
max_num_features=15,
360
title='Top 15 Features by Information Gain',
361
xlabel='Information Gain',
362
grid=True,
363
show_values=True,
364
values_format='{v:.3f}',
365
color='skyblue',
366
edgecolor='navy')
367
plt.show()
368
```
369
370
### Tree Visualization
371
372
```python
373
import xgboost as xgb
374
import matplotlib.pyplot as plt
375
376
# Train a simple model for visualization
377
dtrain = xgb.DMatrix(X[:100], label=y[:100], feature_names=feature_names[:5])
378
simple_model = xgb.train({'max_depth': 3, 'objective': 'binary:logistic'},
379
dtrain, num_boost_round=3)
380
381
# Plot individual trees
382
fig, axes = plt.subplots(1, 3, figsize=(20, 6))
383
384
for i in range(3):
385
xgb.plot_tree(simple_model, ax=axes[i], tree_idx=i,
386
show_info=['split', 'gain'],
387
precision=2)
388
axes[i].set_title(f'Tree {i}')
389
390
plt.tight_layout()
391
plt.show()
392
393
# Create graphviz visualization for high-quality output
394
graphviz_tree = xgb.to_graphviz(simple_model, tree_idx=0,
395
rankdir='TB', # Top to bottom
396
yes_color='lightblue',
397
no_color='lightcoral',
398
condition_node_params={'shape': 'box', 'style': 'filled'},
399
leaf_node_params={'shape': 'ellipse', 'style': 'filled'})
400
401
# Save as PDF or PNG
402
graphviz_tree.render('xgb_tree_visualization', format='png', cleanup=True)
403
print("Tree visualization saved as 'xgb_tree_visualization.png'")
404
405
# Display in Jupyter notebook
406
# graphviz_tree.view()
407
```
408
409
### Configuration Management
410
411
```python
412
import xgboost as xgb
413
414
# Check current configuration
415
current_config = xgb.get_config()
416
print("Current XGBoost configuration:")
417
for key, value in current_config.items():
418
print(f" {key}: {value}")
419
420
# Set global configuration
421
xgb.set_config(verbosity=2, # More verbose output
422
nthread=4, # Use 4 threads globally
423
device='cpu') # Force CPU usage
424
425
print(f"\nUpdated verbosity: {xgb.get_config()['verbosity']}")
426
427
# Use configuration context for temporary changes
428
print("\nTraining with temporary quiet configuration:")
429
with xgb.config_context(verbosity=0): # Silent mode
430
quiet_model = xgb.train({'objective': 'binary:logistic'}, dtrain,
431
num_boost_round=10)
432
print("Model trained silently")
433
434
print("Back to previous verbosity level")
435
436
# Configuration for GPU training (if available)
437
try:
438
with xgb.config_context(device='cuda'):
439
gpu_params = {'objective': 'binary:logistic', 'tree_method': 'gpu_hist'}
440
gpu_model = xgb.train(gpu_params, dtrain, num_boost_round=10)
441
print("GPU training completed")
442
except Exception as e:
443
print(f"GPU training not available: {e}")
444
445
# Reset to default configuration
446
xgb.set_config(verbosity=1, device='cpu')
447
```
448
449
### Distributed Communication Example
450
451
```python
452
import xgboost as xgb
453
from xgboost import collective
454
import numpy as np
455
456
# Example of basic collective operations (typically run across multiple processes)
457
def distributed_example():
458
"""Example showing collective communication primitives."""
459
460
# Initialize collective communication
461
config = collective.Config(timeout=300, retry=3)
462
463
with collective.CommunicatorContext(config=config):
464
rank = collective.get_rank()
465
world_size = collective.get_world_size()
466
467
print(f"Process {rank} of {world_size}")
468
print(f"Running on: {collective.get_processor_name()}")
469
470
# Example data for each process
471
local_data = np.array([rank + 1, rank * 2])
472
473
# Broadcast data from rank 0 to all processes
474
if rank == 0:
475
broadcast_data = {'model_params': {'max_depth': 6, 'learning_rate': 0.1}}
476
else:
477
broadcast_data = None
478
479
shared_params = collective.broadcast(broadcast_data, root=0)
480
print(f"Rank {rank} received: {shared_params}")
481
482
# Sum all local data across processes
483
global_sum = collective.allreduce(local_data, collective.Op.SUM)
484
print(f"Rank {rank} global sum: {global_sum}")
485
486
# Find maximum across all processes
487
global_max = collective.allreduce(local_data, collective.Op.MAX)
488
print(f"Rank {rank} global max: {global_max}")
489
490
# Note: This would typically be run in a multi-process environment
491
# distributed_example()
492
```
493
494
### RabitTracker for Distributed Training
495
496
```python
497
import xgboost as xgb
498
from xgboost.tracker import RabitTracker
499
import multiprocessing as mp
500
import os
501
502
def worker_process(worker_id, tracker_args, data_partition):
503
"""Worker process for distributed training."""
504
505
# Set up environment for this worker
506
os.environ.update(tracker_args)
507
os.environ['DMLC_TASK_ID'] = str(worker_id)
508
509
# Initialize collective communication
510
collective_config = collective.Config()
511
collective.init(collective_config)
512
513
try:
514
# Create local DMatrix from data partition
515
X_local, y_local = data_partition
516
dtrain_local = xgb.DMatrix(X_local, label=y_local)
517
518
# Training parameters
519
params = {
520
'objective': 'binary:logistic',
521
'max_depth': 6,
522
'learning_rate': 0.1,
523
'tree_method': 'hist'
524
}
525
526
# Distributed training
527
model = xgb.train(params, dtrain_local, num_boost_round=50)
528
529
print(f"Worker {worker_id} completed training")
530
return model
531
532
finally:
533
collective.finalize()
534
535
def distributed_training_example():
536
"""Example of distributed training setup with RabitTracker."""
537
538
# Create sample data and split into partitions
539
X, y = make_classification(n_samples=10000, n_features=20,
540
n_classes=2, random_state=42)
541
542
n_workers = 4
543
partition_size = len(X) // n_workers
544
data_partitions = []
545
546
for i in range(n_workers):
547
start_idx = i * partition_size
548
end_idx = (i + 1) * partition_size if i < n_workers - 1 else len(X)
549
partition = (X[start_idx:end_idx], y[start_idx:end_idx])
550
data_partitions.append(partition)
551
552
# Initialize tracker
553
tracker = RabitTracker(n_workers=n_workers, timeout=300)
554
555
# Start tracker
556
tracker_info = tracker.start()
557
print(f"Tracker started at {tracker_info['host_ip']}:{tracker_info['port']}")
558
559
# Get worker arguments
560
worker_args = tracker.worker_args()
561
562
# Start worker processes
563
processes = []
564
for worker_id in range(n_workers):
565
p = mp.Process(target=worker_process,
566
args=(worker_id, worker_args, data_partitions[worker_id]))
567
p.start()
568
processes.append(p)
569
570
# Wait for all workers to complete
571
success = tracker.wait_for(timeout=600)
572
573
# Clean up processes
574
for p in processes:
575
p.join()
576
577
if success:
578
print("Distributed training completed successfully!")
579
else:
580
print("Distributed training failed or timed out")
581
582
# Note: Run this in a script, not in interactive environment
583
# distributed_training_example()
584
```
585
586
### Build Information and Diagnostics
587
588
```python
589
import xgboost as xgb
590
591
# Get comprehensive build information
592
build_info = xgb.build_info()
593
594
print("XGBoost Build Information:")
595
print("=" * 50)
596
597
# Check for key capabilities
598
gpu_support = build_info.get('USE_CUDA', False)
599
nccl_support = build_info.get('USE_NCCL', False)
600
omp_support = build_info.get('USE_OPENMP', False)
601
602
print(f"GPU Support (CUDA): {gpu_support}")
603
print(f"Multi-GPU Support (NCCL): {nccl_support}")
604
print(f"OpenMP Support: {omp_support}")
605
606
# Compiler and build details
607
print(f"\nCompiler: {build_info.get('COMPILER', 'Unknown')}")
608
print(f"Build with shared pointers: {build_info.get('BUILD_WITH_SHARED_PTR', False)}")
609
610
# Print all build flags
611
print(f"\nAll build configuration:")
612
for key, value in sorted(build_info.items()):
613
print(f" {key}: {value}")
614
615
# Version information
616
print(f"\nXGBoost version: {xgb.__version__}")
617
618
# Device availability check
619
def check_device_availability():
620
"""Check what devices are available for XGBoost."""
621
devices = []
622
623
# CPU is always available
624
devices.append('cpu')
625
626
# Check GPU availability
627
if build_info.get('USE_CUDA', False):
628
try:
629
# Try to set CUDA device to test availability
630
with xgb.config_context(device='cuda'):
631
devices.append('cuda')
632
except Exception:
633
pass
634
635
return devices
636
637
available_devices = check_device_availability()
638
print(f"\nAvailable devices: {available_devices}")
639
640
# Memory and performance recommendations
641
def get_performance_recommendations():
642
"""Get performance recommendations based on build configuration."""
643
recommendations = []
644
645
if not build_info.get('USE_CUDA', False):
646
recommendations.append("Consider GPU version for large datasets")
647
648
if not build_info.get('USE_OPENMP', False):
649
recommendations.append("OpenMP not available - limited CPU parallelization")
650
651
if build_info.get('USE_NCCL', False):
652
recommendations.append("NCCL available - good for multi-GPU training")
653
654
return recommendations
655
656
recommendations = get_performance_recommendations()
657
if recommendations:
658
print(f"\nPerformance recommendations:")
659
for rec in recommendations:
660
print(f" - {rec}")
661
```
662
663
### Advanced Visualization with Custom Styling
664
665
```python
666
import xgboost as xgb
667
import matplotlib.pyplot as plt
668
import seaborn as sns
669
from matplotlib.patches import Rectangle
670
671
# Set up styling
672
plt.style.use('seaborn-v0_8')
673
colors = sns.color_palette("husl", 10)
674
675
def create_comprehensive_model_report(model, feature_names=None):
676
"""Create a comprehensive visual report for XGBoost model."""
677
678
fig = plt.figure(figsize=(20, 16))
679
680
# Feature importance by different metrics
681
importance_types = ['weight', 'gain', 'cover', 'total_gain']
682
683
for i, imp_type in enumerate(importance_types):
684
ax = plt.subplot(3, 4, i + 1)
685
xgb.plot_importance(model, ax=ax, importance_type=imp_type,
686
max_num_features=10, color=colors[i],
687
title=f'Importance by {imp_type.title()}')
688
689
# Individual trees (first 4 trees)
690
for i in range(4):
691
if i < model.num_boosted_rounds():
692
ax = plt.subplot(3, 4, i + 5)
693
xgb.plot_tree(model, ax=ax, tree_idx=i, precision=2)
694
ax.set_title(f'Tree {i}')
695
696
# Model performance metrics (if available)
697
ax = plt.subplot(3, 2, 5)
698
699
# Get feature scores for analysis
700
feature_scores = model.get_score(importance_type='gain')
701
if feature_scores:
702
top_features = sorted(feature_scores.items(),
703
key=lambda x: x[1], reverse=True)[:10]
704
705
features, scores = zip(*top_features)
706
bars = ax.barh(range(len(features)), scores, color=colors[:len(features)])
707
ax.set_yticks(range(len(features)))
708
ax.set_yticklabels(features)
709
ax.set_xlabel('Feature Gain')
710
ax.set_title('Top 10 Features by Gain')
711
712
# Add value labels on bars
713
for i, (bar, score) in enumerate(zip(bars, scores)):
714
ax.text(bar.get_width() + max(scores) * 0.01, bar.get_y() + bar.get_height()/2,
715
f'{score:.3f}', ha='left', va='center', fontsize=8)
716
717
# Model info panel
718
ax = plt.subplot(3, 2, 6)
719
ax.axis('off')
720
721
# Create info text
722
info_text = f"""
723
Model Information:
724
725
Number of trees: {model.num_boosted_rounds()}
726
Number of features: {model.num_features()}
727
Best iteration: {getattr(model, 'best_iteration', 'N/A')}
728
Best score: {getattr(model, 'best_score', 'N/A')}
729
730
Top 3 Features:
731
"""
732
733
if feature_scores:
734
for i, (feature, score) in enumerate(top_features[:3]):
735
info_text += f"\n {i+1}. {feature}: {score:.3f}"
736
737
ax.text(0.1, 0.9, info_text, transform=ax.transAxes, fontsize=12,
738
verticalalignment='top', fontfamily='monospace',
739
bbox=dict(boxstyle='round', facecolor='lightgray', alpha=0.8))
740
741
plt.suptitle('XGBoost Model Analysis Report', fontsize=16, fontweight='bold')
742
plt.tight_layout()
743
plt.show()
744
745
# Generate comprehensive report
746
create_comprehensive_model_report(model, feature_names)
747
```