UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

「僕」でもわかるSEO対策、基礎の基礎の基礎

こんにちは UUUMでエンジニアしてるながいです。

題名の通り「僕」でもわかるSEO対策です。ちなみに「僕」はまだパソコンを初めて買ってから1年のキラキラ✨✨✨✨✨✨、ペーペーの「僕」です!

なんでこの記事を書くかというと個人的に気になるから、それだけです。サイトのSEO対策をしてみたかったので、、、

SEO対策すればそれなりのサービスでも人が来るんじゃない?という安直な考えからきています。

逆に言えばすごいサービスでも人に知られなければ使われることはないわけですし。多分、たぶん、tabun、、、

さて!

そもそもSEOってなんの略?

これよくSEOって耳にはしますけど実際なんの略なの?ってところから

こういう使ってるけど知らないとか、読み方違う系の言葉が無限にこの界隈にはありますよね。

ちなみに僕はREADMEを「レドメ」ってなんなんって4ヶ月くらい思いながら読んでました!

Search Engine Optimization:検索エンジン最適化

らしいです。これ知ってる人どれくらいいるのかな、、、僕はもちろん知りませんでした!!

まぁでも読んで字のごとくですね、SEOってそういうものだよね確かにって感じがしますね。

Googleとかの検索エンジン上位に表示されるように自分のサイトを最適化して行こう!って感じですね。

どういうアルゴリズムでGoogleは検索上位を作ってるの?

Googleは色々な要素で上位に表示する項目を決定しているようです。その数は200以上!!!

そして最も重要な要素と言えるのが「被リンク」と「コンテンツ」と言われるものらしいです。

まず被リンクってなんだよって感じです。これは自分のサイト以外に表示されている自分のサイトのリンクいわゆるURLの事のようです。

例えば自分のサイトを誰かがブログでシェアしてくれたりしたとします。その際に、リンクを貼ってシェアすることになると思うのですがそれがいわゆる「被リンク」と言われるもののようです。

しかしこの被リンクには良質なものと低質なものがあるようで、自然な被リンク、例えばシェアなどが良質なものとされるようです。低質なものというのは関係のないリンクのみが羅列掲載された相互リンク集などのコンテンツを指すようです。

この低質なリンクというのはSEO効果を下げてしまうようなので、良質な被リンクだけを増やすようにした方がようさそうですね。とはいえシェアしてもらうにはコンテンツが重要ですので被リンクに関しては余計なことをしなければ良いという感じでしょうか。

となるとエンジニアリングサイドで対策できるのは「コンテンツ」に関してかと思われます。具体的にはそのコンテンツのどこを評価しているのでしょうか?

  • Expertise(専門性)

  • Authoritativeness(権威性)

  • Trustworthiness(信頼性)

どうやらGoogleはこの3つを評価すると明言しているらしいです。

ちなみに日本で使われてる検索エンジンの90%以上がGoogleらしいのでとりあえずGoogleでの対策を押さえておけばいいと思います。というか他のエンジンの対策してる人はいるんでしょうか。

まぁ要するにこのGoogleの評価基準から考えるとコンテンツはユーザーファーストでなおかつ、ユーザーにとって質の高いコンテンツを評価しますよってことかと思います。

具体的にどんな感じでコンテンツを作ったら良いかを次に考えて行きましょう。

どんなコンテンツにすれば良いの?

まずどのキーワードでページをアルゴリズムに評価されるかを考えなければいけません。

原則として1ページ1キーワードという概念があるようです。

例えば「UUUM 所属youtube」って検索した人の上位に次ページを出したいなと思ったらUUUMに所属するyoutuberの一覧が載ってるページを見たいわけで、多数のyoutuberのそれぞれの所属事務所が見たいわけではないはずです。

なのでUUUM所属のYouTuberというキーワードでコンテンツを作らないと「UUUM 所属YouTuber」には引っかからなくなるのです。

そしてさらにコンテンツ量(文字数)が大事なようです。文字数の少ないページは低品質なページとみなされるようです。

確かに検索してて読み応えのないページってあんまり上にほうに出てこないですよね。

具体的には800~1000文字以上あれば低品質なページとはみなされないようです。

比較的難易度の高いキーワードでも2,000~4,000文字程度のコンテンツが上位に表示されているらしいです。

「UUUM 所属YouTuber」だったらそんなに難易度の高いキーワードとも取れないきがするので1200文字くらいでいいんじゃないでしょうか?(適当です)

HTMLタグの重要性について

さてコンテンツの作り方の方針が決まったところで細かなHTMLのタグなどの対策をしていきましょう。

重要なのはtitleタグh1タグだろうと素人ながらに軽く知っています。実際やはりそのようですね。しかしここでもう一つ超重要タグがあるようです。それがmetaタグ

とりあえず一つずつ追っていきましょう。

titleタグ

これは検索したるしたらそもそもみなさんみる見出しっぽいあれになるやつです。Googleくんもここを重要に見てるらしいですよ。

<title>ここにページのタイトルを記述</title>

ポイントは簡潔でキーワードを含めること

そして30文字程度にすること。目立たせること。

また他サイトとの重複を避けることも大切です。

以上を踏まえて、先ほどの「UUUM 所属YouTuber」の例だとしたら、「【UUUM所属】のYouTuber一覧!6000人越え!?人気YouTuberが勢揃い!!

とかでいいんじゃないでしょうか?(適当です)

h1タグ

これもまた重要なポイントです。

<h1>ここに見出しを記述</h1>

