Вот трюк, чтобы сделать это:
import numpy as np
pat = np.array(Pattern)
data = np.array(SampleTarget)
n = len(data)
m = len(pat)
k = data.strides[0] # typically 8 for float64
# data2d is a view to the original data,
# with data_2d[:-m, 6] == data_2d[1:1-m, 5] == ... == data_2d[6:, 0]
data_2d = np.lib.stride_tricks.as_strided(data, shape=(n-m+1, m), strides=(k, k))
# So you can check for matches on data[i, :] for all i
print(np.all(np.isclose(data_2d, pat), axis=1))
Вывод:
array([False, False, False, False, False, False, False, True, False,
False, False, False, False, False, False, False, False, False,
False, False, False, False, False])
Вы можете использовать np.where
или np.argwhere
, чтобы получить индекс совпадения (например, ). Вы можете настроить параметры atol
и rtol
для np.isclose
, чтобы установить порог для приблизительного совпадения.
Пояснение: если вы проделаете трюк as_strided
с data=np.arange(30)
, тогда data2d
будет:
array([[ 0, 1, 2, 3, 4, 5, 6],
[ 1, 2, 3, 4, 5, 6, 7],
[ 2, 3, 4, 5, 6, 7, 8],
...
[21, 22, 23, 24, 25, 26, 27],
[22, 23, 24, 25, 26, 27, 28],
[23, 24, 25, 26, 27, 28, 29]])
EDIT: это эффективный способ создать представление тех же данных с помощью скользящего windows, не требуя дополнительной памяти. Поиск в массиве numpy a[i, j]
находит адрес памяти как start_address + a.strides[0]*i + a.strides[1]*j
; установив для шагов значение (8, 8)
, где 8 - это размер значения с плавающей запятой, вы получите эффект скользящего окна. Поскольку разные элементы массива относятся к одной и той же памяти, лучше всего рассматривать массив, созданный таким образом, как доступный только для чтения.
РЕДАКТИРОВАТЬ: если вы хотите иметь метрику «оценки» c для качества совпадение, вы можете, например, сделать это:
>>> np.linalg.norm(data_2d - pat, axis=1)
array([17.5, 17.4, 13.3, 20.5, 12.9, 14.9, 19.7, 0. , 17.4, 13.8, 16.9,
13.7, 19. , 10.3, 18.3, 15.2, 10.9, 22.3, 13. , 21.8, 15.2, 24.5,
14.9, 20.7])
# (numbers rounded to reduce clutter)
ближе к нулю означает лучшее совпадение. Здесь norm
принимает длину вектора разности d=data-pat
, т.е. sqrt(d[0]**2 + ... + d[m-1]**2)
.
EDIT: если вас интересуют узоры, которые имеют такую же форму, но увеличены или уменьшены значение, вы можете сделать это:
# New dataset with two occurrences of the pattern: one scaled by a factor 1.1,
# one scaled 0.5 with a bit of noise added
data_mod = data*1.1
np.random.seed(1)
data_mod[16:16+m] = pat*0.5 + np.random.uniform(-0.5, 0.5, size=m)
data_2d_mod = np.lib.stride_tricks.as_strided(
data_mod, shape=(n-m+1, m), strides=(k, k))
# pat_inv: pseudoinverse of pat vector
pat_inv = 1/(pat @ pat) * pat
# cofs: fit coefficients, shape (n1,)
cofs = data_2d_mod @ pat_inv # fit coefficients, shape (n1,)
# sum of squared residuals, shape (n1,) - zero means perfect fit
ssqr = ((data_2d_mod - cofs.reshape(-1, 1) * pat)**2).sum(axis=1)
print(f'cofs:\n{np.around(cofs, 2)}')
print(f'ssqr:\n{np.around(ssqr, 1)}')
Результат:
cofs:
[-0.38 -0.14 0.4 -0.54 0.59 0.36 -0.48 1.1 -0.33 0.12 -0.06 0.18
-0.21 0.23 0.22 -0.33 0.52 -0.2 0.22 -0.35 0.6 -0.91 0.92 0.01]
ssqr:
[ 81.6 161.8 147.4 155.1 167.3 196.1 138.6 0. 97.8 103.5 85.9 59.3
57.1 54.9 58.3 29.2 0.7 198.7 217.4 201.9 266.3 235.1 242.8 361.9]
Вы видите, что cofs[7] == 1.1
, что означает, что шаблон нужно было масштабировать с коэффициентом 1,1 для соответствующих данных окно для наилучшего вписывания. Подгонка была идеальной, что видно по ssqr[7] == 0
. Он также находит другой, с cofs[16] == 0.52
(близко к ожидаемому значению 0,5) и ssqr[16] == 0.7
.
Другой пример: cofs[21]==-0.91
и ssqr[12]==235.1
. Это означает, что data_mod[12:19]
чем-то напоминает паттерн, но в инвертированном виде (поменяны местами положительные и отрицательные). Это зависит от того, что вы хотите делать с данными; скорее всего, вы захотите посмотреть на cofs
значения в диапазоне от 0,5 до 2: ваш шаблон поиска может встречаться в данных в 2 раза больше или меньше. Это должно сочетаться с достаточно маленькими ssqr
значениями.
Здесь вы видите три возможных совпадения на графике:
данные с совпадениями с образцом
Если вы используете ssqr
как метрику c, имейте в виду, что серия нулей во входных данных приведет к cofs=0
и ssqr=0
.
Рассмотрите возможность использования np.sqrt(ssqr/m)/np.abs(cofs)
в качестве метри c вместо этого по двум причинам. (1) он будет соответствовать в соответствии с относительной ошибкой и приведет к NaN
значениям в случае нулевого ввода. (2) это более интуитивно понятно; если значение равно 0,5, это означает, что точки данных отклоняются примерно на 0,5 от значений шаблона. Вот значения для этого метри c с использованием тех же данных примера:
[ 9.1 35.3 11.6 8.8 8.3 14.8 9.4 0. 11.4 33.3 55.9 16.4
13.9 12.1 12.9 6.2 0.6 27.2 25.4 15.2 10.4 6.4 6.4 482.5]
Для совпадения в data_mod[21:28]
разница c составляет 6,4, что примерно соответствует различиям. как видно на сюжете.