自分の好きなシリーズ動画である羅小黒戦記のアニメ映画版は日本で上映されたので、
日本人の方からこの動画をどう見ているのかを知りたく、ネット上のコメントを取得して分析してみました。
良い分析結果得られなかったですが、今回とりあえずいろいろ手法の練習にし、今後は今回の結果をもとに、チューニングと精度向上のほうに進みたいと思います。
該当映画コメントサイトはこちらです↓↓↓
Filmarks羅小黒戦記 ぼくが選ぶ未来の映画情報・感想・評価>
1.データクロージング
まずサイトからデータを取得してきました。コメント自体はネタバレあり、ネタバレなしという2種類に分けられたので、それぞれ取ってきました。
①ネタバレなしデータの取得
from selenium import webdriver
import time
browser = webdriver.Chrome()
>サイトのコメントを見ていくと、2021年1月時点、およそ300ページほどあり、一ページつづ10本コメントがあります。そこで自動的にページを繰って全部のデータを取ってもらえると嬉しいですね.
色々記事を参考してましたが、難しいそうなコードを書かれて、いまいち理解できず・・・
自分なりに地味に考えたのは:最終ページ数を取ってきて、Range関数を使ってコツコツで集めるとのやり方でした。
サイトの要素を確認してみると、最終ページを示している 【>|】 という印は
href=”/movies/86613?page=338”で指定されています。要するに、ここの338という数字は求めている最終ページ数ですね!

browser.get('https://filmarks.com/movies/86613/no_spoiler?page=1')
time.sleep(3)
links = []
for link in browser.find_elements_by_xpath("//*[@href]"):
if "page" in str(link.get_attribute('href')):
links.append(link.get_attribute('href'))
取ってきたリンクいくつがありました、それぞれはページ2ー5、next page行く、最終ページ行くと見れますね。
今回はまずネタバレなしのデータを取得するので、page289はネタバレなしの合計ページ数ですね。
サイト上の状況と一致していることが確認しました。


#ページ数のみを取得します。
times_no_spoiler = links[-1].split("page=")[-1]
times_no_spoiler
#289
ほか、ユーザー名、ユーザーがつけたスコア、ユーザーの登録頻度の情報も取ってきたいので、一つの関数にまとめてみました。
ただ最終ページは必ずコメント10本とは言えないので、最終ページ寸前のページまでしかこの関数を使ってません。
最終ページは別途で処理してます。
def find_texts(times,url):
texts = [] #コメント
scores = [] #スコア
users = [] #ユーザー名
conditions = [] #ユーザー登録頻度
for i in range(1,int(times)):
browser.get(url+str(i))
element_text = browser.find_elements('css selector','.p-mark__review')
element_score = browser.find_elements('css selector','.c-rating__score')
text = [m.text for m in element_text]
score = [l.text for l in element_score]
user = [n.get_attribute('alt') for n in browser.find_elements_by_tag_name('img')]
condition = [c.get_attribute('loading') for c in browser.find_elements_by_tag_name('img')]
texts.append(text)
scores.append(score[1:11])
users.append(user[3:13])
conditions.append(condition[3:13])
return texts,scores,users,conditions
早速ネタバレなしのデータを取ってきました。
(いえ、嘘です、早速ではなく、結構時間かかりました。)
results_no_spoiler = find_texts(times_no_spoiler)
text_no_spoiler,score_no_spoiler,user_no_spoiler,condition_no_spoiler = results_no_spoiler
print(len(sum(results_no_spoiler[0],[])))
print(len(sum(results_no_spoiler[1],[])))
print(len(sum(results_no_spoiler[2],[])))
print(len(sum(results_no_spoiler[3],[])))
#2880
#2880
#2880
#2880
問題がなさそうですね!引き続きネタバレなしの最終ページを!
(やり方地味過ぎて申し訳ないです・・・)
browser = webdriver.Chrome()
url_no_spoiler ='https://filmarks.com/movies/86613/no_spoiler?page='
browser.get(url_no_spoiler+str(times_no_spoiler))
element_text_last_no_spoiler = browser.find_elements('css selector','.p-mark__review')
element_score_last_no_spoiler = browser.find_elements('css selector','.c-rating__score')
text_last_no_spoiler = [m.text for m in element_text_last_no_spoiler]
score_last_no_spoiler = [l.text for l in element_score_last_no_spoiler]
user_last_no_spoiler = [n.get_attribute('alt') for n in browser.find_elements_by_tag_name('img')]
condition_last_no_spoiler = [c.get_attribute('loading') for c in browser.find_elements_by_tag_name('img')]
print(len(text_last_no_spoiler))
#7
最終ページ7本のコメントしかないようですね。実際にサイトの方で確認しましたら、一致でした。
texts_no_spoiler = sum(text_no_spoiler,[])+text_last_no_spoiler
scores_no_spoiler = sum(score_no_spoiler,[])+score_last_no_spoiler[1:8]
users_no_spoiler = sum(user_no_spoiler,[])+user_last_no_spoiler[3:10]
conditions_no_spoiler = sum(condition_no_spoiler,[])+condition_last_no_spoiler[3:10]
import pandas as pd
df_no_spoiler = pd.DataFrame({'テキスト':texts_no_spoiler,
'スコア':scores_no_spoiler,
'ユーザー':users_no_spoiler,
'登録頻度':conditions_no_spoiler})
df_no_spoiler['ネタバレ'] = 'なし'
これでネタバレなしのデータ取得できました。

