Extractive#

This tutorial is available as an IPython notebook at Malaya/example/extractive-summarization.

[1]:
%%time
import malaya
from pprint import pprint
CPU times: user 3.91 s, sys: 2.45 s, total: 6.35 s
Wall time: 3.93 s
/home/husein/dev/malaya/malaya/tokenizer.py:214: FutureWarning: Possible nested set at position 3397
  self.tok = re.compile(r'({})'.format('|'.join(pipeline)))
/home/husein/dev/malaya/malaya/tokenizer.py:214: FutureWarning: Possible nested set at position 3927
  self.tok = re.compile(r'({})'.format('|'.join(pipeline)))
[2]:
isu_kerajaan = [
    'Kenyataan kontroversi Setiausaha Agung Barisan Nasional (BN), Datuk Seri Mohamed Nazri Aziz berhubung sekolah vernakular merupakan pandangan peribadi beliau',
    'Timbalan Presiden UMNO, Datuk Seri Mohamad Hasan berkata, kenyataan tersebut tidak mewakili pendirian serta pandangan UMNO \n\nkerana parti itu menghormati serta memahami keperluan sekolah vernakular dalam negara',
    '"Saya ingin menegaskan dua perkara penting',
    'Pertama pendirian beliau tersebut adalah pandangan peribadi yang tidak mewakili pendirian dan pandangan UMNO',
    '"Kedua UMNO sebagai sebuah parti sangat menghormati dan memahami keperluan sekolah vernakular di Malaysia',
    'UMNO berpendirian sekolah jenis ini perlu terus wujud di negara kita," katanya dalam satu kenyataan akhbar malam ini',
    'Mohamed Nazri semalam menjelaskan, kenyataannya mengenai sekolah jenis kebangsaan Cina dan Tamil baru-baru ini disalah petik pihak media',
    'Kata Nazri dalam kenyataannya itu, beliau menekankan bahawa semua pihak perlu menghormati hak orang Melayu dan bumiputera',
    'Mohamad yang menjalankan tugas-tugas Presiden UMNO berkata, UMNO konsisten dengan pendirian itu dalam mengiktiraf kepelbagaian bangsa dan etnik termasuk hak untuk beragama serta mendapat pendidikan',
    'Menurut beliau, persefahaman dan keupayaan meraikan kepelbagaian itu menjadi kelebihan dan kekuatan UMNO dan BN selama ini',
    'Kata beliau, komitmen UMNO dan BN berhubung perkara itu dapat dilihat dengan jelas dalam bentuk sokongan infrastruktur, pengiktirafan dan pemberian peruntukan yang diperlukan',
    '"Saya berharap isu ini tidak dipolitikkan secara tidak bertanggungjawab oleh mana-mana pihak terutama dengan cara yang tidak menggambarkan pendirian sebenar UMNO dan BN," katanya',
    'Beliau turut menegaskan Mohamed Nazri telah mengambil pertanggungjawaban dengan membuat penjelasan maksud sebenarnya ucapanny di Semenyih, Selangor tersebut',
]
[3]:
isu_string = '\n\n\n\nDUA legenda hebat dan ‘The living legend’ ini sudah memartabatkan bidang muzik sejak lebih tiga dekad lalu. Jika Datuk Zainal Abidin, 59, dikenali sebagai penyanyi yang memperjuangkan konsep ‘world music’, Datuk Sheila Majid, 55, pula lebih dikenali dengan irama jazz dan R&B.\n\nNamun, ada satu persamaan yang mengeratkan hubungan mereka kerana sama-sama mencintai bidang muzik sejak dulu.\n\nKetika ditemui dalam sesi fotografi yang diatur di Balai Berita, baru-baru ini, Zainal berkata, dia lebih ‘senior’ daripada Sheila kerana bermula dengan kumpulan Headwind sebelum menempa nama sebagai penyanyi solo.\n\n“Saya mula berkawan rapat dengan Sheila ketika sama-sama bernaung di bawah pengurusan Roslan Aziz Productions (RAP) selepas membina karier sebagai artis solo.\n\n“Namun, selepas tidak lagi bernaung di bawah RAP, kami juga membawa haluan karier seni masing-masing selepas itu,” katanya.\n\nJusteru katanya, dia memang menanti peluang berganding dengan Sheila dalam satu konsert.\n\nPenyanyi yang popular dengan lagu Hijau dan Ikhlas Tapi Jauh itu mengakui mereka memang ada keserasian ketika bergandingan kerana membesar pada era muzik yang sama.\n\n“Kami memang meminati bidang muzik dan saling memahami antara satu sama lain. Mungkin kerana kami berdua sudah berada pada tahap di puncak karier muzik masing-masing.\n\n“Saya bersama Sheila serta Datuk Afdlin Shauki akan terbabit dalam satu segmen yang ditetapkan.\n\n“Selain persembahan solo, saya juga berduet dengan Sheila dan Afdlin dalam segmen interaktif ini. Setiap penyanyi akan menyampaikan enam hingga tujuh lagu setiap seorang sepanjang konsert yang berlangsung tiga hari ini,” katanya.\n\nBagi Sheila pula, dia memang ada terbabit dengan beberapa persembahan bersama Zainal cuma tiada publisiti ketika itu.\n\n“Kami pernah terbabit dengan showcase dan majlis korporat sebelum ini. Selain itu, Zainal juga terbabit dengan Konsert Legenda yang membabitkan jelajah empat lokasi sebelum ini.\n\n“Sebab itu, saya sukar menolak untuk bekerjasama dengannya dalam Festival KL Jamm yang dianjurkan buat julung kali dan berkongsi pentas dalam satu konsert bertaraf antarabangsa,” katanya.\n\n\n\nFESTIVAL KL Jamm bakal menggabungkan pelbagai genre muzik seperti rock, hip hop, jazz dan pop dengan lebih 100 persembahan, 20 ‘showcase’ dan pameran.\n\nKonsert berbayar\n\n\n\nMewakili golongan anak seni, Sheila menaruh harapan semoga Festival KL Jamm akan menjadi platform buat artis yang sudah ada nama dan artis muda untuk membuat persembahan, sekali gus sama-sama memartabatkan industri muzik tempatan.\n\nMenurut Sheila, dia juga mencadangkan lebih banyak tempat diwujudkan untuk menggalakkan artis muda membuat persembahan, sekali gus menggilap bakat mereka.\n\n“Berbanding pada zaman saya dulu, artis muda sekarang tidak banyak tempat khusus untuk mereka menyanyi dan menonjolkan bakat di tempat awam.\n\n“Rata-rata hanya sekadar menyanyi di laman Instagram dan cuma dikenali menerusi satu lagu. Justeru, bagaimana mereka mahu buat showcase kalau hanya dikenali dengan satu lagu?” katanya.\n\nPada masa sama, Sheila juga merayu peminat tempatan untuk sama-sama memberi sokongan pada penganjuran festival KL Jamm sekali gus mencapai objektifnya.\n\n“Peminat perlu ubah persepsi negatif mereka dengan menganggap persembahan artis tempatan tidak bagus.\n\n“Kemasukan artis luar juga perlu dilihat dari sudut yang positif kerana kita perlu belajar bagaimana untuk menjadi bagus seperti mereka,” katanya.\n\nSementara itu, Zainal pula berharap festival itu akan mendidik orang ramai untuk menonton konsert berbayar serta memberi sokongan pada artis tempatan.\n\n“Ramai yang hanya meminati artis tempatan tetapi tidak mahu mengeluarkan sedikit wang untuk membeli tiket konsert mereka.\n\n“Sedangkan artis juga menyanyi untuk kerjaya dan ia juga punca pendapatan bagi menyara hidup,” katanya.\n\nFestival KL Jamm bakal menghimpunkan barisan artis tempatan baru dan nama besar dalam konsert iaitu Datuk Ramli Sarip, Datuk Afdlin Shauki, Zamani, Amelina, Radhi OAG, Dr Burn, Santesh, Rabbit Mac, Sheezy, kumpulan Bunkface, Ruffedge, Pot Innuendo, artis dari Kartel (Joe Flizzow, Sona One, Ila Damia, Yung Raja, Faris Jabba dan Abu Bakarxli) dan Malaysia Pasangge (artis India tempatan).\n\nManakala, artis antarabangsa pula membabitkan J Arie (Hong Kong), NCT Dream (Korea Selatan) dan DJ Sura (Korea Selatan).\n\nKL Jamm dianjurkan Music Unlimited International Sdn Bhd dan bakal menggabungkan pelbagai genre muzik seperti rock, hip hop, jazz dan pop dengan lebih 100 persembahan, 20 ‘showcase’, pameran dan perdagangan berkaitan.\n\nFestival tiga hari itu bakal berlangsung di Pusat Pameran dan Perdagangan Antarabangsa Malaysia (MITEC), Kuala Lumpur pada 26 hingga 28 April ini.\n\nMaklumat mengenai pembelian tiket dan keterangan lanjut boleh melayari www.kljamm.com.'

