[PYTHON] [Verarbeitung natürlicher Sprache] Ich habe diese Woche versucht, die aktuellen Themen in der Slack-Community zu visualisieren

Über diesen Artikel

In diesem Artikel werde ich Ihnen zeigen, wie Sie mit Wordcloud visualisieren, welche Themen in der Slack-Community über einen bestimmten Zeitraum (1 Woche hier) angesprochen wurden.

Den Quellcode finden Sie hier: octocat:

Ich möchte auch lesen: [Verarbeitung natürlicher Sprache] Ich habe versucht, die Bemerkungen jedes Mitglieds in der Slack-Community zu visualisieren

Inhaltsverzeichnis

  1. Verwendungs- und Ausgabebeispiel
  2. Holen Sie sich die Nachricht von Slack
  3. Vorverarbeitung: Erstellen Sie eine Message Mart-Tabelle
  4. Vorbehandlung: Reinigung
  5. Vorbehandlung: Morphologische Analyse (Janome)
  6. Vorverarbeitung: Normalisierung
  7. Vorbehandlung: Stoppen Sie die Wortentfernung
  8. Vorverarbeitung: Wichtige Phrasenextraktion (tf-idf)
  9. Visualisierungsprozess mit Wordcloud
  10. Bonus

* Ich möchte die Vorverarbeitung in Zukunft in einem anderen Artikel zusammenfassen </ font>

1. Verwendungs- und Ausgabebeispiel

1.1. Verwendung

Weitere Informationen finden Sie unter Erste Schritte in README. Der Fluss ist so.

  1. Erstellen Sie eine virtuelle Umgebung mit "Docker-Compose Up-D"
  2. Betreten Sie die Shell mit "docker exec -it ds-py3 bash"
  3. Führen Sie run_wordcloud_by_term.sh aus

1.2 Ausgabebeispiel

Dies ist ein Beispiel für die tatsächliche Ausgabe. Wordcloud ist die Bemerkung jeder einzelnen Periode.

anim__.gif

2. Holen Sie sich die Nachricht von Slack

2.1 Verwenden Sie die Slack-API

Erhalten Sie Slack API-Token von Slack API Official

Wie Sie mit der Slack-API beginnen, wird hier nicht gezeigt.

Um die nachfolgende Verarbeitung durchzuführen, besorgen Sie sich bitte die folgenden Token

  • Channel API
  • Users API

2.2 Erstellen Sie eine Klasse, um Slack-Informationen über die API abzurufen

Hier erstellen wir eine ** SlackApp ** -Klasse, die Slack-Informationen über die API abruft. Die erfassten Informationen werden ohne Verarbeitung im JSON-Format gespeichert.

slack_app.py


#Eine Klasse, die Slackapi verwendet, um die gewünschten Informationen zu erhalten(Nicht verarbeiten)
import requests
import json
from tqdm import tqdm
import pandas as pd


class SlackApp:
    ch_list_url = 'https://slack.com/api/channels.list'
    ch_history_url = 'https://slack.com/api/channels.history'
    usr_list_url = 'https://slack.com/api/users.list'

    def __init__(self, ch_api_key, usr_api_key):
        # NEW members
        self.channels_info = []
        self.users_info = []
        self.messages_info = []
        # OLD members
        self.channelInfo = {}  # k: ch_name, v: ch_id
        self.messages_in_chs = {}
        self.userInfo = {}
        self.ch_api_token = str(ch_api_key)
        self.usr_api_token = str(usr_api_key)

    def load_save_channel_info(self, outdir: str):
        #Holen Sie sich Kanalinformationen über die Slack-API und speichern Sie sie in einer Datei
        payload = {'token': self.ch_api_token}
        response = requests.get(SlackApp.ch_list_url, params=payload)
        if response.status_code == 200:
            json_data = response.json()
            if 'channels' in json_data.keys():
                self.channels_info = json_data['channels']
            with open(outdir + '/' + 'channel_info.json', 'w', encoding='utf-8') as f:
                json.dump(self.channels_info, f, indent=4, ensure_ascii=False)

    def load_save_user_info(self, outdir: str):
        #Holen Sie sich Benutzerinformationen über die Slack-API und speichern Sie sie in einer Datei
        payload = {'token': self.usr_api_token}
        response = requests.get(SlackApp.usr_list_url, params=payload)
        if response.status_code == 200:
            json_data = response.json()
            if 'members' in json_data.keys():
                self.users_info = json_data['members']
            with open(outdir + '/' + 'user_info.json', 'w', encoding='utf-8') as f:
                json.dump(self.users_info, f, indent=4, ensure_ascii=False)

    def load_save_messages_info(self, outdir: str):
        #Erstellen Sie eine Kanal-ID-Liste
        channel_id_list = []
        for ch in self.channels_info:
            channel_id_list.append(ch['id'])
        #Holen Sie sich Benutzerinformationen über die Slack-API und speichern Sie sie in einer Datei
        for ch_id in tqdm(channel_id_list, desc='[loading...]'):
            payload = {'token': self.ch_api_token, 'channel': ch_id}
            response = requests.get(SlackApp.ch_history_url, params=payload)
            if response.status_code == 200:
                json_data = response.json()
                msg_in_ch = {}
                msg_in_ch['channel_id'] = ch_id
                if 'messages' in json_data.keys():
                    msg_in_ch['messages'] = json_data['messages']
                else:
                    msg_in_ch['messages'] = ''
                self.messages_info.append(msg_in_ch)
        with open(outdir + '/' + 'messages_info.json', 'w', encoding='utf-8') as f:
            json.dump(self.messages_info, f, indent=4, ensure_ascii=False)        

2.3. Slack-Informationen abrufen

Verwenden Sie die Klasse ** SlackApp ** früher, um die Informationen abzurufen.

Die zu erfassenden Informationen sind die folgenden drei

  1. Kanalliste
  2. Nachrichtenliste (für jeden Kanal erhalten)
  3. Benutzerliste

slack_msg_extraction.py


#Eine Klasse, die Slackapi verwendet, um die gewünschten Informationen zu erhalten
import sys
import json
sys.path.append('../../src/d000_utils') #Speicherpfad für SlackApp-Skripte hinzugefügt
import slack_app as sa


