chore: import upstream snapshot with attribution
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:07:30 +08:00
commit 468fc26eb0
13649 changed files with 3176860 additions and 0 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,77 @@
# Pre-trained Networks and Transfer Learning
Training CNNs can take a lot of time, and a lot of data is required for that task. However, much of the time is spent learning the best low-level filters that a network can use to extract patterns from images. A natural question arises - can we use a neural network trained on one dataset and adapt it to classify different images without requiring a full training process?
## [Pre-lecture quiz](https://ff-quizzes.netlify.app/en/ai/quiz/15)
This approach is called **transfer learning**, because we transfer some knowledge from one neural network model to another. In transfer learning, we typically start with a pre-trained model, which has been trained on some large image dataset, such as **ImageNet**. Those models can already do a good job extracting different features from generic images, and in many cases just building a classifier on top of those extracted features can yield a good result.
> ✅ Transfer Learning is a term you find in other academic fields, such as Education. It refers to the process of taking knowledge from one domain and applying it to another.
## Pre-Trained Models as Feature Extractors
The convolutional networks that we have talked about in the previous section contained a number of layers, each of which is supposed to extract some features from the image, starting from low-level pixel combinations (such as horizontal/vertical line or stroke), up to higher level combinations of features, corresponding to things like an eye of a flame. If we train CNN on sufficiently large dataset of generic and diverse images, the network should learn to extract those common features.
Both Keras and PyTorch contain functions to easily load pre-trained neural network weights for some common architectures, most of which were trained on ImageNet images. The most often used ones are described on the [CNN Architectures](../07-ConvNets/CNN_Architectures.md) page from the prior lesson. In particular, you may want to consider using one of the following:
* **VGG-16/VGG-19** which are relatively simple models that still give good accuracy. Often using VGG as a first attempt is a good choice to see how transfer learning is working.
* **ResNet** is a family of models proposed by Microsoft Research in 2015. They have more layers, and thus take more resources.
* **MobileNet** is a family of models with reduced size, suitable for mobile devices. Use them if you are short in resources and can sacrifice a little bit of accuracy.
Here are sample features extracted from a picture of a cat by VGG-16 network:
![Features extracted by VGG-16](images/features.png)
## Cats vs. Dogs Dataset
In this example, we will use a dataset of [Cats and Dogs](https://www.microsoft.com/download/details.aspx?id=54765&WT.mc_id=academic-77998-cacaste), which is very close to a real-life image classification scenario.
## ✍️ Exercise: Transfer Learning
Let's see transfer learning in action in corresponding notebooks:
* [Transfer Learning - PyTorch](TransferLearningPyTorch.ipynb)
* [Transfer Learning - TensorFlow](TransferLearningTF.ipynb)
## Visualizing Adversarial Cat
Pre-trained neural network contains different patterns inside it's *brain*, including notions of **ideal cat** (as well as ideal dog, ideal zebra, etc.). It would be interesting to somehow **visualize this image**. However, it is not simple, because patterns are spread all over the network weights, and also organized in a hierarchical structure.
One approach we can take is to start with a random image, and then try to use **gradient descent optimization** technique to adjust that image in such a way, that the network starts thinking that it's a cat.
![Image Optimization Loop](images/ideal-cat-loop.png)
However, if we do this, we will receive something very similar to a random noise. This is because *there are many ways to make network think the input image is a cat*, including some that do not make sense visually. While those images contain a lot of patterns typical for a cat, there is nothing to constrain them to be visually distinctive.
To improve the result, we can add another term into the loss function, which is called **variation loss**. It is a metric that shows how similar neighboring pixels of the image are. Minimizing variation loss makes image smoother, and gets rid of noise - thus revealing more visually appealing patterns. Here is an example of such "ideal" images, that are classified as cat and as zebra with high probability:
![Ideal Cat](images/ideal-cat.png) | ![Ideal Zebra](images/ideal-zebra.png)
-----|-----
*Ideal Cat* | *Ideal Zebra*
Similar approach can be used to perform so-called **adversarial attacks** on a neural network. Suppose we want to fool a neural network and make a dog look like a cat. If we take dog's image, which is recognized by a network as a dog, we can then tweak it a little but using gradient descent optimization, until the network starts classifying it as a cat:
![Picture of a Dog](images/original-dog.png) | ![Picture of a dog classified as a cat](images/adversarial-dog.png)
-----|-----
*Original picture of a dog* | *Picture of a dog classified as a cat*
See the code to reproduce the results above in the following notebook:
* [Ideal and Adversarial Cat - TensorFlow](AdversarialCat_TF.ipynb)
## Conclusion
Using transfer learning, you are able to quickly put together a classifier for a custom object classification task and achieve high accuracy. You can see that more complex tasks that we are solving now require higher computational power, and cannot be easily solved on the CPU. In the next unit, we will try to use a more lightweight implementation to train the same model using lower compute resources, which results in just slightly lower accuracy.
## 🚀 Challenge
In the accompanying notebooks, there are notes at the bottom about how transfer knowledge works best with somewhat similar training data (a new type of animal, perhaps). Do some experimentation with completely new types of images to see how well or poorly your transfer knowledge models perform.
## [Post-lecture quiz](https://ff-quizzes.netlify.app/en/ai/quiz/16)
## Review & Self Study
Read through [TrainingTricks.md](TrainingTricks.md) to deepen your knowledge of some other way to train your models.
## [Assignment](lab/README.md)
In this lab, we will use real-life [Oxford-IIIT](https://www.robots.ox.ac.uk/~vgg/data/pets/) pets dataset with 35 breeds of cats and dogs, and we will build a transfer learning classifier.
@@ -0,0 +1,105 @@
# Deep Learning Training Tricks
As neural networks become deeper, the process of their training becomes more and more challenging. One major problem is so-called [vanishing gradients](https://en.wikipedia.org/wiki/Vanishing_gradient_problem) or [exploding gradients](https://deepai.org/machine-learning-glossary-and-terms/exploding-gradient-problem#:~:text=Exploding%20gradients%20are%20a%20problem,updates%20are%20small%20and%20controlled.). [This post](https://towardsdatascience.com/the-vanishing-exploding-gradient-problem-in-deep-neural-networks-191358470c11) gives a good introduction into those problems.
To make training deep networks more efficient, there are a few techniques that can be used.
## Keeping values in reasonable interval
To make numerical computations more stable, we want to make sure that all values within our neural network are within reasonable scale, typically [-1..1] or [0..1]. It is not a very strict requirement, but the nature of floating point computations is such that values of different magnitudes cannot be accurately manipulated together. For example, if we add 10<sup>-10</sup> and 10<sup>10</sup>, we are likely to get 10<sup>10</sup>, because smaller value would be "converted" to the same order as the larger one, and thus mantissa would be lost.
Most activation functions have non-linearities around [-1..1], and thus it makes sense to scale all input data to [-1..1] or [0..1] interval.
## Initial Weight Initialization
Ideally, we want the values to be in the same range after passing through network layers. Thus it is important to initialize weights in such a way as to preserve the distribution of values.
Normal distribution **N(0,1)** is not a good idea, because if we have *n* inputs, the standard deviation of output would be *n*, and values are likely to jump out of [0..1] interval.
The following initializations are often used:
* Uniform distribution -- `uniform`
* **N(0,1/n)** -- `gaussian`
* **N(0,1/&radic;n_in)** guarantees that for inputs with zero mean and standard deviation of 1 the same mean/standard deviation would remain
* **N(0,&radic;2/(n_in+n_out))** -- so-called **Xavier initialization** (`glorot`), it helps to keep the signals in range during both forward and backward propagation
## Batch Normalization
Even with proper weight initialization, weights can get arbitrary big or small during the training, and they will bring signals out of proper range. We can bring signals back by using one of **normalization** techniques. While there are several of them (Weight normalization, Layer Normalization), the most often used is Batch Normalization.
The idea of **batch normalization** is to take into account all values across the minibatch, and perform normalization (i.e. subtract mean and divide by standard deviation) based on those values. It is implemented as a network layer that does this normalization after applying the weights, but before activation function. As a result, we are likely to see higher final accuracy and faster training.
Here is the [original paper](https://arxiv.org/pdf/1502.03167.pdf) on batch normalization, the [explanation on Wikipedia](https://en.wikipedia.org/wiki/Batch_normalization), and [a good introductory blog post](https://towardsdatascience.com/batch-normalization-in-3-levels-of-understanding-14c2da90a338) (and the one [in Russian](https://habrahabr.ru/post/309302/)).
## Dropout
**Dropout** is an interesting technique that removes a certain percentage of random neurons during training. It is also implemented as a layer with one parameter (percentage of neurons to remove, typically 10%-50%), and during training it zeroes random elements of the input vector, before passing it to the next layer.
While this may sound like a strange idea, you can see the effect of dropout on training MNIST digit classifier in [`Dropout.ipynb`](Dropout.ipynb) notebook. It speeds up training and allows us to achieve higher accuracy in less training epochs.
This effect can be explained in several ways:
* It can be considered to be a random shocking factor to the model, which takes optimiation out of local minimum
* It can be considered as *implicit model averaging*, because we can say that during dropout we are training slightly different model
> *Some people say that when a drunk person tries to learn something, he will remember this better next morning, comparing to a sober person, because a brain with some malfunctioning neurons tries to adapt better to gasp the meaning. We never tested ourselves if this is true of not*
## Preventing overfitting
One of the very important aspect of deep learning is too be able to prevent [overfitting](../../3-NeuralNetworks/05-Frameworks/Overfitting.md). While it might be tempting to use very powerful neural network model, we should always balance the number of model parameters with the number of training samples.
> Make sure you understand the concept of [overfitting](../../3-NeuralNetworks/05-Frameworks/Overfitting.md) we have introduced earlier!
There are several ways to prevent overfitting:
* Early stopping -- continuously monitor error on validation set and stopping training when validation error starts to increase.
* Explicit Weight Decay / Regularization -- adding an extra penalty to the loss function for high absolute values of weights, which prevents the model of getting very unstable results
* Model Averaging -- training several models and then averaging the result. This helps to minimize the variance.
* Dropout (Implicit Model Averaging)
## Optimizers / Training Algorithms
Another important aspect of training is to chose good training algorithm. While classical **gradient descent** is a reasonable choice, it can sometimes be too slow, or result in other problems.
In deep learning, we use **Stochastic Gradient Descent** (SGD), which is a gradient descent applied to minibatches, randomly selected from the training set. Weights are adjusted using this formula:
w<sup>t+1</sup> = w<sup>t</sup> - &eta;&nabla;&lagran;
### Momentum
In **momentum SGD**, we are keeping a portion of a gradient from previous steps. It is similar to when we are moving somewhere with inertia, and we receive a punch in a different direction, our trajectory does not change immediately, but keeps some part of the original movement. Here we introduce another vector v to represent the *speed*:
* v<sup>t+1</sup> = &gamma; v<sup>t</sup> - &eta;&nabla;&lagran;
* w<sup>t+1</sup> = w<sup>t</sup>+v<sup>t+1</sup>
Here parameter &gamma; indicates the extent to which we take inertia into account: &gamma;=0 corresponds to classical SGD; &gamma;=1 is a pure motion equation.
### Adam, Adagrad, etc.
Since in each layer we multiply signals by some matrix W<sub>i</sub>, depending on ||W<sub>i</sub>||, the gradient can either diminish and be close to 0, or rise indefinitely. It is the essence of Exploding/Vanishing Gradients problem.
One of the solutions to this problem is to use only direction of the gradient in the equation, and ignore the absolute value, i.e.
w<sup>t+1</sup> = w<sup>t</sup> - &eta;(&nabla;&lagran;/||&nabla;&lagran;||), where ||&nabla;&lagran;|| = &radic;&sum;(&nabla;&lagran;)<sup>2</sup>
This algorithm is called **Adagrad**. Another algorithms that use the same idea: **RMSProp**, **Adam**
> **Adam** is considered to be a very efficient algorithm for many applications, so if you are not sure which one to use - use Adam.
### Gradient clipping
Gradient clipping is an extension the idea above. When the ||&nabla;&lagran;|| &le; &theta;, we consider the original gradient in the weight optimization, and when ||&nabla;&lagran;|| > &theta; - we divide the gradient by it's norm. Here &theta; is a parameter, in most cases we can take &theta;=1 or &theta;=10.
### Learning rate decay
Training success often depends on the learning rate parameter &eta;. It is logical to assume that larger values of &eta; result in faster training, which is something we typically want in the beginning of the training, and then smaller value of &eta; allow us to fine-tune the network. Thus, in most of the cases we want to decrease &eta; in the process of the training.
This can be done by multiplying &eta; by some number (eg. 0.98) after each epoch of the training, or by using more complicated **learning rate schedule**.
## Different Network Architectures
Selecting right network architecture for your problem can be tricky. Normally, we would take an architecture that has proven to work for our specific task (or similar one). Here is a [good overview](https://www.topbots.com/a-brief-history-of-neural-network-architectures/) or neural network architectures for computer vision.
> It is important to select an architecture that will be powerful enough for the number of training samples that we have. Selecting too powerful model can result in [overfitting](../../3-NeuralNetworks/05-Frameworks/Overfitting.md)
Another good way would be to use and architecture that will automatically adjust to the required complexity. To some extent, **ResNet** architecture and **Inception** are self-adjusting. [More on computer vision architectures](../07-ConvNets/CNN_Architectures.md)
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
# Classification of Oxford Pets using Transfer Learning
Lab Assignment from [AI for Beginners Curriculum](https://github.com/microsoft/ai-for-beginners).
## Task
Imagine you need to develop and application for pet nursery to catalog all pets. One of the great features of such an application would be automatically discovering the breed from a photograph. In this assignment, we will use transfer learning to classify real-life pet images from [Oxford-IIIT](https://www.robots.ox.ac.uk/~vgg/data/pets/) pets dataset.
## The Dataset
We will use the original [Oxford-IIIT](https://www.robots.ox.ac.uk/~vgg/data/pets/) pets dataset, which contains 35 different breeds of dogs and cats.
To download the dataset, use this code snippet:
```python
!wget https://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz
!tar xfz images.tar.gz
!rm images.tar.gz
```
## Stating Notebook
Start the lab by opening [OxfordPets.ipynb](OxfordPets.ipynb)
## Takeaway
Transfer learning and pre-trained networks allow us to solve real-world image classification problems relatively easily. However, pre-trained networks work well on images of similar kind, and if we start classifying very different images (eg. medical images), we are likely to get much worse results.
@@ -0,0 +1,169 @@
# Script file to hide implementation details for PyTorch computer vision module
import builtins
import torch
import torch.nn as nn
from torch.utils import data
import torchvision
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import glob
import os
import zipfile
default_device = 'cuda' if torch.cuda.is_available() else 'cpu'
def load_mnist(batch_size=64):
builtins.data_train = torchvision.datasets.MNIST('./data',
download=True,train=True,transform=ToTensor())
builtins.data_test = torchvision.datasets.MNIST('./data',
download=True,train=False,transform=ToTensor())
builtins.train_loader = torch.utils.data.DataLoader(data_train,batch_size=batch_size)
builtins.test_loader = torch.utils.data.DataLoader(data_test,batch_size=batch_size)
def train_epoch(net,dataloader,lr=0.01,optimizer=None,loss_fn = nn.NLLLoss()):
optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)
net.train()
total_loss,acc,count = 0,0,0
for features,labels in dataloader:
optimizer.zero_grad()
lbls = labels.to(default_device)
out = net(features.to(default_device))
loss = loss_fn(out,lbls) #cross_entropy(out,labels)
loss.backward()
optimizer.step()
total_loss+=loss
_,predicted = torch.max(out,1)
acc+=(predicted==lbls).sum()
count+=len(labels)
return total_loss.item()/count, acc.item()/count
def validate(net, dataloader,loss_fn=nn.NLLLoss()):
net.eval()
count,acc,loss = 0,0,0
with torch.no_grad():
for features,labels in dataloader:
lbls = labels.to(default_device)
out = net(features.to(default_device))
loss += loss_fn(out,lbls)
pred = torch.max(out,1)[1]
acc += (pred==lbls).sum()
count += len(labels)
return loss.item()/count, acc.item()/count
def train(net,train_loader,test_loader,optimizer=None,lr=0.01,epochs=10,loss_fn=nn.NLLLoss()):
optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)
res = { 'train_loss' : [], 'train_acc': [], 'val_loss': [], 'val_acc': []}
for ep in range(epochs):
tl,ta = train_epoch(net,train_loader,optimizer=optimizer,lr=lr,loss_fn=loss_fn)
vl,va = validate(net,test_loader,loss_fn=loss_fn)
print(f"Epoch {ep:2}, Train acc={ta:.3f}, Val acc={va:.3f}, Train loss={tl:.3f}, Val loss={vl:.3f}")
res['train_loss'].append(tl)
res['train_acc'].append(ta)
res['val_loss'].append(vl)
res['val_acc'].append(va)
return res
def train_long(net,train_loader,test_loader,epochs=5,lr=0.01,optimizer=None,loss_fn = nn.NLLLoss(),print_freq=10):
optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)
for epoch in range(epochs):
net.train()
total_loss,acc,count = 0,0,0
for i, (features,labels) in enumerate(train_loader):
lbls = labels.to(default_device)
optimizer.zero_grad()
out = net(features.to(default_device))
loss = loss_fn(out,lbls)
loss.backward()
optimizer.step()
total_loss+=loss
_,predicted = torch.max(out,1)
acc+=(predicted==lbls).sum()
count+=len(labels)
if i%print_freq==0:
print("Epoch {}, minibatch {}: train acc = {}, train loss = {}".format(epoch,i,acc.item()/count,total_loss.item()/count))
vl,va = validate(net,test_loader,loss_fn)
print("Epoch {} done, validation acc = {}, validation loss = {}".format(epoch,va,vl))
def plot_results(hist):
plt.figure(figsize=(15,5))
plt.subplot(121)
plt.plot(hist['train_acc'], label='Training acc')
plt.plot(hist['val_acc'], label='Validation acc')
plt.legend()
plt.subplot(122)
plt.plot(hist['train_loss'], label='Training loss')
plt.plot(hist['val_loss'], label='Validation loss')
plt.legend()
def plot_convolution(t,title=''):
with torch.no_grad():
c = nn.Conv2d(kernel_size=(3,3),out_channels=1,in_channels=1)
c.weight.copy_(t)
fig, ax = plt.subplots(2,6,figsize=(8,3))
fig.suptitle(title,fontsize=16)
for i in range(5):
im = data_train[i][0]
ax[0][i].imshow(im[0])
ax[1][i].imshow(c(im.unsqueeze(0))[0][0])
ax[0][i].axis('off')
ax[1][i].axis('off')
ax[0,5].imshow(t)
ax[0,5].axis('off')
ax[1,5].axis('off')
#plt.tight_layout()
plt.show()
def display_dataset(dataset, n=10,classes=None):
fig,ax = plt.subplots(1,n,figsize=(15,3))
mn = min([dataset[i][0].min() for i in range(n)])
mx = max([dataset[i][0].max() for i in range(n)])
for i in range(n):
ax[i].imshow(np.transpose((dataset[i][0]-mn)/(mx-mn),(1,2,0)))
ax[i].axis('off')
if classes:
ax[i].set_title(classes[dataset[i][1]])
def check_image(fn):
try:
im = Image.open(fn)
im.verify()
return True
except:
return False
def check_image_dir(path):
for fn in glob.glob(path):
if not check_image(fn):
print("Corrupt image: {}".format(fn))
os.remove(fn)
def common_transform():
std_normalize = torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
trans = torchvision.transforms.Compose([
torchvision.transforms.Resize(256),
torchvision.transforms.CenterCrop(224),
torchvision.transforms.ToTensor(),
std_normalize])
return trans
def load_cats_dogs_dataset():
if not os.path.exists('data/PetImages'):
with zipfile.ZipFile('data/kagglecatsanddogs_3367a.zip', 'r') as zip_ref:
zip_ref.extractall('data')
check_image_dir('data/PetImages/Cat/*.jpg')
check_image_dir('data/PetImages/Dog/*.jpg')
dataset = torchvision.datasets.ImageFolder('data/PetImages',transform=common_transform())
trainset, testset = torch.utils.data.random_split(dataset,[20000,len(dataset)-20000])
trainloader = torch.utils.data.DataLoader(trainset,batch_size=32)
testloader = torch.utils.data.DataLoader(trainset,batch_size=32)
return dataset, trainloader, testloader
@@ -0,0 +1,86 @@
# Tensorflow Computer Vision Helper
import tensorflow as tf
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import glob
import os
def plot_convolution(data,t,title=''):
fig, ax = plt.subplots(2,len(data)+1,figsize=(8,3))
fig.suptitle(title,fontsize=16)
tt = np.expand_dims(np.expand_dims(t,2),2)
for i,im in enumerate(data):
ax[0][i].imshow(im)
ximg = np.expand_dims(np.expand_dims(im,2),0)
cim = tf.nn.conv2d(ximg,tt,1,'SAME')
ax[1][i].imshow(cim[0][:,:,0])
ax[0][i].axis('off')
ax[1][i].axis('off')
ax[0,-1].imshow(t)
ax[0,-1].axis('off')
ax[1,-1].axis('off')
#plt.tight_layout()
plt.show()
def plot_results(hist):
fig,ax = plt.subplots(1,2,figsize=(15,3))
ax[0].set_title('Accuracy')
ax[1].set_title('Loss')
for x in ['acc','val_acc']:
ax[0].plot(hist.history[x])
for x in ['loss','val_loss']:
ax[1].plot(hist.history[x])
plt.show()
def display_dataset(dataset, labels=None, n=10, classes=None):
fig,ax = plt.subplots(1,n,figsize=(15,3))
for i in range(n):
ax[i].imshow(dataset[i])
ax[i].axis('off')
if classes is not None and labels is not None:
ax[i].set_title(classes[labels[i][0]])
def check_image(fn):
try:
im = Image.open(fn)
im.verify()
return im.format=='JPEG'
except:
return False
def check_image_dir(path):
for fn in glob.glob(path):
if not check_image(fn):
print("Corrupt image or wrong format: {}".format(fn))
os.remove(fn)
def load_cats_dogs_dataset(batch_size=64):
if not os.path.exists('data/PetImages'):
print("Extracting the dataset")
with zipfile.ZipFile('data/kagglecatsanddogs_3367a.zip', 'r') as zip_ref:
zip_ref.extractall('data')
print("Checking dataset")
check_image_dir('data/PetImages/Cat/*.jpg')
check_image_dir('data/PetImages/Dog/*.jpg')
data_dir = 'data/PetImages'
print("Loading dataset")
ds_train = keras.preprocessing.image_dataset_from_directory(
data_dir,
validation_split = 0.2,
subset = 'training',
seed = 13,
image_size = (224,224),
batch_size = batch_size
)
ds_test = keras.preprocessing.image_dataset_from_directory(
data_dir,
validation_split = 0.2,
subset = 'validation',
seed = 13,
image_size = (224,224),
batch_size = batch_size
)
return ds_train,ds_test