SHOT4

社会人大学院生の勉強記録

問2.2(演算)

解答

type()でデータ型を表示させる。
ポイントになりそうなのは下記の通り。

  • all([True, True, False]) : すべてTrueのときTrueを返す、AND回路
  • any([True, True, False]) : どれか一つでもTrueのときTrueを返す、OR回路
  • abs : 絶対値を返す

# 1.2 + 3.8 予想:float 5.0
x = 1.2 + 3.8
t = type(x)
print(t,x)  # => <class 'float'> 5.0

# 10 // 100 予想:int 0
x = 10 // 100
t = type(x)
print(t,x) # => <class 'int'> 0

# 1 >= 0 予想:boolean True
x = 1 >= 0
t = type(x)
print(t,x) # => <class 'bool'> True

# 'Hello World' == 'Hello World' 予想:boolean True
x = 'Hello World' == 'Hello World'
t = type(x)
print(t,x) # => <class 'bool'> True

# not 'Chainer' != 'Tutorial' 予想:boolean False
x = not 'Chainer' != 'Tutorial'
t = type(x)
print(t,x) # => <class 'bool'> False

# all([True, True, False]) 予想:boolean False
x = all([True, True, False])
t = type(x)
print(t,x) # => <class 'bool'> False

# any([True, True, False]) 予想:boolean True
x = any([True, True, False])
t = type(x)
print(t,x) # => <class 'bool'> True

# abs(-3) 予想:int 3
x = abs(-3)
t = type(x)
print(t,x) # => <class 'int'> 3

# 2 // 0 予想:エラー?
x = 2 // 0
t = type(x)
print(t,x) # => ZeroDivisionError: integer division or modulo by zero

最後のやつは0で割ってるので、ZeroDivisionErrorが出力されている。

meganeshot4.hatenablog.com

問2.1 (組み込み関数)

組み込み関数の練習。リストの処理を順に実行します。

  • len(x) リストの長さを出力する
  • max(x) リストの最大値を出力する
  • min(x) リストの最小値を出力する
  • sum(x) リストの値の合計を出力する
  • sorted(x) リストの値を昇順にソートする

# 以下の FUNC という部分を組み込み関数に置き換えてください
a = [4, 8, 3, 4, 1]

# リストaの長さを求める
res = len(a)
print(res)  # => 5

# リスト a に含まれる値の最大値を求める。
res = max(a)
print(res) # => 8

# リスト a に含まれる値の最小値を求める。
res = min(a)
print(res) # => 1

# リスト a に含まれる値の合計値を求める。
res = sum(a)
print(res) # => 20

# リスト a をソートして、[1, 3, 4, 4, 8] というリストを返す。
res = sorted(a)
print(res) # => [1, 3, 4, 4, 8]

meganeshot4.hatenablog.com

Chainerチュートリアルの演習問題を解く

今回はChainerチュートリアルに挑戦します。
あまりにも数学がわからず大学院の課題が全然わからないので、Chainerチュートリアルの数学を解くついでに、改めてPythonの入門も終わらせました。一通り読み終えたのですが、演習問題に取り組んだので解答を備忘録としてメモしておきます。

tutorials.chainer.org

「2. Python 入門」 の演習問題

【書評】本田由紀「教育は何を評価してきたのか」:裏エピソードとしての教育史と言葉の関係

f:id:meganeshot4:20200621213403j:plain 今回読んだのは、2020年1月に出版された本田由紀先生の「教育は何を評価してきたのか」。あとがきに著者自身が書いている通り、新書にしてはかなり専門的な内容で、且つ著者の最近の研究の集大成のような内容になっているそう。

学部で教育学を専攻していて、そこで教育社会学を少しかじった身としては、「本田由紀」といえばメリトリクラシーとすぐに思い浮かぶ研究者の一人。そんな著者が「教育は何を評価してきたのか」と問い、メリトクラシーが日本では「能力主義」と訳され、日本にいびつな形で導入されてきたことについて、自己反省的な書籍を出したと聞いて、本屋で見かけたと同時にレジに向かっていました。