これでネタバレありのデータも同様な方法で取得し、最後できた全件データは3374件でした。

2.前処理と分かち書き
前処理では、絵文字、iphone系絵文字、改行記号、省略記号(…)、英語を小文字に統一、正規化、url、数字、各種記号の処理を行いました。
import neologdn
import re
import emoji
import string
def filter(desstr, restr=''):
#emoji
res = re.compile(u'[\U00010000-\U0010ffff]')
res_emoji = res.sub(restr,desstr)
#iPhoneのemoji
res_iphone_emoji = ''.join(c for c in desstr if c not in emoji.UNICODE_EMOJI)
#改行記号
res_linebreak = ''.join(res_iphone_emoji.replace('<br/>','').split())
#省略記号
res_ellipsis = ''.join(res_linebreak.replace('…','').split())
#大文字→小文字
res_uppertolower = res_ellipsis.lower()
#正規化
res_normalization = neologdn.normalize(res_uppertolower)
#url
res_url = re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-]+','',res_normalization)
#記号
res_punctuation = res_url.translate(str.maketrans( '', '',string.punctuation))
#数字
res_num = ''.join([i for i in res_punctuation if not i.isdigit()])
return res_num
text_ed = [filter(str(i)) for i in df['text']]
df['texts_ed'] = text_ed
今回Mecabを使って分かち書きを行いました。
def wakati_by_mecab(text):
tagger = MeCab.Tagger(r'C:\Users\※※\Anaconda3\envs\enviroment\Lib\site-packages\MeCab\mecab-ipadic-neologd')
tagger.parse('')
node = tagger.parseToNode(text)
word_list = []
while node:
pos = node.feature.split(",")[0]
if pos in ["名詞", "動詞", "形容詞"]:
word = node.surface
word_list.append(word)
node = node.next
return " ".join(word_list)
texts_wakati = [wakati_by_mecab(i) for i in text_ed]
#stopwordsの処理
texts_wakati_ed =[]
for m in texts_wakati:
text_wakati_ed = [i for i in m.split() if i not in df_stopwords.values.tolist()]
texts_wakati_ed.append(text_wakati_ed)
df['texts_wakati'] = [' '.join(i) for i in texts_wakati_ed]
分かち書き行ったデータはこういう感じです

3.登場人物と関連テーマ
A.ワードクラウドでコメントの概要を把握
まずこの映画の内容について、コメント内一番話題となっている人物とテーマを探りたいので、
とりあえず一番よく出る単語の上位200位のワードクラウドで確認してみました。
from collections import Counter
from wordcloud import WordCloud
import matplotlib.pyplot as plt
from pyecharts.charts import *
from pyecharts import options as opts
from pyecharts.globals import ThemeType
from IPython.display import Image
word_count_1 = Counter(sum(texts_wakati_ed,[]))
wc_1 = WordCloud(
width=2000,
height=1200,
font_path=r'C:\Users\★★\python\UDDigiKyokashoN-R.ttc',
margin=2,
relative_scaling=0.2,
prefer_horizontal=1,
colormap='tab20')
wc2_1 = wc_1.generate_from_frequencies(word_count_1,200)
fig = plt.figure(figsize=(20,12))
plt.imshow(wc2_1, interpolation="bilinear")
plt.axis("off")
plt.show()