そして1ページ1キーワードの原則どおり1ページに対してh1タグは1つしかつけてはいけません。

というのが常識かと思ったのですがGoogleは何個あっても問題はないと声明しているらしいです。

ケースバイケースといったところでしょうか。

そして内容は「キーワード」を入れた、コンテンツに合った「簡潔な文」で作成しましょう。

これは超重要ですね。

SEOを知らないとなんか文字で隠したいしh1にしとくかとかいうノリでつけがちですが、h~系のタグは意識してつける必要が非常にありそうです。

metaタグ

これは僕も見落としていたのですがmetaタグもめちゃくちゃ重要ですね。

metaタグにも色々あると思うのですがこれはmeta description についてです。

<meta name="description" content="ここにページの説明を記述" />

よく検索した結果一覧を見るとtitleのしたに内容の軽い説明見たいのがありますよね。それがこれです。

あの文を見て僕は結構クリックするか決めるのでそういう意味合いが強いかと思います。

直接的に表示順位とは関係はなさそうですね。

クリックしたくなるような説明書きをかければ良さそうです。

ただ一つ注意点があり、pcとspでは表示される文字数に違いがあるようです。

SP 50文字程度
PC 120文字程度

のようですね。どちらに重きを置きのか、もしくはスマホとPCで変更できる機能を作るのも吉ですね。

まとめ

ここまで調べたことを書いてきましたがとりあえずはこの辺りに注意をしてサイトを作れれば初心者としては十分なSEO対策ができるのではないでしょうか?

もちろんSEOというのは奥がめちゃくちゃ深いものなので突き詰めていけばキリはないと思います。

手っ取り早くサイトの閲覧数を稼ぐために最低限のことはしておきたいですよね。

エンジニアとしては作ることが楽しいですけど、こういうコンテンツも重要視していかなければどんなにいいサービスも作ってもらえなくなってしまいます。

エンジニアとしていろんな方面へ手を出してみることが大事ですね〜

www.wantedly.com

インターンで初プルリクエストを出してマージされるまで

こんにちわ。2019年4月15日にUUUMにインターンとして入社したれとるときゃりーです。

インターンに入るまで、個人でサービスを作るなどして開発はしていましたが、実際の会社に入ってコードを書くのは初めてでした。

ちょうどUUUMに入って任された一つ目のタスクが終わったので、振り返りを込めてブログに執筆していこうと思います。

f:id:retoruto-carry:20190529170529p:plain
プルリクがマージされたところ

前提条件

laravel+nuxt.jsのプロダクトにアサインされました。

どのような実装を任されたか

外部のRSSを受け取って、それをサービス内で表示するようにします。

※ RSSとは

RSSは、ニュースやブログなど各種のウェブサイトの更新情報を配信するための文書フォーマットの総称である。( wikipediaより引用)

どのように実装したか

※ コードは一部省略しています

実装の方針

  • バックエンド側は、RSSを受け取り、タイトルやサムネイル画像のURLなど必要な部分のみを抜き出してAPIとして返します

  • フロント側は、バックエンド側のAPIを叩いて表示します

バックエンド側(laravel)

RSSを受け取る

ライブラリを使えば簡単でした。willvincent/feedsを使用しました。

サムネイル画像は簡単に取得できませんでした。記事中の一番最初に出てくるimgタグの中のsrcを取ってくる必要がありました。

画像のパース部分は、最初は正規表現で実装していました。

// 記事の本文の中からimgタグを抽出して画像URLを取得
$pattern = '/<img.*?src\s*=\s*[\"|\'](.*?)[\"|\'].*?>/';
if (preg_match($pattern, html_entity_decode($feedItem->get_content()), $match)) {
    $image = $match[1];
} else {
    $image = null;
}

しかしこの実装だと、記事側でのタグの表記揺れがありうまくいかないときがありました。

正規表現はつらいのでやめました😇

HTMLパーサーのライブラリammadeuss/laravel-html-dom-parserを導入して解決しました。

// 記事の本文の中からimgタグを抽出して画像URLを取得
$image = HTMLDomParser::str_get_html(html_entity_decode($feedItem->get_content()))->find('img')[0]->attr['src'];

テスト

テストは書いたことがなかったので、かなり苦戦しました。 他のテストを参考に、laravelとPHPUnitのドキュメントを見ながら書きました。

上述した、willvincent/feedsをモックする必要があったので、中身を少し読む必要がありました。

個人でサービスを作っている中では、ライブラリのコードをまったく見たことなかったので新鮮でした。

「Qiitaなどで調べるより、直接コードを見たほうが早いので癖をつけたほうが良い」ということを先輩に教えてもらいました。

エディタのコードジャンプ機能も、この時教えてもらいました。(今まで知らなくて1年位やってきたので便利すぎて泣きました😭)

書いたテストはこんな感じです。

