Классификация сигналов сердцебиения на электрокардиограмме (ЭКГ)
Имея набор изображений полос ЭКГ, обученный человеческий глаз может найти местоположение V-биения на каждом изображении. На графике ЭКГ регистрируется V-биение во время преждевременного сокращения желудочков в сердцебиении.
Может ли машина его распознать?
В этой статье объясняется, что я сделал, чтобы обучить модель машинного обучения определять форму V-биения. Но перед этим изображения необходимо предварительно обработать, чтобы определить местонахождение сигналов сердцебиения и извлечь данные. Имеется 540 изображений поездов (для обучения и проверки) и 180 тестовых изображений.
Изучите набор данных
Сначала я проверяю каждое изображение, чтобы увидеть, как выглядит полоса ЭКГ, используя OpenCV. Каждое изображение занимает около 20 секунд слева направо и имеет размер 7522 x 750 пикселей (визуализируйте это как координаты x и y) с третьим измерением для обозначения цвета RGB.
import cv2 image = cv2.imread('a3_45.png') image.shape
Пожалуйста, обратитесь к кодам Python со встроенными комментариями на моем GitHub.
Ниже приведена простая функция построения графика для визуализации сигнала ЭКГ:
def show_graph(x_list, y_list, width, height): plt.figure(figsize = [width, height]) plt.scatter(x_list, y_list, marker='.', s=5) plt.show() return
С помощью этой функции черные пиксели в файле изображения могут быть извлечены в список x-координат и соответствующий список y-координат:
x_list, y_list = [], [] for x in np.arange(0, 7522, 1): for y in np.arange(0, 750, 1): if np.all(image[y][x] == (0, 0, 0)): x_list.append(x) y_list.append(750-y) show_graph(x_list, y_list, 18, 3)
Обратите внимание, что первый пиксель в верхнем левом углу белый, а последний пиксель в правом нижнем углу также белый.
print(image[0][0]) # coordinate format is [y][x] print(image[749][7521])
Вместо стандартной последовательности красный-зеленый-синий (RGB) OpenCV использует массив десятичных кодов Синий-зеленый-красный, поэтому
[255, 0, 0] синий,
[0, 255, 0] зеленый,
[0, 0, 255] красный,
[255, 255, 255] белый,
[0, 0, 0 ] черный.
V-биения отмечены темно-зеленым цветом [0, 128, 0]. Я использовал функцию Пипетка в Microsoft Powerpoint, чтобы найти десятичный код цвета на изображении.
Набор данных TRAIN: обработка изображений, извлечение признаков
Ниже функция «locate_pos» используется для определения положения сигналов биений («V» темно-зеленым, «N» синим). Он сканирует от y_level = 42 до 100, чтобы извлечь первый пиксель указанного цвета, который является координатой x сигнала биений.
def locate_pos(image, color): position_list = [] y_level = 42 while len(position_list) == 0 and y_level < 100: x=100 while x<7422: if np.all(image[y_level][x] == color): position_list.append(x) x += 25 x += 1 y_level+=2 return position_list
После определения координаты x сигнала биений задается окно размером 160 на 750 пикселей для извлечения сигнала биений.
Для каждой координаты x функция «extract_feat» извлекает 1 координату y. Эти 160 y-координат становятся основными характеристиками модели машинного обучения.
def extract_feat(image, begin, end): x_list, y_list = [], [0] # boundary padding add '0' for x in np.arange(begin, end, 1): x_list.append(x-begin) for y in np.arange(0, 750, 1): if np.all(image[y][x] == (0, 0, 0)): y_list.append(750-y) break if y==749: y_list.append(y_list[x-begin]) y_list.pop(0) # remove boundary padding '0' show_graph(x_list, y_list, 2, 2) return y_list
Feature Engineering: для создания новых полезных функций
Чтобы улучшить модель, были добавлены 16 дополнительных функций:
1. Разделить сигнал биений на 4 квадранта
2. Найти минимальное, максимальное, среднее, медианное значение для каждого квадранта.
Последовательность сигналов также может быть зафиксирована путем объединения 2 квадрантов, таким образом формируя дополнительные 12 функций:
1. Разделить сигнал биений на 4 квадранта
2. Объединить 2 последовательных квадранта, чтобы получилось 3 сегмента
3. Найдите минимум, максимум, среднее, медианное значение для каждого сегмента.
from statistics import mean, median def find_stats(y_list, begin, end): temp = [] for i in np.arange(begin, end, 1): temp.append(y_list[i]) return [min(temp), mean(temp), median(temp), max(temp)]
После определения вышеупомянутой функции «find_stats» функция «extract_feat» может быть добавлена для включения дополнительных функций из сводной статистики:
def extract_feat(image, begin, end): x_list, y_list = [], [0] # boundary padding add '0' for x in np.arange(begin, end, 1): x_list.append(x-begin) for y in np.arange(0, 750, 1): if np.all(image[y][x] == (0, 0, 0)): y_list.append(750-y) break if y==749: y_list.append(y_list[x-begin]) y_list.pop(0) # remove boundary padding '0' y_list.extend(find_stats(y_list, 0, 40)) # quardrant 1 y_list.extend(find_stats(y_list, 40, 80)) # quardrant 2 y_list.extend(find_stats(y_list, 80, 120)) # quardrant 3 y_list.extend(find_stats(y_list, 120, 160)) # quardrant 4 y_list.extend(find_stats(y_list, 0, 80)) # segment 1 y_list.extend(find_stats(y_list, 40, 120)) # segment 2 y_list.extend(find_stats(y_list, 80, 160)) # segment 3 return y_list
Моему верному ноутбуку потребовалось около получаса, чтобы извлечь элементы из 540 изображений поездов. Есть 1113 V-битов и 2098 других долей, помеченных как «N», таким образом, общие данные поезда содержат 3211 строк.
train_X, train_y = [], [] imagePaths = sorted(list(paths.list_images('train'))) for imagePath in imagePaths: image = cv2.imread(imagePath) position_list = locate_pos(image, (0, 128, 0)) # V count = len(position_list) for i in range(count): train_X.append(extract_feat(image, position_list[i]-70, position_list[i]+90)) train_y.append(1) position_list = locate_pos(image, (255, 0, 0)) # N for i in range(count): train_X.append(extract_feat(image, position_list[i]-70, position_list[i]+90)) train_y.append(0)
Модель: модель поезда / подгонки, гиперпараметр настройки
Я выбрал модель логистической регрессии, которая отлично подходит для бинарной классификации.
Пожалуйста, обратитесь к кодам Python со встроенными комментариями на моем GitHub.
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(train_X, train_y, test_size=.2, random_state=SEED) logit = LogisticRegression(C = 0.001) logit.fit(X_train, y_train) y_pred = (logit.predict_proba(X_test)[:,1] >= 0.026) print(f1_score(y_test, y_pred)) print(confusion_matrix(y_test, y_pred))
С разделением поезд / тест 80/20 модель была настроена с C = 0,001 и оптимальным порогом 0,026, используя F1-оценку в качестве метрики измерения. Достигнутая точность составляет 100% как для обучения, так и для проверки. Матрица путаницы выглядит действительно хорошо, поскольку все прогнозы верны.
С такой хорошей моделью было бы очень обидно ее потерять.
import pickle pickle.dump(logit, open('logistic_model.pickle', 'wb'))
Набор данных TEST: обработка изображений, прогнозирование с использованием модели
Такая же обработка изображений применяется к тестовым изображениям. На этот раз синий "?" символы должны быть расположены для извлечения сигнала биений. Необходимо выделить и спрогнозировать около 5075 сигналов биений, и это заняло около 40 минут.
filename_list = [] location_list = []imagePaths = sorted(list(paths.list_images('test'))) for imagePath in imagePaths: image = cv2.imread(imagePath) filename_list.append(imagePath) position_list = locate_pos(image, (255, 0, 0)) count = len(position_list) test_X = [] if count > 0: for i in range(count): test_X.append(extract_feat(image, position_list[i]-75, position_list[i]+85)) y_pred = logit.predict(test_X) timing = [] for i in range(len(y_pred)): if y_pred[i] == 1: timing.append((position_list[i]-36)*(20/7450)) location_list.append(timing_list)
Большую часть времени я потратил на эту задачу, чтобы подготовить и обработать изображения, извлечь данные и преобразовать их в функции. Наконец, результаты теста сохраняются в файл csv:
df = pd.DataFrame() df['filename'] = filename_list df['location(sec)'] = location_list df.to_csv('test_results.csv', index=False)
Заключение
Сигнал V-биений можно легко идентифицировать, если различные характеристики его формы можно преобразовать в полезное числовое / векторное представление.
Модели машинного обучения (ML) становятся все более распространенными в отрасли медицинских технологий, помогая нам выполнять трудоемкие задачи точно и более эффективно.
Коды Python для вышеупомянутого анализа доступны на моем GitHub, не стесняйтесь ссылаться на них.
Https://github.com/JNYH/ecg_vbeat
Спасибо за чтение.