Extract aspects from sentence using denpendency parsing and POS tags

Tutorials / Implementations
NLP
Extract aspects in the sentences for aspect sentiment analysis uisng dependency parsing and pos tags.
Published

August 18, 2021

from google.colab import drive
drive.mount('/content/drive')
Mounted at /content/drive

Install required modules

%%capture
!pip install pyvi
!pip install https://github.com/trungtv/vi_spacy/raw/master/packages/vi_spacy_model-0.2.1/dist/vi_spacy_model-0.2.1.tar.gz
!pip install spacy==2.1.4

Note: In this post, we will use spacy version 2 since in version it changes the way to load model, and I don’t know how to make it work for the vi_spacy_model :).

import pandas as pd
import numpy as np
import spacy
from string import punctuation
# Not limit the column width of dataframe
pd.set_option("display.max_colwidth", None)

Load dataset

df = pd.read_csv("/content/drive/MyDrive/SLSOPS/dataset/vietnamese/spacy.csv")
df.head()
db_id raw tokenized_spacy spacy_no_sw star_rating
0 1 Shipper giao đúng hẹn, tận tình mang vào đến cửa căn hộ chung cư. shipper giao đúng hẹn tận_tình mang vào đến cửa căn_hộ chung_cư shipper giao hẹn tận_tình cửa căn_hộ chung_cư 5
1 1 Nhân viên lắp đặt thân thiện, làm việc tốt, tuy nhiên hơi làm dơ tường và đồ đạc trong nhà. nhân_viên lắp_đặt thân_thiện làm_việc tốt tuy_nhiên hơi làm dơ tường và đồ_đạc trong nhà nhân_viên lắp_đặt thân_thiện làm_việc tốt hơi dơ tường đồ_đạc 5
2 1 Sản phẩm đẹp, giá cạnh tranh. sản_phẩm đẹp giá cạnh_tranh sản_phẩm đẹp giá cạnh_tranh 5
3 1 Nhưng giá Tiki bán là ko tặng vật tư giống ở ngoài bán. nhưng giá tiki bán là không tặng_vật_tư giống ở ngoài bán giá tiki tặng_vật_tư 5
4 1 Lúc máy mình lắp xong thì giá vật tư là khoảng hơn 1 triệu. lúc máy mình lắp xong thì giá vật_tư là khoảng hơn 1 triệu máy lắp xong giá vật_tư 1 triệu 5

Extract Aspects using NER and POS

from dataclasses import dataclass
@dataclass
class AsOp:
    aspect: Any = None
    opinion: Any = None
    adv: Any = None 
    verb: Any = None
    negative: bool = False
    aspect_compound: Any = None

@dataclass
class Aspect_Opinion:
    aspect: str = None
    opinion: str = None
    verb: str = None
    negative: bool = False
# Test the libary
import spacy
nlp = spacy.load('vi_spacy_model')
doc = nlp('Cộng đồng xử lý ngôn ngữ tự nhiên')
for token in doc:
    print(token.text, token.tag_, token.dep_)
Cộng_đồng N nsubj
xử_lý V ROOT
ngôn_ngữ N obj
tự_nhiên A compound
# Load the preprocess data
data = list(df['spacy_no_sw'])
data[0]
'shipper giao hẹn tận_tình cửa căn_hộ chung_cư'
from spacy import displacy # module for visualize the denpendency parsing
displacy.render(nlp(df['tokenized_spacy'][2]), style="dep", jupyter=True)
sản_phẩm X đẹp X giá X cạnh_tranh X amod compound xcomp
displacy.render(nlp(sentences[20]), style="dep", jupyter=True)
tiền X nào X của X nấy X nhưng X âm_thanh X ồn_ào X như X động_cơ X bezen X ầm_ầm X thế X này X thì X chịu X rồi X nsubj det case nmod cc amod cc nsubj compound ccomp obj det cc conj advmod

Rules

We check 2 cases: 1. The token POS is N (Noun) and the Dependency tag is not nsubj. 1. The token POS is N (Noun) and the Dependency tag is nsubj.

Note: I come up with these rules by trying with many samples =)) No magic behind it.

aspects = []
doc = nlp(sentences[25])
print(doc)
for i, token in enumerate(doc):
    # Case 1: 
    if token.tag_ == "N" and token.dep_ != "nsubj":
        aspect = token.text
        for child in token.children:
            if child.dep_ == "compound":
                if child in token.lefts:
                    aspect = f"{child.text} {aspect}"
                elif child in token.rights:
                    aspect = f"{aspect} {child.text}"
            elif child.dep_ == "amod":
                opinion = child.text
                for gchild in child.children:
                    if gchild.dep_ == "advmod":
                        if gchild in child.lefts:
                            opinion = f"{gchild.text} {opinion}"
                        elif gchild in child.rights:
                            opinion = f"{opinion} {gchild.text}"
                asp_obj = AsOp(aspect=aspect, opinion=opinion)
                aspects.append(asp_obj)
            elif child.dep_ == "conj":
                asp_obj = None
                if child.tag_ == "N":
                    aspect = child.text
                    opinion = ""
                    for gchild in child.rights:
                        if gchild.dep_ == "xcomp" and gchild.tag_ == "A":
                            opinion = gchild.text
                    if opinion:
                        asp_obj = AsOp(aspect=aspect, opinion=opinion)
                elif child.tag_ == "A":
                    asp_obj = AsOp(aspect=aspect, opinion=child.text)
                if asp_obj: 
                    aspects.append(asp_obj)
            
    # Case 2:
    elif token.tag_ == "N" and token.dep_ == "nsubj":
        opinion = ""
        if token.head.tag_ == "A":
            opinion = token.head.text
            
        for child in token.head.children:
            if child.text == token.text:
                continue
            elif child.tag_ == "A" or child.tag_ == "R":
                if opinion:
                    if child in token.head.lefts: 
                        opinion = f"{child.text} {opinion}"
                    else:
                        opinion = f"{opinion} {child.text}"
                else:
                    opinion = child.text
                    for gchild in child.children:
                        if gchild.dep_ == "advmod":
                            if gchild in child.lefts:
                                opinion = f"{gchild.text} {opinion}"
                            elif gchild in child.rights:
                                opinion = f"{opinion} {gchild.text}"
                asp_obj = AsOp(aspect=token.text, opinion=opinion)
                aspects.append(asp_obj)
print(aspects)
đến chán giờ cứ sử_dụng là bị cúp nguồn điện thì ai dám bật
[]
from dataclasses import dataclass
from typing import Any
@dataclass
class AsOp:
    aspect: Any = None
    opinion: Any = None
from pyvi import ViTokenizer, ViPosTagger
sentence = df['tokenized_spacy'][6]
# print(sentence)
sentences = list(df['tokenized_spacy'])[:100]
aspects = []
cnt = 0
for sentence in sentences:
    try:
        doc = nlp(sentence)
        for i, token in enumerate(doc):
            if token.tag_ == "N" and token.dep_ != "nsubj":
                aspect = token.text
                for child in token.children:
                    if child.dep_ == "compound":
                        if child in token.lefts:
                            aspect = f"{child.text} {aspect}"
                        elif child in token.rights:
                            aspect = f"{aspect} {child.text}"
                    elif child.dep_ == "amod":
                        opinion = child.text
                        for gchild in child.children:
                            if gchild.dep_ == "advmod":
                                if gchild in child.lefts:
                                    opinion = f"{gchild.text} {opinion}"
                                elif gchild in child.rights:
                                    opinion = f"{opinion} {gchild.text}"
                        asp_obj = AsOp(aspect=aspect, opinion=opinion)
                        aspects.append(asp_obj)
                    elif child.dep_ == "conj":
                        asp_obj = None
                        if child.tag_ == "N":
                            aspect = child.text
                            opinion = ""
                            for gchild in child.rights:
                                if gchild.dep_ == "xcomp" and gchild.tag_ == "A":
                                    opinion = gchild.text
                            if opinion:
                                asp_obj = AsOp(aspect=aspect, opinion=opinion)
                        elif child.tag_ == "A":
                            asp_obj = AsOp(aspect=aspect, opinion=child.text)
                        if asp_obj: 
                            aspects.append(asp_obj)
                    
            elif token.tag_ == "N" and token.dep_ == "nsubj":
                opinion = ""
                if token.head.tag_ == "A":
                    opinion = token.head.text
                    
                for child in token.head.children:
                    if child.text == token.text:
                        continue
                    elif child.tag_ == "A" or child.tag_ == "R":
                        if opinion:
                            if child in token.head.lefts: 
                                opinion = f"{child.text} {opinion}"
                            else:
                                opinion = f"{opinion} {child.text}"
                        else:
                            opinion = child.text
                            for gchild in child.children:
                                if gchild.dep_ == "advmod":
                                    if gchild in child.lefts:
                                        opinion = f"{gchild.text} {opinion}"
                                    elif gchild in child.rights:
                                        opinion = f"{opinion} {gchild.text}"
                        asp_obj = AsOp(aspect=token.text, opinion=opinion)
                        aspects.append(asp_obj)
    except:
        print(sentence)
        cnt += 1
aspects
[AsOp(aspect='shipper', opinion='vào'),
 AsOp(aspect='nhân_viên', opinion='tốt'),
 AsOp(aspect='sản_phẩm', opinion='đẹp'),
 AsOp(aspect='giá', opinion='không tặng_vật_tư'),
 AsOp(aspect='giá', opinion='không tặng_vật_tư giống'),
 AsOp(aspect='giá', opinion='khoảng'),
 AsOp(aspect='giá', opinion='hơn khoảng'),
 AsOp(aspect='giá', opinion='hơn'),
 AsOp(aspect='tiền', opinion='hơn'),
 AsOp(aspect='tiền', opinion='không hơn'),
 AsOp(aspect='hàng', opinion='đúng'),
 AsOp(aspect='tiki giao_nhận', opinion='tốt'),
 AsOp(aspect='máy', opinion='tốt'),
 AsOp(aspect='máy', opinion='rất êm'),
 AsOp(aspect='hàng', opinion='nhanh'),
 AsOp(aspect='hàng', opinion='nhanh ổn'),
 AsOp(aspect='sản_phẩm', opinion='tạm'),
 AsOp(aspect='hàng', opinion='nhanh'),
 AsOp(aspect='máy_lạnh', opinion='chưa'),
 AsOp(aspect='máy_lạnh', opinion='chưa được'),
 AsOp(aspect='máy', opinion='chỉ'),
 AsOp(aspect='tiền', opinion='ồn_ào'),
 AsOp(aspect='âm_thanh', opinion='ồn_ào'),
 AsOp(aspect='hàng', opinion='mới'),
 AsOp(aspect='hàng', opinion='cũ'),
 AsOp(aspect='điện', opinion='cực_kỳ tốt'),
 AsOp(aspect='điện', opinion='cực_kỳ tốt liên_tục'),
 AsOp(aspect='sản_phẩm', opinion='cùng'),
 AsOp(aspect='thực_tế', opinion='gần'),
 AsOp(aspect='tiếng', opinion='to'),
 AsOp(aspect='tiếng', opinion='không êm'),
 AsOp(aspect='pin', opinion='không'),
 AsOp(aspect='đội_ngũ', opinion='rất'),
 AsOp(aspect='giá', opinion='thêm'),
 AsOp(aspect='người', opinion='xanh'),
 AsOp(aspect='tiki', opinion='rẻ'),
 AsOp(aspect='máy', opinion='đúng đúng'),
 AsOp(aspect='máy', opinion='khá to'),
 AsOp(aspect='máy', opinion='khá to êm'),
 AsOp(aspect='máy', opinion='khá to êm lại'),
 AsOp(aspect='khi', opinion='sẽ êm'),
 AsOp(aspect='máy', opinion='quá ồn'),
 AsOp(aspect='bạn', opinion='dễ_thương'),
 AsOp(aspect='bạn', opinion='dễ_thương lắm'),
 AsOp(aspect='hàng', opinion='nhanh'),
 AsOp(aspect='độ', opinion='nhiều'),
 AsOp(aspect='độ', opinion='bền'),
 AsOp(aspect='ngày', opinion='hợp_lý'),
 AsOp(aspect='đội', opinion='nhiều'),
 AsOp(aspect='điều', opinion='hòa'),
 AsOp(aspect='sản_phẩm', opinion='không'),
 AsOp(aspect='sản_phẩm', opinion='mới không'),
 AsOp(aspect='sản_phẩm', opinion='mới không ra'),
 AsOp(aspect='sản_phẩm', opinion='mới không ra rồi'),
 AsOp(aspect='điểm', opinion='vô_cùng'),
 AsOp(aspect='điều', opinion='hòa'),
 AsOp(aspect='điều', opinion='hòa'),
 AsOp(aspect='điều', opinion='cũ'),
 AsOp(aspect='phí', opinion='không'),
 AsOp(aspect='lần', opinion='cuối_cùng'),
 AsOp(aspect='chiều', opinion='dài'),
 AsOp(aspect='cái màng', opinion='lọc'),
 AsOp(aspect='biên_độ', opinion='nóng'),
 AsOp(aspect='biên_độ', opinion='lạnh'),
 AsOp(aspect='biên_độ', opinion='cao'),
 AsOp(aspect='tổng_chi_phí', opinion='hết'),
 AsOp(aspect='bao', opinion='gôm'),
 AsOp(aspect='lòng', opinion='thật buồn'),
 AsOp(aspect='giá', opinion='rẻ'),
 AsOp(aspect='giá', opinion='rât'),
 AsOp(aspect='giá', opinion='hợp_lý'),
 AsOp(aspect='7tr', opinion='tròn'),
 AsOp(aspect='cục', opinion='không'),
 AsOp(aspect='cục', opinion='không có'),
 AsOp(aspect='máy', opinion='hơi lạnh'),
 AsOp(aspect='cảm_giác', opinion='rất dễ_chịu'),
 AsOp(aspect='cục', opinion='lạnh'),
 AsOp(aspect='cục', opinion='nóng khoảng'),
 AsOp(aspect='cánh_quạt', opinion='không'),
 AsOp(aspect='cánh_quạt', opinion='không được'),
 AsOp(aspect='cục', opinion='nóng'),
 AsOp(aspect='máy_lạnh', opinion='rất tận_tình'),
 AsOp(aspect='máy_lạnh', opinion='rất tận_tình kém'),
 AsOp(aspect='nhân_viên', opinion='rất tận_tình'),
 AsOp(aspect='nhân_viên', opinion='rất tận_tình kém'),
 AsOp(aspect='dịch_vụ', opinion='rất kém'),
 AsOp(aspect='máy_lạnh', opinion='tạm ổn'),
 AsOp(aspect='máy_lạnh', opinion='tạm ổn êm'),
 AsOp(aspect='cục', opinion='nóng rất'),
 AsOp(aspect='dịch_vụ', opinion='khá ổn'),
 AsOp(aspect='máy', opinion='lạnh'),
 AsOp(aspect='mát', opinion='lạnh'),
 AsOp(aspect='sản_phẩm', opinion='mới'),
 AsOp(aspect='hàng', opinion='nhanh'),
 AsOp(aspect='máy', opinion='êm'),
 AsOp(aspect='máy', opinion='êm ít'),
 AsOp(aspect='máy', opinion='đẹp'),
 AsOp(aspect='máy', opinion='tốt'),
 AsOp(aspect='cục', opinion='dễ'),
 AsOp(aspect='thiết_kế', opinion='trang_nhã'),
 AsOp(aspect='cục', opinion='mạnh_mẽ'),
 AsOp(aspect='cục', opinion='cứng_cáp'),
 AsOp(aspect='tính_năng', opinion='nhiều'),
 AsOp(aspect='máy', opinion='không'),
 AsOp(aspect='tốc_độ gió', opinion='phù_hợp'),
 AsOp(aspect='kích_thước', opinion='khác'),
 AsOp(aspect='chức_năng', opinion='đầy_đủ')]

Discussion

The quality of this approach depends on the accuracy of the dependecy parsing and POS model.

Tags

A - Adjective
C - Coordinating conjunction
E - Preposition
I - Interjection
L - Determiner
M - Numeral
N - Common noun
Nc - Noun Classifier
Ny - Noun abbreviation
Np - Proper noun
Nu - Unit noun
P - Pronoun
R - Adverb
S - Subordinating conjunction
T - Auxiliary, modal words
V - Verb
X - Unknown
F - Filtered out (punctuation)

ccomp: functions like an object of the verb, or adjective.

xcomp: if the subject of the clausal complement is controlled (that is, must be the same as the higher subject or object, with no other possible interpretation)

predicative: a part of a sentence containing a verb that makes a statement about the subject of the verb, such as went home in John went home.