<?php
namespace Tests\Unit\ControllerTest\Api\V1;
use Mockery;
use Tests\TestCase;
use App\Http\Controllers\Api\V1\HogeController;
use Feeds;
class HogeControllerTest extends TestCase
{
    private $hogeController;
    /**
     * {@inheritdoc}
     */
    public function setUp()
    {
        parent::setUp();
        $this->hogeController = new hogeController();
    }
    /**
     * Test Get List Hoge
     *
     * @dataProvider listHogeDataProvider
     * @param array $hoges データセット
     * @param array $expect 期待値
     * @return void
     */
    public function testFetchListHoge(array $hoges, array $expect)
    {
        $fetchedTopics = [];
        foreach ($hoges as $hoge) {
            $simplePieItem = $this->mock('SimplePie_Item');
            $simplePieItem->shouldReceive('get_content')->andReturn($creatorTopic['content']);
            $simplePieItem->shouldReceive('get_title')->andReturn($creatorTopic['title']);
            $fetchedTopics[] = $simplePieItem;
        }
        $feed = $this->mock('SimplePie');
        $feed->shouldReceive('get_items')->withAnyArgs()->andReturn($fetchedTopics);
        $feed->shouldReceive('get_title')->andReturn('title');
        Feeds::shouldReceive('make')->with(Mockery::any())->andReturn($feed);
        $response = $this->json('GET', 'api/v1/hoge');
        $response->assertJson($expect);
    }
    /**
     * @return array
     */
    public function listHogeataProvider()
    {
        return [
            "正常" => [
                [
                    [
                        'content' => '&lt;figure class=&quot;block-image&quot;&gt;'
                            . '<img src="https://example.com/uploads/img1.png" alt="image" width="auto" height="auto">'
                            . '&lt;/figure&gt;',
                        'title' => 'title1',
                    ],
                    ...
                ],
                [
                    'title' => 'title',
                    'hoges' => [
                        [
                            'title' => 'title1',
                            'image' => 'https://example.com/uploads/img1.png',
                        ],
                        ...
                    ],
                    ...
                ]
            ],
            

        ];
    }
    /**
     * @param $class
     * @return Mockery\MockInterface
     */
    private function mock($class)
    {
        $mock = Mockery::mock($class);
        $this->app->instance($class, $mock);
        return $mock;
    }
}

DataProviderを利用することで綺麗にまとまりました。とても便利ですね。

フロント側(nuxt.js)

記事の表示

プロジェクト内はアトミックデザインを意識している構成でした。

アトミックデザインは、詳しくなかったのでいろいろと調べてみましたが、結局良くわかっていません。

変更前

もともと記事を表示するためのカードがあったので、これを再利用したいと考えました。

CardList.vue

<template>
  <div>
    <ul>
      <li v-for="post in posts" :key="post.id">
        <card v-bind="{ post }" />
      </li>
    </ul>
  </div>
</template>

<script>
import Card from "~/components/molecules/Card";

export default {
  name: "CardList",
  components: { Card },
  props: {
    posts: {
      type: Array,
      default: () => []
    }
  }
};
</script>

Card.vue

<template>
  <a :href="`posts/${post.id}`">
    <div v-lazy:background-image="post.image" />
    <div>
      <text-title :value="post.title" />
      <div>
        <text-date :value="publishDate" />
      </div>
    </div>
  </a>
</template>

<script>
import moment from "moment";
import TextTitle from "~/components/atoms/TextTitle.vue";
import TextDate from "~/components/atoms/TextDate";
import { Post } from "~/utils/entities";
import timeFormat from "~/config/timeFormat";

export default {
  name: "Card",
  components: {
    TextTitle,
    TextDate
  },
  props: {
    post: {
      type: Object,
      default: () => new Post({}),
      validator: obj => Post.keys === obj.keys
    }
  },
  computed: {
    publishDate() {
      return moment(this.post.publish_date, "YYYY/MM/DD").format(timeFormat);
    }
  }
};
</script>

変更後

変更前は、Postという記事のオブジェクトの構造に依存していました。そのため、CardList側でStringやNumberに分解してCardに渡してあげることで、Cardを再利用可能なものにしました。

CardList.vue

<template>
  <div>
    <ul>
      <li
        v-for="content in parsedContents"
        :key="content.id"
      >
        <card
          :link="content.link ? content.link : 'posts/' + content.id"
          :image="content.image"
          :title="content.title"
          :publish-date="content.publishDate"
        />
      </li>
    </ul>
  </div>
</template>

<script>
import Card from "~/components/molecules/Card";
export default {
  name: "CardList",
  components: { Card },
  props: {
    contents: {
      type: Array,
      default: () => []
    }
  }
};
</script>

Card.vue

<template>
  <nuxt-link-and-atag-wrapper :to="link">
    <div v-lazy:background-image="image" />
    <div>
      <text-title :value="title" />
      <div>
        <text-date :value="publishDate" />
      </div>
    </div>
  </nuxt-link-and-atag-wrapper>
</template>

<script>
import TextTitle from "~/components/atoms/TextTitle";
import TextDate from "~/components/atoms/TextDate";
import Tag from "~/components/atoms/Tag";
import NuxtLinkAndAtagWrapper from "~/components/molecules/NuxtLinkAndAtagWrapper";
export default {
  name: "Card",
  components: {
    NuxtLinkAndAtagWrapper,
    TextTitle,
    TextDate,
    Tag
  },
  props: {
    link: {
      type: String,
      default: ""
    },
    image: {
      type: String,
      default: ""
    },
    title: {
      type: String,
      default: ""
    },
    publishDate: {
      type: String,
      default: ""
    },
    categoryId: {
      type: Number,
      default: null
    }
  }
};
</script>
クリックすると内部または外部リンクに飛んでくれるコンポーネントを作成

渡されたリンクが、内部リンク("/hoge"など)か、外部リンク("https://example.com"など)を判断して、aタグかnuxt-linkでラップしてくれるコンポーネントを作りました。

関数型コンポーネントを利用しています。

NuxtLinkAndAtagWrapper.vue

