Share This
Связаться со мной
Крути в низ
Categories
//Python или Rust: что выбрать для анализа данных и машинного обучения

Python или Rust: что выбрать для анализа данных и машинного обучения

Популярность Python в анализе данных и ML уже неоспорима, однако быстрорастущая звезда Rust готова бросить ему вызов.

python ili rust chto vybrat dlja analiza dannyh i mashinnogo obuchenija 380bfe7 - Python или Rust: что выбрать для анализа данных и машинного обучения

Python стал основным языком машинного обучения и анализа данных благодаря своей простоте, гибкости и огромному выбору вспомогательных библиотек. Процесс разработки на Python идет гораздо быстрее, чем на любом другом языке, и хотя Python довольно часто комбинируют с R и Julia, ни тот, ни другой язык не может полностью его заменить. Недавно у Python появился новый конкурент – Rust. Он гораздо сложнее Python, но у него есть два важных преимущества – высокая производительность, сопоставимая с C/C++, и максимально надежный механизм обеспечения безопасности. Теперь каждый ML-разработчик и аналитик данных должен решить для себя дилемму – какой из этих языков выбрать. Попробуем сравнить особенности Python, которые сделали его фактическим стандартом в AI/ML и анализе данных, с вескими преимуществами восходящей звезды Rust.

🐍 Библиотека питониста Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста» 🐍🎓 Библиотека собеса по Python Подтянуть свои знания по Python вы можете на нашем телеграм-канале «Библиотека собеса по Python» 🧩🐍 Библиотека задач по Python Интересные задачи по Python для практики можно найти на нашем телеграм-канале «Библиотека задач по Python»

Наследие Python

Python имеет простой и интуитивно понятный синтаксис, который иногда в шутку называют исполняемым псевдокодом. Эта простота и доступность приглянулись многим талантливым разработчикам: они начали создавать всевозможные дополнительные модули, библиотеки и фреймворки. В результате Python очень быстро обзавелся необъятной экосистемой, в которой есть инструменты для решения любых задач. Не все эти задачи решаются максимально эффективно – из-за невысокой производительности Python не подходит для разработки серьезных 3D-игр, например – но для анализа данных, машинного обучения и многих других вещей скорости языка вполне хватает. К тому же многие критически важные модули и библиотеки Python реализованы на уровне С и работают с соответствующей скоростью. Вот так просто выглядит чтение данных из CSV-файла в Python с помощью Pandas:

         import pandas as pd data = pd.read_csv("mydataset.csv") print(data.head())     

Новый конкурент – Rust

Основные плюсы Rustвысокая производительность, безопасность и многопоточность. Он отлично подходит для системного программирования, стремительно набирает популярность в серверной разработке и геймдеве. Rust – не самый очевидный выбор для анализа данных и машинного обучения. Однако в последние несколько лет, благодаря своим веским преимуществам, он все чаще применяется и в этих областях – хотя использовать его заметно сложнее. Чтение того же самого CSV-файла в Rust выглядит так:

         use std::error::Error; use csv::ReaderBuilder;  fn main() -> Result<(), Box<dyn Error>> {     let file = std::fs::File::open("mydataset.csv")?;     let mut rdr = ReaderBuilder::new().has_headers(true).from_reader(file);      for result in rdr.records() {         let record = result?;         println!("{:?}", record);     }      Ok(()) }     

Python или Rust: что проще

Python известен плавной кривой обучения: с минимальными знаниями языка уже можно писать полезные скрипты, а изучение более сложных концепций отложить на потом. По этой причине Pyhton идеально подходит:

  • Специалистам, которым нужно с помощью программирования решать профессиональные задачи – научные и инженерные.
  • Новичкам, не имеющим никакого опыта в программировании.
         # Пример кода на Python print("Hello, World!")      

Rust, напротив, может показаться слишком сложным для начинающих: в нем есть непростые концепции, которые надо осмыслить сразу, например, системы владения и заимствования:

  • Система владения в Rust означает, что каждый объект имеет только одного владельца, который отвечает за его уничтожение. Это предотвращает многие ошибки, связанные с утечкой памяти или двойным освобождением ресурсов.
  • Система заимствования позволяет использовать один и тот же ресурс в нескольких местах кода без необходимости его копирования. Это может значительно повысить производительность, так как не тратятся ресурсы на создание лишних копий объектов.

Однако эти концепции, среди прочих уникальных особенностей Rust, и делают его таким быстрым, безопасным и надежным. Это как раз тот случай, когда усилия, затраченные на изучение языка, будут многократно компенсированы качеством и производительностью готовых приложений.

         // Пример кода на Rust fn main() {    println!("Hello, World!"); }      

Rust или Python: что быстрее

Сравним производительность Rust и Python на решении одной и той же задачи. Напишем код, который вычисляет первые 35 чисел в последовательности Фибоначчи и выводит время, затраченное на это вычисление.

Вычисление последовательности Фибоначчи на Rust:

         use std::time::Instant;  fn fibonacci(n: u32) -> u32 {   match n {       0 => 0,       1 => 1,       _ => fibonacci(n - 1) + fibonacci(n - 2),   } }  fn main() {   let start_time = Instant::now();    for i in 0..35 {       println!("{}", fibonacci(i));   }    let duration = start_time.elapsed();    println!("Время выполнения: {:.2} секунд", duration.as_secs_f64()); }     

Результат:

         0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 Время выполнения: 0.05 секунд     

Вариант на Python:

         import time  def fibonacci(n):    if n <= 0:        return 0    elif n == 1:        return 1    else:        return fibonacci(n-1) + fibonacci(n-2)  start_time = time.time()  for i in range(35):    print(fibonacci(i))  end_time = time.time()  print(f"Время выполнения : {end_time - start_time} секунд")      

Результат:

         0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 Время выполнения : 7.8505754470825195 секунд       

Очевидно, что Rust справился с задачей быстрее:).

Библиотеки и фреймворки

С экосистемой Python сложно соперничать: библиотеки для математических вычислений, анализа данных и работы с нейронными сетями (Numpy, Pandas, TensorFlow, PyTorch, Scikit-Learn и т. д.) стали стандартом де-факто в индустрии анализа данных, Data Science и ML/AI.

Однако Rust и его экосистема быстро развиваются:

  • В распоряжении разработчиков есть модуль ndarray с аналогичной NumPy функциональностью.
  • Имеется аналог Pandas для анализа данных – Polars.
  • Есть библиотека statrs для статистических расчетов и анализа.
  • Библиотека Tangram используется для машинного обучения и прогнозирования.
  • Linfa, Autograd и SmartCore предоставляют функциональность, сходную с PyTorch и TensorFlow.

Кроме того, PyTorch и TensorFlow тоже можно использовать в Rust, а список DS/ML/AI библиотек, разработанных специально для Rust, регулярно пополняется.

🤖 Библиотека Data scientist’а Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека Data scientist’а» 🤖🎓 Библиотека Data Science для собеса Подтянуть свои знания по DS вы можете на нашем телеграм-канале «Библиотека Data Science для собеса» 🤖🧩 Библиотека задач по Data Science Интересные задачи по DS для практики можно найти на нашем телеграм-канале «Библиотека задач по Data Science»

Безопасность и конкурентость

В отличие от Python, Rust обеспечивает безопасность памяти: программа на Rust не может случайно обратиться к неверному адресу в памяти и вызвать ошибку. Это делает Rust более надежным и безопасным для использования в критических приложениях.

Кроме того, Rust является языком с конкурентной моделью выполнения: несколько операций могут выполняться параллельно без необходимости блокировки или синхронизации. Это позволяет Rust достигать более высокой производительности, чем Python, особенно при работе с большими объемами данных.

Для создания пула потоков и выполнения задач параллельно в Python можно использовать модуль concurrent.futures из стандартной библиотеки:

         import concurrent.futures  def process_data(data_chunk):     return data_chunk * 2  data_chunks = [1, 2, 3, 4, 5]  with concurrent.futures.ThreadPoolExecutor() as executor:     future_to_data = {executor.submit(process_data, data_chunk): data_chunk for data_chunk in data_chunks}     for future in concurrent.futures.as_completed(future_to_data):         data_chunk = future_to_data[future]         try:             data = future.result()         except Exception as exc:             print(f'{data_chunk} исключение: {exc}')         else:             print(f'{data_chunk} обработано: {data}')     

Для параллельных операций в Rust используется библиотека Rayon:

         use rayon::prelude::*;  fn process_data(data_chunk: &mut Vec<i32>) {    *data_chunk = data_chunk.iter().map(|&x| x * 2).collect(); }  fn main() {    let mut data_chunks: Vec<Vec<i32>> = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];     data_chunks.par_iter_mut().for_each(|chunk| {        process_data(chunk);    });     println!("{:?}", data_chunks); }      

Управление памятью при обработке данных

В Python управление памятью абстрагировано, что делает его более простым для разработчиков и специалистов по анализу данных, поскольку они могут сосредоточиться на алгоритмах, а не на тонкостях использования памяти. Однако это может привести к проблемам при работе с большими объемами информации: Python использует автоматическую сборку мусора, которая может быть неэффективной для объемных наборов данных.