Load scikit-learn Interface#

Load decomposition and text vectorizer module from sklearn,

def sklearn(model, vectorizer):
    """
    sklearn interface for summarization.

    Parameters
    ----------
    model : object
        Should have `fit_transform` method. Commonly:

        * ``sklearn.decomposition.TruncatedSVD`` - LSA algorithm.
        * ``sklearn.decomposition.LatentDirichletAllocation`` - LDA algorithm.
    vectorizer : object
        Should have `fit_transform` method. Commonly:

        * ``sklearn.feature_extraction.text.TfidfVectorizer`` - TFIDF algorithm.
        * ``sklearn.feature_extraction.text.CountVectorizer`` - Bag-of-Word algorithm.
        * ``malaya.text.vectorizer.SkipGramCountVectorizer`` - Skip Gram Bag-of-Word algorithm.
        * ``malaya.text.vectorizer.SkipGramTfidfVectorizer`` - Skip Gram TFIDF algorithm.

    Returns
    -------
    result: malaya.model.extractive_summarization.SKLearn
    """
[4]:
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from malaya.text.vectorizer import SkipGramCountVectorizer, SkipGramTfidfVectorizer
from sklearn.decomposition import TruncatedSVD, LatentDirichletAllocation

stopwords = malaya.text.function.get_stopwords()
[5]:
vectorizer = SkipGramCountVectorizer(
    max_df = 0.95,
    min_df = 1,
    ngram_range = (1, 3),
    stop_words = stopwords,
    skip = 2
)
[6]:
svd = TruncatedSVD(n_components = 30)
[7]:
model = malaya.summarization.extractive.sklearn(svd, vectorizer)