<script>
export default {
  functional: true,
  render: function(h, { props, children, data }) {
    function isInternalLink(path) {
      return !/^https?:\/\//.test(path);
    }
    if (isInternalLink(props.to)) {
      return h("nuxt-link", data, children);
    } else {
      delete data.attrs.to;
      data.attrs.href = props.to;
      return h("a", data, children);
    }
  }
};
</script>

テスト

このプロジェクトのnuxt.js内ではほとんどテストが書かれていませんでした。

フロントのテストを書くのも初めてだったので、いろいろ調べながら書きました。

テスト対象 TextDate.vue

<template>
  <p>{{ value | readableDate }}</p>
</template>

<script>
import moment from "moment";
import timeFormat from "~/config/timeFormat";
export default {
  name: "TextDate",
  filters: {
    readableDate(str) {
      if (str == "") return "";
      return moment(str, "YYYY/MM/DD").format(timeFormat);
    }
  },
  props: {
    value: {
      type: String,
      default: ""
    }
  }
};
</script>

テスト TextDate.spec.js

import { shallowMount } from "@vue/test-utils";
import TextDate from "@/components/atoms/TextDate.vue";

describe("TextDate.vue", () => {
  let textDate;

  test("Setup correctly", () => {
    textDate = shallowMount(TextDate, {
      propsData: {
        value: "2019-01-01 00:00:00"
      }
    });
    expect(true).toBe(true);
  });

  test("props", () => {
    textDate = shallowMount(TextDate, {
      propsData: {
        value: "2019-01-01 00:00:00"
      }
    });
    expect(textDate.props().value).toBe("2019-01-01 00:00:00");
  });

  test("日時が人間に読みやすいように変換されて表示される", () => {
    textDate = shallowMount(TextDate, {
      propsData: {
        value: "2019-01-01 00:00:00"
      }
    });
    expect(textDate.text()).toBe("2019/01/01");
  });

  test("空文字を渡すと、何も表示されない", () => {
    textDate = shallowMount(TextDate, {
      propsData: {
        value: ""
      }
    });
    expect(textDate.text()).toBe("");
  });
});

フロントエンドのテスト項目は、どのようなものが良いのか、正直イマイチわかっていません....。

感想

まず、プロジェクトにアサインされてから、実際に中身を読むと分からないことが多くて大変でした。

理解するためには、テストやアトミックデザイン、Vuex、DI、Trait、サービスコンテナなど、使ったことのない技術や概念について勉強する必要がありました。

また、他の人が書いたコードを読むと勉強になることがとても多く楽しいです。

これは個人でコードを書いていてもあまり得られない経験です。

いろいろ調べて悩みながらやっていたら、実装に2週間半くらいかかってしまいました・・・。もうちょっと早く実装できるようになりたいです。

まだまだ分からないことがたくさんあるので、勉強していきたいです。

これからも頑張っていきます〜💪

www.wantedly.com

春の開発合宿に行ってきました!

こんにちは!今回担当させていただきます、4/1にインターンとして入社しましたながいです!

はやくも2ヶ月が経とうとしてしまっているわけでですが今回春の開発合宿に行かせていただきましたことを記します!

今回の日程は5/19~5/20の1泊2日もしくは0泊2日の合宿です。

この開発合宿はそれぞれが好きなテーマで開発をしていきます。

新しい技術の吸収、既存サービスの効率化などみんなテーマは多種多様!

そして最後にみんなの前で成果発表するというUUUM攻殻機動隊恒例行事です!

初参加の僕はとっても楽しみ!!

今回のお宿は湯河原にあります「おんやど恵」!!!

事前に調べた感じとっても良さそう!

温泉も楽しみ〜〜

www.onyadomegumi.co.jp

1日目

10:00 

みんなで六本木に集合してお車で湯河原へ向けて出発!

f:id:johnmanjiro3rd:20190522155556j:plain
海老名サービスエリアでx-tapioka

13:00 

旅館到着です!

早速会議室に集合して各自今回の開発合宿の目標を発表!!

ちなみに僕はweb-socketを使ったチャットを開発します。

14:00  

開発着手!!!!!

f:id:johnmanjiro3rd:20190522160429j:plain 自分のお部屋で開発する人、会議室で開発する人みんなそれぞれ黙々と開発してます!

21時間後の11:00に発表ということだけ決まっており、あとは各自で己の限界と向き合うのみ!!

次第に皆静寂へとかわりそこにただ聞こえるのは川のせせらぎと爆音BGM。

集中している緊張感がひしひしと伝わってきました。

18:00  

旅館といえば夕飯!夕飯といえば旅館!みんな大好き夕飯の時間です!!

f:id:johnmanjiro3rd:20190522160834j:plain
なんとも豪華!!普段一人暮らしの学生には食べられない、夢の御膳、、、、

19:00

夕飯で腹を満たしたらまた各自すぐに開発へ!!

ここから朝食の8:00まではどんな時間を過ごしてもOK

温泉へ行くもよし、ひたすら部屋にこもるもよし、みんな集中して取り組んでいまいした。

みなさん集中力おそるべし、、、

眠くなったら足湯につかりながら開発なんてこともできちゃいます!!

しかも効果抜群!捗る捗る!

ashiyu.hasEmpty? //true
ashiyu.filter(seat => seat.empty?).length // 3

足湯大人気!!

2日目

8:00

朝を迎えました。一睡もせずに頑張った人もちらほら、、、

自分は3時間ほど寝てしまいました✨

f:id:johnmanjiro3rd:20190522164032j:plain

みなさん開発合宿の朝っぽい〜