さて、意気揚々と読み始めたはいいものの、冒頭にも記載した通り「はじめに」から最後までデータや歴史を踏まえながら論拠を詰めていくような専門的な内容のため挫折。早々にページを閉じてしまいました。しかしたまたま今回「会わない読書会」の課題図書にすることになり、改めて向き合う時間がとれたので感想をまとめてみようと思います。

書籍の概要

目次は下記の通りです。

  1. 日本社会の現状ー「どんな人」たちが「どんな社会」を作り上げているか
  2. 言葉の磁場ー日本の教育の特徴はどのように論じられてきたか
  3. 画一化と序列化の萌芽ー明治維新から敗戦まで
  4. 「能力」による支配ー戦後から1980年代まで
  5. ハイパー・メリトクラシーへの未知ー1980〜90年代
  6. 復活する教化ー2000年代以降
  7. 出口を探すー水平的な多様性を求めて

構成としては比較的簡易で、1章で問題の前提として世界的にみて日本が特異な状態であることを示し、問題設定を行っています。その後2章にて、メリトクラシーという言葉がどのように日本に受容されてきたのかについて論じています。冒頭に記載したような、著者による自己反省的な色合いが強く出ている箇所、というのは主にこの章のことで、それ以降にはあまり言及されません。

3章以降は明治維新から現代までを時系列に追いながら、「能力」「態度」「資質」という3つのことばと著者自身の設定による「垂直的序列化」「水平的画一化」「水平的多様化」の3つのキーワードを相互に関連させながら論じる構造。そしてそこで提示した問題設定から、最後に具体的な政策提言に落とすという形をとっています。

全体としては、現代社会を比較的長い時間軸でみたときに「能力」「態度」「資質」という3つのことばの意味がどのように変化をしていき、それによって教育政策が「垂直的序列化」と「水平的画一化」を強めたことを主張していて、そのカウンターとして「水平的多様化」を強めるような政策提言をおこなう、という構図です。

一方で本田由紀先生自体、教育社会学の専門であり、その役割意識からか、どちらかといえば現状を暴く部分に力を割いていて、最後のカウンターの主張は短く、また根拠の弱いものに見えます。それに対して長い時間軸の中で、ことばの変遷が今の教育政策の中で大きく変わっていることは切れ味よく述べている印象です。

実際最後の提言では、根拠データがほとんど示されておらず、また比較的安易にイエナプラン教育に言及されています。ただ単純にこの書籍の問いは「教育は何を評価してきたのか」であり、その主要な関心は問題提起にあるので、そこはおまけと捉えていいという認識です。

以下3つくらいの点について、感想をまとめます。

  1. 裏エピソードとしての側面 2.抵抗としての教育社会学
  2. 水平的画一化と垂直的序列化の結託からみる就活

裏エピソードとしての教育史と言葉の関係

この本は、近年盛んにうたわれている「育成すべき資質・能力」について、その裏エピソードを暴露するような形になっています。「育成すべき資質・能力」とは新しい学習指導要領で示されている、「学びに向かう力・人間性等」「思考力・表現力・判断力等」「知識・技能」の3つを重要な資質・能力として、これらを育成することが重要とする考え方のことです。

こういった考え方は「表のエピソード」として、これからの社会(これはVUCAとかSociety5.0とかいろいろな名前で呼ばれる)では、従来のように学力テストで測れる知識を身につけるだけじゃ全然だめで、ここに対応できるような能力、いわゆる「コンピテンシー」を身につける必要があるのだ、という主張です。例えば、変化が激しい現代社会においては、学び続ける姿勢が重要であり、生涯教育社会において「自ら学ぶ姿勢」を身につけることが重要なのだ、というロジックで説明をされるわけです。

しかし本書が示しているのは、この資質・能力はそんな表エピソードからなるものではなく、これまでの歴史的流れの中に萌芽があったものであり、そこには「能力」概念の拡大があらわれていることを示しています。つまり垂直的序列化が強化されていく中で、社会的な状況もあり「知識・技能」だけでは序列がつけられなくなったことによる副産物として、これまで学力を意味していた「能力」は新学力感、人間力と拡大をしながら形を変え、最終的には学力を一つの要素として包含した「資質・能力」に姿を変えたというわけです。