ワードクラウドを確認して、以下のいくつのテーマについて気になりましたので、それぞれの内容を抽出してみました。
[‘シャオヘイ’,’ムゲン’,’フーシー’,’人間’,’妖精’,’日本’,’中国’,’ジブリ’]
l1=[]
l2=[]
for i in range(0,len(df['text'])):
if 'シャオヘイ' in df['text'].iloc[i]:
l1.append(df['texts_wakati'].iloc[i])
if '小黒' in df['text'].iloc[i]:
l2.append(df['texts_wakati'].iloc[i])
...
list_=[len(list(set(l1+l2+l13))),len(list(set(l6+l7+l8))),len(list(set(l3+l4+l5+l12))),len(l9),len(l10),len(l11),len(l14),len(l15)]
df_kw = pd.DataFrame(list_,index=['シャオヘイ','ムゲン','フーシー','人間','妖精','ジブリ','日本','中国'])
df_kw=df_kw.reset_index().rename(columns = {'index':'名前',0:'回数'})
xaxis = df_kw['回数'].to_list()
yaxis = df_kw['名前'].to_list()

#横棒グラフで集計結果を確認しました。
c = (
Bar(init_opts = opts.InitOpts(theme = ThemeType.CHALK))
.add_xaxis(yaxis)
.add_yaxis('',xaxis).reversal_axis()
.set_global_opts(title_opts = opts.TitleOpts(title = 'テーマ言及回数',pos_left = 'top'),
yaxis_opts = opts.AxisOpts(axislabel_opts = opts.LabelOpts(font_size=13)),
xaxis_opts = opts.AxisOpts(axislabel_opts = opts.LabelOpts(font_size=13))
)
.set_series_opts(label_opts = opts.LabelOpts(font_size = 16,position ='right'))
)
c.render_notebook()

テーマごとの詳細はワードクラウドで表示してみました。
5つのテーマの結果しか記載していないが、全然違いがないこと一目瞭然ですね。。。
おそらくユーザーのコメントテーマはかなり近くて、文章レベルじゃあわかりにくいと思うので、Sentenceレベルで行ってみました。

まず文章を一つずつのSentenceに分解しました。
text_kw=' '.join(' '.join(''.join(df['text'].to_list()).split('、')).split('。')).split()

kw_list = ['シャオヘイ','小黒','フーシー','フーシ','風息','ムゲン','無限','師匠','人間','妖精','ジブリ','フウシー','黒猫','日本','中国']
list_kw_text_sh = []
list_kw_text_fs = []
...
list_kw = []
for i in text_kw:
if kw_list[0] in i:
list_kw_text_sh.append(i)
list_kw.append(kw_list[0])
elif kw_list[1] in i:
list_kw_text_sh.append(i)
list_kw.append(kw_list[0])
elif kw_list[2] in i:
list_kw_text_fs.append(i)
list_kw.append(kw_list[2])
elif kw_list[3] in i:
list_kw_text_fs.append(i)
list_kw.append(kw_list[2])
...
df_count = pd.DataFrame(Counter(list_kw).keys(),Counter(list_kw).values(),columns =['KW'] )
df_count=df_count.reset_index().rename(columns = {'index':'回数'})
df_count
yaxis = df_count['KW'].values.tolist()
xaxis = df_count['回数'].values.tolist()
上記得た統計結果をもとにした横棒グラフ結果とワードクラウドは以下の通りです。
これらの結果からテーマごとの違いがわかりましたね。例えば、シャオヘイきっととにかく可愛いですよね、ムゲンはかっこ良く最強イメージの師匠ですね、フーシーの声優さんである櫻井孝宏さんは結構評価されたらろうね、仲間に関する、人間と妖精の共存、居場所が破壊されたりされたシーンがありましたね、この映画に日本のジブリや、ドラゴン、もののけ等の作品に影響され、かなり日本アニメ要素多く感じられましたよね。そこから中国文化に関して語られたユーザーもいたなどなどの情報が見られましたね。



B.ワードクラウドを自動生成する簡易版GUI
この結果をもとに、任意のキーワードを入力すると、自動的にワードクラウドが出てくる簡易版GUIツールを作ってみました。
こんな感じです!(地味不可避・・・)
今後の課題としてどんどん改善していきたいと思います!
from tkinter import *
import tkinter as tk
#ワードクラウド生成用の関数
def get_data():
theme_data = _input.get()
theme_texts =[i for i in text_kw if theme_data in i]
theme_wakati = [wakati_by_mecab(i) for i in theme_texts]
theme_count = Counter(' '.join(theme_wakati).split())
wc = WordCloud(
width=1800,
height=1200,
font_path=r'C:\Users\※※\python\UDDigiKyokashoN-R.ttc',
margin=2,
relative_scaling=0.3,
prefer_horizontal=1,
colormap='tab20')
wc2 = wc.generate_from_frequencies(theme_count,300)
fig = plt.figure(figsize=(18,12))
plt.imshow(wc2, interpolation="bilinear")
plt.axis("off")
plt.show()
#簡易版GUI作成
app = Tk()
_input=tk.Entry(app,show=None) #入力画面の設定
_input.pack()
app.title('キーワード入力')
screenwidth = app.winfo_screenwidth()
screenheight = app.winfo_screenheight()
dialog_width = 400
dialog_height = 170
app.geometry(
"%dx%d+%d+%d" % (dialog_width, dialog_height, (screenwidth - dialog_width) / 2, (screenheight - dialog_height) / 2))
btn = Button(text ='search',command = get_data,width = 10)
btn.place(x=155,y = 80)
btn.pack()
app.mainloop()

4.極性辞書による感情分析
A.単語感情極性対応表でスコアを付く
極性辞書による感情分析は今回メインに以下の記事を参考して行いました。
まず極性辞書 (単語感情極性対応表)を読み込み
極性辞書と結合するため、分かち書きからデータの品詞情報を色々処理を行いました。
#極性辞書を読み込み
dict_= pd.read_table('極性辞書.txt',sep =':',header = None)
dff = dict_.rename(columns ={0:'基本形',1:'読み方',2:'品詞',3:'極性スコア'})

#分かち書き
import MeCab
tagger = MeCab.Tagger(r'C:\Users\※※\Anaconda3\envs\enviroment\Lib\site-packages\MeCab\mecab-ipadic-neologd')
texts = [tagger.parse(i) for i in df.texts_ed]

tmps = []
for t in texts:
tmp = [k.split('\t') for k in t.split('\n')]
tmp_ = tmp[:-2] #文末のEOS部分を切り捨て
tmps.append(tmp_)
surfs = [pd.DataFrame(m).rename(columns = {0:'単語',2:'読み方_old',3:'基本形',4:'品詞_old'}) for m in tmps]
#形容詞-非自立可能、名詞-普通名詞-助数詞可能のような品詞情報を形容詞、名詞に修正
llist= []
for i in surfs:
list_ = []
for m in range(0,len(i)):
if i['品詞_old'][m] !=None:
list_.append(i['品詞_old'].str.split('-')[m][0])
else:
list_.append(i['品詞_old'][m])
llist.append(list_)
#読み方をカタカナからひらがなに変更
import jaconv
llists= []
for i in surfs:
list_y = []
for m in range(0,len(i)):
if i['読み方_old'][m] != None:
list_y.append(jaconv.kata2hira(i['読み方_old'][m]))
else:
list_y.append(i['読み方_old'][m])
llists.append(list_y)
#分かち書きされたデータと極性辞書を結合することによって、単語ごとにスコア値を付けていく
score_results = []
for i in range(0,len(surfs)):
surfs[i]['品詞'] = llist[i]
surfs[i]['読み方'] = llists[i]
if surfs[i].columns.size == 10:
score_result= pd.merge(surfs[i][['単語','基本形','読み方','品詞']],dff,on=['基本形','読み方','品詞'],how = 'left')
score_results.append(score_result)
else:
score_results.append(surfs[i])
すべての文書は単語ごとにスコアが付けられました。
極性辞書にない単語のスコアはNaN値。
文書ごとにすべて単語のスコアを加算すると、文書ごとのスコアが得られる

results = []
for i in range(0,len(score_results)):
if score_results[i].columns.size == 5:
text = ''.join(list(score_results[i]['単語']))
score = score_results[i]['極性スコア'].astype(float).sum()
score_r = score/score_results[i]['極性スコア'].astype(float).count()
result = [text,score,score_r]
results.append(result)
else:
results.append(score_results[i])
df_score = []
for i in range(0,len(results)):
if len(results[i]) == 3:
dfff = pd.DataFrame({
'テキスト':results[i][0],
'累計スコア':results[i][1],
'累計標準化スコア':results[i][2],
'user':df.iloc[i]['user'],
'score':df.iloc[i]['score']},index =range(1))
df_score.append(dfff.iloc[[0]])
ddf_score = pd.concat(df_score).reset_index().sort_values('累計標準化スコア',ascending=False)
一発で行った結果、上位10位と下位10位の内容は以下の通りでした。
分かち書きによるデータの品詞や読み方等極性辞書と一致しない単語があることで、スコア付けがズレが生じたことがわかりました。例えば2678行の「好きです」の「好き」は分かち書きした品詞が形状詞ですが、極性辞書内では名詞とのことで、スコアが付けられませんでした。
ddf_score[:10:]['テキスト'].to_list()
#['フーシーにもムゲンにもそうだよねぇっていう気持ち。シャオヘイがかーわいい。',
'とっても可愛かったー猫好きなら見るべきストーリーも良かった',
'昭和の東映まんが祭りを彷彿とさせる力強くまっすぐなアニメーション。素晴らしかった。',
'やさしいタッチのキャラデザから繰り広げられるゴリッゴリのエフェクトアニメーション作画に圧巻ファンタジーとアクションのミックスシャオヘイかわゆい',
'『ドラゴンボールブロリー』並みのアクションバトルシーンに圧巻',
'おもろかったストーリーわかりやすいし、作画が神デパートでのバトルシーンが一番好き',
'ムゲン様バトルシーンが凄かったこれはdxでみればよかったな',
'可愛い絵が綺麗可愛いアクションシーンがすごい可愛い面白い可愛い可愛い可愛い',
'これは満足ですまずシャオヘイが可愛いし、ムゲン様がカッコいい',
'良いリアルファンタジーで、良い師弟ものでした。わかりやすい面白さがあります。']
ddf_score[-10::]['テキスト'].to_list()
#['ナルト×ドラゴンボール×ジブリみたいで、そら大好きになりますわといった感じです。',
'ドラゴンボールと千と千尋となんかをミックスした感じ。シャオヘイとてもきゃわわ。',
'子供と観たんだけれども、ストーリーがよくわからなかったみたい。',
'アニメで見たいのはこういうのなんだよ', →score=0
'ムゲン様フーシームゲン様フーシーウッ',→score=0
'え好き',→score=0
'アアアァァァァクションンンンンンンだったこれはサイドストーリーひたすら観たいな',→score=0
'livezound×rgbレーザーチネチッタチネm',→score=0
'中国語版ユジク日本語吹き替え版バルト',→score=0
'好きです'→score=0]
B.極性辞書を生成した上でスコアを付く
既存の極性辞書は今回のデータと相性が良くないようなので、独自の極性辞書を生成する手を考えました。
メインはこちらの記事感情分析に用いる極性辞書を自動生成するを参考しました。
model_neologd.vecの取得はこちらの記事fastTextの学習済みモデルを公開しましたをご参考ください。
posi_listとnega_listはデータ内容をみて適宜修正あり
import gensim
model = gensim.models.KeyedVectors.load_word2vec_format('model_neologd.vec', binary=False)
posi_list = ['優れる', '良い','喜ぶ','褒める', 'めでたい','賢い','善い', '適す','天晴','祝う', '功績','賞',
'嬉しい','喜び','才知','徳', '才能','素晴らしい','芳しい','称える','適切','崇める','助ける','抜きんでる','清水',
'雄雄しい','仕合せ','幸い','吉兆','秀でる','かわいい','すすめ','dvd','可愛','涙腺','泣き','豪華','うまい',
'緻密','おもしろかっ','加点','最高','すごい','魅力','王道','綺麗','感動','楽しめ','好き','一番','やすい','いえー',
'酔いしれ','微笑み']
nega_list = ['悪い', '死ぬ', '病気', '酷い', '罵る', '浸ける', '卑しい','下手', '苦しむ', '苦しい', '付く',
'厳しい', '難しい', '殺す', '難い', '荒荒しい','惨い', '責める', '敵', '背く', '嘲る', '苦しめる', '辛い',
'物寂しい', '罰', '不貞腐る','寒い', '下らない','残念','疲れ','複雑','真似','不足','わから','違う','BLM','デモ',
'過激','対立','ステレオタイプ','古い','寝','謎','足し','オリジナリティ','苦痛','後進','昭和','稚拙','なさ','退屈',
'高かっ','残ら','説明','しんど','減点','違和感','ぽんぽこ','粗','仕方ない','合わ','掘り下げ','薄い','未熟','苦手',
'ダメ','ごめん','ガッカリ','眠','抑圧','にくい']
def posi_nega_score(x):
#ポジティブ度合いの判定
posi = []
for i in posi_list:
try:
n = model.similarity(i, x)
posi.append(n)
except:
continue
try:
posi_mean = sum(posi)/len(posi)
except:
posi_mean = 0
#ネガティブ度合いの判定
nega = []
for i in nega_list:
try:
n = model.similarity(i, x)
nega.append(n)
except:
continue
try:
nega_mean = sum(nega)/len(nega)
except:
nega_mean = 0
if posi_mean > nega_mean:
return posi_mean
if nega_mean > posi_mean:
return -nega_mean
else:
return 0
for i in score_results:
if i.columns.size !=2:
i['極性スコア_new'] = i['単語'].apply(lambda x : posi_nega_score(x))

