Архив автора: zab88

Определение угла наклона текста на изображении

В данном посте поделюсь итеративным методом определения угла наклона сканированного текста.
Суть метода заключается в следующем: постепенно меняя угол наклона — считаем некоторую характеристику-функцию от изображения (о ней чуть дальше). При каком угле наклона получим максимум — значит это и есть оптимальный угол наклона.
Теперь о том, что считать, для этого внимательно взглянем на то как выглядит текст на сканированном документе. Если представим изображение только в виде черных и белых пикселей и потом спроецируем их все на ось X, то в местах где есть строчки гистограмма будет выдавать пики по черным пикселям, а где междустрочный интервал — по белым пикселям. Пики будут максимально выраженными при правильном угле. Но мы хотим получить наиболее простой алгоритм, поэтому будет считать только кол-во черных и белых пикселей при проекции на ось X. Т.к. строки текста парралельны друг другу, то при лучшем угле кол-во белых пикселей будет максимальным, а кол-во черных минимальным. Этим и воспользуемся:

import numpy as np
import cv2


def get_angle(img):
    # сперва переведём изображение из RGB в чёрно серый
    # значения пикселей будут от 0 до 255
    img_gray = cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY)

    # а теперь из серых тонов, сделаем изображение бинарным
    th_box = int(img_gray.shape[0] * 0.007) * 2 + 1
    img_bin_ = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, th_box, th_box)

    img_bin = img_bin_.copy()
    num_rows, num_cols = img_bin.shape[:2]

    best_zero, best_angle = None, 0
    # итеративно поворачиваем изображение на пол градуса
    for my_angle in range(-20, 21, 1):
        rotation_matrix = cv2.getRotationMatrix2D((num_cols/2, num_rows /2 ), my_angle/2, 1)
        img_rotation = cv2.warpAffine(img_bin, rotation_matrix, (num_cols*2, num_rows*2),
                                      borderMode=cv2.BORDER_CONSTANT,
                                      borderValue=255)

        img_01 = np.where(img_rotation > 127, 0, 1)
        sum_y = np.sum(img_01, axis=1)
        th_ = int(img_bin_.shape[0]*0.005)
        sum_y = np.where(sum_y < th_, 0, sum_y)

        num_zeros = sum_y.shape[0] - np.count_nonzero(sum_y)

        if best_zero is None:
            best_zero = num_zeros
            best_angle = my_angle

        # лучший поворот запоминаем
        if num_zeros > best_zero:
            best_zero = num_zeros
            best_angle = my_angle

    return best_angle * 0.5


img = cv2.imread('singapore_01.jpg')
best_angle = get_angle(img)
print(best_angle)

cv2.imshow('Result', img)
cv2.waitKey()

Наложение текста на изображение с помощью python

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

1. Пример наложения текста с использованием библиотеки python-OpenCV

import numpy as np
import cv2

# создадим белое изображение
# или можно считать изобрежние с помощью cv2.imread("path_to_file")
img = np.zeros((256, 512, 3), np.uint8)
img[:, :, :] = 255

font = cv2.FONT_HERSHEY_COMPLEX
# вставка текста красного цвета
cv2.putText(img, 'наш произвольный текст', (10, 150), font, 1, color=(0, 0, 255), thickness=2)

cv2.imshow('Result', img)
cv2.waitKey()

# есть ограниченное кол-во вариантов выбора шрифта
# FONT_HERSHEY_COMPLEX
# FONT_HERSHEY_COMPLEX_SMALL
# FONT_HERSHEY_DUPLEX
# FONT_HERSHEY_PLAIN
# FONT_HERSHEY_SCRIPT_COMPLEX
# FONT_HERSHEY_SCRIPT_SIMPLEX
# FONT_HERSHEY_SIMPLEX
# FONT_HERSHEY_TRIPLEX
# FONT_ITALIC

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

Для более продвинутого добавления надписей (можно сказать даже художественного) давайте сформулируем требования, предъявляемые к скрипту.
1. Использование произвольного шрифта из файла ttf
2. Возможность задать угол наклона текста
3. Возможность задать прозрачность текста
4. Выравнивание текста по центру
Четвёртый пункт очень важен, ведь используя произвольный шрифт мы не можем расчитать ширину надписи, и было бы здорово передать эту предобработку библиотеке.
Теперь перейдём к коду:

2. Пример наложение текста с использованием библиотеки PIL

import numpy as np
import cv2
from PIL import Image, ImageDraw, ImageFont

# создадим белое изображение
# или можно считать изобрежние с помощью cv2.imread("path_to_file")
img = np.zeros((256, 512, 3), np.uint8)
img[:, :, :] = 255


# для простоты и совместимости возьмем пустое изображение из первого примера
# Чтобы не использовать opencv, а только PIL используйте функцию Image.open()
def put_text_pil(img: np.array, txt: str):
    im = Image.fromarray(img)

    font_size = 24
    font = ImageFont.truetype('LibreFranklin-ExtraBold.ttf', size=font_size)

    draw = ImageDraw.Draw(im)
    # здесь узнаем размеры сгенерированного блока текста
    w, h = draw.textsize(txt, font=font)

    y_pos = 50
    im = Image.fromarray(img)
    draw = ImageDraw.Draw(im)

    # теперь можно центрировать текст
    draw.text((int((img.shape[1] - w)/2), y_pos), txt, fill='rgb(0, 0, 0)', font=font)

    img = np.asarray(im)

    return img


img = put_text_pil(img, 'Some Styled Black Text Here')
cv2.imshow('Result', img)
cv2.waitKey()

Пример кода с альфа каналом и наклоном будет чуть позже

Сохранение обученной ML модели (объекта python) в базе данных (BLOB)

Стандартная архитектура при создании сервисов с использованием машинного обучения подразумеват обучение модели на одном сервере и её использование на другом сервере, который непосредственно работает на предикт. Однако, на предикт может работать много серверов, и возникает вопрос: как доставить обученную модель на все сервера? Обычно модель сохраняют в виде файла. Поэтому можно сделать mount какого-то общего сетевого ресурса, и на нём хранить модель. Но более гибко будет сохранить модель в базе данных, тем более что все инстансы уже скорее всего имеют соединение с общей базой.
Ниже приведу пример того, как сохранить объект python в базе данных в колонке типа блоб.

import _pickle
import pymysql.cursors

## Для простоты возьмём лёгкий объект
# однако обученная модель может достигать сотни мегабайт, а то и ещё больше
listToPickle = [(10, 10), ("example", 10.0), (1.0, 2.0), "object"]

## Преобразование нашего объекта в строку
pickledList = _pickle.dumps(listToPickle)

## Соединение с базой данных mysql
connection = pymysql.connect(host='localhost', port=3306, user='dbuser', password='pass', db='somedb', cursorclass=pymysql.cursors.DictCursor)

## создание курсора
with connection.cursor() as cursor:
    ## Вставка в БД
    cursor.execute("""INSERT INTO pickleTest VALUES (NULL, 'testCard', %s)""", (pickledList, ))
    connection.commit()

    ## Выборка по сохраненной модели
    cursor.execute("""SELECT features FROM pickleTest WHERE card = 'testCard'""")

    ## Получим и распечатаем результат
    res = cursor.fetchone()

    ## Обратное преобразование
    unpickledList = _pickle.loads(res['pickledStoredList'])
    print(unpickledList)

Как видим всё просто, достаточно перед сохранием в блоб преобразовать объект с помощью _pickle.dumps() а при загрузке объекта обратно преобразовать в строку в объект с помощью _pickle.loads().
Также отмечу, что в третьем питоне не надо устанавливать дополнительных библиотек, используется встроенная библиотека _pickle. Таким образом Pickling в Python — это способ хранения весьма сложных структур данных в двоичном представлении, которые требуется восстановить через некоторое время, для получения той же структуры данных.

Как обойти строки dataframe в цикле (pandas)

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

import pandas as pd

dataframe_from_list = [[1,2], [3,4], [10,20]]
df = pd.DataFrame(dataframe_from_list, columns=['col1', 'col2'])

for index, row in df.iterrows():
    print(index, row)
    print(row['col1'], row['col2'], row['col1'] + row['col2'])