抵抗としての教育社会学

水平的画一化と垂直的序列化は、0になるようなものではなく、その方向に容易に流れていってしまう社会の力の向きとして示されていて、これに対する抵抗のような形で法制度がひかれていたことが示唆されています。仁平さんの言葉を借りれば、ここでいう水平的画一化への抵抗は教育社会学でいえば「教育の抑圧性」を指摘するロジックであり、垂直的序列化への抵抗はある意味で「教育の欠如」を指摘するロジックといえます。

そしてこの問題系のうち、後者の「教育の欠如」を指摘するロジックは現在の教育社会学のメイン領域となっており、そこでは主に教育システム内に存在する格差を暴くような形での研究が多く行われています。何度か引用されている松岡さんの「教育格差」や苅谷さんはそのロジックの代表格と言えるでしょう。しかしこの本では、そのような問題が起きるそもそもの原因を「垂直的序列化」に向かっていく力にあると考え、その力がいかにして社会を方向づけてきたかを明らかにするものになっています。

本田さんは元々がハイパーメリトクラシーの研究者で、このロジックの専門なので、こちらの議論はかなり明瞭な展開です。特に「能力」という言葉が持つ序列の意識と、その意味の拡大を通して種々様々な諸能力が一つの「能力」の序列化に参画していく過程については、なるほどと感嘆しながら読んでいました。また元々は高等学校のキャパが足りなかったことから生じた入学試験からきた「学力」という意味での「能力」概念が、高校全入時代に突入するにつれて中卒労働者の職を奪う事態につながり、そこから高卒の中でも差異を出すための素材として「態度」に注目が集まっていく過程は大学全入時代に突入している今の社会から、数年後の未来を思い描く指針になります。

また一方で、「教育の抑圧性」があまり語られてこなかった裏で、保守層が政府と結びつく形で新教育基本法を制定し、教育勅語を思わせるような「態度」の「教化」が進んでいることを明らかにしています。言うまでもなくこれは「水平的画一化」のロジックです。例えばブラック校則や森友学園の問題などが問題として取り上げられています。ここは正直本田さんの指摘では途中がブラックボックス化しており、社会の不安定化から改めて国力の強化という幻想が立ち上がり、ここにつながっているという論が展開されていますが、序列化ほどの納得感はありませんでした。

水平的画一化と垂直的序列化の結託からみる就活

ただこの水平的画一化と垂直的序列化の結託の力は凄まじく、現在でいえば新卒の就活事情などはまさにこの結託の中にあると言えそうです。ビジネス書の多くが不安定な社会での生き抜き方を煽り、不確定な「能力」を規定していき、またその世界に飛び込む一部の大学生を神格化することで強い序列意識を根付かせています。就活は学生に同じレースに並ぶことを強要し、そこで測られる「能力」に応じて内定が出るような仕組みになっており、レースへの参加が遅れることは致命傷を意味するわけです。

同じレースに並ぶというのは、つまり同じような「リーダーシップ」を語り、同じような髪型で同じようなスーツを着ることを指し、まさに水平的画一化の様相を呈します。その一方でそこには学校歴をはじめとする明確な序列が存在し、その中で自分の位置どりをするゲームのようなものと捉えれば、そこはまさに垂直的序列化といえるわけです。

ここで排除されているのは、例えば「オワコン」ルートとして名高い文系修士課程の学生でしょう。まさに自分自身がその一員なわけですが、そもそもが同じルートに乗れなかったわけですし、このゲームで測られる能力はハイパーメリトクラシーでいう能力で、知識ではありません。よりニッチな領域を突き詰める方向にある修士課程との相性が悪いのは言うまでもないでしょう。

そう捉えると「水平的画一化」と「垂直的序列化」という概念はとてもよくできていて、現状を分析するのに便利そうです。さらにいえば概念自体が方向の軸というニュアンスを持ち、しかも流れとしては画一化、序列化に向かうようになっていて、そこに逆行する向きの「抵抗」を考えることができるのもいい感じ。

