Программирование на уровне типов для представления многомерных массивов (тензоры)

Я хотел бы иметь тип для представления многомерных массивов (тензоров) безопасным способом. поэтому я мог бы написать, например: zero :: Tensor (5,3,2) Integer, который будет представлять многомерный массив из 5 элементов, каждый из которых имеет 3 элемента, каждый из которых имеет 2 элемента, где все элементы Integers

Как бы вы определили этот тип, используя программирование на уровне типов?

Изменить:

После замечательного ответа Алека, который реализовал это с помощью GADTs,

Интересно, могли бы вы сделать еще один шаг и поддержать несколько реализаций class Tensor и операций над тензорами и сериализацией тензоров?

так что вы могли бы, например:

  • GPU или CPU реализации с использованием C
  • чистые Haskell реализации
  • реализация, которая только печатает график вычислений и ничего не вычисляет
  • реализация, кэширующая результаты на диске
  • параллельные или распределенные вычисления
  • так далее...

Все типы безопасны и просты в использовании.

Я намерен создать библиотеку на Haskell, во многом похожую на tensor-flow, но безопасную по типам и гораздо более расширяемую, используя автоматическое дифференцирование. (рекламная библиотека) и точная действительная арифметика (настоящая библиотека)

Я думаю, что функциональный язык, такой как Haskell, гораздо больше подходит для этих вещей (для всех вещей, на мой взгляд), чем экосистема Python, которая каким-то образом выросла.

  • Haskell является чисто функциональным, гораздо более подходящим для вычислительного программирования, чем Python.
  • Haskell намного эффективнее Python и может быть скомпилирован в двоичный код.
  • Ленивость Haskell (возможно) устраняет необходимость оптимизации графа вычислений и таким образом делает код намного проще.
  • гораздо более мощные абстракции в Haskell

Хотя я вижу потенциал, я просто недостаточно хорошо разбираюсь (или недостаточно умен) для этого программирования на уровне типов, поэтому я не знаю, как реализовать такую ​​​​вещь в Haskell и заставить ее скомпилировать.

Вот где мне нужна твоя помощь.


person user47376    schedule 07.05.2017    source источник
comment
Вы можете просмотреть Data.FixedList библиотека для определения типа в зависимости от списков фиксированной длины.   -  person Redu    schedule 07.05.2017
comment
Некоторые, возможно, релевантные ссылки: blog.jle.im/entry /практические-зависимые-типы-в-haskell-1.html blog.jle.im/entry/practical-dependent-types-in-haskell-2.html reddit.com/r/haskell/comments/67f4mw/naperian_tensors Также пакет векторного размера hackage.haskell.org/package/vector-sized   -  person danidiaz    schedule 07.05.2017
comment
Также ознакомьтесь с работой Майка Избицкого над линейным -алгебра часть subhask (он много занимается машинным обучением и т. д., так что это может быть актуально, если вы хотите двигаться в направлении TensorFlow), и мой linearmap-category (которая полностью определяет тензоры -agnostic и никогда не говорит об размерах, но говорит о векторных пространствах — они тоже могут быть бесконечномерными).   -  person leftaroundabout    schedule 08.05.2017


Ответы (1)


Вот один из способов (вот полный Gist). Мы придерживаемся использования чисел Пеано вместо уровня типа Nat GHC только потому, что с ними лучше работает индукция.

{-# LANGUAGE GADTs, PolyKinds, DataKinds, TypeOperators, FlexibleInstances, FlexibleContexts #-}

import Data.Foldable
import Text.PrettyPrint.HughesPJClass

data Nat = Z | S Nat

-- Some type synonyms that simplify uses of 'Nat'
type N0 = Z
type N1 = S N0
type N2 = S N1
type N3 = S N2
type N4 = S N3
type N5 = S N4
type N6 = S N5
type N7 = S N6
type N8 = S N7
type N9 = S N8

-- Similar to lists, but indexed over their length
data Vector (dim :: Nat) a where
  Nil    :: Vector Z a
  (:-)   :: a -> Vector n a -> Vector (S n) a

infixr 5 :-

data Tensor (dim :: [Nat]) a where
  Scalar :: a -> Tensor '[] a
  Tensor :: Vector d (Tensor ds a) -> Tensor (d : ds) a

Для отображения этих типов мы будем использовать пакет pretty (который уже поставляется с GHC).

instance (Foldable (Vector n), Pretty a) => Pretty (Vector n a) where
  pPrint = braces . sep . punctuate (text ",") . map pPrint . toList

instance Pretty a => Pretty (Tensor '[] a) where
  pPrint (Scalar x) = pPrint x

instance (Pretty (Tensor ds a), Pretty a, Foldable (Vector d)) => Pretty (Tensor (d : ds) a) where
  pPrint (Tensor xs) = pPrint xs

Тогда вот экземпляры Foldable для наших типов данных (здесь нет ничего удивительного — я включаю это только потому, что вам это нужно для компиляции экземпляров Pretty):

instance Foldable (Vector Z) where
  foldMap f Nil = mempty

instance Foldable (Vector n) => Foldable (Vector (S n)) where
  foldMap f (x :- xs) = f x `mappend` foldMap f xs


instance Foldable (Tensor '[]) where
  foldMap f (Scalar x) = f x

instance (Foldable (Vector d), Foldable (Tensor ds)) => Foldable (Tensor (d : ds)) where
  foldMap f (Tensor xs) = foldMap (foldMap f) xs

Наконец, часть, которая отвечает на ваш вопрос: мы можем определить Applicative (Vector n) и Applicative (Tensor ds) аналогично тому, как определяется Applicative ZipList (за исключением того, что pure не возвращает и пустой список - он возвращает список правильной длины).

instance Applicative (Vector Z) where
  pure _ = Nil
  Nil <*> Nil = Nil

instance Applicative (Vector n) => Applicative (Vector (S n)) where
  pure x = x :- pure x
  (x :- xs) <*> (y :- ys) = x y :- (xs <*> ys)


instance Applicative (Tensor '[]) where
  pure = Scalar
  Scalar x <*> Scalar y = Scalar (x y)

instance (Applicative (Vector d), Applicative (Tensor ds)) => Applicative (Tensor (d : ds)) where
  pure x = Tensor (pure (pure x))
  Tensor xs <*> Tensor ys = Tensor ((<*>) <$> xs <*> ys)

Затем в GHCi довольно просто сделать вашу функцию zero:

ghci> :set -XDataKinds
ghci> zero = pure 0
ghci> pPrint (zero :: Tensor [N5,N3,N2] Integer)
{{{0, 0}, {0, 0}, {0, 0}},
 {{0, 0}, {0, 0}, {0, 0}},
 {{0, 0}, {0, 0}, {0, 0}},
 {{0, 0}, {0, 0}, {0, 0}},
 {{0, 0}, {0, 0}, {0, 0}}}
person Alec    schedule 08.05.2017
comment
Потрясающий ответ! спасибо, но не могли бы мы сделать еще один шаг в абстракции? скажем, я хочу иметь несколько реализаций тензорных структур данных и операций, может ли этот подход работать с классом типов? - person user47376; 08.05.2017
comment
@ user47376 Ваше редактирование немного меняет вопрос. :) Да, можно сделать этот класс типов вместо GADT. Такой подход оказывается довольно волосатым. Вместо этого я рекомендую вам попробовать аннотировать Vector и Tensor типом, чтобы указать его представление во время выполнения (см., как repa делает это для его Array тип — дополнительная информация о возможных представлениях приведена вверху связанной страницы). - person Alec; 08.05.2017
comment
Что ты имеешь в виду? Информация о типе времени выполнения делает его небезопасным для типов, все дело в том, чтобы заставить компилятор проверить правильность размеров, или я упускаю вашу точку зрения? - person user47376; 09.05.2017
comment
Что делает реализацию класса типов волосатой? Неужели так сложно перейти от: zero :: Tensor '[3,5,4] к zero :: Tensor '[3,5,4] a => a - person user47376; 09.05.2017
comment
@ user47376 repa содержит закодированную информацию о типах, которая сообщает вам, что представляет собой базовое представление - задержанное, неупакованное и т. Д. Сложность заключается в том, что вы в конечном итоге определяете несколько типов данных для того, что в противном случае было бы просто дизъюнктами GADT. - person Alec; 09.05.2017