Линейная регрессия Tensorflow Haskell расходится - PullRequest
0 голосов
/ 12 июня 2019

Я изучал связку [tenorflow haskell .Однако я изо всех сил пытаюсь заставить базовый пример линейной регрессии из readme работать должным образом: он расходится с тем, что кажется очень простой задачей: выучить строку y = 2x+3, иначе Простая линейная регрессия с использованием градиентного спуска.Я сделал github repo , содержащий исполняемый пример (используя stack + nix), но вот суть этого:

-- | compute simple linear regression, using gradient descent on tensorflow
simpleLinearRegression' :: Float -> [Float] -> [Float] -> IO (Float, Float)
simpleLinearRegression' learningRate x y =
    TFL.withEventWriter "test.log" $ \eventWriter -> TF.runSession $ do
        let x' = TF.vector x
            y' = TF.vector y
        b0 <- TF.initializedVariable 0
        b1 <- TF.initializedVariable 0

        let yHat = (x' * TF.readValue b1) + TF.readValue b0
            loss = TFC.square $ yHat - y'

        TFL.histogramSummary "losses" loss
        TFL.scalarSummary "error" $ TF.reduceSum loss
        TFL.scalarSummary "intercept" $ TF.readValue b0
        TFL.scalarSummary "weight" $ TF.readValue b1

        trainStep <- TF.minimizeWith (TF.gradientDescent learningRate)
                                     loss
                                     [b0, b1]
        summaryT <- TFL.mergeAllSummaries
        forM_ ([1 .. iterations] :: [Int64]) $ \step -> do
            if step `mod` logEveryNth == 0
                then do
                   -- TF.run_ trainStep
                    ((), summaryBytes) <- TF.run (trainStep, summaryT)
                    (TF.Scalar beta0, TF.Scalar beta1) <- TF.run
                        (TF.readValue b0, TF.readValue b1)
                    -- liftIO $ putStrLn $ "Y  = " ++ show beta1 ++ "X + " ++ show beta0
                    let summary = decodeMessageOrDie (TF.unScalar summaryBytes)
                    TFL.logSummary eventWriter step summary
                else TF.run_ trainStep

        (TF.Scalar b0', TF.Scalar b1') <- TF.run (TF.readValue b0, TF.readValue b1)
        return (b0', b1')

Это в основном код из файла readme.где я превратил learningRate в параметр и добавил некоторые записи для тензорной доски (хотя это не помогает мне понять проблему).

Есть небольшой набор тестов, демонстрирующий расходящуюся ситуацию:


linearRegressionSpec :: Spec
linearRegressionSpec = do
    -- n = 6 vs n = 7 on same x range: PASS vs FAIL (beta0, beta1: NaN)
    linearRegressionTest     0.01 3 2 $ equidist 6 1 6
    linearRegressionTest     0.01 3 2 $ equidist 7 1 6

    -- n = 6, larger x range: PASS vs FAIL
    linearRegressionTest     0.01 3 2 $ equidist 6 1 6
    linearRegressionTest     0.01 3 2 $ equidist 6 1 7

    -- n = 12 vs n = 13: PASS vs FAIL (beta0, beta1: NaN) (reduced learning rate)
    linearRegressionTest     0.005 3 2 $ equidist 12 1 6
    linearRegressionTest     0.005 3 2 $ equidist 13 1 6

    -- another one, different learning rate, but diverges with growing sample size.
    -- this is the learning rate used in the Readme.
    linearRegressionTest     0.001 3 2 $ equidist 26 1 10
    linearRegressionTest     0.001 3 2 $ equidist 27 1 10

    -- n = 99 vs n = 100, ranging from -1 to 1: PASS vs FAIL (beta1 estimate = 0)
    -- this one is different: the failing case does not diverge.
    linearRegressionTest     0.01 3 2 $ equidist 99  (-1) 1
    linearRegressionTest     0.01 3 2 $ equidist 100 (-1) 1
    linearRegressionTest     0.001 3 2 $ equidist 100 (-1) 1

    -- initial goal: fit linear regression on advertising data from ISLR, Chapter 3.1
    islrOLSSpec

-- | produce a list of n values equally distributed over the range (minX, maxX)
equidist :: Int -> Float -> Float -> [Float]
equidist n minX maxX =
    let n'  = fromIntegral $ n - 1
        f k = ((n' - k) * minX + k*maxX) / n'
    in f <$> [0 .. n']

roughlyEqual :: (Num a, Ord a, Fractional a) => a -> a -> Bool
roughlyEqual expected actual = 0.01 > abs (expected - actual)

-- switching between different implementations
-- fitFunction = Readme.fit
fitFunction = simpleLinearRegression'
-- fitFunction = simpleLinearRegressionMMH

linearRegressionTest :: Float -> Float -> Float -> [Float] -> Spec
linearRegressionTest learnRate beta0 beta1 xs = do
    let ys = (\x -> beta1*x + beta0) <$> xs
    it ("linear regression on one variable, n = "  ++
        show (length xs) ++ ", range (" ++ show (head xs) ++ ", " ++ show (last xs) ++ ")") $ do
            (beta0Hat, beta1Hat) <- fitFunction learnRate (fromList xs) (fromList ys)
            beta0Hat `shouldSatisfy` roughlyEqual beta0
            beta1Hat `shouldSatisfy` roughlyEqual beta1

Из чего я узнаю:

  • уменьшение скорости обучения улучшает конвергенцию
  • увеличение размера выборки уменьшает конвергенцию
  • с учетом дисперсии входных данныхпеременная уменьшает сходимость

Тем не менее, я озадачен поведением.Я не ожидал бы, что расхождение является такой большой проблемой, что мне кажется очень маленьким набором данных.

Вопросы:

  1. Что-то не так с кодом?
  2. Если нет, как вы можете определить, можно ли применять градиентный спуск и когда?
  3. Существуют ли стратегии смягчения (например, нормализации данных)?
  4. Хотя я понимаю взаимосвязь между скоростью обучения и конвергенцией, я удивлен влиянием размера выборки и диапазона входных переменных.Существует ли какая-либо формула для оценки хорошей скорости обучения на основе входных данных?

Я начал это исследование после попытки подбора исходного примера простой линейной регрессии из главы 3.1 Введение в статистическое обучение..Я могу заставить пример (регрессия продаж на ТВ) сходиться со скоростью обучения 0.0000001, что требует очень большого количества шагов.

...