以上長くなりましたが、感想でした。

関連文献/参考資料

参考になりそうな文献は下記。

以前似たような事象について書いた記事 note.com note.com

「吾輩は猫である」のbi-gramと条件付き確率を算出する

こちらは授業の追加課題。コードが汚くなってしまって悔しいので、後日修正を入れます。

問題

夏目漱石の小説『吾輩は猫である』の文章(neko.txt)に対して、単語 bi-gram とその条件付き確率をスムージングなしですべて求めて出力せよ。なお、表層形や品詞の違いは無視して、原形が同じ単語は同じと扱って良い。また文頭記号BOSと文末記号EOSは考えること。ただし、確率の大きい順に並べる必要はない。

キーワード

  • bi-gram
  • 条件付き確率
  • スムージング

考え方

条件付き確率の算出には、単語ごとの文書全体での出現回数と、bi-gramごとの文書全体での出現回数が必要になる。結果としてはこれら2つで割り算をすれば求まるので、順に数値を求めていけばよい。

1. 形態素解析結果を取り込む
2. 文章ごとの単語リストを作成する
3. bi-gramのリストを作成する
4. 単語ごとの出現回数を計算する
5. bi-gramごとの出現回数を計算する
6. 条件付き確率を算出する

1. 形態素解析結果を取り込む

# MeCabのインポート
import MeCab
m = MeCab.Tagger('-Ochasen')

# ファイル名の指定
filename = 'neko.txt.mecab'

# ファイルの読み込みfilename = "neko.txt.mecab"
with open(filename,mode='rt',encoding='utf-8') as f:
    blockList = f.read().split('EOS\n') # 文章ごとに分割
blockList = list(filter(lambda x: x!='', blockList)) 

ここまではお決まりの文言。今回は事前にmecab形態素解析をした「neko.txt.mecab」を利用した。
bi-gramの作成にあたり、文章ごとに処理をしたいので、blockは文章ごととしている。

2. 文章ごとの単語リストを作成する

# 文章ごとの単語リストの作成
res = []
for block in blockList:
    wordList = ["BOS"]
    for line in block.split("\n"):
        if line == "":
            continue
        base = line.split("\t")[1].split(",")[6]
        if base == "\u3000":
            continue
        wordList.append(base)
    wordList.append("EOS")
    res.append(wordList)

文章ごとに処理を回して、出現単語をリストに追加。最初と最後に、文頭と文末を意味する「BOS」と「EOS」を追加している。またこのとき全角スペースが入ると後の計算で邪魔なので「\u3000」は削除、また空白行ができてしまうのでそこも飛ばしている。

今回は形態素解析結果のうち、原型のみ利用するため、baseしか使わない。また元ファイルがmecabファイルなので、タブでsurfaceとfeatureに区切った後、後者からbaseだけを取り出している。

3. bi-gramのリストを作成する

# bigramの算出
def bigram(target):
    bigram = []
    for t in target:
        for i in range(len(t)-1):
            bigram.append([t[i],t[i+1]])
    return bigram

bigram = bigram(res)

変数名とか関数名の名付けに悩んだが、とりあえず今回は動けばいいやということで作成。他の課題でもbigramを作成する必要があったので関数とした。今回はbigramのみに対応。trigramなどは対応していないため、引数はリスト一つのみ。

格納形式はその後の処理を考えて[pre, post]のリスト形式とした。[[pre1,post1],[pre2,post2],......,[pren,postn]]と入っている感じになる。

4. 単語ごとの出現回数を計算する

def count_word(target):
    wcount = {}
    for t in target:
        for w in t:
            if w in wcount:
                value = wcount[w]
                wcount[w] = value + 1
            else:
                wcount[w] = 1
    return wcount

count_w = count_word(res)

ここでは単語ごとの全文書内での出現回数を算出している。事前にdictを作っておき、出てきた単語がdictにある場合はvalueに1を足す、ない場合は新しく割り当て、valueを1とする。この流れは何度も出てきたのでいい加減覚えた。

5. bi-gramごとの出現回数を計算する

