Aidemy Blog

株式会社アイデミーのブログです。機械学習・ディープラーニング関連技術の活用事例や実装方法をまとめる技術記事や、キャリア記事等を発信しています

【現役機械学習エンジニアに聞いてみた】機械学習エンジニアになるには具体的に何をすればよいのか

こんにちは、アイデミー のDAIです。

今回は、弊社技術顧問であり、機械学習エンジニアである、木村にインタビューをしましたので、その内容を記事にしました。

 

木村 優志:

Convergence Lab. 代表。博士(工学)大手通信キャリアの音声認識システムや大手AIベンダの自然言語処理を用いたAI開発に携わる。アイデミー技術顧問KERNRL 初期メンバー

 

twitter.com

 

Convergence Lab.

 

 

DAI:企業のAI案件の要望としては、どのようなものが多いのでしょうか?

 

木村:守秘義務が多いので、具体的なことはお話しできませんが、概要だけ。画像系、音声、言語系の案件が主にあるのですが、印象としては画像系が多いと思います。その次に自然言語処理系ですね。音声系の案件は仕事が少ないと思います。画像認識は、物体検出が多いです。

 

DAI:なるほど、企業側ではどういう課題感を持って、案件を発注してくることが多いのでしょうか?

 

木村:人力でやっているのを機械でやりたいという案件が多いですね。

 

DAI: どのようなスキルセットを持つと、AI案件に携われるのでしょうか?

 

木村:実績がほとんどですね。正直、企業側もスキルで判断できないんですよね。機械学習エンジニアそのものが少ないので。なので、もし機械学習エンジニアとして案件を受注するには、実績がないかぎり、難しいです。なのでいきなりフリーランス等でやるのは無理ですね。

 

DAI:なるほど、狭き門ですね。実績がないとなれないですが、それ以外に未経験から機械学習エンジニアになる方法はないでしょうか

 

木村:実績がなくても機械学習案件に関わるのであれば、ポテンシャル採用で機械学習エンジニアとして採用してもらうのがよいでしょう。しっかりとKaggleやらGithubなどでコードで成果物を作って、自分の学習意欲をしっかりとアピールしていくことが必要です。また、今、機械学習エンジニアを採用するプラットフォームがほとんどないので、普通のエージェントをつかっていても採用されることはありません。自分から積極的にチャンスを掴みに行かないとダメですね。

 

また、機械学習エンジニアとしてではなく、AIの素養があるビジネスパーソンも非常に重要かと思われます。例えば、AIの技術がわかっていて、営業ができる人は少ないです。そういう人材は、各社が自分で育てているイメージですね。なので、そういう人材としてアピールしていくのもよいかと思います。

 

DAI:以前Aidemy Premiumを受講されたjack nightmareさんは、「機械学習 セミナー」で検索して、そのセミナーで自分をアピールしたとおっしゃっていましたし、寺田優也さんはLinkedin経由でエージェントにアプローチしていったと聞きました。学習意欲がわかるポテンシャルと、行動力が大事なんですね。そういう会社を見つけて入社する方法って、どうやればいいんでしょうか。

 

blog.aidemy.net

 

blog.aidemy.net

 

木村:ん〜そうですねぇ。SNSなどで機械学習エンジニアに直接連絡取ってみるのがよいのではないでしょうかね。相手にもWinな形で出会うことができれば、もしかしたらチャンスがあるかもしれないですね。そういうところで、すでに機械学習ができる人のもとに弟子入りするのがよいと思いますよ!

  

DAI: なるほど、ありがとうございました!

 

 

Aidemy Premiumとは

 

Aidemy Premiumとは、集中的な8週間のマンツーマンレッスンで、最先端のAIエンジニアを目指す学習コースです。データ分析コース、自然言語処理コース、カスタマイズコースからコースを選び、完全オンライン上でプログラミングを学ぶことができます。 

 

 

youtu.be

 

 

詳細情報は、こちらのURLからホームページを見ることができるので、ぜひ読んでみてください!

 

aidemy-premium.net

【Aidemy Interview】25歳、社会人2年目の文系エンジニアが、機械学習エンジニアとして転職した話

こんにちは、アイデミーのDai(@never_be_a_pm)です。

 

今回は、Aidemy Premiumプランを受講して、機械学習エンジニアとして転職された寺田優也(@mktrdbg)さんにインタビューをさせていただきました。

 

寺田さんのプロフィール:

 

・現在24歳、大学卒業後、ベンチャーに就職し、現在ソフトウェアエンジニアとして働く。現在社会人2年目で、機械学習エンジニアとして転職を決意。Aidemy Premiumを受講する前に、独学で機械学習、ディープラーニングを学び、その後Aidemy Premiumカスタマイズコースを受講。型破りな転職方法で、見事機械学習エンジニアとして内定を獲得。

 

参考:ブログ等でも精力的に発信されています。

 

www.mktrdbg.com

 

 

文系社会人2年目のPythonエンジニアが、機械学習エンジニアに転職を決意 

 

DAI: 現在のご職業について教えてください。

 

寺田さん:現在社会人2年目で、Pythonのソフトウェアエンジニアをしています。ベンチャーで、Chatbotを作っています。

 

DAI:入社される前にプログラミングの経験があったんですか?

 

寺田さん:はい、学生時代、今の会社に1年間インターンをしていて、コードは書いていました。最初の3ヶ月はSwift(※1)からはじめました。その後、Tech Campにいって、1週間のコースで勉強してから、会社の研修を受けて、AndroidでJavaを半年やりました。

(※1 Swift: iOSアプリケーションを作るための言語)

 

DAI:なるほど、学生時代からプログラミングの経験があったんですね!ちなみに大学は文系でしたか?理系でしたか?

 

寺田さん:文系でした。経済学を学んでいたのですが、特に統計を学ぶ等のことはしていませんでしたね。

 

DAI:そうなんですね。会社に入られてから、Pythonを学習されたということなんですかね?

 

寺田さん:そうですね、入社後から未経験で、Pythonを利用したチャットボットの開発を行なっていました。

 

DAI:転職をしようと思ったきっかけについて、教えてください。

 

寺田さん:今の会社のプロジェクトが終わったのがきっかけですね。その後に、今の技術からもう少し幅が広げられそうなスキルをつけたいと思っていて、何かなぁと思っていた時に、機械学習がよいと思い、機械学習を利用できる仕事につきたいと考えるようになったことがきっかけです。普通に他のプログラミング言語を学んでも、みんなができるようなプログラミング言語を学ぶよりは、エッジの効いたスキルセットを身につけたいと思ったからですね。

 

DAI:転職活動をされる際に、機械学習はどのように学びましたか?

 

寺田さん:そうですね、Aidemy Premiumを受講する前には、一通り機械学習の基礎は書籍や動画などを通して学習を進めていました。

 

DAI:具体的にはどのような書籍を利用して学習していましたか?

 

寺田さん:Coursera Andrew教授のMLコース、ゼロから作るDeepLearning、深層学習青本、Kaggleの各種コンペのKernel、『Pythonによるスクレイピング&機械学習 開発テクニック BeautifulSoup,scikit-learn,TensorFlowを使ってみよう』を利用して学習を進めていました。

 

Machine Learning | Coursera

 

www.oreilly.co.jp

 

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

 

 

https://kaggle.com

 

 

 

DAI:かなり初心者にはレベルの高いものを使って学習されていましたね。正直、かなり大変ではなかったですか?

 

寺田さん:そうですね、かなり大変でした。特に最初に『ゼロから作るDeepLearning』を学んだ時は、ほとんど理解できなかったです。

 

ただ、実際に他の機械学習やディープラーニングを実装して行く中で、理論的な部分が経験的に落とし込めたので、実践的な学習をしてからもう一度戻ると、理解が深まったりしましたね。特にPythonの知識をしっかりつけてから、もう一度やったら理解できるようになりました。

 

DAI:なるほど、実際に体験しながら理論を学んで行くのがよかったんですね。転職活動はどのように進めていったんですか?

 

寺田さん:実は会社を辞める前から、転職活動を始めました。プロジェクトがひと段落したところで、会社を決める前にやめることは上司に伝えていました。もちろん他のプロジェクトなどをすすめてくれましたが、自分のやりたいこととは違うので、断りました。ちょうどそのくらいから、有給をもらっている間に一気に転職活動をしようと決めて、Aidemy Premiumで本格的に機械学習を学ぼうと考えました。

 

 

 

DAI:なるほど、ある程度自学自習した上で、転職活動を進められたのですね。ちなみに数ある機械学習を学べるプログラミングスクールの中で、Aidemy Premiumを受講することにしたのですか?

 

寺田さん:僕自身、すでにPythonの基礎や機械学習の基本的なことは学べていたので、それ以外のコンテンツをしっかりと取捨選択して受講したいと思っていたからですね。

他のスクールだと、一律パッケージになっていて取捨選択できないので、Aidemy Premiumのカスタマイズコースを受講することにしました。

 

DAI:なるほど、だから弊社の「カスタマイズコース」を受講されていましたよね。

 

寺田さん:そうですね。私自身、学習の希望としては、特にどの分野をやりたいということはなく、ビジネス上で必要になりがちな優先度の高い技術項目を学んで行くことにしました。そして、もし全ての学習コースが終わっても、他の学習をやらせてくれるというのもカスタマイズコースを受講するきっかけとなりました。

 

DAI:実際に機械学習エンジニアとして転職する時には、どのように準備しましたか?

 

寺田さん:エージェントを利用していました。Linkedinを利用していましたね。グローバルな経験のある人材を探しているエージェント企業の外国人エージェントから連絡が来ました。Linkedinのプロフィールを充実させていると、海外からアプローチされるんですよね。エージェントから10人くらい連絡がきました。その人に対して、「データサイエンスなどのプロジェクトで探しています。」と伝えましたね。エージェント経由で紹介してくれたのが50社ほどで、そのうち30社ほど書類選考を出しました。そのうち10社くらいが通りました。

 

DAI:おぉ、Linkedinを使うという方法もあるんですね。ちなみに採用面接や書類選考では、どのような点が評価されていたと思いますか?

 

寺田さん:もともとPythonを未経験から1年間業務で学んでいたという点を評価されました。また、ポートフォリオとしてKaggleを使って学んでいたので、書類選考の際にKaggleの実績等を載せておくと、学習ポテンシャルがあると判断していただけたと思いますまた、プログラミングスクールで学んでいるということも、評価が高いようでした。技術的なところの質問はそこまでありませんでしたね。あとは前職でのグローバルな経験がありましたので、英語は今の内定先でも結構評価されました!

 

DAI:なるほど、学習意欲のポテンシャルとして、Kaggleで学んでいることや、Aidemy Premiumで学習を進めていることも高く評価されたのですね。次の転職先では、どのようなお仕事をする予定ですか?

 

寺田さん:次の会社はジェネシスヘルスケア株式会社という会社で機械学習エンジニアとして採用していただけました。保有している遺伝情報や生活習慣データ(歩数・睡眠時間・心拍数など)の分析基盤の構 築から遺伝情報に基づく予測モデリングの構築・最適化、機械学習アルゴリズムの開発を 行うことで科学的インサイトを導き出すことが仕事となりますね。

 

DAI:Aidemy Premiumを受講して、どうでしたか?

 

寺田さん:最初は教師あり学習や、教師なし学習は簡単だと思って受講していたのですが、回を重ねるにつれて難しくなってきました。ただ、自分で一人ではたどり着かなかった部分にも、カリキュラムが包括的でたどり着けたので、そこがよかったです。

 

教材の内容も充実していてよかったですね。また、チューターからのレスポンスが早かったのもよかったです。

 

DAI:なるほど、本日はありがとうございました!

 

> 【Aidemy Interview】コールセンター業務から、3ヶ月で未経験からAIを学習して社内転職した話

 

> Aidemy Premium公式サイト

 

 

 

 

【Aidemy Interview】コールセンター業務から、3ヶ月で未経験からAIを学習して社内転職した話