食べ終わったら発表時間までラストスパート!!

11:00

ついに発表!!! f:id:johnmanjiro3rd:20190523202445j:plain

みなさん本当にすごくて圧倒するばかりでした、、、

あの短時間でなぜこんなすごいものが?というものばかりでした。

みなさんのレベルの高さがすごく伺えました。

ちなみに自分の開発目標であるチャットは一応できました。ダメなポイントを挙げればキリはないですが、、、

まとめ

まず2日間に渡り開発に集中できる素晴らしい環境を整えてくださった施設の皆様ありがとうございました。

合宿を通してみなさんとの距離も近くなったなと思います!

技術が大好きな人たちがたくさんいて素晴らしい環境だなと実感しました!

貴重な時間を過ごせて非常によかったです。

これからも日々精進していきたいなと改めて思うばかりです。

f:id:johnmanjiro3rd:20190522185118j:plain
最後にみんなで集合写真

明日からまたがんばろう!

www.wantedly.com

Kaggleで銀メダルを獲得しました!

こんにちは、UUUMで主にデータ分析業務をしている成松です!

先日(3ヶ月ぐらい前)ソロで参加したElo Merchant Category Recommendationで銀メダルを獲得したので、今回はこちらの振り返りをしたいと思います!


コンペ概要

Eloというブラジルのカード会社が主催したもので、クレジットカードの購買データを使ってRoyalty Scoreを予測するという回帰問題でした。データとしては、各カードの属性が記載された学習用データと検証用データ、そしてカードそれぞれの購買履歴データが記載されたトランザクションデータが主に提供されました。

予測対象であるRoyalty Scoreは、0を中心とした正規分布のような分布と-33付近に外れ値が存在するという分布になっていました。

f:id:n_narimatsu:20190516140107p:plain

評価指標には比較的外れ値の影響を受けやすいRMSEが設定されていたため、外れ値をどう処理するかが非常に重要となりました。

問題設定が分かりやすく評価指標もシンプルで、データサイズもさほど大きくなかったので、初心者でも気軽に参加できたコンペだったのではないかと思います。


スコアの変遷

まず、コンペ終了までに私のスコアがどう変化したのかをご紹介します!

f:id:n_narimatsu:20190517122619p:plain

コンペの序盤 ~ 中盤にかけて運よくスコアを順調に下げることに成功したのですが、後半はほとんど何もできませんでした。


初サブミット!

コンペに参加してすぐにベースとなるモデルを作り始めました。テーブルコンペだったので、深く考えずLightGBMを使いました。また、特徴量はカード属性を表すカテゴリカルデータの他に、購買履歴のデータから購買頻度や金額などを集計し特徴量としてくわえました。

そうして作ったファーストモデルをKaggleに提出したところ、スコアは3.873でした。 この時点で、ほとんどの参加者は3.75 ~ 3.80にいて0.001の精度を競っていました。ですので、この3.873というスコアはめちゃくちゃ低かったです。


Kernelを写経

ファーストモデルを提出した後、このモデルを改良して順位をあげると躍起になっていました。ちょっと特徴量を加えたり、パラメータを調整すれば簡単にスコアが上がるだろうと考えていたのですが、ほとんど改善されませんでした。

そこで、自分のモデルを改良することを諦め、こちらのKernelを写経しました。 自分よりスコアが優れているモデルをそのまま写経したので、当たり前ですがスコアが上がりました!

この時点で順位は上位20%ぐらいだったと思います。


特徴量を大量生産

その後、モデルそのものの改良は一旦ストップし、特徴量を大量生産するフェーズに移行しました。始めのうちはローカルPCでゴリゴリ作っていたのですが、メモリが足りなくなる事や計算処理に時間がかかりすぎる事があり、途中からこちらの動画を参考にBigQueryを使ってました。

最終的には300個ぐらいの特徴量を作ったと思います。その中でも「どのカード(ユーザ)がどのお店で何回購入したか」のマトリックスをNMFで次元圧縮した特徴量はめちゃくちゃ効果的でした!

import pandas as pd
import numpy as np
from scipy.sparse import csr_matrix
from pandas.api.types import CategoricalDtype
from sklearn.decomposition import NMF


N_COMP = 20


# データ読み込み
feats = ["card_id", "merchant_id"]
h_trs = pd.read_csv("../input/historical_transactions.csv", usecols=feats)
h_trs = h_trs.dropna()


# sparse matrixの生成
card_merchant = h_trs.groupby(feats).size().reset_index()
card_merchant.columns = ["card_id", "merchant_id", "count_"]

card_c = CategoricalDtype(sorted(card_merchant.card_id.unique()), ordered=True)
merchant_c = CategoricalDtype(sorted(card_merchant.merchant_id.unique()), ordered=True)

row = card_merchant.card_id.astype(card_c).cat.codes
col = card_merchant.merchant_id.astype(merchant_c).cat.codes
sparse_matrix = csr_matrix((card_merchant["count_"], (row, col)), \
                           shape=(card_c.categories.size, merchant_c.categories.size))


# NMFによる次元削減
model = NMF(n_components=N_COMP, init='random', random_state=0) 
embedded = model.fit_transform(sparse_matrix)

df = pd.DataFrame(embedded, columns=["NMF_comp{}".format(i) for i in range(1, N_COMP+1)])
df["card_id"] = person_c.categories.values


この特徴量と他の300もの特徴量の組み合わせにより、順位が一気に5位まで上がりました!

f:id:n_narimatsu:20190516181823p:plain


アンサンブル

