【问题标题】:How to do Constrained Linear Regression - scikit learn?如何进行约束线性回归 - scikit 学习?
【发布时间】:2021-01-27 21:19:08
【问题描述】:

我正在尝试使用一些约束来执行线性回归主题以获得一定的预测。 我想让模型预测一半的线性预测,而后半部分的线性预测接近前半部分的最后一个值,使用非常窄的范围(使用约束),类似于图中的绿线。

完整代码:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
pd.options.mode.chained_assignment = None  # default='warn'
data = [5.269, 5.346, 5.375, 5.482, 5.519, 5.57, 5.593999999999999, 5.627000000000001, 5.724, 5.818, 5.792999999999999, 5.817, 5.8389999999999995, 5.882000000000001, 5.92, 6.025, 6.064, 6.111000000000001, 6.1160000000000005, 6.138, 6.247000000000001, 6.279, 6.332000000000001, 6.3389999999999995, 6.3420000000000005, 6.412999999999999, 6.442, 6.519, 6.596, 6.603, 6.627999999999999, 6.76, 6.837000000000001, 6.781000000000001, 6.8260000000000005, 6.849, 6.875, 6.982, 7.018, 7.042000000000001, 7.068, 7.091, 7.204, 7.228, 7.261, 7.3420000000000005, 7.414, 7.44, 7.516, 7.542000000000001, 7.627000000000001, 7.667000000000001, 7.821000000000001, 7.792999999999999, 7.756, 7.871, 8.006, 8.078, 7.916, 7.974, 8.074, 8.119, 8.228, 7.976, 8.045, 8.312999999999999, 8.335, 8.388, 8.437999999999999, 8.456, 8.227, 8.266, 8.277999999999999, 8.289, 8.299, 8.318, 8.332, 8.34, 8.349, 8.36, 8.363999999999999, 8.368, 8.282, 8.283999999999999]
time = range(1,85,1)   
x=int(0.7*len(data))
df = pd.DataFrame(list(zip(*[time, data])))
df.columns = ['time', 'data']
# print df
x=int(0.7*len(df))
train = df[:x]
valid = df[x:]
models = []
names = []
tr_x_ax = []
va_x_ax = []
pr_x_ax = []
tr_y_ax = []
va_y_ax = []
pr_y_ax = []
time_model = []
models.append(('LR', LinearRegression()))

for name, model in models:
    x_train=df.iloc[:, 0][:x].values
    y_train=df.iloc[:, 1][:x].values
    x_valid=df.iloc[:, 0][x:].values
    y_valid=df.iloc[:, 1][x:].values

    model = LinearRegression()
    # poly = PolynomialFeatures(5)
    x_train= x_train.reshape(-1, 1)
    y_train= y_train.reshape(-1, 1)
    x_valid = x_valid.reshape(-1, 1)
    y_valid = y_valid.reshape(-1, 1)
    # model.fit(x_train,y_train)
    model.fit(x_train,y_train.ravel())
    # score = model.score(x_train,y_train.ravel())
    # print 'score', score
    preds = model.predict(x_valid)
    tr_x_ax.extend(train['data'])
    va_x_ax.extend(valid['data'])
    pr_x_ax.extend(preds)

    valid['Predictions'] = preds
    valid.index = df[x:].index
    train.index = df[:x].index
    plt.figure(figsize=(5,5))
    # plt.plot(train['data'],label='data')
    # plt.plot(valid[['Close', 'Predictions']])
    x = valid['data']
    # print x
    # plt.plot(valid['data'],label='validation')
    plt.plot(valid['Predictions'],label='Predictions before',color='orange')



y =range(0,58)
y1 =range(58,84)
for index, item in enumerate(pr_x_ax):
    if index >13:
        pr_x_ax[index] = pr_x_ax[13]
pr_x_ax = list([float(i) for i in pr_x_ax])
va_x_ax = list([float(i) for i in va_x_ax])
tr_x_ax = list([float(i) for i in tr_x_ax])
plt.plot(y,tr_x_ax,  label='train' , color='red',  linewidth=2)
plt.plot(y1,va_x_ax,  label='validation1' , color='blue',  linewidth=2)
plt.plot(y1,pr_x_ax,  label='Predictions after' , color='green',  linewidth=2)
plt.xlabel("time")
plt.ylabel("data")
plt.xticks(rotation=45)
plt.legend()
plt.show()

如果你看到这个图:

label:Predictions before,模型预测它没有任何约束(我不需要这个结果)。

标签:Predictions after,模型在约束范围内预测它,但这是在模型预测之后并且所有值都等于 index = 71 , item 8.56 的最后一个值。

我在第 64 行中使用了循环 for index, item in enumerate(pr_x_ax):,曲线是从 71 秒到 85 秒的直线,如您所见,以便向您展示我需要模型如何工作。

我可以建立模型给出相同的结果而不是 for 循环吗???

请多多指教

【问题讨论】:

  • 你能把你的问题写得更短更切题吗?我读了两遍,还是没明白。
  • 我提出了一个简短的问题。
  • @Algabri 看起来你在绿线上绘制的右转根本无法预测,因为只训练红线点。要使此类转弯可预测,您需要在该转弯位置周围设置一些训练点。否则现在通过给定模型的训练点只是认为所有预测都应该只用直线来近似而不用转弯。
  • @Algabri 为了获得好的解决方案,您需要随机抽取数据进行训练和验证。而不是做df[:x]df[x:] 你需要将随机的(x, y) 点放到训练集和随机到验证集的所需比例,比如70% 用于训练,30% 用于验证。那么你的训练应该会达到正确的绿色结果。
  • @Algabri 此外,LinearRegression 仅使用单条直线预测数据,它不能有转弯,对于分段线性近似,如您在绿线上想要的那样,应该使用一些更复杂的模型,我会尝试阅读文档以找到这样的模型。

标签: python machine-learning scikit-learn linear-regression


【解决方案1】:

我希望通过绘制绿线在您的问题中,您确实希望经过训练的模型能够预测向右的线性水平转向。但是当前训练的模型只画了一条橙色的直线。

对于任何算法和类型的任何训练模型来说,为了学习行为模型中的一些异常变化,至少需要具有该异常变化的一些样本。或者至少观察数据中的一些隐藏含义应该指向有这种不寻常的变化。

换句话说,为了让您的模型学习右转绿线,模型应该在训练数据集中具有右转点。但是您将train = df[:int(0.7 * len(df))] 的第一个(最左边)70% 的数据作为训练数据,并且该训练数据没有这样的右转,并且该训练数据看起来接近一条直线。

因此,您需要以不同的方式将数据重新采样到训练和验证中 - 从X 的整个范围内随机抽取 70% 的样本,其余的用于验证。这样在您的训练数据中也包括右转的样本。

第二件事是LinearRegression 模型总是只用一条直线对预测进行建模,这条直线不能右转。为了右转,您需要一些更复杂的模型。

模型右转的一种方法是分段线性,即有几条连接的直线。我在sklearn里面没有找到现成的分段线性模型,只用了其他的pip模型。所以我决定实现我自己的简单类PieceWiseLinearRegression,它使用np.piecewise()scipy.optimize.curve_fit() 来对分段线性函数进行建模。

下一张图显示了应用上面提到的两个东西的结果,代码在后面,以不同的方式重新采样数据集并建模分段线性函数。您当前的线性模型LR 仍然只使用一条直线进行预测,而我的分段线性PWLR2 橙色线由两段组成并正确预测右转:

为了清楚地看到一张PWLR2 图表,我也做了下一张图片:

我的类PieceWiseLinearRegression 在创建对象时只接受一个参数n - 用于预测的线性段数。以上图片使用n = 2

import sys, numpy as np, pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
np.random.seed(0)

class PieceWiseLinearRegression:
    @classmethod
    def nargs_func(cls, f, n):
        return eval('lambda ' + ', '.join([f'a{i}'for i in range(n)]) + ': f(' + ', '.join([f'a{i}'for i in range(n)]) + ')', locals())
        
    @classmethod
    def piecewise_linear(cls, n):
        condlist = lambda xs, xa: [(lambda x: (
            (xs[i] <= x if i > 0 else np.full_like(x, True, dtype = np.bool_)) &
            (x < xs[i + 1] if i < n - 1 else np.full_like(x, True, dtype = np.bool_))
        ))(xa) for i in range(n)]
        funclist = lambda xs, ys: [(lambda i: (
            lambda x: (
                (x - xs[i]) * (ys[i + 1] - ys[i]) / (
                    (xs[i + 1] - xs[i]) if abs(xs[i + 1] - xs[i]) > 10 ** -7 else 10 ** -7 * (-1, 1)[xs[i + 1] - xs[i] >= 0]
                ) + ys[i]
            )
        ))(j) for j in range(n)]
        def f(x, *pargs):
            assert len(pargs) == (n + 1) * 2, (n, pargs)
            xs, ys = pargs[0::2], pargs[1::2]
            xa = x.ravel().astype(np.float64)
            ya = np.piecewise(x = xa, condlist = condlist(xs, xa), funclist = funclist(xs, ys)).ravel()
            #print('xs', xs, 'ys', ys, 'xa', xa, 'ya', ya)
            return ya
        return cls.nargs_func(f, 1 + (n + 1) * 2)
        
    def __init__(self, n):
        self.n = n
        self.f = self.piecewise_linear(self.n)

    def fit(self, x, y):
        from scipy import optimize
        self.p, self.e = optimize.curve_fit(self.f, x, y, p0 = [j for i in range(self.n + 1) for j in (np.amin(x) + i * (np.amax(x) - np.amin(x)) / self.n, 1)])
        #print('p', self.p)
        
    def predict(self, x):
        return self.f(x, *self.p)