こんにちは、AidemyのDaiです。(@never_be_a_pm

 

今回は、弊社オンラインAIプログラミングスクールサービス「Aidemy Premium」を受講され、AI関係の部署に社内転職が決まった jack nightmareさん(ツイッターID:@jacknightmare13)にインタビューをさせていただきました。

 

 

 

自分の仕事がなくなるかもしれないと思い、プログラミングを学び始める

 

DAI: それでは、まず最初に現在のご職業を教えてください。

 

Jackさん:新卒では営業職をやっていましたが、今は転職してコールセンターでの対応品質の管理や、人材育成をやっています。30人くらいのオペレーターのマネージャーをしていて、お客様の顧客体験やNPSを向上する上げるために、話し方をサポートしています。最初の1-2年はオペレーターをしていて、3年目からはほとんど管理をしています。

 

DAI: なるほど、ではプログラミングを学習しようと思ったきっかけを教えてください

 

Jackさん:たまたまYoutubeで見たホリエモンチャンネルを見たのがきっかけで、Aidemyを知りました。

 


堀江貴文のQ&A「プログラミングは誰でも出来る!?」〜vol.1039〜

 

Jackさん: AI学習が10秒ですぐに出来るという事で好奇心を持ちました。

 

ここ最近、AIを聞かないことがないくらい、一般的になってきたと思うんですよね。また、自分のコールセンターという職種がAIがとって変わられる職種ということもあり、危機感がありプログラミングを学ぼうと考え始めました。

 

DAI: なるほど。確かにオックスフォード大学が出している論文ではコールセンターの仕事がなくなると言われていますね。まさに、仕事がなくなる側から、なくす側に移ろうと考えたのですね。

 

Jackさん:はい、それはよく言われますね(笑)

 

Progateで学習してから、Aidemy Premiumを受講

 

DAI:Aidemy Premiumを受ける前まで、どのようにプログラミング学習を進めていたのでしょうか。

 

Jackさん:「機械学習するならまずはProgateでPythonをやるのがよい!」と聞いたので、まずはProgateでPythonを勉強しはじめました。

 

prog-8.com

 

ProgateでPythonの基礎を勉強してから、Aidemy Premiumをすぐに受講しました。

 

AIに関するプログラミングスクールは他にもあったのですが、Aidemy Premiumを選んだ一番の決定打が、Aidemyが早稲田大学で導入されているということでした。あとは、経営者の顔がツイッターで観れたのがよかったので、受講を決めました。

 

DAI: なるほど。弊社ではAidemy Premiumでは、どのようなコースを受講されたのですか?

 

Jackさんカスタマイズコースを受講していました。「会話の中で、人との関係性の見えないところを可視化したい」と思っていたので、自然言語処理などのコースを学習していました。


あとは、予測ができるようになりたいので、回帰をしっかり勉強しようと思って回帰について学べる、教師あり学習等も学習していました、弊社のサービスで、為替を予測するようなサービスがあるのですが、これを自分で実装できるようになりたいと思い、カスタマイズコースを受講いたしました。

 

※ Jackさんが受講したコース一覧

  • Python入門
  • Numpy
  • Pandas
  • 機械学習概論
  • データクレンジング
  • データの可視化
  • 教師あり学習(回帰)
  • 教師あり学習(分類)
  • 教師なし学習
  • 時系列分析
  • 自然言語処理
  • 異常検知入門
  • 深層学習画像認識
  • 男女認識

 

 

未経験でもAI転職の門戸が開いていた

 

DAIAidemy Premiumで学習している途中から、転職を検討されていたのですか?

 

Jackさん:はい、そうですね。その時から転職は検討していました。

 

DAI: 具体的にどんな転職活動をされていましたか?

 

Jackさん「機械学習 セミナー」で検索して、そこにいた企業の人にひたすら自分を売り込みに行きました。

 

DAI:だいぶ型破りな転職方法ですね笑

 

Jackさん:そうですね(笑)Aidemy Premiumを受講していることや、学んでいることを積極的にアピールしました。

 

DAI: それはすごいですね、、実際、どんな反応されましたか?

 

Jackさん:かなり、好感的でした。それで、何社か機械学習エンジニアを募集している会社を教えてもらうことができまして。普通にエージェントを通すとかならずいけないところをご紹介してもらえました。

 

DAI: 転職も強行突破すると行けるんですね(笑)選考はどんな感じでしたか?

 

Jackさん:何社かご紹介したうち、職務経歴書を送ったら、機械学習エンジニアを募集している会社の書類選考が通りました!未経験でも門は開くんですね!

 

DAI: すごい!! 

 

エンジニア経験が求められるポジションに、未経験から社内転職

 

Jackさん:ただ、実は今社内で新規事業でAIを利用した職種で募集がありまして。そこがエンジニア経験がないとなれない職での募集だったんですが、そこも同じようにエンジニア未経験ながらアピールしてみましたところ、なんと採用してもらえました。

 

DAI: 行動力がすごい!次の仕事ではどのような仕事をされるのでしょうか。

 

Jackさん:AIチャットボットを活⽤した新規サービス商材の開発部隊にて、システムの運⽤・環境の要件定義〜システム開発〜導⼊を担当することになりました。

 

DAI: どのような点が評価されたんですかね。

 

Jackさん:やはり、もともとの職務経験と、Aidemy PremiumでAIを学習していることをアピールできた点が評価されたかと思います。

 

DAI: 確かに、AIの知識があっても、ビジネスの要件に落とし込むのとはまた別のスキルですからね。弊社としても人材として、ビジネスドメインの知識がありつつ、AIの知識がある人材の需要が強くなってきていると感じます。

 

Aidemy Premiumを学んでから、何を学習したか

 

DAI:ちなみに、Aidemy Premiumを受講してから、転職されるまでどのような学習ルートですすめましたか?

 

Jackさん:Aidemy Premiumで全体感をつかんだ上で、そのあと、個々の細かい部分に関しては、書籍や、Udemyの講座を受けていました。

 

DAI:どのような書籍を参考にしましたか?

 

Jackさん:まず、『Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎 ・すぐに使える! 業務で実践できる!』という本は参考になりました。この本はチートシートがあって、非常によかったです。

 

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

 

 

 

また、『すぐに使える!業務で実践できる!PythonによるAI・機械学習・深層学習アプリの作り方』という本も非常に参考になりました。

 

すぐに使える! 業務で実践できる! Pythonによる AI・機械学習・深層学習アプリのつくり方

すぐに使える! 業務で実践できる! Pythonによる AI・機械学習・深層学習アプリのつくり方

 

 

 

あとは、Udemyでのオンライン学習で、『実践 Python データサイエンス』という講座で学習しました。動画で非常にわかりやすかったです。

 

【世界で5万人が受講】実践 Python データサイエンス | Udemy

 

Aidemy Premiumとは

Aidemy Premiumとは、集中的な8週間のマンツーマンレッスンで、最先端のAIエンジニアを目指す学習コースです。データ分析コース、自然言語処理コース、カスタマイズコースからコースを選び、完全オンライン上でプログラミングを学ぶことができます。

 

www.youtube.com

 

詳細情報は、こちらのURLからホームページを見ることができるので、ぜひ読んでみてください!

 

> Aidemy Premiumについて詳しくみてみる

ベイズの定理を用いて方言を分類してみた

初めまして、Aidemy研修生の尾熊です。

 
突然ですが、皆さんも大学や就職で自分の地元以外の人と会話をしたとき自分は普通だと思っていたのに方言だったー!!なんてことはありませんか??
f:id:thipot:20180717110416j:plain

今回は方言をベイズの定理を用いて方言を分類してみたいと思います。

目次

1.     環境
2.    ベイズの定理の説明
3.    ナイーブベイズ分類
4.    形態素解析
5.    方言を分類する
6.    学習
7.    実行結果
8.    まとめ
9.    参考文献

1.  環境

今回使った環境について紹介します。

Python 3.6.4      プログラミング言語
Anaconda 1.6.14   環境開発に使います
Jupyter 5.4.0     プログラムを実行できます、実行結果が分かりやすいです。
Tokenizer         形態素解析に使用します。

 

2. ベイズの定理の説明

 『条件付き確率』に関して成り立つ定理でP(A) > 0 のとき次の式が成り立ちます。

P(B|A)=P(A|B)P(B)/P(A)

P(A)とは、Aが起こる確率
P(B)とは、事象 A が起きる前の、事象 B の確率
P(B|A)とは事象 A が起きた後での、事象 B の確率

式で書かれただけでは意味が分かりづらいのですね・・そこで例をひとつ示します

A(P)を雨が降る確率だとし、B(P)を友達が約束の時間に遅れる確率とすると
P(B|A)は雨が降っていた時に友達が約束の時間に遅れる確率となります。

3. ナイーブベイズ分類

ベイズの定理では、Aを入力文章とすると、Bはどこの方言であるかということになります。
ナイーブベイズ分類では、ある文章を方言ごとに分けるのに、文章中の単語の出現率を調べます。

ナイーブベイズ分類は全ての方言の確率を計算し、その中で一番確率の高い方言を結果として出力します。
そのためとてもシンプルな分類です。
しかし、少ないトレーニングデータでも正しい結果が出る。重要でない特徴量の影響を受けにくいなどの特徴があるので今回の分類に使用します。

4. 形態素分析

さて、長かった前置きが終わりました、これから実際に文章を分解し解析をするコードを示します。

まずは形態素解析に必要なTokenizerをインストールしておきます。

import math, sys
from janome.tokenizer import Tokenizer # 形態素解析用

class BayesianFilter:
    """ ベイジアンフィルタ """
    def __init__(self):
        self.words = set() # 出現した単語を全て記録
        self.word_dict = {} # 方言ごとの単語出現回数を記録
        self.category_dict = {} # 方言の出現回数を記録

    # 形態素解析を行う 
    def split(self, text):
        result = []
        t = Tokenizer()
        malist = t.tokenize(text)
        for w in malist:
            sf = w.surface   # 区切られた単語そのまま 
            bf = w.base_form# 単語の基本形
            if bf == '' or bf == "*": bf = sf
            result.append(bf)
        return result

       # 単語と方言を数える処理 
    def inc_word(self, word, category):
        # 単語を方言に追加
        if not category in self.word_dict:
            self.word_dict[category] = {}
        if not word in self.word_dict[category]:
            self.word_dict[category][word] = 0
        self.word_dict[category][word] += 1
        self.words.add(word)

    def inc_category(self, category):
        # 方言を加算する
        if not category in self.category_dict:
            self.category_dict[category] = 0
        self.category_dict[category] += 1

5. 方言を分類する

方言を分類するために単語をスコア化し方言ごとに分ける。

    # 文章を学習する
    def fit(self, text, category):
        """ 文章の学習 """
        word_list = self.split(text)
        for word in word_list:
            self.inc_word(word, category)
        self.inc_category(category)

    # 方言ごとにおける単語リストのスコアを計算する
    def score(self, words, category):
        score = math.log(self.category_prob(category))
        for word in words:
            score += math.log(self.word_prob(word, category))
        return score
    # テキストの方言分けを行う
    def predict(self, text):
        best_category = None
        max_score = -sys.maxsize 
        words = self.split(text)
        score_list = []
        for category in self.category_dict.keys():
            score = self.score(words, category)
            score_list.append((category, score))
            if score > max_score:
                max_score = score
                best_category = category
        return best_category, score_list

    # 方言内の単語出現数を得る
    def get_word_count(self, word, category):
        if word in self.word_dict[category]:
            return self.word_dict[category][word]
        else:
            return 0

    # 方言/総方言を計算
    def category_prob(self, category):
        sum_categories = sum(self.category_dict.values())
        category_v = self.category_dict[category]
        return category_v / sum_categories
        
    # 方言内の単語の出現率を計算 
    def word_prob(self, word, category):
        n = self.get_word_count(word, category) + 1 
        d = sum(self.word_dict[category].values()) + len(self.words)
        return n / d

方言ごとのスコアを計算した時に、確率を掛け合わせ続けると値が小さくなりすぎる可能性があります。
なので、log関数を使用して対数を求めています。

6. 学習

ベイジアンフィルタは機械学習の教師あり学習になるため最初にいくつかの文章を学習させる必要があります。
今回は広島弁、大阪弁、博多弁、沖縄弁、北海道弁の5つに分類できるようにしました。
一つの方言につき20個の例文を学習させています。

bf = BayesianFilter()

#広島弁
bf.fit("寝不足じゃけぶちたいぎー","広島弁")
bf.fit("じゃけえ、ゆったじゃろ","広島弁")
bf.fit("お盆は帰ってきんさい","広島弁")
bf.fit("たちまち","広島弁")
bf.fit("汚な!あんたの部屋わやくちゃじゃ","広島弁")
bf.fit("ワレぶちまわしたろうか","広島弁")
           :
             :
#北海道弁
bf.fit("道産子(どさんこ)","北海道弁")
bf.fit("いつもあんたのことを考えてるっしょ","北海道弁")
bf.fit("あんたのこと好きだけどさ、どうしたら良いっしょか","北海道弁")

7. 実行結果

学習には使用していない文章を使ってどの方言か分類できているか検証してみます。
文章は大阪弁を使っているので、結果に大阪弁が表示されると正解になります。

pre, scorelist = bf.predict("今週末ユニバ行けへん?")
print("結果",pre)
print(scorelist)

結果 大阪弁
[('広島弁', -37.14937973016429), ('大阪弁', -30.792911151724105), ('博多弁', -36.56367867243777), ('沖縄弁', -36.90188366791113), ('北海道弁', -38.39497824243125)]

出力結果の解説をすると
左が方言名で、右の数字が単語の出現率となっています。出現率はマイナスなので、値が小さいほど出現率は高くなります。
今回は大阪弁の出現率が一番高いので、ちゃんと大阪弁に分類されていますね‼︎


他の方言でも試してみましたが、分類できました。

8. まとめ


最初はMLP(多層パーセプトロン)で分類をしようと思っていたのですが、方言で書かれた文章は以外に少なく断念しました。
良く考えると方言で話すことはあっても、文章を書くことは少ないのでデータが少なかったのかなと思いました。

学習させる文章を変えると分類に成功する確率が変わってきて面白かったです。
簡単に実装できるので興味が湧いた方はぜひやってみてください

9. 参考文献

books.google.co.jp

TinderAPIで女の子の顔写真を集めて、加工アリorナシを自動で判定してみた

こんにちは、Aidemy研修生のタットヤムです。

みなさん、かわいい女の子は好きですか?僕は大好きです。

     f:id:kirraria:20180628002323p:plain

今どきはマッチングアプリでいつでもかわいい女の子を見放題なのでいい時代ですね。中でも「Tinder」はアメリカを中心に世界的に流行ってる、相手の写真を見てアリなら右、ナシなら左にスワイプするだけでマッチングするという、かわいい子の顔を見て癒されるのにうってつけなアプリです。

しかしTinderでスワイプして写真を眺めていると、「この子かわいいけど加工してるっぽい?」なんてことありますよね。

Aidemyの研修でせっかくディープラーニングを学んだので、今回はTinderのPythonAPIを使って日本各地の女の子の顔写真を集めて、ディープラーニングで加工がされているかどうかを自動で判定してみようと思います。

だいたい手順はこんな感じになります。そのまま目次にしてしまいますね。

 

0.環境

主な使用ライブラリなどを載せておきます。

・Python 3.6.4    プログラミング言語

・Anaconda 1.6.9    プログラミングする環境を管理する

・Jupyter 4.4.0           ノートにそのまま書くみたいにプログラミングができる

・numpy 1.14.0          数学でやった行列みたいなのを上手に扱える

・matplotlib 2.1.2       グラフを描いたりしてくれる

・OpenCV 3.4.1.15    写真を上手に扱える

・Keras 2.2.0             機械学習のプログラムがたくさん入ったパッケージ的なもの

・Pynder 0.0.13    TinderをPythonで動かすためのAPI

 

インポート部分は全部でこんな感じになってます。

import os
import re
import random
import matplotlib.pyplot as plt
import cv2
import numpy as np
import urllib.request
from urllib.parse import quote
import httplib2
import pynder
from keras import optimizers
from keras.applications.vgg16 import VGG16
from keras.layers import Dense, Dropout, Flatten, Input
from keras.models import Model, Sequential, load_model
from keras.utils.np_utils import to_categorical

 

加えていくつか関数を定義しておきます。

def get_image(img_url): # ウェブ上の画像をダウンロードする
    opener = urllib.request.build_opener()
    http = httplib2.Http(".cache")
    response, content = http.request(img_url)            
    return content

def aidemy_imshow(name, img): # Jupyter notebook上で画像を表示
    b,g,r = cv2.split(img)
    img = cv2.merge([r,g,b])
    plt.imshow(img)
    plt.show()
cv2.imshow = aidemy_imshow

def jpg_count(folder_name): # ディレクトリ内のjpgファイルを数える
    files = os.listdir("./"+folder_name)
    jpgcount = 0
    for file in files:
        index = re.search(".jpg", file)
        if index:
            jpgcount += 1
    return jpgcount

def image_save(img,folder_name): # 画像を保存する
    # ディレクトリがなければ作る
    if not os.path.exists(folder_name):
        os.mkdir(folder_name)
    jpgcount = jpg_count(folder_name)
    # カウント数+1でファイル名をつけて保存
    w_pass = "./{}/{}.jpg".format(folder_name,jpgcount)
    cv2.imwrite(w_pass,img)

1.写真を集める

はじめに、Tinderから写真をたくさん集めます。コードは各章の下に貼っておきます。

PynderでTinderにアクセスするためには、アクセストークンというものを調べないといけません。アクセストークンはこのWebページ(Tinderface)に書いてあるとおりにやると調べることができます。

なおディープラーニングの学習に使う写真は、札幌・秋田・宇都宮・東京・横浜・名古屋・京都・大阪・福岡・沖縄を回るようにして集めました。

こんな感じで各都市で300人ずつスワイプして画像を集めました。 

f:id:kirraria:20180703203212p:plain

# 写真を保存するディレクトリを作成する
if not os.path.exists("images"):
        os.mkdir("images")

AccessToken = "ここにアクセストークンを貼ってね"
session = pynder.Session(AccessToken)
friends = session.get_fb_friends()

# 探索する都市の緯度と経度を指定
Locations = [["Sapporo", 43.055248, 141.345505],
             ["Akita", 40.824589, 140.755203],
             ["Utunomiya", 36.554241, 139.897705],
             ["Tokyo", 35.680909, 139.767372],
             ["Yokohama", 35.465786, 139.622313],
             ["Nagoya", 35.154919, 136.920593],
             ["Kyoto", 35.009129, 135.754807],
             ["Osaka", 34.702509, 135.496505],
             ["Hukuoka", 33.579788, 130.402405],
             ["Okinawa", 26.204830, 127.692398]
            ]

# 各都市で取得する人数の上限
Limit = 300

for location in Locations:
    session.update_location(location[1], location[2])
    users = session.nearby_users()
    count = 0
    for user in users:
        count += 1
        if count > Limit:
            break
        photo_urls = user.get_photos(width="640")
        for url in photo_urls:
            img_buf = np.fromstring(get_image(url), dtype='uint8')
            img = cv2.imdecode(img_buf, 1)
            image_save(img,"./images/{}".format(location[0]))
    print(location[0] + " is done")

2.顔の部分を抽出する

次に、集めた写真の顔の部分だけを抽出して、ディープラーニングが学習しやすいようにします。しかし何千枚もの写真を1つ1つ加工していくのは大変なので、ここではHaar-like特徴分類器というプログラムを使います。

仕組みを簡単に説明すると、人の顔はだいたい両目のところが暗く鼻のところは明るいとか、目が暗くてその下の頬は明るいとか、明暗でいくつかパターンがあって、そのパターンがあるものを人の顔として認識するという仕組みです。詳しくは下の記事を読んでみてください。

qiita.com

 結果を見てみると、わりとしっかりと顔に反応して抽出してくれていることが分かります。ディープラーニングとはまた違いますが、一見単純な仕組みでもちゃんと人の顔を抽出してくれるっていうのが何というかロマンがありますよね。

ただし顔スタンプなど、顔に似ているものもけっこう拾ってしまうので、これは次の手順のときに、一緒に分別しないといけません。

f:id:kirraria:20180703203219p:plain

# HAAR分類器の特徴量
cascade_path = "D:\Anaconda3\envs\Conda_AITrade\Lib\site-packages\cv2\data\haarcascade_frontalface_alt.xml"

face_num = [0, 0] # [0]が顔認識数、[1]が顔認識されなかったファイル数(経過表示)

if not os.path.exists("faces"):
        os.mkdir("faces")

for location in Locations:
    # 都市ディレクトリ内のjpgファイル数を読み込み
    imageN = jpg_count("./images/{}".format(location[0]))
    print(location[0]," : ",imageN)
    
    # ディレクトリ内のファイルをリストに格納
    dir_pass = "./images/{}/".format(location[0])
    file_names = os.listdir(dir_pass)
    for file_name in file_names:
        print(file_name, face_num)
        # 画像を読み込み、顔認識分類器にかける
        img = cv2.imread(dir_pass + file_name)
        # 画像ファイルが読み込めないとき
        if img is None:
            print("img is none.")
            continue
        img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        cascade = cv2.CascadeClassifier(cascade_path)
        faces_poses = cascade.detectMultiScale(img_gray, scaleFactor=1.1, minNeighbors=2, minSize=(64,64))
        # 顔が検出されたとき
        if len(faces_poses) > 0:
            for fp in faces_poses:
                img_face = img[fp[1]:fp[1]+fp[3], fp[0]:fp[0]+fp[2]]
                img_face = cv2.resize(img_face, (64, 64))
                image_save(img_face,"faces/{}".format(location[0]))
                face_num[0] += 1
        # 顔が検出されなかったとき
        else:
            face_num[1] += 1

3.重複した顔写真を削除する

次に、集めた顔写真の中で重複しているものを削除します。

同じ顔写真が複数あるとディープラーニングの学習に偏りが生じてしまう可能性があるので、しっかり消していきましょう。

今回はある写真が複数あるかどうかを調べるために、写真のヒストグラムの類似率を計算しました。カラー画像を構成する3色それぞれの濃淡をヒストグラムとして計算し、ヒストグラムが一定以上類似している写真を重複として判定して削除します。

類似度の閾値は、画像の一覧を目視してだいたい重複がなくなるぐらいに設定しましたが、もうちょっといい設定方法があったかもしれません。

顔写真はこれまでの処理で削られた結果、全部で1294枚となりました。

# パラメータ定義 ####################################
# 類似度の閾値
criret = 0.999

# 処理の実行 #######################################
indir_pass = "./faces/"
outdir_pass = "./faces_dd"
file_names = os.listdir(indir_pass)
passhist_list = []

for file_name in file_names:
    # 画像を読み込む
    img = cv2.imread(indir_pass + file_name)
    # 画像ファイルが読み込めないとき
    if img is None:
        print("img is none.")
        continue
    # RGBのヒストグラムを計算しリストに格納
    img_hist = []
    img_hist.append(cv2.calcHist([img], [0], None, [256], [0, 256]))
    img_hist.append(cv2.calcHist([img], [1], None, [256], [0, 256]))
    img_hist.append(cv2.calcHist([img], [2], None, [256], [0, 256]))
    # 参照ヒストグラムとの類似率を計算し、閾値以下であれば新規顔画像として保存する
    for pass_hist in passhist_list:
        if cv2.compareHist(img_hist[0],pass_hist[0],cv2.HISTCMP_CORREL) > criret:
            break
        if cv2.compareHist(img_hist[1],pass_hist[1],cv2.HISTCMP_CORREL) > criret:
            break
        if cv2.compareHist(img_hist[2],pass_hist[2],cv2.HISTCMP_CORREL) > criret:
            break
    else: # すべての参照ヒストグラムとの類似率が閾値以下だったとき
        passhist_list.append(img_hist)
        image_save(img, outdir_pass)

4.顔写真を普通の写真と加工写真に分ける

次に、集めた顔写真を普通の写真と、プリクラの写真、SNOWで加工した写真の3つに分けます。

実はこの作業が一番大変で、何千の画像データを1つ1つ見て確認してディレクトリに振り分けていくのは気が遠くなるような作業でした。またプリクラならまだわかりやすいのですが、SNOWで加工した写真は一見普通に見えるようになっている写真もあるので判定に困ることも多かったです。

しかしここの振り分けのゆらぎが、のちのモデルの性能に大きな影響を与えることもあるので、しっかりと自分の中で軸を定めて分けていきましょう。

5.ディープラーニングモデルに学習させる

いよいよ振り分けた顔写真を使って、ディープラーニングの学習を行っていきます。ディープラーニングをするためには、学習モデルを設計しないといけないのでそれから始めましょう。

5.1.学習モデル

今回の学習モデルはこんな感じに設計しました。今回はVGG16モデルを転移学習として使用し、その後2層の全結合層を経て出力層にて3クラスの分類を行います。

- 入力 (64x64×3 : 64×64のカラー画像)
- VGG16モデル(16層の転移学習モデル)
- 全結合層1(64, sigmoid関数, dropout=0.5)
- 全結合層2(32, sigmoid関数, dropout=0.5)
- 出力層(3, softmax関数)

5.2.転移学習

ちなみに2行目にあるVGG16モデルというのは、転移学習と呼ばれるもので、簡単にいうとほかの人があらかじめ学習までさせてくれた学習モデルをそっくり借りてくるというものです。

人間が物体を認識するとき、たとえばこんな風になりますよね。

→映像が見えた
→なんか丸っこいものが見えた、顔かな
→なんか目が大きいからSNOWっぽい

この「映像が見えた」ところから「なんか目が大きいからSNOWっぽい」まですべての工程を学習させるのがしんどいので、「映像が見えた」から「なんか丸っこいものが見えた、顔かな」ってところまでをほかの人が学習させてくれたものを使い、最後の「なんか目が大きいからSNOWっぽい」で加工されているかどうか判定するところだけを自分のお好みで学習させるという感じです。

5.3.データの水増し

今回はデータの80%をトレーニングデータ、20%をテストデータとしました。この2つを分ける理由は後述します。

それとモデル学習の前にもう一つ、データの水増しというのを行います。トレーニングデータの顔写真1035枚だとちょっと少ないので、画像を左右反転させたり、ぼかしたりして一つの画像を何枚にも増やします。今回は一つの画像が2×2×2=8枚になるように水増ししたので、データは8280枚となりました。

5.4.学習の実行

最後にモデルの学習を実行します。そのまま学習を進めてしまうとVGG16モデルのパラメータも変わってしまうので、そこだけ変わらないように固定します。今回はミニバッチ=32のepoch=100で学習を実行しました。

学習の経過を下のグラフに示します。青線がトレーニングデータに対する正答率で赤線がテストデータに対する正答率です。

f:id:kirraria:20180703184325p:plain

結果としてトレーニングデータに対してテストデータの正答率の上がり方が悪いので、若干過学習気味といった感じになってしまいました。最終的な正答率は約90%でした。原因としては、ラベリング時の振り分け基準があいまい、使ったモデルが適当でなかった等が考えられます。

# フォルダを生成
if not os.path.exists("result"):
        os.mkdir("result")

# Pynderを使いTinderにログイン
session = pynder.Session(AccessToken)

# モデルの読み込み
model = load_model("my_model.h5")

for location in Locations:
    session.update_location(location[1], location[2])
    users = session.nearby_users()
    count = 0
    for user in users:
        count += 1
        if count > Limit:
            break
        photo_urls = user.get_photos(width="640")
        for un, url in enumerate(photo_urls):
            img_buf = np.fromstring(get_image(url), dtype='uint8')
            img = cv2.imdecode(img_buf, 1)
            img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            cascade = cv2.CascadeClassifier(cascade_path)
            faces_poses = cascade.detectMultiScale(img_gray, scaleFactor=1.1, minNeighbors=2, minSize=(64,64))
            # 顔が検出されたとき
            if len(faces_poses) > 0:
                for fp in faces_poses:
                    # 顔を囲む枠を生成
                    cv2.rectangle(img, tuple(fp[0:2]), tuple(fp[0:2]+fp[2:4]), (0, 0, 255), thickness=3)
                    # 学習したモデルでスコアを計算する
                    img_face = img[fp[1]:fp[1]+fp[3], fp[0]:fp[0]+fp[2]]
                    img_face = cv2.resize(img_face, (64, 64))
                    score = model.predict(np.expand_dims(img_face, axis=0))
                    # 最も高いスコアを書き込む
                    score_argmax = np.argmax(np.array(score[0]))
                    text =  "{0} {1:.3}% ".format(Labels[score_argmax], score[0][score_argmax]*100)
                    cv2.putText(img, text, (fp[0],fp[1]+fp[3]+30), cv2.FONT_HERSHEY_DUPLEX, 1, (0,0,255), 2)
                # 画面に表示
                cv2.imshow("{}_{}".format(location[0], count), img)
                plt.show()
                image_save(img,"result")
            # 顔が検出されなかったとき
            else:
                continue

6.実際にTinderの写真に適用してみる

それでは実際に、新規にTinderから取得した写真をモデルに入力し、正しくラベリングできているかどうかを見ていきましょう。

まずは普通の写真から、高いパーセンテージで普通とラベリングされています。

f:id:kirraria:20180703200241j:plain
f:id:kirraria:20180703200207j:plain

 

次にプリクラの写真です。左の写真はちょっとパーセンテージが低いですね。ほかの写真でも実行した結果、プリクラの写真はたまに普通の写真としてラベリングされてしまうようです。

f:id:kirraria:20180703200343j:plain
f:id:kirraria:20180703200425j:plain

 

最後にSNOWで加工された写真です。SNOWの写真もプリクラの写真と同様に、基本的には拾ってくれるのですが、たまに普通の写真としてラベリングされてしまいました。

f:id:kirraria:20180703200820j:plain
f:id:kirraria:20180703200842j:plain

 

これらは間違ってしまっているものです。ぼやけていたりノイズが入ったりしていると誤った判定になりやすいなと感じました。

f:id:kirraria:20180703202753j:plain
f:id:kirraria:20180703202805j:plain
# フォルダを生成
if not os.path.exists("result"):
        os.mkdir("result")

# Pynderを使いTinderにログイン
session = pynder.Session(AccessToken)

# モデルの読み込み
model = load_model("my_model.h5")


for location in Locations:
    session.update_location(location[1], location[2])
    users = session.nearby_users()
    count = 0
    for user in users:
        count += 1
        if count > Limit:
            break
        photo_urls = user.get_photos(width="640")
        for un, url in enumerate(photo_urls):
            img_buf = np.fromstring(get_image(url), dtype='uint8')
            img = cv2.imdecode(img_buf, 1)
            img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            cascade = cv2.CascadeClassifier(cascade_path)
            faces_poses = cascade.detectMultiScale(img_gray, scaleFactor=1.1, minNeighbors=2, minSize=(64,64))
            # 顔が検出されたとき
            if len(faces_poses) > 0:
                for fp in faces_poses:
                    # 顔を囲む枠を生成
                    cv2.rectangle(img, tuple(fp[0:2]), tuple(fp[0:2]+fp[2:4]), (0, 0, 255), thickness=3)
                    # 学習したモデルでスコアを計算する
                    img_face = img[fp[1]:fp[1]+fp[3], fp[0]:fp[0]+fp[2]]
                    img_face = cv2.resize(img_face, (64, 64))
                    score = model.predict(np.expand_dims(img_face, axis=0))
                    # 最も高いスコアを書き込む
                    score_argmax = np.argmax(np.array(score[0]))
                    text =  "{0} {1:.3}% ".format(Labels[score_argmax], score[0][score_argmax]*100)
                    cv2.putText(img, text, (fp[0],fp[1]+fp[3]+30), cv2.FONT_HERSHEY_DUPLEX, 1, (0,0,255), 2)
                # 画面に表示
                cv2.imshow("{}_{}".format(location[0], count), img)
                plt.show()
                image_save(img,"result")
            # 顔が検出されなかったとき
            else:
                continue

7.まとめ

「TinderAPIで女の子の顔写真を集めて、加工アリorナシを自動で判定してみた」はいかがだったでしょうか?

私は、TinderのAPIで取得できる情報は写真以外にもいろいろあるみたいなので、ソーシャル系のデータ解析としていろいろ取り組めることがあるかもしれないなあと感じています。

またプログラミングの課題で扱うような整理されたデータでは考慮しないようなところをいろいろ考えなくてはならないことを身をもって体感することができたことはとてもよい経験でした。正直ディープラーニングの学習モデルの出来としてはまだまだだと思いますが、モデルの構築理論などをしっかり勉強してより良いモデルが作れるように精進していきたいと思います。

最後までお付き合いくださり、ありがとうございました!

 

#Aidemynote エントリー作品のまとめ

「Aidemyユーザーにアウトプットの機会を持ってもらいたい」
そんな気持ちで生まれた#Aidemynote企画。今回は、なかでも秀逸だったものを分類してまとめました!

アジェンダは以下の通りです。

-【Aidemy初心者向け】Aidemyで勉強してみたnote 
-【Aidemy一通り勉強した人向け】実装してみたnote
-エヴァンジェリストの活動note

 

 

【Aidemy初心者向け】Aidemyで勉強してみたnote 

 

-「AIプログラミングを学んでみよう」
まずはこれを読んでみよう!Aidemyを使うメリットってなに?

note.mu


-Aidemyをやってみた感想
Aidemyの"ヤバい"点4つをわかりやすく解説してくれました。

note.mu


-「有料化してもAidemyは利用価値ありか!?」

Aidemy課金すべきかしないべきか!?を考察した記事

https://note.mu/z2_mrs/n/n3c702680a86c


-「Aidemyを利用したらマッハで課金してしまった話。」

課金ユーザーの方の記事。この記事のようにツイートと何か良いことがあるかも?

note.mu

 

-「Aidemyをやってみた① Python入門」

コースごとにnoteを書いてくれました!

Python入門が気になっている方必読の記事。

note.mu


-「Aidemyをやってみた② 機械学習概論」

機械学習概論って実際どうなの?

note.mu


-「Aidemyをやってみた③ ブロックチェーン基礎」

話題のブロックチェーンを実装しながら学べるのはAidemyだけ!

率直な感想を読むことができます!

note.mu


-Aidemyやってみたけど次何したら良いの?を他サービスと比較しつつ解説しています。

note.mu


-「Aidemy基本コース→Udemy学習でPython入門を学ぶと道が開ける」

ブロックチェーン講座について紹介している記事です。

note.mu

 

 

【Aidemy一通り勉強した人向け】実装してみたnote

ここからは、Aidemyで学んだことをアウトプットした記事です。

やってみたいなと思った人で、Aidemyのどのコースを受講すれば良いかわからない人のために、受講ルートを3つに分けたので参考にしてくださいね!

 

Python入門→NumPyを用いた数値計算→Matplotlibによるデータの可視化データクレンジング機械学習概論教師あり学習(分類)ディープラーニング基礎→CNNを用いた画像認識

Python入門→NumPyを用いた数値計算→Matplotlibによるデータの可視化→Pandasを用いたデータ処理データクレンジング機会学習概論教師あり学習(回帰)時系列解析

-「画像認識で『綾鷹を選ばせる』AIを作る」

言わせたい、AIに、「選ばれたのは綾鷹でした」

これをやってみたい人が学ぶといいAidemyの講座:CNNを用いた画像認識

qiita.com


-「ディープラーニングで動画に自動でモザイクをかける『ディープモザイク』作ってみました」

モザイクをかけたい動画が手元にある、、!そんなあなたにディープラーニング!

これをやってみたい人が学ぶといいAidemyの講座:ディープラーニング基礎

karaage.hatenadiary.jp

 

-「【Aidemy × Bio】Aidemyのコースを応用して、遺伝子解析データを学習させてみた」
ハーバード大発。機械学習でデータを要約、その特徴を掴む手綱とする

これをやってみたい人が学ぶといいAidemyの講座:教師なし学習

note.mu


-「【Aidemy × Bio】機械学習はがん細胞を見分けられるか?:遺伝子解析データをもとに教師あり学習(分類〉を行ってみる!」

では、機械学習はがん細胞と正常の細胞を見分けることが出来るのか?

これをやってみたい人が学ぶといいAidemyの講座:教師あり学習(分類)

note.mu


-「【Aidemy × MIT】MITのチュートリアルの教材を利用して、ワインの特性データのクラスタリングを深めてみる!」
ワインのデータを元に、教師なし学習
これをやってみたい人が学ぶといいAidemyの講座:教師なし学習

note.mu

 

--「【Aidemy × MIT】MITのチュートリアルの教材を利用して、ワインの特性データの分類(classification)を行ってみる!」
ワインのデータをもとに教師あり学習を行う

これをやってみたい人が学ぶといいAidemyの講座:教師あり学習(分類)

note.mu


- 「【MITの機械学習講座】回帰編をつかって、エイズウイルスの研究をしてみよう!」
エイズウイルスの薬耐性について回帰を行う

これをやってみたい人が学ぶといいAidemyの講座:教師あり学習(回帰〉

note.mu

 

-「ナスDがセネガルの監督に似ているか確かめるために、似ているワールドカップ出場者を検索するAIをつくった」

自分はどのワールドカップ出場選手に似ているか判別してくれるAI

これをやってみたい人が学ぶといいAidemyの講座:CNNを用いた画像認識

qiita.com


-「AIで日経平均を予言!?2018年末に26,000園(Prophetで楽々予測)」

Facebookの時系列予測ツールPrpophetを活用して投資判断をしてみる

これをやってみたい人が学ぶといいAidemyの講座:時系列解析
startupangels.jp


-「Deep Learningを用いた樹皮画像による樹種同定(Keras,CNN,転移学習,VGG16)」

ディープラーニングでコナラとイチョウを見分ける

これをやってみたい人が学ぶといいAidemyの講座:CNNを用いた画像認識

www.asanohatake.com

 -「女性モデルの分類を主成分分析で挑戦」

どんな体型の人がモデルに向いているのか?

これをやってみたい人が学ぶといいAidemyの講座:教師なし学習

note.mu

 

-「機械学習を使って焼き肉で肉が焼けたかどうかを区別してみた」

焼き肉の焼き加減を区別するAI

これをやってみたい人が学ぶといいAidemyの講座:CNNを用いた画像認識

qiita.com

 

 

エヴァンジェリストの活動
-Aidemyエヴァンジェリストがある一日をシェアしてくれました。

note.mu



 

 

 

Aidemy公式Facebookページでは、こういったアウトプットブログや新講座の情報、さらにセール情報も発信しています。

 

 

 

 

安倍首相は常にネガティブ!?!?

   実行結果
f:id:ryo0927:20180627204643p:plain
初めまして、Aidemy研修生の加賀美です。
今回は、自然言語処理(MeCab)とTwitter APIを使い、安倍首相のツイートを感情分析してみました。

・対象者

 PythonでTwitter APiを使ってみたい人

 MeCabを使って日本語の感情分析をやってみたい人

では、早速Tweitter APIを利用して感情分析をしてみましょう!!

目次

実装

Tweitter APIのkey取得

Twitterデータは、APIを使って取得します。APIを使用するために必要なkeyは以下の4つです。

 ・Consumer Key
 ・Consumer Secret
 ・Access Token
 ・Access Token Secret

これらのkeyを取得する手順の仕方については、以下記事でわかりやすくまとめられていますので、ご参照ください。
www.randpy.tokyo

ツイートを取得する

以下のコードで、特定のユーザのツイートを取得します。

#今回使用するモジュール
import neologdn
import re
import json
from requests_oauthlib import OAuth1Session
import requests
import MeCab
import pandas as pd
import re
from statistics import mean
from bs4 import BeautifulSoup
from datetime import datetime
import matplotlib.pyplot as plt
%matplotlib inline

#カンマの間に取得したkeyを入れてください
consumer_key = ''
consumer_secret = ''
access_token = ''
access_token_secret = ''

#APIの認証
twitter = OAuth1Session(consumer_key,consumer_secret,access_token,access_token_secret)

#ユーザのツイートを取得
url = 'https://api.twitter.com/1.1/statuses/user_timeline.json'

#取得するユーザを指定 @の後の名前
screen_name = "AbeShinzo"

#パラメータ設定 max_id: どのツイートから取得するか決める
params = {'screen_name': screen_name,'count': 100,
         'max_id':1008645666061500419,'include_rts':False,
          'exclude_replies':True,'contributor_details':False}

#リクエストを投げる
res = twitter.get(url,params = params)

#テキスト・時間・IDを追加する場所
tweet_list = []
time_ = []
tweet_id = []

#ツイートを取得 今回は制限に引っかからないようにしていますが、念の為、制限まで行ったらストップするようにしています。’if limit == 1の数字を変更するとより多くのツイートを取得できます。’
for j in range(1):
    res = twitter.get(url,params = params)
    if res.status_code == 200:
        #APIの残りを表示
        limit = res.headers['x-rate-limit-remaining']
        print ("API remain: " + limit)
        if limit == 1:
            #15分間停止させる
            sleep(60*15)
        
        timeline = json.loads(res.text)
        #各ツイートの本文を'tweet_list'に追加
        #各ツイートの投稿日を'time_'に追加
        for i in range(len(timeline)):
            if i != len(timeline)-1:
                tweet_list.append(timeline[i]['text']+'\n')
                time_.append(timeline[i]['created_at'])
                tweet_id.append(timeline[i]['id'])
            else:
                tweet_list.append(timeline[i]['text']+'\n')
                time_.append(timeline[i]['created_at'])
                tweet_id.append(timeline[i]['id'])
                
                params['max_id'] = timeline[i]['id']-1
# 日付の整形 Twitter APIから得られる日付は米国の時間のため日本時間に直す
time_list = []
def make_convert_date_format(src_format, dst_format):
    def convert_date_format(s):
        return datetime.strftime(datetime.strptime(s, src_format),dst_format)

    return convert_date_format

convert_date_format = make_convert_date_format('%a %b %d %H:%M:%S %z %Y', '%Y-%m-%d')
for i in range(len(time_)):
    time_list.append(convert_date_format(time_[i]))
    
# ツイートからURLを削除、不要な文字を消す
def format_text(text):
    text = re.sub(r"(https?|ftp)(:\/\/[-_\.!~*\'()a-zA-Z0-9;\/?:\@&=\+\$,%#]+)", "", text)
    text = re.sub("\d", "", text)
    text = re.sub("\s", "", text)
    text = re.sub("\n","",text)
    text = re.sub("、","",text)
    text = re.sub("#","",text)
    text = re.sub("[()]","",text)
    text = re.sub("・","",text)
    text = re.sub("。","",text)
    text = re.sub("「","",text)
    text = re.sub("」","",text)
    text = re.sub("[a-zA-Z0-9]","",text)
    text = re.sub("@","",text)
    return text

neo_tweet = []
tweet_c = []
for i in range(len(tweet_list)):
    neo_tweet.append(neologdn.normalize(tweet_list[i]))
    tweet_c.append(format_text(neo_tweet[i]))

データフレーム にして確認。paramsのmax_idを変更するとそのツイートよりも前のツイートを取得します。

#データフレームを作成
df = pd.DataFrame({'tweet_list':tweet_list,'time':time_list,'tweet_id':tweet_id})
print(df.head())

f:id:ryo0927:20180621001903p:plain
実行結果
 

ツイートを形態素解析する

先に、以下のリンク先から単語感情極性対応表をダウンロードしてローカルに保存してください。
PN Table

#PN Tableを読み込み
pn_df = pd.read_csv('pn_ja.dic.txt',sep=':',encoding='shift-jis',
                    names=('Word','Reading','POS', 'PN'))
# MeCabインスタンス作成
m = MeCab.Tagger('')  # 指定しなければIPA辞書

# PN Tableをデータフレームからdict型に変換しておく
word_list = list(pn_df['Word'])
pn_list = list(pn_df['PN'])  # 中身の型はnumpy.float64
pn_dict = dict(zip(word_list, pn_list))

# テキストを形態素解析して辞書のリストを返す関数
def get_diclist(text):
    parsed = m.parse(text)      # 形態素解析結果(改行を含む文字列として得られる)
    lines = parsed.split('\n')  # 解析結果を1行(1語)ごとに分けてリストにする
    lines = lines[0:-2]         # 後ろ2行は不要なので削除
    diclist = []
    for word in lines:
        l = re.split('\t|,',word)  # 各行はタブとカンマで区切られてるので
        d = {'Surface':l[0], 'POS1':l[1], 'POS2':l[2], 'BaseForm':l[7]}
        diclist.append(d)
    return(diclist)

# 形態素解析結果の単語ごとdictデータにPN値を追加する関数
def add_pnvalue(diclist_old):
    diclist_new = []
    for word in diclist_old:
        base = word['BaseForm']        # 個々の辞書から基本形を取得
        if base in pn_dict:
            pn = float(pn_dict[base])
        else:
            pn = 'notfound'            # その語がPN Tableになかった場合
        word['PN'] = pn
        diclist_new.append(word)
    return(diclist_new)

# 各ツイートのPN平均値をとる関数
def get_pnmean(diclist):
    pn_list = []
    for word in diclist:
        pn = word['PN']
        if pn != 'notfound':
            pn_list.append(pn)  # notfoundだった場合は追加もしない            
    if len(pn_list) > 0:        # 「全部notfound」じゃなければ
        pnmean = mean(pn_list)
    else:
        # 全部notfoundならゼロにする
        pnmean = 0
    return(pnmean)

# 各ツイートをPN値に変換
pnmeans_list = []
for tw in tweet_c:
    dl_old = get_diclist(tw)
    dl_new = add_pnvalue(dl_old)
    pnmean = get_pnmean(dl_new)
    pnmeans_list.append(pnmean)

PN値に欠損値がないか確認

PN値とは、そのツイートがPositiveかNegativeかどうかを判断する数値です。この数値は+1 ~ -1の範囲で表現されます。+1に近ければポジティブ、-1に近ければネガティブと判断することができます。

a = pd.Series(pnmeans_list)
print(a.head(25))

f:id:ryo0927:20180623033225p:plain
22、23行目に0がありましたこれを確認してみます。
安倍首相のツイートをクリックして後ろの数字をTweet_IDの数字に変更すると該当するツイートに飛ぶことができます。

tn1 = tweet_id[22]
tn2 = tweet_id[23]
print('Tweet_ID 22行目: '+str(tn1))
print('Tweet_ID 23行目: '+str(tn2))

f:id:ryo0927:20180621004243p:plain
22行目のツイート
f:id:ryo0927:20180621004339p:plain
23行目のツイート
f:id:ryo0927:20180621004429p:plain
なぜPN値が0になってしまうのかというと、MeCabという日本語の形態素解析エンジンを使用しているため、英語に対応していないからです。

英語のツイートを除外する

PN値が0の、PN値・ツイート・ツイートID・投稿日を除外して別のリストに保存する

# PN値が0のものを削除
pnl = []
twl = []
tl = []
il = []
for i in range(len(pnmeans_list)):
    if pnmeans_list[i] != 0:
        pnl.append(float('{:.4}'.format(pnmeans_list[i])))
        twl.append(tweet_c[i])
        tl.append(time_list[i])
        il.append(tweet_id[i])

データフレーム にして22、23行目を確認

df1 = pd.DataFrame({'tweet':twl,'time':tl,'tweet_id':il,'PN値':pnl})
print(df1.iloc[22])
print(df1.iloc[23])

f:id:ryo0927:20180623033937p:plain
上の画像を見ると、英語のツイートがなくなっていることが確認できます。

PN値を見て、ポジティブかネガティブか判断する

さて、ここからが本題です。PN値の平均を出して、普段ポジティブとネガティブのツイートどちらが多いのか確認していきます。

#データフレームの作成
df = pd.DataFrame({'tweet':twl,'time':tl,'tweet_id':il,'PN値':pnl})

# 全体のPN値の平均を計算
_mean = df['PN値'].mean()
print('平均: '+str('{0:.4}'.format(_mean)))
pnl_max = max(pnl)
print('最大値: '+str(pnl_max))
pnl_min = min(pnl)
print('最小値: '+str(pnl_min))

print()
# PN値が0以上のものを抜き出す
for i in range(len(il)):
    if pnl[i] >= 0:
        print('PN値0以上: '+str(df.iloc[i]))
print()

# PN値が最小のものを抜き出す
for i in range(len(pnl)):
    if pnl[i] == pnl_min:
        print('PN値最小:'+str(df.iloc[i]))
print()

#ツイート毎にPN値をプロットする
plot_df = pd.DataFrame(pnl,index=tl)
plot_df.plot.bar(figsize=(20,10))
plt.tick_params(labelsize = 15)
plt.tight_layout()
plt.show()

f:id:ryo0927:20180623035750p:plain
f:id:ryo0927:20180627203500p:plain
ほう、グラフにするとかなりわかりやすいですね。12/20日以外は常にネガティブなツイートをしているようですね(笑)

よし、完成!じゃないんです。まだ、正規化を行っていません。
上で定義した、「def get_diclist(text)」を下記のように変更してください。

# ストップワードを取得
def stopwords():
    target_url = 'http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt'
    r =requests.get(target_url)
    soup=BeautifulSoup(r.text, "html.parser")
    stop_=str(soup).split()
    return stop_

# 正規化
def dictionary_words(text):
    stop_word = stopwords()
    parsed = m.parse(text)# 形態素解析結果(改行を含む文字列として得られる)
    lines = parsed.split('\n')  # 解析結果を1行(1語)ごとに分けてリストにする
    lines = lines[0:-2]         # 後ろ2行は不要なので削除
    diclist = []
    for word in lines:
        l = re.split('\t|,',word)  # 各行はタブとカンマで区切られている
        d = {'Surface':l[0], 'POS1':l[1], 'BaseForm':l[7]}
        if d['POS1'] == '名詞' or '動詞' or '形容詞' or '副詞':
            if not d['BaseForm'] in stop_word:
                diclist.append(d)
    return (diclist)

そして、下記も変更してください

# 各ツイートをPN値に変換
pnmeans_list = []
for tw in tweet_c:
    dl_old = dictionary_words(tw) #ここを変更
    dl_new = add_pnvalue(dl_old)
    pnmean = get_pnmean(dl_new)
    pnmeans_list.append(pnmean)

すると,,,,
f:id:ryo0927:20180623040014p:plain
f:id:ryo0927:20180627204643p:plain
グラフの見え方とPN値が変わりましたね。
正規化で「名詞・動詞・形容詞・副詞」だけを抽出することによって、それ以外の品詞にPN値が引っ張られないようにすることができます。

PN値が最大、最小のツイートを確認する

PN値が最大のツイートを見てみましょう
f:id:ryo0927:20180621033503p:plain
すごいポジティブ!!(笑)
「新しい」、「創っていってくれる」、「全力」、「支援」というキーワードがPN値を上げてくれる要因になったのかなと思いました。

次にPN値が最小のツイートを見てみます
f:id:ryo0927:20180621033642p:plain
いや、どんだけブルガリアに行きたくないんだ(笑)
「凍った」、「昼食後」がPN値を下げているのかなと思いました。

感想

今回は特定のユーザのツイートを取得してみましたが、次はキーワードやハッシュタグを取得して感情分析を行って行きたいと思っています。
自分でプログラムを組むと勉強したことの復習や、勉強で出てこなかった部分がとても勉強になりました。
「もう少しコードが短くなるよ!」「こんなやり方があるよ!」などありましたらぜひ教えてください!