新しく得た極性スコアを用いて文書ごとのスコアを算出しましょう
#スコアを標準化
import numpy as np
for i in score_results:
if i.columns.size!=2:
sscore = np.array(i['極性スコア_new'])
sscore_std = (sscore - sscore.min())/(sscore.max() - sscore.min())
sscore_scaled = sscore_std * (1 - (-1)) + (-1)
i['標準化スコア_new'] = sscore_scaled
#文書のスコアを算出
results_new = []
for i in score_results:
if i.columns.size != 2:
text_new = ''.join(list(i['単語']))
score_new = i['標準化スコア_new'].astype(float).sum()
score_r_new = score_new/i['標準化スコア_new'].astype(float).count()
result_new = [text_new,score_new,score_r_new]
results_new.append(result_new)
else:
results_new.append(i)
#上位10位と下位10位
ddf_score_new[:10:]['テキスト'].to_list()
#['戦目新宿バルト戦目t・ジョイプリンス品川戦目新宿バルト戦目新宿バルト戦目チネチッタ川崎戦目チネチッタ川崎最新字幕版戦目チネチッタ川崎最新字幕版',
'久しぶりにちゃんとしたアニメーション映画を観たなっていう感じだった',
'期待どおり最高。ストーリー、キャラ、アクション、テンポ感、会話の掛け合い、声優来場者特典イラストカード。',
'可愛い絵が綺麗可愛いアクションシーンがすごい可愛い面白い可愛い可愛い可愛い',
'『ドラゴンボールブロリー』並みのアクションバトルシーンに圧巻',
'大変大変大変大変大変大変大変大変大変大変大変大変大変大変大変大変大変大変大変大変大変に、好みの映画でしたパンフ売り切れだったので、アニプレックスさんの通販で買いました。買えてよかった',
'涙腺ガバだから泣いためちゃくちゃ王道だしクオリティ高い中国アニメいいです',
'バトルかっこよす。櫻井vs宮野、木属性vs金属性、自然vs人工。',
'超神作画、、ムゲンさま、、宮野さま、、共に生きること',
'もう一度見たい★★★★★期待以上☆☆☆☆期待通り☆☆☆期待外れ☆☆時間返せ☆dynamic・・・・・・・・・・・★・・・・・・realisticーーfantastic・・・・・・・・・・・・・・・・・・static日時場所バルト客入☆☆★☆☆']
ddf_score_new[-10::]['テキスト'].to_list()
['線も造形もシンプルだけどとにかく動き回りまくってて楽しいキャラクターや世界観に想像の余地があってもっと見たくなるからまで説明する鬼滅とはある意味反対でそこは好み',
'しゅき正直話として飲み込めないとこも多々あったけど、しゅきという感情にねじ伏せられる',
'かわいいしかない映画。ロシャオとムゲンのやり取りが好きすぎる',
'変化を受けいられない人たちは切り捨てるしかなかったのかと思ったら、悲しくなってきた',
'「《共存》コソコソ生きろって」構図と動きの表現に勢いがある。話の内容は製作国の最近のことを考えるとちょっと複雑。',
'ソフト化未定ってどういうことやアニプレックスなんとかしてくれや',
'アニメーションはすごく迫力がありキャラクターも可愛らしく大変楽しめたがblm運動や香港のデモのことなどが頭によぎりマイノリティの過激派と穏健派の対立という物語はもう古いというかマジョリティによるステレオタイプの再生産じゃ無いかと思ってしまった',
'ひたすらかわいい、しかもアクションが楽しい字幕のスピード、大きさ、色がいつもの映画のそれとは違ってて追えない箇所が多かった',
'森になる森になるしかなかった森になるしかなかった人はどうすればいいのか',
'とにかくシャオヘイがかわいい話が進むにつれて変化するムゲンとの関係も見ていて楽しかった']
うん、ちょっとまずいですね・・・
‘〇〇良かったですが、〇〇はダメですね’というようなコメントはちらほら見られますね
すごい人から、中盤スコアを除くと、悪い評価と良い評価の差が付けられるかもしれないというアドバイスを頂きましたため、実施してみましょう。
results_new = []
for i in score_results:
if i.columns.size != 2:
text_new = ''.join(list(i['単語']))
score_new = i[abs(i['標準化スコア_new'])>0.5]['標準化スコア_new'].sum() #極性スコアの絶対値は0.5以上しか加算しないと指定
score_r_new = score_new/i['標準化スコア_new'].astype(float).count()
result_new = [text_new,score_new,score_r_new]
results_new.append(result_new)
else:
results_new.append(i)
#上位10位と下位10位


