Pickleファイルの読み込み
サイズの大きなCSVをそのまま読み込むと4分とか時間がかかって困ることがあったので、pickleに変換保存して以降はそれを読み込むことで早くなった。pickleとはデータフレームに限らずクラスなどオブジェクトであればなんでもpickleとして保存できるみたい。
詳しいことはわからないけど、読み込むのは早くなる一方でファイルが壊れると直しようがないみたい。大事なデータはCSVでも持っておいた方がよさそう。あと、pickleだとファイルサイズが大きくなるみたい。
参考サイト
csvをpickleで保存して読み取りを速くする - Qiita
やってみた
Ubiquant Market Predictionのデータ読み込みについてやってみた。データ数は以下の通り300万ほど。
train.shape (3141410, 304)
CSVをそのまま読み込むと7分かかった。
%%time path = '../input/ubiquant-market-prediction/train.csv' basecols = ['row_id', 'time_id', 'investment_id', 'target'] features = [f'f_{i}' for i in range(300)] dtypes = { 'row_id': 'str', 'time_id': 'uint16', 'investment_id': 'uint16', 'target': 'float32', } for col in features: dtypes[col] = 'float32' train = pd.read_csv( path, usecols= basecols+features, dtype=dtypes ) CPU times: user 4min 20s, sys: 17.8 s, total: 4min 38s Wall time: 7min 9s
pickleで保存してから読み込むと1分もかからず。これはこっちのがよいわ。
%%time train_pkl = pd.read_pickle("./train.pkl") CPU times: user 1.77 s, sys: 5.79 s, total: 7.56 s Wall time: 43.7 s
Jigsaw Rate Severity of Toxic Comments(正規表現エスケープ+顔文字+大文字小文字)
置換その2。正規表現のエスケープ+顔文字抽出+大文字小文字。
参考サイト
やってみた
正規表現のエスケープ
最初のきっかけはnotebookそのまま使ったけど置換できてなくない?から。正規表現モジュールre
を使わずに.replaceの中で正規表現のエスケープの記載を入れてもうまくいかないよなと。多分そういうことだと理解。他のNotebookをそのまま使う前にはちょっと確認しないとなと。
tmp = "You, sir, are my hero. Any chance you remember what page that's on?" tmp1 = tmp.replace(r"\'s", " ") tmp2 = re.sub(r"\'s", " ", tmp) print(f"tmp:{tmp}") # tmp:You, sir, are my hero. Any chance you remember what page that's on? print(f"tmp1:{tmp1}") # tmp1:You, sir, are my hero. Any chance you remember what page that's on? print(f"tmp2:{tmp2}") # tmp2:You, sir, are my hero. Any chance you remember what page that on?
def short_word_rep(text): text = text.replace(r"what's", "what is ") text = text.replace(r"can't", "cannot ") text = text.replace(r"n't", " not ") text = text.replace(r"i'm", "i am ") text = text.replace(r"I'm", "i am ") # text = text.replace(r"\'ve", " have ") # text = text.replace(r"\'re", " are ") # text = text.replace(r"\'d", " would ") # text = text.replace(r"\'ll", " will ") # text = text.replace(r"\'scuse", " excuse ") # text = text.replace(r"\'s", " ") text= re.sub(r"\'s", " ", text) text= re.sub(r"\'ll", " will ", text) text= re.sub(r"\'scuse", " excuse ", text) text= re.sub(r"\'d", " would ", text) text= re.sub(r"\'re'", " are ", text) text= re.sub(r"\'ve'", " have ", text) return text def text_cleaning(text): text = short_word_rep(text) template = re.compile(r'https?://\S+|www\.\S+') text = template.sub(r'', text) soup = BeautifulSoup(text, 'lxml') only_text = soup.get_text() text = only_text emoji_pattern = re.compile("[" u"\U0001F600-\U0001F64F" u"\U0001F300-\U0001F5FF" u"\U0001F680-\U0001F6FF" u"\U0001F1E0-\U0001F1FF" u"\U00002702-\U000027B0" u"\U000024C2-\U0001F251" "]+", flags = re.UNICODE) text = emoji_pattern.sub(r'', text) # text = re.sub(r"[^a-zA-Z\d]", " ", text) text = re.sub(r"[^a-zA-Z\d|!]", " ", text) text = re.sub(r"!+", r"!", text) text = re.sub(' +', ' ', text) text = text.strip() return text clean_df = df.copy(deep= True) clean_df["text"] = df["text"].apply(text_cleaning)
顔文字
試しに「:(」という顔文字だけを抽出。この顔文字がある52コメンと少ないけど攻撃的なスコア高い!この情報を落とすのはよくにだろう。こんなに低いのであれば、この顔文字があるかも攻撃的スコア自体に組み込んでもよいのでは??とりあえずsad
という単語に変換するけど。。
df["face"] = df["text"].apply(lambda x: re.search(r"\:\(", x)) df["face_flg"] = df["face"].apply(lambda x: "NOT_sad_face" if x is None else "sad_face") agg_d = { "y": ["mean", "count"] } group = df.groupby("face_flg").agg(agg_d) group.columns = ["_".join(i) for i in group.columns] group
tmp = df[df.face_flg == "sad_face"].iloc[0,0] tmp_sub = re.sub(r"\:\(", "sad", tmp) print(f"tmp:{tmp}") print(f"tmp1:{tmp_sub}")
tmp:closedmouth is a DICK-FUCK this guy is a jackass who sux cock 4 a fuckin job y the fuck does the trans do it?????cuz its into tht kinda shit >:( tmp1:closedmouth is a DICK-FUCK this guy is a jackass who sux cock 4 a fuckin job y the fuck does the trans do it?????cuz its into tht kinda shit > sad
逆に笑顔顔文字はスコアが低いわけでもなかった。よって情報なしということで削除してしまう。
df["face"] = df["text"].apply(lambda x: re.search(r"\(\:", x)) df["face_flg"] = df["face"].apply(lambda x: "NOT_happy_face0" if x is None else "happy_face0") # df["face_flg"] = df["face_flg"].mask(df.face is not None, "happy_face0") # df["face"] = df["text"].apply(lambda x: re.search(r"\:\)", x)) # # df["face_flg"] = df["face"].apply(lambda x: x if x is None else "happy_face1") # # df["face_flg"] = df["face"].apply(lambda x: "NOT_happy_face" if x is None else "happy_face") # df["face_flg"] = df["face_flg"].mask(df.face is not None, "happy_face1") agg_d = { "y": ["mean", "count"] } group = df.groupby("face_flg").agg(agg_d) group.columns = ["_".join(i) for i in group.columns] group
大文字小文字
GOODとgoodだったらなんとなく大文字の方が強い表現に見えるので。単語内に2文字以上大文字があったら大文字に、1文字以下であれば小文字にする。
text = "He is Very VeRy GOOD boy YEah" text = text.split(" ") text = [str.upper(i) if len(re.findall(r"[A-Z]", i)) >= 2 else str.lower(i) for i in text] text = " ".join(text) text # 'he is very VERY GOOD boy YEAH'
実際、大文字が10文字以上あるテキストの攻撃性y
は通常のものより高そう。
tmp = clean_df.copy(deep= True) tmp["upper_flg"] = tmp["text"].apply(lambda x: "zero" if len(re.findall(r"[A-Z]", x)) <= 10 else "NOT_zero") agg_d = { "y": ["mean", "std", "count"] } tmp.groupby("upper_flg").agg(agg_d)
Jigsaw Rate Severity of Toxic Comments(文字列の正規表現)
文字列について記号やhttpリンクの置換を行う上では文字列の正規表現についてはやらないといかんなと思い。
参考サイト
やってみる
「文字列から数字だけ抽出したいな~~」というときに、「0,1,2,,,,9」のどれかに一致するやつを検索するより「\d」と一文字で表せたら楽だよね(それを連続しているもの単位で抽出できたら尚更)という話と理解。まずはhttpリンクを抽出。
r'https?://\S+|www\.\S+'
は縦線「|」は和集合なのでhttps?://\S+
とwww\.\S+
のどちらかに一致するものを抽出。?
は直前文字が0or1文字なのでhttp
orhttps
、://
はそのままで、\S+
は任意の空白文字が1回以上の繰り返し。www\.\S+
は\.
が任意の一文字\S+
が任意の空白以外文字。違いは。。?
template = re.compile(r'https?://\S+|www\.\S+') tmp0 = df["text"].apply(lambda x: template.search(x)) # 具体的な文字列で一応確認 text = tmp0.dropna().iloc[0].group() # http://www.laayouneinvest.ma/fr/index.asp) template.sub(r"", text) # ''
a-zA-Z\d
について、a-z
は小文字アルファベットとA-Z
は大文字アルファベットと\d
は数字0-9。^
はそれ以外なので、記号をすべて落とす。個人的に!は記号だけど残してよいのでは?と思う。!!!が多いと何となく攻撃的に見えそう。実際に!を1つ以上含むコメントと含まないものでは、含むものの方がスコアyが大きい(攻撃的)。ということで、!だけは残す。ただ、10個も連続しているやつはなんか邪魔になりそうなので一つにしてしまう。
text = "dfd.adjaofspfdu304738" text = re.sub(r"[^a-zA-Z\d]", " ", text) # 'dfd adjaofspfdu304738'
df["extram_cnt"] = df["text"].apply(lambda x: x.count("!")) df["extram_cnt_01"] = df["extram_cnt"].apply(lambda x: "not_zero" if x != 0 else "zero") agg_d = { "y": ["mean", "count"] } tmp = df.groupby("extram_cnt_01").agg(agg_d) tmp.columns = ["_".join(i) for i in tmp.columns] tmp
text = "dfd!!adjaofspfd@@@u304738" text = re.sub(r"[^a-zA-Z\d]", " ", text) # 'dfd adjaofspfd u304738' text = re.sub(r"[^a-zA-Z\d|!]", " ", text) # 'dfd!!adjaofspfd u304738' text = "dfhafos!!!fhdof!!!" text = re.sub(r"!+", r"!", text) # 'dfhafos!fhdof!'
あとは連続するスペースを削除。
text = "dfd!!adjaofs pfd@@@u304738" text = re.sub(' +', ' ', text) # 'dfd!!adjaofs pfd@@@u304738'
最後にユニコードによって絵文字を除去。日本語やハングルも削除できた。ユニコードの詳細はちょっとわからず。。これは所与として受け入れる!一応みてみると下2つのユニコードだけでできた。絵文字を含むテキストは1259 (/159571)。日本語もそれほど汚い文字は入ってなかったので削除してしまってよいでしょう!
def emoji_flg(text): emoji_pattern = re.compile("[" u"\U0001F600-\U0001F64F" u"\U0001F300-\U0001F5FF" u"\U0001F680-\U0001F6FF" u"\U0001F1E0-\U0001F1FF" u"\U00002702-\U000027B0" u"\U000024C2-\U0001F251" "]+", flags = re.UNICODE) # text = emoji_pattern.sub(r'', text) text = emoji_pattern.search(text) return text if text is None else text.group() df["emoji_flg"] = df["text"].apply(emoji_flg) len(df.emoji_flg.dropna()) # 1259
あと、タグを外す。そのためにbeautifulsoupのget_textを使用。<*>みたいなやつが抜ける。
from bs4 import BeautifulSoup def f_test(text): soup = BeautifulSoup(text, 'lxml') only_text = soup.get_text() return True if text == only_text else False df["bs_text"] = df["text"].apply(f_test) bs_diff = df[~df.bs_text].copy() i = 0 text = bs_diff.iloc[i,0] soup = BeautifulSoup(text, 'lxml') only_text = soup.get_text() print(f"text:{text}") print(f"only_text:{only_text}")
text:1)I am an admin, so I guess you were only re-stating the obvious, and 2) the article has been restored and reverted to the more complete version. - '''' - <*> only_text:1)I am an admin, so I guess you were only re-stating the obvious, and 2) the article has been restored and reverted to the more complete version. - '''' -
Jigsaw Rate Severity of Toxic Comments(googletransで翻訳)
文字列データは逆翻訳というもので水増しするといいらしい!
参考サイト
逆翻訳を使ったテキストデータ水増し – Kaggle Note
Using Google Translate for NLP Augmentation | Kaggle
やってみる
早速エラーだ!
!pip install --quiet googletrans from googletrans import Translator translator = Translator() #この2行で日本語から英語に翻訳しています #この処理のときにgoogle翻訳のサービスを使っている # translation = translator.translate("ここに翻訳したい文字列を入れる", src='ja', dest="en") translation = translator.translate("ここに翻訳したい文字列を入れる", dest="en") #翻訳されて英語を出力できます! print(translation.text)
AttributeError: 'NoneType' object has no attribute 'group'
調べてみたところ、新しいライブラリをインストールするとうまくいくとのこと、たしかにうまくいった!一度読み込んだ古いほうのライブラリをアンインストールせずに新しいバージョンのライブラリをインストールしようとしてもエラーはきえなかったけど、新しくカーネル立ち上げて新しいバージョン読み込んだらうまくいった。理由はあまりわからない。
!pip install --quiet googletrans==4.0.0-rc1 from googletrans import Translator translator = Translator() #この2行で日本語から英語に翻訳しています #この処理のときにgoogle翻訳のサービスを使っている translation = translator.translate("ここに翻訳したい文字列を入れる", src='ja', dest="en") # translation = translator.translate("ここに翻訳したい文字列を入れる", dest="en") #翻訳されて英語を出力できます! print(translation.text) # Insert the string you want to translate here
ひとまずちょいと元のテキストが何語なのかを判定。英語以外が混じっているかもしれないし。そしたらなんかエラーでてきた。。理由がよくわからないが、おそらく多くの処理をかけすぎてはじかれているっぽい。エラーハンドリングしてみる。ほぼほぼ英語ですな。ただ、これものすごい時間がかかった。。
def detect_lang(x): try: ret = translator.detect(x).lang except Exception as e: ret = "dummy" return ret translator = Translator() i= 0 step = 500 adds = [] while i <= 10000: # while i <= len(df): print(i) i+= step add = df["text"].iloc[i - step:i].apply(detect_lang) adds.append(add) add_lang = pd.concat(adds, axis= 0, sort= True) add_lang.name = "org_lang" df = pd.merge(df, add_lang, left_index= True, right_index= True, how= "left") df.org_lang.value_counts()
en 7131 dummy 3335 fr 5 de 5 es 2 pl 2 ja 2 fy 2 nl 2 yo 1 ar 1 mt 1 hr 1 pt 1 cy 1 el 1 hu 1 vi 1 ha 1 iw 1 gd 1 bg 1 eo 1 Name: org_lang, dtype: int64
諸々やりましたが翻訳するには先にテキストをきれいにしてからの方がよさそうなので、そっち先にやります!
Jigsaw Rate Severity of Toxic Comments(絵文字削除)
前処理その1:絵文字の処理
まずは絵文字を含むテキストを判定。含まれた絵文字を削除するかどうかはまた判断。
参考サイト
SNSテキストから顔文字・絵文字・URLを抽出する - Qiita
やってみた
まずは絵文字を含むテキストを抽出。テキストをみてみたところ、絵文字が攻撃的な意味を直接もつものはそれほどなかった💩。👎とかあんのかなと思ってたけど。なので、絵文字を削除しても情報は抜けないと思うので削除。for文でひとつずつ消したけど、もうちょっとうまくできないものかねえ。
import emoji import unicodedata def emoji_detect(text): def extract_emoji(words): return [w for w in words if w in emoji.UNICODE_EMOJI_ENGLISH] text = unicodedata.normalize('NFKC', text) # NFKC正規化 emojis = extract_emoji(text) return emojis df["text_emoji"] = df["text"].apply(lambda x: emoji_detect(x)) df["emoji_check"] = df["text_emoji"].apply(lambda x: True if len(x) != 0 else False) tmp_emoji = df.loc[df['emoji_check']].copy() tmp_NOT_emoji = df.loc[~df["emoji_check"]].copy() emoji_list = [] for i in range(len(tmp_emoji.text_emoji)): emoji_list.extend(tmp_emoji.text_emoji.iloc[i]) emoji_list = list(set(emoji_list)) for i in emoji_list: tmp_emoji["text"] = tmp_emoji["text"].str.replace(i, "") print(tmp_emoji.shape) # (1133, 4) print(tmp_NOT_emoji.shape) # (158438, 4) print(emoji_list) # ['♠', '☎', '♨', '☀' '✒', '▶', '🎄',... df = pd.concat([tmp_emoji, tmp_NOT_emoji], axis= 0, sort= True).loc[:, ["text", "y"]].copy() del tmp_emoji, tmp_NOT_emoji
Jigsaw Rate Severity of Toxic Comments(TF-IDF)
sklearnでTF-IDF。用意された文書の集まりから語彙リストのようなものを作成して、頻度が低いほど点数高くする(IDF)。各文書について語彙ごとの出現頻度も出す(TF)。それを掛け合わせることで、各文書ごとに語彙のベクトル的なものを作成。
sklearn.feature_extraction.text.TfidfVectorizer — scikit-learn 1.0.2 documentation
https://www.takapy.work/entry/2019/01/14/141423
https://gotutiyan.hatenablog.com/entry/2020/09/10/181919
手を動かしてみる
こんなデータをいじる。
とりあえず参考にしたNotebookの通りのパラメータとかでやってみる。termsを最初の10個ほどみてみたけど、単語がばらされすぎてこれでうまく特徴づけられるのか??という感じ。analyzer
は分析上は意図をもってchar_wb
にしているみたいではあるけど。。とにもかくにもワードごとのベクトルとしたところでその要素数が43448個もあるのではなにが特徴づけているのかもよくわからない。。ということで、次元削減したい→PCAしてみる。
from sklearn.feature_extraction.text import TfidfVectorizer # for convert a collection of raw documents to a matrix of TF-IDF features # TF-IDFの計算 tfidf_vectorizer = TfidfVectorizer( # use_idf=True, # lowercase=False, # max_features= 20000, analyzer= "char_wb", min_df= 3, # 語彙構築の際にこれより頻度が低いワード削除 max_df= 0.5, # 語彙構築の際にこれより頻度が高いワード削除 ngram_range= (3,5), ) # ("vect3", TfidfVectorizer(min_df= 3, max_df=0.5, analyzer = 'char_wb', ngram_range = (3,5))), # 文章内の全単語のTfidf値を取得 tfidf_matrix = tfidf_vectorizer.fit_transform(df['text']) # index 順の単語リスト terms = tfidf_vectorizer.get_feature_names() # 単語毎のtfidf値配列:TF-IDF 行列 (numpy の ndarray 形式で取得される) # 1つ目の文書に対する、各単語のベクトル値 # 2つ目の文書に対する、各単語のベクトル値 # ・・・ # が取得できる(文書の数 * 全単語数)の配列になる。(toarray()で密行列に変換) tfidfs = tfidf_matrix.toarray() print(f"term length: {len(terms)}") print(f"tfidfs shape: {tfidfs.shape}") terms[:10]
term length: 43448 tfidfs shape: (5000, 43448) [' ! ', ' !!', ' !! ', ' !!!', ' !!! ', ' !!!!', ' " ', ' ""', ' "" ', ' """']
PCAして主成分の大きい要素をみるとたしかにfから始まる言葉がたくさん☆そしてこれをみるとanalyzer
をcharter_wb
とすることで単語の中でngramに分けたり可能→1つのfuckからfuc, uck,,,,とカウントを増やしてfuckの存在感を高められているのかもしれないと思うようになった。あと!が多いやつは攻撃的に見えるのでしょうね。!の数はよい情報かもしれん。
from sklearn.decomposition import PCA pca = PCA(n_components=2) pca.fit(tfidfs) comp = pd.DataFrame(pca.components_, columns= terms, index= ["PCA1", "PCA2"]) comp1 = comp.loc["PCA1",:] comp1.abs().sort_values(ascending= False).head(15) comp2 = comp.loc["PCA2",:] comp2.abs().sort_values(ascending= False).head(15)
Jigsaw Rate Severity of Toxic Comments(文字列の変換)
言語使うものとかもやってみようかな~と。初心者向けの簡単にスコア提出までの流れをまとめてるNotebookをみていく。
Most VoteのNotebookをみると1. TF-IDF 2. リッジ回帰 3. RoBERTaの3つがメインっぽい。とりあえずTF-IDFとリッジを使ってるNotebookを手を動かしてみようと思う。
文字データをきれいに
文字列の変換も色々書き方がアルンデスネ。ちょっと細かくみれてないです
def clean(data, col): # Replace each occurrence of pattern/regex in the Series/Index # Clean some punctutations data[col] = data[col].str.replace('\n', ' \n ') data[col] = data[col].str.replace(r'([a-zA-Z]+)([/!?.])([a-zA-Z]+)',r'\1 \2 \3') # Replace repeating characters more than 3 times to length of 3 data[col] = data[col].str.replace(r'([*!?\'])\1\1{2,}',r'\1\1\1') # Add space around repeating characters data[col] = data[col].str.replace(r'([*!?\']+)',r' \1 ') # patterns with repeating characters data[col] = data[col].str.replace(r'([a-zA-Z])\1{2,}\b',r'\1\1') data[col] = data[col].str.replace(r'([a-zA-Z])\1\1{2,}\B',r'\1\1\1') data[col] = data[col].str.replace(r'[ ]{2,}',' ').str.strip() return data # the function returns the processed value