Подтвердить что ты не робот

Как ускорить создание матрицы перехода в Numpy?

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

def increment_counts_in_matrix_from_chain(markov_chain, transition_counts_matrix):
    for i in xrange(1, len(markov_chain)):
        old_state = markov_chain[i - 1]
        new_state = markov_chain[i]
        transition_counts_matrix[old_state, new_state] += 1

Я пробовал ускорить его тремя способами:

1) Используя разреженный однострочный матричный шаблон на основе этого кода Matlab:

transition_matrix = full(sparse(markov_chain(1:end-1), markov_chain(2:end), 1))

Что в Numpy/SciPy выглядит следующим образом:

def get_sparse_counts_matrix(markov_chain, number_of_states):
    return coo_matrix(([1]*(len(markov_chain) - 1), (markov_chain[0:-1], markov_chain[1:])), shape=(number_of_states, number_of_states)) 

И я попробовал еще пару настроек Python, например, используя zip():

for old_state, new_state in zip(markov_chain[0:-1], markov_chain[1:]):
    transition_counts_matrix[old_state, new_state] += 1 

И очереди:

old_and_new_states_holder = Queue(maxsize=2)
old_and_new_states_holder.put(markov_chain[0])
for new_state in markov_chain[1:]:
    old_and_new_states_holder.put(new_state)
    old_state = old_and_new_states_holder.get()
    transition_counts_matrix[old_state, new_state] += 1

Но ни один из этих 3 методов не ускользнул. Фактически, все, кроме решения zip(), было как минимум на 10 раз медленнее, чем мое первоначальное решение.

Есть ли какие-либо другие решения, на которые стоит обратить внимание?



Модифицированное решение для построения матрицы перехода из множества цепочек
Лучшим ответом на этот вопрос был DSM. Тем не менее, для тех, кто хочет заполнить матрицу перехода на основе списка миллионов цепей марков, самый быстрый способ:

def fast_increment_transition_counts_from_chain(markov_chain, transition_counts_matrix):
    flat_coords = numpy.ravel_multi_index((markov_chain[:-1], markov_chain[1:]), transition_counts_matrix.shape)
    transition_counts_matrix.flat += numpy.bincount(flat_coords, minlength=transition_counts_matrix.size)

def get_fake_transitions(markov_chains):
    fake_transitions = []
    for i in xrange(1,len(markov_chains)):
        old_chain = markov_chains[i - 1]
        new_chain = markov_chains[i]
        end_of_old = old_chain[-1]
        beginning_of_new = new_chain[0]
        fake_transitions.append((end_of_old, beginning_of_new))
    return fake_transitions

def decrement_fake_transitions(fake_transitions, counts_matrix):
    for old_state, new_state in fake_transitions:
        counts_matrix[old_state, new_state] -= 1

def fast_get_transition_counts_matrix(markov_chains, number_of_states):
    """50% faster than original, but must store 2 additional slice copies of all markov chains in memory at once.
    You might need to break up the chains into manageable chunks that don't exceed your memory.
    """
    transition_counts_matrix = numpy.zeros([number_of_states, number_of_states])
    fake_transitions = get_fake_transitions(markov_chains)
    markov_chains = list(itertools.chain(*markov_chains))
    fast_increment_transition_counts_from_chain(markov_chains, transition_counts_matrix)
    decrement_fake_transitions(fake_transitions, transition_counts_matrix)
    return transition_counts_matrix
4b9b3361

Ответ 1

Как насчет чего-то подобного, используя np.bincount? Не супер-прочный, но функциональный. [Спасибо @Warren Weckesser за настройку.]

import numpy as np
from collections import Counter

def increment_counts_in_matrix_from_chain(markov_chain, transition_counts_matrix):
    for i in xrange(1, len(markov_chain)):
        old_state = markov_chain[i - 1]
        new_state = markov_chain[i]
        transition_counts_matrix[old_state, new_state] += 1

def using_counter(chain, counts_matrix):
    counts = Counter(zip(chain[:-1], chain[1:]))
    from_, to = zip(*counts.keys())
    counts_matrix[from_, to] = counts.values()

def using_bincount(chain, counts_matrix):
    flat_coords = np.ravel_multi_index((chain[:-1], chain[1:]), counts_matrix.shape)
    counts_matrix.flat = np.bincount(flat_coords, minlength=counts_matrix.size)

def using_bincount_reshape(chain, counts_matrix):
    flat_coords = np.ravel_multi_index((chain[:-1], chain[1:]), counts_matrix.shape)
    return np.bincount(flat_coords, minlength=counts_matrix.size).reshape(counts_matrix.shape)

который дает:

In [373]: t = np.random.randint(0,50, 500)
In [374]: m1 = np.zeros((50,50))
In [375]: m2 = m1.copy()
In [376]: m3 = m1.copy()

In [377]: timeit increment_counts_in_matrix_from_chain(t, m1)
100 loops, best of 3: 2.79 ms per loop

In [378]: timeit using_counter(t, m2)
1000 loops, best of 3: 924 us per loop

In [379]: timeit using_bincount(t, m3)
10000 loops, best of 3: 57.1 us per loop

[править]

Избегание flat (за счет отсутствия работы на месте) может сэкономить некоторое время для небольших матриц:

In [80]: timeit using_bincount_reshape(t, m3)
10000 loops, best of 3: 22.3 us per loop

Ответ 2

Просто для пинков, и потому, что я хотел попробовать, я применил Numba к вашей проблеме. В коде, который включает просто добавление декоратора (хотя я сделал прямой вызов, чтобы я мог проверить варианты jit, которые здесь предоставляют numba):

import numpy as np
import numba

def increment_counts_in_matrix_from_chain(markov_chain, transition_counts_matrix):
    for i in xrange(1, len(markov_chain)):
        old_state = markov_chain[i - 1]
        new_state = markov_chain[i]
        transition_counts_matrix[old_state, new_state] += 1

autojit_func = numba.autojit()(increment_counts_in_matrix_from_chain)
jit_func = numba.jit(argtypes=[numba.int64[:,::1],numba.double[:,::1]])(increment_counts_in_matrix_from_chain)

t = np.random.randint(0,50, 500)
m1 = np.zeros((50,50))
m2 = np.zeros((50,50))
m3 = np.zeros((50,50))

И затем тайминги:

In [10]: %timeit increment_counts_in_matrix_from_chain(t,m1)
100 loops, best of 3: 2.38 ms per loop

In [11]: %timeit autojit_func(t,m2)                         

10000 loops, best of 3: 67.5 us per loop

In [12]: %timeit jit_func(t,m3)
100000 loops, best of 3: 4.93 us per loop

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

Тем не менее, получив ускорение 35x и 485x без изменения кода и просто добавив вызов numba (можно также назвать как декоратор), довольно впечатляет в моей книге. Вероятно, вы могли бы получить аналогичные результаты с помощью cython, но для этого потребовалось бы немного больше шаблонов и записи файла setup.py.

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

Ответ 3

Здесь более быстрый метод. Идея состоит в том, чтобы подсчитать количество вхождений каждого перехода и использовать подсчеты в векторизованном обновлении матрицы. (Я предполагаю, что один и тот же переход может произойти несколько раз в markov_chain.) Класс Counter из библиотеки collections используется для подсчета числа вхождений каждого перехода.

from collections import Counter

def update_matrix(chain, counts_matrix):
    counts = Counter(zip(chain[:-1], chain[1:]))
    from_, to = zip(*counts.keys())
    counts_matrix[from_, to] += counts.values()

Пример синхронизации, в ipython:

In [64]: t = np.random.randint(0,50, 500)

In [65]: m1 = zeros((50,50))

In [66]: m2 = zeros((50,50))

In [67]: %timeit increment_counts_in_matrix_from_chain(t, m1)
1000 loops, best of 3: 895 us per loop

In [68]: %timeit update_matrix(t, m2)
1000 loops, best of 3: 504 us per loop

Это быстрее, но не на порядок быстрее. Для реальной скорости вы можете рассмотреть возможность реализации этого в Cython.

Ответ 4

Хорошо, мало идей, чтобы вмешаться, с некоторым небольшим улучшением (по стоимости человека без изменений)

Начнем со случайного вектора целых чисел от 0 до 9 длины 3000:

L = 3000
N = 10
states = array(randint(N),size=L)
transitions = np.zeros((N,N))

Ваш метод на моей машине имеет производительность timeit 11,4 мс.

Первое, что нужно сделать для небольшого улучшения, - это дважды прочитать данные, сохранив их во временной переменной:

old = states[0]
for i in range(1,len(states)):
    new = states[i]
    transitions[new,old]+=1
    old=new

Это дает вам улучшение на 10% и сокращает время до 10,9 мс.

Более инволютивный подход использует шаги:

def rolling(a, window):
    shape = (a.size - window + 1, window)
    strides = (a.itemsize, a.itemsize)
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

state_2 = rolling(states, 2)
for i in range(len(state_2)):
    l,m = state_2[i,0],state_2[i,1]
    transitions[m,l]+=1

Шаги позволяют вам читать последовательные числа массива, обманывая массив, чтобы думать, что строки начинаются по-другому (хорошо, это не очень хорошо описано, но если вы потратите некоторое время, чтобы прочитать о шагах, вы получите его ) Этот подход теряет работоспособность, достигая 12,2 мс, но это коридор, чтобы обмануть систему еще больше. сглаживая как матрицу перехода, так и перечеркнутую матрицу на одномерные массивы, вы можете ускорить работу еще немного:

transitions = np.zeros(N*N)
state_2 = rolling(states, 2)
state_flat = np.sum(state_2 * array([1,10]),axis=1)
for i in state_flat:
    transitions[i]+=1
transitions.reshape((N,N))

Это сокращается до 7,75 мс. Это не по порядку величины, но все равно на 30% лучше:)