若干改善?でもやはり一部良い評価なのに下位に来てしまった文書がありますね
またそもそも各単語のスコアを確認してみると、シャオヘイやムゲンというキャラクタ名のスコアもマイナスになっていて、更に「話」や「が」、「つれ」、「の」等も無駄にマイナスになっていますね。やはり今回生成した辞書そのものが怪しいですね。。

それに応じて得たスコアも怪しいかもしれないですが、こんな感じです。
data = ddf_score_new[ddf_score_new['スコア']!='-']['累計標準化スコア'].to_list()
fig = plt.figure(figsize=(12,8))
plt.hist(data,bins=40, density=True,stacked=True, facecolor="blue", edgecolor="black", alpha=0.7)
plt.show()

score_new = [round((3+i/0.5),1) for i in ddf_score_new['累計標準化スコア']]
ddf_score_new['score_test'] = score_new

C.まとめ
極性辞書を使って、容易にすべてのデータに対してスコアをつけられることが使いやすいですね
しかし、生成した辞書の正確性をどのように保証していくのか、今後の課題として探求していきたいと思います。
中盤のスコアを無視し、両端側(今回は絶対値0.5以上のスコアを加算)スコアの加算は有効な手段と考えられます。
5.機械学習による感情分析_LSTM(今後データ量を増加する予定)
A.ラベル付け
まずユーザーのスコアによるポジフラグとネガフラグを付けていきたいと思いますが、今回スコア2以下のデータはわずか7件しかないため、スコア3.5以下のデータのうち、ネガ情報入っているデータをネガフラグつけることにしました。(ここはあくまで人工知能の人工の部分です。)
#今回のテストデータ
df.loc[df['label'] == '-','sentiment'] = 3
#ネガフラグデータ
df.loc[df['label'] == '0','sentiment'] =0
#ポジフラグデータ
df.loc[(df['label'] == '1'),'sentiment'] =1
#ネガではなく、ポジでもないデータ
df.loc[df['label'] == '2','sentiment'] =2
print(len(df[df.sentiment == 1]),len(df[df.sentiment == 0]),len(df[df.sentiment == 2]),len(df[df.sentiment == 3]))
#2302 180 587 305

