이것저것 코딩하는 블로그

[NLP] keras를 이용하여 CNN-LSTM 구현하고 실험하기 (Text Classification) 본문

Natural Language Process

[NLP] keras를 이용하여 CNN-LSTM 구현하고 실험하기 (Text Classification)

2021. 5. 18. 11:51

keras에서 제공하는 Convolutional Network와 LSTM을 이용해 CNN-LSTM을 구현하고 데이터셋에 대해 정확도를 실험해보고자 한다. 본 포스팅에 사용되는 모든 코드는 https://github.com/jasonwei20/eda_nlp 의 코드를 참고한 것이다.

 

1. 모델 쌓기

 

def build_cnn_lstm(sentence_length, word2vec_len, num_classes):
    model = Sequential()
    model.add(layers.convolutional.Convolution1D(128, 3, padding='same', strides=1))
    model.add(Activation('relu'))
    model.add(layers.convolutional.MaxPooling1D(pool_size=2))
    model.add(LSTM(64, recurrent_dropout=0.5))
    model.add(Dropout(0.5))
    model.add(Dense(num_classes, activation='softmax'))

    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

    return model

layer을 쌓을 sequence를 선언한 다음, Convolution layer을 삽입한다. padding은 same, stride는 1을 적용한다. convolution1D인 이유는 text data이기 때문이다. activation layer과 maxpooling layer을 삽입하여 CNN 구조를 완성한다. 이후 LSTM 구조를 적용하여 CNN-LSTM 구조를 완성한다. Classification Task이므로 Loss는 cross-entropy loss를 사용했다.

 

2. Data process

 

1) word2vec.p

# get the pickle file for the word2vec so you don't have to load the entire huge file each time
def gen_vocab_dicts(folder, output_pickle_path, huge_word2vec):
    vocab = set()
    text_embeddings = open(huge_word2vec, 'r').readlines()
    word2vec = {}

    # get all the vocab
    all_txt_paths = get_all_txt_paths(folder)
    print(all_txt_paths)

    # loop through each text file
    for txt_path in all_txt_paths:

        # get all the words
        try:
            all_lines = open(txt_path, "r").readlines()
            for line in all_lines:
                words = line[:-1].split(' ')
                for word in words:
                    vocab.add(word)
        except:
            print(txt_path, "has an error")

    print(len(vocab), "unique words found")

    # load the word embeddings, and only add the word to the dictionary if we need it
    for line in text_embeddings:
        items = line.split(' ')
        word = items[0]
        if word in vocab:
            vec = items[1:]
            word2vec[word] = np.asarray(vec, dtype='float32')
    print(len(word2vec), "matches between unique words and word2vec dictionary")

    pickle.dump(word2vec, open(output_pickle_path, 'wb'))
    print("dictionaries outputted to", output_pickle_path)

주어진 데이터셋에 사용할 word2vec file을 만드는 과정이다. 모든 unique한 단어들을 vocab에 저장한 뒤 미리 주어진 text embedding에서 vocab에 있는 단어만 dictionary에 추가하여 pickle 라이브러리를 이용해 주어진 데이터셋에 대한 word2vec.p 파일을 완성하고 저장한다. text embedding file은 https://nlp.stanford.edu/projects/glove/ 에서 가져와서 사용했다.

 

2) train data process

 

# getting the x and y inputs in numpy array form from the text file
def get_x_y(train_txt, num_classes, word2vec_len, input_size, word2vec, percent_dataset):
    # read in lines
    train_lines = open(train_txt, 'r').readlines()
    shuffle(train_lines)
    train_lines = train_lines[:int(percent_dataset * len(train_lines))]
    num_lines = len(train_lines)

    # initialize x and y matrix
    x_matrix = None
    y_matrix = None

    try:
        x_matrix = np.zeros((num_lines, input_size, word2vec_len))
    except:
        print("Error!", num_lines, input_size, word2vec_len)
    y_matrix = np.zeros((num_lines, num_classes))

    # insert values
    for i, line in enumerate(train_lines):

        parts = line[:-1].split('\t')
        label = int((float)(parts[0]))
        sentence = parts[1]


        # insert x
        words = sentence.split(' ')
        words = words[:x_matrix.shape[1]]  # cut off if too long
        for j, word in enumerate(words):
            if word in word2vec:
                x_matrix[i, j, :] = word2vec[word]

        # insert y
        y_matrix[i][label] = 1.0

    return x_matrix, y_matrix

주어진 데이터셋이 label, text 순서로 이루어져 있고 그 구분점이 \t라는 가정 하에 돌아가는 코드이다. label은 y_matrix에, text는 x_matrix에 word2vec file을 통해 vector로 변환한 형태로 삽입한다. 

 

3. run the model!

 

def run_model(train_file, test_file, num_classes, input_size, percent_dataset, word2vec):

	#initialize model
	model = build_cnn_lstm(input_size, word2vec_len, num_classes)

	#load data
	train_x, train_y = get_x_y(train_file, num_classes, word2vec_len, input_size, word2vec, percent_dataset)
	test_x, test_y = get_x_y(test_file, num_classes, word2vec_len, input_size, word2vec, 1)

	#implement early stopping
	callbacks = [EarlyStopping(monitor='val_loss', patience=3)]

	#train model
	model.fit(	train_x, 
				train_y, 
				epochs=100000, 
				callbacks=callbacks,
				validation_split=0.1, 
				batch_size=1024, 
				shuffle=True, 
				verbose=0)
	#model.save('checkpoints/lol')
	#model = load_model('checkpoints/lol')

	#evaluate model
	y_pred = model.predict(test_x)
	test_y_cat = one_hot_to_categorical(test_y)
	y_pred_cat = one_hot_to_categorical(y_pred)
	acc = accuracy_score(test_y_cat, y_pred_cat)

	#clean memory???
	train_x, train_y = None, None
	gc.collect()

	#return the accuracy
	#print("data with shape:", train_x.shape, train_y.shape, 'train=', train_file, 'test=', test_file, 'with fraction', percent_dataset, 'had acc', acc)
	return acc

1. 의 코드로 모델을 쌓고, 2. 의 get_x_y를 통해 train data와 test data를 가공한 후에 train data를 model.fit으로 훈련시킨다. 이후 test data로 accuracy를 계산하여 반환한다. 훈련된 파라미터를 저장하고 싶다면 model.save의 주석 처리를 지우고 저장할 경로를 입력하면 된다. 저장한 모델로 결과만 내고 싶다면 위를 다 지우고 model.load의 주석을 지운 뒤 괄호에는 모델이 저장된 경로를 불러오면 된다. 

 

acc = run_model(train_path, test_path, num_classes, input_size, 1, word2vec)

train file은 위 github 주소에 있는 sst-2 data를 추천한다. 언급한 대로 정제가 되어있는 데이터이다. 

이제 직접 모델을 훈련하고 정확도를 구해보자!

Comments