В Rust есть возможность управлять памятью на более низком уровне, обеспечивая эффективное использование ресурсов. Это позволяет точно контролировать использование памяти и может быть очень полезно при работе с большими наборами данных. В приведенном ниже примере происходит следующее:

  • large_array создается внутри функции main и становится владельцем Vec. Это означает, что large_array отвечает за освобождение памяти, выделенной для Vec, когда он выходит из области видимости.
  • sum создается как ссылка на результат вызова large_array.iter().sum(). Таким образом, sum не владеет данными, а просто ссылается на них.
  • В конце функции main, когда large_array выходит из области видимости, память, выделенная для Vec, автоматически освобождается. Это гарантируется системой управления памятью Rust, которая автоматически освобождает память, когда владелец выходит из области видимости.
         fn main() {    let mut large_array: Vec<f64> = vec![1.0; 1_000_000];    let sum: f64 = large_array.iter().sum();    println!("Сумма элементов равна {}", sum); }     

Параллелизм и многопоточность

В Python многопоточность и параллелизм можно реализовать помощью модулей threading и multiprocessing, но они имеют свои особенности и ограничения. В частности, из-за Global Interpreter Lock (GIL) в Python, многопоточность не всегда может привести к увеличению производительности при выполнении задач, которые интенсивно нагружают процессор. В то же время модуль multiprocessing позволяет обойти GIL, создавая отдельные процессы, каждый из которых имеет свой собственный интерпретатор Python и свою собственную копию памяти. Приведенный ниже пример демонстрирует многопроцессорность, которая является формой параллелизма:

         import multiprocessing import time  def heavy(data, i, proc):     for index in range(len(data)):         data[index] += 1     print(f"Обработка № {i} ядро {proc}")  def sequential(calc, proc, data):     print(f"Запускаем поток № {proc}")     for i in range(calc):         heavy(data, i, proc)     print(f"{calc} обработок данных завершено. Процессор № {proc}")     def processesed(procs, calc):    # procs - количество ядер    # calc - количество операций на ядро      processes = []    manager = multiprocessing.Manager()    data = manager.list([num for num in range(1, 10)])      # делим вычисления на количество ядер    for proc in range(procs):        p = multiprocessing.Process(target=sequential, args=(calc, proc, data))        processes.append(p)        p.start()   # ждем, пока все ядра завершат свою работу    for p in processes:        p.join()     return data   start = time.time() # узнаем количество ядер у процессора n_proc = multiprocessing.cpu_count() # вычисляем, сколько циклов вычислений будет приходиться # на 1 ядро, чтобы в сумме получилось 80+ calc = 80 // n_proc + 1 data = processesed(n_proc, calc) end = time.time() print(f"Количество ядер в процессоре: {n_proc}n"    f"На каждом ядре произведено обработок данных: {calc}n"    f"Итого {n_proc * calc} обработок за: {end - start}n"    f"Обработанные данные: {data}")      

В Rust есть библиотека Rayon, которая позволяет легко преобразовать последовательные вычисления в параллельные. Rayon гарантирует отсутствие условий гонки данных, что идеально обеспечивает параллельность вычислений:

         use rayon::prelude::*; use num_cpus;  fn main() {   let mut data: Vec<i32> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];    // Определяем количество ядер процессора   let num_cpus = num_cpus::get();    // Вычисляем количество циклов вычислений для каждого ядра   let num_cycles = 80 / num_cpus + 1;    // Выполняем циклы вычислений   for _ in 0..num_cycles {       data.par_iter_mut().for_each(|x| {           *x += 1;       });   }    println!("{:?}", data); }      

Визуализация данных

Python располагает несколькими библиотеками для визуализации данных: Мatplotlib, Seaborn, Bokeh, Plotly, Altair и т.д. Самые популярные из них – Мatplotlib и Seaborn. Эти библиотеки позволяют легко создавать графики и диаграммы, что делает визуализацию данных в Python максимально простой и удобной:

         import matplotlib.pyplot as plt  # Данные data = [1, 2, 3, 4, 5]  # Названия месяцев months = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май']  # Создаем график fig, ax = plt.subplots()  # Выводим данные ax.plot(months, data)  # Устанавливаем названия осей ax.set_xlabel('Месяц') ax.set_ylabel('Данные')  plt.show()      

Результат:

python ili rust chto vybrat dlja analiza dannyh i mashinnogo obuchenija 600071b - Python или Rust: что выбрать для анализа данных и машинного обучения