B.ベクトル化
前処理と分かち書きを行いましたら、gensimのword2vecモデルを使ってデータのベクトル化を行いました
import gensim
model = gensim.models.KeyedVectors.load_word2vec_format('model_neologd.vec', binary=False)
#Wikiモデル内格納している各単語
vocab_list = list(model.wv.vocab.keys())
#Wikiモデル内格納している各単語のベクトル
word_vectors = model.wv.syn0
#Wikiモデル内格納している各単語及び単語のインデックス情報
word_index = {word: index for index, word in enumerate(vocab_list)}
len_list =[len(i.split()) for i in df['texts_wakati']]
print('ファイル全件',len(len_list),'件です')
print('単語全部',sum(len_list),'個です')
print('単語数の最大値は',max(len_list),'です')
#ファイル全件 3374 件です
#単語全部 206770 個です
#単語数の最大値は 2087 です
import random
pos_data = df[df['sentiment'] == 1]
neg_data = df[df['sentiment'] == 0]
#ポジ・ネガデータを17:1で分割する
pos_index_train = random.sample(list(pos_data.index),2174)
neg_index_train = random.sample(list(neg_data.index),170)
#インデックスを取得
pos_index_validation = [x for x in pos_data.index if x not in pos_index_train]
neg_index_validation = [x for x in neg_data.index if x not in neg_index_train]
#テキストデータ等を取得
pos_sentence_train = pos_data.loc[pos_index_train]
neg_sentence_train = neg_data.loc[neg_index_train]
pos_sentence_validation = pos_data.loc[pos_index_validation]
neg_sentence_validation = neg_data.loc[neg_index_validation]
#trainデータとvalidデータを結合する
df_train = pd.concat([pos_sentence_train,neg_sentence_train],axis =0)
df_validation = pd.concat([pos_sentence_validation,neg_sentence_validation],axis =0)
df_all = pd.concat([df_train,df_validation],axis = 0)
print(len(df_train),len(df_validation),len(df_all))
#2344 138 2482
X_all = df_all.texts_wakati.tolist()
X_train = df_train.texts_wakati.tolist()
X_validation = df_validation.texts_wakati.tolist()
y_train = df_train['sentiment'].values.astype('int8')
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
#まずTokenizerを使ってベクトルを作成してみました
tokenizer = Tokenizer(
nb_words = 2000,
filters='!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~\t\n',
lower=True,
split=' ')
tokenizer.fit_on_texts(X_all)
X = tokenizer.texts_to_sequences(X_all)
X = pad_sequences(X)
X.shape
#(2482,1715)
C.LSTMモデルを使って実施
embed_dim = 128
lstm_out = 256
batch_size = 32
from keras.models import Sequential
from keras.layers import Embedding
from tensorflow.keras.layers import Dropout, Dense, LSTM,Flatten
model_1 = Sequential()
model_1.add(Embedding(2000,embed_dim,input_length = X.shape[1]))
model_1.add(LSTM(lstm_out,dropout=0.2,return_sequences=True))
model_1.add(Flatten())
model_1.add(Dense(2,activation='softmax'))
model_1.compile(loss = 'categorical_crossentropy', optimizer='adam',metrics = ['accuracy'])
print(model_1.summary())

from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from keras.callbacks import Callback
x_train5,y_train5,x_label5,y_label5 = train_test_split(X_train,y_binary, train_size=0.8, random_state=234)
history = model_1.fit(x_train5,x_label5,batch_size=batch_size,epochs= 2,validation_data=(y_train5, y_label5),verbose=2)

y_lstm = model_1.predict(X_validation,batch_size=batch_size)
df_validation['score_new'] = [round((i/0.2),1) for i in y_lstm[:,1].tolist()]
#出た結果とユーザーの評価と比較してみると
xaxis = df_validation['user'].tolist()
yaxis1 = df_validation['score'].astype(float).tolist()
yaxis2 = df_validation['score_new'].astype(float).tolist()
plt.figure(figsize = (12,8))
plt.title('Result Analysis')
plt.plot(yaxis1,color='red',linewidth=2.0,linestyle='--',label = 'user')
plt.plot(yaxis2,color='green',linewidth=2.0,linestyle='solid',label = 'LSTM')
plt.legend()
plt.show()

まとめ
epochs5回と30回も実行してみましたが、どうやらトレーニングデータのaccuracyがひたすらよくなって、val_accuracyは維持する結果しかならないですね。
そもそも3000件しかないデータだとディプラーニングで過学習しやすいと後ほど知人にからかわれました・・・
とりあえずデータを増加してから再度チャレンジしたいと思います!!!
6.残り課題
極性辞書のチューニング
LSTMのパラメータファインチューニング
絵文字といった感情表現を含めて考慮したら?
異常検知
人物のネットワーク分析
参考文献
1.selenium Web element
2.pythonで絵文字を駆逐する
3.感情分析に用いる極性辞書を自動生成する
4.感情分析でニュース記事のネガポジ度合いをスコア化する
5.Python实现输入电影名字自动生成豆瓣评论词云图(带GUI界面)小程序
6.fastTextの学習済みモデルを公開しました