data = [5.269, 5.346, 5.375, 5.482, 5.519, 5.57, 5.593999999999999, 5.627000000000001, 5.724, 5.818, 5.792999999999999, 5.817, 5.8389999999999995, 5.882000000000001, 5.92, 6.025, 6.064, 6.111000000000001, 6.1160000000000005, 6.138, 6.247000000000001, 6.279, 6.332000000000001, 6.3389999999999995, 6.3420000000000005, 6.412999999999999, 6.442, 6.519, 6.596, 6.603, 6.627999999999999, 6.76, 6.837000000000001, 6.781000000000001, 6.8260000000000005, 6.849, 6.875, 6.982, 7.018, 7.042000000000001, 7.068, 7.091, 7.204, 7.228, 7.261, 7.3420000000000005, 7.414, 7.44, 7.516, 7.542000000000001, 7.627000000000001, 7.667000000000001, 7.821000000000001, 7.792999999999999, 7.756, 7.871, 8.006, 8.078, 7.916, 7.974, 8.074, 8.119, 8.228, 7.976, 8.045, 8.312999999999999, 8.335, 8.388, 8.437999999999999, 8.456, 8.227, 8.266, 8.277999999999999, 8.289, 8.299, 8.318, 8.332, 8.34, 8.349, 8.36, 8.363999999999999, 8.368, 8.282, 8.283999999999999]
time = list(range(1, 85))
df = pd.DataFrame(list(zip(time, data)), columns = ['time', 'data'])

choose_train = np.random.uniform(size = (len(df),)) < 0.8
choose_valid = ~choose_train

x_all = df.iloc[:, 0].values
y_all = df.iloc[:, 1].values
x_train = df.iloc[:, 0][choose_train].values
y_train = df.iloc[:, 1][choose_train].values
x_valid = df.iloc[:, 0][choose_valid].values
y_valid = df.iloc[:, 1][choose_valid].values
x_all_lin = np.linspace(np.amin(x_all), np.amax(x_all), 500)

models = []
models.append(('LR', LinearRegression()))
models.append(('PWLR2', PieceWiseLinearRegression(2)))
        
for imodel, (name, model) in enumerate(models):
    model.fit(x_train[:, None], y_train)
    x_all_lin_pred = model.predict(x_all_lin[:, None])
    plt.plot(x_all_lin, x_all_lin_pred, label = f'pred {name}')

plt.plot(x_train, y_train, label='train')
plt.plot(x_valid, y_valid, label='valid')
plt.xlabel('time')
plt.ylabel('data')
plt.legend()
plt.show()

【讨论】:

  • 非常感谢您的帮助。看来这将解决我的问题。我正在尝试在 python 2 中运行您的代码,该代码于今年第一年到期,但我仍在使用它,因为物理机器人系统需要它(它需要更新整个系统)。我得到了这个错误 return eval('lambda ' + ', '.join(['a{i}' for i in range(n)]) + ': (' + ', '.join([' a{i}'for i in range(n)]) + ')', locals()) File "", line 1 lambda a{i}, a{i}, a{i }, a{i}, a{i}, a{i}, a{i}: (a{i}, a{i}, a{i}, a{i}, a{i}, a{ i}, a{i}) ^ SyntaxError: 无效语法
  • @Algabri 而不是'a{i}' 在这两个地方都使用'a%s' % i
  • 非常感谢您发自内心的帮助。你真的节省了我的时间。