В данном примере использовалась функция iterrows для обхода датафрейма. Для обращения к колоночным значениям в строке используется row['название_колонки'].

А теперь давайте подумаем, зачем нам итерироваться по датафрейму: самое очевидно это взять некоторые колоночные значения из строки и подсчитать некоторую функцию.
Но это можно сделать и с помощью apply метода с указанием направления по оси x:

result = df.apply(lambda row: row['col1'] + row['col2'], axis=1)
print(result)
# 3, 7, 30

Соответсвенно, вместо lambda функции можно поставить свою, или в простом случае использовать оптимизированные numpy функции, например np.sum

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

from multiprocessing import Pool
import numpy as np

# для примера возьмем функцию суммы по строке, приведенную выше
def calculate_sum_column(df):
    df['sum_column'] = df.apply(lambda row: row['col1'] + row['col2'], axis=1)
    return df

# в данном примере расспараллеливаем на восемь потоков. Будьте аккуратны - при распараллеливании тратится больше оперативной памяти
def parallelize_dataframe(df, func):
    a,b,c,d,e,f,g,h = np.array_split(df, 8)
    pool = Pool(8)
    some_res = pool.map(func, [a,b,c,d,e,f,g,h])
    df = pd.concat(some_res)
    pool.close()
    pool.join()
    return df

# имитация большого датасета
df = pd.concat([df, df, df], ignore_index=True)

df = parallelize_dataframe(df, calculate_sum_column)
print(df.head(10))

С помощью данного гибкого «многоядерного» подхода можно многократно ускорить обход датафрейма и вычислить необходимую функцию

Классификация текстов методом Doc2vec в python

Используя любой метод классификации текстов, первоночально необходимо придумать способ перевода текстового документа в набор чисел. Ранее уже был рассмотрен метод на основе TF-IDF, в этот раз рассмотрим метод DOC2VEC. Из названия сразу понятно, что метод переводит документ в вектор, причём делается это просто. Сразу приведу код:

# определим датасет
some_texts = [
   'текст номер один',
   'Текст следуюйщих под номером два',
   'третий набор слов',
   'что-то ещё для полноценного дата-сета',
   'пример текстового документа'
]

from gensim.models.doc2vec import Doc2Vec, TaggedDocument
documents = [TaggedDocument(doc, [i]) for i, doc in enumerate(some_texts)]

model = Doc2Vec(documents, vector_size=5, workers=4, epochs=3)

Обучение, в данном примере (взятом из документации), происходит сразу при создании объекта. (Мне это показалось немного странным, после sklearn с методами fit и transform).
Некоторые основные параметры:
vector_size — какой конечный размер будет у вектора. Тонкий параметр, который придётся подбирать. Чем больше выборка и длиннее каждый документ — тем имеет смысл ставить параметр больше, порядка 100-300. Для небольших текстов и датасетов может хватить размерности 5-10.
epoch — кол-во эпох на обучении. Тоже потребует настройки, обычно 10-20

workers — сколько ядер хотите задействовать на обучении. (Узнать кол-во: from multiprocessing import cpu_count)
Стоит также отметить, что начальные веса для документов берутся случайно, и в дальнейшем происходит утряска-оптимизация весов. Поэтому после каждого обучения результирующий вектор для одного и того же документа будет разным.

Полезные методы Doc2Vec

# сохранение модели для дальнейшего использования
model.save("my_doc2vec_model")
# загрузка модели
model = Doc2Vec.load("my_doc2vec_model")
# нахождение наиболее похожего документа
vector_to_search = model.infer_vector(["ищем", "похожий", "текст"])
# три наиболее похожих
similar_documents = model.docvecs.most_similar([vector_to_search], topn=3)
for s in similar_documents:
    print(some_texts[s[0]])

И так, модель Doc2vec обучена, теперь можно любой документ перевести в вектор и обучить вашим любимым классификатором. Давайте для пример возьмем RandomForest

from sklearn.ensemble import RandomForestClassifier

clf = RandomForestClassifier()
clf.fit([model.infer_vector([x.words]) for x in documents], [1, 1, 1, 0, 0])
 
res = clf.predict([model.infer_vector(['текст', 'номер', 'три'])])
print(res)  # [1]

