Урок 1 - основы python для машинного обучения¶

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

In [ ]:
 

Введение в Python 🐍¶

Переменные и простые операции над ними¶

Основой любого процесса на компьютере служит хранение и обработка какой-либо информации. Это лежит в основе всех алгоритмов и программ, в том числе и алгоритмов машинного обучения.

Для этого созданы переменные - ссылки на "ячейки памяти" компьютера, в которые можно записывать какую-либо информацию. Давайте создадим перменную, назовём её "a" и присвоим ей значение, равное 1.

In [ ]:
a = 1

Теперь выведем переменную на экран. Для этого воспользуемся функцией print. Функции в программировании - некоторая процедура, описывающая действия, которые нужно произвести с передаваемыми ей данными (аргументами).

In [ ]:
print(a)
1

Функция print - встроенная, т.е. она уже реализована в языке python. Позже мы познакомимся с тем, как самим создавать свои функции и не только.

Мы можем создавать любое количество переменных и называть их как угодно. Главное, чтобы они начинались не с цифры (обязательно) и содержали в названии только латинские буквы (желательно), цифры и символ _.

In [ ]:
b = 3
c = 5

С переменными, хранящими числа (позже мы выясним, что переменные могут хранить не только числа), мы можем делать любые стандартные математические операции:

In [ ]:
d = b + c
print(d)

