werry-chanの日記.料理とエンジニアリング

料理!コーディング!研究!日常!飯!うんち!睡眠!人間の全て!

spacy GINZA固有表現抽出をjupyterなしでprintする

spacy GINZAを使った固有表現抽出の記事を色々見ていると, jupyterのdisplay使ったコードばかりでした。

筆者は普段リモートワークでLinuxCUIで開発しており, jupyter使うと面倒になるため, なるべくやりたくないのです。

リモートでCUIマシンでjupyter使う場合, jupyter用にport開けて暗号化かけて, 毎回サーバ立てて使い終わったら落としたり面倒です。


今回は, spacy GINZA entityのprintをjupyterなしでやります。

まずはセットアップです。

自然言語処理Libのspacyです。

pip install spacy

spacyだけでも形態要素解析が可能です。


次に日本語対応のモデルが使えるようにします。

pip install -U ginza ja_ginza_electra

ginza-electraは今のところ公開されている中では, 日本語の対応が一番良かった感じがします。


実際のコードは以下になります。

内容としては単純な文字列処理になります。

import spacy

#model loading
nlp = spacy.load('ja_ginza_electra')

in_text = '猫次郎は5月18日の朝9時にタヌキ商店へタヌ吉に会いに行った。'
doc     = nlp(in_text)

hidden_text = copy.deepcopy(in_text)
for ent in doc.ents:
    hidden_text = ( hidden_text[:ent.start_char] +
                    '*' * int(ent.end_char - ent.start_char) +
                    hidden_text[ent.end_char:]
                   )

labeled_text = copy.deepcopy(in_text)
num_diff_word = 0
for ent in doc.ents:
    labeled_text = ( labeled_text[:ent.start_char + num_diff_word] +
                     '"' + ent.text + '[' + ent.label_ + ']' + '"' +
                     labeled_text[ent.end_char + num_diff_word:]
                    )
    num_diff_word += len(ent.label_) + 4
print(in_text)
print(hidden_text)
print(labeled_text)

出力は以下のようになります。

        in_text: 猫次郎は5月18日の朝9時にタヌキ商店へタヌ吉に会いに行った。
hidden_text: ***は*****の***に*****へ***に会いに行った。
labeled_text:"猫次郎[Person]"は"5月18日[Date]"の"朝9時[Time]"に"タヌキ商店[Dish]"へ"タヌ吉[Person]"に会いに行った。

以上でJupyterなしでentity出力できました。

Pytorch公式のDataLoaderなしで学習を回す

Pytorch公式のtorch.utils.data.DataLoaderって, torch.utils.data.Datasetクラス継承してiteratorとかlen改造したりと, 初心者の心を折る要素がいっぱいあります。

今回は, DataLoaderなしでNumpyを基本として学習させる方法をメモします。

要点だけ言えば, numpy.array(dtype=np.float32)からtorch.tensor()へcast(型変換)すればOKです。
torch.nn.Module系のモデルは, 基本的にtorch.tensor(dtype=torch.float32)しか受け付けません。
mobile-net系とかは量子化されてそうだから違うのかもしれない...


では実装, まずは簡易データを作ります。

# make_easy_dataset.py
import numpy as np


def make_easy_dataset(X_length):
    theta = np.linspace(0,10*np.pi, 5000)
    sin   = np.sin(theta)
    X, y  = [], []
    for i in range(len(theta) - X_length - 1):
        X.append([sin[i : i + X_length]])
        label = [0,1]
        if sin[i + X_length + 1] > sin[i + X_length]:
            label = [1,0]
        y.append(label)
    return np.array(X, dtype=np.float32), np.array(y, dtype=np.float32)

Xは, sin波の時系列データ,
yは, 次に上昇するか否かの判定結果です。

この時, 注意すべきは, Xの型を dtype=np.float32 に設定しておくことです。
np.float32に設定しておかないと, 後ほどtorch.tensorへの変換でエラーが出ます。


次にモデルを適当に作っておきます。

以下は, 自作の任意層数1dConvolution簡易モデル, 任意層数FullConnect簡易モデル, 簡易結合networkです。
自分で使いたいモデルがある人は, 自分のモデルでOKです。

# DNN_module.py
import torch
from   torch import nn


class SimpleCNN1d(nn.Module):
    def __init__(self, in_channel:int, out_channel:int, num_block:int, kernel_size:int, stride:int=1, dropout:float=0.0):
        super(SimpleCNN1d, self).__init__()
        self.in_channel  = in_channel
        self.out_channel = out_channel
        self.num_block   = num_block
        self.kernel_size = kernel_size
        self.dropout     = dropout
        layers = []
        for i in range(num_block):
            if i != 0:
                layers += [nn.Conv1d(out_channel, out_channel, kernel_size, stride), nn.ReLU()]
            else:
                layers += [nn.Conv1d( in_channel, out_channel, kernel_size, stride), nn.ReLU()]
        if dropout > 0:
            layers += [nn.Dropout(dropout)]
        self.net = nn.Sequential(*layers)
    def forward(self, x):
        return self.net(x)


class FC_classifier(nn.Module):
    def __init__(self, in_channel:int, in_length:int, out_channel:int, num_block:int):
        super(FC_classifier, self).__init__()
        self.in_dim      = in_channel * in_length
        self.out_channel = out_channel
        self.num_block   = num_block
        self.flatten     = nn.Flatten()
        layers = []
        for i in range(num_block):
            in_dim_  = self.in_dim // (i + 1)
            if in_dim_ < 1:
                in_dim_ = 1
            out_dim_ = in_dim_ // 2
            if out_dim_ < out_channel or i == num_block-1:
                out_dim_ = out_channel
            layers += [nn.Linear(self.in_dim, out_dim_)]
        layers += [nn.Hardsigmoid()]
        self.net = nn.Sequential(*layers)
    def forward(self, x):
        x = self.flatten(x)
        out = self.net(x)
        return out


class CombinedNet(nn.Module):
    def __init__(self, models):
        super(CombinedNet, self).__init__()
        layers = []
        for model in models:
            layers += [model]
        self.net = nn.Sequential(*layers)
    def forward(self, x):
        return self.net(x)


それではnumpyベースのデータセットとモデルを定義して, 実際に学習を回しましょう。

import torch
import make_easy_dataset as med
import DNN_module


