Repository: ludwig-ai/ludwig

Issue:
learning_curves() function fails with ValueError: x and y must have same first dimension
**Describe the bug**
learning_curves() function fails when plotting two more more models at once.  Error message:
```
Traceback (most recent call last):
  File "/opt/project/sandbox/mwe.py", line 76, in <module>
    learning_curves(train_stats_list,'y', model_names=model_names, output_directory='./viz', file_format='png')
  File "/usr/local/lib/python3.6/dist-packages/ludwig-0.2.1-py3.6.egg/ludwig/visualize.py", line 740, in learning_curves
  File "/usr/local/lib/python3.6/dist-packages/ludwig-0.2.1-py3.6.egg/ludwig/utils/visualization_utils.py", line 94, in learning_curves_plot
  File "/usr/local/lib/python3.6/dist-packages/matplotlib/axes/_axes.py", line 1666, in plot
    lines = [*self._get_lines(*args, data=data, **kwargs)]
  File "/usr/local/lib/python3.6/dist-packages/matplotlib/axes/_base.py", line 225, in __call__
    yield from self._plot_args(this, kwargs)
  File "/usr/local/lib/python3.6/dist-packages/matplotlib/axes/_base.py", line 391, in _plot_args
    x, y = self._xy_from_xy(x, y)
  File "/usr/local/lib/python3.6/dist-packages/matplotlib/axes/_base.py", line 270, in _xy_from_xy
    "have shapes {} and {}".format(x.shape, y.shape))
ValueError: x and y must have same first dimension, but have shapes (100,) and (24,)
```
Ludwig version: 0.2.1

**To Reproduce**
This program will reproduce the error:
```
#
# Minimum Working Example for learning curves Plot error with early stopping
#


#%%
import numpy as np
import pandas as pd
import warnings
warnings.simplefilter('ignore')

import ludwig
from ludwig.api import LudwigModel
from ludwig.visualize import learning_curves

print(ludwig.__version__)

#%%
NUM_ROWS = 5000
NUM_COLS = 20

#%% create synthetic training data explanatory variables
np.random.seed(42)

# create relevant explanatory variables
X = np.random.rand(NUM_ROWS, NUM_COLS)
X_df = pd.DataFrame(X)
X_df.columns = ['X_' + '{:0>3d}'.format(i+1) for i in range(NUM_COLS)]

#%% create response variable
np.random.seed(21)
beta = -2 + 4 * np.random.rand(NUM_COLS)
beta = np.insert(beta, 0, 1)
print(beta)

# closure to create response variable
def create_logistic(beta):
    return lambda x: 1.0 / (np.exp(-np.sum(np.insert(x, 0, 1) * beta)) + 1)

calc_logistic = create_logistic(beta)

# Create response binary response variable
logit_y = pd.Series(np.apply_along_axis(calc_logistic, 1, X))
print(logit_y.describe())

# create binary classification response variable
y = (logit_y > logit_y.describe()['50%']).astype('int')
y_df = pd.DataFrame(y)
y_df.columns = ['y']

df_train = pd.concat([X_df, y_df], axis=1)

#%% setup two models to train
model_definitions = [
    {
        'input_features': [{'name': 'X_'+'{:0>3d}'.format(i+1), 'type': 'numerical'} for i in range(NUM_COLS)],
        'output_features': [{'name':'y', 'type':'binary', 'fc_layers':[{'fc_size':128}]}]
    },
     {
        'input_features': [{'name': 'X_'+'{:0>3d}'.format(i+1), 'type': 'numerical'} for i in range(NUM_COLS)],
        'output_features': [{'name':'y', 'type':'binary', 'fc_layers':[{'fc_size':128}, {'fc_size': 64}, {'fc_size': 32}]}],
    }
]

model_names = ['model1', 'model2']

train_stats_list = []

# train the models
for i in range(len(model_definitions)):
    print('training model:', model_names[i])
    model = LudwigModel(model_definition=model_definitions[i])
    train_stats = model.train(data_df=df_train)
    train_stats_list.append(train_stats)

# create learning_curve plot for the training
learning_curves(train_stats_list,'y', model_names=model_names, output_directory='./viz', file_format='png')
```

**Expected behavior**
Learning rate curve plot for the multiple model training.

**Screenshots**
See error message above.

**Environment (please complete the following information):**
 - OS: MacOS 10.15.2
 - Docker Container base image: Ubuntu 18.04
 - TensorFlow: 1.14.0
- Python version: 3.6.8
- Ludwig version: 0.2.1

**Additional context**
From what I can tell the error occurs because early stopping caused different number of epochs to be recorded for the two model training runs.  

If I can determine root cause, I'll submit a PR to fix.



Discussion:

Resolving patch:
diff --git a/ludwig/utils/visualization_utils.py b/ludwig/utils/visualization_utils.py
--- a/ludwig/utils/visualization_utils.py
+++ b/ludwig/utils/visualization_utils.py
@@ -90,11 +90,11 @@ def learning_curves_plot(
         name_prefix = algorithm_names[
                           i] + ' ' if algorithm_names is not None and i < len(
             algorithm_names) else ''
-        ax.plot(xs, train_values[i], label=name_prefix + 'training',
+        ax.plot(xs[:len(train_values[i])], train_values[i], label=name_prefix + 'training',
                 color=colors[i * 2], linewidth=3)
         if i < len(vali_values) and vali_values[i] is not None and len(
                 vali_values[i]) > 0:
-            ax.plot(xs, vali_values[i], label=name_prefix + 'validation',
+            ax.plot(xs[:len(vali_values[i])], vali_values[i], label=name_prefix + 'validation',
                     color=colors[i * 2 + 1], linewidth=3)
 
     ax.legend()


Test patch: