목차
(1) 사전 학습을 위한 cs231n강의
(2) 학습에 사용할 데이터셋
(3) 데이터 증강에 대한 설명
(4) CNN모델 뼈대 구축
※ 참고: 1세대 AI의료기업 왓슨, 그리고 뷰노, 루닛, JLK
(1) 사전학습을 위한 cs231n 강의
Lecture2: Image Classification Pipeline
https://doctorham.tistory.com/37
https://doctorham.tistory.com/38
Lecture3: Loss Functions and Optimization
https://doctorham.tistory.com/39
Lecture4: Backpropagation and Neural Networks
https://doctorham.tistory.com/41
※ Practice: cs231 assignment1 - Two Layer Neural Net
(2) 학습에 사용할 데이터 셋
https://www.kaggle.com/nih-chest-xrays/data
(3) 데이터 전처리방법에 대한 설명: dealing with a small dataset
데이터 증강에 사용할 파이썬 라이브러리: Albumentations (https://albumentations.ai/), imgaug (https://github.com/aleju/imgaug)
“ 가장 좋은 방법은 학습 과정에서 대량의 데이터를 학습시키는 것이다(5). 학습용 데이터는 데이터의 다양한 특징들을 대부분 포함하고 있어야 하며, 구별하고자 하는 특징들이 골고루 분포하고 있어야 한다. 하지만, 의료영상은 개인 정보 문제 등으로 인해 다량의 데이터를 수집하기가 어렵다. 또한, 정답 데이터(레이블)를 만들기 위해 의료인의 노동이 필요하기 때문에 많은 비용이 든다는 어려움 있다(6, 7), 또한, 데이터를 수집하는 과정에서 각 질환의 유병률 등에 따라 수집된 데이터의 균형이 맞지 않을 가능성이 높다(8). "
딥러닝 기반 의료영상 분석을 위한 데이터 증강 기법 - 김민규, 배현진 (울산대학교 의과대학 융합학과)
* 데이터 증강 시점에 따른 증강 방법 분류
Off-line Augmentation: 데이터 학습 전에 Transformation을 수행하는 방법. 작은 데이터 셋에 선호되는 방법이다.
On-line Augmentation(Augmentation on the fly): mini-batch를 하면서 data를 feed하기 직전에 데이터를 증강(Transformation)하는 방법. 큰 데이터 셋을 이용할 때 선호되는 방법이다.
기본적이고 많이 사용되는 데이터 증강 방법(Basic Data Augmentation)
1. 뒤집기(수직, 수평)
flipped = tf.image.flip_left_right(image)
visualize(image, flipped)
2. 돌리기
3. Scaling하기(outward-larger or inward-smaller): outward의 경우 이미지의 부분만 사용하게 되고, inward의 경우에는 이미지 외부를 추측하여 채워넣는 과정이 필요하다.
# 이미지를 돌리고 스케일링하기
data_augmentation = tf.keras.Sequential([
layers.experimental.preprocessing.RandomFlip("horizontal_and_vertical"),
layers.experimental.preprocessing.RandomRotation(0.2),
])
# Add the image to a batch
image = tf.expand_dims(image, 0)
plt.figure(figsize=(10, 10))
for i in range(9):
augmented_image = data_augmentation(image)
ax = plt.subplot(3, 3, i + 1)
plt.imshow(augmented_image[0])
plt.axis("off")
4. 이미지 크롭하고 원하는 사이즈로 조정하기
cropped = tf.image.central_crop(image, central_fraction=0.5)
visualize(image,cropped)
5. 이미지 채도 변경하기
saturated = tf.image.adjust_saturation(image, 3)
visualize(image, saturated)
6. Translation: X축이나 Y축으로 이미지를 이동하는 방법이다.
def Translate(data, sampling_rate, shift_max, shift_direction):
shift = np.random.randint(sampling_rate * shift_max+1)
if shift_direction == 'right':
shift = -shift
elif shift_direction == 'both':
direction = np.random.randint(0, 2)
if direction == 1:
shift = -shift
augmented_data = np.roll(data, shift)
# Set to silence for heading/ tailing
if shift > 0:
augmented_data[:shift] = 0
else:
augmented_data[shift:] = 0
return augmented_data
7. 이미지에 가우시안 노이즈 주기: Over-fitting은 주로 Neural Network이 High Frequency의 이미지를 학습하는 과정에서 생긴다. 가우시안 노이즈는 high-frequency, low-frequency부분을 공평하게 왜곡하여 모델의 성능을 향상시킬 수 있다. 가우시안 노이즈 대신 salt-and-pepper noise를 사용할 수도 있다.
def noising(data,noise_factor):
noise = np.random.randn(len(data))
augmented_data = data + noise_factor * noise
# Cast back to same data type
augmented_data = augmented_data.astype(type(data[0]))
return augmented_data
조금 더 복잡한 방법의 데이터 증강 방법(Advanced Data Augmentation)
GAN (구현 방법은 다음 포스팅에서 다룰 예정)
이미지 데이터를 증강하는 방법으로 가장 각광 받고 있는 것이 GAN이다.
이를 구현하는 쉬운 방법으로는 Neural style transfer이 있다. 이는 이미지의 texture, ambience, appearance(즉 style)를 캐치하여 이를 다른 컨텐츠와 섞는 방법이다. 유일한 단점은 있다면 결과 이미지가 비현실적일 수 있다는 것이다. 하지만 최근에는 Deep Photo Style Transfer와 같은 방법도 개발되어 이러한 단점을 효과적으로 보완하는 것이 가능하다.
데이터 증강을 함에 있어 주의할 점은 내가 사용하고자 하는 데이터에 맞는 증강 방법을 사용하는 것이다. 만약 자동차 이미지의 데이터를 증강하고자 한다면 자동차 판매를 하는 곳에서는 이미지를 좌우 반전시키기만 해도 모델의 성능이 크게 향상될 것이다. 그러나 만약 보험회사에서 이러한 이미지 데이터를 증강하고자 한다면 뒤집힌 자동차, 부서진 자동차의 이미지 또한 필요할 것이다. 따라서 사용자의 목적에 맞게 적절하게 데이터를 증강하는 것이 필요하다.
GAN은 기본적으로, 가짜 이미지를 만들어 내는 방법이기 때문에 모델의 신뢰도에 대해 의문을 제기할 수 있다. 이에 대해서는 다음 논문을 살펴보도록 한다.
" GAN의 학습에는 환자의 개인 정보가 담긴 의료데이터를 사용하긴 하나, 학습이 완료된 뒤에는 학습된 생성기만 떼어내어 영상을 생성해낸다. 생성기는 네트워크를 이루고 있는 가짜 영상을 생성하기 위해 학습된 가중치만 존재하므로, 환자의 개인 정보를 역으로 유추해낼 수 있는 방법은 없다. 따라서, GAN을 이용하여 생성한 데이터를 연구에 사용함에 있어 법적 윤리적 책임을 피할 수 있다."
딥러닝 기반 의료영상 분석을 위한 데이터 증강 기법 - 김민규, 배현진 (울산대학교 의과대학 융합학과)
하지만 GAN 방법에도 다음과 같은 한계점이 존재하므로 신중하게 사용해야 한다.
" 특히, GAN 기반의 증강 기법은 영상처리 기반의 데이터 증강 기법에 비해 주의를 기울여 사용해야 할 것으로 보인다. 이는 GAN의 한계 때문인데, 첫째로 생성자와 판별자가 상호적으로 조금씩 각자의 성능을 키워야 하지만 한쪽의 능력이 크게 우세해지면 더이상 학습을 하지 못한다. 또한, 학습 데이터의 전체 분포를 배우지 못하고 쉬운 패턴만 익혀서 생성할 수 있다. 생성자는 판별자를 속여서 가짜 데이터를 진짜 데이터로 판별하게 하는데, 진짜처럼 만들 수 있는 쉬운 영상만 만들어서 판별자에게 알려주게 되고, 판별자가 생성자를 수정하게 하기 위한 피드백을 주지 못한다. 또한, 앞서 소개한 CycleGAN과 같은 영상 변환의 경우에는 실제로 존재하지 않는 영상을 만들어 내기도 한다.
이와 같은 문제를 해결하기 위해 GAN으로 생성한 영상이 실제와 얼마나 유사한지 알아보기 위한 연구가 다양하게 진행되고 있다. 예를 들어, GAN으로 생성한 영상의 품질을 평가하기 위해 Mean Absolute Error, Peak Signal-to-Noise Ratio, Structural SIMilarity, 그리고 Inception Score 등 수학적 정의를 이용한 평가 지표가 개발되거나 시각 튜링 테스트 등이 실시되기도 한다. 하지만 유사성을 정량화하는 것에는 아직도 한계가 있다. 특히, 모드 붕괴(mode collapse)와 같은 이유로 GAN이 생성한 영상의 다양성을 정량적으로 측정할 수 있는 후속 연구가 필요하다. "
딥러닝 기반 의료영상 분석을 위한 데이터 증강 기법 - 김민규, 배현진 (울산대학교 의과대학 융합학과)
최신 데이터 증강 방법(참고)
Cutout: 학습 영상의 일부분을 무작위로 가리는 데이터 증강 기법
Mixup: 은 두 개의 서로 다른 클래스에 해당하는 데이터를 하나로 섞는 방식으로 데이터를 증강하는 기법
CutMix: 영상의 일부를 랜덤하게 자른 뒤 해당 영역에 다른 클래스의 영상을 떼어 붙이는 방식을 통해 데이터를 증강하는 기법이다
AugMix: 입력 영상에 대해 여러 번의 영상처리 기반 변환을 통한 증강 영상을 만들고, 이러한 증강 영상을 각기 다르게 여러 장 만들어 이 영상들을 픽셀 단위의 볼록 조합(convex combinations)을 통해 하나의 영상으로 합친다.
(4) Multi-Label Classification을 위한 CNN모델 뼈대 구축하기
* Multi-Label Classification이란?
하나의 데이터가 여러 클래스에 동시에 속할 가능성이 존재하는 분류 방법이다.
* Headless Model이란?
새 모델을 처음부터 빌드하고 훈련하는 대신 사전 훈련 된 모델을 사용할 수 있다. AlexNet, GoggleNet, ResNet, EfficientNet등의 Covolution계층을 사용할 수 있다. 여기에 Fully-Connected Layer를 임의로 추가하여 전체 모델을 완성할 수 있다. 이러한 headless모델은 TF Hub에서 다운로드 받아서 사용할 수 있다(https://tfhub.dev/) 라이브러리를 임포트 해 주어야 한다.
Tutorial: https://tensorflow.google.cn/tutorials/images/transfer_learning_with_hub
위의 사이트에서 학습된 모델을 사용하는 방법에 대한 튜토리얼을 제공하는 웹 페이지이다. 페이지에서 설명하는 대로 따라하면 기본적인 사용 방법을 익힐 수 있다.
다양한 모델이 존재하는 것을 확인할 수 있다.
이부분의 코드를 복사하여 flatten layer위에 넣어준다.
※CNN뼈대 구축(Baseline Code)
<1> 데이터 파이프라이닝
라이브러리 임포트 및 구글 드라이브 마운트
# 필요한 라이브러리 전부 임포트
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import os
from glob import glob
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
from tqdm import tqdm
from keras.preprocessing.image import ImageDataGenerator
from keras.applications.densenet import DenseNet121
from keras.layers import Dense, GlobalAveragePooling2D
from keras.models import Model
from keras.models import load_model
from keras.preprocessing.image import *
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import random
import io
import tensorflow as tf
from tensorflow import optimizers
from tensorflow import keras
from tensorflow.keras.utils import to_categorical
import tensorflow.keras.layers as L
import tensorflow_hub as hub
import shutil
from google.colab import drive
drive.mount('/content/drive')
이미지의 metadata가 들어있는 csv파일 다운로드
Data_Entry = pd.read_csv('/content/drive/MyDrive/GraduationProject/Data_Entry_2017.csv')
BBox_List = pd.read_csv('/content/drive/MyDrive/GraduationProject/BBox_List_2017.csv')
train_df = pd.read_csv('/content/drive/MyDrive/GraduationProject/train_df.csv')
csv파일에서 질병별로 이미지의 인덱스(이름)를 분류하기
# 각 Label별로 이미지를 분류
No_Finding = list(train_df[train_df['No Finding'] == 1]['Image Index'])
Pneumonia = list(train_df[train_df['Pneumonia'] == 1]['Image Index'])
Cardiomegaly = list(train_df[train_df['Cardiomegaly'] == 1]['Image Index'])
Emphysema = list(train_df[train_df['Emphysema'] == 1]['Image Index'])
Effusion = list(train_df[train_df['Effusion'] == 1]['Image Index'])
Hernia = list(train_df[train_df['Hernia'] == 1]['Image Index'])
Infiltration = list(train_df[train_df['Infiltration'] == 1]['Image Index'])
Mass = list(train_df[train_df['Mass'] == 1]['Image Index'])
Nodule = list(train_df[train_df['Nodule'] == 1]['Image Index'])
Pneumothorax = list(train_df[train_df['Pneumothorax'] == 1]['Image Index'])
Pleural_Thickening = list(train_df[train_df['Pleural_Thickening'] == 1]['Image Index'])
Fibrosis = list(train_df[train_df['Fibrosis'] == 1]['Image Index'])
Edema = list(train_df[train_df['Edema'] == 1]['Image Index'])
Consolidation = list(train_df[train_df['Consolidation'] == 1]['Image Index'])
소스폴더와, 복사할 폴더의 경로 설정하기
src1 = '/content/drive/MyDrive/GraduationProject/images/images_001'
src2 = '/content/drive/MyDrive/GraduationProject/images/images_002'
src3 = '/content/drive/MyDrive/GraduationProject/images/images_003'
src4 = '/content/drive/MyDrive/GraduationProject/images/images_004'
src5 = '/content/drive/MyDrive/GraduationProject/images/images_005'
src6 = '/content/drive/MyDrive/GraduationProject/images/images_006'
src7 = '/content/drive/MyDrive/GraduationProject/images/images_007'
src8 = '/content/drive/MyDrive/GraduationProject/images/images_008'
src9 = '/content/drive/MyDrive/GraduationProject/images/images_009'
src10 = '/content/drive/MyDrive/GraduationProject/images/images_010'
src11 = '/content/drive/MyDrive/GraduationProject/images/images_011'
src12 = '/content/drive/MyDrive/GraduationProject/images/images_012'
dst1 = '/content/drive/MyDrive/GraduationProject/train/No_Finding'
dst2 = '/content/drive/MyDrive/GraduationProject/train/Cardiomegaly'
dst3 = '/content/drive/MyDrive/GraduationProject/train/Emphysema'
dst4 = '/content/drive/MyDrive/GraduationProject/train/Effusion'
dst5 = '/content/drive/MyDrive/GraduationProject/train/Hernia'
dst6 = '/content/drive/MyDrive/GraduationProject/train/Infiltration'
dst7 = '/content/drive/MyDrive/GraduationProject/train/Mass'
dst8 = '/content/drive/MyDrive/GraduationProject/train/Nodule'
dst9 = '/content/drive/MyDrive/GraduationProject/train/Pneumothorax'
dst10 = '/content/drive/MyDrive/GraduationProject/train/Pleural_Thickening'
dst11 = '/content/drive/MyDrive/GraduationProject/train/Pneumonia'
dst12 = '/content/drive/MyDrive/GraduationProject/train/Fibrosis'
dst13 = '/content/drive/MyDrive/GraduationProject/train/Edema'
dst14 = '/content/drive/MyDrive/GraduationProject/train/Consolidation'
# 분류된 이미지 리스트의 마지막에 경로를 추가
No_Finding.append(dst1)
Cardiomegaly.append(dst2)
Emphysema.append(dst3)
Effusion.append(dst4)
Hernia.append(dst5)
Infiltration.append(dst6)
Mass.append(dst7)
Nodule.append(dst8)
Pneumothorax.append(dst9)
Pleural_Thickening.append(dst10)
Pneumonia.append(dst11)
Fibrosis.append(dst12)
Edema.append(dst13)
Consolidation.append(dst14)
# 질병별로 분류된 각 이미지 파일이름 리스트를 하나의 리스트에 넣어준다.
Diseases = [No_Finding, Cardiomegaly, Emphysema, Effusion, Hernia, Infiltration, Mass, Nodule, Pneumothorax, Pleural_Thickening, Pneumonia, Fibrosis, Edema, Consolidation]
# sorce폴더의 경로를 하나의 리스트로 모아준다.
src_folders = [src1, src2, src3, src4, src5, src6, src7, src8, src9, src10, src11, src12 ]
분류된 이미지 인덱스에 따라 이미지 파일 분류하기
for src in src_folders:
# No Finding
for disease in Diseases:
for file_name in disease:
if file_name == disease[-1]:
break
img_src = src + '/' + file_name
img_dst = disease[-1] + '/' + file_name
try:
shutil.copyfile(img_src, img_dst)
except FileNotFoundError:
continue
<2> 모델 구축
# 기본적인 변수 지정
batch_size = 8
img_height = 600
img_width = 600
# Image 데이터 로드하기
data_dir = '/content/drive/MyDrive/GraduationProject/train'
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset="training",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset="validation",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
# 모델 만들기
feature_extractor_model = "https://tfhub.dev/google/tf2-preview/mobilenet_v2/feature_vector/4"
feature_extractor_layer = hub.KerasLayer(
feature_extractor_model, input_shape=(224, 224, 3), trainable=False)
num_classes = 2
model = tf.keras.Sequential([
hub.KerasLayer("https://tfhub.dev/tensorflow/efficientnet/b7/feature-vector/1",
trainable=False), # Can be True, see below.
tf.keras.layers.Dense(num_classes, activation='softmax')
])
model.build([None, img_height, img_width, 3]) # Batch input shape.
model.summary()
model.compile(
optimizer=tf.keras.optimizers.Adam(),
loss=tf.keras.losses.SparseCategoricalCrossentropy(),
metrics=['acc'])
class CollectBatchStats(tf.keras.callbacks.Callback):
def __init__(self):
self.batch_losses = []
self.batch_acc = []
def on_train_batch_end(self, batch, logs=None):
self.batch_losses.append(logs['loss'])
self.batch_acc.append(logs['acc'])
self.model.reset_metrics()
batch_stats_callback = CollectBatchStats()
history = model.fit(train_ds,validation_data=val_ds,epochs=10)
(참고) 1세대 AI의료기업 왓슨, 그리고 뷰노, 루닛, JLK
왓슨은 잘 알려져있다시피 AI기술과 의학을 접목시킨 1세대 대표 주자이다. 그러나 왓슨은 다음과 같은 이유로 인해 쇠퇴하게 되고, 다음 세대 기업들이 왓슨의 목표를 기반으로 성장 해 나가고 있다.
"성균관의대 양광모 교수(삼성서울병원 건강의학본부)는 "기대했던 것보다 의사와의 의견 일치율이 떨어진다는 점이 시장에서 외면받는 요인으로 보인다"며 "정확도가 떨어져 의사는 물론 병원에서도 관심이 덜한 것 같다"고 말했다.
단국의대 최상규 교수(방사선종양학과)도 같은 의견을 제시했다. 최 교수는 "암을 치료하는 교수로서 처음에는 관심이 컸다. 그런데 사용해 본 의사들이 '생각보다 별로'라는 얘기를 종종 했다"며 "의사와의 의견 일치율이 떨어지면 왓슨을 써야 할 이유가 없어지는 것 아닌가"라고 반문했다. "
" 최상규 교수는 "왓슨은 대상국의 상황을 고려하지 못한다는 단점이 있다. 한국인의 특성을 제대로 반영하지 못하는 것은 물론, 한국어의 독특함도 제대로 읽어내지 못해 의견 일치율이 떨어지는 것 같다"며 "왓슨이 매력적이려면 우리나라 빅데이터가 어느 정도 반영돼야 할 것"이라고 말했다. "
(1) 뷰노
자체 딥러닝 엔진 뷰노 넷(VUNO Net)을 기반으로 최적의 의료 인공지능 솔루션을 자체 개발할 수 있는 역량을 보유한 기업이다. 글로벌 헬스케어 기업 및 의료 기관과의 전략적 파트너십 확대로 의료인공지능 솔루션에 대한 해외 마케팅 활동을 강화할 방침이다. 병리, 생체신호를 포함한 제품 파이프라인을 확대해 국내외 사업영역을 적극적으로 확장해 나갈 예정이다. 클라우드 기반으로 실사용량에 따라 과금되는 사용량비례(Pay-Per-Use) 과금 정책을 기본으로 한다. 보안 상 자체 서버를 보유한 의료기관을 대상으로는 설치형(on premise)형으로서 기간 단위로 과금하는 방식을 사용한다. 판매 방식도 자체적으로 구축한 영업조직이 직접 제품을 판매하는 것에서 더 나아가, 하드웨어 및 소프트웨어 의료기기 제품에 탑재해 판매할 수 있도록 기존의 의료기기 기업에게 판매하는 B2B 판매채널도 구축한 바 있다. 의료기기에 해당하는 제품 7종은 국내 식품의약품안전처로부터 인허가를 획득했고, 5종은 유럽 CE인증을 획득했다. 시장조사기관 마켓스앤마켓스(MnM)에 따르면 글로벌 AI 헬스케어 시장 규모는 2018년부터 연평균 50%씩 성장해 2025년 362억 달러(약 43조 원) 규모를 형성할 것으로 예상된다. 특히 코로나19의 장기화로 인한 비대면 의료 수요가 폭증하면서 전 세계적인 시장 규모도 가파르게 성장할 것으로 보이며, 국내 AI 헬스케어 시장 역시 연평균 성장률 45%로 2023년 약 2조 465억 원 규모를 기록할 것으로 전망, 해외 매출이라는 실질적인 성과로 이어진다면 주가에도 반영될 것으로 기대중이다.
(2) 루닛
루닛의 대표 제품은 '루닛 인사이트(Lunit INSIGHT)'다. 딥러닝 기반 인공지능 기술을 활용한 의료AI 제품이다. 두 종류가 있다. 하나는 흉부 X레이를 분석해 폐 관련 질환을 빠르고 정확히 진단하는데 도움을 주는 '루닛 인사이트 CXR', 다른 하나는 유방암 등이 의심되는 이상 부위를 알려주는 '루닛 인사이트 MMG'다.
"의료AI 기업이 많다. 어떤 비교우위를 갖고 있나" 라는 질문에는 아래와 같이 답변하였다.
"가장 큰 차이점은 정확도다. 우리 제품은 정확도가 높다. 제품 종류는 많지 않다. 의료 분야는 생명을 다룬다, 잘못하면 제로(0)가 될 수 있다. 정확도가 매우 중요하다. 정확하지 않으면 병원에서 쓰지 않는다. 의학 저널에 실린 논문을 보면 우리 회사가 경쟁사보다 월등히 많다. GE헬스케어가 최근 우리 제품을 선택 한 것도 이 때문이다. 검증된
AI 제품을 출시하기 위해 세계적으로 권위 있는 더랜셋디지털헬스(The Lancet Digital Health), JAMA네트워크오픈(JAMANetwork Open), CID(Clinical Infectious Diseases) 같은 학술지에 지속적으로 논문을 발행, 채택되고 있다. 우리 기본 철학은 특정분야를 깊게 파고, 다양한 의료 저널에 게재하고, 세계적으로 판매하는거다. 우리 제품은 국내 빅5 종합병원은 물론 1~3차 병원과 보건소에서 사용하고 있다. 해외서는 멕시코, 아랍에미리트연합, 중국, 태국, 대만 등에서 흉부 엑스레이 및 유방촬영술 영상 분석에 사용하고 있다."
(3) JLK
JLK에서 진행하는 흉부 X-ray진단 사업은 아래와 같다.
'바닥부터 시작하는 인공지능' 카테고리의 다른 글
[cs231n assignment1] Implementing Two-Layer Neural Network (0) | 2021.05.27 |
---|---|
[CS231N] Lecture4: Backpropagation and Neural Networks (1) 정리 (0) | 2021.05.17 |
[CS231N] Lecture3: Loss Functions and Optimization(1) 정리 (0) | 2021.04.28 |
[CS231N]Lecture2: Image Classification Pipeline(2) 정리 (0) | 2021.04.20 |
[CS231N] Lecture2: Image Classification Pipeline(1) 정리 (0) | 2021.04.17 |