def count_bigram(target):
    bigram_count = {}
    for bi in target:
        key = bi[1]+" | "+bi[0]
        if key in bigram_count:
            value = bigram_count[key]
            bigram_count[key] = value + 1
        else:
            bigram_count[key] = 1
    return bigram_count

count_bi = count_bigram(bigram)

今度はbi-gramの出現回数を数える。今回はbi-gramのリストがすでにあるので、これの中を探索していけばいい。基本的な考え方は上と同じ。また出力は条件付き確率の形式でkeyを保存する形式を選択した。

例えば、「吾輩は猫である。」の場合は下記のようになっている。
(吾輩|BOS)(は|吾輩)(猫|は)(で|猫)(ある|で)(。|ある)(EOS|。)

これは単純にdict型のkeyに複数の変数を入れらなかったのと、複雑なリスト計算の方法がわからなかったので、まとめてkeyにしてしまった。

6. 条件付き確率を算出する

def calc_prob(bigram,count_w,count_bi):
    ans = {}
    for bi in bigram:
        key = bi[1]+" | "+bi[0]
        if key in ans:
            continue
        pre = bi[0]
        prb = count_bi[key]/count_w[pre]
        ans[key] = prb
    return ans

ans = calc_prob(bigram,count_w,count_bi)

# 答えの出力 例:P(は | 吾輩)、P(で | 猫)、P(BOS | 吾輩)
print("P(は|吾輩) = ",ans["は | 吾輩"])
print("P(で|猫) = ",ans["で | 猫"])
print("P(吾輩|BOS) = ",ans["吾輩 | BOS"])

最後に条件付き確率を計算する。
ここまで単語の出現数とbi-gramの出現数は算出しているので、どちらかをキーにして算出すればOK。今回は3で作ったbi-gramのリストが[pre,post]という形式で中身が保存されているので、これを順に出していくことにした。

まず5で作ったbi-gramごとの出現回数を呼び出せるように、[pre,post]を用いてkeyを作っておく。これを最終的な返り値でもキーとして利用する。あとは確率を計算して、順にkeyに割り当てていき返すだけ。

全部を確認してもわからないので、最初の「吾輩は猫である。」から
P(は|吾輩)、P(で|猫)、P(吾輩|BOS)の3つを出力してみた。

出力結果

P(は|吾輩) = 0.3887733887733888
P(で|猫) = 0.036290322580645164
P(吾輩|BOS) = 0.020304017372421282


ソースコード

# MeCabのインポート
import MeCab
m = MeCab.Tagger('-Ochasen')

# bigramの算出
def bigram(target):
    bigram = []
    for t in target:
        for i in range(len(t)-1):
            bigram.append([t[i],t[i+1]])
    return bigram

# 単語ごと出現回数の算出
def count_word(target):
    wcount = {}
    for r in res:
        for w in r:
            if w in wcount:
                value = wcount[w]
                wcount[w] = value + 1
            else:
                wcount[w] = 1
    return wcount

# bigramごと出現回数の算出
def count_bigram(target):
    bigram_count = {}
    for bi in target:
        key = bi[1]+" | "+bi[0]
        if key in bigram_count:
            value = bigram_count[key]
            bigram_count[key] = value + 1
        else:
            bigram_count[key] = 1
    return bigram_count

def calc_prob(bigram,count_w,count_bi):
    ans = {}
    for bi in bigram:
        key = bi[1]+" | "+bi[0]
        if key in ans:
            continue
        pre = bi[0]
        prb = count_bi[key]/count_w[pre]
        ans[key] = prb
    return ans

# ファイル名の指定
filename = 'neko.txt.mecab'

# ファイルの読み込みfilename = "neko.txt.mecab"
with open(filename,mode='rt',encoding='utf-8') as f:
    blockList = f.read().split('EOS\n') # 文章ごとに分割

# 空白行を削除する
blockList = list(filter(lambda x: x!='', blockList)) 

