В этом уроке: можем ли мы иметь панд в Rust? Что такое смарткор?
Добро пожаловать в третий учебник по Rust и его приложению для машинного обучения! Сегодня мы познакомимся с Polars, фантастическим пакетом Rust для работы с фреймами данных, сериями и smartcore, который станет одним из наших лучших друзей ML на Rust :)
- Эталонное репозиторий Github для сегодняшнего руководства находится здесь: https://github.com/Steboss/ML_and_Rust/tree/master/tutorial_1
- Здесь вы можете найти мое краткое введение о Руси.
- Предыдущее руководство - часть 2- о
rusty_machine
и линейной регрессии - Сегодня мы будем иметь дело с линейной регрессией и знаменитым набором данных Boston Housing.
- Чтобы протестировать функции в Rust онлайн, я могу порекомендовать вам это фантастическое приложение https://play.rust-lang.org.
Что вы узнаете в конце этого урока?
- Работа с фреймами данных и сериями и их операциями в Rust
- Реализуйте линейную регрессию с помощью smartcore
- смешать в одном коде smartcore и polars
Фреймы данных в Rust
Одним из наиболее часто используемых объектов науки о данных является thedataframe
. Если вы специалист по данным или питонист, вам наверняка приходилось играть с Pandas
и фреймами данных. Как следует из названия, эти вычислительные объекты представляют собой данные, вставленные в табличный фрейм, что позволяет легко визуализировать данные и управлять ими. В Rust есть собственные пакеты управления фреймами данных, один из них - Polars.
Polars - это полностью параллельный процессор данных, основанный на Apache Arrow, написанном Ричи Винком. Этот пакет показал высокую производительность по сравнению с популярными пакетами фреймов данных, такими как data.table
in 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
. Помните, мы возвращаем фрейм данных изCsvReader
so 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
столбцы, которые нам нужны. Чтобы выбрать несколько столбцов, нам нужно передать Rustvec
, таким образом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/