def main():
    # -------------------------------------
    # load api token
    # -------------------------------------
    credentials_root = '../../conf/local/'
    credential_fpath = credentials_root + 'credentials.json'
    print('load credential.json ...')
    with open(credential_fpath, 'r') as f:
        credentials = json.load(f)
    # -------------------------------------
    # start slack app
    # -------------------------------------    
    print('start slack app ...')
    app = sa.SlackApp(
        credentials['channel_api_key'],
        credentials['user_api_key']
        )
    outdir = '../../data/010_raw'
    # -------------------------------------
    # get channels info
    # -------------------------------------
    app.load_save_channel_info(outdir)
    # -------------------------------------
    # get user info
    # -------------------------------------
    app.load_save_user_info(outdir)
    # -------------------------------------
    # get msg info
    # -------------------------------------
    app.load_save_messages_info(outdir)


if __name__ == "__main__":
    main()

3. Vorverarbeitung: Erstellen Sie eine Message Mart-Tabelle

3.1. Design der Message Mart-Tabelle

Die von SlackAPI erfassten Informationen wurden gemäß den Spezifikationen von SlackAPI im JSON-Format gespeichert. Formatieren Sie die Tabellendaten so, dass sie leicht analysiert werden können. Beachten Sie beim Entwerfen einer Tabelle Tidy Data.

Dieses Mal habe ich es wie folgt gestaltet. Die folgende Tabelle basiert auf dem Bild der minimal erforderlichen Informationen + α.

message mart table

No Column Name Type Content
0 index int AUTO INCREMENT
1 ch_id str Kanal ID
2 msg str Nachrichtenzeichenfolge
3 uid str Benutzer-ID des Sprechers
4 timestamp datetime Zeit zu sprechen

Kanaltabelle (Bonus)

No Column Name Type Content
0 index int AUTO INCREMENT
1 ch_id str Kanal ID
2 ch_name str Kanalname (Name wird auf der Slack-Benutzeroberfläche angezeigt)
3 ch_namenorm str Normalisierter Kanalname
4 ch_membernum int Anzahl der Teilnehmer im Kanal

Benutzertabelle (Bonus)

No Column Name Type Content
0 index int AUTO INCREMENT
1 uid str Benutzeridentifikation
2 uname str Nutzername

3.2. RAW-Daten → In Message Mart-Tabelle formatieren

Der tatsächliche Code ist unten.

  • 【Hinweis】 -- ../../ data / 010_raw: Informationen, die vom Speicherort im Slack JSON-Format abgerufen wurden --user_info.json: JSON-Dateiname (User Information) --messages_info.json: JSON-Dateiname (Message Information) für alle Kanäle --channel_info.json: Name der JSON-Datei (Channel Information)

make_msg_mart_table.py


import json
import pandas as pd


def make_user_table(usr_dict: dict) -> pd.DataFrame:
    uid_list = []
    uname_list = []
    for usr_ditem in usr_dict:
        if usr_ditem['deleted'] == True:
            continue
        uid_list.append(usr_ditem['id'])
        uname_list.append(usr_ditem['profile']['real_name_normalized'])
    user_table = pd.DataFrame({'uid': uid_list, 'uname': uname_list})
    return user_table


def make_msg_table(msg_dict: dict) -> pd.DataFrame:
    ch_id_list = []
    msg_list = []
    uid_list = []
    ts_list = []
    for msg_ditem in msg_dict:
        if 'channel_id' in msg_ditem.keys():
            ch_id = msg_ditem['channel_id']
        else:
            continue
        if 'messages' in msg_ditem.keys():
            msgs_in_ch = msg_ditem['messages']
        else:
            continue
        # get message in channel
        for i, msg in enumerate(msgs_in_ch):
            # if msg by bot, continue
            if 'user' not in msg:
                continue
            ch_id_list.append(ch_id)
            msg_list.append(msg['text'])
            uid_list.append(msg['user'])  #Es gibt keinen Schlüssel für Bot
            ts_list.append(msg['ts'])
    df_msgs = pd.DataFrame({
        'ch_id': ch_id_list,
        'msg': msg_list,
        'uid': uid_list,
        'timestamp': ts_list
    })
    return df_msgs


def make_ch_table(ch_dict: dict) -> pd.DataFrame:
    chid_list = []
    chname_list = []
    chnormname_list = []
    chmembernum_list = []
    for ch_ditem in ch_dict:
        chid_list.append(ch_ditem['id'])
        chname_list.append(ch_ditem['name'])
        chnormname_list.append(ch_ditem['name_normalized'])
        chmembernum_list.append(ch_ditem['num_members'])
    ch_table = pd.DataFrame({
        'ch_id': chid_list,
        'ch_name': chname_list,
        'ch_namenorm': chnormname_list,
        'ch_membernum': chmembernum_list
    })
    return ch_table


def main():
    # 1. load user/message/channels
    input_root = '../../data/010_raw'
    user_info_fpath = input_root + '/' + 'user_info.json'
    with open(user_info_fpath, 'r', encoding='utf-8') as f:
        user_info_rawdict = json.load(f)
        print('load ... ', user_info_fpath)
    msg_info_fpath = input_root + '/' + 'messages_info.json'
    with open(msg_info_fpath, 'r', encoding='utf-8') as f:
        msgs_info_rawdict = json.load(f)
        print('load ... ', msg_info_fpath)
    ch_info_fpath = input_root + '/' + 'channel_info.json'
    with open(ch_info_fpath, 'r', encoding='utf-8') as f:
        ch_info_rawdict = json.load(f)
        print('load ... ', ch_info_fpath)
    # 2. make and save tables
    # user
    output_root = '../../data/020_intermediate'
    df_user_info = make_user_table(user_info_rawdict)
    user_tbl_fpath = output_root + '/' + 'users.csv'
    df_user_info.to_csv(user_tbl_fpath, index=False)
    print('save ... ', user_tbl_fpath)
    # msg
    df_msg_info = make_msg_table(msgs_info_rawdict)
    msg_tbl_fpath = output_root + '/' + 'messages.csv'
    df_msg_info.to_csv(msg_tbl_fpath, index=False)
    print('save ... ', msg_tbl_fpath)
    # channel
    df_ch_info = make_ch_table(ch_info_rawdict)
    ch_tbl_fpath = output_root + '/' + 'channels.csv'
    df_ch_info.to_csv(ch_tbl_fpath, index=False)
    print('save ... ', ch_tbl_fpath)


if __name__ == "__main__":
    main()

4. Vorbehandlung: Reinigung

4.1 Inhalt des Reinigungsprozesses

Bezieht sich im Allgemeinen auf das Entfernen von Rauschen Abhängig von den Zieldaten und dem Zweck müssen verschiedene Verarbeitungen durchgeführt werden. Hier wurde die folgende Verarbeitung ausgeführt.

  1. Löschen Sie die URL-Zeichenfolge
  2. Löschen Sie die Erwähnungszeichenfolge
  3. Löschen Sie Unicode-Piktogramme
  4. Löschen Sie Sonderzeichen in HTML (z. B. & gt).
  5. Codeblock löschen
  6. Inline-Codeblock löschen
  7. Die Meldung "○○ ist dem Kanal beigetreten" wurde entfernt.
  8. Andere Lärmschutzmaßnahmen, die dieser Community eigen sind

4.2 Durchführung des Reinigungsprozesses

cleaning.py


import re
import pandas as pd
import argparse
from pathlib import Path


def clean_msg(msg: str) -> str:
    # sub 'Return and Space'
    result = re.sub(r'\s', '', msg)
    # sub 'url link'
    result = re.sub(r'(<)http.+(>)', '', result)
    # sub 'mention'
    result = re.sub(r'(<)@.+\w(>)', '', result)
    # sub 'reaction'
    result = re.sub(r'(:).+\w(:)', '', result)
    # sub 'html key words'
    result = re.sub(r'(&).+?\w(;)', '', result)
    # sub 'multi lines code block'
    result = re.sub(r'(```).+(```)', '', result)
    # sub 'inline code block'
    result = re.sub(r'(`).+(`)', '', result)
    return result


def clean_msg_ser(msg_ser: pd.Series) -> pd.Series:
    cleaned_msg_list = []
    for i, msg in enumerate(msg_ser):
        cleaned_msg = clean_msg(str(msg))
        if 'Ist dem Kanal beigetreten' in cleaned_msg:
            continue
        cleaned_msg_list.append(cleaned_msg)
    cleaned_msg_ser = pd.Series(cleaned_msg_list)
    return cleaned_msg_ser


def get_ch_id_from_table(ch_name_parts: list, input_fpath: str) -> list:
    df_ch = pd.read_csv(input_fpath)
    ch_id = []
    for ch_name_part in ch_name_parts:
        for i, row in df_ch.iterrows():
            if ch_name_part in row.ch_name:
                ch_id.append(row.ch_id)
                break
    return ch_id


def main(input_fname: str):
    input_root = '../../data/020_intermediate'
    output_root = input_root
    # 1. load messages.csv (including noise)
    msgs_fpath = input_root + '/' + input_fname
    df_msgs = pd.read_csv(msgs_fpath)
    print('load :{0}'.format(msgs_fpath))
    # 2. Drop Not Target Records
    print('drop records (drop non-target channel\'s messages)')
    non_target_ch_name = ['general', 'Ankündigung des Managements']
    non_target_ch_ids = get_ch_id_from_table(non_target_ch_name, input_root + '/' + 'channels.csv')
    print('=== non target channels bellow ====')
    print(non_target_ch_ids)
    for non_target_ch_id in non_target_ch_ids:
        df_msgs = df_msgs.query('ch_id != @non_target_ch_id')
    # 3. clean message string list
    ser_msg = df_msgs.msg
    df_msgs.msg = clean_msg_ser(ser_msg)
    # 4. save it
    pin = Path(msgs_fpath)
    msgs_cleaned_fpath = output_root + '/' + pin.stem + '_cleaned.csv'
    df_msgs.to_csv(msgs_cleaned_fpath, index=False)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("input_fname", help="set input file name", type=str)
    args = parser.parse_args()
    input_fname = args.input_fname
    main(input_fname)

Eigentlich wollte ich "nur entfernen, wenn der Codeblock Quellcode enthält", konnte es aber nicht. : Schweiß:

5. Vorbehandlung: Morphologische Analyse (Janome)

5.1. Was ist morphologische Analyse?

Im Allgemeinen ist es der Prozess, "Morphologie" in einem Satz zu finden. Details der morphologischen Analyse werden anderen Artikeln überlassen.

Der eigentliche Zweck hier ist das "Teilen".

5.2 Was ist Teilung?

Grob gesagt ist es der Prozess, einen Satz in Informationen umzuwandeln, die als "Wort Wort Wort" bezeichnet werden. Zum Beispiel

** Beispielsatz: Ich mag Baseball. ** **. ** Beispielsatzsegmentierung: "Ich mag Baseball" **

Es wird in Form von sein.

In der Praxis ist es vorzuziehen, "Nomen" </ font> zu extrahieren, wenn Sie mit Wörtern umgehen möchten, die den Kontext eines Satzes wie dieses Mal darstellen. Deshalb,

** Trennung von Beispielsätzen (nur Nomenklatur): "Private Baseball" **

Es wäre noch besser.

  • Ist es nicht besser, "Ich" auch zu löschen? Ich dachte du da. Siehe das Kapitel "Entfernen von Stoppwörtern"

5.3 Durchführung der morphologischen Analyse und Teilung

Wenn Sie eine morphologische Analyse implementieren möchten

  • Was für die morphologische Analysebibliothek zu verwenden ist --Was als Wörterbuchdaten zu verwenden

Das müssen wir entscheiden.

Dieses Mal habe ich Folgendes getan.

  • Bibliothek für morphologische Analysen: Janome
  • Wörterbuchdaten: Janome Standard (NEologd für neue Wörter ist noch besser)

Darüber hinaus sind die zu extrahierenden Teilwörter diejenigen, die zur Erreichung des Zwecks erforderlich sind, während ein Blick auf "Teilteilsystem des morphologischen Analysewerkzeugs" geworfen wird. etwas? Dachte ich aus der Sicht.

Janome, das offizielle Maskottchen, ist süß. (Ich weiß nicht, ob Janome der Name ist)

morphological_analysis.py


from janome.tokenizer import Tokenizer
from tqdm import tqdm
import pandas as pd
import argparse
from pathlib import Path
import sys


exc_part_of_speech = {
    "Substantiv": ["Nicht unabhängig", "代Substantiv", "Nummer"]
}
inc_part_of_speech = {
    "Substantiv": ["Verbindung ändern", "Allgemeines", "固有Substantiv"],
}


class MorphologicalAnalysis:

    def __init__(self):
        self.janome_tokenizer = Tokenizer()

    def tokenize_janome(self, line: str) -> list:
        # list of janome.tokenizer.Token
        tokens = self.janome_tokenizer.tokenize(line)
        return tokens

    def exists_pos_in_dict(self, pos0: str, pos1: str, pos_dict: dict) -> bool:
        # Retrurn where pos0, pos1 are in pos_dict or not.
        # ** pos = part of speech
        for type0 in pos_dict.keys():
            if pos0 == type0:
                for type1 in pos_dict[type0]:
                    if pos1 == type1:
                        return True
        return False

    def get_wakati_str(self, line: str, exclude_pos: dict,
                       include_pos: dict) -> str:
        '''
        exclude/include_pos is like this
        {"Substantiv": ["Nicht unabhängig", "代Substantiv", "Nummer"], "Adjektiv": ["xxx", "yyy"]}
        '''
        tokens = self.janome_tokenizer.tokenize(line, stream=True)  #Generator zum Speichern von Speicher
        extracted_words = []
        for token in tokens:
            part_of_speech0 = token.part_of_speech.split(',')[0]
            part_of_speech1 = token.part_of_speech.split(',')[1]
            # check for excluding words
            exists = self.exists_pos_in_dict(part_of_speech0, part_of_speech1,
                                             exclude_pos)
            if exists:
                continue
            # check for including words
            exists = self.exists_pos_in_dict(part_of_speech0, part_of_speech1,
                                             include_pos)
            if not exists:
                continue
            # append(Erworbene Oberflächenschichtform zur Absorption von Notationsschwankungen)
            extracted_words.append(token.surface)
        # wakati string with extracted words
        wakati_str = ' '.join(extracted_words)
        return wakati_str


def make_wakati_for_lines(msg_ser: pd.Series) -> pd.Series:
    manalyzer = MorphologicalAnalysis()
    wakati_msg_list = []
    for msg in tqdm(msg_ser, desc='[mk wakati msgs]'):
        wakati_msg = manalyzer.get_wakati_str(str(msg), exc_part_of_speech,
                                              inc_part_of_speech)
        wakati_msg_list.append(wakati_msg)
    wakati_msg_ser = pd.Series(wakati_msg_list)
    return wakati_msg_ser


def main(input_fname: str):
    input_root = '../../data/020_intermediate'
    output_root = '../../data/030_processed'
    # 1. load messages_cleaned.csv
    msgs_cleaned_fpath = input_root + '/' + input_fname
    df_msgs = pd.read_csv(msgs_cleaned_fpath)
    # 2. make wakati string by record
    ser_msg = df_msgs.msg
    df_msgs['wakati_msg'] = make_wakati_for_lines(ser_msg)
    # 3. save it
    pin = Path(msgs_cleaned_fpath)
    msgs_wakati_fpath = output_root + '/' + pin.stem + '_wakati.csv'
    df_msgs.to_csv(msgs_wakati_fpath, index=False)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("input_fname", help="set input file name", type=str)
    args = parser.parse_args()
    input_fname = args.input_fname
    # input file must been cleaned
    if 'cleaned' not in input_fname:
        print('input file name is invalid.: {0}'.format(input_fname))
        print('input file name must include \'cleaned\'')
        sys.exit(1)
    main(input_fname)

6. Vorverarbeitung: Normalisierung

6.1. Was ist Normalisierung?

Die Normalisierung bei der Vorverarbeitung der Verarbeitung natürlicher Sprache bezieht sich auf die folgende Verarbeitung. Es wird auch "Namensidentifikation" genannt.

  1. Vereinheitlichung der Zeichentypen
  2. Kana auf vollen Winkel vereinen
  3. Das Alphabet wird zu niedrigeren Rängen vereinheitlicht ... usw.
  4. Nummernwechsel
  5. Ersetzen Sie alle Zahlen durch 0 usw.
    • Es gibt wenige Situationen, in denen Zahlen für die Verarbeitung natürlicher Sprache wichtig sind.
  6. Vereinheitlichung von Wörtern mit einem Wörterbuch
  7. Beurteilen Sie "Sony" und "Sony" als gleich und vereinheitlichen Sie die Notation als "Sony".

Die Welt der Normalisierung ist tief, also lasse ich sie hier.

6.2. Implementierung der Normalisierung

Dieses Mal wurde der Einfachheit halber nur die folgende Verarbeitung implementiert. Es ist wahnsinnig einfach.

  1. Vereinheitlichen Sie das Alphabet mit niedrigeren Buchstaben
  2. Ersetzen Sie alle Zahlen durch 0

normalization.py


import re
import pandas as pd
from tqdm import tqdm
import argparse
from pathlib import Path
import sys


def normarize_text(text: str):
    normalized_text = normalize_number(text)
    normalized_text = lower_text(normalized_text)
    return normalized_text


def normalize_number(text: str) -> str:
    """
    pattern = r'\d+'
    replacer = re.compile(pattern)
    result = replacer.sub('0', text)
    """
    #Ersetzen Sie fortlaufende Zahlen durch 0
    replaced_text = re.sub(r'\d+', '0', text)
    return replaced_text


def lower_text(text: str) -> str:
    return text.lower()


def normalize_msgs(wktmsg_ser: pd.Series) -> pd.Series:
    normalized_msg_list = []
    for wktstr in tqdm(wktmsg_ser, desc='normalize words...'):
        normalized = normarize_text(str(wktstr))
        normalized_msg_list.append(normalized)
    normalized_msg_ser = pd.Series(normalized_msg_list)
    return normalized_msg_ser


def main(input_fname: str):
    input_root = '../../data/030_processed'
    output_root = input_root
    # 1. load wakati messages
    msgs_fpath = input_root + '/' + input_fname
    df_msgs = pd.read_csv(msgs_fpath)
    # 2. normalize wakati_msg (update)
    ser_msg = df_msgs.wakati_msg
    df_msgs.wakati_msg = normalize_msgs(ser_msg)
    # 3. save it
    pin = Path(msgs_fpath)
    msgs_ofpath = output_root + '/' + pin.stem + '_norm.csv'
    df_msgs.to_csv(msgs_ofpath, index=False)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("input_fname", help="set input file name", type=str)
    args = parser.parse_args()
    input_fname = args.input_fname
    # input file must been cleaned
    if 'wakati' not in input_fname:
        print('input file name is invalid.: {0}'.format(input_fname))
        print('input file name must include \'wakati\'')
        sys.exit(1)
    main(input_fname)

7. Vorbehandlung: Stoppen Sie die Wortentfernung

7.1. Was ist das Entfernen von Stoppwörtern?

"Stoppwortentfernung" ist der Vorgang zum Entfernen von Stoppwörtern. (Zu viel wie es ist ...)

Was ist ein "** Stoppwort **"?

Nach dem goo Landessprachenwörterbuch

Wörter, die bei Volltextsuchen so häufig vorkommen, dass sie selbst von der Suche ausgeschlossen werden. Bezieht sich auf "ha", "no", "desu" auf Japanisch und a, the, auf Englisch. Stoppwörter.

Aufgaben zur Verarbeitung natürlicher Sprache haben oft den Zweck, "den Kontext eines Satzes zu verstehen". Stoppwörter sind für diesen Zweck nicht erforderlich und sollten entfernt werden.

7.2. Wie entscheide ich das Stoppwort?

Es gibt zwei Hauptpunkte. Dieses Mal werden wir 1 verwenden.

  1. Methode mit einem Wörterbuch (** ← Dieses Mal wird übernommen **)
  2. Methode unter Verwendung der Häufigkeit des Auftretens

Verwenden Sie für Wörterbuchdaten hier.

Es gibt mehrere Gründe für die Wahl von Methode 1.

  • Bei Verwendung eines Wörterbuchs Vorhandenes Wörterbuch ist die Einführung einfach.
  • Bei Verwendung der Häufigkeit des Auftretens müssen nur "Wörter extrahiert werden, die in ** allgemeinen Konversationen ** häufig vorkommen und daher entfernt werden sollten". Wenn die Häufigkeit des Auftretens nur aus den vorliegenden Slack-Daten berechnet wird, werden ** "Wörter, die sich auf spannende Themen beziehen" möglicherweise unbeabsichtigt entfernt **. Daher hielt ich es für erforderlich, separate Daten vorzubereiten und zu aggregieren. .. ...... Es ist ein bisschen schwierig

7.3. Implementierung der Stoppwortentfernung

Entfernt die Wörter, die in den im vorherigen Abschnitt eingeführten Wörterbuchdaten registriert sind. Außerdem wurden die folgenden Zeichen entfernt. Es ist ein Wort, das ich beim Stimmen verschiedener Dinge für nervig hielt.

  • 「-」
  • "-"
  • 「w」
  • 「m」
  • "Lol"

stopword_removal.py


import pandas as pd
import urllib.request
from pathlib import Path
from tqdm import tqdm
import argparse
from pathlib import Path
import sys


def maybe_download(path: str):
    stopword_def_page_url = 'http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt'
    p = Path(path)
    if p.exists():
        print('File already exists.')
    else:
        print('downloading stop words definition ...')
        # Download the file from `url` and save it locally under `file_name`:
        urllib.request.urlretrieve(stopword_def_page_url, path)
    #Stoppwort hinzugefügt
    sw_added_list = [
        '-',
        '- -',
        'w',
        'W',
        'm',
        'Lol'
    ]
    sw_added_str = '\n'.join(sw_added_list)
    with open(path, 'a') as f:
        print(sw_added_str, file=f)


def load_sw_definition(path: str) -> list:
    with open(path, 'r', encoding='utf-8') as f:
        lines = f.read()
        line_list = lines.split('\n')
        line_list = [x for x in line_list if x != '']
    return line_list


def remove_sw_from_text(wktstr: str, stopwords: list) -> str:
    words_list = wktstr.split(' ')
    words_list_swrm = [x for x in words_list if x not in stopwords]
    swremoved_str = ' '.join(words_list_swrm)
    return swremoved_str


def remove_sw_from_msgs(wktmsg_ser: pd.Series, stopwords: list) -> pd.Series:
    swremved_msg_list = []
    for wktstr in tqdm(wktmsg_ser, desc='remove stopwords...'):
        removed_str = remove_sw_from_text(str(wktstr), stopwords)
        swremved_msg_list.append(removed_str)
    swremved_msg_ser = pd.Series(swremved_msg_list)
    return swremved_msg_ser


def main(input_fname: str):
    input_root = '../../data/030_processed'
    output_root = input_root
    # 1. load stop words
    sw_def_fpath = 'stopwords.txt'
    maybe_download(sw_def_fpath)
    stopwords = load_sw_definition(sw_def_fpath)
    # 2. load messages
    msgs_fpath = input_root + '/' + input_fname
    df_msgs = pd.read_csv(msgs_fpath)
    # 3. remove stop words
    ser_msg = df_msgs.wakati_msg
    df_msgs.wakati_msg = remove_sw_from_msgs(ser_msg, stopwords)
    # 4. save it
    pin = Path(msgs_fpath)
    msgs_ofpath = output_root + '/' + pin.stem + '_rmsw.csv'
    df_msgs.to_csv(msgs_ofpath, index=False)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("input_fname", help="set input file name", type=str)
    args = parser.parse_args()
    input_fname = args.input_fname
    # input file must been cleaned
    if 'norm' not in input_fname:
        print('input file name is invalid.: {0}'.format(input_fname))
        print('input file name must include \'norm\'')
        sys.exit(1)
    main(input_fname)

8. Vorverarbeitung: Wichtige Phrasenextraktion (tf-idf)

8.1. Was ist tf-idf?

Es gibt viele großartige Kommentarartikel. Ich habe auf diesen Artikel verwiesen. TF-IDF | Qiita

Hier werde ich grob erklären.

  • tf (Term Frequency)
  • Häufigkeit des Auftretens von Wörtern in einem Dokument ――Wenn es groß ist, "erscheint das Wort oft im Dokument"
  • idf (Inverse Document Frequency)
  • Verhältnis der Dokumente, in denen ein Wort vorkommt (zu allen Dokumenten) ** Umgekehrte Nummer ** ――Wenn es groß ist, erscheint es in anderen Dokumenten nicht viel.

tf-idf ist das Produkt von tf und idf. Mit anderen Worten ** Großes tf-idf ** = ** Erscheint häufig in einem Dokument ** & ** Nicht oft in anderen Dokumenten ** = Wichtig für das Verständnis des ** Kontextes des Dokuments **

8.2. Implementierung der Textverarbeitung durch tf-idf

8.2.1. Was sollte ein Dokument / alle Dokumente sein?

Der Zweck dieser Zeit ist es, die Merkmale von Bemerkungen für einen bestimmten Zeitraum (1 Woche) zu sehen. Daher dachte ich, dass es möglich sein sollte zu verstehen, ** welche Eigenschaften die Bemerkungen eines bestimmten Zeitraums (1 Woche) für alle bisherigen Beiträge haben **.

Deshalb,

  • ** Alle Dokumente **: Alle Beiträge bisher
  • ** 1 Dokument **: Eine Masse von Stellen für einen bestimmten Zeitraum (1 Woche)

Ich habe tf-idf als berechnet.

8.2.2. Implementierung

Schreiben Sie den Prozessablauf einfach

  1. Lesen Sie die Message Mart-Tabelle (vorverarbeitet).
  2. Gruppierung nach Nachrichtenzeitraum
  3. Teilen und gruppieren Sie vergangene Daten ab dem Zeitpunkt der Verarbeitung in 7-Tage-Einheiten
  4. Berechnen Sie tf-idf mit einer Gruppe von Nachrichten als ein Dokument
  5. Extrahieren Sie Wörter, deren tf-idf-Punktzahl über dem Schwellenwert liegt (Ausgabe als Wörterbuch).

important_word_extraction.py


import pandas as pd
import json
from datetime import datetime, date, timedelta, timezone
from pathlib import Path
from sklearn.feature_extraction.text import TfidfVectorizer
JST = timezone(timedelta(hours=+9), 'JST')

#Gruppieren von Nachrichten nach einem bestimmten Zeitraum
def group_msgs_by_term(df_msgs: pd.DataFrame, term: str) -> dict:
    # set term
    term_days = 8
    if term == 'lm':
        term_days = 31
    print('group messages every {0} days'.format(term_days))
    # analyze timestamp
    now_in_sec = (datetime.now(JST) - datetime.fromtimestamp(0, JST)).total_seconds()
    interval_days = timedelta(days=term_days)
    interval_seconds = interval_days.total_seconds()
    oldest_timestamp = df_msgs.min().timestamp
    oldest_ts_in_sec = (datetime.fromtimestamp(oldest_timestamp, JST) - datetime.fromtimestamp(0, JST)).total_seconds()
    loop_num = (abs(now_in_sec - oldest_ts_in_sec) / interval_seconds) + 1
    # extract by term
    dict_msgs_by_term = {}
    df_tmp = df_msgs
    now_tmp = now_in_sec
    for i in range(int(loop_num)):
        # make current term string
        cur_term_s = 'term_ago_{0}'.format(str(i).zfill(3))
        print(cur_term_s)
        # current messages
        df_msgs_cur = df_tmp.query('@now_tmp - timestamp < @interval_seconds')
        df_msgs_other = df_tmp.query('@now_tmp - timestamp >= @interval_seconds')
        # messages does not exist. break.
        if df_msgs_cur.shape[0] == 0:
            break
        # add current messages to dict
        dict_msgs_by_term[cur_term_s] = ' '.join(df_msgs_cur.wakati_msg.dropna().values.tolist())
        # update temp value for next loop
        now_tmp = now_tmp - interval_seconds
        df_tmp = df_msgs_other
    return dict_msgs_by_term

# tf-Extrahieren Sie wichtige Wörter und geben Sie sie als Wörterbuch zurück, während Sie sich auf die IDF-Punktzahl beziehen
def extract_important_word_by_key(feature_names: list, bow_df: pd.DataFrame, uids: list) -> dict:
    # >Schauen Sie sich jede Zeile an und extrahieren Sie wichtige Wörter(tfidf Top X Wörter)
    dict_important_words_by_user = {}
    for uid, (i, scores) in zip(uids, bow_df.iterrows()):
        #Erstellen Sie eine Tabelle mit Wörtern und tfidf-Ergebnissen für den Benutzer
        words_score_tbl = pd.DataFrame()
        words_score_tbl['scores'] = scores
        words_score_tbl['words'] = feature_names
        #Sortieren Sie in absteigender Reihenfolge nach tfidf score
        words_score_tbl = words_score_tbl.sort_values('scores', ascending=False)
        words_score_tbl = words_score_tbl.reset_index()
        # extract : tf-idf score > 0.001
        important_words = words_score_tbl.query('scores > 0.001')
        #Erstellen eines Wörterbuchs für den Benutzer'uid0': {'w0': 0.9, 'w1': 0.87}
        d = {}
        for i, row in important_words.iterrows():
            d[row.words] = row.scores
        #Nur zur Tabelle hinzufügen, wenn das Wörterbuch des Benutzers mindestens ein Wort enthält
        if len(d.keys()) > 0:
            dict_important_words_by_user[uid] = d
    return dict_important_words_by_user

#Extrahieren Sie wichtige Wörter in der angegebenen Periodeneinheit
def extraction_by_term(input_root: str, output_root: str, term: str) -> dict:
    # ---------------------------------------------
    # 1. load messages (processed)
    # ---------------------------------------------
    print('load msgs (all of history and last term) ...')
    msg_fpath = input_root + '/' + 'messages_cleaned_wakati_norm_rmsw.csv'
    df_msgs_all = pd.read_csv(msg_fpath)
    # ---------------------------------------------
    # 2. group messages by term
    # ---------------------------------------------
    print('group messages by term and save it.')
    msgs_grouped_by_term = group_msgs_by_term(df_msgs_all, term)
    msg_grouped_fpath = input_root + '/' + 'messages_grouped_by_term.json'
    with open(msg_grouped_fpath, 'w', encoding='utf-8') as f:
        json.dump(msgs_grouped_by_term, f, ensure_ascii=False, indent=4)
    # ---------------------------------------------
    # 3.Tf für alle Dokumente-IDF-Berechnung
    # ---------------------------------------------
    print('tfidf vectorizing ...')
    # >Wörter in allen Dokumenten sind Spalten und die Anzahl der Dokumente (=Es wird eine Matrix erstellt, in der Benutzer) die Zeile ist. Tf für jedes Element-Es gibt einen IDF-Wert
    tfidf_vectorizer = TfidfVectorizer(token_pattern=u'(?u)\\b\\w+\\b')

    bow_vec = tfidf_vectorizer.fit_transform(msgs_grouped_by_term.values())
    bow_array = bow_vec.toarray()
    bow_df = pd.DataFrame(bow_array,
                        index=msgs_grouped_by_term.keys(),
                        columns=tfidf_vectorizer.get_feature_names())
    # ---------------------------------------------
    # 5. tf-Extrahieren Sie wichtige Wörter basierend auf idf
    # ---------------------------------------------
    print('extract important words ...')
    dict_word_score_by_term = extract_important_word_by_key(
        tfidf_vectorizer.get_feature_names(),
        bow_df, msgs_grouped_by_term.keys())
    return dict_word_score_by_term

9. Visualisierungsprozess mit Wordcloud

9.1 Was ist Wordcloud?

Wörter mit einer hohen Punktzahl sind groß und Wörter mit einer niedrigen Punktzahl sind klein. Verschiedene Werte wie "Auftrittshäufigkeit" und "Wichtigkeit" können für die Partitur frei eingestellt werden.

Offizielles Repository: amueller / word_cloud: octocat:

9.2. Wordcloud-Schriftarten verfügbar

Dieses Mal werde ich dies verwenden. Was ist hausgemachtes abgerundetes M +

9.3. Implementierung von Wordcloud

Im vorherigen Kapitel "8. Vorverarbeitung: Extrahieren wichtiger Wörter (tf-idf)" wurde die folgende JSON-Datei ausgegeben.

important_word_tfidf_by_term.json


{
  "term_ago_000": {
    "Daten": 0.890021,
    "Spiel": 0.780122,
    "Artikel": 0.720025,
    :
  },
  "term_ago_001": {
    "Übersetzung": 0.680021,
    "Daten": 0.620122,
    "deepl": 0.580025,
    :
  },
  :
}

Laden Sie dies und erstellen Sie ein Wordcloud-Bild. Verwenden Sie die Methode "WordCloud.generate_from_frequancies ()".

wordcloud_from_score.py


from wordcloud import WordCloud
import matplotlib.pyplot as plt
import json
from pathlib import Path
from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd
from tqdm import tqdm
import sys
import argparse


def main(input_fname: str):
    input_root = '../../data/031_features'
    output_root = './wordcloud_by_user' if 'by_user' in input_fname else './wordcloud_by_term'
    p = Path(output_root)
    if p.exists() is False:
        p.mkdir()
    # -------------------------------------
    # 1. load tf-idf score dictionary
    # -------------------------------------
    d_word_score_by_user = {}
    tfidf_fpath = input_root + '/' + input_fname
    with open(tfidf_fpath, 'r', encoding='utf-8') as f:
        d_word_score_by_user = json.load(f)
    # -------------------------------------
    # 2. gen word cloud from score
    # -------------------------------------
    fontpath = './rounded-l-mplus-1c-regular.ttf'
    for uname, d_word_score in tqdm(d_word_score_by_user.items(), desc='word cloud ...'):
        # img file name is user.png
        uname = str(uname).replace('/', '-')
        out_img_fpath = output_root + '/' + uname + '.png'
        # gen
        wc = WordCloud(
            background_color='white',
            font_path=fontpath,
            width=900, height=600,
            collocations=False
            )
        wc.generate_from_frequencies(d_word_score)
        wc.to_file(out_img_fpath)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("input_fname", help="set input file name", type=str)
    args = parser.parse_args()
    input_fname = args.input_fname
    main(input_fname)

10. Bonus

Artikel, auf die ich mich besonders bezog

Andere Referenzmaterialien (große Menge) sind in hier: octocat: zusammengefasst.

Werbung

Dieses Mal verwenden wir Daten aus der Slack-Community namens Data Learning Guild. Die Data Learning Guild ist eine Online-Community von Datenanalyse-Mitarbeitern. Wenn Sie interessiert sind, überprüfen Sie bitte hier.

Offizielle Homepage der Data Learning Guild

Adventskalender 2019 der Data Learning Guild

Recommended Posts

[Verarbeitung natürlicher Sprache] Ich habe diese Woche versucht, die aktuellen Themen in der Slack-Community zu visualisieren
[Verarbeitung natürlicher Sprache] Ich habe versucht, die Bemerkungen jedes Mitglieds in der Slack-Community zu visualisieren
Ich habe versucht, mit der Bibliothek GiNZA zur Verarbeitung natürlicher Sprache eindeutige Ausdrücke zu extrahieren
Ich habe versucht, die Zeit und die Zeit der C-Sprache zu veranschaulichen
Verwenden Sie die Swagger-Benutzeroberfläche. Wenn Sie einen Swagger-UI-ähnlichen Stil wünschen, kopieren Sie einfach die gesamte Datei unter / dist unter https://github.com/swagger-api/swagger-ui. Danach schreiben Sie einfach hier in index.html die URL der yml-Datei um. Wenn Sie der Meinung sind, dass Sie @ApiModel oder @ApiModelProperty nicht finden können, wurden übrigens anscheinend alle Anmerkungen zur Schemadefinition in @Schema integriert. Emotionsanalyse mit Java, Swagger, OpenAPI, Micronaut Natural Language Processing! Ich habe versucht, die Bewertung anhand des Überprüfungstextes vorherzusagen
Ich habe versucht, natürliche Sprache mit Transformatoren zu verarbeiten.
Ich habe versucht, die Version 2020 mit 100 Sprachverarbeitung zu lösen [Kapitel 3: Reguläre Ausdrücke 25-29]
[Python] [Verarbeitung natürlicher Sprache] Ich habe Deep Learning ausprobiert (auf Japanisch von Grund auf neu erstellt)
Ich habe versucht, die 2020-Version von 100 Sprachverarbeitungsproblemen zu lösen [Kapitel 1: Vorbereitungsbewegung 00-04]
Ich habe versucht, die 2020-Version von 100 Sprachverarbeitungsproblemen zu lösen [Kapitel 1: Vorbereitungsbewegung 05-09]
Ich habe versucht, die in Python installierten Pakete grafisch darzustellen
Ich habe versucht, die Spacha-Informationen von VTuber zu visualisieren
Ich habe versucht, die Sprache mit CNN + Melspectogram zu identifizieren
Ich habe versucht zu überprüfen, ob die Natural Language API (Emotionsanalyse) Net Slang unterstützt.
[Erste Datenwissenschaft ⑥] Ich habe versucht, den Marktpreis von Restaurants in Tokio zu visualisieren
Ich habe versucht, den in Pandas häufig verwendeten Code zusammenzufassen
[Python] Ich habe versucht, die folgende Beziehung von Twitter zu visualisieren
Ich habe versucht, die im Geschäftsleben häufig verwendeten Befehle zusammenzufassen
Ich habe versucht, die Mail-Sendefunktion in Python zu implementieren
[TF] Ich habe versucht, das Lernergebnis mit Tensorboard zu visualisieren
Ich habe versucht, 100 Sprachverarbeitung klopfen 2020
Probieren Sie das Buch "Einführung in die Verarbeitung natürlicher Sprachanwendungen in 15 Schritten" aus - Kapitel 2 Schritt 06 Memo "Identifier"
Probieren Sie das Buch "Einführung in die Verarbeitung natürlicher Sprachanwendungen in 15 Schritten" aus - Kapitel 2 Schritt 02 Memo "Vorverarbeitung"
Probieren Sie das Buch "Einführung in die Verarbeitung natürlicher Sprachanwendungen in 15 Schritten" aus - Kapitel 2 Schritt 07 Memo "Evaluation"
Ich habe versucht, den Datenverkehr mit WebSocket in Echtzeit zu beschreiben
Ich habe versucht, die Lesezeichen zu visualisieren, die mit Doc2Vec und PCA nach Slack fliegen
Ich habe versucht, die Version 2020 mit 100 Sprachverarbeitung zu lösen [Kapitel 2: UNIX-Befehle 10-14]
Ich habe versucht, den Getränkepräferenzdatensatz durch Tensorzerlegung zu visualisieren.
Ich habe versucht, das Bild mit OpenCV im "Skizzenstil" zu verarbeiten
Dockerfile mit den notwendigen Bibliotheken für die Verarbeitung natürlicher Sprache mit Python
Ich habe versucht, das Bild mit OpenCV im "Bleistift-Zeichenstil" zu verarbeiten
Ich habe versucht, die Version 2020 mit 100 Sprachverarbeitung zu lösen [Kapitel 2: UNIX-Befehle 15-19]
[Word2vec] Lassen Sie uns das Ergebnis der Verarbeitung von Unternehmensbewertungen in natürlicher Sprache visualisieren
Ich habe versucht, den allgemeinen Zustand der VTuber-Kanalbetrachter zu visualisieren
Ich habe versucht, 100 Sprachverarbeitung klopfen 2020: Kapitel 3
Ich habe versucht, 100 Sprachverarbeitung klopfen 2020: Kapitel 1
Vorbereitung zum Starten der Verarbeitung natürlicher Sprache
Ich habe versucht, den Ball zu bewegen
Ich habe versucht, 100 Sprachverarbeitung zu klopfen 2020: Kapitel 2
Ich habe versucht, 100 Sprachverarbeitung zu klopfen 2020: Kapitel 4
Ich habe versucht, den Abschnitt zu schätzen.
Probieren Sie das Buch "Einführung in die Verarbeitung natürlicher Sprachanwendungen in 15 Schritten" - Kapitel 4 Schritt 14 Memo "Hyperparametersuche"
Probieren Sie das Buch "Einführung in die Entwicklung natürlicher Anwendungen in 15 Schritten" aus - Kapitel 2 Schritt 04 Memo "Feature Extraction"
Probieren Sie das Buch "Einführung in die Verarbeitung natürlicher Sprachanwendungen in 15 Schritten" aus - Kapitel 4 Schritt 15 Memo "Datenerfassung"
Probieren Sie das Buch "Einführung in die Verarbeitung natürlicher Sprachanwendungen in 15 Schritten" - Kapitel 3 Schritt 08 Memo "Einführung in neuronale Netze".
Probieren Sie das Buch "Einführung in die Entwicklung natürlicher Anwendungen in 15 Schritten" aus - Kapitel 2 Schritt 05 Memo "Feature Quantity Conversion"
Probieren Sie das Buch "Einführung in die Verarbeitung natürlicher Sprachanwendungen in 15 Schritten" aus - Kapitel 3 Schritt 11 Memo "Worteinbettungen"
[Python] Ich habe versucht, die Nacht der Galaxienbahn mit WordCloud zu visualisieren!
Ich habe versucht, die Trefferergebnisse von Hachinai mithilfe der Bildverarbeitung zu erhalten
Ich habe versucht, die Altersgruppe und die Ratenverteilung von Atcoder zu visualisieren
[C-Sprache] Ich möchte Zufallszahlen im angegebenen Bereich generieren
Ich habe versucht, den Text des Romans "Wetterkind" mit Word Cloud zu visualisieren
Ich habe versucht, das Modell mit der Low-Code-Bibliothek für maschinelles Lernen "PyCaret" zu visualisieren.
Ich habe versucht, den Höhenwert von DTM in einem Diagramm anzuzeigen
Ich habe das VGG16-Modell mit Keras implementiert und versucht, CIFAR10 zu identifizieren
Ich habe versucht, das RWA-Modell (Recurrent Weighted Average) in Keras zu trainieren
Ich habe versucht, PLSA in Python zu implementieren
Ich habe versucht, den Befehl umask zusammenzufassen
Ich habe versucht, Permutation in Python zu implementieren