12月中旬あたりぐらいになると、新たな特徴量を作るためのネタが切れてしまいました。
新たな特徴量を作るぐらいしかスコア改善の方法が分からなかったので、1月中旬ぐらいまでかなり伸び悩みました。

悩んだ結果、モデルを複数作ってそれらをアンサンブルしようと決めました。 Kaggleでは、複数のモデルをアンサンブルすることで精度を上げるという手法がよく使われます。各モデルがもつ特有の弱点を他のモデルで補い合うことで精度が向上しやすいのではと思われます。

私の場合、複数個のモデルを作るにあたり下記のようなことを考えました。

  • 外れ値を予測する問題として扱ったらどうか?
  • 外れ値以外は、外れ値なしで学習したモデルの方が精度が良いのではないか?

最終的には2つの学習モデルを新たに作り、合計3つのモデルをアンサンブルしました。

f:id:n_narimatsu:20190517123746p:plain


コンペ終了

3つの学習器によるアンサンブルモデルを作った後も試行錯誤をしたり、raddarさんの神Kernelを参考にしたものの、これ以上スコアが上がることなく3ヶ月間のコンペが終了しました。

Private LBでは多少のShake Downをくらったものの、4129チーム中196位に踏みとどまり銀メダルを獲得しました!!!

f:id:n_narimatsu:20190517120336p:plain


まとめ

Eloコンペは、本気で最後までやりきった初めてのコンペだったので、無事に銀メダルを獲得できてよかったです!日々投稿され続けるKernelやDiscussionをキャッチアップし続けるのは骨が折れましたが、その分多くの学びを得ることができたと思います。今後はテーブルコンペに限らずいろんなコンペに参加して、2019年内にKaggle Masterになることを目標に頑張っていきたいと思います!

www.wantedly.com

webサーバーとアプリケーションサーバーの関係を調べてみた

UUUMでエンジニアインターンをしている鷲見です。弊社では社内システムの一部をRailsを使って開発しています。なので今回はRailsで開発する上で必要な知識を一部書いてきます。

HTTP

私たちがwebページブラウザがwebサーバーに対してコンテンツを要求し、webサーバーは要求されたコンテンツをブラウザに対して返します。HTTPはこの一連のやりとりにおける通信のルール(プロトコル)です。この通信のルールが世界標準で決められているため、私たちはどの種類のブラウザであっても、世界中のどんなwebサーバーにもアクセスできるのです。

URI

URIとはwebに存在する情報を名前をつけて識別するためのルールのことです。URIを使うことでwebに存在する全ての情報は一意のもので表現することができるようになるます。HTTPはURIを使ってwebサーバーからほしいコンテンツを指定することができます。URIとURLの違いとしてはURIが情報の識別するルールに対し、URLがURIにのっとり情報の場所を示したものになります。

webサーバーとアプリケーションサーバー

webサーバー

webサーバーはブラウザからのコンテンツのリクエストを受け取り、ブラウザにレスポンスを返す役割のが役割です。このときのリクエストがHTML、CSS、画像ファイルのような更新しない限り同じ表示コンテンツを表示する静的なwebコンテンツだった場合、webサーバーが処理してレスポンスを返します。そしてクライアントごとで表示内容を変化させる処理が必要な動的なwebコンテンツの場合、webサーバーはアプリケーションサーバーへとリクエストを送ります。アプリケーションサーバーから返ってきた結果をレスポンスとして返します。webサーバーとして NginxとApacheなんかが有名です。

アプリケーションサーバー

アプリケーションサーバーは私たちが作ったRailsアプリケーションを動かしてくれるものです。webサーバーから送られてきたリクエストをアプリケーションサーバーからRailsアプリケーションに伝え、Railsアプリが処理した結果をwebサーバーに返します。ローカル環境下での開発の場合はPumaのようなRails用のアプリケーションサーバーのみを立てますが、本番環境ではwebサーバーをRailsアプリケーションの手前に置くことで、静的なコンテンツの処理を負担させることが多いです.RailsのアプリケーションサーバーとしてRainbows、Pumaなんかがあります。

Rack

RackはRuby製のフレームワークとアプリケーションサーバーの間に入り、互いをつなぐ役割をしてくれます。送られてきたHTTPのリクエストをサーバーはRackを使用しアプリにも理解できる形に変換して伝えてくれる。逆にアプリからのレスポンスはRackを通じてHTTPに変換されてサーバーに返ります。Rackを使用することでサーバーとフレームワークの組み合わせが自由になります。

全体の動き

ここまでのことをまとめるとブラウザから送られてきたリクエストをwebサーバーが受け取り、静的なwebコンテンツだった場合レスポンスをブラウザに返します。しかし、リクエストが動的なwebコンテンツの場合webサーバーでは処理せず、アプリケーションサーバーに送り、アプリケーションサーバーがRailsアプリにRackを通して伝えます。結果を先ほどとは逆の順番でブラウザに返します。

具体的に言うとブラウザからNginx、NginxからPuma、PumaからRackを通してRailsアプリに伝わります。レスポンスはこの逆の順序となります。

おわりに

プログラムは書いていけば理解が膨らみますが、知識関連はブログみたいに書かないと中々定着してくれないなーと常々思いましたー。

www.wantedly.com

ターミナルをインスタ映えするPokemonに変えよう!

こんにちは。2月入社の井上です。

今回のブログでは技術的なことではなく日常的な話題をしようと思うのですが、みなさんターミナルは何を使ってますか?

macデフォルトのターミナル、tmux、iTermなどなど、たくさんのターミナルがありますね。

日常用の僕のオススメはhyperです。

hyper.is

オススメの理由ですが、みなさんはターミナルに何を求めますか?

僕は✨✨インスタ映え✨✨です。

なんとこのhyper, ポケモンのpluginがあるんですよ!

f:id:iammyeye1:20190425123401p:plain

どうです??

インスタ映えじゃないですか??

hyperを導入してインスタ映え系エンジニアになっちゃいましょう!

hyperをインストールする

brew caskまたは公式サイトHyper™から入れちゃいましょう!

brew update
brew cask install hyper

hyper.jsを編集する

hyperをアプリケーションとして開くと、homeに.hyper.jsが生成されます。

そいつを編集しましょう。

vi .hyper.js

ファイルの下の方にプラグインの設定部分があるので、追加と書かれている部分を追記します。

    // for advanced config flags please refer to https://hyper.is/#cfg
    pokemon: 'random',   #追加
    pokecursor: 'false',   #追加
    unibody: 'false',  #追加
    poketab: 'true',   #追加
  },

  // a list of plugins to fetch and install from npm
  // format: [@org/]project[#version]
  // examples:
  //   `hyperpower`
  //   `@company/project`
  //   `project#1.0.1`
  plugins: ['hyper-pokemon'],  #追加

  // in development, you can create a directory under
  // `~/.hyper_plugins/local/` and include it here
  // to load it and avoid it being `npm install`ed
  localPlugins: [],

  keymaps: {
    // Example
    // 'window:devtools': 'cmd+alt+o',
  },
};

自分の.hyper.jsの設定はこれです!! gist:2e6c6953ff03c0f8c22c0392f2f76ef4 · GitHub

追加部分の簡単な説明

    pokemon: 'random' #壁紙のポケモンの種類を指定
    pokecursor: 'false',   #trueだとカーソルをポケモンにしてくれるらしい(僕の端末では動かなかったです)
    unibody: 'false',  #headerの部分の色を壁紙と揃えるか
    poketab: 'true',   #タブの部分でgifのポケモンアイコンを表示するか

使ってみる

そうすると、ポケモンのテーマがこんな感じで適用されるはずです!

f:id:iammyeye1:20190425125548p:plain

先ほどのセットアップでは、hyperの起動のたびにポケモンが変わる仕様になっています。

またcommand + Shift + u で、好きなタイミングでポケモンを変更することができます。

最高!!

今は関東地方のポケモンのみが壁紙になっているようですが、次に壁紙になる地方は投票で決めるっぽいです!!

## Vote the next Region

Vote for the Pokémon Region you want to see themes from next.<br/>
In essence, this poll will determine the creation order of all Pokémon Regions.<br/>
The poll will be kept alive until the project's completion, when all **600+** themes will be available.

投票状況↓

https://m131jyck4m.execute-api.us-west-2.amazonaws.com/prod/poll/01BMH8W2ETBFXQ9H6PSS0X9VZ8/Johto https://m131jyck4m.execute-api.us-west-2.amazonaws.com/prod/poll/01BMH8W2ETBFXQ9H6PSS0X9VZ8/Hoenn https://m131jyck4m.execute-api.us-west-2.amazonaws.com/prod/poll/01BMH8W2ETBFXQ9H6PSS0X9VZ8/Sinnoh https://m131jyck4m.execute-api.us-west-2.amazonaws.com/prod/poll/01BMH8W2ETBFXQ9H6PSS0X9VZ8/Unova https://m131jyck4m.execute-api.us-west-2.amazonaws.com/prod/poll/01BMH8W2ETBFXQ9H6PSS0X9VZ8/Kalos

以下に投票用リンクを貼っときます!!

ジョウト地方

ホウエン地方

シンオウ地方

イッシュ地方

カロス地方

終わりに

hyperは普通のターミナルですので、日常でターミナル使いたいな〜って思った時にポケモンのプラグイン入れてサクッと使ってみてください!!

ターミナルはhyper、君に決めた!




参考記事

Hyper Store - hyper-pokemon

爆速でターミナルをポケモンにする - Qiita


www.wantedly.com

RubyKaigi2019 に参加してきました

こんにちは、趣味エンジニアのめる(@c5meru)です!
UUUMに入社して1年と2ヶ月が経とうとしていますが、自分が入社した時、現場のエンジニアは5人くらいしか居なかったのに、いま数えてみたら現在22人もいるようで、そのスケールの違いに圧倒される日々を送っています。

さて、自分は先日、会社のカンファレンス参加制度を利用して RubyKaigi2019 に参加してきました。
今回が初めての RubyKaigi なので書きたいことがたくさんあるのですが、こちらのUUUMエンジニアブログでは、1日目に行われた、まつもとゆきひろさんのKeynoteでお話があった内容について書かせていただきたいと思います!

The Year of Concurrency by Matz

f:id:c5meg1012:20190420115628j:plain

  • Rubyは「だいたいのことにはいい」
  • チームやプロダクトが大きくなるとデメリットが見えてくる
  • 他の言語と比較して遅いのは、マルチコアを活用できていないから
  • Github cookpad Airbnb などもRubyを使っている
    • これらよりパフォーマンスを求められるサービスはあまりない
    • だから「十分に速い」とも言える
  • パフォーマンスが良くないっていう人もいるけど、特筆する例はTwitterの一件だけ
    • しかも古いバージョンであった1.8を使い続けていたケース
  • Rubyは今までも性能の改善を続けてきたし、Ruby3でも改善を進めている
    • Ruby3のポイント
      • 静的解析
      • パフォーマンス改善
      • Concurrency

静的解析

  • プロジェクトが大きくなってくるとテストのサイズや実行時間が長くなって苦痛になってくる
    • Matzはテスト嫌い、できれば書きたくない
    • DRYじゃないし
    • でも人類は間違いのないプログラムをまだ書けないから仕方がない
  • JSのTypescriptや、他の言語でもtype hintingがある
  • Matzは型宣言が嫌い、できれば書きたくない
    • DRYじゃないし
    • コンピュータに仕事させられてる感じがする
    • だからここは妥協したくない
    • なくてもプログラムを書くことはできるから
    • けどできるようにするために進めている
      • 型定義シンタックス .rbi
      • 静的型チェッカー
        • Stripe さんの Sorbet と soutaro さんの steep がある
        • rbiに対応できるように進めている
      • 型宣言を書かなくても型チェックができるように進めている
        • Gems で型定義ファイルを配布すれば、既存の型情報はカバーできるから

パフォーマンス改善

  • alibabaでもRubyは使われているらしい
  • メモリがボトルネック
    • なのでGC周りの改善、キャッシュの改善をすすめている
  • CPUもボトルネック
    • 昨年はMJITを導入、メリットもあったがデメリットも多くあった
    • Ruby2.6ではベンチマークで使っているファミコンエミュレータは2.8倍早くなる
      • RailsなどのWebアプリケーションは、たくさんメソッドがあってコンパイルする量が多くなるので遅くなる
      • PHP8のパフォーマンス改善も同様だそう

Concurrency / 並行性

  • IOボトルネック
  • 現状の Thread/Fiber にも課題を感じている
    • オブジェクトを今からイミュータブルにはできない、過去の経緯があり破壊的変更になってしまう
    • Guild(仮称)をつかう
      • ゲーム業界から反対が出た(このClass使ってるんだけど)
    • frozen objectのようなイミュータブルなものだけ送れるように
    • GuildはJSでいうwebworkersみたいなもの
    • Guildは慣れるまでは使いづらい、IOボトルネックを解消するにも使いづらさがある
    • シングルスレッドのNode.jsはどうしているのか
      • ノンブロッキングIO
      • V8のパフォーマンス
    • AutoFibers(仮称)でIOブロッキングを避けられるようにすすめている
      • Goはgoroutine
      • Elixirはprocess
    • Rubyはそういうのを当初考えていなかった、破壊的変更を避けるために、気をつけながら改善している

メンテナンス方針

  • 互換性を維持しながら改善するのは難しい
    • Ruby3、その先も、いろいろ改善してRubyをGreatにしたい
    • Frozen String Literalsは3.0がデフォルトにはまだできなかった
    • emacsからもってきた '?' literalsも、3.0では消せなかった
    • バッククオートも、3.0では消せなかった
    • キーワード引数は、3.0で変わる
      • もともと直感的でなくわかりづらいと思ってた
      • 静的型付けと相性が悪そう
      • 大きな非互換になる見込み
        • 2.7から警告を出すそう
    • numbered block parameter
      • すでに trunk では導入されているが、議論が続いている
      • まだやらない
    • パターンマッチング
      • 昨日 trunk にマージされた
  • Rubyは生き残らなければならない
    • わたしたちはRubyが好きだし
    • Rubyのプロダクトがすでにたくさんあるし
    • わたしたちはRubyでご飯食べてるし
      • でもみなさんは、JSでもPythonでも使ってもらっていいんですよ
      • わたしたちコミッターは、Rubyがなくなったら本当に困る
    • よりよい、よりたのしい、より生産的な言語に
      • Rubyを使うことがメリットになるように
    • たまに後悔するような実装もしてしまう
      • キーワード引数、Threadなど
      • ユーザーが多いから直しづらい
      • もう間違えないようにしなくては、と慎重にやっている
      • みなさんの意見をないがしろにしたくない
      • 決まってからでは変えられないので、決まる前にどんどん意見を言ってほしい
    • 最近はもどかしいけれど、Matz自身は設計だけ考えて、実装は他のコミッターにやってもらってる
      • Cが書ける人がいたらぜひ参加してほしい

その他

Keynoteの次のセッション「Ruby 3 Progress Report」では、上記よりもさらに踏みこんだ、Ruby3の開発進捗がお話しされました。
Matzさんとは対照的に、自分はテストも静的解析も好きなので(笑)、特に静的解析の仕組みは非常に興味深く、Ruby3が今から待ち遠しくなりました!
上記にもあったキーワード引数の破壊的変更では、この変更だけのために何度も何度も開発者会議が開かれたという話を聞いて、日頃わたしたちが使っているRuby(などのプログラミング言語)は、たくさんの方が時間をかけて作ってくださったものなんだな、ということを実感しました。ありがたや...!
また、RubyGemsのアカウント乗っ取りが多発しているという注意喚起もあり、RubyGemsアカウントを持っている方は、必ず2段階認証を設定してほしいということでした!

まとめ

セッション以外のことについては、個人ブログの方に書かせていただきましたが、全体的にお祭り感もあって非常に楽しいカンファレンスでした!
RubyKaigiをもっともっと楽しむには、Rubyのしくみを読んだり、(関連するセッションが多い)mrubyを押さえておいたり、英語力をがんばって高めておくと良いだろうな、と思ったので、今後参加される方は参考にしていただければと思います!


www.wantedly.com