Sentence level#

This will predict scores for each sentences,

def sentence_level(
    self,
    corpus,
    isi_penting: str = None,
    top_k: int = 3,
    important_words: int = 10,
    **kwargs
):
    """
    Summarize list of strings / string on sentence level.

    Parameters
    ----------
    corpus: str / List[str]
    isi_penting: str, optional (default=None)
        if not None, will put priority based on `isi_penting`.
    top_k: int, (default=3)
        number of summarized strings.
    important_words: int, (default=10)
        number of important words.

    Returns
    -------
    dict: {'summary', 'top-words', 'cluster-top-words', 'score'}
    """
[8]:
r = model.sentence_level(isu_kerajaan)
r.keys()
[8]:
dict_keys(['summary', 'top-words', 'cluster-top-words', 'score'])
[9]:
pprint(r['summary'])
('"Kedua UMNO sebagai sebuah parti sangat menghormati dan memahami keperluan '
 'sekolah vernakular di Malaysia. kerana parti itu menghormati serta memahami '
 'keperluan sekolah vernakular dalam negara. Timbalan Presiden UMNO, Datuk '
 'Seri Mohamad Hasan berkata, kenyataan tersebut tidak mewakili pendirian '
 'serta pandangan UMNO .')
[10]:
r['cluster-top-words']
[10]:
['nazri',
 'tugas presiden umno',
 'pendirian pandangan',
 'sekolah',
 'tugas umno',
 'menghormati',
 'vernakular']
[11]:
r['score'][:20]
[11]:
[('Kenyataan', 0.07025717383217371),
 ('kontroversi', 0.07025717383217371),
 ('Setiausaha', 0.07025717383217371),
 ('Agung', 0.07025717383217371),
 ('Barisan', 0.07025717383217371),
 ('Nasional', 0.07025717383217371),
 ('(BN),', 0.07025717383217371),
 ('Datuk', 0.07025717383217371),
 ('Seri', 0.07025717383217371),
 ('Mohamed', 0.07025717383217371),
 ('Nazri', 0.07025717383217371),
 ('Aziz', 0.07025717383217371),
 ('berhubung', 0.07025717383217371),
 ('sekolah', 0.07025717383217371),
 ('vernakular', 0.07025717383217371),
 ('merupakan', 0.07025717383217371),
 ('pandangan', 0.07025717383217371),
 ('peribadi', 0.07025717383217371),
 ('beliau.', 0.07025717383217371),
 ('Timbalan', 0.11720421338582344)]
[12]:
r = model.sentence_level(isu_kerajaan, isi_penting = 'Mohamed Nazri')
pprint(r['summary'])
('Beliau turut menegaskan Mohamed Nazri telah mengambil pertanggungjawaban '
 'dengan membuat penjelasan maksud sebenarnya ucapanny di Semenyih, Selangor '
 'tersebut. Kata Nazri dalam kenyataannya itu, beliau menekankan bahawa semua '
 'pihak perlu menghormati hak orang Melayu dan bumiputera. Mohamed Nazri '
 'semalam menjelaskan, kenyataannya mengenai sekolah jenis kebangsaan Cina dan '
 'Tamil baru-baru ini disalah petik pihak media.')

Word level#

This will predict scores for each words. This interface will not returned a summary, just score for each words.

def word_level(
    self,
    corpus,
    isi_penting: str = None,
    window_size: int = 10,
    important_words: int = 10,
    **kwargs
):
    """
    Summarize list of strings / string on word level.

    Parameters
    ----------
    corpus: str / List[str]
    isi_penting: str, optional (default=None)
        if not None, will put priority based on `isi_penting`.
    window_size: int, (default=10)
        window size for each word.
    important_words: int, (default=10)
        number of important words.

    Returns
    -------
    dict: {'top-words', 'cluster-top-words', 'score'}
    """
[13]:
r = model.word_level(isu_kerajaan, isi_penting = 'Mohamed Nazri')
r.keys()
[13]:
dict_keys(['top-words', 'cluster-top-words', 'score'])
[14]:
r['score'][:20]
[14]:
[('Kenyataan', 0.16552039870942348),
 ('kontroversi', 0.20987431777510318),
 ('Setiausaha', 0.2910836210326182),
 ('Agung', 0.33043507623549134),
 ('Barisan', 0.37584569725320854),
 ('Nasional', 0.42408230816674647),
 ('(BN),', 0.43905076388215),
 ('Datuk', 0.43905076388215),
 ('Seri', 0.43762341571724483),
 ('Mohamed', 0.4242747172617825),
 ('Nazri', 0.4242747172617825),
 ('Aziz', 0.40712951562675587),
 ('berhubung', 0.381848567733613),
 ('sekolah', 0.3688951936035658),
 ('vernakular', 0.3680946243164509),
 ('merupakan', 0.3655159287144041),
 ('pandangan', 0.3462360110432939),
 ('peribadi', 0.32917833572272337),
 ('beliau.', 0.32917833572272337),
 ('Timbalan', 0.3119875589719322)]

Load Encoder summarization#

We leverage the power of deep encoder models like skip-thought or Transformer to do extractive summarization for us.

def encoder(vectorizer):
    """
    Encoder interface for summarization.

    Parameters
    ----------
    vectorizer : object
        encoder interface object, eg, BERT, skip-thought, XLNET, ALBERT, ALXLNET.
        should have `vectorize` method.

    Returns
    -------
    result: malaya.model.extractive_summarization.Encoder
    """
[15]:
encoder = malaya.transformer.huggingface(model = 'mesolitica/electra-base-generator-bahasa-cased')
[16]:
encoder = malaya.summarization.extractive.encoder(encoder)

Sentence level#

This will predict scores for each sentences,

def sentence_level(
    self,
    corpus,
    isi_penting: str = None,
    top_k: int = 3,
    important_words: int = 10,
    batch_size: int = 16,
    **kwargs
):
    """
    Summarize list of strings / string on sentence level.

    Parameters
    ----------
    corpus: str / List[str]
    isi_penting: str, optional (default=None)
        if not None, will put priority based on `isi_penting`.
    top_k: int, (default=3)
        number of summarized strings.
    important_words: int, (default=10)
        number of important words.
    batch_size: int, (default=16)
        for each feed-forward, we only feed N size of texts for each batch.
        This to prevent OOM.

    Returns
    -------
    dict: {'summary', 'top-words', 'cluster-top-words', 'score'}
    """
[17]:
%%time

r = encoder.sentence_level(isu_string, isi_penting = 'antarabangsa')
pprint(r['summary'])
You're using a ElectraTokenizerFast tokenizer. Please note that with a fast tokenizer, using the `__call__` method is faster than using a method to encode the text followed by a call to the `pad` method to get a padded encoding.
('“Sebab itu, saya sukar menolak untuk bekerjasama dengannya dalam Festival KL '
 'Jamm yang dianjurkan buat julung kali dan berkongsi pentas dalam satu '
 'konsert bertaraf antarabangsa,” katanya. Penyanyi yang popular dengan lagu '
 'Hijau dan Ikhlas Tapi Jauh itu mengakui mereka memang ada keserasian ketika '
 'bergandingan kerana membesar pada era muzik yang sama. Menurut Sheila, dia '
 'juga mencadangkan lebih banyak tempat diwujudkan untuk menggalakkan artis '
 'muda membuat persembahan, sekali gus menggilap bakat mereka.')
CPU times: user 1min 56s, sys: 435 ms, total: 1min 57s
Wall time: 21.4 s

Word level#

This will predict scores for each words. This interface will not returned a summary, just score for each words.

def word_level(
    self,
    corpus,
    isi_penting: str = None,
    window_size: int = 10,
    important_words: int = 10,
    batch_size: int = 16,
    **kwargs
):
    """
    Summarize list of strings / string on word level.

    Parameters
    ----------
    corpus: str / List[str]
    isi_penting: str, optional (default=None)
        if not None, will put priority based on `isi_penting`.
    window_size: int, (default=10)
        window size for each word.
    important_words: int, (default=10)
        number of important words.
    batch_size: int, (default=16)
        for each feed-forward, we only feed N size of texts for each batch.
        This to prevent OOM.

    Returns
    -------
    dict: {'summary', 'top-words', 'cluster-top-words', 'score'}
    """
[18]:
%%time

r = encoder.word_level(isu_string)
r['score'][:20]
CPU times: user 3min 48s, sys: 910 ms, total: 3min 49s
Wall time: 35.9 s
[18]:
[('DUA', 0.8360301),
 ('legenda', 0.8258482),
 ('hebat', 0.8716588),
 ('dan', 0.8656551),
 ('‘The', 0.87194645),
 ('living', 0.8791537),
 ('legend’', 0.8807227),
 ('ini', 0.89136076),
 ('sudah', 0.8815483),
 ('memartabatkan', 0.79127175),
 ('bidang', 0.7525849),
 ('muzik', 0.821806),
 ('sejak', 0.69763315),
 ('lebih', 0.75871396),
 ('tiga', 0.8398214),
 ('dekad', 0.87585723),
 ('lalu.', 0.8727054),
 ('Jika', 0.8787331),
 ('Datuk', 0.879779),
 ('Zainal', 0.8774382)]