# 文章ごとの単語リストの作成
res = []
for block in blockList:
    wordList = ["BOS"]
    for line in block.split("\n"):
        if line == "":
            continue
        base = line.split("\t")[1].split(",")[6]
        if base == "\u3000":
            continue
        wordList.append(base)
    wordList.append("EOS")
    res.append(wordList)


# bi-gramの作成
bigram = bigram(res)

# 単語ごと出現回数の算出
count_w = count_word(res)

# bigramごと出現回数の算出
count_bi = count_bigram(bigram)

# 条件付確率の算出
ans = calc_prob(bigram,count_w,count_bi)

# 答えの出力 例:P(は | 吾輩)、P(で | 猫)、P(BOS | 吾輩)
print("P(は|吾輩) = ",ans["は | 吾輩"])
print("P(で|猫) = ",ans["で | 猫"])
print("P(吾輩|BOS) = ",ans["吾輩 | BOS"])

35. 単語の出現頻度

問題文

文章中に出現する単語とその出現頻度を求め,出現頻度の高い順に並べよ.
nlp100.github.io

ソースコード

# MeCabのインポート
import MeCab
m = MeCab.Tagger('-Ochasen')

# ファイル名の指定
filename = 'neko.txt.mecab'

# ファイルの読み込みfilename = "neko.txt.mecab"
with open(filename,mode='rt',encoding='utf-8') as f:
    blockList = f.read().split('EOS\n') # 文章ごとに分割

# 空白行を削除する
blockList = list(filter(lambda x: x!='', blockList)) 

# 単語リストの作成
res = []
for block in blockList:
    for line in block.split("\n"):
        if line == "":
            continue
        surface = line.split("\t")[0]
        attr = line.split("\t")[1].split(",")
        lineDict = {
            'surface': surface,
            'base': attr[6],
            'pos': attr[0],
            'pos1': attr[1]
        }
        res.append(lineDict)

# 出現頻度の算出
ans = {}
for r in res:
    key = r['base']
    if key in ans:
        value = ans[key]
        ans[key] = value + 1
    else:
        ans[key] = 1

# 出現頻度の高い順に並び替え
ans = sorted(ans.items(), key = lambda x:x[1], reverse=True)

# 答えの出力
print("出現頻度の高い順に40位まで出力")
print(ans[:40])

出力

出現頻度の高い順に40位まで出力
[('の', 9194), ('。', 7486), ('て', 6848), ('、', 6772), ('は', 6420), ('に', 6243), ('を', 6071), ('だ', 5975), ('と', 5508), ('が', 5337), ('た', 4267), ('する', 3657), ('「', 3231), ('」', 3225), ('ない', 3052), ('も', 2479), ('ある', 2320), ('*', 2191), ('で', 2081), ('から', 2031), ('いる', 1777), ('ん', 1568), ('か', 1529), ('云う', 1408), ('事', 1207), ('です', 1164), ('ます', 1146), ('なる', 1120), ('へ', 1034), ('う', 987), ('もの', 981), ('君', 973), ('主人', 932), ('ぬ', 719), ('よう', 696), ('見る', 675), ('ね', 657), ('この', 649), ('御', 636), ('ば', 617)]

所感

まず単語リストの作成でかなり手こずり、いつも参考にさせていただいているupuraさんのブログから処理を作成する。次に出力してみるも、結果が授業スライドと異なり困惑する。

結論、「surface」を使うか「base」を使うかで結果が変わるのが原因と発覚。今回は単語の出現頻度が知りたいので、活用によって形が変わってしまうsurfaceではなく、baseを利用することで解決した。

また1行で並び替えができるsorted(list.items(),key = lambda x:x[1]. reverse=True)は今後頻出することになる。

00. 文字列の逆順

問題文

文字列”stressed”の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.
nlp100.github.io

ソースコード

回答例1

str = "stressed"
print(str[::-1])

回答例2

str = "stressed"
ans = []
for i in range(len(str)):
    ans.append(str[-(i+1)])
    
print("".join(ans))

出力

desserts

所感

最初は回答例2で取組み、その後調べたところリストのスライスを使えば簡単に解けることを知った。文字列の逆順をforで回すのに少し苦労したが、同様の方法で文字列によらず逆順は取り出せる。