if __name__ == "__main__":
    data_len    = 10
    X, y = med.make_easy_dataset(data_len)

    num_block   = 1
    base_model        = DNN_module.SimpleCNN1d( in_channel = 1,
                                                out_channel = 1,
                                                num_block   = num_block,
                                                kernel_size = 3,
                                                dropout     = 0.2,
                                                )

    base_out_data_len = data_len - 2*num_block
    classifier        = DNN_module.FC_classifier(   in_channel  = 1,
                                                    in_length   = base_out_data_len,
                                                    out_channel = 2,
                                                    num_block   = 1,
                                                    )

    combined_model    = DNN_module.CombinedNet([base_model, classifier])


    batch_size = 256
    epochs = 10
    for t in range(epochs):
        for num_batch in range(len(X) // batch_size + 1):
            index_start =   num_batch * batch_size
            index_end   = index_start + batch_size
            # torch.tensorへcast(型変換)
            X_batch     = torch.tensor(X[index_start : index_end]) # float32
            y_batch     = torch.tensor(y[index_start : index_end], dtype=torch.long) # classiferなのでintでOK, float 32でもOK
            out = combined_model(X_batch)
            # after processing ....

以上です。

基本的にnumpy.float32型データでデータセットを作っていれば, torch.tensor()へ変換できます。

anaconda重すぎエラー:Warning: This Python interpreter is in a conda environment, but the environment has not been activated. Libraries may fail to load.

初心者向けの環境構築に優しいanacondaさん

使い倒して大量の仮想環境を作り過ぎてないですか?

筆者はanacondaが100GBの容量持ってて
Warning:
This Python interpreter is in a conda environment, but the environment has
not been activated. Libraries may fail to load.

などと言われて, さらに

Error loading "C:\Users\[username]\anaconda3\lib\site-packages\torch\lib\shm.dll" or one of its dependencies.

とか言われて, Pytorchが見つからないとか毎回めんど過ぎたので消しました。

消し方は, 直接 C:\Users\[username]\anaconda3\ を消しましょう。
pathが残っていると面倒なので, システム環境設定からも消えているか確認しましょう。

全て消し終わったら, Python標準の仮想環境構築用のvenvを使った方が楽ちんだと思います。

python -m venv ./my_venv/

と叩けば, 今いるdirectoryにmy_venvが出来てます。

activateは,

linuxなら commandで source ./my_venv/bin/activate
windowsなら cmdで ./my_venv/Scripts/activate

でactivateして仮想環境上にpipで環境構築しましょう。

エンジニアの海老チリ

簡単で確実に旨いビールがすすむ最強優勝する

エンジニアの作る海老チリです。

f:id:werry-chan:20230705202611j:image

レシピ(2人前)

食材

 ・海老 二人分欲しいだけ

 ・キャベツ 盛り付け用に1-2枚

ソース

 ・豆板醤 辛くしたいだけ

 ・ニンニク 1-2片

 ・ケチャップ 大さじ2

 ・お酢 大さじ2

 ・みりん 大さじ1

 ・だし 味の素orウェイパーor鶏ガラ粉末

 ・片栗粉 小さじ2+海老洗う用少し

 

1. 下準備、 海老を剝いておきます。剥いた海老の臭み抜きには、片栗粉を身にまぶして、水で溶きながら洗い落とすと良いです。僕は退勤後に海老の下処理するとか面倒で嫌なので、剥き済の冷凍海老使ってます。

2. 油にニンニクと豆板醤入れて、弱火で香り良く赤くなるまで炒める。香味油完成。

3. 海老に片栗粉をつけて香味油で炒めます。適度に焼き目がついたらOK

4. ケチャップ、お酢、みりん、出汁 を全量フライパンに入れて均一に溶かしながら海老に絡める。

5. レタス千切って皿に盛って、海老も盛り付けて完成です。

 

簡単で確実に旨いビールがすすむ最強優勝してしまいました。

 

OS破壊:Linuxにdefault installされているPythonを消すと終わりです

ある程度新しいversionのLinuxに元からdefaultで入っているPythonを消すとほとんど何もできなくなります。
OS内部の処理にPythonを使っている部分が多くあるようです。

筆者もCUI上で復旧を試みたのですが, そもそもaptが使えなくなっていました。
その他通信系もすべて使えなくなっていました。

おそらく, 通信系管理をPython使って実装されていたのかと思います。

どうしても復旧しないといけない場合には, 通信使わないでPythonと依存ライブラリを全て入れる方法がありそうですが...
おすすめしないです。

筆者は, まだ立ち上げたばかりだったので, OS再インストールしました。

中身のデータだけ吸い出したい場合には, SSD/HDDを取り出して, 別のHDDでOS再インストールするなり, 必要なデータだけ取り出したりすることも出来そうですが....
考えるだけでも憂鬱になりますね。

Pythonクラス継承とoperator overloadingで四則演算可能なMACアドレスクラスを自作する

MACアドレスは, デバイスの固有住所や固有名詞的な立ち位置なので, 個人情報に該当すると扱われ, しばしば暗号化して管理することがあります。

MACアドレスの暗号化を自前で実装したり, 解いたりする場合には, 四則演算することが必要です。

今回は, Pythonでoperator overloadingとクラス継承を使って, これらを実装しましょう。
operator overloadingについては, こちらの記事を参考にしてください。
operator overloadでオリジナルの演算子を実装 for Python - werry-chanの日記.料理とエンジニアリング


まずは, 親クラスとして, 符号なしN進数クラスを作ります。

# this code is werry-chann lib
# this lib provide oct hex unsigned class
class unsigned_int:
    def __init__(self, val, upper_lim=256):
        self.val       = val
        self.upper_lim = upper_lim
        self.check_val()
    def show(self):
        print(self.val, end="")
    def check_val(self):
        self.val = int(self.val % self.upper_lim)

表示 show() 関数とN進数チェック check_val() 関数も作っておきました。

次はこのクラス間で四則演算できるように, 演算子を定義します。

# this code is werry-chann lib
# this lib provide oct hex unsigned class
import copy

class unsigned_int:
    def __init__(self, val, upper_lim=256):
        self.val       = val
        self.upper_lim = upper_lim
        self.check_val()
    def show(self):
        print(self.val, end="")
    def check_val(self):
        self.val = int(self.val % self.upper_lim)
    def to_myclass(self, val):
        temp = copy.deepcopy(self)
        temp.val = val
        return temp
    def __add__(self, add_uint):
        if type(self) != add_uint.__class__:
            add_uint = self.to_myclass(add_uint)
        ret_uint = copy.deepcopy(self)
        ret_uint.val += add_uint.val
        ret_uint.check_val()
        return ret_uint
    def __sub__(self, sub_uint):
        if type(self) != sub_uint.__class__:
            sub_uint = self.to_myclass(sub_uint)
        ret_uint = copy.deepcopy(self)
        ret_uint.val -= sub_uint.val
        ret_uint.check_val()
        return ret_uint
    def __mul__(self, mul_uint):
        if type(self) != mul_uint.__class__:
            mul_uint = self.to_myclass(mul_uint)
        ret_uint = copy.deepcopy(self)
        ret_uint.val *= mul_uint.val
        ret_uint.check_val()
        return ret_uint
    def __truediv__(self, dev_uint):
        if type(self) != dev_uint.__class__:
            dev_uint = self.to_myclass(dev_uint)
        ret_uint = copy.deepcopy(self)
        ret_uint.val /= dev_uint.val
        ret_uint.check_val()
        return ret_uint

class unsigned_hex(unsigned_int):
    def __init__(self, val):
        super().__init__(val, upper_lim=8)

class unsigned_oct(unsigned_int):
    def __init__(self, val):
        super().__init__(val, upper_lim=16)

演算子を定義するついでに, クラス継承で8進数と16進数を作っておきました。



それでは, 目的のMACアドレスのクラスを作ってみましょう。

典型的なMACアドレス
F1:10:0B:16:FD:3A
のように, 16進数 2桁 6個で構成されています。

先ほど作成したunsigned_intクラスを使って実装します。
16進数2桁なので, 256=16^2, unsigned_intのupper_limはdefault引数256が使えますので, 省略可能です。
今回は入力される文字列にspace, tabなどは考慮せず, 必ず2桁以下, 正の値で入力されることを前提に実装していきます。

class mac_address:
    def __init__(self, str_digits, digits=6, upper_lim=256):
        self.digits = digits
        self.val_list = str_digits.split(':')
        for i in range(len(self.val_list)):
            self.val_list[i] = unsigned_int( int('0x' + str(self.val_list[i]), 0) )
    def show(self):
        for i in range(len(self.val_list)):
            self.val_list[i].show(); print(",", end="")

__init__()初期化関数でlistに各値を保持しています。

16進数の文字列の先頭に'0x'をつけてint('0x' + str(val), 0)としてやると
文字列からintの数値として読み込めます。

では先ほどの符号なしN進数の実装と同様に演算子を作成しましょう。

class mac_address:
    def __init__(self, str_digits, digits=6, upper_lim=256):
        self.digits = digits
        self.val_list = str_digits.split(':')
        for i in range(len(self.val_list)):
            self.val_list[i] = unsigned_int( int('0x' + str(self.val_list[i]), 0) )
    def show(self):
        for i in range(len(self.val_list)):
            self.val_list[i].show(); print(",", end="")
    def __add__(self, add_digits):
        ret_digits = copy.deepcopy(self)
        for i in range(self.digits):
            ret_digits.val_list[i] += add_digits.val_list[i]
        return ret_digits
    def __sub__(self, sub_digits):
        ret_digits = copy.deepcopy(self)
        for i in range(self.digits):
            ret_digits.val_list[i] -= sub_digits.val_list[i]
        return ret_digits
    def __mul__(self, mul_digits):
        ret_digits = copy.deepcopy(self)
        for i in range(self.digits):
            ret_digits.val_list[i] *= mul_digits.val_list[i]
        return ret_digits
    def __truediv__(self, dev_digits):
        ret_digits = copy.deepcopy(self)
        for i in range(self.digits):
            ret_digits.val_list[i] /= dev_digits.val_list[i]
        return ret_digits

やっとのことで演算可能なMACアドレスの自作クラスが完成しました。

普通はMACアドレスて基本は固定値か乱数なので, 演算しようとする人はいないので, めちゃくちゃ使いどころが限られるクラスになります。

自作するしかないですね。

以下に全コード載せておきます。

# this code is werry-chann lib
# this lib provide oct hex unsigned class
import copy

class unsigned_int:
    def __init__(self, val, upper_lim=256):
        self.val       = val
        self.upper_lim = upper_lim
        self.check_val()
    def show(self):
        print(self.val, end="")
    def check_val(self):
        self.val = int(self.val % self.upper_lim)
    def to_myclass(self, val):
        temp = copy.deepcopy(self)
        temp.val = val
        return temp
    def __add__(self, add_uint):
        if type(self) != add_uint.__class__:
            add_uint = self.to_myclass(add_uint)
        ret_uint = copy.deepcopy(self)
        ret_uint.val += add_uint.val
        ret_uint.check_val()
        return ret_uint
    def __sub__(self, sub_uint):
        if type(self) != sub_uint.__class__:
            sub_uint = self.to_myclass(sub_uint)
        ret_uint = copy.deepcopy(self)
        ret_uint.val -= sub_uint.val
        ret_uint.check_val()
        return ret_uint
    def __mul__(self, mul_uint):
        if type(self) != mul_uint.__class__:
            mul_uint = self.to_myclass(mul_uint)
        ret_uint = copy.deepcopy(self)
        ret_uint.val *= mul_uint.val
        ret_uint.check_val()
        return ret_uint
    def __truediv__(self, dev_uint):
        if type(self) != dev_uint.__class__:
            dev_uint = self.to_myclass(dev_uint)
        ret_uint = copy.deepcopy(self)
        ret_uint.val /= dev_uint.val
        ret_uint.check_val()
        return ret_uint

class unsigned_hex(unsigned_int):
    def __init__(self, val):
        super().__init__(val, upper_lim=8)

class unsigned_oct(unsigned_int):
    def __init__(self, val):
        super().__init__(val, upper_lim=16)

class mac_address:
    def __init__(self, str_digits, digits=6, upper_lim=256):
        self.digits = digits
        self.val_list = str_digits.split(':')
        for i in range(len(self.val_list)):
            self.val_list[i] = unsigned_int( int('0x' + str(self.val_list[i]), 0) )
    def show(self):
        for i in range(len(self.val_list)):
            self.val_list[i].show(); print(",", end="")
    def __add__(self, add_digits):
        ret_digits = copy.deepcopy(self)
        for i in range(self.digits):
            ret_digits.val_list[i] += add_digits.val_list[i]
        return ret_digits
    def __sub__(self, sub_digits):
        ret_digits = copy.deepcopy(self)
        for i in range(self.digits):
            ret_digits.val_list[i] -= sub_digits.val_list[i]
        return ret_digits
    def __mul__(self, mul_digits):
        ret_digits = copy.deepcopy(self)
        for i in range(self.digits):
            ret_digits.val_list[i] *= mul_digits.val_list[i]
        return ret_digits
    def __truediv__(self, dev_digits):
        ret_digits = copy.deepcopy(self)
        for i in range(self.digits):
            ret_digits.val_list[i] /= dev_digits.val_list[i]
        return ret_digits

if __name__ == "__main__":
    ui  = unsigned_hex(5)
    ui_ = unsigned_hex(7)
    (ui_ - 20 ).val; print()
    (ui  / ui_).show; print()
    ma0 = mac_address( '6:10:2B:C0:0D:34')
    ma1 = mac_address('F1:10:0B:16:FD:3A')

    ma0.show(); print()
    ma1.show(); print()
    (ma0 - ma1).show(); print()
    (ma0 + ma1).show(); print()
    (ma0 * ma1).show(); print()
    (ma0 / ma1).show(); print()

operator overloadでオリジナルの演算子を実装 for Python

非常に稀かもしれませんが, 一般的に使われる四則演算とは異なったルールで計算をしたいことがあるかと思います。

筆者は, 少し変わった暗号計算解くために
例えば, N桁M進数 を符号なしの循環数として大量に演算する必要があり, N digit Unsigned M decimalクラスを作ったことがありました。
その件については, こちらの記事で簡易実装例を載せておきました。
Pythonクラス継承とoperator overloadingで四則演算可能なMACアドレスクラスを自作する - werry-chanの日記.料理とエンジニアリング


そのような時には, operator overloadingを実装しましょう。

今回は分数を計算するためのfractionクラスを例に説明していきます。

fractionクラスは計算上の丸め,桁落ち誤差などを避けることが可能なクラスで, 標準ライブラリとしても実装されているものになります。
今回はこれを自作していこうと思います。
標準ライブラリの中身を実際には見ていないので, 異なる部分多数あるかと思いますが, 今回はoperator overloadingの実装がメインテーマなのでお目こぼしください

まずはfractionクラスを作成した時に呼ばれる初期化関数を作成します。

class fraction:
    def __init__(self, num: int, den: int):
        self.num = num
        self.den = den

Pythonは勝手に型推定されて変換されて面倒なので, 関数設定でint型しか入れれないように拘束します。
さらに, その後も勝手にfloat型に変換されないように気を付けましょう。

次に, 分数を扱うので, 入力された数値がきちんと約分された状態にしましょう。
約分はこの後もたくさん使うので, 早めに書いておくと素敵です。

import math

class fraction:
    def __init__(self, num: int, den: int):
        self.num = num
        self.den = den
        self.transmission() #約分する
    def transmission(self):
        gcd_val  = math.gcd(self.num, self.den)
        self.num = int(self.num / gcd_val) #型変換避けint()
        self.den = int(self.den / gcd_val) #型変換避けint()

では次に, きちんと約分が出来ているか確認ついでに, 表示関数を作成しましょう。

import math

class fraction:
    def __init__(self, num: int, den: int):
        self.num = num
        self.den = den
        self.transmission()
    def transmission(self):
        gcd_val  = math.gcd(self.num, self.den)
        self.num = int(self.num / gcd_val)
        self.den = int(self.den / gcd_val)
    def show(self):
        self.transmission()
        print(self.num, '/', self.den, end='')

if __name__ == "__main__":
    f1 = fraction(-2,5)
    f1.show() # output -> -2 / 5
    print(f1) # output -> <__main__.fraction object at 0x000001E7D6B54508>

これで, 今後の作業中においても適宜数値が確認できるようになりました。
ちなみにオリジナルのクラスなので, 普通にprint()しても数値は見えません。

では次は, 加算演算子を作成していきましょう。

自作したfractionクラスを
f_0 = f_0 + f_1, あるいは f_0 += f_1
のように扱いたい場合には, 演算子を自分で定義する必要があります。

def __add__(self, val): # valは上記のf_1にあたる
と自作の+演算子による結果を定義してやります。

import math
import copy

class fraction:
    def __init__(self, num: int, den: int):
        self.num = num
        self.den = den
        self.transmission()
    def transmission(self):
        gcd_val = math.gcd(self.num, self.den)
        self.num = int(self.num / gcd_val)
        self.den = int(self.den / gcd_val)
    def show(self):
        self.transmission()
        print(self.num, '/', self.den, end='')
    def __add__(self, val):
        if type(val) != self.__class__: # 演算相手が別の型の時は
            val_ = fraction(val, 1) # 型を合わせてやる, 元のvalを壊さない形で
        else:
            val_ = val #元のvalを壊さないように計算用の変数を用意してやる
        ret_val      = copy.deepcopy(self) #同上
        ret_val.num  = (ret_val.num * val_.den) + (val_.num * ret_val.den) # 通分して分子を計算
        ret_val.den *= val_.den # 分母も通分状態にする
        ret_val.transmission() # 最後に約分して返す
        return ret_val

分数の計算だったので, 若干癖のある計算かもしれないですが, 上記のような形になります。
まぁ小学生の範囲でも実装すると意外と面倒です。
毎回返す直前呼んでいる約分(transmission())関数でint型の分子, 分母になるようにしているので, 推定型変換への注意が少なく済んで良いですね。
筆者は, Pythonのバグの5割は型推定のせいだと思っています

それでは同様にして他の演算子なども作成してしまいましょう。
#edited 20230703, before __dev__(), after __truediv__()

# this code is werry-chann lib
# this lib provide fraction class
import copy
import math


class fraction:
    def __init__(self, num: int, den: int):
        self.num = num
        self.den = den
        self.transmission()
    def transmission(self):
        gcd_val = math.gcd(self.num, self.den)
        self.num = int(self.num / gcd_val)
        self.den = int(self.den / gcd_val)
    def show(self):
        self.transmission()
        print(self.num, '/', self.den, end='')
    def to_int(self):
        self.transmission()
        return int(self.num / self.den)
    def to_float(self):
        self.transmission()
        return float(self.num / self.den)
    def __add__(self, val):
        if type(val) != self.__class__:
            val_ = fraction(val, 1)
        else:
            val_ = val
        ret_val      = copy.deepcopy(self)
        ret_val.num  = (ret_val.num * val_.den) + (val_.num * ret_val.den)
        ret_val.den *= val_.den
        ret_val.transmission()
        return ret_val
    def __sub__(self, val):
        if type(val) != self.__class__:
            val_ = fraction(val, 1)
        else:
            val_ = val
        ret_val     = copy.deepcopy(self)
        ret_val.num  = (ret_val.num * val_.den) - (val_.num * ret_val.den)
        ret_val.den *= val_.den
        ret_val.transmission()
        return ret_val
    def __mul__(self, val):
        if type(val) != self.__class__:
            val_ = fraction(val, 1)
        else:
            val_ = val
        ret_val      = copy.deepcopy(self)
        ret_val.num  = ret_val.num * val_.num
        ret_val.den *= val_.den
        ret_val.transmission()
        return ret_val
    def __truediv__(self, val): #edited 20230703, before __dev__(), after __truediv__()
        if type(val) != self.__class__:
            val_ = fraction(val, 1)
        else:
            val_ = val
        ret_val     = copy.deepcopy(self)
        ret_val.num = ret_val.num * val_.den
        ret_val.den =  ret_val.den * val_.num
        ret_val.transmission()
        return ret_val


if __name__ == "__main__":
    f1 = fraction(-2,5)
    print(f1) # <__main__.fraction object at 0x0000020364C2FFC8>
    f2 = fraction(1,20)
    f3 = f1 + f2
    f3.show() # -7 / 20
    f4 = f1 - 3
    f4.show() # -17 / 5

以上で四則演算の実装が完了しました。

ここまで実装が出来た人であれば残りの比較演算子(==, <, >)などについても調べながらできると思います。

以下の参考文献1つ目のリンクには他の演算子についても記述してありますので, 参考になりましたら幸いです。

参考文献
Operator Overloading in Python - GeeksforGeeks
https://docs.python.org/3/reference/datamodel.htm
operator — Standard operators as functions — Python 3.11.4 documentation
2, 3番目のリンクが公式なのですが, なかなか初心者には厳しそうでした。