В Rust для визуализации можно использовать библиотеку plotters:

         use plotters::prelude::*;  fn main() -> Result<(), Box<dyn std::error::Error>> {   let data = vec![1, 2, 3, 4, 5];   let months = vec!["Январь", "Февраль", "Март", "Апрель", "Май"];    let root = BitMapBackend::new("plot.png", (640, 480)).into_drawing_area();   root.fill(&WHITE)?;    let mut chart = ChartBuilder::on(&root)       .caption("Месячная статистика", ("Arial", 50).into_font())       .margin(5)       .x_label_area_size(30)       .y_label_area_size(30)       .build_cartesian_2d(months.iter().cloned().map(|m| m.to_string()).collect::<Vec<String>>(), 0..10)?;    chart.configure_mesh().draw()?;    chart.draw_series(LineSeries::new(       data.iter().enumerate().map(|(i, &v)| (months[i].to_string(), v)),       &BLUE,   ))?;    Ok(()) }      

Интеграция с другими языками программирования

Python может бесшовно интегрироваться с библиотеками C и C++ с помощью Cython. Это дает возможность использовать функции и данные из этих библиотек в Python-коде.

Если у вас есть библиотека my_lib на C:

         // my_lib.c #include <stdlib.h>  int add(int a, int b) {    return a + b; }     

Ее можно скомпилировать:

         gcc -shared -o my_lib.so my_lib.c     

И вызвать в Python:

         from ctypes import CDLL  # Загрузка библиотеки my_lib = CDLL('./my_lib.so')  # Вызов функции add result = my_lib.add(1, 2) print(result) # Выводит: 3     

Rust предлагает возможности для интеграции с библиотеками C через Foreign Function Interface (FFI). FFI позволяет вызывать функции, определенные в другом языке программирования, из кода на Rust. Это достигается путем определения внешней функции в Rust с сигнатурой функции, совместимой с C, и затем динамического связывания с общей библиотекой, содержащей реализацию функции:

         // Пример использования FFI для вызова функции C из Rust extern "C" {    fn my_c_function(arg1: i32, arg2: f64) -> f64; }  fn main() {    let result = unsafe { my_c_function(42, 3.14) };    println!("Result: {}", result); }       

Стоит заметить, в Python-проектах можно использовать модули, написанные на Rust, и наоборот, в Rust можно вызывать Python. Проще всего это сделать с помощью фреймворка PyO3, который позволяет создавать нативные модули Python на Rust. PyO3 обеспечивает простоту и удобство в создании привязок и интеграции кода Rust и Python. Вот простейший пример создания Python-модуля на Rust:

         // Rust код, использующий PyO3 для создания модуля Python use pyo3::prelude::*; use pyo3::wrap_pyfunction;  #[pyfunction] fn process(data: Vec<i32>) -> Vec<i32> {    data.iter().map(|x| x * 2).collect() }  #[pymodule] fn rust_module(py: Python, m: &PyModule) -> PyResult<()> {    m.add_function(wrap_pyfunction!(process, m)?)?;    Ok(()) }     

Скомпилированный модуль в коде Python можно использовать так:

         import rust_module  data = [1, 2, 3, 4, 5] result = rust_module.process(data) print(result) # Выводит: [2, 4, 6, 8, 10]     

Подведем итоги

Выбор между Python и Rust для анализа данных и машинного обучения – сложная дилемма, поскольку оба языка предлагают уникальные преимущества:

  • Python обладает максимально простым, понятным и гибким синтаксисом, располагает обширной экосистемой библиотек и фреймворков для машинного обучения и работы с данными. Это делает его отличным выбором для разработчиков, которые только начинают изучать анализ данных и машинное обучение.
  • С другой стороны, Rust обеспечивает максимальную производительность, безопасность, эффективную поддержку многопоточности и параллелизма. Rust отлично подходит для продвинутых разработчиков, создающих ПО для работы с большими наборами данных и выполнения сложных вычислительных задач.

В конечном итоге выбор между Python и Rust зависит от ваших конкретных потребностей и уровня опыта. Если вы только начинаете изучать анализ данных и машинное обучение, то лучше выбрать Python. Если у вас уже есть опыт работы с другими языками программирования, а ваш проект нуждается в более высокой производительности и безопасности, то Rust будет самым подходящим вариантом.

***

Материалы по теме

  • 🤖 Машинное обучение: что это такое и как оно работает
  • 🤖 Создание собственного ИИ-бота на Python за 33 строчки кода

  • 0 views
  • 0 Comment

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Связаться со мной
Close