По своему опыту могу сказать, что doc2vec несколько капризная штука и по настоящему хорошо работать этот метод мне не удалось заставить. Особенно неприятно удивили результаты с находением наиболее похожих документов в датасете. Однако, т.к. это другой подход, отличный от классического bag of words, tf-idf а также от LDA и подходов на нейронных сетях, то можно его смело рекомендовать использовать в дополнение с другими алгоритмами для достижения максимальной точности классификации.

Скачать список доменов в зоне .ru

В доменной зоне .ru находится примерно 5 000 000 зарегистрированных доменов. Если учитывать домены третьего уровня, то эта цифра увеличивается примерно до восьми миллионов. Файл для скачивания представлят собой .txt файл в архиве, в нём находится примерно 1 000 000 доменов актуальных на май 2019. Думаю миллиона вполне хватит для проверки статистических гипотез, которые возникают в ходе исследовательских задач. (ссылка на скачиване находится чуть ниже)

Так же для интереса приведу данные по количеству доменов в других расспространённых зонах, а всего в мире зарегистрировано порядка 400 000 000 доменов.
зона .com 150 миллионов
зона .net 14 миллионов
зона .org 11 миллионов
зона .info 7 миллионов
зона .uk 12 миллионов
зона .de 5 миллионов

Как быстро получить информацию со всех сайтов, указанных в базе доменов? Для этого воспользуемся python скриптом. Обходить все домены лучше всего не поочереди, а асинхронно или в несколько потоков, т.к. задержки, возникающие при запросе будут тормозить следующий запрос и накапливаться, а это нам не к чему. Самый быстрый и мощный вариан asyncio, однако уровень вхождения в библиотеку чуть выше среднего, поэтому ниже показан самый простой вариант как быстро обойти все домены. С помощью данного скрипта можно обойти миллион доменов из списка за 3-4 часа. В качестве информации, которую будем брать с сайтов, давайте возьмем данные о IP адресе, где расположен хостинг.

скрипт на python для парсинга инфорамации о сайтах из базы доменов на примере ip:

import socket
# с помощью multiprocessing сможем обходить сайты не последовательно
from multiprocessing.dummy import Pool as ThreadPool
import dns.resolver
import pandas as pd

# определим функцию для получения IP адреса
def get_ip(url):
    try:
        data_ip = socket.gethostbyname(url)
    except:
        return None
    return data_ip


df_all_ru = pd.read_csv('all_ru.csv')
sites_list = df_all_ru['domain_name'].tolist()

# за раз давайте будем брать сразу 100 доменов
# имеет смысл подобрать этот параметр в зависимости от скорости вашего интернета и задачи которую решаете
p = ThreadPool(100)
result_ip = p.map(get_ip, sites_list)
p.close()
p.join()

df_all_ru['ip'] = result_ip
df_all_ru.to_csv('ru_domains_ips.csv', index=False)

С помощью данного простого скрипта можно быстро получить данные с большого количества сайтов.

Непосредственно скачать файл со списком ру доменов здесь

Реализация алгоритма кластеризации шумных данных DBSCAN на python

Самым известным алгоритмом кластеризации является k-means, с него начинаются все обучающие примеры по кластеризации. Но несмотря на простоту и скорость k-means, у него есть одна очень большая проблема — это наперед заданное количество кластеров. В реальном же мире чаще всего нам не известно на сколько групп следует разбить данные, более того часть данных могут быть вредными выбросами. DBSCAN отлично разбивает множество на оптимальное количество кластеров и не учитывает выбросы, неудивительно что в 2014ом году алгоритм получил метку «проверено временем» на одной из конференций по анализу данных.
Мы будет рассматривать реализация на основе библиотеки sklearn
* В алгоритма два входных параметра min_samples и eps. Если в заданной окрестности eps есть минимальное необходимое количество объектов min_samples, то данная окрестность будет считаться кластером.
* Если в заданной области нет необходимого количества объектов, то инициирующая эту область точка считается выбросом
Кластеры, найденные DBSCAN могут иметь любую форму, в отличии от алгоритма k-means (которые предполагают только выпуклую форму)

Ниже приведём простой пример кластеризации с помощью DBSCAN.

from sklearn.datasets import load_iris
from sklearn.cluster import DBSCAN

iris = load_iris()
dbscan = DBSCAN(eps=0.5, min_samples=5)

dbscan.fit(iris.data)

# Готово! Распечатаем метки принадлежности к кластерам
print(dbscan.labels_)

Теперь давайте визуализируем полученный результат

from sklearn.decomposition import PCA
import matplotlib.pyplot as pl

pca = PCA(n_components=2).fit(iris.data)
pca_2d = pca.transform(iris.data)
for i in range(0, pca_2d.shape[0]):
    if dbscan.labels_[i] == 0:
        c1 = pl.scatter(pca_2d[i, 0], pca_2d[i, 1], c='r', marker='+')
    elif dbscan.labels_[i] == 1:
        c2 = pl.scatter(pca_2d[i, 0], pca_2d[i, 1], c='g', marker='o')
    elif dbscan.labels_[i] == -1:
        c3 = pl.scatter(pca_2d[i, 0], pca_2d[i, 1], c='b', marker='*')

pl.legend([c1, c2, c3], ['Cluster 1', 'Cluster 2', 'Noise'])
pl.title('DBSCAN finds 2 clusters and noise')
pl.show()

В результате получим вот такое разбиение по кластерам и шум
кластеризация dbscan

Hyperopt и xgboost: Пример поиска гиперпараметров с помощью Hyperopt для xgboost

Xgboost пожалуй самая популярная библиотека градиентного бустинга, поэтому именно на её примере покажу как можно улучшить качество простым техническим приёмом — автоматическим подбором параметров. Чтобы не усложнять поиск оптимальных гиперпараметров, возьмем лишь несколько основных: n_estimators, eta и max_depth. Основная идея библиотеки hyperopt — это построение математической гипотезы о том как выглядит функция результа на множества параметров и проверка этой гипотезы с каждой итерацией. Т.к. перебор и обучение может быть весьма продолжительны — то на вход задается количество итерации, после которого происходит остановка поиска. Но все равно предсказать, когда закончится поиск проблематично т.к. при разных гиперпараметрах обучение и предсказание занимает разное время. Поэтому я и советую начинать с небольшого количества итерации и небольшого количества параметров. К сожалению останова по истечению заранее заданного времени нет, поэтому мощный комьютер для data-sciece вам в руки и вперед!

import numpy as np
from sklearn import datasets
from sklearn.model_selection import cross_val_score
from xgboost import XGBClassifier

from hyperopt import hp, tpe
from hyperopt.fmin import fmin

# Для теста возьмем классический сет "ирисы Фишера"
# Разумеется вместо X и y Вам следует взять свои фичи и таргет
iris = datasets.load_iris()
X = iris.data
y = iris.target


def hyperopt_xgb_score(params):
    clf = XGBClassifier(**params)
    # усреднение по 3ем фолдам, для уменьшения влияния стахостичности
    # для ускорения можно использовать train_test_split один раз
    current_score = cross_val_score(clf, X, y, cv=3).mean()
    print(current_score, params)
    return -current_score


simple_space_xgb = {
            'n_estimators': hp.choice('n_estimators', range(100, 1000)),
            'eta': hp.quniform('eta', 0.025, 0.5, 0.025),
            'max_depth':  hp.choice('max_depth', np.arange(1, 14, dtype=int)),
}

best = fmin(fn=hyperopt_xgb_score, space=simple_space_xgb, algo=tpe.suggest, max_evals=10)
print('best:')
print(best)

Как видимо нет ничего сложного, главное определить:
1) пространство параметров, внутри которых hyperopt будет оптимизировать
2) функцию оценки.
Обратите внимание, что в функции оценки возвращается результат со знаком минус — это сделано специально, т.к. гиперопт минимизирует функционал, а нам надо максимизировать точность.
Для построения пространства в основном используется либо набор возможных значений (hp.choice) либо отрезок в пространстве R hp.quniform(label, low, high, q), где q округление.
Для тех, кто планирует более мощную настройку, оставлю space более мощный для XGB.

space_xgb2 = {
            'n_estimators': hp.choice('n_estimators', range(100, 1000)),
            'eta': hp.quniform('eta', 0.025, 0.5, 0.025),
            'max_depth':  hp.choice('max_depth', np.arange(1, 14, dtype=int)),
            'min_child_weight': hp.quniform('min_child_weight', 1, 6, 1),
            'subsample': hp.quniform('subsample', 0.5, 1, 0.05),
            'gamma': hp.quniform('gamma', 0.5, 1, 0.05),
            'colsample_bytree': hp.quniform('colsample_bytree', 0.5, 1, 0.05),
            'eval_metric': 'auc',
            'objective': 'binary:logistic',
            # Increase this number if you have more cores. Otherwise, remove it and it will default
            # to the maxium number.
            'nthread': 4,
            'booster': 'gbtree',
            'tree_method': 'exact',
            'silent': 1
        }

Теперь с помощью данного поста Вы можете построить оптимизацию гиперпараметров не ограничиваясь xgboost.

Пошаговая инструкция по установке tensorflow-gpu и keras на ubuntu

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

В данном посте будет рассказано об общем процесс установки составляющих, а в конце сравним скорость нейронной сети с использованием GPU и без.

Начальные требования:
операционная система: ubuntu 16, 17 или 18
видеокарта nvidia

Установка драйевров
Проверим какие драйверы установятся командой:

ubuntu-drivers devices

Вы должны увидеть примерно нижеследующий вывод:

== /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 ==
modalias : pci:v000010DEd00001C81sv00001458sd00003765bc03sc00i00
vendor   : NVIDIA Corporation
model    : GP107 [GeForce GTX 1050]
driver   : nvidia-driver-390 - distro non-free recommended
driver   : xserver-xorg-video-nouveau - distro free builtin

Далее установим драйверы видеокарты:

sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt update
sudo apt upgrade

sudo ubuntu-drivers autoinstall

sudo reboot

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

nvidia-smi

запомните эту команду nvidia-smi — думаю она часто вам будет требоваться, чтобы посмотреть загрузку видеокарт, убить какой-нибудь процесс на видеокарте, да и смотреть текущую температуру для избегания перегрева.

Установка CUDA.
Важно, на данный момент нужна версия 9.0, для совместимости с tensorflow. Скачать можно по ссылке https://developer.nvidia.com/cuda-90-download-archive?target_os=Linux&target_arch=x86_64&target_distro=Ubuntu&target_version=1704&target_type=runfilelocal качаем base installer 1.6Gb и запасаемся терпением. Скорости на сайтах nvidia ограничены на отдачу файлов.
Убедитесь, что выбрана конфигурация загрузки как на изображении:
cuda

sudo sh cuda_9.0.176_384.81_linux.run --override --silent --toolkit

Если всё прошло удачно, то появится папка:
ls /usr/local/cuda-9.0/

Добавляем cuda в PATH

export PATH=/usr/local/cuda-9.0/bin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/usr/local/cuda-9.0/lib64\${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

Установка cuDNN
Теперь надо вспомнить навыки регистрации, придётся зарегистрироваться на сайте nvidia для скачивания cudnn (это библиотека оптимизирует расчёт глубоких нейронных сетей)
Ссылка для скачивания https://developer.nvidia.com/compute/machine-learning/cudnn/secure/v7.1.4/prod/9.0_20180516/cudnn-9.0-linux-x64-v7.1

# разархивируем
tar -xzvf cudnn-9.0-linux-x64-v7.1.tgz 

# копируем файлы в директорию с CUDA
sudo cp cuda/include/cudnn.h /usr/local/cuda/include
sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64

# немного меняем права, чтобы файлы библиотеки были доступны всем пользователям
sudo chmod a+r /usr/local/cuda/include/cudnn.h /usr/local/cuda/lib64/libcudnn*

Теперь добавляем пути в bashrc (в любом редакторе, я предпочитаю mc)

mcedit ~/.bashrc

И в конец файла пишем:

export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/cuda/lib64:/usr/local/cuda/extras/CUPTI/lib64"
export CUDA_HOME=/usr/local/cuda
export PATH=/usr/local/cuda-9.0/bin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/usr/local/cuda-9.0/lib64\${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

Перезагрузим конфиг bashrc

source ~/.bashrc
sudo ldconfig

И проверим на наличие корректного пути:

echo $CUDA_HOME
# должно вывестить /usr/local/cuda

Установка tensorflow с поддержкой GPU
Подробная инструкция по установке есть на официальном сайте tensorflow.org/install/ но если не лезть в детали, то достаточно запустить 2 команды:

pip3 install tensorflow
pip3 install tensorflow-gpu

pip3 install keras

Теперь можно запускать пример с обучением и предсказанием нейронной сети, например с использованием keras библиотеки.
Отмечу, что по умолчанию keras использует именно GPU, однако считать можно и на CPU. Для сравнения скоростей расчетов можно использовать код отключения GPU — тогда расчёт будет производиться только за счёт процессора. Добавьте этот код перед импортом tensorflow и keras

import os
os.environ["CUDA_VISIBLE_DEVICES"] = ""

Конфигурация компьютера для машинного обучения. Бюджетный и оптимальный подбор.

Искусственный интеллект уже не первый год наступает на пятки естественному и делает это не в последнюю очередь за счёт роста мощности железа. Поэтому для создания современных алгоритмов ML вам скорее всего потребуется производительный компьютер. В данном посте будет предложена 2 сборки:
первая сборка ПК для ML: начинающая, рассмотрим минимальную систему с которой относительно комфортно работать, стоимостью до 30 тысяч рублей.
вторая сбалансированная цена/производительность: подороже, мощнее, позволяющая решать широкий круг задач. стоимостью 60-90 тысяч рублей.
Изначально думал выделить ещё и сборку для энтузиастов, но потом решил что те, кто готов выкладывать большие деньги на ML, те точно знают что они хотят и скорее всего будут собирать под конкретную задачу.

Процессор:
Основные показатели — количество ядер и производительность одного ядра. У Intel более быстрые ядра, зато у AMD за те же деньги вы получите больше ядер. Что важнее и где найти балланс, зависит от задач. Если будете гонять нейронки на видеокартах, то берите интел. Если хотите решать широкий круг задач, то AMD т.к. в конечном итоге при правильном распараллеливании программы расчёты будут идти бысрее. Но учтите, не всё можно распараллелить. По поводу гипертрэйдинга — он немного ускоряет систему, но в реальности не так значительно, поэтому ядра первостепенней.
Бюджетный вариант: 4-6 ядер
Средний вариант: 6-8 или более ядер с хорошим бустингом на одно ядро.

Оперативныя помять:
Бюджетный вариант: 16Gb
Средний: 32Gb двумя планками по 16, чтобы иметь возможность расшириться до 64 в случае необходимости. Больше 64Gb сокеты 1151-v2 и AM4 не поддерживают. Если хотите получить несколько дополнительный процентов производительности от AMD — берите более быструю память и обязательно 2ух канальную.
Много где в AI советуют чтобы количество оперативной памяти было в 2 раза больше чем видео памяти, у меня пока не сложилось конкретной рекомендации на эту тему, но оставлю это здесь.

Видеокарты:
В бюджетной сборке предлагаю обойтись вообще без видекарты (если в процессоре есть встроенная графика) или с самой дешевой, которую найдёте. Так можно сэкономить, приобретя более мощные остальные компоненты и оставляя возможность апргрейда. На ней Вы считать не будете — она только для вывода изображения на монитор и всё! Как же так, возможно кто-то спросит? Ответ простой — GPU в первую очередь нужна для нейронных сетей, но на слабой видеокарте вроде geforce 1050 скорость расчётов будет в большинстве случаев такой же как на мощном CPU. Некоторые алгритмы градиентного бустинга также умеют использовать мощь GPU, но на опыте — в сравнении с 1050 выигрыша никакого нет. А при покупке карты дороже, уже вылезем из бюджета в 30т.р.
В средней сборке видеокарта однозначно нужна и желательно помощнее. Выбор производителя однозначе — это nvidia. Стоит сразу отметить важную деталь — SLI вам никак не понадобится, tensorflow c cuda отлично распараллелены и вы можете использовать разные типы карт nvidia, вовсе не обязательно одинаковые. Однако в нейронных сетях есть такое понятие как батч (batch) и чтобы он был больше — нужно больше памяти на видеокарте. Чем больше batch тем лучше и дабы не быть ограниченным снизу слабой видеокартой по размеру batch — лучше брать видеокарты с одинаковым объёмом памяти.
Времена майнинга прошли и купить карту стало легче, брать ли БУ или нет — каждый пусть решит для себя самостоятельно. Чтобы облегчить выбор, напишу сравнительную производительность разных карт, за базу (1X) пусть выступит 1050. Это позволит вам лучше соотнести мощность с ценой и подобрать оптимум.
1050 1X
1050Ti 1.12X
1060 3Gb 1.95X
1060 6Gb 2.09X
1070 2.91X
1070Ti 3.39X
1080 3.7X
2070 3.9X
1080Ti 4.42X
2080 4.45X
2080Ti 5.79X
Меньше 4 Gb лучше не брать, может оказаться мало даже для простых задач.

Материнская плата:
В выборе материнской плате надо знать следующее — будете ли вы разгонять процессор и сколько видеокарт планируете подключать. Я не являюсь сторонником разгона, но если Вы планируйте выжимать из системы максимум — то обратите внимание на возможность разгона. Более тонкий момент с количеством видекарт, а точнее с количеством PCI-E линий. практически все материнки поддерживают 16 линий для одной видеокарты. Проблемы начинаются при использовании большего количества видеокарт для машинного обучения. Конфигурация 8PCI-E на 8PCI-E для двух видеокарт мне кажется оптимальной для среднего решения. Но если в планах использование только одной видеокарты, то можно и сэкономить не покупая более дорогой сокет.

Жесткий диск:
Бюджетная сборка: берите HDD, скажем на террабайт. Конечно система будет загружаться медленнее чем с ssd. но его можно будет потом докупить. А если сразу выложиться на ssd — то есть опасения что не влезут большие датасеты. Учтите, что на установку всех нужных библиотек и программ вполне может уйти под 100Gb жесткого диска.
В оптимальной сборке предлагается взять SSD + HDD. Некоторые модели SSD-M2 имеют повышенную скорость чтения и записи, по сравнению с обычными SATA3 ssd но с пользовательской стороны значительного ускорения не заметно, однаком любителям топчика — SSD-M2 советую, особенно если оперативной памяти маловато — будет очень быстрый swap.

Охлаждение и корпус:
В бюджетной версии можно обойтись самым простым корпусом, а вот в оптимальном уже стоит задуматься об охлаждении видеокарт/ы и процессора. Если видеокарты всё же две и между ними мало места — то верхняя будет греться ощутимо сильнее. Одно из самый простых и действенных решения — поставить вентилятор аля 140мм сбоку, дующий сразу на видеокарты. Ещё один лайфхак — дабы пыли внутри скапливалось поменьше — рекомендуется вентиляторы на вдув ставить мощнее вентиляторов на выдув.

Блок питания:
Основной параметр — мощность в ватах. Чтобы понять на сколько ватт нужен вам блок, рекомендую в поисковике вбить «power supply calculator» и Вы найдете несколько приятных интерфейсов для расчетов. Для самого бюджетного варианта вполне подойдёт блок на 400-500W, для систем с двумя видеокартами блока на 850W будет достаточно, 850W хватит даже для двух 2080Ti. Для одной 1080Ti или 2080Ti будет достаточно 600W. Для двух средних карт вроде 1070/2070 650/700W тоже будет достаточно. Цены на разные блоки питания скачут существенно, и параметров у них много, но обычно более дорогой блок питания оснащается большим количеством защит и если начинка стоит дорого, лучше взять блок качественнее.

Большинство компонентов можно приобрести на вторичном рынке — это будет заметно дешевле. Конечно, в покупке БУ комплектующих есть некоторый риск, но за те же деньги вы сможете получать результат вычислений быстрее. Меньше всего опасаться можно за процессоры и оперативную память, если они работают — то почти наверняка, при нормальной эксплуатации, они ещё будут работать лет десять.