В этом уроке: можем ли мы иметь панд в Rust? Что такое смарткор?



Добро пожаловать в третий учебник по Rust и его приложению для машинного обучения! Сегодня мы познакомимся с Polars, фантастическим пакетом Rust для работы с фреймами данных, сериями и smartcore, который станет одним из наших лучших друзей ML на Rust :)

Что вы узнаете в конце этого урока?

  • Работа с фреймами данных и сериями и их операциями в Rust
  • Реализуйте линейную регрессию с помощью smartcore
  • смешать в одном коде smartcore и polars

Фреймы данных в Rust

Одним из наиболее часто используемых объектов науки о данных является thedataframe. Если вы специалист по данным или питонист, вам наверняка приходилось играть с Pandas и фреймами данных. Как следует из названия, эти вычислительные объекты представляют собой данные, вставленные в табличный фрейм, что позволяет легко визуализировать данные и управлять ими. В Rust есть собственные пакеты управления фреймами данных, один из них - Polars.

Polars - это полностью параллельный процессор данных, основанный на Apache Arrow, написанном Ричи Винком. Этот пакет показал высокую производительность по сравнению с популярными пакетами фреймов данных, такими как data.tablein R и Spark. Цель Polars - иметь дело с данными, слишком большими для Pandas и слишком маленькими для Spark. Polars существует в двух API: eager, где операции выполняются немедленно, как в пандах, и lazy, , который оптимизирован для запросов и объединения данных. Помимо этого учебного пособия, необходимо глубоко изучить Polars, но я обязательно напишу что-нибудь об этом очень скоро, с хорошими тестами для сложных операций с данными.

Давайте посмотрим, что люди, изучающие машинное обучение, могут сделать с этим пакетом, так cargo new polars_learning! и наборы данных можно найти здесь: https://github.com/Steboss/ML_and_Rust/tree/master/tutorial_2/datasets и коды здесь: https://github.com/Steboss/ML_and_Rust/tree/master/tutorial_2 / polars_learning

Первое: что делать с Cargo.toml? Давайте добавим первую необходимую зависимость:

На данный момент самая последняя версия поляров - 0.14.2

как мы можем прочитать файл csv и вернуть его в качестве фрейма данных?

Хорошо, здесь много информации. Сначала давайте посмотрим на импорт:

  • Все вещи, с которыми нам нужно иметь дело polars, находятся в prelude (более или менее), поэтому мы можем либо импортировать все оттуда use polars::prelude::*;, либо просто импортировать то, что нам нужно (например, CsvReader, DataType, DataFrame…)
  • Затем use std::fs::File и use std::path::{Path} используются для чтения данного файла, например iris.csv
  • Наконец, use polars::prelude::SerReader содержит все черты, необходимые для CsvReader работы с методом ::new. Помните, мы возвращаем фрейм данных из CsvReaderso no ;

Чтобы прочитать файл csv, мы можем использовать CsvReader:

  • определение входного файла с помощью let file = File::open(path).expect("Cannot open file.");
  • проверьте, присутствуют ли какие-то заголовки .has_header
  • и собрать весь контент .finish(). Конечный результат - это тип PolarResult<DataFrame>, а именно фрейм данных

Наконец, Some похож на Just и Nothing в Haskell, это variant enum и позволяет читать первые 5 строк.

После чтения фрейма данных:

мы хотели бы узнать больше о его размере и форме.

Здесь нет ничего сложного, шаги аналогичны Python Pandas, как в этой функции:

Мы ничего не возвращаем из этой функции, поэтому -> (). Как только фрейм данных создан, мы можем получить df.shape() и распечатать его как {:#?}, чтобы обеспечить правильный синтаксический анализ. Кроме того, мы можем проверить schema, а также dtype для каждого столбца, width - количество столбцов - и height - количество строк.

Мы можем осмотреть столбцы и проверить, как они выглядят:

С get_columns() мы можем читать все столбцы фрейма данных, get_column_names() извлекает все заголовки. Кроме того, мы можем перебирать значения столбцов и делать что-то вроде Python, распечатывая имя столбца и его значения.

Теперь что-то более сексуальное:

объединение фреймов данных вместе.

Часто бывает, что нам нужно складывать разные фреймы данных, особенно когда мы читаем огромные файлы (будет ли это иметь место и для Polars? Мы вернемся к этому очень скоро):

Итак, что у нас есть?

  • вертикальный стек с vstack, где мы можем объединить два фрейма данных вместе (в Pandas это pd.concat([df1, df2]))
  • Мы можем извлечь тип &series из столбца df3.column("sepal.length").unwrap()
  • Что еще более важно, мы можем подумать об извлечении серии для выполнения некоторых операций, поэтому мы можем получить тип series как:

let sepal_length = df3.drop_in_place("sepal.length").unwrap();

  • Затем мы можем проделать некоторые операции с sepal_length и снова добавить это обратно в df как:

let _df4 = df3.insert_at_idx(0, sepal_length).unwrap()

(Здесь подчеркивание перед df4, потому что в Rust - для лучших практик - неиспользуемые переменные должны иметь _)

Теперь, когда вы знаете, как работать с сериалами,

давайте посмотрим, как на самом деле выполнять операции с сериями.

Например, мы хотим выполнить лог-преобразование столбца фрейма данных:

  • Во-первых, мы можем напрямую применить закрытие к столбцу с apply_at_idx - строкой 22. Это метод фрейма данных, нам нужно указать индекс столбца, который мы хотим изменить, и операцию в виде карты, например. |s| s+1 добавляет 1 в столбец
  • В противном случае мы можем действовать непосредственно в сериале. В этом случае мы можем подумать об использовании изменяемого фрейма данных и функции numb_to_log: a) в строке 4 мы преобразуем серию в фрагментированный массив - а именно типизированный массив, который позволяет применять замыкания к данным и собирать результаты типа T. б) Операции drop_in_place.unwrap().rename() создают серию из sepal.length и переименовывают ее в log10.sepal.length. Затем .f64().unwrap() указывает тип этой серии и unwrap преобразует серию в ChunkedArray. c) Наконец, мы можем преобразовать этот массив в массив log10: cast::<Float64Type>() преобразуется в правильный числовой тип и возвращает объект типа Result<>, поэтому нам нужно unwrap, чтобы иметь массив f64, а затем apply(|s| s.log10()) log-преобразовывает цифры. log10 - это метод типа числа f64 в Rust. г) Важно отметить, что мы можем преобразовать ChunkedArray обратно в серию с into_series() и вернуться к основной функции, добавив новую серию в виде столбца с df.with_column()
  • Вышеупомянутое было хорошим примером работы непосредственно с сериями и массивами по частям, но можем ли мы сделать то же самое непосредственно с фреймом данных? Конечно, мы можем -line 31-: сначала apply_at_idx сообщить фрейму данных, что мы хотим применить операцию к столбцу, затем мы можем сопоставить этот столбец с |s|, преобразовать его в массив по частям, s.f64().unwrap() и, наконец, применить операцию так, apply(|t| t.log10())) где |t| относится к элементам столбца.

Последнее, что я хочу показать вам о полярах, это то, что связано с файлом Cargo.toml, а именно с концепцией features. В Rust любой пакет имеет дополнительные функции, скажем, методы, которые можно использовать при необходимости. Например, polars может преобразовывать числовой фрейм данных в массив с именем to_ndarray:

Функции не вступают в действие немедленно, если не добавлены в Cargo.toml явным образом. Это сделано для того, чтобы конечный скомпилированный пакет Rust не имел недопустимых размеров, и помогает сократить время компиляции. Мы видим, что polars имеет множество дополнительных функций, которые можно использовать: https://github.com/pola-rs/polars/blob/master/polars/Cargo.toml

Чтобы добавить to_ndarray, который содержится в polars-core - ядре для всех операций с фреймами данных - нам нужно явно добавить это в файл Cargo:

Только так мы сможем использовать to_ndarray для фрейма данных.

Smartcore + Polars: реализуйте линейную регрессию с фреймами данных!

Smartcore - это довольно новый пакет Rust с множеством приложений для машинного обучения и очень активным сообществом. Алгоритмы машинного обучения в smartcore варьируются от классификации до кластеризации и показателей для оценки модели - еще несколько относительно rusty machine

Кроме того, smartcore оптимально интегрирован с различными библиотеками Rust Algebra, такими как ndarray или nalgebra, что дает этому пакету гораздо большую гибкость при работе с различными типами данных. Обсуждая с Лоренцо, активным участником smartcore, все еще существует пробел в реализации более общего анализа данных, поэтому мы очень скоро увидим более быстрые методы для интеграции фреймов данных Polars в smartcore, но сейчас мы используем этот случай, чтобы узнать больше данных, играющих в Rust!

А теперь давайте поработаем руками с Smartcore: cargo new smartcore_linear_regression и коды можно найти здесь: https://github.com/Steboss/ML_and_Rust/tree/master/tutorial_2/smartcore_linear_regression

Во-первых, давайте подготовим Cargo.toml с необходимыми зависимостями и функциями:

Во-вторых, давайте перейдем к main.rs и подумаем о 4 шагах для реализации линейной регрессии:

  • прочитать ввод boston_dataset.csv и выполнить синтаксический анализ в кадре данных Polars
  • Извлеките соответствующие обучающие функции и цель
  • преобразовать функции и цель в формат DenseMatrix smartcore
  • запустить LinearRegression :)

Среди всего обычного импорта мы должны импортировать 93_ функции, а именно LinearRegression, DenseMatrix, BaseMatrix, train_test_split и mean_squared_error. Как мы увидим, smartcore требует в качестве входных данных тип матрицы DenseMatrix или BaseMatrix, которые оба основаны на пакете nalgebra.

Первые два шага - хороший повод узнать больше о Rust:

  • в read_csv я оставил метод with_delimiter, который может оказаться полезным. Это не относится к этому набору данных, однако with_delimiter хочет, чтобы байты использовались только в качестве входных данных. Например, если рассматривать хэш в качестве разделителя между столбцами: with_delimiter(b'#') - обратите внимание, мы используем ', а не кавычки ", что привело бы к ошибке.
  • из feature_and_target мы впервые возвращаем 2 переменные одновременно. Просто заключите переменные и их типы в круглые скобки ()
  • Здесь вы могли увидеть, насколько удобен polars, а не настраиваемый читатель csv, где мы можем select столбцы, которые нам нужны. Чтобы выбрать несколько столбцов, нам нужно передать Rust vec, таким образом vec![col1, col2, ...]

Шаг 3: преобразуйте наш фрейм данных функции и целевой столбец в желаемый формат smartcore DenseMatrix

  • Как видите, в строке 7 мы используем ndarray для преобразования фрейма данных в массив. Оттуда мы можем инициализировать нулевую матрицу xmatrix.
  • Эта матрица имеет тип DenseMatrix с числами <f64>. Чтобы создать нулевую матрицу, мы можем просто использовать BaseMatrix::zeros.
  • Затем немного внимания к типам Rust, мы инициализируем два счетчика, один для строк row и один для столбцов col, и перебираем значения массива.
  • Итерация идет, поскольку features_res был одномерным массивом. На каждой итерации мы set преобразуем значение в xmatrix путем ОТКАЗЫВАЯСЯ от ЗАЕМНОГО значения, используя *val, чтобы не было ошибки: expected f64 not &f64. Наконец, мы можем вернуть DenseMatrix с помощью Ok

Аналогичный способ был использован для преобразования массива target в vector - обратите внимание, как вставка значения в вектор в Rust похожа на C ++ push

Наконец, важно выделить mut вдоль всех этих переменных, поскольку мы установили их в ноль, а затем заполнили. В коде github я также оставил - прокомментировал - процедуру, чтобы сделать то же самое в main без использования функций.

И, наконец, линейная регрессия и примерка в смарткор! Это очень просто, и он запомнил мой подход sklearn, что делает smartcore фантастическую библиотеку для работы - и нам не нужно беспокоиться о train_test_split как о rusty_machine:

Просто как тот!

НА СТАРТ, ВНИМАНИЕ, МАРШ!

Теперь у нас есть все готово для запуска нашего кода. Как всегда, вы можете запустить cargo run в папке Rust. Если все прошло хорошо, вы должны увидеть Cargo.lock файл и targetпапку с нашим скомпилированным кодом. Кроме того, cargo run запустит наш main.rs

Если вы достаточно довольны, вы можете собрать весь пакет с cargo build, который будет выполнять дальнейшую оптимизацию нашего кода, и вуаля!

🎊🎊🎊

На данный момент все! Определенно замечательный шаг вперед в изучении Rust сегодня! Следите за новостями в следующем уроке!

Если у вас возникнут вопросы или комментарии, напишите мне по электронной почте: [email protected].

Или вы можете связаться со мной в Instagram: https://www.instagram.com/a_pic_of_science/