juooo1117

Movie genres - Clustering practice with NMF 본문

Artificial Intelligence

Movie genres - Clustering practice with NMF

Hyo__ni 2023. 12. 14. 10:52

Clustering with NMF(Non-negative factorization)

Document Classification

참고 블로그 : https://www.davidsbatista.net/blog/2017/04/01/document_classification/

  -  classifiy a document into a set of pre-defined categories using different supervised classifiers and text representations.

  -  Only use the plot of the movie and the genre on which the movie is classified.

  -  Dataset: IMDB datasets of movie, genres.list & plot.list files form a mirror FTP → parse files in order to associate the titles, plots, and genres → movies_genres.csv (containing the plot and the genres associated to each movie)

 

Pre-processing and cleaning

117,352 movies and each of them is associated with 28 possible genres in datasets

The genres columns simply contain a 1 or 0 depending on whether the movie is classified into that particular genre or not.(multi-label binary mask is already provided in this file)

 

데이터셋 안에는 아래와 같은 분포로 28가지 장르에 해당되는 영화가 있는 것을 확인하였다.

One thing that notice when working with this dataset is that there are plots written in different languages.

⇒ langdetect 기능을 이용해서 영어로 어떤 언어로 작성되어 있는지 분류해서, 영어(en)로 작성된 것들만 사용하자.

from langdetect import detect

df['plot_lang'] = df['plot'].apply(lambda text: detect(text))
df['plot_lang'].value_counts()

# en  117196 / nl     120 / de      14 / it       6 / da       5
# sv       2 / es       2 / no       2 / fr       2 / pt       2 / hu       1

 

Vector Representation and Classification

text에 해당하는 'plot' column만을 사용해서 'docs' list로 만들고, punctuation & 불용어 제거 & 토큰화 작업을 거친다. 

이후에 CountVectorizer 기능을 이용해서 bow(bag of words)로 만든다. 즉, 단어들을 embedding vector 공간에 하나씩 숫자로 맵핑시켜주는 것이다.

bow_transformer = CountVectorizer(analyzer=text_process).fit(docs)
len(bow_transformer.vocabulary_)    # 218879 -> 개의 vocabulary 자리가 있는것
# docs[0]에 해당하는 문서에 있는 단어들의 맵핑된 숫자를 확인해 보자
bow_0 = bow_transformer.transform([docs[0]])

 

Transformation to Doc-Term Matrix

위 과정을 거치면서 docs는 'document - term matrix' 형태로 수치화 되었다.

docs.shape → (117196, 218879) 이므로, 117196개의 text 데이터와 vectorization을 거쳐서 만들어진 218879개의 단어공간이 있는 것이다.

docs = bow_transformer.transform(docs)
print('Shape of Sparse Matrix: ', docs.shape)  # (117196, 218879)

NMF Model 에 넣기 위해서 'Term - document matrix' 형태로 순서를 바꾸어 주어야 한다. 즉, 행:단어 / 열:문서 의 matrix로 변형하는 것

따라서 .transpose() 를 이용해서 transposed matrix 형태로 바꾸어 주자! csr_matrix(compresed sparse row matrix) 을 csc_matrix( - column matrix)로 바꾸어 주는 것

type(docs)                     # scipy.sparse._csr.csr_matrix

docs_trans = docs.transpose()
type(docs_trans)               # scipy.sparse._csc.csc_matrix
print('Shape of Sparse Matrix: ', docs_trans.shape)   # (218879, 117196)

 

NMF Model training

NMF model을 이용해서 문서를 클러스터링 한다. 이 때 처음 살펴보았던 영화의 장르 카테고리는 총 27개 이므로 n_components는 27개로 지정해 주었다.

H.shape은 (27, 117196) 으로, 각각 class 개수와 document 개수에 해당한다.

from sklearn.decomposition import NMF
model = NMF(n_components=27)

W = model.fit_transform(docs_trans)
H = model.components_
H.shape    # (class 개수, documnet 개수)

 

전체 문서 117196개 각각에서 best score에 해당하는 클러스터의 카테고리가 무엇인지 확인해 보자. 즉, 각각의 문서마다 어떤 category genre의 클러스터가 가장 높은 값으로 매겨졌는지를 확인해 보는 것이다.

이렇게 각각의 문서별로 가장 높은 확률로 예측된 class(;장르)를 'pred_labels'에 저장한다.

pred_labels = H.argmax(axis=0)
len(pred_labels)    # 117196
pred_labels         # array([ 9,  3,  6, ...,  4, 26,  8])

 

# H values of doc0, doc1, doc2 (각 문서(column)에서 가장 높은 확률로 예측한 장르의 category값(row)은 분홍색과 같다.)

confusion matrix를 이용해서 NMF로 예측한 category 값과 실제 category가 얼마나 일치하는지 확인해 보자.

data_y = data_df.drop(['title', 'plot', 'plot_lang'], axis=1).to_numpy()
len(data_y)                           # 117196
target = list(data_y.argmax(axis=1))  # category information - 정답 matrix

from sklearn.metrics import confusion_matrix
pd.DataFrame(confusion_matrix(target, pred_labels))

 

confusion matrix는 (target, pred_labels) 형태이며, 영화 장르 category가 총 27개이기 때문에 행열은 27x27로 구성되어 있다.

e.g. 실제로는 '0'이라는 genre category를 갖는 데이터가 '2'라는 category에 1061개가 들어가 있다(예측한 값).

best label로 매칭했을 때 전체 클러스터링 결과가 어떤지 수치로 확인해 보자 ( → argmax label로 매칭했을 때의 성능)

맞게 예측한 데이터와 그 비율은 각각 31436개, 0.268이다.

best_labels = list(cmatrix.argmax(axis=0))

i = 0
sum_result = 0
for c in cmatrix.transpose():
  print(c)
  print('label:', best_labels[i])
  print('value:', c[best_labels[i]])
  sum_result += c[best_labels[i]]
  i += 1

print('sum_result:', sum_result)   # 31436
sum_result / len(data_y)           # 0.268

 

[Practice Code]

https://github.com/juooo1117/practice_AI_Learning/blob/main/text_classification/MovieGenres_clustering_NMF.ipynb