3S
  • Portfolio Top
  • Categories
  • Tags
  • Archives

検量線周りのまとめ②

非線形関数フィッティング¶

 酵素反応は一般に基質濃度を[S]として、 $$-\frac{d[S]}{dt}=k[S]$$ として表されます。この解を解析的に求めることは簡単で、時間と基質濃度の関係は以下の式で与えられます。 $$[S] = Ae^{-kt}\qquad (\text{Aは積分定数})$$  この式は非線形関数ですが、誤差関数を指定して(今回は二乗誤差)パラメータを最尤推定すれば曲線フィッティングが可能です。

 一応今回の実験のプロトコルや原理をかいつまんで書いておくので、非線形関数フィッティングだけ見たい場合はここへどうぞ。

原理¶

リゾチームは、真正細菌の細胞壁を構成する多糖類を加水分解する酵素である。 Wikipedia

この時の反応は酵素反応であり、細胞溶液の濃度は、式(2)の曲線に乗ると考えられます。
なお、直接濃度を測ることは難しいので、$\mathrm{OD}_{600}$ の値を用います。なお、$\mathrm{OD}_{600}$ と濃度の関係は $\mathrm{OD}_{600}=a[S]+b$ という関係で表せることがわかっているので、以下の式で表されます。 $$\begin{aligned} \mathrm{OD}_{600} &= aAe^{-kt} + b\\ &= Ce^{-kt} + b \end{aligned}$$

プロトコル¶

  1. 0.3 mg/mL菌液(Mycrococcus lysodeikticus)25 mLを分注した。
  2. リゾチームを500 mM酢酸ナトリウムpH 4.5に溶かし、0.1 mg/mLを作製した。
  3. 菌液をよく撹拌してから1.4 mLを光学セルに入れ、600 nmの吸光度を測定した。これを、t=0 秒後の吸光度の値とした。
  4. 0.1 mg/mLのリゾチーム溶液を0.1 mL入れて反応を開始した。リゾチームを添加して120 秒後までの吸光度を10 秒毎に測定した。なお、この時毎回の測定の間にピペッティングをした。

結果¶

t[s] 0 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150
$\mathrm{OD}_{600}$ 0.939 0.677 0.576 0.508 0.451 0.419 0.381 0.362 0.339 0.321 0.299 0.282 0.275 0.276 0.267 0.252
In [1]:
import numpy as np
import matplotlib.pyplot as plt
In [2]:
time = [0,10,20,30,40,50,60,70,80,90,100,110,120,130,140,150]
OD = [0.939, 0.677, 0.576, 0.508, 0.451, 0.419, 0.381, 0.362, 0.339, 0.321, 0.299, 0.282, 0.275, 0.276, 0.267, 0.252]
In [3]:
plt.plot(time, OD)
plt.scatter(time, OD, label="data")
plt.title("The relationship between time and $\mathrm{OD}_{600}$")
plt.xlabel("time [s]")
plt.ylabel("$\mathrm{OD}_{600}$")
plt.legend()
plt.show()

考察(フィッティング)¶

 今回は、SciPyの curve_fit メソッドを使います。基本的な使い方は、パラメトリックな関数(f)とフッティングしたいデータ(xdata,ydata)を引数に与えるだけです。
 この時、関数 f の形は $$\mathrm{ydata} = \mathrm{f(xdata, *params)} + \epsilon$$ を想定されているので、指定されている形通りに与えます。
 なお、戻り値は、誤差が最小になるパラメータの配列 popt と、それらの推定される共分散行列 pcov(2-D)です。

In [4]:
from scipy.optimize import curve_fit

以下のパラメトリック関数を定義します。 $$\mathrm{OD}_{600} = Ce^{-kt} + b$$

In [5]:
def func(t,C,k,b): # 順番に注意。t(xdata)が最初。
    return  C * np.exp(-k*t) + b
In [6]:
# フィッティング
popt, _ = curve_fit(func, time, OD, method='dogbox')

最適化手法¶

参考:MathWorks
 上記のフィッティングは、最小二乗法に基づいていますが、解析的に求めることはできません。この curve_fit メソッドでは、以下の3つの手法が用意されています。

trf(Trust Region Reflective algorithm, 信頼領域法)¶

 反復法の1つで、基本的な概念は、シンプルな関数 $q$ で 点 $\mathbf{x}$ の近傍 $N$ (信頼領域)での目的関数 $f$ の挙動を近似するというもの。 $$\underset{\mathbf{s}}{\min}\{q(\mathbf{s}), \mathbf{s}\in N\}$$ ここで、$f(\mathbf{x}+\mathbf{s})<f(\mathbf{x})$ の場合、現在の点が $\mathbf{x}+\mathbf{s}$ に更新される。そうでない場合は現在の点は変更されず、信頼領域 $N$ は縮小され、テストステップの計算が繰り返される。なお、標準的な信頼領域法では、

  • 二次近似 $q$ は、$\mathbf{x}$ における $f$ のテイラー近似のはじめの $2$ 項によって決められる。
  • 近傍 $N$ は球体または楕円体。

dogbox(dogleg algorithm with rectangular trust regions)¶

 信頼領域法の一種。経路に沿って、信頼領域(rectangula)内で目的関数 $f$ を最小化する $\mathbf{x}$ を見つける。

lm(Levenberg-Marquardt algorithm)※default¶

 ニュートン法でヘッセ行列・またその逆行列が必要となるが、それらを用いるのは大変。
→ ヤコビ行列の積でヘッセ行列を近似しよう(ガウス・ニュートン法)
→ 最急降下法と組み合わせよう(初期は最急降下法、近傍でガウス・ニュートン法に切り替えて収束速度を上げる?)

In [7]:
optimal_func = lambda t: func(t,popt[0],popt[1],popt[2])
In [8]:
X = np.linspace(min(time), max(time), 1000)
Y = np.vectorize(optimal_func)(X)
In [9]:
plt.plot(X,Y, label="$\mathrm{OD}_{600} = Ce^{-kt}+b$")
plt.scatter(time, OD, label="data")
plt.title("The relationship between time and $\mathrm{OD}_{600}$")
plt.xlabel("time [s]")
plt.ylabel("$\mathrm{OD}_{600}$")
plt.legend()
plt.show()
In [10]:
popt[0],popt[1],popt[2]
Out[10]:
(0.6275861226226153, 0.032148117696153874, 0.27216089574449037)

各種パラメータを代入すると、以下の式になった。 $$\mathrm{OD}_{600} = 0.62e^{-0.32t}+0.27$$

In [11]:
print("RMSE: {:.3f}".format(np.sqrt(np.sum((OD-np.vectorize(optimal_func)(time))**2)/len(OD))))
RMSE: 0.022

補正¶

補正①¶

 まず考えられる補正方法として、プロトコルの2で菌液1.4 mLの吸光度をt=0 秒後の吸光度の値としたにも関わらず、t>0 では0.1 mg/mLのリゾチーム溶液が0.1 mL加えられており、条件が明らかに揃っていません。そこで、今回は単純にt=0秒後の吸光度の値を14/15倍にして補正します。

In [12]:
OD1 = [od*(14/15) if i==0 else od for i,od in enumerate(OD)]
In [13]:
# フィッティング
popt1, _ = curve_fit(func, time, OD1, method='dogbox')
In [14]:
optimal_func1 = lambda t: func(t,popt1[0],popt1[1],popt1[2])
In [15]:
Y1 = np.vectorize(optimal_func1)(X)
In [16]:
plt.plot(X,Y1, label="$\mathrm{OD}_{600} = Ce^{-kt}+b$")
plt.scatter(time, OD1, label="data")
plt.title("The relationship between time and $\mathrm{OD}_{600}$")
plt.xlabel("time [s]")
plt.ylabel("$\mathrm{OD}_{600}$")
plt.legend()
plt.show()
In [17]:
popt1[0],popt1[1],popt1[2]
Out[17]:
(0.5871856985136539, 0.028176701384466905, 0.26227771876439065)

各種パラメータを代入すると、以下の式になった。 $$\mathrm{OD}_{600} = 0.59e^{-0.028t}+0.26$$

In [18]:
print("RMSE: {:.3f}".format(np.sqrt(np.sum((OD1-np.vectorize(optimal_func1)(time))**2)/len(OD1))))
RMSE: 0.015

補正②¶

 続いて考えられるものとして、分光光度計の測定時間の誤差が挙げられます。というのも、分光光度計を押してから計測に数秒かかっているので、どのタイミングで測られたのかわかりません。(TAの方に聞いてもわかりませんでした笑)
 誤差があるのはt=0 秒後とt=10 秒後の間だけなので、初期値を取り除いてみます。

In [19]:
time2 = [t for i,t in enumerate(time) if i!=0]
OD2   = [od for i,od in enumerate(OD) if i!=0]
In [20]:
# フィッティング
popt2, _ = curve_fit(func, time2, OD2, method="dogbox")
In [21]:
optimal_func2 = lambda t: func(t,popt2[0],popt2[1],popt[2])
In [22]:
X2 = np.linspace(min(time2), max(time2), 1000)
Y2 = np.vectorize(optimal_func2)(X2)
In [23]:
plt.plot(X2,Y2, label="$\mathrm{OD}_{600} = Ce^{-kt}+b$")
plt.scatter(time2, OD2, label="data")
plt.title("The relationship between time and $\mathrm{OD}_{600}$")
plt.xlabel("time [s]")
plt.ylabel("$\mathrm{OD}_{600}$")
plt.legend()
plt.show()
In [24]:
print("RMSE: {:.3f}".format(np.sqrt(np.sum((OD2-np.vectorize(optimal_func2)(time2))**2)/len(OD2))))
RMSE: 0.030

各種パラメータを代入すると、以下の式になった。 $$\mathrm{OD}_{600} = 0.53e^{-0.022t}+0.24$$

 ここで、初期値以外の点を利用してフィッティングさせた関数を利用して、何秒誤差があったのかを調べます。解析的に逆関数を求めることができるので、上のフィッティング式の逆関数を求めます。

$$t = \frac{1}{k}\ln\frac{C}{\mathrm{OD}_{600}-b}$$
In [25]:
def inv_func2(od,C,k,b):
    return 1/k * np.log(C/(od-b))
In [26]:
inv_optimal_func2 = lambda od: inv_func2(od,popt2[0],popt2[1],popt2[2])
In [27]:
# この値をとるtを求めたい。
OD[0]
Out[27]:
0.939
In [28]:
optimal_func2(inv_optimal_func2(OD[0]))
Out[28]:
0.9688133478428795
In [29]:
# 誤差がある…値が小さいから??関数ミスった??
In [30]:
# パラメータ。確かに値は小さい。
popt2[0],popt2[1],popt2[2]
Out[30]:
(0.5322786897114126, 0.022362718093662307, 0.24234754790161073)
In [31]:
inv_optimal_func2(OD[0])
Out[31]:
-12.034290565630748
In [32]:
# -12秒は流石に嘘。
In [ ]:
 

  • « 細胞分子生物学Ⅱ 第7回
  • 細胞分子生物学Ⅰ 第7回 »
hidden
Table of Contents
Published
May 28, 2019
Last Updated
May 28, 2019
Category
生物化学実験Ⅰ
Tags
  • 3S 95
  • 生物化学実験Ⅰ 2
Contact
Other contents
  • Home
  • Blog
  • Front-End
  • Kerasy
  • Python-Charmers
  • Translation-Gummy
    • 3S - Shuto's Notes
    • MIT
    • Powered by Pelican. Theme: Elegant