First Commit
This commit is contained in:
+62
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phpml\Dataset;
|
||||
|
||||
use Phpml\Exception\InvalidArgumentException;
|
||||
|
||||
class ArrayDataset implements Dataset
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $samples = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $targets = [];
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $samples, array $targets)
|
||||
{
|
||||
if (count($samples) !== count($targets)) {
|
||||
throw new InvalidArgumentException('Size of given arrays does not match');
|
||||
}
|
||||
|
||||
$this->samples = $samples;
|
||||
$this->targets = $targets;
|
||||
}
|
||||
|
||||
public function getSamples(): array
|
||||
{
|
||||
return $this->samples;
|
||||
}
|
||||
|
||||
public function getTargets(): array
|
||||
{
|
||||
return $this->targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $columns
|
||||
*/
|
||||
public function removeColumns(array $columns): void
|
||||
{
|
||||
foreach ($this->samples as &$sample) {
|
||||
$this->removeColumnsFromSample($sample, $columns);
|
||||
}
|
||||
}
|
||||
|
||||
private function removeColumnsFromSample(array &$sample, array $columns): void
|
||||
{
|
||||
foreach ($columns as $index) {
|
||||
unset($sample[$index]);
|
||||
}
|
||||
|
||||
$sample = array_values($sample);
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phpml\Dataset;
|
||||
|
||||
use Phpml\Exception\FileException;
|
||||
|
||||
class CsvDataset extends ArrayDataset
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $columnNames = [];
|
||||
|
||||
/**
|
||||
* @throws FileException
|
||||
*/
|
||||
public function __construct(string $filepath, int $features, bool $headingRow = true, string $delimiter = ',', int $maxLineLength = 0)
|
||||
{
|
||||
if (!file_exists($filepath)) {
|
||||
throw new FileException(sprintf('File "%s" missing.', basename($filepath)));
|
||||
}
|
||||
|
||||
$handle = fopen($filepath, 'rb');
|
||||
if ($handle === false) {
|
||||
throw new FileException(sprintf('File "%s" can\'t be open.', basename($filepath)));
|
||||
}
|
||||
|
||||
if ($headingRow) {
|
||||
$data = fgetcsv($handle, $maxLineLength, $delimiter);
|
||||
$this->columnNames = array_slice((array) $data, 0, $features);
|
||||
} else {
|
||||
$this->columnNames = range(0, $features - 1);
|
||||
}
|
||||
|
||||
$samples = $targets = [];
|
||||
while ($data = fgetcsv($handle, $maxLineLength, $delimiter)) {
|
||||
$samples[] = array_slice($data, 0, $features);
|
||||
$targets[] = $data[$features];
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
parent::__construct($samples, $targets);
|
||||
}
|
||||
|
||||
public function getColumnNames(): array
|
||||
{
|
||||
return $this->columnNames;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phpml\Dataset;
|
||||
|
||||
interface Dataset
|
||||
{
|
||||
public function getSamples(): array;
|
||||
|
||||
public function getTargets(): array;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phpml\Dataset\Demo;
|
||||
|
||||
use Phpml\Dataset\CsvDataset;
|
||||
|
||||
/**
|
||||
* Classes: 6
|
||||
* Samples per class:
|
||||
* 70 float processed building windows
|
||||
* 17 float processed vehicle windows
|
||||
* 76 non-float processed building windows
|
||||
* 13 containers
|
||||
* 9 tableware
|
||||
* 29 headlamps
|
||||
* Samples total: 214
|
||||
* Features per sample: 9.
|
||||
*/
|
||||
class GlassDataset extends CsvDataset
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$filepath = __DIR__.'/../../../data/glass.csv';
|
||||
parent::__construct($filepath, 9, true);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phpml\Dataset\Demo;
|
||||
|
||||
use Phpml\Dataset\CsvDataset;
|
||||
|
||||
/**
|
||||
* Classes: 3
|
||||
* Samples per class: 50
|
||||
* Samples total: 150
|
||||
* Features per sample: 4.
|
||||
*/
|
||||
class IrisDataset extends CsvDataset
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$filepath = __DIR__.'/../../../data/iris.csv';
|
||||
parent::__construct($filepath, 4, true);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phpml\Dataset\Demo;
|
||||
|
||||
use Phpml\Dataset\CsvDataset;
|
||||
|
||||
/**
|
||||
* Classes: 3
|
||||
* Samples per class: class 1 59; class 2 71; class 3 48
|
||||
* Samples total: 178
|
||||
* Features per sample: 13.
|
||||
*/
|
||||
class WineDataset extends CsvDataset
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$filepath = __DIR__.'/../../../data/wine.csv';
|
||||
parent::__construct($filepath, 13, true);
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phpml\Dataset;
|
||||
|
||||
use Phpml\Exception\DatasetException;
|
||||
|
||||
class FilesDataset extends ArrayDataset
|
||||
{
|
||||
public function __construct(string $rootPath)
|
||||
{
|
||||
if (!is_dir($rootPath)) {
|
||||
throw new DatasetException(sprintf('Dataset root folder "%s" missing.', $rootPath));
|
||||
}
|
||||
|
||||
$this->scanRootPath($rootPath);
|
||||
}
|
||||
|
||||
private function scanRootPath(string $rootPath): void
|
||||
{
|
||||
$dirs = glob($rootPath.DIRECTORY_SEPARATOR.'*', GLOB_ONLYDIR);
|
||||
|
||||
if ($dirs === false) {
|
||||
throw new DatasetException(sprintf('An error occurred during directory "%s" scan', $rootPath));
|
||||
}
|
||||
|
||||
foreach ($dirs as $dir) {
|
||||
$this->scanDir($dir);
|
||||
}
|
||||
}
|
||||
|
||||
private function scanDir(string $dir): void
|
||||
{
|
||||
$target = basename($dir);
|
||||
|
||||
$files = glob($dir.DIRECTORY_SEPARATOR.'*');
|
||||
if ($files === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (array_filter($files, 'is_file') as $file) {
|
||||
$this->samples[] = file_get_contents($file);
|
||||
$this->targets[] = $target;
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phpml\Dataset;
|
||||
|
||||
use Phpml\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* MNIST dataset: http://yann.lecun.com/exdb/mnist/
|
||||
* original mnist dataset reader: https://github.com/AndrewCarterUK/mnist-neural-network-plain-php
|
||||
*/
|
||||
final class MnistDataset extends ArrayDataset
|
||||
{
|
||||
private const MAGIC_IMAGE = 0x00000803;
|
||||
|
||||
private const MAGIC_LABEL = 0x00000801;
|
||||
|
||||
private const IMAGE_ROWS = 28;
|
||||
|
||||
private const IMAGE_COLS = 28;
|
||||
|
||||
public function __construct(string $imagePath, string $labelPath)
|
||||
{
|
||||
$this->samples = $this->readImages($imagePath);
|
||||
$this->targets = $this->readLabels($labelPath);
|
||||
|
||||
if (count($this->samples) !== count($this->targets)) {
|
||||
throw new InvalidArgumentException('Must have the same number of images and labels');
|
||||
}
|
||||
}
|
||||
|
||||
private function readImages(string $imagePath): array
|
||||
{
|
||||
$stream = fopen($imagePath, 'rb');
|
||||
|
||||
if ($stream === false) {
|
||||
throw new InvalidArgumentException('Could not open file: '.$imagePath);
|
||||
}
|
||||
|
||||
$images = [];
|
||||
|
||||
try {
|
||||
$header = fread($stream, 16);
|
||||
|
||||
$fields = unpack('Nmagic/Nsize/Nrows/Ncols', (string) $header);
|
||||
|
||||
if ($fields['magic'] !== self::MAGIC_IMAGE) {
|
||||
throw new InvalidArgumentException('Invalid magic number: '.$imagePath);
|
||||
}
|
||||
|
||||
if ($fields['rows'] != self::IMAGE_ROWS) {
|
||||
throw new InvalidArgumentException('Invalid number of image rows: '.$imagePath);
|
||||
}
|
||||
|
||||
if ($fields['cols'] != self::IMAGE_COLS) {
|
||||
throw new InvalidArgumentException('Invalid number of image cols: '.$imagePath);
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $fields['size']; $i++) {
|
||||
$imageBytes = fread($stream, $fields['rows'] * $fields['cols']);
|
||||
|
||||
// Convert to float between 0 and 1
|
||||
$images[] = array_map(function ($b) {
|
||||
return $b / 255;
|
||||
}, array_values(unpack('C*', (string) $imageBytes)));
|
||||
}
|
||||
} finally {
|
||||
fclose($stream);
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
private function readLabels(string $labelPath): array
|
||||
{
|
||||
$stream = fopen($labelPath, 'rb');
|
||||
|
||||
if ($stream === false) {
|
||||
throw new InvalidArgumentException('Could not open file: '.$labelPath);
|
||||
}
|
||||
|
||||
$labels = [];
|
||||
|
||||
try {
|
||||
$header = fread($stream, 8);
|
||||
|
||||
$fields = unpack('Nmagic/Nsize', (string) $header);
|
||||
|
||||
if ($fields['magic'] !== self::MAGIC_LABEL) {
|
||||
throw new InvalidArgumentException('Invalid magic number: '.$labelPath);
|
||||
}
|
||||
|
||||
$labels = fread($stream, $fields['size']);
|
||||
} finally {
|
||||
fclose($stream);
|
||||
}
|
||||
|
||||
return array_values(unpack('C*', (string) $labels));
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Phpml\Dataset;
|
||||
|
||||
use Phpml\Exception\DatasetException;
|
||||
use Phpml\Exception\FileException;
|
||||
|
||||
class SvmDataset extends ArrayDataset
|
||||
{
|
||||
public function __construct(string $filePath)
|
||||
{
|
||||
[$samples, $targets] = self::readProblem($filePath);
|
||||
|
||||
parent::__construct($samples, $targets);
|
||||
}
|
||||
|
||||
private static function readProblem(string $filePath): array
|
||||
{
|
||||
$handle = self::openFile($filePath);
|
||||
|
||||
$samples = [];
|
||||
$targets = [];
|
||||
$maxIndex = 0;
|
||||
while (false !== $line = fgets($handle)) {
|
||||
[$sample, $target, $maxIndex] = self::processLine($line, $maxIndex);
|
||||
$samples[] = $sample;
|
||||
$targets[] = $target;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
foreach ($samples as &$sample) {
|
||||
$sample = array_pad($sample, $maxIndex + 1, 0);
|
||||
}
|
||||
|
||||
return [$samples, $targets];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
private static function openFile(string $filePath)
|
||||
{
|
||||
if (!file_exists($filePath)) {
|
||||
throw new FileException(sprintf('File "%s" missing.', basename($filePath)));
|
||||
}
|
||||
|
||||
$handle = fopen($filePath, 'rb');
|
||||
if ($handle === false) {
|
||||
throw new FileException(sprintf('File "%s" can\'t be open.', basename($filePath)));
|
||||
}
|
||||
|
||||
return $handle;
|
||||
}
|
||||
|
||||
private static function processLine(string $line, int $maxIndex): array
|
||||
{
|
||||
$columns = self::parseLine($line);
|
||||
|
||||
$target = self::parseTargetColumn($columns[0]);
|
||||
$sample = array_fill(0, $maxIndex + 1, 0);
|
||||
|
||||
$n = count($columns);
|
||||
for ($i = 1; $i < $n; ++$i) {
|
||||
[$index, $value] = self::parseFeatureColumn($columns[$i]);
|
||||
if ($index > $maxIndex) {
|
||||
$maxIndex = $index;
|
||||
$sample = array_pad($sample, $maxIndex + 1, 0);
|
||||
}
|
||||
|
||||
$sample[$index] = $value;
|
||||
}
|
||||
|
||||
return [$sample, $target, $maxIndex];
|
||||
}
|
||||
|
||||
private static function parseLine(string $line): array
|
||||
{
|
||||
$line = explode('#', $line, 2)[0];
|
||||
$line = rtrim($line);
|
||||
$line = str_replace("\t", ' ', $line);
|
||||
|
||||
return explode(' ', $line);
|
||||
}
|
||||
|
||||
private static function parseTargetColumn(string $column): float
|
||||
{
|
||||
if (!is_numeric($column)) {
|
||||
throw new DatasetException(sprintf('Invalid target "%s".', $column));
|
||||
}
|
||||
|
||||
return (float) $column;
|
||||
}
|
||||
|
||||
private static function parseFeatureColumn(string $column): array
|
||||
{
|
||||
$feature = explode(':', $column, 2);
|
||||
if (count($feature) !== 2) {
|
||||
throw new DatasetException(sprintf('Invalid value "%s".', $column));
|
||||
}
|
||||
|
||||
$index = self::parseFeatureIndex($feature[0]);
|
||||
$value = self::parseFeatureValue($feature[1]);
|
||||
|
||||
return [$index, $value];
|
||||
}
|
||||
|
||||
private static function parseFeatureIndex(string $index): int
|
||||
{
|
||||
if (!is_numeric($index) || !ctype_digit($index)) {
|
||||
throw new DatasetException(sprintf('Invalid index "%s".', $index));
|
||||
}
|
||||
|
||||
if ((int) $index < 1) {
|
||||
throw new DatasetException(sprintf('Invalid index "%s".', $index));
|
||||
}
|
||||
|
||||
return (int) $index - 1;
|
||||
}
|
||||
|
||||
private static function parseFeatureValue(string $value): float
|
||||
{
|
||||
if (!is_numeric($value)) {
|
||||
throw new DatasetException(sprintf('Invalid value "%s".', $value));
|
||||
}
|
||||
|
||||
return (float) $value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user