# IPython Sihirli İfadeler

IPython, etkileşimli olarak rahat ve verimli çalışmayı sağlayan Python programlama diline özel tanımlanmış komutlar sunar. Sihirli ifadeler IPython çekirdeğine özgü oldukları için sadece Python ile çalışırlar. Jupyter’in desteklediği diğer diller (R, Julia, vd.) şimdilik bu özelliğe sahip değiller.IPython Notebook aslında Jupyter Notebook'un önceki verisyonu diyebiliriz.Jupyter özelliklerini IPython‘dan alınmış, ve yorumlanabilir herhangi birden fazla dili destekleyecek şekilde geliştirilmiştir. Hatta Jupyter adı, tasarlandığı üç temel dilin dolaylı bir kısaltmasıdır: **JU**lia, **PYT**hon ve **R** ve Jupiter gezegeninden esinlenilmiştir.

Jupyter IPythonın özelliklerini devralmıştır. Bu dokümanda Jupyter‘in devraldığı IPython özelliklerinden biri de sihirli ifadeler (line magic/cell magic) denen bazı kolaylaştırıcı, özelleşmiş komutlardan bahsedeceğim.Line Magic(satır sihirleri) % karakteri ile eklenir ve OS komut satırı çağrıları gibi çalışır, yalnızca geçerli satırın sonuna kadar uzanan komutlardır. Burada bağımsız değişkenler parantez veya tırnak işaretleri olmadan iletilir. Cell Magic(hücre sihirleri) önüne %% double eklenir ve yalnızca satırın geri kalanını değil, aynı zamanda altındaki satırları da bağımsız bir bağımsız değişken olarak alan işlevlerdir. Sihirli ifadeler sadece hücre bazında uygulanabilir, genele yayılamayacağını unutmamak gerekir.

% lsmagic komutu tüm sihirleri listelemek için kullanılır.

```python
%lsmagic
```

    Available line magics:
    %alias  %alias_magic  %autoawait  %autocall  %automagic  %autosave  %bookmark  %cd  %clear  %cls  %colors  %conda  %config  %connect_info  %copy  %ddir  %debug  %dhist  %dirs  %doctest_mode  %echo  %ed  %edit  %env  %gui  %hist  %history  %killbgscripts  %ldir  %less  %load  %load_ext  %loadpy  %logoff  %logon  %logstart  %logstate  %logstop  %ls  %lsmagic  %macro  %magic  %matplotlib  %mkdir  %more  %notebook  %page  %pastebin  %pdb  %pdef  %pdoc  %pfile  %pinfo  %pinfo2  %pip  %popd  %pprint  %precision  %prun  %psearch  %psource  %pushd  %pwd  %pycat  %pylab  %qtconsole  %quickref  %recall  %rehashx  %reload_ext  %ren  %rep  %rerun  %reset  %reset_selective  %rmdir  %run  %save  %sc  %set_env  %store  %sx  %system  %tb  %time  %timeit  %unalias  %unload_ext  %who  %who_ls  %whos  %xdel  %xmode

    Available cell magics:
    %%!  %%HTML  %%SVG  %%bash  %%capture  %%cmd  %%debug  %%file  %%html  %%javascript  %%js  %%latex  %%markdown  %%perl  %%prun  %%pypy  %%python  %%python2  %%python3  %%ruby  %%script  %%sh  %%svg  %%sx  %%system  %%time  %%timeit  %%writefile

    Automagic is ON, % prefix IS NOT needed for line magics.

**NOT:** Sihirli kelimeyi yazıp Shift-Tab bastığınızda bir yardım balonu çıkar.

Sihirli ifadelerin kullanımının özeti ve her bir komutun kısa açıklaması için **%quickref** komutunu, sihirli ifadelerin tam ve kapsamlı bir referansı için **%magic** komutunu kullanabilirsiniz.

Şimdi sihirli ifadelerden birkaçının nasıl kullanıldığını inceleyelim.

## Bazı Sihirli Komutların Kullanımına Örnekler

> **%run** komutu ile python kodunuzu ve jupyter notebook kodunuz çalıştırabiliriniz.

Başka bir notta yer alan kodumuz:

```python
%run C:\Users\Okul\Desktop\JupyterExample\example.ipynb
```

> **%store** komutu, değişkenleri iki farklı not defteri arasında geçirmenizi yani değişkenlerin kalıcı olmasını sağlar.Bu işlemi değişkenleri sabit diske kaydederek gerçekleştirir.

Başka bir notta yer alan kodumuz:

```python
%store -r cumle
print(cumle)
```

    Cümlemin kalıcı olmasını istiyorum.

> **%who** komutu defterde tanımlanmış değişkenlerin listelenmesini sağlar. Str (int,float,long,bool vb.) gibi bir parametre iletildiğinde yalnızca bu türdeki değişkenler listelenir. **%reset** komutu defterde tanımlanmış değişkenlerin listelenmesini sağlar.  Modüllerde tanımlanmış isimlere dokunmaz.

```python
JupyterNotebook = "IPython Notebook"
pi = 3.141592654
```

```python
%who str
```

    JupyterNotebook	 cumle

**NOT:**  %store komutu ile sabit diske kaydedilen değişken varsa listede gelir.

```python
%reset
```

    Once deleted, variables cannot be recovered. Proceed (y/[n])? Y

```python
%who str
```

    No variables match your requested type.

> **%%writefile** ve **%pycat** komutları sırasıyla bir hücrenin içeriğini dışa aktarılmasını yani içeriğin harici bir dosyaya kaydedilmesini ve harici bir komut dosyasının içeriğini gösterilmesini sağlar.

```python
%%writefile example2.py

import numpy
def append_if_not_exists(arr, x):
    if x not in arr:
        arr.append(x)

def some_useless_slow_function():
    arr = list()
    for i in range(10000):
        x = numpy.random.randint(0, 10000)
        append_if_not_exists(arr, x)
```

    Overwriting example2.py

Kodu çalıştırdıktan sonra otomatik olarak example2.py dosyası oluşturuldu.

```python
%pycat example2.py
```

Kodu çalıştırdıktan sonra harici bir pencerede kodu görüntüleyebildim.

> **%prun** programınızın her bir işleve ne kadar zaman harcadığını göstermeye yarar.Her dahili işlevin ifade içinde kaç kez çağrıldığını, her çağrının zamanını ve işlevin tüm çalışmalarının toplam süresini gösteren sıralı bir tablo elde edersiniz.

> **%time,%%time** ve **%timeıt,%%timeit** komutları zamanlama için yararlı komutlardır, yavaş kodunuz olduğunda ve sorunun nerede olduğunu belirlemeye çalışırken özellikle kullanışlıdır. %time,%%time size hücrenizdeki kodun tek bir çalıştırılma süresi hakkında bilgi verecektir. %timeıt,%%timeit ardından gelen Python komutunu bir döngü içinde birçok defa çalıştırır, sonra ortalama süreyi bulur. Bunu birkaç defa tekrarlar ve elde ettiği sonucu ortalama ve standart sapma olarak verir.

```python
%%time
import time
for _ in range(1000):
    time.sleep(0.01) # sleep for 0.01 seconds
```

    Wall time: 13.5 s

```python
import numpy
%timeit numpy.random.normal(size=100)
```

    5.49 µs ± 109 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

> **%qtconsole** komutu yeni bir terminal terminal açmak için kullanılır.Defterinizin akışına müdahale etmeden işlem yapmak istediğinizde, ayrı bir pencerede bir IPython penceresi açabilirsiniz.

```python
%qtconsole
```

Anlatmak istediğim sihirli sözcükler şimdilik bu kadar.
Birazda Ipythonun sahip olduğu zengin çıktılara göz atalım.

> **%%html** hücredeki HTML kodunu işler ve HTML belgesini ekrana verir. **%%javascript** html mantığıyla aynı şekilde js kodu da ekleyebiliriz.

```python
%%html
<!DOCTYPE html>
<html>
<head>
<style>
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}

tr:nth-child(even) {
  background-color: #dddddd;
}

.rendered_html tr, .rendered_html th, .rendered_html td {
    padding: 0.5em 9.9em;

</style>
</head>
<body>

<h2>HTML Table</h2>

<table>
  <tr>
    <th>Company</th>
    <th>Contact</th>
    <th>Country</th>
  </tr>
  <tr>
    <td>Alfreds Futterkiste</td>
    <td>Maria Anders</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Centro comercial Moctezuma</td>
    <td>Francisco Chang</td>
    <td>Mexico</td>
  </tr>
  <tr>
    <td>Ernst Handel</td>
    <td>Roland Mendel</td>
    <td>Austria</td>
  </tr>
  <tr>
    <td>Island Trading</td>
    <td>Helen Bennett</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Laughing Bacchus Winecellars</td>
    <td>Yoshi Tannamuri</td>
    <td>Canada</td>
  </tr>
  <tr>
    <td>Magazzini Alimentari Riuniti</td>
    <td>Giovanni Rovelli</td>
    <td>Italy</td>
  </tr>
</table>

</body>
</html>

```

<!DOCTYPE html>

HTML Table

    Company
    Contact
    Country

    Alfreds Futterkiste
    Maria Anders
    Germany

    Centro comercial Moctezuma
    Francisco Chang
    Mexico

    Ernst Handel
    Roland Mendel
    Austria

    Island Trading
    Helen Bennett
    UK

    Laughing Bacchus Winecellars
    Yoshi Tannamuri
    Canada

    Magazzini Alimentari Riuniti
    Giovanni Rovelli
    Italy

> **%latex** komutu ile tarayıcıda MathJax kullanılarak oluşturulan LaTeX'te matematiksel ifadelerin görüntülenmesi sağlanır.

```python
from IPython.display import Latex
```

```latex
%%latex
\begin{align}
\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\
\nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} & = 0
\end{align}
```

\begin{align}
\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\
\nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} & = 0
\end{align}

> **%matloblib** komutuyla, hangi arka yüzü kullanmak istediğinizi belirleyebilirsiniz. Birçok Jupyter defter kullanıcısı, defterin başında %matplotlib inline komutunu vermeyi tercih eder. Bu sayede ürettiğiniz matplotlib grafikleri defterin içine gömülür ve tam bir belge oluşturmanızı sağlar.

```python
%matplotlib --list
```

    Available matplotlib backends: ['tk', 'gtk', 'gtk3', 'wx', 'qt4', 'qt5', 'qt', 'osx', 'nbagg', 'notebook', 'agg', 'svg', 'pdf', 'ps', 'inline', 'ipympl', 'widget']

```python
%matplotlib notebook
```

**NOT:** matplotlib arka yüzünü bir oturum içinde sadece bir kere belirleyebilirsiniz. Aşağıdaki komutlar için çekirdeği tekrar başlatmanız gerekecektir. Çekirdeği, menü çubuğunda Kernel -> Kernel Restart seçerek, veya iki kere sıfıra basarak tekrar başlatabilirsiniz.

```python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
plt.figure()
x = np.linspace(0, 5 * np.pi, 1000)
for n in range(1, 4):
    plt.plot(np.sin(n * x))
plt.show()
```

    <IPython.core.display.Javascript object>


Table of Contents

1  Sao phải Quan tâm?

1.1  Khoa Học Dữ Liệu Không còn là Nó Trước đây (hoặc Nó của Ngày sau)

1.2  Trả lời những câu hỏi khác nhau

2  Khi nào  Quan hệ tương quan là  Quan hệ nhân quả

3  Thiên lệch

4  Ý tưởng chủ đạo

5  Tài liệu tham khảo

# Sao phải Quan tâm?

## Khoa Học Dữ Liệu Không còn là Nó Trước đây (hoặc Nó của Ngày sau)

Chuyên gia dữ liệu được lăng xê là  [Nghề nghiệp Quyến rũ nhất Thế kỉ 21](https://hbr.org/2012/10/data-scientist-the-sexiest-job-of-the-21st-century) bởi Harvard Business Review. Đây không phải là một diễn ngôn trống rỗng. Trong thập kỉ vừa qua, Chuyên Gia Dữ Liệu  thực sự tỏa sáng. Các chuyên gia AI sở hữu [mức lương không hề kém cạnh các siêu sao thể thao](https://www.economist.com/business/2016/04/02/million-dollar-babies). Theo đuổi tiền tài và danh vọng, hàng trăm các chuyên gia trẻ  lao đầu vào cuộc đua truy tìm kho báu cố giành danh hiệu chuyên gia dữ liệu sớm nhất. Ngành nghề mới như thể đang lên đồng. Một vài phương pháp giảng dạy tân tiến có thể giúp bạn trở thành một chuyên gia dữ liệu mà không phải nhìn dù chỉ một công thức toán học. Các chuyên gia tư vấn hứa hẹn triệu đô nếu công ty của bạn có thể hé mở tiềm năng dữ liệu. AI hoặc, ML, được gọi là nguồn điện mới và dữ liệu là mỏ dầu mới.

Trong khi ấy, chúng ta lãng quên các ngành khoa học xưa cũ  vốn vẫn sử dụng dữ liệu từ trước đến nay. Xưa nay các chuyên gia kinh tế cố trả lời đâu là tác động thực sự của giáo dục đối với thu nhập. Các chuyên gia thống kê y sinh cố tìm hiểu liệu chất béo bão hòa có làm tăng nguy cơ nhồi máu cơ tim. Các chuyên gia tâm lý nghiên cứu liệu những lời xác nhận có làm hôn nhân hạnh phúc hơn. Thành thật mà nói, khoa học dữ liệu không phải là một ngành khoa học mới. Bạn chỉ biết nhiều hơn về nó gần đây vì lượng thông tin marketing khổng lồ từ truyền thông.

Sử dụng phép so sánh của Jim Collins, hãy nghĩ về việc bạn đang chế một cốc bia đá lạnh yêu thích. Nếu bạn làm đúng cách, phần lớn chiếc cốc sẽ đầy bia với một lớp bọt dầy phía trên. Chiếc cốc ấy thật giống khoa học dữ liệu.
1. Nó là bia. Nền tảng thống kê đã được chứng minh giá trị hàng trăm năm qua.
2. Nó là bọt. Những bong bóng được dựng trên nền kì vọng viển vông và sẽ dần tan biến.

Thứ bọt này có thể biến mất nhanh hơn bạn nghĩ. Tạp chí "The Economist" từng viết:
> Những chuyên gia tư vấn từng dự đoán AI có tác động thay đổi thế giới cũng chính là những người tường thuật việc các nhà quản lý thực tế tại các công ty đang ngộ ra rằng AI rất khó triển khai, và cơn sốt đang nguội dần. Svetlana Sicular từ  công ty nghiên cứu Gartner nói rằng năm 2020 có thể là năm AI đổ đèo, trong mô hình "Vòng đời Ảo vọng" nổi tiếng. Các nhà đầu tư sực tỉnh khi đang chạy theo đám đông: một khảo sát các startup AI tại châu Âu của MMC, một quỹ đầu tư mạo hiểm, cho thấy thực tế 40% không hề dùng AI chút nào.

Giữa cơn lên đồng ấy, chúng ta, các chuyên gia dữ liệu, nên làm gì. Trước tiên, nếu bạn khôn ngoan, bạn sẽ học cách phớt lờ lớp bọt. Cái mà ta muốn thưởng thức là bia. Toán và thống kê vốn hữu ích muôn đời và sẽ chẳng thể bỗng dưng thoái trào ở đây. Thêm vào đó, hãy học những gì khiến công việc của bạn hữu ích và giá trị, không phải những công cụ hào nhoáng chẳng biết để làm gì. Cuối cùng hãy nhớ rằng không thể đi ngang về tắt. Kiến thức về toán và thống kê giá trị chính vì chúng khó để học và dùng. Nếu mọi người đều có khả năng sử dụng, thì nguồn cung thừa mứa sẽ hạ giá trị của chúng. Vì thế, hãy **khổ luyện** và học chúng nhiều nhất có thể!

## Trả lời những câu hỏi khác nhau
Vấn đề mà ML đang làm rất tốt là dự đoán. Ajay Agrawal, Joshua Gans và Avi Goldfarb viết trong cuốn sách "Cỗ máy Dự đoán": "Làn sóng trí tuệ nhân tạo thực ra không mang đến cho chúng ta trí tuệ mà một phần thiết yếu của trí tuệ - khả năng phán đoán". Chúng ta có thể tạo ra vô số điều kì diệu với "machine learning". Yêu cầu duy nhất là chúng ta đặt vấn đề trong khuôn khổ dự đoán. Muốn dịch tiếng Anh sang tiếng Bồ Đào Nha? Hãy xây dựng mô hình ML giúp dự đoán câu văn tiếng Bồ Đào Nha cho mỗi câu tiếng Anh. Muốn nhận diện khuôn mặt? Hãy xây dựng mô hình ML giúp dự đoán sự hiện diện của một khuôn mặt trong một phần bức tranh. Muốn sản xuất ô tô tự lái? Hãy xây dựng mô hình ML giúp dự đoán phương hướng bánh lái và áp lực lên chân phanh và chân ga dựa vào các tấm hình ngoại cảnh ô tô.

Tuy nhiên ML không phải liều thuốc vạn năng. Nó rất kì diệu trong một khuôn khổ nhất định và thất bại nếu dữ liệu dùng để dự đoán sai lệch đôi chút với dữ liệu xây dựng mô hình mà nó đã quen thuộc. Một ví dụ khác trong cuốn sách "Cỗ máy Dự đoán": "Trong nhiều ngành nghề, mức giá thấp tương quan với doanh số thấp. Ví dụ trong ngành khách sạn, giá thấp ngoài mùa du lịch và giá tăng cao khi nhu cầu đạt đỉnh điểm và các khách sạn kín phòng. Dựa vào dữ liệu, một mô hình dự đoán ngờ nghệch có thể đề xuất rằng việc tăng giá làm tăng mức độ phủ kín phòng.”

ML rất dở trong các vấn đề liên quan đến mối liên hệ ngược giữa nguyên nhân - kết quả. Chúng buộc chúng ta trả lời những câu hỏi "Nếu .. thì chuyện gì xảy ra", mà các chuyên gia kinh tế gọi là các giả tưởng. Điều gì xảy ra nếu thay vì mức giá niêm yết này cho một mặt hàng, tôi niêm yết mức giá khác? Điều gì xảy ra nếu thay vì chế độ ăn ít béo, tôi theo chế độ ăn ít đường? Nếu bạn làm việc cho một ngân hàng cung cấp tín dụng, bạn sẽ phải tìm hiểu liệu việc thay đổi tập khách hàng sẽ thay đổi doanh số như thế nào? Hoặc nếu bạn làm việc cho một chính quyền địa phương , bạn có thể sẽ phải tìm hiểu làm thế nào để cải thiện hệ thống giáo dục. Liệu bạn có nên phát máy tính bảng cho mỗi đứa trẻ cho hợp thời đại kỉ nguyên trí tuệ số? Hoặc liệu có nên xây các thư viện theo kiểu truyền thống? Dù bạn làm ngành nghề nào thì bạn cũng sẽ phải đối mặt với những câu hỏi kiểu như "chiến dịch marketing này sẽ mang tới bao nhiêu khách hàng mới?", "chương trình huấn luyện nhân viên này có làm tăng hiệu suất làm việc không?". Đáng tiếc cho ML, trong các ngành nghề truyền thống, các loại câu hỏi này phổ biến hơn các câu hỏi về dự đoán. Để trả lời chúng, chúng ta không thể dựa vào các dự đoán dựa trên quan hệ tương quan.

Trả lời các câu hỏi kiểu này khó hơn mọi người tưởng. Ai đó có thể nói đi nói lại "Quan hệ tương quan không phải Quan hệ nhân quả", "Quan hệ tương quan không phải Quan hệ nhân quả". Nhưng thực tế để giải thích nó tường tận cần nhiều hơn thế. Phần dưới đây sẽ trình bày **làm thể nào để quan hệ tương quan hé lộ quan hệ nhân quả**.

# Khi nào  Quan hệ tương quan là  Quan hệ nhân quả

Bằng trực giác, khá dễ hiểu tại sao quan hệ tương quan không phải quan hệ nhân quả. Nếu ai đó nói với bạn: trẻ học tốt hơn ở các trường phát máy tính bảng cho học sinh, bạn có thể phản biện rằng đó cũng là các trường giàu hơn. Vì thế họ có có điều kiện giảng dạy tốt hơn dù có phát máy tính bảng hay không. Vì thế chúng ta không thể kết luận rằng việc cung cấp máy tính bảng cho lớp học làm tăng kết quả học tập của trẻ. Chúng ta chỉ có thể nói rằng việc sử dụng máy tính bảng trong trường học tương quan với kết quả học tập cao.

```python
import pandas as pd
import numpy as np
from scipy.special import expit
import seaborn as sns
from matplotlib import pyplot as plt
from matplotlib import style

style.use("fivethirtyeight")

np.random.seed(123)
n = 100
học_phí = np.random.normal(1000, 300, n).round()
máy_tính_bảng = np.random.binomial(1, expit((học_phí - học_phí.mean()) / học_phí.std())).astype(bool)
điểm_enem = np.random.normal(200 - 50 * máy_tính_bảng + 0.7 * học_phí, 200)
điểm_enem = (điểm_enem - điểm_enem.min()) / điểm_enem.max()
điểm_enem *= 1000

data = pd.DataFrame(dict(điểm_enem=điểm_enem, học_phí=học_phí, máy_tính_bảng=máy_tính_bảng))
```

```python
plt.figure(figsize=(6,8))
sns.boxplot(y="điểm_enem", x="máy_tính_bảng", data=data).set_title(' Điểm ENEM theo việc trang bị Máy tính bảng trong Lớp học')
plt.show()
```

Để hiểu hơn vấn đề đơn giản này, hãy cùng bắt đầu với việc thiết lập một số kí hiệu. Đây là ngôn ngữ chung để nói về
 quan hệ nhân quả. Gọi \\(T_i\\) là lượng can thiệp đối với cá nhân i.

$
T_i=\begin{cases}
1 \ \text{nếu cá thể i tiếp nhận can thiệp}\\
0 \ \text{nếu ngược lại}\\
\end{cases}
$

Can thiệp  ở đây không nhất thiết là điều trị y tế hoặc bất cứ thứ gì liên quan đến lĩnh vực y tế. Thay vào đó, nó chỉ là một thuật ngữ chúng ta dùng để nói về một biện pháp/hành động/chương trình/sáng kiến mà chúng ta muốn tìm hiểu tính hiệu quả của nó. Trong trường hợp này, can thiệp là việc phát máy tính bảng cho học sinh.

Gọi biến \\(Y_i\\) là kết quả quan sát cho cá thể i.

$
Y_i
$

Kết quả là biến số chúng ta quan tâm. Chúng ta muốn biết liệu can thiệp có ảnh hưởng đến kết quả hay không.

Đây là điều thú vị. **Thách thức cơ bản của suy luận nhân quả** là chúng ta không bao giờ có thể quan sát cùng một cá thể đồng thời tiếp nhận hoặc không tiếp nhận can thiệp. Giống như việc chúng ta đi đến ngã rẽ và chỉ có thể biết điều gì chờ đón phía trước nếu chúng ta chọn đi một trong 2 con đường. Hãy nghĩ về thơ của Robert Frost [[Con đường chưa đi/The road not taken]](https://sites.google.com/site/thorobertfrost/)

```
Con đường chia hai ngả giữa rừng thu
Nhưng than ôi, đành phải theo một lối
Tôi làm kẻ lữ hành phân vân mãi
Đưa mắt nhìn xuống lối chạy về xa
Tới khúc cong khuất sau lùm cây bụi.
```

Tóm lại, chúng ta sẽ nói rất nhiều về các **kết quả tiềm năng**. Chúng tiềm năng vì chưa thực sự diễn ra. Thay vào đó, chúng cho ta biết điều gì có thể xảy đến nếu có can thiệp. Đôi khi chúng ta gọi kết quả tiềm năng diễn ra là hiện thực và kết quả tiềm năng không diễn ra là giả tưởng.

Chúng ta dùng chỉ số dưới để kí hiệu:

\\(Y_{0i}\\) là kết quả tiềm năng  của cá thể i không được can thiệp (đối chứng).

\\(Y_{1i}\\) là kết quả tiềm năng  của cùng cá thể i khi có can thiệp.

Lưu ý đôi khi chúng ta nhìn thấy các kết quả tiềm năng được kí hiệu dưới dạng hàm số \\(Y_i(t)\\). \\(Y_{0i}\\) có thể là \\(Y_i(0)\\) và \\(Y_{1i}\\) có thể là \\(Y_i(1)\\). Ở đây chúng ta sẽ chủ yếu dùng chỉ số dưới để kí hiệu.

Trong ví dụ máy tính bảng của chúng ta, \\(Y_{1i}\\) là kết quả học tập của học sinh i nếu được phát máy tính bảng dùng trong lớp học.  Bây giờ, nếu học sinh i được phát máy tính bảng, chúng ta quan sát thấy \\(Y_{1i}\\). Nếu không, \\(Y_{1i}\\) vẫn tồn tại trên lí thuyết nhưng chúng ta không thể quan sát. Trong trường hợp này nó là một kết quả tiềm năng (giả tưởng).

Với kết quả tiềm năng, chúng ta có thể định nghĩa hiệu ứng can thiệp cá thể:

 \\(Y_{1i} - Y_{0i}\\)

Tất nhiên, do thách thức cơ bản của suy luận nhân quả, chúng ta không bao giờ biết được hiệu ứng can thiệp cá thể vì chúng ta chỉ có thể quan sát được 1 trong 2 kết quả tiềm năng. Vì vậy, hãy tập trung ước lượng một đại lượng đơn giản hơn: **tác động can thiệp trung bình (Average Treatment Effect - ATE)**. Đại lượng này được định nghĩa như sau:

\\(ATE = E[Y_1 - Y_0]\\)

Trong đó E[...] là giá trị kì vọng. Một đại lượng dễ ước tính hơn nữa là **tác động can thiệp trung bình trên nhóm đươc can thiệp (Average Treatment Effect on the Treated- ATET)**:

\\(ATET = E[Y_1 - Y_0 | T=1]\\)

Chúng ta biết rằng sẽ không bao giờ có thể quan sát được cả 2 kết quả tiềm năng, nhưng để dễ hiểu hơn, hãy tạm giả thiết là ta có thể. Giả sử chúng ta thu thập dữ liệu cho 4 trường học. Chúng ta quan sát được từng trường có phát máy tính bảng cho học sinh hay không và kết quả học tập của học sinh. Ở đây việc phát máy tính bảng là can thiệp, vì thế \\(T=1\\) tương ứng với việc phát máy tính bảng cho học sinh. \\(Y\\) là điểm số.

```python
pd.DataFrame(dict(
    i= [1,2,3,4],
    y0=[500,600,800,700],
    y1=[450,600,600,750],
    t= [0,0,1,1],
    y= [500,600,600,750],
    te=[-50,0,-200,50],
))
```

      i
      y0
      y1
      t
      y
      te

      0
      1
      500
      450
      0
      500
      -50

      1
      2
      600
      600
      0
      600
      0

      2
      3
      800
      600
      1
      600
      -200

      3
      4
      700
      750
      1
      750
      50

\\(ATE\\) ở đây là trung bình cộng của cột cuối cùng (tác động can thiệp):

\\(ATE=(-50 + 0 - 200 + 50)/4 = -50\\)

\\(ATET\\) ở đây là trung bình cộng của cột cuối cùng khi mà \\(T=1\\):

\\(ATET=(- 200 + 50)/2 = -75\\)

Điều này nghĩa là với các trường nhận can thiệp, tính trung bình, máy tính bảng làm giảm kết quả học tập của học sinh 75 điểm. Tất nhiên chúng ta không bao giờ biết rõ như thế. Bảng số liệu trên đây thực tế trông giống thế này (NaN là các giá trị chưa biết):

```python
pd.DataFrame(dict(
    i= [1,2,3,4],
    y0=[500,600,np.nan,np.nan],
    y1=[np.nan,np.nan,600,750],
    t= [0,0,1,1],
    y= [500,600,600,750],
    te=[np.nan,np.nan,np.nan,np.nan],
))
```

      i
      y0
      y1
      t
      y
      te

      0
      1
      500.0
      NaN
      0
      500
      NaN

      1
      2
      600.0
      NaN
      0
      600
      NaN

      2
      3
      NaN
      600.0
      1
      600
      NaN

      3
      4
      NaN
      750.0
      1
      750
      NaN

# Thiên lệch

Thiên lệch là nguyên nhân khiến quan hệ tương quan khác với quan hệ nhân quả. May mắn là nó khá dễ hiểu bằng trực giác thông thường. Quay trở lại với ví dụ máy tính bảng trong lớp học của chúng ta. Nếu ai đó khẳng định rằng học sinh học tập tốt hơn tại các trường phát máy tính bảng; chúng ta có thể phản biện rằng các trường đó có điều kiện giảng dạy tốt hơn dù có phát máy tính bảng hay không. Các trường này nhiều khả năng giàu  hơn các trường khác và vì thế trả lương giáo viên cao hơn, trang bị phòng học tốt hơn ,... Nói cách khác, trường học được can thiệp (phát máy tính bảng) không thể so sánh với các trường đối chứng.

Sử dụng kí hiệu, kết quả tiềm năng \\(Y_0\\) của trường được can thiệp khác với \\(Y_0\\) của trường đối chứng. Lưu ý \\(Y_0\\) của trường được can thiệp **là giả tưởng**. Chúng ta không quan sát được nó, nhưng chúng ta có thể suy luận về nó. Trong trường hợp cụ thể này, chúng ta có thể đi xa hơn và khái quát cách thế giới vận hành. Chúng ta có thể nói rằng \\(Y_0\\) của trường được can thiệp lớn hơn \\(Y_0\\) của trường đối chứng. Vì trường đủ tiền phát máy tính bảng cho học sinh thì cũng đủ tiền để trang trải các điều kiện vật chất khác giúp trẻ đạt điểm cao hơn. Hãy dừng lại ở đây một chút. Phải mất chút thời gian để quen với cách nói về các kết quả tiềm năng. Hãy thử đọc lại đoạn văn này để chắc chắn rằng bạn thực sự hiểu nó.

Bây giờ chúng ra có thể chứng minh bằng toán học tại sao quan hệ tương quan không phải quan hệ nhân quả. Quan hệ tương quan được đo bởi \\(E[Y|T=1] - E[Y|T=0]\\). Trong ví dụ của chúng ta, nó là hiệu giữa điểm thi trung bình của các trường phát máy tính bảng và điểm thi trung bình của các trường không phát máy tính bảng. Trong khi đó, quan hệ nhân quả được đo bởi \\(E[Y_1 - Y_0]\\).

Để biết chúng liên quan đến nhau như thế nào, hãy dùng công thức tính quan hệ tương quan và thay các kết quả quan sát bằng các kết quả tiềm năng. Với các trường được can thiệp, kết quả quan sát là \\(Y_1\\). Với các trường đối chứng, kết quả quan sát là \\(Y_0\\).

$
E[Y|T=1] - E[Y|T=0] = E[Y_1|T=1] - E[Y_0|T=0]
$

Bây giờ hãy cộng rồi trừ kết quả giả tưởng \\(E[Y_0|T=1]\\). Nó cho ta biết điều gì xảy ra với kết quả của các trường được can thiệp nếu chúng không nhận can thiệp.

$
E[Y|T=1] - E[Y|T=0] = E[Y_1|T=1] - E[Y_0|T=0] + E[Y_0|T=1] - E[Y_0|T=1]
$

Cuối cùng, chúng ta sắp xếp lại và nhóm các kì vọng và thu được:

$
E[Y|T=1] - E[Y|T=0] = \underbrace{E[Y_1 - Y_0|T=1]}_{ATET} + \underbrace{\{ E[Y_0|T=1] - E[Y_0|T=0] \}}_{THIÊN LỆCH}
$

Công thức toán học đơn giản này có thể giúp ta giải quyết các vấn đề liên quan đến các câu hỏi về nhân quả. Tôi không thể nhấn mạnh hết việc hiểu ngọn ngành công thức này quan trọng đến nhường nào. Nếu bạn bị ép xăm một thứ gì đó lên cánh tay, phương trình này có thể là một lựa chọn tốt. Nó là một thứ gì đó cần giữ chặt lấy và cần thực sự hiểu, giống như một khẩu quyết có thể diễn giải theo hàng trăm cách khác nhau. Thực tế, hãy xem xét kĩ lưỡng hơn và tìm hiểu một vài hàm ý của nó.

Trước hết, phương trình này giải thích tại sao quan hệ tương quan không phải quan hệ nhân quả. Chúng ta có thể thấy quan hệ tương quan bao gồm tác động can thiệp trên nhóm được can thiệp cộng với phần thiên lệch. **Thiên lệch được tính bằng khác biệt giữa nhóm được can thiệp và nhóm đối chứng trước khi có can thiệp,  nếu tạm giả thiết cả 2 nhóm đều chưa từng nhận can thiệp**. Bây giờ ta có thể nói chính xác vì sao ta nghi ngờ nếu ai đó nói rằng việc dùng máy tính bảng trong lớp học tăng cường kết quả học tập. Chúng ta cho rằng, trong trường hợp này, \\(E[Y_0|T=0] < E[Y_0|T=1]\\), nghĩa là, các trường có tiền để phát máy tính bảng vốn dĩ hoạt động tốt hơn các trường khác, **dù có can thiệp phát máy tính bảng hay không**.

Tại sao lại có điều này? Chúng ta sẽ nói kĩ hơn khi về vấn đề này khi đề cập đến nhiễu (trong các bài tới), nhưng bây giờ bạn có thể nghĩ về việc thiên lệch xảy ra do nhiều yếu tố chúng ta không thể kiếm soát nhưng tác động đến can thiệp. Kết quả là các trường được can thiệp và đối chứng không chỉ khác nhau về việc phát máy tính bảng. Chúng cũng có thể khác biệt về học phí, địa điểm, giáo viên ... Để chúng ta có thể nói rằng việc dùng máy tính bảng trong lớp học làm tăng kết quả học tập, chúng ta cần đảm bảo các trường học theo đuổi và không theo đuổi chính sách này tương tự nhau về mặt bằng chung.

```python
plt.figure(figsize=(10,6))
sns.scatterplot(x="học_phí", y="điểm_enem", hue="máy_tính_bảng", data=data, s=70).set_title('Điểm ENEM theo Học phí')
plt.show()
```

Chúng ta cũng có thể nêu điều kiện cần để quan hệ tương quan đồng thời là quan hệ nhân quả. **Nếu \\(E[Y_0|T=0] = E[Y_0|T=1]\\), thì, quan hệ tương quan  LÀ QUAN HỆ NHÂN QUẢ!** Để hiểu điều này không chỉ cần ghi nhớ công thức. Có một lập luận khá trực quan ở đây. Nói rằng \\(E[Y_0|T=0] = E[Y_0|T=1]\\) nghĩa là nói rằng nhóm được can thiệp và nhóm đối chứng khá tương đồng khi không tính đến can thiệp. Hoặc, trong trường hợp giả thiết nhóm được can thiệp chưa được can thiệp, nếu ta có thể quan sát \\(Y_0\\), thì kết quả của nó phải tương tự như nhóm đối chứng. Về mặt toán học, phần thiên lệch bị giản ước.

$
E[Y|T=1] - E[Y|T=0] = E[Y_1 - Y_0|T=1]
$

Và nếu nhóm được can thiệp và nhóm đối chứng chỉ khác nhau ở can thiệp, tức là \\(E[Y_0|T=0] = E[Y_0|T=1]\\), ta có:

$
\begin{align}
E[Y_1 - Y_0|T=1] &= E[Y_1|T=1] - E[Y_0|T=1] \\\\
    &= E[Y_1|T=1] - E[Y_0|T=0] \\\\
    &= E[Y|T=1] - E[Y|T=0]
\end{align}
$

Vì thế trong trường hợp này, **hiệu của các giá trị trung bình LÀ tác động nhân quả**:

$
E[Y|T=1] - E[Y|T=0] = ATE = ATET
$

Điều này khá quan trọng, nên chúng ta hãy nhắc lại bằng một biểu đồ thú vị. Nếu chúng ta chỉ đơn thuần so sánh giá trị trung bình giữa nhóm được can thiệp và đối chứng, chúng ta thu được đồ thị sau (các chấm xanh không được nhận can thiệp, phát máy tính bảng):

Lưu ý sự khác biệt trong kết quả giữa hai nhóm được tạo nên bởi 2 nguyên nhân:

1. Tác động can thiệp. Sự gia tăng điểm số thuần tuý do phát máy tính bảng cho học sinh.
2. Các khác biệt khác giữa nhóm được can thiệp và đối chứng KHÔNG được tạo ra bởi bản thân can thiệp. Trong trường hợp này, nhóm được can thiệp và đối chứng khác nhau ở chỗ nhóm được can thiệp có học phí cao hơn hẳn. Một số khác biệt trong kết quả thi có thể do tác động tích cực của học phí cao với chất lượng giáo dục.

Chúng ta chỉ có thể biết được tác động thực sự của can thiệp nếu ta có quyền lực siêu nhiên quan sát được kết quả tiềm năng, giống như đồ thị bên trái dưới đây. Hiệu ứng can thiệp cá thể là hiệu giữa kết quả của mỗi cá nhân và kết quả lý thuyết của cùng cá nhân đó nếu thay đổi việc phân bổ can thiệp. Chúng là các  kết quả giả tưởng và được biểu diễn bằng màu nhạt hơn.

Ở đồ thị bên trái, ta biểu diễn thiên lệch được nhắc đến ở phần trước. Chúng ta thu được thiên lệch nếu chúng ta không chỉ định can thiệp cho bất kì ai. Trong trường hợp này, chúng ta chỉ còn kết quả tiềm năng \\(Y_0\\). Sau đó, ta theo dõi xem nhóm được can thiệp và đối chứng khác nhau như thế nào. Nếu chúng khác biệt, nhân tố nào đó ngoài can thiệp gây ra khác biệt giữa 2 nhóm. Nhân tố nào đó ở đây là nguyên nhân thiên lệch và gây nhiễu tác động can thiệp trong thực tế.

Bây giờ, hãy so sánh nó với tình huống giả tưởng rằng không có thiên lệch. Giả sử máy tính bảng được phát ngẫu nhiên tại các trường học. Trong các tình huống này, các trường giàu và trường nghèo đều nhận được can thiệp với xác suất như nhau. Can thiệp sẽ phân bổ đồng đều theo học phí.

Trong trường hợp này, hiệu giữa kết quả của nhóm được can thiệp và đối chứng LÀ tác động nhân quả trung bình. Điều này xảy ra và không có nguyên nhân nào khác gây ra khác biệt giữa nhóm được can thiệp và đối chứng ngoài bản thân can thiệp. Vì thế mọi sự khác biệt đều có thể gán cho can thiệp. Nói cách khác là không có thiên lệch.

Nếu chúng ta sắp xếp để không ai được nhận can thiệp, và vì thế chúng ta chỉ quan sát được các \\(Y_0\\), chúng ta sẽ không thấy khác biệt giữa nhóm được can thiệp và đối chứng. Đây là mấu chốt của suy luận nhân quả. Tìm kiếm phương pháp thông minh để loại bỏ  thiên lệch  rồi từ đó quy kết mọi khác biệt giữa nhóm được can thiệp và đối chứng là do tác động can thiệp trung bình.

Đây là nhiệm vụ "khủng" của suy luận nhân quả. Nó đòi hỏi những phương thức thông mình để loại bỏ thiên lệch, làm cho các đối tượng nhận can thiệp và đối chứng tương đương và từ đó mọi khác biệt giữa chúng có thể gán cho tác động can thiệp trung bình. Mục đích sau cùng của suy luận nhân quả là khám phá cách thế giới vận hành, loại bỏ ảo giác và cách diễn giải sai lệch. Và bây giờ chúng ta hiểu điều này, chúng ta có thể tiến tới học cách phương pháp quyền lực giúp loại bỏ thiên lệch, vũ khí của những chiến binh Quả cảm Chân chính giúp xác định tác động nhân quả.

# Ý tưởng chủ đạo

Trên đây chúng ta đã nhận thấy quan hệ tương quan không phải quan hệ nhân quả. Quan trọng hơn, chúng ta thấy rõ tại sao không phải vậy và làm thế nào để quan hệ tương quan trở thành quan hệ nhân quả. Chúng ta cũng đã tìm hiểu công thức kết quả tiềm năng như là một phương pháp để gói gọn vấn đề suy luận nhân quả. Cùng với đó, chúng ta tìm hiểu thống kê trong 2 tình huống hiện thực loại trừ nhau: có và không có can thiệp. Đáng tiếc là chúng ta chỉ có thể quan sát và đo lường 1 trong 2 tình huống đó, và đây là thách thức cơ bản của suy luận nhân quả.

Trong các bài tới, chúng ta sẽ tìm hiểu một số kĩ thuật cơ bản để ước lượng tác động nhân quả, bắt đầu từ tiêu chuẩn vàng nhờ vào thử nghiệm ngẫu nhiên. Tôi cũng sẽ tổng kết một số khái niệm thống kê cần dùng. Tôi kết thúc bài viết này với một cuộc thoại thường được trích dẫn trong các lớp học suy luận nhân quả từ series phim Kungfu:

```
'Điều gì xảy đến với mỗi đời người đều đã được định sẵn. Ai bước qua cuộc đời này cũng đều theo số phận an bài mà thôi.' -Caine
'Đúng vậy, nhưng mỗi người lại toàn quyền sống theo cách mình chọn lựa. Tưởng chừng mâu thuẫn, nhưng cả hai đều là chân lý. - Trưởng lão
```

# Tài liệu tham khảo

Tôi muốn dành loạt bài viết này để vinh danh Joshua Angrist, Alberto Abadie and Christopher Walters vì khóa học Kinh tế lượng tuyệt cú mèo của họ. Phần lớn ý tưởng trong loạt bài này được lấy từ các bài giảng của họ được tổ chức bởi Hiệp hội Kinh tế Mĩ.  Theo dõi các bài giảng này là những gì tôi làm trong suốt năm 2020 khó nhằn.
* [Kinh tế lượng với dữ liệu chéo](https://www.aeaweb.org/conference/cont-ed/2017-webcasts)
* [Luyện chưởng Kinh tế lượng Gần như Vô hại](https://www.aeaweb.org/conference/cont-ed/2020-webcasts)

Tôi cũng muốn giới thiệu cuốn sách lý thú của Angrist. Chúng cho tôi thấy Kinh tế lượng, hoặc 'Lượng theo cách họ gọi không chỉ vô cùng hữu ích mà còn rất vui.

* [Kinh tế lượng Gần như Vô hại](https://www.mostlyharmlesseconometrics.com/)
* [Luyện chưởng 'Lượng](https://www.masteringmetrics.com/)

Tài liệu tham khảo cuối cùng của tôi là cuốn sách của Miguel Hernan and Jamie Robins. Nó là người bạn đồng hành tin cậy với tôi khi trả lời những câu hỏi nhân quả khó nhằn.

* [Sách Suy Luận Nhân Quả](https://www.hsph.harvard.edu/miguel-hernan/causal-inference-book/)

Phép so sánh cốc bia trong bài này được lấy từ [Đầu tư Chứng khoán](https://jlcollinsnh.com/2012/04/15/stocks-part-1-theres-a-major-market-crash-coming-and-dr-lo-cant-save-you/), viết bởi JL Colins. Đây là loạt bài phải đọc cho bất kì ai muốn đầu tư hiệu quả.

# Bảng Từ Viết tắt
|Viết tắt| Tiếng Anh | Tiếng Việt |
| --- | --- | --- |
|AI|Artificial Intelligence|Trí tuệ Nhân tạo|
|ATET|Average Treatment Effect on the Treated|Tác động Can thiệp Trung bình trên Nhóm được Can thiệp|
|ML|Machine Learning|Học Máy|

# Bảng Thuật ngữ
| Thuật ngữ | Tiếng Anh |
| --- | --- |
|chuyên gia dữ liệu|data scientist|
|dự đoán|predict, prediction|
|giả tưởng|counterfactual|
|hiện thực|factual|
|hiệu ứng can thiệp cá thể|individual treatment effect|
|khoa học dữ liệu|data science|
|kinh tế lượng|econometrics|
|kết quả|outcome|
|kết quả giả tưởng|counterfactual outcome|
|kết quả quan sát|observed outcome|
|kết quả tiềm năng|potential outcome|
|nhiễu|confounding|
|nhóm được can thiệp|treatment group, test group|
|nhóm đối chứng|control group, untreated group|
|phần thiên lệch|bias term|
|quan hệ nhân quả|causality, causation, causal relationship|
|quan hệ tương quan|association, correlation|
|quan sát|observe, observation|
|suy luận nhân quả|causal inference, causal reasoning|
|thiên lệch|bias|
|thử nghiệm ngẫu nhiên|randomised trial|
|trí tuệ nhân tạo|artificial intelligence|
|tác động can thiệp|treatment effect, treatment impact|
|tác động can thiệp trung bình|average treatment effect|
|tác động can thiệp trung bình trên nhóm đươc can thiệp|average treatment effect on the treated|
|tác động can thiệp trên nhóm được can thiệp|treatment effect on the treated|
|tác động nhân quả trung bình|average causal effect|
|được can thiệp|treated|
|đối chứng|untreated, non-treated|


## Release the Kraken!

```python
# The next library we're going to look at is called Kraken, which was developed by Université
# PSL in Paris. It's actually based on a slightly older code base, OCRopus. You can see how the
# flexible open-source licenses allow new ideas to grow by building upon older ideas. And, in
# this case, I fully support the idea that the Kraken - a mythical massive sea creature - is the
# natural progression of an octopus!
#
# What we are going to use Kraken for is to detect lines of text as bounding boxes in a given
# image. The biggest limitation of tesseract is the lack of a layout engine inside of it. Tesseract
# expects to be using fairly clean text, and gets confused if we don't crop out other artifacts.
# It's not bad, but Kraken can help us out be segmenting pages. Lets take a look.
#
# Please note that Kraken is only supported on Linux and Mac OS X, it is not supported on Windows.
# Documentation and Installation Notes can be found at: https://pypi.org/project/kraken/
```

```python
!pip install --pre coremltools
```

    Requirement already satisfied: coremltools in c:\users\mosae\anaconda3\lib\site-packages (5.0)
    Requirement already satisfied: sympy in c:\users\mosae\anaconda3\lib\site-packages (from coremltools) (1.8)
    Requirement already satisfied: numpy>=1.14.5 in c:\users\mosae\anaconda3\lib\site-packages (from coremltools) (1.21.2)
    Requirement already satisfied: protobuf>=3.1.0 in c:\users\mosae\anaconda3\lib\site-packages (from coremltools) (3.19.1)
    Requirement already satisfied: tqdm in c:\users\mosae\anaconda3\lib\site-packages (from coremltools) (4.59.0)
    Requirement already satisfied: packaging in c:\users\mosae\anaconda3\lib\site-packages (from coremltools) (20.9)
    Requirement already satisfied: pyparsing>=2.0.2 in c:\users\mosae\anaconda3\lib\site-packages (from packaging->coremltools) (2.4.7)
    Requirement already satisfied: mpmath>=0.19 in c:\users\mosae\anaconda3\lib\site-packages (from sympy->coremltools) (1.2.1)

    WARNING: You are using pip version 21.2.4; however, version 21.3.1 is available.
    You should consider upgrading via the 'c:\users\mosae\anaconda3\python.exe -m pip install --upgrade pip' command.

```python
# First, we'll take a look at the kraken module itself
import kraken
help(kraken)
```

    Help on package kraken:

    NAME
        kraken - entry point for kraken functionality

    PACKAGE CONTENTS
        binarization
        ketos
        kraken
        lib (package)
        linegen
        pageseg
        repo
        rpred
        serialization
        transcribe

    DATA
        absolute_import = _Feature((2, 5, 0, 'alpha', 1), (3, 0, 0, 'alpha', 0...
        division = _Feature((2, 2, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 1310...
        print_function = _Feature((2, 6, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0)...

    FILE
        c:\users\mosae\anaconda3\lib\site-packages\kraken\__init__.py

```python
# There isn't much of a discussion here, but there are a number of sub-modules that look
# interesting. I spend a bit of time on their website, and I think the pageseg module, which
# handles all of the page segmentation, is the one we want to use. Lets look at it
from kraken import pageseg
help(pageseg)
```

```python
# So it looks like there are a few different functions we can call, and the segment
# function looks particularly appropriate. I love how expressive this library is on the
# documentation front -- I can see immediately that we are working with PIL.Image files,
# and the author has even indicated that we need to pass in either a binarized (e.g. '1')
# or grayscale (e.g. 'L') image. We can also see that the return value is a dictionary
# object with two keys, "text_direction" which will return to us a string of the
# direction of the text, and "boxes" which appears to be a list of tuples, where each
# tuple is a box in the original image.
#
# Lets try this on the image of text. I have a simple bit of text in a file called
# two_col.png which is from a newspaper on campus here
from PIL import Image
im=Image.open("download.png")
# Lets display the image inline
display(im)
# Lets now convert it to black and white and segment it up into lines with kraken
bounding_boxes=pageseg.segment(im.convert('1'))['boxes']
# And lets print those lines to the screen
print(bounding_boxes)
```

```python
# Ok, pretty simple two column text and then a list of lists which are the bounding boxes of
# lines of that text. Lets write a little routine to try and see the effects a bit more
# clearly. I'm going to clean up my act a bit and write real documentation too, it's a good
# practice
def show_boxes(img):
    '''Modifies the passed image to show a series of bounding boxes on an image as run by kraken

    :param img: A PIL.Image object
    :return img: The modified PIL.Image object
    '''
    # Lets bring in our ImageDraw object
    from PIL import ImageDraw
    # And grab a drawing object to annotate that image
    drawing_object=ImageDraw.Draw(img)
    # We can create a set of boxes using pageseg.segment
    bounding_boxes=pageseg.segment(img.convert('1'))['boxes']
    # Now lets go through the list of bounding boxes
    for box in bounding_boxes:
        # An just draw a nice rectangle
        drawing_object.rectangle(box, fill = None, outline ='red')
    # And to make it easy, lets return the image object
    return img

# To test this, lets use display
display(show_boxes(Image.open("download.png")))
```

```python
# Not bad at all! It's interesting to see that kraken isn't completely sure what to do with this
# two column format. In some cases, kraken has identified a line in just a single column, while
# in other cases kraken has spanned the line marker all the way across the page. Does this matter?
# Well, it really depends on our goal. In this case, I want to see if we can improve a bit on this.
#
# So we're going to go a bit off script here. While this week of lectures is about libraries, the
# goal of this last course is to give you confidence that you can apply your knowledge to actual
# programming tasks, even if the library you are using doesn't quite do what you want.
#
# I'd like to pause the video for the moment and collect your thoughts. Looking at the image above,
# with the two column example and red boxes, how do you think we might modify this image to improve
# kraken's ability to text lines?
```

```python
# Thanks for sharing your thoughts, I'm looking forward to seeing the breadth of ideas that everyone
# in the course comes up with. Here's my partial solution -- while looking through the kraken docs on
# the pageseg() function I saw that there are a few parameters we can supply in order to improve
# segmentation. One of these is the black_colseps parameter. If set to True, kraken will assume that
# columns will be separated by black lines. This isn't our case here, but, I think we have all of the
# tools to go through and actually change the source image to have a black separator between columns.
#
# The first step is that I want to update the show_boxes() function. I'm just going to do a quick
# copy and paste from the above but add in the black_colseps=True parameter
def show_boxes(img):
    '''Modifies the passed image to show a series of bounding boxes on an image as run by kraken

    :param img: A PIL.Image object
    :return img: The modified PIL.Image object
    '''
    # Lets bring in our ImageDraw object
    from PIL import ImageDraw
    # And grab a drawing object to annotate that image
    drawing_object=ImageDraw.Draw(img)
    # We can create a set of boxes using pageseg.segment
    bounding_boxes=pageseg.segment(img.convert('1'), black_colseps=True)['boxes']
    # Now lets go through the list of bounding boxes
    for box in bounding_boxes:
        # An just draw a nice rectangle
        drawing_object.rectangle(box, fill = None, outline ='red')
    # And to make it easy, lets return the image object
    return img
```

```python
# The next step is to think of the algorithm we want to apply to detect a white column separator.
# In experimenting a bit I decided that I only wanted to add the separator if the space of was
# at least 25 pixels wide, which is roughly the width of a character, and six lines high. The
# width is easy, lets just make a variable
char_width=25
# The height is harder, since it depends on the height of the text. I'm going to write a routine
# to calculate the average height of a line
def calculate_line_height(img):
    '''Calculates the average height of a line from a given image
    :param img: A PIL.Image object
    :return: The average line height in pixels
    '''
    # Lets get a list of bounding boxes for this image
    bounding_boxes=pageseg.segment(img.convert('1'))['boxes']
    # Each box is a tuple of (top, left, bottom, right) so the height is just top - bottom
    # So lets just calculate this over the set of all boxes
    height_accumulator=0
    for box in bounding_boxes:
        height_accumulator=height_accumulator+box[3]-box[1]
        # this is a bit tricky, remember that we start counting at the upper left corner in PIL!
    # now lets just return the average height
    # lets change it to the nearest full pixel by making it an integer
    return int(height_accumulator/len(bounding_boxes))

# And lets test this with the image with have been using
line_height=calculate_line_height(Image.open("readonly/two_col.png"))
print(line_height)
```

```python
# Ok, so the average height of a line is 31.
# Now, we want to scan through the image - looking at each pixel in turn - to determine if there
# is a block of whitespace. How bit of a block should we look for? That's a bit more of an art
# than a science. Looking at our sample image, I'm going to say an appropriate block should be
# one char_width wide, and six line_heights tall. But, I honestly just made this up by eyeballing
# the image, so I would encourage you to play with values as you explore.
# Lets create a new box called gap box that represents this area
gap_box=(0,0,char_width,line_height*6)
gap_box
```

```python
# It seems we will want to have a function which, given a pixel in an image, can check to see
# if that pixel has whitespace to the right and below it. Essentially, we want to test to see
# if the pixel is the upper left corner of something that looks like the gap_box. If so, then
# we should insert a line to "break up" this box before sending to kraken
#
# Lets call this new function gap_check
def gap_check(img, location):
    '''Checks the img in a given (x,y) location to see if it fits the description
    of a gap_box
    :param img: A PIL.Image file
    :param location: A tuple (x,y) which is a pixel location in that image
    :return: True if that fits the definition of a gap_box, otherwise False
    '''
    # Recall that we can get a pixel using the img.getpixel() function. It returns this value
    # as a tuple of integers, one for each color channel. Our tools all work with binarized
    # images (black and white), so we should just get one value. If the value is 0 it's a black
    # pixel, if it's white then the value should be 255
    #
    # We're going to assume that the image is in the correct mode already, e.g. it has been
    # binarized. The algorithm to check our bounding box is fairly easy: we have a single location
    # which is our start and then we want to check all the pixels to the right of that location
    # up to gap_box[2]
    for x in range(location[0], location[0]+gap_box[2]):
        # the height is similar, so lets iterate a y variable to gap_box[3]
        for y in range(location[1], location[1]+gap_box[3]):
            # we want to check if the pixel is white, but only if we are still within the image
            if x < img.width and y < img.height:
                # if the pixel is white we don't do anything, if it's black, we just want to
                # finish and return False
                if img.getpixel((x,y)) != 255:
                    return False
    # If we have managed to walk all through the gap_box without finding any non-white pixels
    # then we can return true -- this is a gap!
    return True
```

```python
# Alright, we have a function to check for a gap, called gap_check. What should we do once
# we find a gap? For this, lets just draw a line in the middle of it. Lets create a new function
def draw_sep(img,location):
    '''Draws a line in img in the middle of the gap discovered at location. Note that
    this doesn't draw the line in location, but draws it at the middle of a gap_box
    starting at location.
    :param img: A PIL.Image file
    :param location: A tuple(x,y) which is a pixel location in the image
    '''
    # First lets bring in all of our drawing code
    from PIL import ImageDraw
    drawing_object=ImageDraw.Draw(img)
    # next, lets decide what the middle means in terms of coordinates in the image
    x1=location[0]+int(gap_box[2]/2)
    # and our x2 is just the same thing, since this is a one pixel vertical line
    x2=x1
    # our starting y coordinate is just the y coordinate which was passed in, the top of the box
    y1=location[1]
    # but we want our final y coordinate to be the bottom of the box
    y2=y1+gap_box[3]
    drawing_object.rectangle((x1,y1,x2,y2), fill = 'black', outline ='black')
    # and we don't have anything we need to return from this, because we modified the image
```

```python
# Now, lets try it all out. This is pretty easy, we can just iterate through each pixel
# in the image, check if there is a gap, then insert a line if there is.
def process_image(img):
    '''Takes in an image of text and adds black vertical bars to break up columns
    :param img: A PIL.Image file
    :return: A modified PIL.Image file
    '''
    # we'll start with a familiar iteration process
    for x in range(img.width):
        for y in range(img.height):
            # check if there is a gap at this point
            if (gap_check(img, (x,y))):
                # then update image to one which has a separator drawn on it
                draw_sep(img, (x,y))
    # and for good measure we'll return the image we modified
    return img

# Lets read in our test image and convert it through binarization
i=Image.open("readonly/two_col.png").convert("L")
i=process_image(i)
display(i)

#Note: This will take some time to run! Be patient!
```

```python
# Not bad at all! The effect at the bottom of the image is a bit unexpected to me, but it makes
# sense. You can imagine that there are several ways we might try and control this. Lets see how
# this new image works when run through the kraken layout engine
display(show_boxes(i))
```

```python
# Looks like that is pretty accurate, and fixes the problem we faced. Feel free to experiment
# with different settings for the gap heights and width and share in the forums. You'll notice though
# method we created is really quite slow, which is a bit of a problem if we wanted to use
# this on larger text. But I wanted to show you how you can mix your own logic and work with
# libraries you're using. Just because Kraken didn't work perfectly, doesn't mean we can't
# build something more specific to our use case on top of it.
#
# I want to end this lecture with a pause and to ask you to reflect on the code we've written
# here. We started this course with some pretty simple use of libraries, but now we're
# digging in deeper and solving problems ourselves with the help of these libraries. Before we
# go on to our last library, how well prepared do you think you are to take your python
# skills out into the wild?
```

## Comparing Image Data Structures

```python
# OpenCV supports reading of images in most file formats, such as JPEG, PNG, and TIFF. Most image and
# video analysis requires converting images into grayscale first. This simplifies the image and reduces
# noise allowing for improved analysis. Let's write some code that reads an image of as person, Floyd
# Mayweather and converts it into greyscale.

# First we will import the open cv package cv2
import cv2 as cv
# We'll load the floyd.jpg image
img = cv.imread('readonly/floyd.jpg')
# And we'll convert it to grayscale using the cvtColor image
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# Now, before we get to the result, lets talk about docs. Just like tesseract, opencv is an external
# package written in C++, and the docs for python are really poor. This is unfortunatly quite common
# when python is being used as a wrapper. Thankfully, the web docs for opencv are actually pretty good,
# so hit the website docs.opencv.org when you want to learn more about a particular function. In this
# case cvtColor converts from one color space to another, and we are convering our image to grayscale.
# Of course, we already know at least two different ways of doing this, using binarization and PIL
# color spaces conversions

# Lets instpec this object that has been returned.
import inspect
inspect.getmro(type(gray))
```

```python
# We see that it is of type ndarray, which is a fundamental list type coming from the numerical
# python project. That's a bit surprising - up until this point we have been used to working with
# PIL.Image objects. OpenCV, however, wants to represent an image as a two dimensional sequence
# of bytes, and the ndarray, which stands for n dimensional array, is the ideal way to do this.
# Lets look at the array contents.
gray
```

```python
# The array is shown here as a list of lists, where the inner lists are filled with integers.
# The dtype=uint8 definition indicates that each of the items in an array is an 8 bit unsigned
# integer, which is very common for black and white images. So this is a pixel by pixel definition
# of the image.
#
# The display package, however, doesn't know what to do with this image. So lets convert it
# into a PIL object to render it in the browser.
from PIL import Image

# PIL can take an array of data with a given color format and convert this into a PIL object.
# This is perfect for our situation, as the PIL color mode, "L" is just an array of luminance
# values in unsigned integers
image = Image.fromarray(gray, "L")
display(image)
```

```python
# Lets talk a bit more about images for a moment. Numpy arrays are multidimensional. For
# instance, we can define an array in a single dimension:
import numpy as np
single_dim = np.array([25, 50 , 25, 10, 10])

# In an image, this is analagous to a single row of 5 pixels each in grayscale. But actually,
# all imaging libraries tend to expect at least two dimensions, a width and a height, and to
# show a matrix. So if we put the single_dim inside of another array, this would be a two
# dimensional array with element in the height direction, and five in the width direction
double_dim = np.array([single_dim])

double_dim
```

```python
# This should look pretty familiar, it's a lot like a list of lists! Lets see what this new
# two dimensional array looks like if we display it
display(Image.fromarray(double_dim, "L"))
```

```python
# Pretty unexciting - it's just a little line. Five pixels in a row to be exact, of different
# levels of black. The numpy library has a nice attribute called shape that allows us to see how
# many dimensions big an array is. The shape attribute returns a tuple that shows the height of
# the image, by the width of the image
double_dim.shape
```

```python
# Lets take a look at the shape of our initial image which we loaded into the img variable
img.shape
```

```python
# This image has three dimensions! That's because it has a width, a height, and what's called
# a color depth. In this case, the color is represented as an array of three values. Lets take a
# look at the color of the first pixel
first_pixel=img[0][0]
first_pixel
```

```python
# Here we see that the color value is provided in full RGB using an unsigned integer. This
# means that each color can have one of 256 values, and the total number of unique colors
# that can be represented by this data is 256 * 256 *256 which is roughly 16 million colors.
# We call this 24 bit color, which is 8+8+8.
#
# If you find yourself shopping for a television, you might notice that some expensive models
# are advertised as having 10 bit or even 12 bit panels. These are televisions where each of
# the red, green, and blue color channels are represented by 10 or 12 bits instead of 8. For
# ten bit panels this means that there are 1 billion colors capable, and 12 bit panels are
# capable of over 68 billion colors!
```

```python
# We're not going to talk much more about color in this course, but it's a fun subject. Instead,
# lets go back to this array representation of images, because we can do some interesting things
# with this.
#
# One of the most common things to do with an ndarray is to reshape it -- to change the number
# of rows and columns that are represented so that we can do different kinds of operations.
# Here is our original two dimensional image
print("Original image")
print(gray)
# If we wanted to represent that as a one dimensional image, we just call reshape
print("New image")
# And reshape takes the image as the first parameter, and a new shape as the second
image1d=np.reshape(gray,(1,gray.shape[0]*gray.shape[1]))
print(image1d)
```

```python
# So, why are we talking about these nested arrays of bytes, we were supposed to be talking
# about OpenCV as a library. Well, I wanted to show you that often libraries working on the
# same kind of principles, in this case images stored as arrays of bytes, are not representing
# data in the same way in their APIs. But, by exploring a bit you can learn how the internal
# representation of data is stored, and build routines to convert between formats.
#
# For instance, remember in the last lecture when we wanted to look for gaps in an image so
# that we could draw lines to feed into kraken? Well, we use PIL to do this, using getpixel()
# to look at individual pixels and see what the luminosity was, then ImageDraw.rectangle to
# actually fill in a black bar separator. This was a nice high level API, and let us write
# routines to do the work we wanted without having to understand too much about how the images
# were being stored. But it was computationally very slow.
#
# Instead, we could write the code to do this using matrix features within numpy. Lets take
# a look.
import cv2 as cv
# We'll load the 2 column image
img = cv.imread('readonly/two_col.png')
# And we'll convert it to grayscale using the cvtColor image
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
```

```python
# Now, remember how slicing on a list works, if you have a list of number such as
# a=[0,1,2,3,4,5] then a[2:4] will return the sublist of numbers at position 2 through 4
# inclusive - don't forget that lists start indexing at 0!
# If we have a two dimensional array, we can slice out a smaller piece of that using the
# format a[2:4,1:3]. You can think of this as first slicing along the rows dimension, then
# in the columns dimension. So in this example, that would be a matrix of rows 2, and 3,
# and columns 1, and 2. Here's a look at our image.
gray[2:4,1:3]
```

```python
# So we see that it is all white. We can use this as a "window" and move it around our
# our big image.
#
# Finally, the ndarray library has lots of matrix functions which are generally very fast
# to run. One that we want to consider in this case is count_nonzero(), which just returns
# the number of entries in the matrix which are not zero.
np.count_nonzero(gray[2:4,1:3])
```

```python
# Ok, the last benefit of going to this low level approach to images is that we can change
# pixels very fast as well. Previously we were drawing rectangles and setting a fill and line
# width. This is nice if you want to do something like change the color of the fill from the
# line, or draw complex shapes. But we really just want a line here. That's really easy to
# do - we just want to change a number of luminosity values from 255 to 0.
#
# As an example, lets create a big white matrix
white_matrix=np.full((12,12),255,dtype=np.uint8)
display(Image.fromarray(white_matrix,"L"))
white_matrix
```

```python
# looks pretty boring, it's just a giant white square we can't see. But if we want, we can
# easily color a column to be black
white_matrix[:,6]=np.full((1,12),0,dtype=np.uint8)
display(Image.fromarray(white_matrix,"L"))
white_matrix
```

```python
# And that's exactly what we wanted to do. So, why do it this way, when it seems so much
# more low level? Really, the answer is speed. This paradigm of using matricies to store
# and manipulate bytes of data for images is much closer to how low level API and hardware
# developers think about storing files and bytes in memory.
#
# How much faster is it? Well, that's up to you to discover; there's an optional assignment
# for this week to convert our old code over into this new format, to compare both the
# readability and speed of the two different approaches.
```

## OpenCV

```python
# Ok, we're just about at the project for this course. If you reflect on the specialization
# as a whole you'll realize that you started with probably little or no understanding of python,
# progressed through the basic control structures and libraries included with the language
# with the help of a digital textbook, moved on to more high level representations of data
# and functions with objects, and now started to explore third party libraries that exist for
# python which allow you to manipulate and display images. This is quite an achievement!
#
# You have also no doubt found that as you have progressed the demands on you to engage in self-
# discovery have also increased. Where the first assignments were maybe straight forward, the
# ones in this week require you to struggle a bit more with planning and debugging code as
# you develop.
#
# But, you've persisted, and I'd like to share with you just one more set of features before
# we head over to a project. The OpenCV library contains mechanisms to do face detection on
# images. The technique used is based on Haar cascades, which is a machine learning approach.
# Now, we're not going to go into the machine learning bits, we have another specialization on
# Applied Data Science with Python which you can take after this if you're interested in that topic.
# But here we'll treat OpenCV like a black box.
#
# OpenCV comes with trained models for detecting faces, eyes, and smiles which we'll be using.
# You can train models for detecting other things - like hot dogs or flutes - and if you're
# interested in that I'd recommend you check out the Open CV docs on how to train a cascade
# classifier: https://docs.opencv.org/3.4/dc/d88/tutorial_traincascade.html
# However, in this lecture we just want to use the current classifiers and see if we can detect
# portions of an image which are interesting.
#
# First step is to load opencv and the XML-based classifiers
import cv2 as cv
face_cascade = cv.CascadeClassifier('readonly/haarcascade_frontalface_default.xml')
eye_cascade = cv.CascadeClassifier('readonly/haarcascade_eye.xml')
```

```python
# Ok, with the classifiers loaded, we now want to try and detect a face. Lets pull in the
# picture we played with last time
img = cv.imread('readonly/floyd.jpg')
# And we'll convert it to grayscale using the cvtColor image
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# The next step is to use the face_cascade classifier. I'll let you go explore the docs if you
# would like to, but the norm is to use the detectMultiScale() function. This function returns
# a list of objects as rectangles. The first parameter is an ndarray of the image.
faces = face_cascade.detectMultiScale(gray)
# And lets just print those faces out to the screen
faces
```

```python
faces.tolist()[0]
```

```python
# The resulting rectangles are in the format of (x,y,w,h) where x and y denote the upper
# left hand point for the image and the width and height represent the bounding box. We know
# how to handle this in PIL
from PIL import Image

# Lets create a PIL image object
pil_img=Image.fromarray(gray,mode="L")

# Now lets bring in our drawing object
from PIL import ImageDraw
# And lets create our drawing context
drawing=ImageDraw.Draw(pil_img)

# Now lets pull the rectangle out of the faces object
rec=faces.tolist()[0]

# Now we just draw a rectangle around the bounds
drawing.rectangle(rec, outline="white")

# And display
display(pil_img)
```

```python
# So, not quite what we were looking for. What do you think went wrong?
# Well, a quick double check of the docs and it is apparent that OpenCV is return the coordinates
# as (x,y,w,h), while PIL.ImageDraw is looking for (x1,y1,x2,y2). Looks like an easy fix
# Wipe our old image
pil_img=Image.fromarray(gray,mode="L")
# Setup our drawing context
drawing=ImageDraw.Draw(pil_img)
# And draw the new box
drawing.rectangle((rec[0],rec[1],rec[0]+rec[2],rec[1]+rec[3]), outline="white")
# And display
display(pil_img)
```

```python
# We see the face detection works pretty good on this image! Note that it's apparent that this is
# not head detection, but that the haarcascades file we used is looking for eyes and a mouth.
# Lets try this on something a bit more complex, lets read in our MSI recruitment image
img = cv.imread('readonly/msi_recruitment.gif')
# And lets take a look at that image
display(Image.fromarray(img))
```

```python
# Whoa, what's that error about? It looks like there is an error on a line deep within the PIL
# Image.py file, and it is trying to call an internal private member called __array_interface__
# on the img object, but this object is None
#
# It turns out that the root of this error is that OpenCV can't work with Gif images. This is
# kind of a pain and unfortunate. But we know how to fix that right? One was is that we could
# just open this in PIL and then save it as a png, then open that in open cv.
#
# Lets use PIL to open our image
pil_img=Image.open('readonly/msi_recruitment.gif')
# now lets convert it to greyscale for opencv, and get the bytestream
open_cv_version=pil_img.convert("L")
# now lets just write that to a file
open_cv_version.save("msi_recruitment.png")
```

```python
# Ok, now that the conversion of format is done, lets try reading this back into opencv
cv_img=cv.imread('msi_recruitment.png')
# We don't need to color convert this, because we saved it as grayscale
# lets try and detect faces in that image
faces = face_cascade.detectMultiScale(cv_img)

# Now, we still have our PIL color version in a gif
pil_img=Image.open('readonly/msi_recruitment.gif')
# Set our drawing context
drawing=ImageDraw.Draw(pil_img)

# For each item in faces, lets surround it with a red box
for x,y,w,h in faces:
    # That might be new syntax for you! Recall that faces is a list of rectangles in (x,y,w,h)
    # format, that is, a list of lists. Instead of having to do an iteration and then manually
    # pull out each item, we can use tuple unpacking to pull out individual items in the sublist
    # directly to variables. A really nice python feature
    #
    # Now we just need to draw our box
    drawing.rectangle((x,y,x+w,y+h), outline="white")
display(pil_img)
```

```python
# What happened here!? We see that we have detected faces, and that we have drawn boxes
# around those faces on the image, but that the colors have gone all weird! This, it turns
# out, has to do with color limitations for gif images. In short, a gif image has a very
# limited number of colors. This is called a color pallette after the pallette artists
# use to mix paints. For gifs the pallette can only be 256 colors -- but they can be *any*
# 256 colors. When a new color is introduced, is has to take the space of an old color.
# In this case, PIL adds white to the pallette but doesn't know which color to replace and
# thus messes up the image.
#
# Who knew there was so much to learn about image formats? We can see what mode the image
# is in with the .mode attribute
pil_img.mode
```

```python
# We can see a list of modes in the PILLOW documentation, and they correspond with the
# color spaces we have been using. For the moment though, lets change back to RGB, which
# represents color as a three byte tuple instead of in a pallette.
# Lets read in the image
pil_img=Image.open('readonly/msi_recruitment.gif')
# Lets convert it to RGB mode
pil_img = pil_img.convert("RGB")
# And lets print out the mode
pil_img.mode
```

```python
# Ok, now lets go back to drawing rectangles. Lets get our drawing object
drawing=ImageDraw.Draw(pil_img)
# And iterate through the faces sequence, tuple unpacking as we go
for x,y,w,h in faces:
    # And remember this is width and height so we have to add those appropriately.
    drawing.rectangle((x,y,x+w,y+h), outline="white")
display(pil_img)
```

```python
# Awesome! We managed to detect a bunch of faces in that image. Looks like we have missed
# four faces. In the machine learning world we would call these false negatives - something
# which the machine thought was not a face (so a negative), but that it was incorrect on.
# Consequently, we would call the actual faces that were detected as true positives -
# something that the machine thought was a face and it was correct on. This leaves us with
# false positives - something the machine thought was a face but it wasn't. We see there are
# two of these in the image, picking up shadow patterns or textures in shirts and matching
# them with the haarcascades. Finally, we have true negatives, or the set of all possible
# rectangles the machine learning classifier could consider where it correctly indicated that
# the result was not a face. In this case there are many many true negatives.
```

```python
# There are a few ways we could try and improve this, and really, it requires a lot of
# experimentation to find good values for a given image. First, lets create a function
# which will plot rectanges for us over the image
def show_rects(faces):
    #Lets read in our gif and convert it
    pil_img=Image.open('readonly/msi_recruitment.gif').convert("RGB")
    # Set our drawing context
    drawing=ImageDraw.Draw(pil_img)
    # And plot all of the rectangles in faces
    for x,y,w,h in faces:
        drawing.rectangle((x,y,x+w,y+h), outline="white")
    #Finally lets display this
    display(pil_img)
```

```python
# Ok, first up, we could try and binarize this image. It turns out that opencv has a built in
# binarization function called threshold(). You simply pass in the image, the midpoint, and
# the maximum value, as well as a flag which indicates whether the threshold should be
# binary or something else. Lets try this.
cv_img_bin=cv.threshold(img,120,255,cv.THRESH_BINARY)[1] # returns a list, we want the second value
# Now do the actual face detection
faces = face_cascade.detectMultiScale(cv_img_bin)
# Now lets see the results
show_rects(faces)
```

```python
# That's kind of interesting. Not better, but we do see that there is one false positive
# towards the bottom, where the classifier detected the sunglasses as eyes and the dark shadow
# line below as a mouth.
#
# If you're following in the notebook with this video, why don't you pause things and try a
# few different parameters for the thresholding value?
```

```python
# The detectMultiScale() function from OpenCV also has a couple of parameters. The first of
# these is the scale factor. The scale factor changes the size of rectangles which are
# considered against the model, that is, the haarcascades XML file. You can think of it as if
# it were changing the size of the rectangles which are on the screen.
#
# Lets experiment with the scale factor. Usually it's a small value, lets try 1.05
faces = face_cascade.detectMultiScale(cv_img,1.05)
# Show those results
show_rects(faces)
# Now lets also try 1.15
faces = face_cascade.detectMultiScale(cv_img,1.15)
# Show those results
show_rects(faces)
# Finally lets also try 1.25
faces = face_cascade.detectMultiScale(cv_img,1.25)
# Show those results
show_rects(faces)
```

```python
# We can see that as we change the scale factor we change the number of true and
# false positives and negatives. With the scale set to 1.05, we have 7 true positives,
# which are correctly identified faces, and 3 false negatives, which are faces which
# are there but not detected, and 3 false positives, where are non-faces which
# opencv thinks are faces. When we change this to 1.15 we lose the false positives but
# also lose one of the true positives, the person to the right wearing a hat. And
# when we change this to 1.25 we lost more true positives as well.
#
# This is actually a really interesting phenomena in machine learning and artificial
# intelligence. There is a trade off between not only how accurate a model is, but how
# the inaccuracy actually happens. Which of these three models do you think is best?
```

```python
# Well, the answer to that question is really, "it depends". It depends why you are trying
# to detect faces, and what you are going to do with them. If you think these issues
# are interesting, you might want to check out the Applied Data Science with Python
# specialization Michigan offers on Coursera.
#
# Ok, beyond an opportunity to advertise, did you notice anything else that happened when
# we changed the scale factor? It's subtle, but the speed at which the processing ran
# took longer at smaller scale factors. This is because more subimages are being considered
# for these scales. This could also affect which method we might use.
#
# Jupyter has nice support for timing commands. You might have seen this before, a line
# that starts with a percentage sign in jupyter is called a "magic function". This isn't
# normal python - it's actually a shorthand way of writing a function which Jupyter
# has predefined. It looks a lot like the decorators we talked about in a previous
# lecture, but the magic functions were around long before decorators were part of the
# python language. One of the built-in magic functions in juptyer is called timeit, and this
# repeats a piece of python ten times (by default) and tells you the average speed it
# took to complete.
#
# Lets time the speed of detectmultiscale when using a scale of 1.05
%timeit face_cascade.detectMultiScale(cv_img,1.05)
```

```python
# Ok, now lets compare that to the speed at scale = 1.15
%timeit face_cascade.detectMultiScale(cv_img,1.15)
```

```python
# You can see that this is a dramatic difference, roughly two and a half times slower
# when using the smaller scale!
#
# This wraps up our discussion of detecting faces in opencv. You'll see that, like OCR, this
# is not a foolproof process. But we can build on the work others have done in machine learning
# and leverage powerful libraries to bring us closer to building a turn key python-based
# solution. Remember that the detection mechanism isn't specific to faces, that's just the
# haarcascades training data we used. On the web you'll be able to find other training data
# to detect other objects, including eyes, animals, and so forth.
```

## More Jupyter Widgets

```python
# One of the nice things about using the Jupyter notebook systems is that there is a
# rich set of contributed plugins that seek to extend this system. In this lecture I
# want to introduce you to one such plugin, call ipy web rtc. Webrtc is a fairly new
# protocol for real time communication on the web. Yup, I'm talking about chatting.
# The widget brings this to the Jupyter notebook system. Lets take a look.
#
# First, lets import from this library two different classes which we'll use in a
# demo, one for the camera and one for images.
from ipywebrtc import CameraStream, ImageRecorder
# Then lets take a look at the camera stream object
help(CameraStream)
```

```python
# We see from the docs that it's east to get a camera facing the user, and we can have
# the audio on or off. We don't need audio for this demo, so lets create a new camera
# instance
camera = CameraStream.facing_user(audio=False)
# The next object we want to look at is the ImageRecorder
help(ImageRecorder)
```

```python
# The image recorder lets us actually grab images from the camera stream. There are features
# for downloading and using the image as well. We see that the default format is a png file.
# Lets hook up the ImageRecorder to our stream
image_recorder = ImageRecorder(stream=camera)
# Now, the docs are a little unclear how to use this within Jupyter, but if we call the
# download() function it will actually store the results of the camera which is hooked up
# in image_recorder.image. Lets try it out
# First, lets tell the recorder to start capturing data
image_recorder.recording=True
# Now lets download the image
image_recorder.download()
# Then lets inspect the type of the image
type(image_recorder.image)
```

```python
# Ok, the object that it stores is an ipywidgets.widgets.widget_media.Image. How do we do
# something useful with this? Well, an inspection of the object shows that there is a handy
# value field which actually holds the bytes behind the image. And we know how to display
# those.
# Lets import PIL Image
import PIL.Image
# And lets import io
import io
# And now lets create a PIL image from the bytes
img = PIL.Image.open(io.BytesIO(image_recorder.image.value))
# And render it to the screen
display(img)
```

```python
# Great, you see a picture! Hopefully you are following along in one of the notebooks
# and have been able to try this out for yourself!
#
# What can you do with this? This is a great way to get started with a bit of computer vision.
# You already know how to identify a face in the webcam picture, or try and capture text
# from within the picture. With OpenCV there are any number of other things you can do, simply
# with a webcam, the Jupyter notebooks, and python!
```

Python Project pillow, tesseract, and openCV (University of Michigan)/The Python Imaging Library OpenCV module_3.ipynb


# Passive ownership and flows
## Why it matters

- Passive management has seen unprecedented growth in the past 10-15 years

Source: Sushko, V., and G. Turner (2018) The implications of passive investing for securities markets, BIS Quarterly Review, March 2018, 113–131.
- Academics have found that institutional ownership in general tends to be positively associated with security return volatility (Sias, 1996)
- Ben-David et al. (2018) find that this is particularly true for ETFs

**The main mechanism:**
- Liquidity shocks (i.e. inflows and outflows) that affect the fund propagate to the underlying securities, thereby increasing volatility
- This mechanism is stronger in the case of passive funds because of the limited discretionality in the timing and direction of trades

**Key results of our work:**
- In our paper, "The Shift from Active to Passive" (July 2019), we find that passively held stocks tend to be more volatile after correcting for other stock characteristics
- The effect of passive ownership on volatility is concentrated at the end of the continuous trading session and at the Close

Source: GS Global Markets Division.
- The effect of passive inflows and outflows is similarly concentrated around the Close

Source: GS Global Markets Division.
- This is arguably due to the concentration of portfolio trades in the order flow during that phase
- We also detect differences in the impact of passive investing between US and European markets due to the different design of the closing auction

**Passive ownership and the March 2020 selloff**
- In our paper, "Dissecting the Viral Selloff" (April 2020), we group stocks by their level of passive ETF ownership and compare the percentage increase in volatility between January and March 2020

Source: GS Global Markets Division.

- Stocks widely held by ETFs have experienced larger volatility increases, both in the US and in Europe
- The result holds true after correcting for other effects, including sector, country, size and liquidity

## Measuring passive ownership and flows
- Starting from fund-level data, we produce stock-level indicators by using the latest information on holdings and flows
- Raw ownership ratios:
\begin{equation}
PassETFRatio_{i}=\frac{\sum_{j\in F_{PassETF}}w_{ij}AuM_{j}}{MktCap_{i}}
\label{passETFratio}
\end{equation}
where $w_{ij}$ is the weight of stock $i$ in fund $j$, $AuM_{j}$ is the total value of fund $j$'s holdings, $F_{PassETF}$ the set of passive ETFs and $MktCap_{i}$ is
the market capitalisation in US dollars of stock $i$
- Building on our research, we carry out a series of cross-sectional normalisations and adjustments to make the scores comparable across regions and over time
- Scores are normalised between -3 and 3. Higher scores indicate a higher level of passive ownership
- Similarly, for flow scores a higher score indicates stronger inflows to passive funds holding the stock

#### Import libraries

```python
import pandas as pd
from gs_quant.markets.securities import SecurityMaster, AssetIdentifier
from gs_quant.session import GsSession
from gs_quant.data import Dataset
import datetime as dt
import matplotlib.pyplot as plt
```

#### Supply credentials

```python
# external users should substitute their client id and secret; please skip this step if using internal jupyterhub
GsSession.use(Environment.PROD, client_id=client_id, client_secret=client_secret, scopes=('read_product_data',))
```

## Load basket of stocks from text file

```python
# load data from csv
ptf_df = pd.read_csv('trade_list_world.csv')
ptf_df = ptf_df.rename(columns={'Symbol': 'SEDOL', 'Trade Value ($)': 'Value'})
ptf_df
```

## Map identifiers

```python
# load Marquee identifiers and Bloomberg Ids
identifiers_df = {}
for sedol_id in ptf_df.SEDOL:
    identifiers_df[sedol_id] = {}
    asset = SecurityMaster.get_asset(sedol_id, AssetIdentifier.SEDOL, sort_by_rank=True)
    identifiers_df[sedol_id]['assetId'] = asset.get_marquee_id()
    identifiers_df[sedol_id]['BCID'] = asset.get_identifiers(as_of=dt.date(2021, 6, 10))['BCID']

identifiers_df = pd.DataFrame.from_dict(identifiers_df, orient='index')
```

## Query Marquee datasets

```python
## Get monthly ownership data
ds = Dataset('OWNERSHIP_SCORES_MONTHLY')
data_df = ds.get_data(start=dt.date(2021, 5, 9), end=dt.date(2021, 6, 10),
                      assetId=identifiers_df.assetId.to_list())

## Get weekly flow data
ds2 = Dataset('OWNERSHIP_SCORES_WEEKLY_FLOW')
data2_df = ds2.get_data(start=dt.date(2021, 6, 3), end=dt.date(2021, 6, 10),
                        assetId=identifiers_df.assetId.to_list())
```

```python
## merge results in a summary table
ptf_df = ptf_df.merge(identifiers_df, right_index=True, left_on='SEDOL').merge(data_df, on='assetId')
ptf_df = ptf_df.merge(data2_df, on='assetId')
```

## Explore the data

```python
## display most passively held names
ratios_names = [x for x in ptf_df.columns if x.endswith('Ratio')]
ptf_df[['BCID']+ratios_names].sort_values(by='passiveRatio', ascending=False).head()
```

```python
## display least passively held names
ptf_df[['BCID']+ratios_names].sort_values(by='passiveRatio', ascending=True).head()
```

```python
## display most affected by passive inflows
ptf_df[['BCID']+ratios_names].sort_values(by='passiveFlowRatio', ascending=False).head()
```

```python
## display most affected by passive outflows
ptf_df[['BCID']+ratios_names].sort_values(by='passiveFlowRatio', ascending=True).head()
```

## Compute impact of trade on portfolio-level ownership

```python
# plot breakdown by passive ETF ratio
own_slices = [(-3, -2), (-2, -1), (-1, 0), (0, 1), (1, 2), (2, 3.1)]
slice_labels = ['[-3,-2)', '[-2,-1)', '[-1,0)', '[0,1)', '[1,2)', '[2,3]']
# scores are bounded between -3 and 3

# compute total value sold and bought
tot_traded_long = ptf_df.loc[ptf_df['Value']>0].sum()['Value']
tot_traded_short = ptf_df.loc[ptf_df['Value']<0].sum()['Value']

# compute values traded and plot bars
text_kwargs = dict(ha='center', va='center', fontsize=12, color='black')
fig, ax = plt.subplots(2, 1, figsize=(8, 12))
cnc = 0
for etf_var, var_label, chart_title in [('passiveEtfRatio', 'Passive ETF ratio', 'Breakdown by ETF ownership'),
                                        ('etfFlowRatio', 'Passive ETF flow ratio', 'Breakdown by ETF flows')]:
    slc = 0
    for lb, ub in own_slices:
        ax[cnc].barh(slc, ptf_df.loc[(ptf_df[etf_var]>=lb) & (ptf_df[etf_var]<ub)
            & (ptf_df['Value']>0)].sum()['Value']/tot_traded_long, color='#939393')
        ax[cnc].barh(slc, -ptf_df.loc[(ptf_df[etf_var]>=lb) & (ptf_df[etf_var]<ub)
            & (ptf_df['Value']<0)].sum()['Value']/tot_traded_short, color='#7399c6')
        slc += 1
    ax[cnc].set_xlabel('Proportion of value traded')
    ax[cnc].set_ylabel(var_label)
    ax[cnc].set_yticks(range(6))
    ax[cnc].set_yticklabels(slice_labels)
    ax[cnc].set_title(chart_title)
    cnc += 1
plt.text(0.6, 5, 'Buys', **text_kwargs)
plt.text(-0.25, 5, 'Sells', **text_kwargs)
plt.text(0.6, 12.75, 'Buys', **text_kwargs)
plt.text(-0.25, 12.75, 'Sells', **text_kwargs)
plt.show()
```

```python
# compute portfolio-level change
ptf_delta = {}
for si in ['passiveRatio', 'passiveEtfRatio']:
    ptf_delta[si] = sum(ptf_df[si]*ptf_df['Value'])/1.0e+09
ptf_delta
```

##### Disclaimers:
###### Indicative Terms/Pricing Levels: This material may contain indicative terms only, including but not limited to pricing levels. There is no representation that any transaction can or could have been effected at such terms or prices. Proposed terms and conditions are for discussion purposes only. Finalized terms and conditions are subject to further discussion and negotiation.
###### www.goldmansachs.com/disclaimer/sales-and-trading-invest-rec-disclosures.html If you are not accessing this material via Marquee ContentStream, a list of the author's investment recommendations disseminated during the preceding 12 months and the proportion of the author's recommendations that are 'buy', 'hold', 'sell' or other over the previous 12 months is available by logging into Marquee ContentStream using the link below. Alternatively, if you do not have access to Marquee ContentStream, please contact your usual GS representative who will be able to provide this information to you.
###### Please refer to https://marquee.gs.com/studio/ for price information of corporate equity securities.
###### Notice to Australian Investors: When this document is disseminated in Australia by Goldman Sachs & Co. LLC ("GSCO"), Goldman Sachs International ("GSI"), Goldman Sachs Bank Europe SE ("GSBE"), Goldman Sachs (Asia) L.L.C. ("GSALLC"), or Goldman Sachs (Singapore) Pte ("GSSP") (collectively the "GS entities"), this document, and any access to it, is intended only for a person that has first satisfied the GS entities that:
###### • the person is a Sophisticated or Professional Investor for the purposes of section 708 of the Corporations Act of Australia; and
###### • the person is a wholesale client for the purpose of section 761G of the Corporations Act of Australia.
###### To the extent that the GS entities are providing a financial service in Australia, the GS entities are each exempt from the requirement to hold an Australian financial services licence for the financial services they provide in Australia. Each of the GS entities are regulated by a foreign regulator under foreign laws which differ from Australian laws, specifically:
###### • GSCO is regulated by the US Securities and Exchange Commission under US laws;
###### • GSI is authorised by the Prudential Regulation Authority and regulated by the Financial Conduct Authority and the Prudential Regulation Authority, under UK laws;
###### • GSBE is subject to direct prudential supervision by the European Central Bank and in other respects is supervised by the German Federal Financial Supervisory Authority (Bundesanstalt für Finanzdienstleistungsaufischt, BaFin) and Deutsche Bundesbank;
###### • GSALLC is regulated by the Hong Kong Securities and Futures Commission under Hong Kong laws; and
###### • GSSP is regulated by the Monetary Authority of Singapore under Singapore laws.
###### Notice to Brazilian Investors
###### Marquee is not meant for the general public in Brazil. The services or products provided by or through Marquee, at any time, may not be offered or sold to the general public in Brazil. You have received a password granting access to Marquee exclusively due to your existing relationship with a GS business located in Brazil. The selection and engagement with any of the offered services or products through Marquee, at any time, will be carried out directly by you. Before acting to implement any chosen service or products, provided by or through Marquee you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Any steps necessary in order to implement the chosen service or product, including but not limited to remittance of funds, shall be carried out at your discretion. Accordingly, such services and products have not been and will not be publicly issued, placed, distributed, offered or negotiated in the Brazilian capital markets and, as a result, they have not been and will not be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários), nor have they been submitted to the foregoing agency for approval. Documents relating to such services or products, as well as the information contained therein, may not be supplied to the general public in Brazil, as the offering of such services or products is not a public offering in Brazil, nor used in connection with any offer for subscription or sale of securities to the general public in Brazil.
###### The offer of any securities mentioned in this message may not be made to the general public in Brazil. Accordingly, any such securities have not been nor will they be registered with the Brazilian Securities and Exchange Commission (Comissão de Valores Mobiliários) nor has any offer been submitted to the foregoing agency for approval. Documents relating to the offer, as well as the information contained therein, may not be supplied to the public in Brazil, as the offer is not a public offering of securities in Brazil. These terms will apply on every access to Marquee.
###### Ouvidoria Goldman Sachs Brasil: 0800 727 5764 e/ou ouvidoriagoldmansachs@gs.com
###### Horário de funcionamento: segunda-feira à sexta-feira (exceto feriados), das 9hs às 18hs.
###### Ombudsman Goldman Sachs Brazil: 0800 727 5764 and / or ouvidoriagoldmansachs@gs.com
###### Available Weekdays (except holidays), from 9 am to 6 pm.

###### Note to Investors in Israel: GS is not licensed to provide investment advice or investment management services under Israeli law.
###### Notice to Investors in Japan
###### Marquee is made available in Japan by Goldman Sachs Japan Co., Ltd.

###### 本書は情報の提供を目的としております。また、売却・購入が違法となるような法域での有価証券その他の売却若しくは購入を勧めるものでもありません。ゴールドマン・サックスは本書内の取引又はストラクチャーの勧誘を行うものではございません。これらの取引又はストラクチャーは、社内及び法規制等の承認等次第で実際にはご提供できない場合がございます。

###### <適格機関投資家限定　転売制限>
###### ゴールドマン・サックス証券株式会社が適格機関投資家のみを相手方として取得申込みの勧誘（取得勧誘）又は売付けの申込み若しくは買付けの申込みの勧誘(売付け勧誘等)を行う本有価証券には、適格機関投資家に譲渡する場合以外の譲渡が禁止される旨の制限が付されています。本有価証券は金融商品取引法第４条に基づく財務局に対する届出が行われておりません。なお、本告知はお客様によるご同意のもとに、電磁的に交付させていただいております。
###### ＜適格機関投資家用資料＞
###### 本資料は、適格機関投資家のお客さまのみを対象に作成されたものです。本資料における金融商品は適格機関投資家のお客さまのみがお取引可能であり、適格機関投資家以外のお客さまからのご注文等はお受けできませんので、ご注意ください。 商号等/ゴールドマン・サックス証券株式会社 金融商品取引業者　関東財務局長（金商）第６９号
###### 加入協会/　日本証券業協会、一般社団法人金融先物取引業協会、一般社団法人第二種金融商品取引業協会
###### 本書又はその添付資料に信用格付が記載されている場合、日本格付研究所（JCR）及び格付投資情報センター（R&I）による格付は、登録信用格付業者による格付（登録格付）です。その他の格付は登録格付である旨の記載がない場合は、無登録格付です。無登録格付を投資判断に利用する前に、「無登録格付に関する説明書」（http://www.goldmansachs.com/disclaimer/ratings.html）を十分にお読みください。
###### If any credit ratings are contained in this material or any attachments, those that have been issued by Japan Credit Rating Agency, Ltd. (JCR) or Rating and Investment Information, Inc. (R&I) are credit ratings that have been issued by a credit rating agency registered in Japan (registered credit ratings). Other credit ratings are unregistered unless denoted as being registered. Before using unregistered credit ratings to make investment decisions, please carefully read "Explanation Regarding Unregistered Credit Ratings" (http://www.goldmansachs.com/disclaimer/ratings.html).
###### Notice to Mexican Investors: Information contained herein is not meant for the general public in Mexico. The services or products provided by or through Goldman Sachs Mexico, Casa de Bolsa, S.A. de C.V. (GS Mexico) may not be offered or sold to the general public in Mexico. You have received information herein exclusively due to your existing relationship with a GS Mexico or any other Goldman Sachs business. The selection and engagement with any of the offered services or products through GS Mexico will be carried out directly by you at your own risk. Before acting to implement any chosen service or product provided by or through GS Mexico you should consider, at your sole discretion, whether it is suitable for your particular circumstances and, if necessary, seek professional advice. Information contained herein related to GS Mexico services or products, as well as any other information, shall not be considered as a product coming from research, nor it contains any recommendation to invest, not to invest, hold or sell any security and may not be supplied to the general public in Mexico.
###### Notice to New Zealand Investors: When this document is disseminated in New Zealand by Goldman Sachs & Co. LLC ("GSCO") , Goldman Sachs International ("GSI"), Goldman Sachs Bank Europe SE ("GSBE"), Goldman Sachs (Asia) L.L.C. ("GSALLC") or Goldman Sachs (Singapore) Pte ("GSSP") (collectively the "GS entities"), this document, and any access to it, is intended only for a person that has first satisfied; the GS entities that the person is someone:
###### (i) who is an investment business within the meaning of clause 37 of Schedule 1 of the Financial Markets Conduct Act 2013 (New Zealand) (the "FMC Act");
###### (ii) who meets the investment activity criteria specified in clause 38 of Schedule 1 of the FMC Act;
###### (iii) who is large within the meaning of clause 39 of Schedule 1 of the FMC Act; or
###### (iv) is a government agency within the meaning of clause 40 of Schedule 1 of the FMC Act.
###### No offer to acquire the interests is being made to you in this document. Any offer will only be made in circumstances where disclosure is not required under the Financial Markets Conducts Act 2013 or the Financial Markets Conduct Regulations 2014.
###### Notice to Swiss Investors: This is marketing material for financial instruments or services. The information contained in this material is for general informational purposes only and does not constitute an offer, solicitation, invitation or recommendation to buy or sell any financial instruments or to provide any investment advice or service of any kind.
###### THE INFORMATION CONTAINED IN THIS DOCUMENT DOES NOT CONSITUTE, AND IS NOT INTENDED TO CONSTITUTE, A PUBLIC OFFER OF SECURITIES IN THE UNITED ARAB EMIRATES IN ACCORDANCE WITH THE COMMERCIAL COMPANIES LAW (FEDERAL LAW NO. 2 OF 2015), ESCA BOARD OF DIRECTORS' DECISION NO. (9/R.M.) OF 2016, ESCA CHAIRMAN DECISION NO 3/R.M. OF 2017 CONCERNING PROMOTING AND INTRODUCING REGULATIONS OR OTHERWISE UNDER THE LAWS OF THE UNITED ARAB EMIRATES. ACCORDINGLY, THE INTERESTS IN THE SECURITIES MAY NOT BE OFFERED TO THE PUBLIC IN THE UAE (INCLUDING THE DUBAI INTERNATIONAL FINANCIAL CENTRE AND THE ABU DHABI GLOBAL MARKET). THIS DOCUMENT HAS NOT BEEN APPROVED BY, OR FILED WITH THE CENTRAL BANK OF THE UNITED ARAB EMIRATES, THE SECURITIES AND COMMODITIES AUTHORITY, THE DUBAI FINANCIAL SERVICES AUTHORITY, THE FINANCIAL SERVICES REGULATORY AUTHORITY OR ANY OTHER RELEVANT LICENSING AUTHORITIES IN THE UNITED ARAB EMIRATES. IF YOU DO NOT UNDERSTAND THE CONTENTS OF THIS DOCUMENT, YOU SHOULD CONSULT WITH A FINANCIAL ADVISOR. THIS DOCUMENT IS PROVIDED TO THE RECIPIENT ONLY AND SHOULD NOT BE PROVIDED TO OR RELIED ON BY ANY OTHER PERSON.