March 2016

Volume 31 Number 3

Python - Введение в программирование с помощью SciPy для разработчиков на C#

By Джеймс Маккафри | Март 2016

Продукты и технологии:
C#, Python, SciPy, IDLE
В статье рассматриваются:

  • установка стека SciPy;
  • установка NumPy и SciPy;
  • редактирование и выполнение SciPy-программы;
  • Демонстрационная программа для SciPy.

Исходный код можно скачать по ссылке.

Формального определения термина науки о данных (data science) не существует, но я рассматриваю его как применение ПО для анализа данных с использованием классических метод статистики и алгоритмов машинного обучения. До недавнего времени большая часть анализа данных выполнялась с помощью дорогостоящих коммерческих продуктов, но в последние несколько лет значительно увеличилось использование альтернативного ПО с открытым исходным кодом.

По итогам бесед со своими коллегами, могу сказать, что имеется три наиболее распространенных подхода к анализу в науке о данных с применением ПО с открытым исходным кодом: язык R, язык Python в комбинации с библиотекой SciPy («scientific Python») и интегрированные языковые и исполняющие среды вроде SciLab и Octave.

В этой статье я дам краткий обзор программирования с помощью SciPy, чтобы вы точно понимали, что это такое, и могли определить, надо ли вам тратить время на ее изучение. В статье предполагается, что у вас есть некоторый опыт программирования на C# или похожем универсальном языке программирования наподобие Java, но знания Python или SciPy не требуется.

По моему мнению, самая трудная часть в освоении нового языка программирования или технологии — начало работы с ним, поэтому я подробно опишу, как установить (и удалить) ПО, необходимое для выполнения SciPy-программы. Затем я расскажу о нескольких способах редактирования и запуска SciPy-программы и поясню, почему я предпочитаю использовать программу Integrated Development Environment (IDLE).

В заключение мы подробно разберем типичную программу, которая использует SciPy для решения системы линейных уравнений, чтобы продемонстрировать схожесть с программированием на C# и отличия от него. На рис. 1 показан вывод демонстрационной программы — он даст вам представление, о чем пойдет речь в этой статье.

Вывод типичной SciPy-программы

Рис. 1. Вывод типичной SciPy-программы

Установка стека SciPy

Стек SciPy состоит из трех компонентов: Python, NumPy и SciPy. Язык Python имеет базовые средства, такие как управляющие структуры наподобие цикла while, универсальный тип данных list, но, что интересно, отсутствует встроенный тип «массив». Библиотека NumPy добавляет поддержку массивов и матриц плюс некоторые сравнительно простые функции, например для поиска в массиве и его сортировки. Библиотека SciPy добавляет сложные функции, работающие с данными, которые хранятся в массивах и матрицах.

Чтобы выполнить SciPy-программу (с технической точки зрения, это скрипт, поскольку Python является интерпретируемым, а не компилируемым языком), нужно установить Python, затем NumPy, потом SciPy. Установка не особо сложна, и вы можете установить программный пакет, включающий все три компонента. Один из популярных пакетов — дистрибутив Anaconda, который поддерживается Continuum Analytics на сайте continuum.io. Однако я покажу, как установить эти компоненты индивидуально.

Python поддерживается почти всеми версиями Windows. Чтобы установить Python, отправляйтесь по ссылке python.org/downloads, где вам предложат на выбор Python либо версии 3.x, либо версии 2.x (рис. 2). Эти две версии не полностью совместимы, но библиотеки NumPy и SciPy поддерживают обеими версиями. Я предлагаю установить версию 2.x, поскольку существуют некоторые сторонние функции, пока не поддерживаемые в версии 3.x.

Установка Python

Рис. 2. УстановкаPython

Щелкнув кнопку Download, вы должны выбрать между немедленным запуском пакета .msi программы установки и его сохранением для последующего запуска. Можете щелкнуть кнопку Run. Установщик использует мастер. На первом экране будет запрос, хотите ли вы установить пакет для всех пользователей или только для текущего пользователя. По умолчанию установка происходит для всех пользователей, так что щелкните Next.

На следующем экране вас попросят указать корневой каталог установки. По умолчанию — это C:\Python27 (а не более привычный каталог C:\Program Files), и я предлагаю использовать вариант по умолчанию, щелкнув Next. Следующий экран позволяет включать или исключать различные компоненты вроде документации и утилит, таких как pip (аббревиатура «pip installs Python»). Компоненты Python по умолчанию отлично подходят, так что вновь щелкните Next.