print(b - c)
print(a * b)
print(a / c)
print(c // a) # деление нацело
print((c * 5) % b) # % - остаток от деления
print(b ** c) # возведение в степень
8
-2
3
0.2
5
1
243

Собственно, переменные могут хранить далеко не только числа, а любые типы данных, реализованных в питоне или созданных в последующем нами. Ещё один пример простого типа данных, уже реализованного в питоне (не во всех языках он есть изначально) - строки.

In [ ]:
a = "hello world"
print(a)
hello world

Важно обратить внимание, что мы можем осуществлять некоторые общепринятые арифметические операции над строками:

In [ ]:
a = "hello"
b = "world"
print(a + b)
print(a + " " + b)
print(a * 2)
helloworld
hello world
hellohello

А вот некоторые операции, например умножение строки на строку, осуществить не получится

In [ ]:
print(a * b) # выдаст ошибку
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-ad00c5ebab7d> in <cell line: 1>()
----> 1 print(a * b) # выдаст ошибку

TypeError: can't multiply sequence by non-int of type 'str'

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


Консольный ввод данных¶

Взаимодействовать с переменными, значение которых мы не можем задать или изменить при необходимости пользователя, довольно бессмысленно. Поэтому изучим самый простой способ ввода переменных - консольный ввод с клавиатуры. Для этого используется встроенная функция input

In [ ]:
a = input()
print("Введенное значение:", a) # через запятую мы можем перечислить несколько объектов и они выведутся через пробел
hello
Введенное значение: hello

Как вы думаете, почему код ниже не складывает числа, а "склеивает" их?

In [ ]:
a = input()
b = input()
print(a + b)
5
6
56

На примере выше легко догадаться, что стандартный ввод считывает значения в строковом формате, независимо от того, цифры это или строки. Для того, чтобы получить "арифметическое" число из строки-числа, можем явно конвертировать его с помощью int.

In [ ]:
a = input()
a = int(a)
print(a * 2)
5
10
In [ ]:
# или можно проще:
a = int(input(a))
print(a * 2)
1010
20

Практическая задача¶

Напишите программу, которая принимает на вход три целых числа - стороны треугольника, и считает его площадь. Гарантируется, что три введенных числа гарантировнно образуют треугольник. Площадь треугольника через три стороны вычисляется по формуле Геррона:

$\sqrt{p(p-a)(p-b)(p-c)}$, где $p=\frac{a+b+c}{2}$.

In [ ]:
# ваш код здесь

Логические условия¶

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

Посмотрим, как это реализуется на python.

In [ ]:
a = int(input())
b = int(input())

if a > b * 2: # если a больше b в два раза
  print(a * 2) # то выводим a * 2
else: # иначе
  print(b // 2) # выводим b // 2
10
2
20

Это самый простой пример использования конструкции if-else. Также мы можем использовать if без else:

In [ ]:
a = int(input())
b = int(input())

if a > b * 2: # если a больше b в два раза
  print(a * 2) # то выводим a * 2

print(b + a)
10
5
15

Также в языке python существует довольно уникальная относительно других языков конструкция - оператор elif (иначе-если).

In [ ]:
a = int(input())
b = int(input())

if a > b * 2:
  print(a * 2)
elif a > b * 1.5:
  print(a * 1.5 + b)
else:
  print(b + a)
4
10
14

Мы можем использовать сколько угодно elif, но перед ним обязательно должен быть минимум один if.

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

Примеры логических операций:

a > b # больше
a < b # меньше
a >= b # больше или равно
a <= b # меньше или равно
a == b # в точности равно
a != b # не равно
a is int # такого-же типа данных

# Нетривиальные примеры:
a % 2 == 0


Праткическая задача¶

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

високосными годами считаются те годы, порядковый номер которых либо кратен 4, но при этом не кратен 100, либо кратен 400 (например, 2000-й год являлся високосным, а 2100-й будет невисокосным годом).

In [ ]:
# ваш код здесь

Циклы¶

В большинстве программ количество повторений выполнения того или иного участка кода зависит от входных параметров или изначально неизвестна. Для этого в языках программирования реализованы циклы.

Хоть традиционно первый для изучения циклом является цикл while, в рамках ограниченного времени интенсива мы рассмотрим только один, значительно более часто использующийся цикл, for.

In [ ]:
n = int(input())

for i in range(n):
  print(i)
6
0
1
2
3
4
5

Цикл for в связке с range позволяет итерироваться по заданному диапазону значений.

внутри цикла мы также можем использовать логические ветвления и другие циклы

In [ ]:
start_row = int (input())
end_row = int (input())
start_col = int (input())
end_col = int (input())

for g in range (start_col, end_col+1):
    print('\t', g, end='')

print(end='\n')
for i in range (start_row, end_row+1):
    print(i, '\t', end='')
    for j in range (start_col, end_col+1):
        print(i * j, end='\t')
    print(end='\n')
7
10
5
9
	 5	 6	 7	 8	 9
7 	35	42	49	56	63	
8 	40	48	56	64	72	
9 	45	54	63	72	81	
10 	50	60	70	80	90	

Списки¶

Все серьёзные задачи так или иначе требует хранения и обработки некоторого массива данных. В связи с парадигмами, на которых основан язык python, стандартные статические массивы с одним типом данных заменены на списки - динамические структуры, способные хранить в себе произвольные типы данных.

Пример создания списка:

In [ ]:
lst = [1, 2, 4, 5]

Индексирование списков (можно провести аналогию с range) начинается с нуля.

In [ ]:
# получим нулевой элемент списка
print(lst[0])

# получим третий элемент списка
print(lst[2])
1
4
In [ ]:
# получим последний элемент списка
print(lst[-1])
5
In [ ]:
# получим срез списка с первого до третьего элемента (включительно)
sl = lst[1:4]
print(sl)
[2, 4, 5]
In [ ]:
# или со второго до конца
In [ ]:
print(lst[2:])
[4, 5]
In [ ]:
# с помощью функции len мы можем узнать размер нашего списка

print(len(lst))
4
In [ ]:
# мы можем перебирать элементы списка

for el in lst:
  print(el)
3
6
12
15
In [ ]:
# список динамический, поэтому мы можем перебирать его элементы и изменять их

for i in range(len(lst)):
    lst[i] *= 3

print(lst)
[3, 6, 12, 15]

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

lst.append(1) # добавляет элемент в конец списка
lst.extend([3, 5]) # расширяет список другим списком, добавляя в конец все элементы
del lst[idx] # удаляет элемент списка по индексу
lst.remove(3) # удаляет первый попавшийся заданный элемент из списка
lst.index(5) # возвращает индекс элемента, если он есть в списке

Функции split и join¶

В питоне есть много очень удобных функций, сильно упрощающий процесс рвботы. Продемонстрируем несколько:

In [ ]:
string = "1 3 4 5 10 45"
lst = string.split()

print(lst) # важно заметить, что все элементы являются строками
['1', '3', '4', '5', '10', '45']
In [ ]:
# мы можем сплитовать не только по пробелу

string = "1 3 4 5 10 45"
lst = string.split("4")

print(lst)
['1 3 ', ' 5 10 ', '5']
In [ ]:
lst = ["1", "2", "4", "5", "10"]
string = "\t".join(lst)

print(string)
1	2	4	5	10
Практическая задача¶

Напишите программу, на вход которой подаётся список чисел одной строкой. Программа должна для каждого элемента этого списка вывести сумму двух его соседей. Для элементов списка, являющихся крайними, одним из соседей считается элемент, находящий на противоположном конце этого списка.

In [ ]:
# ваш код здесь

Функции¶

Мы можем создавать собственные функци и затем применять их в других участках программы, тем самым декомпонируя код:

In [ ]:
# пример простой функции

def f(n):
    return n * 10 + 5

print(f(2))
25

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

С более наглядной и подробной реализацией функций мы ознакомимся в следующих занятиях.

In [ ]:
 

Основы библиотек, необходимых для машинного обучения¶

Библиотеки, по своей сути, представляют из себя уже разработанные и реализованные другими разработчиками функции и классы, выполняющие конкретные задачи. Библиотеки могут быть как простые, как, например, встроенная в python библиотека math, так и сложны и масштабные, такие как pandas и numpy. В современной разработке, знание и владение популярными библиотеками и технологиями является ключевым навыком для решения задач, на который смотрят чуть ли не в первую очередь при трудоустройстве в любую компанию.

Numpy¶

Одной из самых ключевых библиотек в языке python, используемых для машинного обучения, статистики, больших данных и мат. моделировании является библиотека numpy (numerical python). Numpy реализует в себе функционал массивов, а также позволяет эффективно реализовывать математические операции над векторами, матрицами и просто последовательностями чисел, что лежит в основе любого анализа данных и построения алгоритма машинного обучения.

Создадим numpy array - массив, основной тип данных в нумпае.

In [ ]:
import numpy as np

# Создание массива из списка
a = np.array([1, 2, 3, 4, 5])
print(a)
print("---------")

# Создание двумерного массива из списка списков
b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(b)
print("---------")

# Создание массива из диапазона значений
c = np.arange(0, 10, 2)  # Создаем массив из значений от 0 до 10 с шагом 2
print(c)
print("---------")
[1 2 3 4 5]
---------
[[1 2 3]
 [4 5 6]
 [7 8 9]]
---------
[0 2 4 6 8]
---------
In [ ]:
d = np.linspace(0, 1, 5)  # Создаем массив из 5 равномерно распределенных значений от 0 до 1
print(d)
print("---------")

# Создание массива из случайных значений
e = np.random.rand(3, 3)  # Создаем массив из 3x3 случайных значений
print(e)
print("---------")

# Создание массива из нулей
f = np.zeros((2, 3))  # Создаем массив из нулей размером 2x3
print(f)
print("---------")

# Создание массива из единиц
g = np.ones((3, 3))   # Создаем массив из единиц размером 3x3
print(g)
print("---------")
[0.   0.25 0.5  0.75 1.  ]
---------
[[0.79951566 0.38279667 0.19417234]
 [0.81969364 0.73066775 0.43193641]
 [0.5943596  0.64725553 0.69801765]]
---------
[[0. 0. 0.]
 [0. 0. 0.]]
---------
[[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]
---------

Продемонстрируем базовые операции на примере двумерного массива:

In [ ]:
import numpy as np

# Создаем двумерный массив
arr = np.array([[1, 2], [3, 4], [5, 6]])

# Выводим массив на экран
print(arr)

# Получаем размерность массива
print("размерность массива:", arr.shape)

# Получаем элементы массива
print(arr[0, 1])
print(arr[2, 0])
[[1 2]
 [3 4]
 [5 6]]
размерность массива: (3, 2)
2
5

Сразу обратим внимание на удобство в индексации массива: для получения конкретного элемента на пересечении строчки и столбца нам достаточно указать через запятую индекс-строку и индекс-столбец. Аналогично мы можем брать срезы по строкам и столбцам.

Тажке мы без проблем можем изменять элементы массива.

In [ ]:
arr[0, 1] = 7
arr[1, 1] = 8
arr[2, 1] = 9
print(arr)
print("-------")

# Выводим строку
print(arr[1])
print("-------")

# Выводим столбец
print(arr[:, 0])
print("-------")

# Выводим срез
print(arr[:2, 1])
[[1 7]
 [3 8]
 [5 9]]
-------
[3 8]
-------
[1 3 5]
-------
[7 8]

Но самой главной функцией numpy является возможность быстро производить математические операции с массивами, как из области линейной алгебры, так и статистики. Ниже переведены основные примеры:

In [ ]:
a = np.random.randint(1, 10, size=(6, 6))
b = np.random.randint(1, 10, size=(6, 6))


print("Матрица a:\n", a)
print("Матрица b:\n", b)

d = a - b
print("Разность матриц a и b:\n", d)


m = np.dot(a, b)
print("Произведение матриц a и b:\n", m)


b_inv = np.linalg.inv(b)


result = np.dot(a, b_inv)
print("Результат деления матрицы a на b:\n", result)


h = np.transpose(a)
i = np.transpose(b)
print("Матрица a после транспонирования:\n", h)
print("Матрица b после транспонирования:\n", i)


std_a = np.std(a)
median_b = np.median(b)
variance_a = np.var(a)
min_b = np.min(b)
max_a = np.max(a)
sum_b = np.sum(b)

print("Стандартное отклонение матрицы a:", std_a)
print("Медиана матрицы b:", median_b)
print("Дисперсия матрицы a:", variance_a)
print("Минимальное значение матрицы b:", min_b)
print("Максимальное значение матрицы a:", max_a)
print("Сумма элементов матрицы b:", sum_b)
Матрица a:
 [[2 4 2 1 9 9]
 [7 9 4 8 1 3]
 [3 6 2 2 7 3]
 [5 1 3 6 6 1]
 [7 7 6 6 9 3]
 [2 3 1 5 6 7]]
Матрица b:
 [[3 8 3 8 1 2]
 [1 4 1 9 5 1]
 [8 8 2 7 2 7]
 [3 8 3 2 3 4]
 [2 1 6 6 5 4]
 [2 5 1 3 9 1]]
Разность матриц a и b:
 [[-1 -4 -1 -7  8  7]
 [ 6  5  3 -1 -4  2]
 [-5 -2  0 -5  5 -4]
 [ 2 -7  0  4  3 -3]
 [ 5  6  0  0  4 -1]
 [ 0 -2  0  2 -3  6]]
Произведение матриц a и b:
 [[ 65 110  80 149 155  71]
 [ 94 204  71 196 116  90]
 [ 57 102  70 147 105  65]
 [ 72 127  77 121  73  81]
 [118 204 115 236 144 126]
 [ 58 117  69 117 127  65]]
Результат деления матрицы a на b:
 [[-3.11394463  2.24145275  0.28942944  2.80657998  0.18383589 -1.00123305]
 [ 1.9319583  -1.37008183  0.50067257 -1.16001569  0.20569443  0.81874229]
 [-0.16158502 -0.06708889  0.10621007  0.43918843  0.06591189  0.62638718]
 [ 1.30204013 -1.39384598  0.53161081 -2.02073759  0.66763816  1.48088779]
 [ 1.74991593 -1.99613272  0.37232373 -1.54035422  0.78567425  1.90875462]
 [-2.33051227  2.20356462  0.39535926  1.85410828  0.08070844 -1.04932182]]
Матрица a после транспонирования:
 [[2 7 3 5 7 2]
 [4 9 6 1 7 3]
 [2 4 2 3 6 1]
 [1 8 2 6 6 5]
 [9 1 7 6 9 6]
 [9 3 3 1 3 7]]
Матрица b после транспонирования:
 [[3 1 8 3 2 2]
 [8 4 8 8 1 5]
 [3 1 2 3 6 1]
 [8 9 7 2 6 3]
 [1 5 2 3 5 9]
 [2 1 7 4 4 1]]
Стандартное отклонение матрицы a: 2.6063786901644224
Медиана матрицы b: 3.0
Дисперсия матрицы a: 6.79320987654321
Минимальное значение матрицы b: 1
Максимальное значение матрицы a: 9
Сумма элементов матрицы b: 148

numpy позволяет легко изменять форму многомерных структур, если это возможно:

In [ ]:
# создаем матрицы размером 3x4
arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

# используем метод reshape() для изменения формы матрицы на 2x6
new_arr = arr.reshape(2, 6)

print("Исходная матрица:")
print(arr)
print("Матрица после изменения формы:")
print(new_arr)
Исходная матрица:
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
Матрица после изменения формы:
[[ 1  2  3  4  5  6]
 [ 7  8  9 10 11 12]]
In [ ]:
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
c = np.array([[9, 10], [11, 12]])

#объединяем матрицы по горизонтальной оси (по строкам)
d = np.concatenate((a, b, c), axis=1)
print(d)
#объединяем матрицы по вертикальной оси (по столбцам)
e = np.concatenate((a, b, c), axis=0)
print(e)
[[ 1  2  5  6  9 10]
 [ 3  4  7  8 11 12]]
[[ 1  2]
 [ 3  4]
 [ 5  6]
 [ 7  8]
 [ 9 10]
 [11 12]]
In [ ]:
# превратим матрицу в одномерный вектор, "сгладим" двумерный массив

a = np.array([[1, 2, 9, 8], [7, 5, 3, 4], [2, 1, 5, 6], [4, 8, 7, 8]])
print(a.flatten())
[1 2 9 8 7 5 3 4 2 1 5 6 4 8 7 8]
In [ ]:
import numpy as np

a = np.array([[4, 2, 9, 89], [17, 5, 3, 14], [25, -5, 5, 6], [4, 18, 17, 8]])
print(f"Индекс максимального элемента: {np.argmax(a)}")
print(f"Индекс минимального элемента: {np.argmin(a)}")
Индекс максимального элемента: 3
Индекс минимального элемента: 9

numpy позволяет удобно выбирать конкретные элементы массивов, фильтруя их с помощью "масок" - логических условий.

In [ ]:
print(a[a > 10]) # выведем только те элементы, которые больше 10
a[a > 10] -= 1 # отнимем единицу от всех элементов, которые больше 10
print(a)
[85 13 21 14 13]
[[ 4  2  9 84]
 [12  5  3 10]
 [20 -5  5  6]
 [ 4 13 12  8]]

Библиотека matplotlib¶

Зачастую бывает необходимо визуализировать какой-либо набор данных и посмотреть на него "вживую". Для подобного разработаны различные статистические методы визуального анализа данных, придуманы различные удобные графики. Графический вывод таких данных реализован в питоне с помощью библиотеки matplotlib - практически полноценном графическом движке, позволяющим рисовать и визуализировать различные графики и не только, на основе этой библиотеки построены другие, более сложные продвинутые инструменты.

построим стандартный график, передав в функцию plot массив координат по абсциссе и ординате:

In [ ]:
import matplotlib.pyplot as plt
In [ ]:
x = np.linspace(-100, 100, 100)
plt.plot(x, x ** 2)
plt.show()
No description has been provided for this image

Визуализируем точки на плоскости:

In [ ]:
x = np.random.random(size=100)
y = np.random.random(size=100) ** 2 / 4
In [ ]:
plt.scatter(x, y)
Out[ ]:
<matplotlib.collections.PathCollection at 0x7a2717ca5c90>
No description has been provided for this image

Мы можем делать огромное количество визуальных преобразований над графиком. Одно из самых полезных - окрашивание в разные цвета конкретных элементов.

In [ ]:
import random

plt.scatter(x, y, color=[random.choice(["r", "g", "b"]) for _ in range(len(x))])
Out[ ]:
<matplotlib.collections.PathCollection at 0x7a27184fe770>
No description has been provided for this image

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

In [ ]:
 
In [1]:
import numpy as np

arr = np.random.rand(5, 4)
print(np.median(arr, axis=1))
[0.71051848 0.5335695  0.23265111 0.55832107 0.16912649]
In [2]:
arr
Out[2]:
array([[0.87327603, 0.8848748 , 0.54776092, 0.08004845],
       [0.29802988, 0.24904079, 0.76910911, 0.86959785],
       [0.05214861, 0.41315362, 0.49729177, 0.00473203],
       [0.66850622, 0.90998592, 0.33015958, 0.44813593],
       [0.04728021, 0.2242949 , 0.65381188, 0.11395809]])
In [ ]: