mecabのインストール

結構詰まりやすいので書きます

mecab導入(ubuntu想定)

前提知識:必要なアセットが異なる

コマンドライン上で使う: mecab

スクリプト上で使う: mecab-python3

mecabの導入

1. MeCabのインストール

$ apt install mecab libmecab-dev mecab-ipadic-utf8

2. mecab-python3のインストール

$ pip install mecab-python3

3. NEologdのインストール

$ git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
$ cd mecab-ipadic-neologd
$ ./bin/install-mecab-ipadic-neologd -n

4. この時点では、わざわざ新語辞書のパス指定をしなければならない状態…

5. neologd(新語辞書)が入っているパスを表示

$ echo `mecab-config --dicdir`"/mecab-ipadic-neologd"

6. Mecabが参照する辞書のパスを新語辞書に変更

/etc/mecabrc 中のdicdirを変更して、新語辞書をデフォルトにする
    1. $ vim /etc/mecabrc
    2. dicdir = (5.で表示されたパス)に変更
    3. 保存

7. コマンドライン上でmecabと打ち、西野カナがちゃんと西野カナで認識されるか確認

8. スクリプト上でMeCabをimportし、MeCab.Tagger()で使用確認

使用できない場合、/usr/local/etc/mecabrcがないと言われるので、それを作り、中身は/etc/mecabrcと同じにする → するとおそらくできる

注)おそらくpermissionダメって言われるので、chmod 666とかでpermission変更したのち作成

辞書で単純な条件分岐をシンプルに

if文で条件分岐したのちに何か処理をする...といったことを書くとき、冗長になる時があります。

それをスマートに書く方法が辞書を使う方法です。

LeetCode - 150. Evaluate Reverse Polish Notationを題材にします。

逆ポーランド記法を評価するのに、スタックを用いてやります。

例)

入力として、["4", "13", "5", "/", "+"] が与えられたら、(4 + (13 / 5)) を計算して、6を返します。

この時に冗長な書き方としては、

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for token in tokens:
            if token == "+":
                num1, num2 = stack.pop(), stack.pop()
                stack.append(num2 + num1)
            elif token == "-":
                num1, num2 = stack.pop(), stack.pop()
                stack.append(num2 - num1)
            elif token == "*":
                num1, num2 = stack.pop(), stack.pop()
                stack.append(num2 * num1)
            elif token == "/":
                num1, num2 = stack.pop(), stack.pop()
                stack.append(int(num2 / num1))
            else:
                stack.append(int(token))
        return stack[-1]

となります。冗長、と表現しましたがもしかしたらこちらの方がわかりやすいという方もいるかもしれません。

今回はコード量を減らしたいという一心です笑

辞書を用いると...

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        operations = {
            "+": lambda a, b: a + b,
            "-": lambda a, b: a - b,
            "*": lambda a, b: a * b,
            "/": lambda a, b: int(a / b)
        }
        for token in tokens:
            if token in "+-*/":
                operation = operations[token]
                num1, num2 = stack.pop(), stack.pop()
                stack.append(operation(num2, num1))
            else:
                stack.append(int(token))
        return stack[-1]

となります。

スマート!

シェル周りを勉強した

最近自分の中でのシェルに対する興味が沸々と湧いております🔥

色々と調べておもしろかったのでまとめます。

そもそもOSとは?

オペレーティングシステム(Operating System; OS)とは

コンピュータ内の様々な複雑な動作を意識せずに、人々が容易にコンピュータを利用できるようにするソフトウェア群のことです。つまりプログラムの集合体です。

MacOSとかWindows, UNIX, LinuxなどがOSです。

例えば私たちがキーボードから入力した文字を読み込むという操作でさえ、コンピュータ内ではハードウェアの仕様にしたがって複雑な手順が踏まれます。しかし私たちはそんなことは意識せずにキーボードから入力を行えます。

OSがなければ、コンピュータをちょっと触ることでさえ専門知識が必要になっていたかもしれません

このOSに以降説明するシェルやカーネルも含まれます。

まずシェルって?

シェルはlsやcdなどのコマンドを解釈して実行する"プログラム"のこと。プログラムだったんですね

シェルの基本動作は以下の通り

  1. プロンプト(%などのコマンドライン上での"入力待ちだよ"と知らせる表示のこと)を表示
  2. ユーザからの入力があれば、入力を解析しコマンド名を切り出す
  3. コマンド名で指定された実行可能ファイルを実行
  4. 実行可能ファイルの実行が終了したら最初(1. )に戻る

※) ここで実行可能ファイルはソースプログラムをコンパイルして出来上がるファイルのことで、機械語命令の並びのことを指します。より詳細に言えば、命令列の他にコード領域の大きさやデータ領域の大きさなど、様々な情報が含まれています。

この書き方だとまるでシェルというプログラムが実行可能ファイルを実行しているように思えますが、実際は違います。

実際はシェル自体がコマンドを解釈して実行しているのではなく、システムコールカーネルに対して行い、カーネルがこれらのコマンドの操作を実行します。

※) システムコール = 特別な関数呼び出し(ユーザが直接弄れない部分の操作を行う関数を呼ぶ)

カーネルとは?

OSの核となる部分で、コンピュータ内の低レイヤー部分に対しての操作を行います。

あらゆる操作はこのカーネルによって行われます。カーネルもプログラムです。

カーネルは低レイヤーでの複雑な処理(メモリ管理、デバイス管理など)を行ってくれます。

全体のイメージ

上図におけるApplicationがユーザが使用するアプリです。例えばコマンドラインです。

ここで、コマンドラインでは上記の説明の通り、ユーザが入力したコマンドを理解し、カーネルに実行してもらうためにシェルというプログラムが動いています。

コマンドに対する気付き

普段、Linux上でlsとかrmとか色々なコマンドを使います。何気なくこれらコマンドを叩いていますが、これってlsとかrmとかの操作が書かれた実行可能ファイルを実行しているんですねー

恥ずかしながらあんまり今まで意識してませんでした笑

Macにはbinディレクトリというものがあってそこにコマンドの実行可能ファイルが置かれている

実際に見てみると

$ ls /bin
[    cp    dd   expr   launchctl   mkdir   pwd   sleep   test
bash   csh   df    hostname   link   mv   rm   stty   unlink
cat   dash   echo   kill   ln   pax   rmdir   sync   wait4path
chmod   date   ed   ksh   ls   ps   sh   tcsh   zsh

こんな感じでlsとかrmとか様々なコマンドの実行可能ファイルが置かれていることが確認できますね。

ファイルの種類を確認してみると

$ file ls
ls: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64e:Mach-O 64-bit executable arm64e]
ls (for architecture x86_64):   Mach-O 64-bit executable x86_64
ls (for architecture arm64e):   Mach-O 64-bit executable arm64e

バイナリファイルのようです。実行可能ファイルはバイナリファイルの一種でコンピュータが理解できるような形のファイルです。2進数がズラーって感じのファイル。

このfileっていうコマンド自体も/bin以外のどっかにあるfileという実行可能ファイルを実行しています。

これまでコマンドラインにコマンドを入力して、不自由なくやりたいことができているのですが、この時に「なんでコマンドの名前を打っただけで、その実行可能ファイルの場所を認識して実行できているんだ?」という疑問が湧いてきます。

これ、実際には絶対パスで指定コマンドの実行可能ファイルをしっかり確認しているんですね

環境変数 PATH

シェルは"PATH"という環境変数に入っているパス内に、コマンドとして打った実行可能ファイルがあるかを確認し、あれば実行、なければcommand not foundというエラーを吐きます。

PATHを確認してみるとmacのデフォルトでは

$ echo $PATH
/user/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

というように表示されると思います。

: で複数のパスが区切られていて、「/user/local/bin」「/usr/bin」「/bin」「/usr/sbin」「/sbin」という5つパスが確認できます。

コマンドライン上でlsコマンドを打つとシェルはこの5つのパスの中にlsという実行可能ファイルがあるかを順番に探します。シェルがやってたことってこういうことだったんですね

環境変数PATHに新たにパスを付け加えるにはexportコマンドを利用して「export PATH=$PATH:通したいパス」で設定します。

しかしここでの設定はシェルが閉じてしまうと消えてしまいます。したがってシェルが開くたびに設定しなければなりません。これは面倒!!

なのでシェルが起動すると同時に毎度実行されるファイルである、.zshrcファイルにこれを書きましょう。

パスを通す とは?

command not foundエラー → シェルが指定のコマンドを見つけられていない → シェルにコマンドの実行可能ファイルの場所を伝えたい

パスを通す = シェルにコマンドを探してもらう場所を伝える = .zshrcファイル(シェル起動時に同時に実行するファイル)にexportコマンドをかく

です。

Permission Denied

パスも通してこれでコマンドを打ってみると...."""Permisson Denied"""というやつが現れることがあります。うざい!!って感じですね

Permission Deniedとは、あなたに権限がないから、実行可能ファイルが実行できないよという表示です。

なぜこんなものがあるのでしょうか?

そもそもLINUXは、サーバーでよく使われるOSですが、そのため1つのマシンへ複数のユーザーが同時にログインして操作することを前提として作られています。

この際に、秘密のファイルを他のユーザーに見られてしまったり、誤って他のユーザのファイルを上書きしてしまうことを防ぐため、アクセス権限が必要でした。

したがって一つ一つのファイルには、「誰に、どのような操作を許可するのか」という権限を規定する情報が設定されています。この情報のことを、パーミッションと呼びます。

これ以上の説明はここに託します...とてもわかりやすかったので...🙏 qiita.com

ここで言いたかったことは、

コマンド打ったらcommand not foundが出た

↓ コマンドがないか or シェルが実行可能ファイルの場所を認識できていない

whichコマンドで、そのコマンドの場所を調べる

↓(なければインストール)

.zshrcに変更加える(シェルに実行可能ファイルの位置を教えてあげる)

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

Permission Deniedが出た

実行可能ファイルのpermissionを変える

です

何か間違っていることがあったらご指摘いただけるとありがたいです🙇‍♂️

重要コマンド(自分用)

自分のためにメモっておきます。また追記していきます。

GPU関連

  • GPUがどのくらい使われているか確認するコマンド(1秒ごとに表示)
nvidia-smi -l 1

pip関連

  • あるコマンドをアップデートするとき
pip install -U <コマンド>

ssh関連

  • sshのconfigみるコマンド

config = sshでどこに繋がれるのかとかの諸々の設定を書いておくファイル

書いておけば、ssh Hostnameだけで飛べる。そうしないと長いIPアドレスを打つはめになる。

cat .ssh/config

バックグラウンドでコマンド実行

時間がかかるコマンドをssh先のマシンでやりたい時がある。この時、ssh接続が切れてもコマンドを動かし続けたい

そこで出てくるのが tmux コマンドである

※ tmuxでコマンドをバックグラウンド実行させて ctl-b then dでセッションを出た後、exitでそのssh接続を切らないように。exitで切ると何故だがtmuxのセッションが消えてしまう...

なので今のところtmuxでのセッションをデタッチした後に、右上の×ボタンでssh接続を切ると何故だがtmuxのセッションがしっかりと動いてくれている...(アドバイスください..)

  • tmuxインストール
$ apt-get install tmux
  • 名前付きセッション(例: hogehoge)作成
$ tmux new -s hogehoge
  • セッションをデタッチ(抜けるイメージ)
Ctl-b then d
  • 今起動中のセッション表示
$ tmux ls
  • セッション(例: hogehoge)に再びアタッチ
$ tmux attach -t hogehoge

ディスク容量確認

df -h

メモリ関連

  • PCのメモリ使用状況を確認(-h オプションで見やすくする)
free -h
  • PCのメモリ使用状況を確認(1秒ごと)
free -h -s 1

ファイル操作

a.txtの先頭100行を取得してb.txtに送る

head -n 100 a.txt > b.txt

オプションの確認

あるコマンドのオプションを確認したいときは脳死ググるのではなく--help使おう

(あるコマンド) --help

Docker関連

Dockerにおいて、イメージを作って、イメージからインスタンスとしてコンテナを作り、そこで開発を行う。

  • 今あるimageを表示
sudo docker images
  • 作ったDockerファイルでimageを作る

例えば、ローカルで作ったDockerfileを入れておくレポジトリに移動して、そのレポジトリ内で

sudo docker build -t [作るimageの名前] [(Dockerfileが入っているディレクトリのPATH、大抵 "." でok)]
  • 停止中も含め全てのコンテナをリスト表示
sudo docker ps -a
  • 作成済みコンテナを起動
sudo docker start  -i [コンテナIDの先頭1,2桁]
  • コンテナ作成&起動(新しいコンテナ作るときはコレ)
sudo docker run --gpus=all -it --name=[自分が付けたいコンテナ名] [元となるimageの名前] [シェルの指定(bash, zshなど)]

sudo docker run -p (ホストのポート番号):(コンテナのポート) --gpus=all -it --name=[自分が付けたいコンテナ名] [元となるimageの名前] [シェルの指定(bash, zshなど)]

研究室はUbuntuなのでbashに指定しよう、仮想環境アクセスではポートフォワーディング忘れずに。

シェル: 自分たちが普段、コンソールで操作する時にコマンドを受け取ってそれを解釈してカーネルに伝えるプログラム。つまりユーザはシェルというインターフェースを通して、カーネルにコマンドの実行をお願いする。

bin: いろんなコマンド(cp, rm, mv,..)に関するバイナリファイル(実行ファイル)が保存されているディレクトリのこと。バイナリファイルが入っているからbinary file -> bin である。

ファイル拡張子: 単に人間が見分けやすいようにファイル名の後ろにつけているもの。本当はなんの意味もなく、.~がついてないからファイルではないのかというと全くそんなことはない。

一方で、windowsなどはわかりやすさのために拡張子がついてないとエラーが出るようにプログラムされているものもある。

  • コンテナ削除
sudo docker rm [コンテナIDの先頭1,2桁]
  • イメージ削除
sudo docker rmi [イメージIDの先頭1,2桁]
  • コンテナからログアウト(コンテナは停止されない)
ctl-p ctl-q

この後、docker ps -aでコンテナを確認するとSTATUSがUp(コンテナは停止しておらず、ログアウトした状態=dettach)の状態になる。

また開始するにはsudo docker attach [コンテナ名]

  • コンテナから出るとき(コンテナは停止される)
exit

この後、docker ps -aでコンテナを確認するとSTATUSがExited(コンテナ停止)の状態になる。

また開始するにはsudo docker start -i [コンテナIDの先頭1, 2桁]

Dockerでの作業の流れ

  1. Dockerfileを作って、それを元にimageを作る

  2. imageからコンテナ作る

  3. コンテナ内で自分が作業しているgithubをclone

  4. GPU使って実験

  5. ローカルでは主にコードの修正、ミニデータでのコード検証

  6. 動作確認できたらgithubに挙げて、コンテナ内で実験

unix関連

  • n個のコマンドを続けて書く&改行
コマンド1 && \
コマンド2 && \
:
:
コマンドn

&& が続けて、\ が改行の意味を持っている

環境構築関連

  • インストール済みのパッケージ一覧表示
pip freeze
  • requirements.txtの作り方
pip freeze > requirements.txt

間違っていることを書いていたら、ご指摘いただけると非常にありがたいです。。

ミュータブルなオブジェクトには気をつけよう

LeetCode - 78. Subsetsを解いているときに,エラーの原因がなかなかわからないという状況に陥りました.

問題としては,あるリストの全てのサブリストを要素とするリストをreturnせよという問題です.

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans = []
        def dfs(A, limit, index):
            if len(A) == limit:
                ans.append(A)
            else:
                for i in range(index+1, len(nums)):
                    A.append(nums[i])
                    dfs(A, limit, i)
                    A.pop()
            
        for i in range(len(nums)+1):
            dfs([], i, -1)
        return ans

最初,私はこのようなコードを書きました. しかし,[1, 2, 3]を入力としたとき,

input: [1, 2, 3]
output: [[], [], [], [], [], [], [], []]

となり,うまくいきませんでした.

なぜ,ansに[ ]しかappendされないのか.

そこで,

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans = []
        def dfs(A, limit, index):
            if len(A) == limit:
                ans.append(A)
                print(ans)
            else:
                for i in range(index+1, len(nums)):
                    A.append(nums[i])
                    dfs(A, limit, i)
                    A.pop()
            
        for i in range(len(nums)+1):
            dfs([], i, -1)
        return ans

とし,ansの動きを見てみると,

[[]]
[[], [1]]
[[], [2], [2]]
[[], [3], [3], [3]]
[[], [], [], [], [1, 2]]
[[], [], [], [], [1, 3], [1, 3]]
[[], [], [], [], [2, 3], [2, 3], [2, 3]]
[[], [], [], [], [], [], [], [1, 2, 3]]
[[], [], [], [], [], [], [], []]

となっていました. ansの中身のリストAが更新されてしまっている.

ansに毎度idが同じリストAをappendしてしまっており,リストはミュータブルなのでリストAが更新されるたび,ansの中身も更新されてしまっていた.

したがって,

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans = []
        def dfs(A, limit, index):
            if len(A) == limit:
                ans.append(A[:])
            else:
                for i in range(index+1, len(nums)):
                    A.append(nums[i])
                    dfs(A, limit, i)
                    A.pop()
            
        for i in range(len(nums)+1):
            dfs([], i, -1)
        return ans

のようにansにリストAのコピーをappendすればうまくいく.コピーすればidが異なるものとなるからです.

ミュータブルなものを配列に格納するときには,今後注意していこうと思いました.

GitHub関連 エラー対処法メモ

いつもググってしまっている気がするので、メモ書きのために残しておきます。

1. Githubレポジトリ作成

2. アップしたいローカルディレクトリに移動

3. .gitディレクトリ作成

$ git init

これで、カレントディレクトリの下に.gitというディレクトリが作成されて、Gitで使用するファイルが保存されます。

4. カレントディレクトリをaddしてコミット

$ git add .
$ git commit  -m "first commit"

5. Githubで作成したレポジトリをリモートリポジトリとして登録

git remote add origin [GithubのレポジトリURL]

6. ローカルのディレクトリ、ファイルをリモートへプッシュ

git push -u origin master

<追記 2020/09/15>

pushしようとしたら、こんなエラーが出た。

 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'https://github.com/ryuryukke/Coursera_Deep_Learning'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

リモートリポジトリで変更した内容が、ローカルに反映されていないからpushできないみたい。  おそらく、github上でREADME.mdを変更してそのままだったからやな、

・してあげること

リモートリポジトリで変更した内容を、ローカルに反映させる。

・手順

  1. リモートの「master」ブランチの最新情報を、ローカルの「origin/master」ブランチが受け取る。
git fetch
  1. ローカルの「origin/master」ブランチから、ローカルの「master」ブランチへ最新情報を持ってくる。
git merge

これで、ローカルのファイル(この場合はREADME.md)が最新の状態に更新された。

そしてpushすると、うまくいった!

このリモートとローカルのブランチの関係を表したこの図がわかりやすかったです。

f:id:spond:20200915103233j:plain

この図はqiita記事からの引用です。感謝です。

<追記 2020/12/15>

一個前のcommitを取り消したい時

まず、これまでのコミットの履歴をみる

git log

そして

git revert [コミットID]

でok.

git reset は恐いです。

<追記 2021/08/25>

ネストしたレポジトリをGitHubに作りたい

A

┣ B

┗ C

というディレクトリ構成でAをgithubにpushするとBとCがsubmodule化され、開けなくなる。

対処法

git submodule を使う

手順

1. Aに対するリモートレポジトリを作成(https://github.com/A.gitとする)

そして、git init, git remote add origin https://github.com/A.gitを行う

2. BとCをリモートレポジトリに対し、サブモジュールとして追加

BとCそれぞれに対し、

git submodule add https://github.com/A.git B
git submodule add https://github.com/A.git C

を実行。

3. commit & push

git commit -m 'add submodule: B'
git commit -m 'add submodule: C'
git push origin master

これでいける

LeetCode - 200. Number of Islands

LeetCodeを解いていたらかっこいい書き方があったので共有します

問題はLeetCode-200. Number of Islandsです

概要としては、gridの0(海),1(土地)で表された島の数はいくつですかという問題です

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        if not grid:
            return 0
        h, w = len(grid), len(grid[0])
        ans = 0
        def dfs(i, j):
            if 0 <= i < h and 0 <= j < w and grid[i][j] == "1":
                grid[i][j] = "0"
                list(map(dfs, (i, i, i+1, i-1), (j+1, j-1, j, j)))
                return 1
            return 0
        
        for i in range(h):
            for j in range(w):
                ans += dfs(i, j)
        return ans

dfs(i, j) dfs(i, j+1) dfs(i+1, j) dfs(i-1, j) とやらなくてはならないところを map関数を使ってうまくやっています。なんかすげえとなりました笑

list()をmapにかけているのは、python3からmap関数を書いただけで、何かの変数に渡さない場合、中身が動かないからです。

このコードから学ばさせていただきました。感謝です。