Начнется установка, и вы увидите окно с привычной синей полоской прогресса. По окончании установки появится окно с кнопкой Finish — щелкните ее.

По умолчанию процесс установки Python не модифицирует переменную окружения PATH. Вы захотите добавить в эту переменную C:\Python27, C:\Python27\Scripts и C:\Python27\Lib\idlelib, чтобы у вас была возможность запускать Python из командной оболочки и использовать редактор IDLE без необходимости перехода в соответствующие каталоги.

Вы должны удостовериться, что Python установлен правильно. Запустите командную оболочку и перейдите в корневой каталог своей системы, введя команду cd \. Теперь введите команду python -- version (обратите внимание на два символа-дефиса). Если Python отвечает, он установлен правильно.

Установка NumPy и SciPy

Пакеты NumPy и SciPy можно установить из исходного кода с помощью утилиты pip в Python. Подход с pip хорошо работает для пакетов, содержащих чистый код на Python, но NumPy и SciPy имеют точки подключения (hooks) к скомпилированному коду на языке C, поэтому установить их через pip не так-то просто.

К счастью, члены сообщества Python создали заранее скомпилированные в двоичный код установщики для NumPy и SciPy. Я рекомендую задействовать тот, который поддерживается в репозитарии SourceForge. Чтобы установить NumPy, перейдите по ссылке bit.ly/1Q3mo4M, где вы увидите ссылки на разные версии. Советую использовать самую свежую версию, которая скачивается чаще всего.

Вы увидите список ссылок. Найдите ссылку с именем, напоминающим numpy-1.10.2-win32-superpack-python2.7.exe, как показано на рис. 3. Убедитесь, что исполняемый файл соответствует вашей версии Python, и щелкните эту ссылку. После короткой паузы вам будет предложено сразу же запустить самораспаковывающийся исполняемый файл установщика или сохранить его для установки впоследствии. Щелкните кнопку Run.

Установка NumPy

Рис. 3. УстановкаNumPy

Установщик NumPy использует мастер. Первый экран просто отображает вводную заставку. Щелкните кнопку Next. На следующем экране вас попросят указать каталог установки. Установщик найдет существующий каталог установки Python и порекомендует установить  NumPy в каталог C:\Python27\Lib\site-packages. Согласитесь и щелкните кнопку Next.

Следующий экран дает вам последний шанс выйти из процесса установки, но не делайте этого. Щелкните кнопкуNext. При установке появится окно с индикатором прогресса, и, если вы присмотритесь, вы заметите некоторые интересные сообщения, записываемые в журнал. При завершении установки NumPy станет доступна кнопка Finish — щелкните ее. Затем вы увидите последнее окно Setup Completed с кнопкой Close, которую и следует нажать.

Одна из приятных особенностей стека SciPy состоит в том, что он позволяет очень легко удалять компоненты.

После установки NumPy следующий шаг — установка пакета SciPy, и этот процесс идентичен таковому для NumPy. Перейдите по bit.ly/1QbwJ0z и найдите самый недавний, хорошо отработанный каталог. Перейдите в него и найдите ссылку на исполняемый файл с названием, похожим на scipy-0.16.1-win32-­superpack-python2.7.exe, и щелкните его для запуска самораспаковывающегося установщика.

Одна из приятных особенностей стека SciPy состоит в том, что он позволяет очень легко удалять компоненты. Вы можете перейти в Windows Control Panel, Programs and Features, выбрать компонент (т. е. Python, NumPy или SciPy) для удаления, щелкнуть кнопку Uninstall, и компонент будет быстро и корректно удален.

Редактирование и запуск SciPy-программы

Если вы пишете программы, используя какой-либо .NET-язык, у вас не так много вариантов, и вы почти наверняка работаете в Visual Studio. Но при написании программы на Python вариантов уйма. Советую использовать редактор и исполняющую среду IDLE.

Файл запуска программы idle.bat по умолчанию находится в каталоге C:\Python27\Lib\idelib. Если вы добавили этот каталог в свою системную переменную окружения PATH, то можете запускать IDLE открытием командной оболочки и вводом команды idle. Это приведет к старту программы IDLE Python Shell, как показано в верхней части рис. 4.

Редактирование и запуск программы с использованием IDLE

Рис. 4. Редактирование и запуск программы с использованием IDLE

Вы можете создать новый файл исходного кода на Python, выбрав File | New File в строке меню. Появится похожее на предыдущее отдельное окно редактора, как показано в нижней части рис. 4. Наберите в окне редактора следующие семь выражений:

# test.py
import numpy as np
import scipy.misc as sm
arr = np.array([1.0, 3.0, 5.0])
print arr
n = sm.factorial(4)
print "4! = " + str(n)

После этого сохраните свою программу как test.py в любом удобном для вас каталоге. Теперь вы можете запустить ее, выбрав Run | Run Module в окне редактора или нажав клавишу F5. Вывод программы будет отображаться в окне Python Shell. Элементарно!

Некоторые опытные разработчики на Python критикуют IDLE за ее простоту. Но именно этим она мне и нравится. Ее возможности далеки от изощренной среды программирования Visual Studio, но она обеспечивает раскраску синтаксиса и имеет хороший генератор сообщений об ошибках, реагирующий на написание неправильного кода.

Вместо применения IDLE для редактирования и запуска программ можно использовать любой текстовый редактор, включая Notepad. В этом случае программа запускается из командной строки:

C:\IntroToPython> python test.py

Это предполагает наличие пути к интерпретатору python.exe в вашей системной переменной окружения PATH. Вывод показывается в командной оболочке.

Существует много Python IDE. Одна из популярных IDE с открытым исходным кодом, которая специально предназначена для использования со SciPy, — программа Scientific Python Development Environment (Spyder). Вы найдете информацию о ней на pythonhosted.org/spyder.

Интересная альтернатива IDLE иSpyder — плагин Python Tools for Visual Studio (PTVS) с открытым исходным кодом. Как и предполагает его название, PTVS позволяет редактировать и выполнять программы Python с помощью Visual Studio. Информацию о PTVS см. на microsoft.github.io/PTVS.

Демонстрационная программа для SciPy

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

Рис. 5. Типичная SciPy-программа

# linear_systems.py
# Python 2.7
import numpy as np
import scipy.linalg as spla
def my_print(arr, cols, dec, nl):
  n = len(arr)
  fmt = "%." + str(dec) + "f" # like %.4f
  for i in xrange(n):  # alt: for x in arr
    if i > 0 and i % cols == 0:
      print ""
    print fmt % arr[i],
  if nl == True:
    print "\n"
def main():
  print "\nBegin linear system using SciPy demo \n"
  print "Goal is to solve the system: \n"
  print "3x0 + 4x1 - 8x2 = 9"
  print "2x0 - 5x1 + 6x2 = 7"
  print " x0 + 9x1 - 7x2 = 3"
  print ""
  A = np.matrix([[3.0, 4.0, -8.0],
                 [2.0, -5.0, 6.0],
                 [1.0, 9.0, -7.0]])
  b = np.array([9.0, 7.0, 3.0])      # b is an array
  b = np.reshape(b, (3,1))           # b is a col vector
  print "Matrix A is "
  print A
  print ""
  print "Array b is "
  my_print(b, b.size, 2, True)
  d = spla.det(A)
  if d == 0.0:
    print "Determinant of A is zero so no solution "
  else:
    Ai = spla.inv(A)
    print "Determinant of A is non-zero "
    print "Inverse of A is "
    print Ai
    print ""
  Aib = np.dot(Ai, b)
  print "A inverse times b is "
  print Aib
  print ""
  x = spla.solve(A, b)
  print "Using x = linalg.solve(A,b) gives x = "
  print x
  print ""
  try:
    A = np.array([[2.0, 4.0], [3.0, 6.0]])
    print "Matrix A is "
    print A
    print ""
    print "Inverse of A is "
    Ai = spla.inv(A)
    print Ai
  except Exception, e:
    print "Fatal error: " + str(e)
  print "\nEnd SciPy demo \n"
if __name__ == "__main__":
  main()

Демонстрационная программа начинается с двух строк комментариев:

# linear_systems.py
# Python 2.7

Поскольку версии Python 2.x и 3.x не полностью совместимы, всегда неплохо явно указывать, какую версию Python вы используете. Далее демонстрация загружает весь модуль NumPy и один подмодуль SciPy:

import numpy as np
import scipy.linalg as spla

Вы можете считать эти выражения чем-то похожим на то, как в программу на C# добавляется ссылка на Microsoft .NET Framework DLL, а затем эта сборка вводится в область видимости выражением using. Название подмодуля linalg расшифровывается как «linear algebra» (линейная алгебра). SciPy организован в 16 основных подмодулей и два вспомогательных.

Затем демонстрируется реализация определенной в программе функции для отображения массива:

def my_print(arr, cols, dec, nl):
  n = len(arr)
  fmt = "%." + str(dec) + "f" # like %.4f
  for i in xrange(n):  # alt: for x in arr
    if i > 0 and i % cols == 0:
      print ""
    print fmt % arr[i],
  if nl == True:
    print "\n"

В Python блоки кода отделяются отступами, а не фигурными скобками. Здесь я использую два пробела в качестве отступа для экономии места; большинство программистов на Python применяет по четыре пробела в качестве отступа.

Функция my_print имеет четыре параметра: массив для отображения, количество столбцов для вывода значений, число десятичных разрядов для каждого значения и флаг, указывающий, надо ли перейти на новую строку. Функция len возвращает размер массива (число ячеек в нем). Альтернатива — задействовать свойство «размер массива»:

n = arr.size

Функция xrange возвращает итератор, и это стандартный способ перебора массива. Альтернатива — использовать шаблон «for x in arr», похожий на выражение foreach в C#.

При написании программы на Python вариантов уйма. Советую использовать редактор и исполняющую среду IDLE.

Поскольку корни как Python, так и C# уходят в язык C, большая часть синтаксиса Python знакома программистам на C#. В демонстрации % — это операция по модулю, но также используется для форматирования вывода значений с плавающей точкой и как логический оператор вместо &&; == — это проверка на равенство, а True и False (именно в таком написании) являются булевыми константами.

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

def main():
  print "\nBegin linear system using SciPy demo \n"
  print "Goal is to solve the system: \n"
  print "3x0 + 4x1 - 8x2 = 9"
  print "2x0 - 5x1 + 6x2 = 7"
  print " x0 + 9x1 - 7x2 = 3"

Цель — найти значения переменных x0, x1 и x2, которые удовлетворяли бы всем трем уравнениям. Слово «main» не является ключевым в Python, так что его можно было бы использовать как угодно. Наличие некоей функции main не требуется. В коротких программах (обычно менее одной страницы кода) я, как правило, обхожусь без функции main и просто начинаю с исполняемых выражений.

Далее демонстрационная программа подготавливает задачу, помещая значения коэффициентов в NumPy-матрицу 3×3 с именем A и константы в NumPy-массив с именем b:

A = np.matrix([[3.0, 4.0, -8.0],
               [2.0, -5.0, 6.0],
               [1.0, 9.0, -7.0]])
b = np.array([9.0, 7.0, 3.0])

Здесь функции matrix и array на самом деле принимают в качестве аргументов списки Python (обозначаемые квадратными скобками) с «зашитыми» в код значениями. Кроме того, вы можете создавать матрицы и массивы, используя NumPy-функцию zeros, и считывать данные из текстового файла в массив или матрицу с помощью функции loadtxt.

Если вы изучали курс алгебры, то, вероятно, помните, что для решения системы уравнений Ax = b для x, где A — квадратная матрица коэффициентов и b — матрица-столбец констант (т. е. имеет n строк и только один столбец), нужно найти матрицу, обратную A, а затем выполнить матричное перемножение обратной матрицы и матрицы-столбца b.

К этому моменту в демонстрации b является массивом с тремя ячейками, а не матрицей-столбцом 3×1. Чтобы преобразовать b в матрицу-столбец, программа использует функцию reshape:

b = np.reshape(b, (3,1))

В библиотеке NumPy много функций, способных манипулировать массивами и матрицами. Например, функция flatten преобразует матрицу в массив. Теперь, как оказалось, SciPy-функция перемножения матриц достаточно интеллектуальна, чтобы логически определить ваши намерения, если вы перемножаете матрицу и массив, поэтому вызов reshape на самом деле здесь не требуется.

Идем дальше. Демонстрационная программа отображает значения в матрицах A и b:

print "Matrix A is "
print A
print ""
print "Array b is "
my_print(b, b.size, 2, True)

В Python 2.x print — это выражение, а не функция, как в Python 3.x, поэтому скобки не обязательны. Моя функция my_print не возвращает никакого значения, а значит, она эквивалентна void-функции из C# и вызывается так, как вы и ожидали. Python поддерживает вызовы с именованными параметрами, поэтому данный вызов мог бы выглядеть так:

my_print(arr=b, cols=3, dec=2, nl=True)

Демонстрационная программа находит матрицу, обратную для A:

d = spla.det(A)
if d == 0.0:
  print "Determinant of A is zero so no solution "
else:
  Ai = spla.inv(A)
  print "Determinant of A is non-zero "
  print "Inverse of A is "
  print Ai

SciPy-функция det возвращает детерминанту квадратной матрицы. Если матрица коэффициентов для системы линейных уравнений имеет детерминанту, равную нулю, эту матрицу нельзя обратить. Выражение if-else в Python должно быть знакомо вам. В Python есть изящное ключевое слово elif для управляющих конструкций if-else-if, например:

if n < 0:
  print "n is negative"
elif n == 0:
  print "n equals zero"
else:
  print "n is positive"

Некоторые опытные разработчики на Python критикуют IDLE за ее простоту. Но именно этим она мне и нравится.

Демонстрационная программа решает систему уравнений, используя перемножение матриц через NumPy-функцию dot:

Aib = np.dot(Ai, b)
print "A inverse times b is "
print Aib

Функция dot имеет такое название потому, что перемножение матриц является формой скалярного умножения (dot product).

После этого программа напрямую решает систему уравнений с помощью NumPy-функцииsolve:

x = spla.solve(A, b)
print "Using x = linalg.solve(A,b) gives x = "
print x

Многие функции SciPy и NumPy имеют необязательные параметры со значениями по умолчанию, что в какой-то мере эквивалентно перегрузке C#-метода. У SciPy-функции solve пять необязательных параметров. Смысл в том, что, когда вы видите пример вызова SciPy- или NumPy-функции и даже, как вам кажется, понимаете пример, всегда лучше заглянуть в документацию, чтобы выяснить, есть ли у нее какие-либо полезные необязательные параметры.

Библиотеки NumPy и SciPy отчасти перекрывают друг друга. Например, в пакете NumPy также есть подмодуль linalg, в котором имеется функция solve. Однако NumPy-функция solve не имеет никаких необязательных параметров.

Далее демонстрационная программа показывает пример механизма try-except в Python:

try:
  A = np.array([[2.0, 4.0], [3.0, 6.0]])
  Ai = spla.inv(A)
  print Ai
except Exception, e:
  print "Fatal error: " + str(e)

Этот шаблон должен выглядеть знакомым, если вы когда-нибудь использовали C#-выражения try-catch. В C# конкатенация строк может выполняться неявным образом. Например, в C# вы могли бы написать:

int n = 99;
Console.WriteLine("The value of n is " + n);

Но при конкатенации строк в Python вы должны делать это явным образом с приведением, используя функцию str:

n = 99
print "The value of n is " + str(n)

Демонстрационная программа завершается выражением print и особым заклинанием в Python:

print "\nEnd SciPy demo \n"
if __name__ == "__main__":
  main()

В Python блоки кода отделяются отступами, а не фигурными скобками.

Последним выражением демонстрационной программы могло бы быть просто main(), что было бы интерпретировано как инструкция вызвать определенную в программе функцию main, и программа работала бы совершенно нормально. Добавление шаблона if __name__ == "__main__" (обратите внимание на два знака подчеркивания до и после как name, так и main) устанавливает текущий модель в качестве точки входа в программу. Когда программа на Python начинает выполнение, интерпретатор на внутреннем уровне помечает начальный модуль как:

"__main__"

Поэтому, допустим, что у вас есть какие-то другие определенные в программе модули с исполняемыми выражениями и что вы импортировали их. Без if-проверки интерпретатор Python увидел бы исполняемые выражения в импортированных модулях и выполнил бы их. Немного перефразируя, если вы добавляете if-проверку в свои Python-файлы, определенные в программе, эти файлы могут импортироваться другими программами на Python и это не вызовет никаких проблем.

Итак, к чему все это?

Вашей первой реакцией на эту статью вполне может быть следующее: «Что ж, все это даже интересно, но в своей повседневной работе мне не требуется решать системы линейных уравнений или использовать заумные математические функции». На это я ответил бы так: «Что ж, это правда, но, возможно, одна из причин, по которой вы не пользуетесь какой-то частью функциональности библиотеки SciPy, заключается в том, что не осознаете, какие типы задач вы можете решать».

Иначе говоря, по моему мнению, среди разработчиков прослеживается тенденция браться в основном за те задачи, для решения которых у них есть инструменты. Например, если вы знаете Windows Communication Foundation (WCF), то будете использовать WCF (и я вам сочувствую). А если вы добавите SciPy в свой личный арсенал, то, возможно, обнаружите, что у вас есть данные, которые можно превратить в полезную информацию.


Джеймс Маккафри*(Dr. James McCaffrey) работает на Microsoft Research в Редмонде (штат Вашингтон). Принимал участие в создании нескольких продуктов Microsoft, в том числе Internet Explorer и Bing. С ним можно связаться по адресу jammc@microsoft.com.*

Выражаю благодарность за рецензирование статьи экспертам Microsoft Дэну Либлингу (Dan Liebling) и Кирку Олинику (Kirk Olynyk).