├── .gitignore
├── .travis.yml
├── LICENSE
├── MANIFEST.in
├── README.md
├── docs
├── conf.py
├── index.rst
├── modules.rst
├── sktensor.rst
└── usage.rst
├── examples
└── cp_sensory_bread_data.py
├── setup.cfg
├── setup.py
└── sktensor
├── __init__.py
├── core.py
├── cp.py
├── dedicom.py
├── dtensor.py
├── indscal.py
├── ktensor.py
├── pyutils.py
├── rescal.py
├── setup.py
├── sptensor.py
├── tests
├── __init__.py
├── sptensor_fixture.py
├── sptensor_rand_fixture.py
├── test_base.py
├── test_dtensor.py
├── test_ktensor.py
├── test_pyutils.py
├── test_sptensor.py
├── test_tucker_hooi.py
├── test_utils.py
└── ttm_fixture.py
├── tucker.py
├── utils.py
└── version.py
/.gitignore:
--------------------------------------------------------------------------------
1 | syntax: glob
2 | *.pyc
3 | build
4 | dist
5 | scikit_tensor.egg-info
6 | docs/build
7 | docs/source/api
8 | .cache
9 | .eggs/
10 | venv/
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | cache: pip
3 | python:
4 | - "2.7_with_system_site_packages"
5 | - 3.4
6 | before_install:
7 | - sudo apt-get update -qq
8 | - if [[ $TRAVIS_PYTHON_VERSION == *"2."* ]]; then sudo apt-get install python-numpy python-scipy; fi
9 | - if [[ $TRAVIS_PYTHON_VERSION == *"3."* ]]; then sudo apt-get install python3-numpy python3-scipy; fi
10 | - sudo apt-get install -qq libatlas-dev libatlas-base-dev liblapack-dev gfortran
11 | install:
12 | - pip install -U pip wheel
13 | - pip install numpy
14 | - pip install scipy nose pytest sphinx numpydoc sphinx-rtd-theme
15 | - pip install -e .
16 | script:
17 | - py.test
18 | - python setup.py build_sphinx
19 | - python setup.py egg_info -b.dev sdist --formats gztar
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.rst
2 | recursive-include docs *
3 | recursive-include examples *
4 | recursive-include sktensor *.c *.h *.pyx *.pxd
5 | recursive-include sktensor/datasets *.csv *.csv.gz *.rst *.jpg *.txt
6 | include LICENSE
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scikit-tensor
2 | 
3 |
4 | scikit-tensor is a Python module for multilinear algebra and tensor
5 | factorizations. Currently, scikit-tensor supports basic tensor operations
6 | such as folding/unfolding, tensor-matrix and tensor-vector products as
7 | well as the following tensor factorizations:
8 |
9 | * Canonical / Parafac Decomposition
10 | * Tucker Decomposition
11 | * RESCAL
12 | * DEDICOM
13 | * INDSCAL
14 |
15 | Moreover, all operations support dense and tensors.
16 |
17 | #### Dependencies
18 | The required dependencies to build the software are `Numpy >= 1.3`, `SciPy >= 0.7`.
19 |
20 | #### Usage
21 | Example script to decompose sensory bread data (available from http://www.models.life.ku.dk/datasets) using CP-ALS
22 |
23 | ```python
24 | import logging
25 | from scipy.io.matlab import loadmat
26 | from sktensor import dtensor, cp_als
27 |
28 | # Set logging to DEBUG to see CP-ALS information
29 | logging.basicConfig(level=logging.DEBUG)
30 |
31 | # Load Matlab data and convert it to dense tensor format
32 | mat = loadmat('../data/sensory-bread/brod.mat')
33 | T = dtensor(mat['X'])
34 |
35 | # Decompose tensor using CP-ALS
36 | P, fit, itr, exectimes = cp_als(T, 3, init='random')
37 | ```
38 |
39 | #### Install
40 | This package uses distutils, which is the default way of installing python modules. The use of virtual environments is recommended.
41 |
42 | pip install scikit-tensor
43 |
44 | To install in development mode
45 |
46 | git clone git@github.com:mnick/scikit-tensor.git
47 | pip install -e scikit-tensor/
48 |
49 | #### Contributing & Development
50 | scikit-tensor is still an extremely young project, and I'm happy for any contributions (patches, code, bugfixes, *documentation*, whatever) to get it to a stable and useful point. Feel free to get in touch with me via email (mnick at AT mit DOT edu) or directly via github.
51 |
52 | Development is synchronized via git. To clone this repository, run
53 |
54 | git clone git://github.com/mnick/scikit-tensor.git
55 |
56 | #### Authors
57 | Maximilian Nickel: [Web](http://web.mit.edu/~mnick/www), [Email](mailto://mnick AT mit DOT edu), [Twitter](http://twitter.com/mnick)
58 |
59 | #### License
60 | scikit-tensor is licensed under the [GPLv3](http://www.gnu.org/licenses/gpl-3.0.txt)
61 |
62 | #### Related Projects
63 | * [Matlab Tensor Toolbox](http://www.sandia.gov/~tgkolda/TensorToolbox/index-2.5.html):
64 | A Matlab toolbox for tensor factorizations and tensor operations freely available for research and evaluation.
65 | * [Matlab Tensorlab](http://www.tensorlab.net/)
66 | A Matlab toolbox for tensor factorizations, complex optimization, and tensor optimization freely available for
67 | non-commercial academic research.
68 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # scikit-tensor documentation build configuration file, created by
5 | # sphinx-quickstart on Sun Apr 20 14:28:17 2014.
6 | #
7 | # This file is execfile()d with the current directory set to its
8 | # containing dir.
9 | #
10 | # Note that not all possible configuration values are present in this
11 | # autogenerated file.
12 | #
13 | # All configuration values have a default; values that are commented out
14 | # serve to show the default.
15 |
16 | import sys
17 | import os
18 | import sphinx_rtd_theme
19 |
20 | # If extensions (or modules to document with autodoc) are in another directory,
21 | # add these directories to sys.path here. If the directory is relative to the
22 | # documentation root, use os.path.abspath to make it absolute, like shown here.
23 | # sys.path.insert(0, os.path.abspath('.'))
24 |
25 | # -- General configuration ------------------------------------------------
26 |
27 | # If your documentation needs a minimal Sphinx version, state it here.
28 | # needs_sphinx = '1.0'
29 |
30 | # Add any Sphinx extension module names here, as strings. They can be
31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
32 | # ones.
33 | extensions = [
34 | 'sphinx.ext.autodoc',
35 | 'sphinx.ext.pngmath',
36 | 'sphinx.ext.napoleon',
37 | 'sphinx.ext.autosummary',
38 | 'numpydoc'
39 | ]
40 |
41 | # Add any paths that contain templates here, relative to this directory.
42 | templates_path = ['_templates']
43 |
44 | # The suffix of source filenames.
45 | source_suffix = '.rst'
46 |
47 | # The encoding of source files.
48 | # source_encoding = 'utf-8-sig'
49 |
50 | # The master toctree document.
51 | master_doc = 'index'
52 |
53 | # General information about the project.
54 | project = 'scikit-tensor'
55 | copyright = '2016, Maximilian Nickel'
56 |
57 | # The version info for the project you're documenting, acts as replacement for
58 | # |version| and |release|, also used in various other places throughout the
59 | # built documents.
60 | #
61 | # The short X.Y version.
62 | version = '0.1'
63 | # The full version, including alpha/beta/rc tags.
64 | release = '0.1'
65 |
66 | # The language for content autogenerated by Sphinx. Refer to documentation
67 | # for a list of supported languages.
68 | # language = None
69 |
70 | # There are two options for replacing |today|: either, you set today to some
71 | # non-false value, then it is used:
72 | # today = ''
73 | # Else, today_fmt is used as the format for a strftime call.
74 | # today_fmt = '%B %d, %Y'
75 |
76 | # List of patterns, relative to source directory, that match files and
77 | # directories to ignore when looking for source files.
78 | exclude_patterns = ['_build', '**tests**', '**setup**', '**extern**',
79 | '**data**']
80 |
81 | # The reST default role (used for this markup: `text`) to use for all
82 | # documents.
83 | # default_role = None
84 |
85 | # If true, '()' will be appended to :func: etc. cross-reference text.
86 | # add_function_parentheses = True
87 |
88 | # If true, the current module name will be prepended to all description
89 | # unit titles (such as .. function::).
90 | # add_module_names = True
91 |
92 | # If true, sectionauthor and moduleauthor directives will be shown in the
93 | # output. They are ignored by default.
94 | # show_authors = False
95 |
96 | # The name of the Pygments (syntax highlighting) style to use.
97 | pygments_style = 'sphinx'
98 |
99 | # A list of ignored prefixes for module index sorting.
100 | # modindex_common_prefix = []
101 |
102 | # If true, keep warnings as "system message" paragraphs in the built documents.
103 | # keep_warnings = False
104 |
105 |
106 | # -- Options for HTML output ----------------------------------------------
107 |
108 | # The theme to use for HTML and HTML Help pages. See the documentation for
109 | # a list of builtin themes.
110 | html_theme = "sphinx_rtd_theme"
111 |
112 | # Theme options are theme-specific and customize the look and feel of a theme
113 | # further. For a list of options available for each theme, see the
114 | # documentation.
115 | # html_theme_options = {}
116 |
117 | # Add any paths that contain custom themes here, relative to this directory.
118 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
119 |
120 | # The name for this set of Sphinx documents. If None, it defaults to
121 | # " v documentation".
122 | # html_title = None
123 |
124 | # A shorter title for the navigation bar. Default is the same as html_title.
125 | # html_short_title = None
126 |
127 | # The name of an image file (relative to this directory) to place at the top
128 | # of the sidebar.
129 | # html_logo = None
130 |
131 | # The name of an image file (within the static path) to use as favicon of the
132 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
133 | # pixels large.
134 | # html_favicon = None
135 |
136 | # Add any paths that contain custom static files (such as style sheets) here,
137 | # relative to this directory. They are copied after the builtin static files,
138 | # so a file named "default.css" will overwrite the builtin "default.css".
139 | html_static_path = ['_static']
140 |
141 | # Add any extra paths that contain custom files (such as robots.txt or
142 | # .htaccess) here, relative to this directory. These files are copied
143 | # directly to the root of the documentation.
144 | # html_extra_path = []
145 |
146 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
147 | # using the given strftime format.
148 | # html_last_updated_fmt = '%b %d, %Y'
149 |
150 | # If true, SmartyPants will be used to convert quotes and dashes to
151 | # typographically correct entities.
152 | # html_use_smartypants = True
153 |
154 | # Custom sidebar templates, maps document names to template names.
155 | # html_sidebars = {}
156 |
157 | # Additional templates that should be rendered to pages, maps page names to
158 | # template names.
159 | # html_additional_pages = {}
160 |
161 | # If false, no module index is generated.
162 | # html_domain_indices = True
163 |
164 | # If false, no index is generated.
165 | # html_use_index = True
166 |
167 | # If true, the index is split into individual pages for each letter.
168 | # html_split_index = False
169 |
170 | # If true, links to the reST sources are added to the pages.
171 | # html_show_sourcelink = True
172 |
173 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
174 | # html_show_sphinx = True
175 |
176 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
177 | # html_show_copyright = True
178 |
179 | # If true, an OpenSearch description file will be output, and all pages will
180 | # contain a tag referring to it. The value of this option must be the
181 | # base URL from which the finished HTML is served.
182 | # html_use_opensearch = ''
183 |
184 | # This is the file name suffix for HTML files (e.g. ".xhtml").
185 | # html_file_suffix = None
186 |
187 | # Output file base name for HTML help builder.
188 | htmlhelp_basename = 'dspydoc'
189 |
190 |
191 | # -- Options for LaTeX output ---------------------------------------------
192 |
193 | pngmath_latex_preamble = (
194 | '\\usepackage{amsmath}\n'
195 | '\\usepackage{amssymb}\n'
196 | '\\newcommand{\\unfold}[2]{{#1}_{(#2)}}\n'
197 | '\\newcommand{\\ten}[1]{\\mathcal{#1}}\n'
198 | '\\newcommand{\\kr}{\\otimes}'
199 | )
200 |
201 | latex_elements = {
202 | # The paper size ('letterpaper' or 'a4paper').
203 | # 'papersize': 'letterpaper',
204 |
205 | # The font size ('10pt', '11pt' or '12pt').
206 | # 'pointsize': '10pt',
207 |
208 | # Additional stuff for the LaTeX preamble.
209 | 'preamble': pngmath_latex_preamble,
210 | }
211 |
212 | # Grouping the document tree into LaTeX files. List of tuples
213 | # (source start file, target name, title,
214 | # author, documentclass [howto, manual, or own class]).
215 | latex_documents = [
216 | ('index', 'index.tex', 'scikit-tensor Documentation',
217 | 'Maximilian Nickel', 'manual'),
218 | ]
219 |
220 | # The name of an image file (relative to this directory) to place at the top of
221 | # the title page.
222 | # latex_logo = None
223 |
224 | # For "manual" documents, if this is true, then toplevel headings are parts,
225 | # not chapters.
226 | # latex_use_parts = False
227 |
228 | # If true, show page references after internal links.
229 | # latex_show_pagerefs = False
230 |
231 | # If true, show URL addresses after external links.
232 | # latex_show_urls = False
233 |
234 | # Documents to append as an appendix to all manuals.
235 | # latex_appendices = []
236 |
237 | # If false, no module index is generated.
238 | # latex_domain_indices = True
239 |
240 |
241 | # -- Options for manual page output ---------------------------------------
242 |
243 | # One entry per manual page. List of tuples
244 | # (source start file, name, description, authors, manual section).
245 | man_pages = [
246 | ('index', 'scikit-tensor', 'scikit-tensor Documentation',
247 | ['Maximilian Nickel'], 1)
248 | ]
249 |
250 | # If true, show URL addresses after external links.
251 | # man_show_urls = False
252 |
253 |
254 | # -- Options for Texinfo output -------------------------------------------
255 |
256 | # Grouping the document tree into Texinfo files. List of tuples
257 | # (source start file, target name, title, author,
258 | # dir menu entry, description, category)
259 | texinfo_documents = [
260 | ('index', 'scikit-tensor', 'scikit-tensor Documentation',
261 | 'Maximilian Nickel', 'scikit-tensor',
262 | 'One line description of project.',
263 | 'Miscellaneous'),
264 | ]
265 |
266 | # Documents to append as an appendix to all manuals.
267 | # texinfo_appendices = []
268 |
269 | # If false, no module index is generated.
270 | # texinfo_domain_indices = True
271 |
272 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
273 | # texinfo_show_urls = 'footnote'
274 |
275 | # If true, do not generate a @detailmenu in the "Top" node's menu.
276 | # texinfo_no_detailmenu = False
277 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Index
2 | =====
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | self
8 | usage
9 | modules
10 |
11 | scikit-tensor is a Python module for multilinear algebra and tensor factorizations. Currently, scikit-tensor supports basic tensor operations such as folding/unfolding, tensor-matrix and tensor-vector products as well as the following tensor factorizations:
12 |
13 | - Canonical / Parafac Decomposition
14 | - Tucker Decomposition
15 | - RESCAL
16 | - DEDICOM
17 | - INDSCAL
18 |
19 | Moreover, all operations support dense and tensors.
20 |
21 | Installation
22 | ============
23 |
24 | .. code:: sh
25 |
26 | pip install scikit-tensor
27 |
28 | Indices and tables
29 | ==================
30 |
31 | * :ref:`genindex`
32 | * :ref:`modindex`
33 | * :ref:`search`
34 |
--------------------------------------------------------------------------------
/docs/modules.rst:
--------------------------------------------------------------------------------
1 | Modules
2 | =======
3 |
4 | .. toctree::
5 |
6 | sktensor
7 |
--------------------------------------------------------------------------------
/docs/sktensor.rst:
--------------------------------------------------------------------------------
1 | sktensor package
2 | ================
3 |
4 | Submodules
5 | ----------
6 |
7 | sktensor.core module
8 | --------------------
9 |
10 | .. automodule:: sktensor.core
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | sktensor.cp module
16 | ------------------
17 |
18 | .. automodule:: sktensor.cp
19 | :members:
20 | :undoc-members:
21 | :show-inheritance:
22 |
23 | sktensor.dedicom module
24 | -----------------------
25 |
26 | .. automodule:: sktensor.dedicom
27 | :members:
28 | :undoc-members:
29 | :show-inheritance:
30 |
31 | sktensor.dtensor module
32 | -----------------------
33 |
34 | .. automodule:: sktensor.dtensor
35 | :members:
36 | :undoc-members:
37 | :show-inheritance:
38 |
39 | sktensor.indscal module
40 | -----------------------
41 |
42 | .. automodule:: sktensor.indscal
43 | :members:
44 | :undoc-members:
45 | :show-inheritance:
46 |
47 | sktensor.ktensor module
48 | -----------------------
49 |
50 | .. automodule:: sktensor.ktensor
51 | :members:
52 | :undoc-members:
53 | :show-inheritance:
54 |
55 | sktensor.pyutils module
56 | -----------------------
57 |
58 | .. automodule:: sktensor.pyutils
59 | :members:
60 | :undoc-members:
61 | :show-inheritance:
62 |
63 | sktensor.rescal module
64 | ----------------------
65 |
66 | .. automodule:: sktensor.rescal
67 | :members:
68 | :undoc-members:
69 | :show-inheritance:
70 |
71 | sktensor.setup module
72 | ---------------------
73 |
74 | .. automodule:: sktensor.setup
75 | :members:
76 | :undoc-members:
77 | :show-inheritance:
78 |
79 | sktensor.sptensor module
80 | ------------------------
81 |
82 | .. automodule:: sktensor.sptensor
83 | :members:
84 | :undoc-members:
85 | :show-inheritance:
86 |
87 | sktensor.tucker module
88 | ----------------------
89 |
90 | .. automodule:: sktensor.tucker
91 | :members:
92 | :undoc-members:
93 | :show-inheritance:
94 |
95 | sktensor.utils module
96 | ---------------------
97 |
98 | .. automodule:: sktensor.utils
99 | :members:
100 | :undoc-members:
101 | :show-inheritance:
102 |
103 | sktensor.version module
104 | -----------------------
105 |
106 | .. automodule:: sktensor.version
107 | :members:
108 | :undoc-members:
109 | :show-inheritance:
110 |
111 |
112 | Module contents
113 | ---------------
114 |
115 | .. automodule:: sktensor
116 | :members:
117 | :undoc-members:
118 | :show-inheritance:
119 |
--------------------------------------------------------------------------------
/docs/usage.rst:
--------------------------------------------------------------------------------
1 | Usage
2 | =====
3 |
4 | Example script to decompose sensory bread data (available from http://www.models.life.ku.dk/datasets) using CP-ALS
5 |
6 | .. code:: python
7 |
8 | import logging
9 | from scipy.io.matlab import loadmat
10 | from sktensor import dtensor, cp_als
11 |
12 | # Set logging to DEBUG to see CP-ALS information
13 | logging.basicConfig(level=logging.DEBUG)
14 |
15 | # Load Matlab data and convert it to dense tensor format
16 | mat = loadmat('../data/sensory-bread/brod.mat')
17 | T = dtensor(mat['X'])
18 |
19 | # Decompose tensor using CP-ALS
20 | P, fit, itr, exectimes = cp_als(T, 3, init='random')
21 |
--------------------------------------------------------------------------------
/examples/cp_sensory_bread_data.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import logging
4 | from scipy.io.matlab import loadmat
5 | from sktensor import dtensor, cp_als
6 |
7 | # Set logging to DEBUG to see CP-ALS information
8 | logging.basicConfig(level=logging.DEBUG)
9 |
10 | # Load Matlab data and convert it to dense tensor format
11 | mat = loadmat('../data/sensory-bread/brod.mat')
12 | T = dtensor(mat['X'])
13 |
14 | # Decompose tensor using CP-ALS
15 | P, fit, itr, exectimes = cp_als(T, 3, init='random')
16 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | descr = """Python module for multilinear algebra and tensor factorizations"""
3 |
4 | import os
5 | import sys
6 |
7 | DISTNAME = 'scikit-tensor'
8 | DESCRIPTION = descr
9 | with open(os.path.join(os.path.dirname(__file__), 'README.md')) as f:
10 | LONG_DESCRIPTION = f.read()
11 | MAINTAINER = 'Maximilian Nickel',
12 | MAINTAINER_EMAIL = 'mnick@mit.edu',
13 | URL = 'http://github.com/mnick/scikit-tensor'
14 | LICENSE = 'GPLv3'
15 | DOWNLOAD_URL = URL
16 | PACKAGE_NAME = 'sktensor'
17 | EXTRA_INFO = dict(
18 | classifiers=[
19 | "Development Status :: 3 - Alpha",
20 | 'Intended Audience :: Developers',
21 | 'Intended Audience :: Science/Research',
22 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
23 | 'Topic :: Scientific/Engineering',
24 | 'Topic :: Software Development',
25 | 'Operating System :: Microsoft :: Windows',
26 | 'Operating System :: POSIX',
27 | 'Operating System :: Unix',
28 | 'Operating System :: MacOS',
29 | 'Programming Language :: Python :: 2',
30 | 'Programming Language :: Python :: 2.6',
31 | 'Programming Language :: Python :: 2.7',
32 | 'Programming Language :: Python :: 3',
33 | 'Programming Language :: Python :: 3.3',
34 | ]
35 | )
36 |
37 | try:
38 | import setuptools # If you want to enable 'python setup.py develop'
39 | EXTRA_INFO.update(dict(
40 | zip_safe=False, # the package can run out of an .egg file
41 | include_package_data=True,
42 | ))
43 | except:
44 | print('setuptools module not found.')
45 | print("Install setuptools if you want to enable 'python setup.py develop'.")
46 |
47 |
48 | def configuration(parent_package='', top_path=None, package_name=DISTNAME):
49 | if os.path.exists('MANIFEST'):
50 | os.remove('MANIFEST')
51 |
52 | from numpy.distutils.misc_util import Configuration
53 | config = Configuration(None, parent_package, top_path)
54 |
55 | # Avoid non-useful msg: "Ignoring attempt to set 'name' (from ... "
56 | config.set_options(
57 | ignore_setup_xxx_py=True,
58 | assume_default_configuration=True,
59 | delegate_options_to_subpackages=True,
60 | quiet=True
61 | )
62 |
63 | config.add_subpackage(PACKAGE_NAME)
64 | return config
65 |
66 |
67 | def get_version():
68 | """Obtain the version number"""
69 | import imp
70 | mod = imp.load_source('version', os.path.join(PACKAGE_NAME, 'version.py'))
71 | return mod.__version__
72 |
73 |
74 | def setup_package():
75 | # Call the setup function
76 | metadata = dict(
77 | name=DISTNAME,
78 | maintainer=MAINTAINER,
79 | maintainer_email=MAINTAINER_EMAIL,
80 | description=DESCRIPTION,
81 | license=LICENSE,
82 | url=URL,
83 | download_url=DOWNLOAD_URL,
84 | long_description=LONG_DESCRIPTION,
85 | version=get_version(),
86 | install_requires=[
87 | 'numpy',
88 | 'scipy',
89 | 'nose'
90 | ],
91 | #test_suite="nose.collector",
92 | **EXTRA_INFO
93 | )
94 |
95 | if (len(sys.argv) >= 2
96 | and ('--help' in sys.argv[1:] or sys.argv[1]
97 | in ('--help-commands', 'egg_info', '--version', 'clean'))):
98 |
99 | # For these actions, NumPy is not required.
100 | #
101 | # They are required to succeed without Numpy for example when
102 | # pip is used to install Scikit when Numpy is not yet present in
103 | # the system.
104 | try:
105 | from setuptools import setup
106 | except ImportError:
107 | from distutils.core import setup
108 |
109 | metadata['version'] = get_version()
110 | else:
111 | metadata['configuration'] = configuration
112 | from numpy.distutils.core import setup
113 |
114 |
115 | setup(**metadata)
116 |
117 | if __name__ == "__main__":
118 | setup_package()
119 |
--------------------------------------------------------------------------------
/sktensor/__init__.py:
--------------------------------------------------------------------------------
1 | from .version import __version__
2 |
3 | from .utils import *
4 | from .core import *
5 |
6 | # data types
7 | from .sptensor import sptensor, unfolded_sptensor
8 | from .dtensor import dtensor, unfolded_dtensor
9 | from .ktensor import ktensor
10 |
11 | # import algorithms
12 | from .cp import als as cp_als
13 | from .tucker import hooi as tucker_hooi
14 | from .tucker import hooi as tucker_hosvd
15 |
--------------------------------------------------------------------------------
/sktensor/core.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2013 Maximilian Nickel
2 | #
3 | # This program is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU General Public License as published by
5 | # the Free Software Foundation, either version 3 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 | import numpy as np
17 | from numpy import array, dot, zeros, ones, arange, kron
18 | from numpy import setdiff1d
19 | from scipy.linalg import eigh
20 | from scipy.sparse import issparse as issparse_mat
21 | from scipy.sparse import csr_matrix
22 | from scipy.sparse.linalg import eigsh
23 | from abc import ABCMeta, abstractmethod
24 | from .pyutils import is_sequence, func_attr
25 | #from coremod import khatrirao
26 |
27 | import sys
28 | import types
29 |
30 | module_funs = []
31 |
32 |
33 | def modulefunction(func):
34 | module_funs.append(func_attr(func, 'name'))
35 |
36 |
37 | class tensor_mixin(object):
38 | """
39 | Base tensor class from which all tensor classes are subclasses.
40 | Can not be instaniated
41 |
42 | See also
43 | --------
44 | sktensor.dtensor : Subclass for *dense* tensors.
45 | sktensor.sptensor : Subclass for *sparse* tensors.
46 | """
47 |
48 | __metaclass__ = ABCMeta
49 |
50 | def ttm(self, V, mode=None, transp=False, without=False):
51 | """
52 | Tensor times matrix product
53 |
54 | Parameters
55 | ----------
56 | V : M x N array_like or list of M_i x N_i array_likes
57 | Matrix or list of matrices for which the tensor times matrix
58 | products should be performed
59 | mode : int or list of int's, optional
60 | Modes along which the tensor times matrix products should be
61 | performed
62 | transp: boolean, optional
63 | If True, tensor times matrix products are computed with
64 | transpositions of matrices
65 | without: boolean, optional
66 | It True, tensor times matrix products are performed along all
67 | modes **except** the modes specified via parameter ``mode``
68 |
69 |
70 | Examples
71 | --------
72 | Create dense tensor
73 |
74 | >>> T = zeros((3, 4, 2))
75 | >>> T[:, :, 0] = [[ 1, 4, 7, 10], [ 2, 5, 8, 11], [3, 6, 9, 12]]
76 | >>> T[:, :, 1] = [[13, 16, 19, 22], [14, 17, 20, 23], [15, 18, 21, 24]]
77 | >>> T = dtensor(T)
78 |
79 | Create matrix
80 |
81 | >>> V = array([[1, 3, 5], [2, 4, 6]])
82 |
83 | Multiply tensor with matrix along mode 0
84 |
85 | >>> Y = T.ttm(V, 0)
86 | >>> Y[:, :, 0]
87 | array([[ 22., 49., 76., 103.],
88 | [ 28., 64., 100., 136.]])
89 | >>> Y[:, :, 1]
90 | array([[ 130., 157., 184., 211.],
91 | [ 172., 208., 244., 280.]])
92 |
93 | """
94 | if mode is None:
95 | mode = range(self.ndim)
96 | if isinstance(V, np.ndarray):
97 | Y = self._ttm_compute(V, mode, transp)
98 | elif is_sequence(V):
99 | dims, vidx = check_multiplication_dims(mode, self.ndim, len(V), vidx=True, without=without)
100 | Y = self._ttm_compute(V[vidx[0]], dims[0], transp)
101 | for i in xrange(1, len(dims)):
102 | Y = Y._ttm_compute(V[vidx[i]], dims[i], transp)
103 | return Y
104 |
105 | def ttv(self, v, modes=[], without=False):
106 | """
107 | Tensor times vector product
108 |
109 | Parameters
110 | ----------
111 | v : 1-d array or tuple of 1-d arrays
112 | Vector to be multiplied with tensor.
113 | modes : array_like of integers, optional
114 | Modes in which the vectors should be multiplied.
115 | without : boolean, optional
116 | If True, vectors are multiplied in all modes **except** the
117 | modes specified in ``modes``.
118 |
119 | """
120 | if not isinstance(v, tuple):
121 | v = (v, )
122 | dims, vidx = check_multiplication_dims(modes, self.ndim, len(v), vidx=True, without=without)
123 | for i in range(len(dims)):
124 | if not len(v[vidx[i]]) == self.shape[dims[i]]:
125 | raise ValueError('Multiplicant is wrong size')
126 | remdims = np.setdiff1d(range(self.ndim), dims)
127 | return self._ttv_compute(v, dims, vidx, remdims)
128 |
129 | #@abstractmethod
130 | #def ttt(self, other, modes=None):
131 | # pass
132 |
133 | @abstractmethod
134 | def _ttm_compute(self, V, mode, transp):
135 | pass
136 |
137 | @abstractmethod
138 | def _ttv_compute(self, v, dims, vidx, remdims):
139 | pass
140 |
141 | @abstractmethod
142 | def unfold(self, rdims, cdims=None, transp=False):
143 | pass
144 |
145 | @abstractmethod
146 | def uttkrp(self, U, mode):
147 | """
148 | Unfolded tensor times Khatri-Rao product:
149 | :math:`M = \\unfold{X}{3} (U_1 \kr \cdots \kr U_N)`
150 |
151 | Computes the _matrix_ product of the unfolding
152 | of a tensor and the Khatri-Rao product of multiple matrices.
153 | Efficient computations are perfomed by the respective
154 | tensor implementations.
155 |
156 | Parameters
157 | ----------
158 | U : list of array-likes
159 | Matrices for which the Khatri-Rao product is computed and
160 | which are multiplied with the tensor in mode ``mode``.
161 | mode: int
162 | Mode in which the Khatri-Rao product of ``U`` is multiplied
163 | with the tensor.
164 |
165 | Returns
166 | -------
167 | M : np.ndarray
168 | Matrix which is the result of the matrix product of the unfolding of
169 | the tensor and the Khatri-Rao product of ``U``
170 |
171 | See also
172 | --------
173 | For efficient computations of unfolded tensor times Khatri-Rao products
174 | for specialiized tensors see also
175 | dtensor.uttkrp, sptensor.uttkrp, ktensor.uttkrp, ttensor.uttkrp
176 |
177 | References
178 | ----------
179 | .. [1] B.W. Bader, T.G. Kolda
180 | Efficient Matlab Computations With Sparse and Factored Tensors
181 | SIAM J. Sci. Comput, Vol 30, No. 1, pp. 205--231, 2007
182 | """
183 | pass
184 |
185 | @abstractmethod
186 | def transpose(self, axes=None):
187 | """
188 | Compute transpose of tensors.
189 |
190 | Parameters
191 | ----------
192 | axes : array_like of ints, optional
193 | Permute the axes according to the values given.
194 |
195 | Returns
196 | -------
197 | d : tensor_mixin
198 | tensor with axes permuted.
199 |
200 | See also
201 | --------
202 | dtensor.transpose, sptensor.transpose
203 | """
204 | pass
205 |
206 |
207 | def istensor(X):
208 | return isinstance(X, tensor_mixin)
209 |
210 |
211 | # dynamically create module level functions
212 | conv_funcs = [
213 | 'norm',
214 | 'transpose',
215 | 'ttm',
216 | 'ttv',
217 | 'unfold',
218 | ]
219 |
220 | for fname in conv_funcs:
221 | def call_on_me(obj, *args, **kwargs):
222 | if not istensor(obj):
223 | raise ValueError('%s() object must be tensor (%s)' % (fname, type(obj)))
224 | func = getattr(obj, fname)
225 | return func(*args, **kwargs)
226 |
227 | nfunc = types.FunctionType(
228 | func_attr(call_on_me, 'code'),
229 | {
230 | 'getattr': getattr,
231 | 'fname': fname,
232 | 'istensor': istensor,
233 | 'ValueError': ValueError,
234 | 'type': type
235 | },
236 | name=fname,
237 | argdefs=func_attr(call_on_me, 'defaults'),
238 | closure=func_attr(call_on_me, 'closure')
239 | )
240 | setattr(sys.modules[__name__], fname, nfunc)
241 |
242 |
243 | def check_multiplication_dims(dims, N, M, vidx=False, without=False):
244 | dims = array(dims, ndmin=1)
245 | if len(dims) == 0:
246 | dims = arange(N)
247 | if without:
248 | dims = setdiff1d(range(N), dims)
249 | if not np.in1d(dims, arange(N)).all():
250 | raise ValueError('Invalid dimensions')
251 | P = len(dims)
252 | sidx = np.argsort(dims)
253 | sdims = dims[sidx]
254 | if vidx:
255 | if M > N:
256 | raise ValueError('More multiplicants than dimensions')
257 | if M != N and M != P:
258 | raise ValueError('Invalid number of multiplicants')
259 | if P == M:
260 | vidx = sidx
261 | else:
262 | vidx = sdims
263 | return sdims, vidx
264 | else:
265 | return sdims
266 |
267 |
268 | def innerprod(X, Y):
269 | """
270 | Inner prodcut with a Tensor
271 | """
272 | return dot(X.flatten(), Y.flatten())
273 |
274 |
275 | def nvecs(X, n, rank, do_flipsign=True, dtype=np.float):
276 | """
277 | Eigendecomposition of mode-n unfolding of a tensor
278 | """
279 | Xn = X.unfold(n)
280 | if issparse_mat(Xn):
281 | Xn = csr_matrix(Xn, dtype=dtype)
282 | Y = Xn.dot(Xn.T)
283 | _, U = eigsh(Y, rank, which='LM')
284 | else:
285 | Y = Xn.dot(Xn.T)
286 | N = Y.shape[0]
287 | _, U = eigh(Y, eigvals=(N - rank, N - 1))
288 | #_, U = eigsh(Y, rank, which='LM')
289 | # reverse order of eigenvectors such that eigenvalues are decreasing
290 | U = array(U[:, ::-1])
291 | # flip sign
292 | if do_flipsign:
293 | U = flipsign(U)
294 | return U
295 |
296 |
297 | def flipsign(U):
298 | """
299 | Flip sign of factor matrices such that largest magnitude
300 | element will be positive
301 | """
302 | midx = abs(U).argmax(axis=0)
303 | for i in range(U.shape[1]):
304 | if U[midx[i], i] < 0:
305 | U[:, i] = -U[:, i]
306 | return U
307 |
308 |
309 | def center(X, n):
310 | Xn = unfold(X, n)
311 | N = Xn.shape[0]
312 | m = Xn.sum(axis=0) / N
313 | m = kron(m, ones((N, 1)))
314 | Xn = Xn - m
315 | return fold(Xn, n)
316 |
317 |
318 | def center_matrix(X):
319 | m = X.mean(axis=0)
320 | return X - m
321 |
322 |
323 | def scale(X, n):
324 | Xn = unfold(X, n)
325 | m = np.float_(np.sqrt((Xn ** 2).sum(axis=1)))
326 | m[m == 0] = 1
327 | for i in range(Xn.shape[0]):
328 | Xn[i, :] = Xn[i] / m[i]
329 | return fold(Xn, n, X.shape)
330 |
331 |
332 | # TODO more efficient cython implementation
333 | def khatrirao(A, reverse=False):
334 | """
335 | Compute the columnwise Khatri-Rao product.
336 |
337 | Parameters
338 | ----------
339 | A : tuple of ndarrays
340 | Matrices for which the columnwise Khatri-Rao product should be computed
341 |
342 | reverse : boolean
343 | Compute Khatri-Rao product in reverse order
344 |
345 | Examples
346 | --------
347 | >>> A = np.random.randn(5, 2)
348 | >>> B = np.random.randn(4, 2)
349 | >>> C = khatrirao((A, B))
350 | >>> C.shape
351 | (20, 2)
352 | >>> (C[:, 0] == np.kron(A[:, 0], B[:, 0])).all()
353 | true
354 | >>> (C[:, 1] == np.kron(A[:, 1], B[:, 1])).all()
355 | true
356 | """
357 |
358 | if not isinstance(A, tuple):
359 | raise ValueError('A must be a tuple of array likes')
360 | N = A[0].shape[1]
361 | M = 1
362 | for i in range(len(A)):
363 | if A[i].ndim != 2:
364 | raise ValueError('A must be a tuple of matrices (A[%d].ndim = %d)' % (i, A[i].ndim))
365 | elif N != A[i].shape[1]:
366 | raise ValueError('All matrices must have same number of columns')
367 | M *= A[i].shape[0]
368 | matorder = arange(len(A))
369 | if reverse:
370 | matorder = matorder[::-1]
371 | # preallocate
372 | P = np.zeros((M, N), dtype=A[0].dtype)
373 | for n in range(N):
374 | ab = A[matorder[0]][:, n]
375 | for j in range(1, len(matorder)):
376 | ab = np.kron(ab, A[matorder[j]][:, n])
377 | P[:, n] = ab
378 | return P
379 |
380 |
381 | def teneye(dim, order):
382 | """
383 | Create tensor with superdiagonal all one, rest zeros
384 | """
385 | I = zeros(dim ** order)
386 | for f in range(dim):
387 | idd = f
388 | for i in range(1, order):
389 | idd = idd + dim ** (i - 1) * (f - 1)
390 | I[idd] = 1
391 | return I.reshape(ones(order) * dim)
392 |
393 |
394 | def tvecmat(m, n):
395 | d = m * n
396 | i2 = arange(d).reshape(m, n).T.flatten()
397 | Tmn = zeros((d, d))
398 | Tmn[arange(d), i2] = 1
399 | return Tmn
400 |
401 | #i = arange(d);
402 | #rI = m * (i-1)-(m*n-1) * floor((i-1)/n)
403 | #print rI
404 | #I1s = s2i((d,d), rI, arange(d))
405 | #print I1s
406 | #Tmn[I1s] = 1
407 | #return Tmn.reshape((d,d)).T
408 |
409 | # vim: set et:
410 |
--------------------------------------------------------------------------------
/sktensor/cp.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | # Copyright (C) 2013 Maximilian Nickel
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 | """
17 | This module holds diffent algorithms to compute the CP decomposition, i.e.
18 | algorithms where
19 |
20 | .. math:: \\ten{X} \\approx \sum_{r=1}^{rank} \\vec{u}_r^{(1)} \outer \cdots \outer \\vec{u}_r^{(N)}
21 |
22 | """
23 | import logging
24 | import time
25 | import numpy as np
26 | from numpy import array, dot, ones, sqrt
27 | from scipy.linalg import pinv
28 | from numpy.random import rand
29 | from .core import nvecs, norm
30 | from .ktensor import ktensor
31 |
32 | _log = logging.getLogger('CP')
33 | _DEF_MAXITER = 500
34 | _DEF_INIT = 'nvecs'
35 | _DEF_CONV = 1e-5
36 | _DEF_FIT_METHOD = 'full'
37 | _DEF_TYPE = np.float
38 |
39 | __all__ = [
40 | 'als',
41 | 'opt',
42 | 'wopt'
43 | ]
44 |
45 |
46 | def als(X, rank, **kwargs):
47 | """
48 | Alternating least-sqaures algorithm to compute the CP decomposition.
49 |
50 | Parameters
51 | ----------
52 | X : tensor_mixin
53 | The tensor to be decomposed.
54 | rank : int
55 | Tensor rank of the decomposition.
56 | init : {'random', 'nvecs'}, optional
57 | The initialization method to use.
58 | - random : Factor matrices are initialized randomly.
59 | - nvecs : Factor matrices are initialzed via HOSVD.
60 | (default 'nvecs')
61 | max_iter : int, optional
62 | Maximium number of iterations of the ALS algorithm.
63 | (default 500)
64 | fit_method : {'full', None}
65 | The method to compute the fit of the factorization
66 | - 'full' : Compute least-squares fit of the dense approximation of.
67 | X and X.
68 | - None : Do not compute the fit of the factorization, but iterate
69 | until ``max_iter`` (Useful for large-scale tensors).
70 | (default 'full')
71 | conv : float
72 | Convergence tolerance on difference of fit between iterations
73 | (default 1e-5)
74 |
75 | Returns
76 | -------
77 | P : ktensor
78 | Rank ``rank`` factorization of X. ``P.U[i]`` corresponds to the factor
79 | matrix for the i-th mode. ``P.lambda[i]`` corresponds to the weight
80 | of the i-th mode.
81 | fit : float
82 | Fit of the factorization compared to ``X``
83 | itr : int
84 | Number of iterations that were needed until convergence
85 | exectimes : ndarray of floats
86 | Time needed for each single iteration
87 |
88 | Examples
89 | --------
90 | Create random dense tensor
91 |
92 | >>> from sktensor import dtensor, ktensor
93 | >>> U = [np.random.rand(i,3) for i in (20, 10, 14)]
94 | >>> T = dtensor(ktensor(U).toarray())
95 |
96 | Compute rank-3 CP decomposition of ``T`` with ALS
97 |
98 | >>> P, fit, itr, _ = als(T, 3)
99 |
100 | Result is a decomposed tensor stored as a Kruskal operator
101 |
102 | >>> type(P)
103 |
104 |
105 | Factorization should be close to original data
106 |
107 | >>> np.allclose(T, P.totensor())
108 | True
109 |
110 | References
111 | ----------
112 | .. [1] Kolda, T. G. & Bader, B. W.
113 | Tensor Decompositions and Applications.
114 | SIAM Rev. 51, 455–500 (2009).
115 | .. [2] Harshman, R. A.
116 | Foundations of the PARAFAC procedure: models and conditions for an 'explanatory' multimodal factor analysis.
117 | UCLA Working Papers in Phonetics 16, (1970).
118 | .. [3] Carroll, J. D., Chang, J. J.
119 | Analysis of individual differences in multidimensional scaling via an N-way generalization of 'Eckart-Young' decomposition.
120 | Psychometrika 35, 283–319 (1970).
121 | """
122 |
123 | # init options
124 | ainit = kwargs.pop('init', _DEF_INIT)
125 | maxiter = kwargs.pop('max_iter', _DEF_MAXITER)
126 | fit_method = kwargs.pop('fit_method', _DEF_FIT_METHOD)
127 | conv = kwargs.pop('conv', _DEF_CONV)
128 | dtype = kwargs.pop('dtype', _DEF_TYPE)
129 | if not len(kwargs) == 0:
130 | raise ValueError('Unknown keywords (%s)' % (kwargs.keys()))
131 |
132 | N = X.ndim
133 | normX = norm(X)
134 |
135 | U = _init(ainit, X, N, rank, dtype)
136 | fit = 0
137 | exectimes = []
138 | for itr in range(maxiter):
139 | tic = time.clock()
140 | fitold = fit
141 |
142 | for n in range(N):
143 | Unew = X.uttkrp(U, n)
144 | Y = ones((rank, rank), dtype=dtype)
145 | for i in (list(range(n)) + list(range(n + 1, N))):
146 | Y = Y * dot(U[i].T, U[i])
147 | Unew = Unew.dot(pinv(Y))
148 | # Normalize
149 | if itr == 0:
150 | lmbda = sqrt((Unew ** 2).sum(axis=0))
151 | else:
152 | lmbda = Unew.max(axis=0)
153 | lmbda[lmbda < 1] = 1
154 | U[n] = Unew / lmbda
155 |
156 | P = ktensor(U, lmbda)
157 | if fit_method == 'full':
158 | normresidual = normX ** 2 + P.norm() ** 2 - 2 * P.innerprod(X)
159 | fit = 1 - (normresidual / normX ** 2)
160 | else:
161 | fit = itr
162 | fitchange = abs(fitold - fit)
163 | exectimes.append(time.clock() - tic)
164 | _log.debug(
165 | '[%3d] fit: %.5f | delta: %7.1e | secs: %.5f' %
166 | (itr, fit, fitchange, exectimes[-1])
167 | )
168 | if itr > 0 and fitchange < conv:
169 | break
170 |
171 | return P, fit, itr, array(exectimes)
172 |
173 |
174 | def opt(X, rank, **kwargs):
175 | ainit = kwargs.pop('init', _DEF_INIT)
176 | maxiter = kwargs.pop('maxIter', _DEF_MAXITER)
177 | conv = kwargs.pop('conv', _DEF_CONV)
178 | dtype = kwargs.pop('dtype', _DEF_TYPE)
179 | if not len(kwargs) == 0:
180 | raise ValueError('Unknown keywords (%s)' % (kwargs.keys()))
181 |
182 | N = X.ndim
183 | U = _init(ainit, X, N, rank, dtype)
184 |
185 |
186 | def wopt(X, rank, **kwargs):
187 | raise NotImplementedError()
188 |
189 |
190 | def _init(init, X, N, rank, dtype):
191 | """
192 | Initialization for CP models
193 | """
194 | Uinit = [None for _ in range(N)]
195 | if isinstance(init, list):
196 | Uinit = init
197 | elif init == 'random':
198 | for n in range(1, N):
199 | Uinit[n] = array(rand(X.shape[n], rank), dtype=dtype)
200 | elif init == 'nvecs':
201 | for n in range(1, N):
202 | Uinit[n] = array(nvecs(X, n, rank), dtype=dtype)
203 | else:
204 | raise 'Unknown option (init=%s)' % str(init)
205 | return Uinit
206 |
207 | # vim: set et:
208 |
--------------------------------------------------------------------------------
/sktensor/dedicom.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2013 Maximilian Nickel
2 | #
3 | # This program is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU General Public License as published by
5 | # the Free Software Foundation, either version 3 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 | import logging
17 | import time
18 | import numpy as np
19 | from numpy import dot, ones, zeros, diag, kron, outer, array, prod, eye
20 | from numpy.linalg import norm, solve, eigvals
21 | from numpy.random import rand
22 | from scipy.linalg import qr
23 | from scipy.sparse.linalg import eigsh
24 | from scipy.optimize import fmin_l_bfgs_b, fmin_ncg, fmin_tnc
25 | from scipy.sparse import issparse
26 |
27 | _DEF_MAXITER = 500
28 | _DEF_INIT = 'nvecs'
29 | _DEF_PROJ = True
30 | _DEF_CONV = 1e-5
31 | _DEF_NNE = -1
32 | _DEF_OPTFUNC = 'lbfgs'
33 |
34 | _log = logging.getLogger('DEDICOM')
35 | np.seterr(invalid='raise')
36 |
37 |
38 | def asalsan(X, rank, **kwargs):
39 | """
40 | ASALSAN algorithm to compute the three-way DEDICOM decomposition
41 | of a tensor
42 |
43 | See
44 | ---
45 | .. [1] Brett W. Bader, Richard A. Harshman, Tamara G. Kolda
46 | "Temporal analysis of semantic graphs using ASALSAN"
47 | 7th International Conference on Data Mining, 2007
48 |
49 | .. [2] Brett W. Bader, Richard A. Harshman, Tamara G. Kolda
50 | "Temporal analysis of Social Networks using Three-way DEDICOM"
51 | Technical Report, 2006
52 | """
53 | # init options
54 | ainit = kwargs.pop('init', _DEF_INIT)
55 | proj = kwargs.pop('proj', _DEF_PROJ)
56 | maxIter = kwargs.pop('maxIter', _DEF_MAXITER)
57 | conv = kwargs.pop('conv', _DEF_CONV)
58 | nne = kwargs.pop('nne', _DEF_NNE)
59 | optfunc = kwargs.pop('optfunc', _DEF_OPTFUNC)
60 | if not len(kwargs) == 0:
61 | raise BaseException('Unknown keywords (%s)' % (kwargs.keys()))
62 |
63 | # init starting points
64 | D = ones((len(X), rank))
65 | sz = X[0].shape
66 | n = sz[0]
67 | R = rand(rank, rank)
68 | if ainit == 'random':
69 | A = rand(n, rank)
70 | elif ainit == 'nvecs':
71 | S = zeros((n, n))
72 | T = zeros((n, n))
73 | for i in range(len(X)):
74 | T = X[i]
75 | S = S + T + T.T
76 | evals, A = eigsh(S, rank)
77 | if nne > 0:
78 | A[A < 0] = 0
79 | if proj:
80 | Q, A2 = qr(A)
81 | X2 = __projectSlices(X, Q)
82 | R = __updateR(X2, A2, D, R, nne)
83 | else:
84 | R = __updateR(X, A, D, R, nne)
85 | elif isinstance(ainit, np.ndarray):
86 | A = ainit
87 | else:
88 | raise 'Unknown init option ("%s")' % ainit
89 |
90 | # perform decomposition
91 | if issparse(X[0]):
92 | normX = [norm(M.data) ** 2 for M in X]
93 | Xflat = [M.tolil().reshape((1, prod(M.shape))).tocsr() for M in X]
94 | else:
95 | normX = [norm(M) ** 2 for M in X]
96 | Xflat = [M.flatten() for M in X]
97 | M = zeros((n, n))
98 | normXSum = sum(normX)
99 | #normX = norm(X)**2
100 | fit = fitold = f = fitchange = 0
101 | exectimes = []
102 | for iters in xrange(maxIter):
103 | tic = time.clock()
104 | fitold = fit
105 | A = __updateA(X, A, D, R, nne)
106 | if proj:
107 | Q, A2 = qr(A)
108 | X2 = __projectSlices(X, Q)
109 | R = __updateR(X2, A2, D, R, nne)
110 | D, f = __updateD(X2, A2, D, R, nne, optfunc)
111 | else:
112 | R = __updateR(X, A, D, R, nne)
113 | D, f = __updateD(X, A, D, R, nne, optfunc)
114 |
115 | # compute fit
116 | f = 0
117 | for i in xrange(len(X)):
118 | AD = dot(A, diag(D[i, :]))
119 | M = dot(dot(AD, R), AD.T)
120 | f += normX[i] + norm(M) ** 2 - 2 * Xflat[i].dot(M.flatten())
121 | f *= 0.5
122 | fit = 1 - (f / normXSum)
123 | fitchange = abs(fitold - fit)
124 |
125 | exectimes.append(time.clock() - tic)
126 |
127 | # print iter info when debugging is enabled
128 | _log.debug('[%3d] fit: %.5f | delta: %7.1e | secs: %.5f' % (
129 | iters, fit, fitchange, exectimes[-1]
130 | ))
131 |
132 | if iters > 1 and fitchange < conv:
133 | break
134 | return A, R, D, fit, iters, array(exectimes)
135 |
136 |
137 | def __updateA(X, A, D, R, nne):
138 | rank = A.shape[1]
139 | F = zeros((X[0].shape[0], rank))
140 | E = zeros((rank, rank))
141 |
142 | AtA = dot(A.T, A)
143 | for i in range(len(X)):
144 | Dk = diag(D[i, :])
145 | DRD = dot(Dk, dot(R, Dk))
146 | DRtD = DRD.T
147 | F += X[i].dot(dot(A, DRtD)) + X[i].T.dot(dot(A, DRD))
148 | E += dot(DRD, dot(AtA, DRtD)) + dot(DRtD, dot(AtA, DRD))
149 | if nne > 0:
150 | E = dot(A, E) + nne
151 | A = A * (F / E)
152 | else:
153 | A = solve(E.T, F.T).T
154 | return A
155 |
156 |
157 | def __updateR(X, A, D, R, nne):
158 | r = A.shape[1] ** 2
159 | T = zeros((r, r))
160 | t = zeros(r)
161 | for i in range(len(X)):
162 | AD = dot(A, diag(D[i, :]))
163 | ADt = AD.T
164 | tmp = dot(ADt, AD)
165 | T = T + kron(tmp, tmp)
166 | tmp = dot(ADt, X[i].dot(AD))
167 | t = t + tmp.flatten()
168 | r = A.shape[1]
169 | if nne > 0:
170 | Rflat = R.flatten()
171 | T = dot(T, Rflat) + nne
172 | R = (Rflat * t / T).reshape(r, r)
173 | else:
174 | # TODO check if this is correct
175 | R = solve(T, t).reshape(r, r)
176 | #R = (pinv(T + eye(r ** 2)).dot(t)).reshape(r, r)
177 | return R
178 |
179 |
180 | def __updateD(X, A, D, R, nne, optfunc):
181 | f = 0
182 | for i in range(len(X)):
183 | d = D[i, :]
184 | u = Updater(X[i], A, R)
185 | if nne > 0:
186 | bounds = len(d) * [(0, None)]
187 | res = fmin_l_bfgs_b(
188 | u.updateD_F, d, u.updateD_G, factr=1e12, bounds=bounds
189 | )
190 | else:
191 | if optfunc == 'lbfgs':
192 | res = fmin_l_bfgs_b(u.updateD_F, d, u.updateD_G, factr=1e12)
193 | D[i, :] = res[0]
194 | f += res[1]
195 | elif optfunc == 'ncg':
196 | res = fmin_ncg(
197 | u.updateD_F, d, u.updateD_G, fhess=u.updateD_H,
198 | full_output=True, disp=False
199 | )
200 | # TODO: check return value of ncg and update D, f
201 | raise NotImplementedError()
202 | elif optfunc == 'tnc':
203 | res = fmin_tnc(u.updateD_F, d, u.updateD_G, disp=False)
204 | # TODO: check return value of tnc and update D, f
205 | raise NotImplementedError()
206 | return D, f
207 |
208 |
209 | class Updater:
210 | def __init__(self, Z, A, R):
211 | self.Z = Z
212 | self.A = A
213 | self.R = R
214 | self.x = None
215 |
216 | def precompute(self, x, cache=True):
217 | if not cache or self.x is None or (x != self.x).any():
218 | self.AD = dot(self.A, diag(x))
219 | self.ADt = self.AD.T
220 | self.E = self.Z - dot(self.AD, dot(self.R, self.ADt))
221 |
222 | def updateD_F(self, x):
223 | self.precompute(x)
224 | return norm(self.E, 'fro') ** 2
225 |
226 | def updateD_G(self, x):
227 | """
228 | Compute Gradient for update of D
229 |
230 | See [2] for derivation of Gradient
231 | """
232 | self.precompute(x)
233 | g = zeros(len(x))
234 | Ai = zeros(self.A.shape[0])
235 | for i in range(len(g)):
236 | Ai = self.A[:, i]
237 | g[i] = (self.E * (dot(self.AD, outer(self.R[:, i], Ai)) +
238 | dot(outer(Ai, self.R[i, :]), self.ADt))).sum()
239 | return -2 * g
240 |
241 | def updateD_H(self, x):
242 | """
243 | Compute Hessian for update of D
244 |
245 | See [2] for derivation of Hessian
246 | """
247 | self.precompute(x)
248 | H = zeros((len(x), len(x)))
249 | Ai = zeros(self.A.shape[0])
250 | Aj = zeros(Ai.shape)
251 | for i in range(len(x)):
252 | Ai = self.A[:, i]
253 | ti = dot(self.AD, outer(self.R[:, i], Ai)) + dot(outer(Ai, self.R[i, :]), self.ADt)
254 |
255 | for j in range(i, len(x)):
256 | Aj = self.A[:, j]
257 | tj = outer(Ai, Aj)
258 | H[i, j] = (
259 | self.E * (self.R[i, j] * tj + self.R[j, i] * tj.T) -
260 | ti * (
261 | dot(self.AD, outer(self.R[:, j], Aj)) +
262 | dot(outer(Aj, self.R[j, :]), self.ADt)
263 | )
264 | ).sum()
265 | H[j, i] = H[i, j]
266 | H *= -2
267 | e = eigvals(H).min()
268 | H = H + (eye(H.shape[0]) * e)
269 | return H
270 |
271 |
272 | def __projectSlices(X, Q):
273 | X2 = []
274 | for i in range(len(X)):
275 | X2.append(Q.T.dot(X[i].dot(Q)))
276 | return X2
277 |
--------------------------------------------------------------------------------
/sktensor/dtensor.py:
--------------------------------------------------------------------------------
1 | # sktensor.dtensor - base class for dense tensors
2 | # Copyright (C) 2013 Maximilian Nickel
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | import numpy as np
18 | from numpy import array, prod, argsort
19 | from .core import tensor_mixin, khatrirao
20 | from .pyutils import inherit_docstring_from, from_to_without
21 |
22 |
23 | __all__ = [
24 | 'dtensor',
25 | 'unfolded_dtensor',
26 | ]
27 |
28 |
29 | class dtensor(tensor_mixin, np.ndarray):
30 | """
31 | Class to store **dense** tensors
32 |
33 | Parameters
34 | ----------
35 | input_array : np.ndarray
36 | Multidimenional numpy array which holds the entries of the tensor
37 |
38 | Examples
39 | --------
40 | Create dense tensor from numpy array
41 |
42 | >>> T = np.zeros((3, 4, 2))
43 | >>> T[:, :, 0] = [[ 1, 4, 7, 10], [ 2, 5, 8, 11], [3, 6, 9, 12]]
44 | >>> T[:, :, 1] = [[13, 16, 19, 22], [14, 17, 20, 23], [15, 18, 21, 24]]
45 | >>> T = dtensor(T)
46 | """
47 |
48 | def __new__(cls, input_array):
49 | obj = np.asarray(input_array).view(cls)
50 | return obj
51 |
52 | def __array_wrap__(self, out_arr, context=None):
53 | return np.ndarray.__array_wrap__(self, out_arr, context)
54 |
55 | def __eq__(self, other):
56 | return np.equal(self, other)
57 |
58 | def _ttm_compute(self, V, mode, transp):
59 | sz = array(self.shape)
60 | r1, r2 = from_to_without(0, self.ndim, mode, separate=True)
61 | #r1 = list(range(0, mode))
62 | #r2 = list(range(mode + 1, self.ndim))
63 | order = [mode] + r1 + r2
64 | newT = self.transpose(axes=order)
65 | newT = newT.reshape(sz[mode], prod(sz[r1 + list(range(mode + 1, len(sz)))]))
66 | if transp:
67 | newT = V.T.dot(newT)
68 | p = V.shape[1]
69 | else:
70 | newT = V.dot(newT)
71 | p = V.shape[0]
72 | newsz = [p] + list(sz[:mode]) + list(sz[mode + 1:])
73 | newT = newT.reshape(newsz)
74 | # transpose + argsort(order) equals ipermute
75 | newT = newT.transpose(argsort(order))
76 | return dtensor(newT)
77 |
78 | def _ttv_compute(self, v, dims, vidx, remdims):
79 | """
80 | Tensor times vector product
81 |
82 | Parameter
83 | ---------
84 | """
85 | if not isinstance(v, tuple):
86 | raise ValueError('v must be a tuple of vectors')
87 | ndim = self.ndim
88 | order = list(remdims) + list(dims)
89 | if ndim > 1:
90 | T = self.transpose(order)
91 | sz = array(self.shape)[order]
92 | for i in np.arange(len(dims), 0, -1):
93 | T = T.reshape((sz[:ndim - 1].prod(), sz[ndim - 1]))
94 | T = T.dot(v[vidx[i - 1]])
95 | ndim -= 1
96 | if ndim > 0:
97 | T = T.reshape(sz[:ndim])
98 | return T
99 |
100 | def ttt(self, other, modes=None):
101 | pass
102 |
103 | def unfold(self, mode):
104 | """
105 | Unfolds a dense tensor in mode n.
106 |
107 | Parameters
108 | ----------
109 | mode : int
110 | Mode in which tensor is unfolded
111 |
112 | Returns
113 | -------
114 | unfolded_dtensor : unfolded_dtensor object
115 | Tensor unfolded along mode
116 |
117 | Examples
118 | --------
119 | Create dense tensor from numpy array
120 |
121 | >>> T = np.zeros((3, 4, 2))
122 | >>> T[:, :, 0] = [[ 1, 4, 7, 10], [ 2, 5, 8, 11], [3, 6, 9, 12]]
123 | >>> T[:, :, 1] = [[13, 16, 19, 22], [14, 17, 20, 23], [15, 18, 21, 24]]
124 | >>> T = dtensor(T)
125 |
126 | Unfolding of dense tensors
127 |
128 | >>> T.unfold(0)
129 | array([[ 1., 4., 7., 10., 13., 16., 19., 22.],
130 | [ 2., 5., 8., 11., 14., 17., 20., 23.],
131 | [ 3., 6., 9., 12., 15., 18., 21., 24.]])
132 | >>> T.unfold(1)
133 | array([[ 1., 2., 3., 13., 14., 15.],
134 | [ 4., 5., 6., 16., 17., 18.],
135 | [ 7., 8., 9., 19., 20., 21.],
136 | [ 10., 11., 12., 22., 23., 24.]])
137 | >>> T.unfold(2)
138 | array([[ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11.,
139 | 12.],
140 | [ 13., 14., 15., 16., 17., 18., 19., 20., 21., 22., 23.,
141 | 24.]])
142 | """
143 |
144 | sz = array(self.shape)
145 | N = len(sz)
146 | order = ([mode], from_to_without(N - 1, -1, mode, step=-1, skip=-1))
147 | newsz = (sz[order[0]][0], prod(sz[order[1]]))
148 | arr = self.transpose(axes=(order[0] + order[1]))
149 | arr = arr.reshape(newsz)
150 | return unfolded_dtensor(arr, mode, self.shape)
151 |
152 | def norm(self):
153 | """
154 | Computes the Frobenius norm for dense tensors
155 | :math:`norm(X) = \sqrt{\sum_{i_1,\ldots,i_N} x_{i_1,\ldots,i_N}^2}`
156 |
157 | References
158 | ----------
159 | [Kolda and Bader, 2009; p.457]
160 | """
161 | return np.linalg.norm(self)
162 |
163 | @inherit_docstring_from(tensor_mixin)
164 | def uttkrp(self, U, n):
165 | order = list(range(n)) + list(range(n + 1, self.ndim))
166 | Z = khatrirao(tuple(U[i] for i in order), reverse=True)
167 | return self.unfold(n).dot(Z)
168 |
169 | @inherit_docstring_from(tensor_mixin)
170 | def transpose(self, axes=None):
171 | return dtensor(np.transpose(array(self), axes=axes))
172 |
173 |
174 | class unfolded_dtensor(np.ndarray):
175 |
176 | def __new__(cls, input_array, mode, ten_shape):
177 | obj = np.asarray(input_array).view(cls)
178 | obj.ten_shape = ten_shape
179 | obj.mode = mode
180 | return obj
181 |
182 | def __array_finalize__(self, obj):
183 | if obj is None:
184 | return
185 | self.ten_shape = getattr(obj, 'ten_shape', None)
186 | self.mode = getattr(obj, 'mode', None)
187 |
188 | def fold(self):
189 | shape = array(self.ten_shape)
190 | N = len(shape)
191 | order = ([self.mode], from_to_without(0, N, self.mode, reverse=True))
192 | arr = self.reshape(tuple(shape[order[0]],) + tuple(shape[order[1]]))
193 | arr = np.transpose(arr, argsort(order[0] + order[1]))
194 | return dtensor(arr)
195 |
--------------------------------------------------------------------------------
/sktensor/indscal.py:
--------------------------------------------------------------------------------
1 | from numpy import zeros, dot, diag
2 | from numpy.random import rand
3 | from scipy.linalg import svd, norm, orth
4 | from scipy.sparse.linalg import eigsh
5 | import time
6 | import logging
7 |
8 | _log = logging.getLogger('INDSCAL')
9 |
10 | _DEF_MAXITER = 50
11 | _DEF_INIT = 'random'
12 | _DEF_CONV = 1e-7
13 |
14 |
15 | def orth_als(X, ncomp, **kwargs):
16 |
17 | ainit = kwargs.pop('init', _DEF_INIT)
18 | maxiter = kwargs.pop('max_iter', _DEF_MAXITER)
19 | conv = kwargs.pop('conv', _DEF_CONV)
20 | if not len(kwargs) == 0:
21 | raise ValueError('Unknown keywords (%s)' % (kwargs.keys()))
22 |
23 | K = len(X)
24 | normX = sum([norm(Xk)**2 for Xk in X])
25 |
26 | A = init(X, ainit, ncomp)
27 | fit = 0
28 | exectimes = []
29 | for itr in range(maxiter):
30 | tic = time.time()
31 | fitold = fit
32 | D = _updateD(X, A)
33 | A = _updateA(X, A, D)
34 |
35 | fit = sum([norm(X[k] - dot(A, dot(diag(D[k, :]), A.T)))**2 for k in range(K)])
36 | fit = 1 - fit / normX
37 | fitchange = abs(fitold - fit)
38 |
39 | exectimes.append(time.time() - tic)
40 | _log.info('[%3d] fit: %0.5f | delta: %7.1e | secs: %.5f' % (
41 | itr, fit, fitchange, exectimes[-1]
42 | ))
43 | if itr > 0 and fitchange < conv:
44 | break
45 | return A, D
46 |
47 |
48 | def _updateA(X, A, D):
49 | G = zeros(A.shape)
50 | for k in range(len(X)):
51 | G = G + dot(X[k], dot(A, diag(D[k, :])))
52 | U, _, Vt = svd(G, full_matrices=0)
53 | A = dot(U, Vt)
54 | return A
55 |
56 |
57 | def _updateD(X, A):
58 | K, R = len(X), A.shape[1]
59 | D = zeros((K, R))
60 | for k in range(K):
61 | D[k, :] = diag(dot(A.T, dot(X[k], A)))
62 | D[D < 0] = 0
63 | return D
64 |
65 |
66 | def init(X, init, ncomp):
67 | N, K = X[0].shape[0], len(X)
68 | if init == 'random':
69 | A = orth(rand(N, ncomp))
70 | elif init == 'nvecs':
71 | S = zeros(N, N)
72 | for k in range(K):
73 | S = S + X[k] + X[k].T
74 | _, A = eigsh(S, ncomp)
75 | return A
76 |
--------------------------------------------------------------------------------
/sktensor/ktensor.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2013 Maximilian Nickel
2 | #
3 | # This program is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU General Public License as published by
5 | # the Free Software Foundation, either version 3 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 | import numpy as np
17 | from numpy import dot, ones, array, outer, zeros, prod, sum
18 | from sktensor.core import khatrirao, tensor_mixin
19 | from sktensor.dtensor import dtensor
20 |
21 | __all__ = [
22 | 'ktensor',
23 | 'vectorized_ktensor',
24 | ]
25 |
26 |
27 | class ktensor(object):
28 | """
29 | Tensor stored in decomposed form as a Kruskal operator.
30 |
31 | Intended Usage
32 | The Kruskal operator is particularly useful to store
33 | the results of a CP decomposition.
34 |
35 | Parameters
36 | ----------
37 | U : list of ndarrays
38 | Factor matrices from which the tensor representation
39 | is created. All factor matrices ``U[i]`` must have the
40 | same number of columns, but can have different
41 | number of rows.
42 | lmbda : array_like of floats, optional
43 | Weights for each dimension of the Kruskal operator.
44 | ``len(lambda)`` must be equal to ``U[i].shape[1]``
45 |
46 | See also
47 | --------
48 | sktensor.dtensor : Dense tensors
49 | sktensor.sptensor : Sparse tensors
50 | sktensor.ttensor : Tensors stored in form of the Tucker operator
51 |
52 | References
53 | ----------
54 | .. [1] B.W. Bader, T.G. Kolda
55 | Efficient Matlab Computations With Sparse and Factored Tensors
56 | SIAM J. Sci. Comput, Vol 30, No. 1, pp. 205--231, 2007
57 | """
58 |
59 | def __init__(self, U, lmbda=None):
60 | self.U = U
61 | self.shape = tuple(Ui.shape[0] for Ui in U)
62 | self.ndim = len(self.shape)
63 | self.rank = U[0].shape[1]
64 | self.lmbda = lmbda
65 | if not all(array([Ui.shape[1] for Ui in U]) == self.rank):
66 | raise ValueError('Dimension mismatch of factor matrices')
67 | if lmbda is None:
68 | self.lmbda = ones(self.rank)
69 |
70 | def __eq__(self, other):
71 | if isinstance(other, ktensor):
72 | # avoid costly elementwise comparison for obvious cases
73 | if self.ndim != other.ndim or self.shape != other.shape:
74 | return False
75 | # do elementwise comparison
76 | return all(
77 | [(self.U[i] == other.U[i]).all() for i in range(self.ndim)] +
78 | [(self.lmbda == other.lmbda).all()]
79 | )
80 | else:
81 | # TODO implement __eq__ for tensor_mixins and ndarrays
82 | raise NotImplementedError()
83 |
84 | def uttkrp(self, U, mode):
85 |
86 | """
87 | Unfolded tensor times Khatri-Rao product for Kruskal tensors
88 |
89 | Parameters
90 | ----------
91 | X : tensor_mixin
92 | Tensor whose unfolding should be multiplied.
93 | U : list of array_like
94 | Matrices whose Khatri-Rao product should be multiplied.
95 | mode : int
96 | Mode in which X should be unfolded.
97 |
98 | See also
99 | --------
100 | sktensor.sptensor.uttkrp : Efficient computation of uttkrp for sparse tensors
101 | ttensor.uttkrp : Efficient computation of uttkrp for Tucker operators
102 | """
103 | N = self.ndim
104 | if mode == 1:
105 | R = U[1].shape[1]
106 | else:
107 | R = U[0].shape[1]
108 | W = np.tile(self.lmbda, 1, R)
109 | for i in range(mode) + range(mode + 1, N):
110 | W = W * dot(self.U[i].T, U[i])
111 | return dot(self.U[mode], W)
112 |
113 | def norm(self):
114 | """
115 | Efficient computation of the Frobenius norm for ktensors
116 |
117 | Returns
118 | -------
119 | norm : float
120 | Frobenius norm of the ktensor
121 | """
122 | N = len(self.shape)
123 | coef = outer(self.lmbda, self.lmbda)
124 | for i in range(N):
125 | coef = coef * dot(self.U[i].T, self.U[i])
126 | return np.sqrt(coef.sum())
127 |
128 | def innerprod(self, X):
129 | """
130 | Efficient computation of the inner product of a ktensor with another tensor
131 |
132 | Parameters
133 | ----------
134 | X : tensor_mixin
135 | Tensor to compute the inner product with.
136 |
137 | Returns
138 | -------
139 | p : float
140 | Inner product between ktensor and X.
141 | """
142 | N = len(self.shape)
143 | R = len(self.lmbda)
144 | res = 0
145 | for r in range(R):
146 | vecs = []
147 | for n in range(N):
148 | vecs.append(self.U[n][:, r])
149 | res += self.lmbda[r] * X.ttv(tuple(vecs))
150 | return res
151 |
152 | def toarray(self):
153 | """
154 | Converts a ktensor into a dense multidimensional ndarray
155 |
156 | Returns
157 | -------
158 | arr : np.ndarray
159 | Fully computed multidimensional array whose shape matches
160 | the original ktensor.
161 | """
162 | A = dot(self.lmbda, khatrirao(tuple(self.U)).T)
163 | return A.reshape(self.shape)
164 |
165 | def totensor(self):
166 | """
167 | Converts a ktensor into a dense tensor
168 |
169 | Returns
170 | -------
171 | arr : dtensor
172 | Fully computed multidimensional array whose shape matches
173 | the original ktensor.
174 | """
175 | return dtensor(self.toarray())
176 |
177 | def tovec(self):
178 | v = zeros(sum([s * self.rank for s in self.shape]))
179 | offset = 0
180 | for M in self.U:
181 | noff = offset + prod(M.shape)
182 | v[offset:noff] = M.flatten()
183 | offset = noff
184 | return vectorized_ktensor(v, self.shape, self.lmbda)
185 |
186 |
187 | class vectorized_ktensor(object):
188 |
189 | def __init__(self, v, shape, lmbda):
190 | self.v = v
191 | self.shape = shape
192 | self.lmbda = lmbda
193 |
194 | def toktensor(self):
195 | order = len(self.shape)
196 | rank = len(self.v) / sum(self.shape)
197 | U = [None for _ in range(order)]
198 | offset = 0
199 | for i in range(order):
200 | noff = offset + self.shape[i] * rank
201 | U[i] = self.v[offset:noff].reshape((self.shape[i], rank))
202 | offset = noff
203 | return ktensor(U, self.lmbda)
204 |
205 | # vim: set et:
206 |
--------------------------------------------------------------------------------
/sktensor/pyutils.py:
--------------------------------------------------------------------------------
1 | def inherit_docstring_from(cls):
2 | def docstring_inheriting_decorator(fn):
3 | fn.__doc__ = getattr(cls, fn.__name__).__doc__
4 | return fn
5 | return docstring_inheriting_decorator
6 |
7 |
8 | def is_sequence(obj):
9 | """
10 | Helper function to determine sequences
11 | across Python 2.x and 3.x
12 | """
13 | try:
14 | from collections import Sequence
15 | except ImportError:
16 | from operator import isSequenceType
17 | return isSequenceType(obj)
18 | else:
19 | return isinstance(obj, Sequence)
20 |
21 |
22 | def is_number(obj):
23 | """
24 | Helper function to determine numbers
25 | across Python 2.x and 3.x
26 | """
27 | try:
28 | from numbers import Number
29 | except ImportError:
30 | from operator import isNumberType
31 | return isNumberType(obj)
32 | else:
33 | return isinstance(obj, Number)
34 |
35 |
36 | def func_attr(f, attr):
37 | """
38 | Helper function to get the attribute of a function
39 | like, name, code, defaults across Python 2.x and 3.x
40 | """
41 | if hasattr(f, 'func_%s' % attr):
42 | return getattr(f, 'func_%s' % attr)
43 | elif hasattr(f, '__%s__' % attr):
44 | return getattr(f, '__%s__' % attr)
45 | else:
46 | raise ValueError('Object %s has no attr' % (str(f), attr))
47 |
48 |
49 | def from_to_without(frm, to, without, step=1, skip=1, reverse=False, separate=False):
50 | """
51 | Helper function to create ranges with missing entries
52 | """
53 | if reverse:
54 | frm, to = (to - 1), (frm - 1)
55 | step *= -1
56 | skip *= -1
57 | a = list(range(frm, without, step))
58 | b = list(range(without + skip, to, step))
59 | if separate:
60 | return a, b
61 | else:
62 | return a + b
63 |
--------------------------------------------------------------------------------
/sktensor/rescal.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | # rescal.py - python script to compute the RESCAL tensor factorization
3 | # Copyright (C) 2013 Maximilian Nickel
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | import logging
19 | import time
20 | import numpy as np
21 | from numpy import dot, zeros, array, eye, kron, prod
22 | from numpy.linalg import norm, solve, inv, svd
23 | from scipy.sparse import csr_matrix, issparse
24 | from scipy.sparse.linalg import eigsh
25 | from numpy.random import rand
26 |
27 | __version__ = "0.5"
28 | __all__ = ['als']
29 |
30 | _DEF_MAXITER = 100
31 | _DEF_INIT = 'nvecs'
32 | _DEF_CONV = 1e-4
33 | _DEF_LMBDA = 0
34 | _DEF_ATTR = []
35 | _DEF_NO_FIT = 1e9
36 | _DEF_FIT_METHOD = None
37 |
38 | _log = logging.getLogger('RESCAL')
39 |
40 |
41 | def als(X, rank, **kwargs):
42 | """
43 | RESCAL-ALS algorithm to compute the RESCAL tensor factorization.
44 |
45 |
46 | Parameters
47 | ----------
48 | X : list
49 | List of frontal slices X_k of the tensor X.
50 | The shape of each X_k is ('N', 'N').
51 | X_k's are expected to be instances of scipy.sparse.csr_matrix
52 | rank : int
53 | Rank of the factorization
54 | lmbdaA : float, optional
55 | Regularization parameter for A factor matrix. 0 by default
56 | lmbdaR : float, optional
57 | Regularization parameter for R_k factor matrices. 0 by default
58 | lmbdaV : float, optional
59 | Regularization parameter for V_l factor matrices. 0 by default
60 | attr : list, optional
61 | List of sparse ('N', 'L_l') attribute matrices. 'L_l' may be different
62 | for each attribute
63 | init : string, optional
64 | Initialization method of the factor matrices. 'nvecs' (default)
65 | initializes A based on the eigenvectors of X. 'random' initializes
66 | the factor matrices randomly.
67 | compute_fit : boolean, optional
68 | If true, compute the fit of the factorization compared to X.
69 | For large sparse tensors this should be turned of. None by default.
70 | maxIter : int, optional
71 | Maximium number of iterations of the ALS algorithm. 500 by default.
72 | conv : float, optional
73 | Stop when residual of factorization is less than conv. 1e-5 by default
74 |
75 | Returns
76 | -------
77 | A : ndarray
78 | array of shape ('N', 'rank') corresponding to the factor matrix A
79 | R : list
80 | list of 'M' arrays of shape ('rank', 'rank') corresponding to the
81 | factor matrices R_k
82 | fval : float
83 | function value of the factorization
84 | itr : int
85 | number of iterations until convergence
86 | exectimes : ndarray
87 | execution times to compute the updates in each iteration
88 |
89 | Examples
90 | --------
91 | >>> X1 = csr_matrix(([1,1,1], ([2,1,3], [0,2,3])), shape=(4, 4))
92 | >>> X2 = csr_matrix(([1,1,1,1], ([0,2,3,3], [0,1,2,3])), shape=(4, 4))
93 | >>> A, R, fval, iter, exectimes = rescal([X1, X2], 2)
94 |
95 | See
96 | ---
97 | For a full description of the algorithm see:
98 | .. [1] Maximilian Nickel, Volker Tresp, Hans-Peter-Kriegel,
99 | "A Three-Way Model for Collective Learning on Multi-Relational Data",
100 | ICML 2011, Bellevue, WA, USA
101 |
102 | .. [2] Maximilian Nickel, Volker Tresp, Hans-Peter-Kriegel,
103 | "Factorizing YAGO: Scalable Machine Learning for Linked Data"
104 | WWW 2012, Lyon, France
105 | """
106 |
107 | # ------------ init options ----------------------------------------------
108 | ainit = kwargs.pop('init', _DEF_INIT)
109 | maxIter = kwargs.pop('maxIter', _DEF_MAXITER)
110 | conv = kwargs.pop('conv', _DEF_CONV)
111 | lmbdaA = kwargs.pop('lambda_A', _DEF_LMBDA)
112 | lmbdaR = kwargs.pop('lambda_R', _DEF_LMBDA)
113 | lmbdaV = kwargs.pop('lambda_V', _DEF_LMBDA)
114 | func_compute_fval = kwargs.pop('compute_fval', _DEF_FIT_METHOD)
115 | orthogonalize = kwargs.pop('orthogonalize', False)
116 | P = kwargs.pop('attr', _DEF_ATTR)
117 | dtype = kwargs.pop('dtype', np.float)
118 |
119 | # ------------- check input ----------------------------------------------
120 | if not len(kwargs) == 0:
121 | raise ValueError('Unknown keywords (%s)' % (kwargs.keys()))
122 |
123 | # check frontal slices have same size and are matrices
124 | sz = X[0].shape
125 | for i in range(len(X)):
126 | if X[i].ndim != 2:
127 | raise ValueError('Frontal slices of X must be matrices')
128 | if X[i].shape != sz:
129 | raise ValueError('Frontal slices of X must be all of same shape')
130 | #if not issparse(X[i]):
131 | #raise ValueError('X[%d] is not a sparse matrix' % i)
132 |
133 | if func_compute_fval is None:
134 | if orthogonalize:
135 | func_compute_fval = _compute_fval_orth
136 | elif prod(X[0].shape) * len(X) > _DEF_NO_FIT:
137 | _log.warn('For large tensors automatic computation of fit is disabled by default\nTo compute the fit, call rescal.als with "compute_fit=True"\nPlease note that this might cause memory and runtime problems')
138 | func_compute_fval = None
139 | else:
140 | func_compute_fval = _compute_fval
141 |
142 | n = sz[0]
143 | k = len(X)
144 |
145 | _log.debug(
146 | '[Config] rank: %d | maxIter: %d | conv: %7.1e | lmbda: %7.1e' %
147 | (rank, maxIter, conv, lmbdaA)
148 | )
149 | _log.debug('[Config] dtype: %s / %s' % (dtype, X[0].dtype))
150 |
151 | # ------- convert X and P to CSR ------------------------------------------
152 | for i in range(k):
153 | if issparse(X[i]):
154 | X[i] = X[i].tocsr()
155 | X[i].sort_indices()
156 | for i in range(len(P)):
157 | if issparse(P[i]):
158 | P[i] = P[i].tocoo().tocsr()
159 | P[i].sort_indices()
160 |
161 | # ---------- initialize A ------------------------------------------------
162 | _log.debug('Initializing A')
163 | if ainit == 'random':
164 | A = array(rand(n, rank), dtype=dtype)
165 | elif ainit == 'nvecs':
166 | S = csr_matrix((n, n), dtype=dtype)
167 | for i in range(k):
168 | S = S + X[i]
169 | S = S + X[i].T
170 | _, A = eigsh(csr_matrix(S, dtype=dtype, shape=(n, n)), rank)
171 | A = array(A, dtype=dtype)
172 | else:
173 | raise ValueError('Unknown init option ("%s")' % ainit)
174 |
175 | # ------- initialize R and Z ---------------------------------------------
176 | R = _updateR(X, A, lmbdaR)
177 | Z = _updateZ(A, P, lmbdaV)
178 |
179 | # precompute norms of X
180 | normX = [sum(M.data ** 2) for M in X]
181 |
182 | # ------ compute factorization ------------------------------------------
183 | fit = fitchange = fitold = f = 0
184 | exectimes = []
185 | for itr in range(maxIter):
186 | tic = time.time()
187 | fitold = fit
188 | A = _updateA(X, A, R, P, Z, lmbdaA, orthogonalize)
189 | R = _updateR(X, A, lmbdaR)
190 | Z = _updateZ(A, P, lmbdaV)
191 |
192 | # compute fit value
193 | if func_compute_fval is not None:
194 | fit = func_compute_fval(X, A, R, P, Z, lmbdaA, lmbdaR, lmbdaV, normX)
195 | else:
196 | fit = np.Inf
197 |
198 | fitchange = abs(fitold - fit)
199 |
200 | toc = time.time()
201 | exectimes.append(toc - tic)
202 |
203 | _log.debug('[%3d] fval: %0.5f | delta: %7.1e | secs: %.5f' % (
204 | itr, fit, fitchange, exectimes[-1]
205 | ))
206 | if itr > 0 and fitchange < conv:
207 | break
208 | return A, R, f, itr + 1, array(exectimes)
209 |
210 |
211 | # ------------------ Update A ------------------------------------------------
212 | def _updateA(X, A, R, P, Z, lmbdaA, orthogonalize):
213 | """Update step for A"""
214 | n, rank = A.shape
215 | F = zeros((n, rank), dtype=A.dtype)
216 | E = zeros((rank, rank), dtype=A.dtype)
217 |
218 | AtA = dot(A.T, A)
219 |
220 | for i in range(len(X)):
221 | F += X[i].dot(dot(A, R[i].T)) + X[i].T.dot(dot(A, R[i]))
222 | E += dot(R[i], dot(AtA, R[i].T)) + dot(R[i].T, dot(AtA, R[i]))
223 |
224 | # regularization
225 | I = lmbdaA * eye(rank, dtype=A.dtype)
226 |
227 | # attributes
228 | for i in range(len(Z)):
229 | F += P[i].dot(Z[i].T)
230 | E += dot(Z[i], Z[i].T)
231 |
232 | # finally compute update for A
233 | A = solve(I + E.T, F.T).T
234 | return orth(A) if orthogonalize else A
235 |
236 |
237 | # ------------------ Update R ------------------------------------------------
238 | def _updateR(X, A, lmbdaR):
239 | rank = A.shape[1]
240 | U, S, Vt = svd(A, full_matrices=False)
241 | Shat = kron(S, S)
242 | Shat = (Shat / (Shat ** 2 + lmbdaR)).reshape(rank, rank)
243 | R = []
244 | for i in range(len(X)):
245 | Rn = Shat * dot(U.T, X[i].dot(U))
246 | Rn = dot(Vt.T, dot(Rn, Vt))
247 | R.append(Rn)
248 | return R
249 |
250 |
251 | # ------------------ Update Z ------------------------------------------------
252 | def _updateZ(A, P, lmbdaZ):
253 | Z = []
254 | if len(P) == 0:
255 | return Z
256 | #_log.debug('Updating Z (Norm EQ, %d)' % len(P))
257 | pinvAt = inv(dot(A.T, A) + lmbdaZ * eye(A.shape[1], dtype=A.dtype))
258 | pinvAt = dot(pinvAt, A.T).T
259 | for i in range(len(P)):
260 | if issparse(P[i]):
261 | Zn = P[i].tocoo().T.tocsr().dot(pinvAt).T
262 | else:
263 | Zn = dot(pinvAt.T, P[i])
264 | Z.append(Zn)
265 | return Z
266 |
267 |
268 | def _compute_fval(X, A, R, P, Z, lmbdaA, lmbdaR, lmbdaZ, normX):
269 | """Compute fit for full slices"""
270 | f = lmbdaA * norm(A) ** 2
271 | for i in range(len(X)):
272 | ARAt = dot(A, dot(R[i], A.T))
273 | f += (norm(X[i] - ARAt) ** 2) / normX[i] + lmbdaR * norm(R[i]) ** 2
274 | return f
275 |
276 |
277 | def _compute_fval_orth(X, A, R, P, Z, lmbdaA, lmbdaR, lmbdaZ, normX):
278 | f = lmbdaA * norm(A) ** 2
279 | for i in range(len(X)):
280 | f += (normX[i] - norm(R[i]) ** 2) / normX[i] + lmbdaR * norm(R[i]) ** 2
281 | return f
282 |
283 |
284 | def sptensor_to_list(X):
285 | from scipy.sparse import lil_matrix
286 | if X.ndim != 3:
287 | raise ValueError('Only third-order tensors are supported (ndim=%d)' % X.ndim)
288 | if X.shape[0] != X.shape[1]:
289 | raise ValueError('First and second mode must be of identical length')
290 | N = X.shape[0]
291 | K = X.shape[2]
292 | res = [lil_matrix((N, N)) for _ in range(K)]
293 | for n in range(X.nnz()):
294 | res[X.subs[2][n]][X.subs[0][n], X.subs[1][n]] = X.vals[n]
295 | return res
296 |
297 | def orth(A):
298 | [U, _, Vt] = svd(A, full_matrices=0)
299 | return dot(U, Vt)
300 |
--------------------------------------------------------------------------------
/sktensor/setup.py:
--------------------------------------------------------------------------------
1 | def configuration(parent_package='', top_path=None):
2 | from numpy.distutils.misc_util import Configuration
3 | config = Configuration('sktensor', parent_package, top_path)
4 |
5 | config.add_subpackage('tests')
6 |
7 | return config
8 |
--------------------------------------------------------------------------------
/sktensor/sptensor.py:
--------------------------------------------------------------------------------
1 | # sktensor.sptensor - base module for sparse tensors
2 | # Copyright (C) 2013 Maximilian Nickel
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | import numpy as np
18 | from numpy import zeros, ones, array, arange, copy, ravel_multi_index, unravel_index
19 | from numpy import setdiff1d, hstack, hsplit, vsplit, sort, prod, lexsort, unique, bincount
20 | from scipy.sparse import coo_matrix
21 | from scipy.sparse import issparse as issparse_mat
22 | from sktensor.core import tensor_mixin
23 | from sktensor.utils import accum
24 | from sktensor.dtensor import unfolded_dtensor
25 | from sktensor.pyutils import inherit_docstring_from, from_to_without
26 |
27 |
28 | __all__ = [
29 | 'concatenate',
30 | 'fromarray',
31 | 'sptensor',
32 | 'unfolded_sptensor',
33 | ]
34 |
35 |
36 | class sptensor(tensor_mixin):
37 | """
38 | A sparse tensor.
39 |
40 | Data is stored in COOrdinate format.
41 |
42 | Sparse tensors can be instantiated via
43 |
44 | Parameters
45 | ----------
46 | subs : n-tuple of array-likes
47 | Subscripts of the nonzero entries in the tensor.
48 | Length of tuple n must be equal to dimension of tensor.
49 | vals : array-like
50 | Values of the nonzero entries in the tensor.
51 | shape : n-tuple, optional
52 | Shape of the sparse tensor.
53 | Length of tuple n must be equal to dimension of tensor.
54 | dtype : dtype, optional
55 | Type of the entries in the tensor
56 | accumfun : function pointer
57 | Function to be accumulate duplicate entries
58 |
59 | Examples
60 | --------
61 | >>> S = sptensor(([0,1,2], [3,2,0], [2,2,2]), [1,1,1], shape=(10, 20, 5), dtype=np.float)
62 | >>> S.shape
63 | (10, 20, 5)
64 | >>> S.dtype
65 |
66 | """
67 |
68 | def __init__(self, subs, vals, shape=None, dtype=None, accumfun=None, issorted=False):
69 | if not isinstance(subs, tuple):
70 | raise ValueError('Subscripts must be a tuple of array-likes')
71 | if len(subs[0]) != len(vals):
72 | raise ValueError('Subscripts and values must be of equal length')
73 | if dtype is None:
74 | dtype = array(vals).dtype
75 | for i in range(len(subs)):
76 | if array(subs[i]).dtype.kind != 'i':
77 | raise ValueError('Subscripts must be integers')
78 |
79 | vals = array(vals, dtype=dtype)
80 | if accumfun is not None:
81 | vals, subs = accum(
82 | subs, vals,
83 | issorted=False, with_subs=True, func=accumfun
84 | )
85 | self.subs = subs
86 | self.vals = vals
87 | self.dtype = dtype
88 | self.issorted = issorted
89 | self.accumfun = accumfun
90 |
91 | if shape is None:
92 | self.shape = tuple(array(subs).max(axis=1).flatten() + 1)
93 | else:
94 | self.shape = tuple(int(d) for d in shape)
95 | self.ndim = len(subs)
96 |
97 | def __eq__(self, other):
98 | if isinstance(other, sptensor):
99 | self._sort()
100 | other._sort()
101 | return (self.vals == other.vals).all() and (array(self.subs) == array(other.subs)).all()
102 | elif isinstance(other, np.ndarray):
103 | return (self.toarray() == other).all()
104 | else:
105 | raise NotImplementedError('Unsupported object class for sptensor.__eq__ (%s)' % type(other))
106 |
107 | def __getitem__(self, idx):
108 | # TODO check performance
109 | if len(idx) != self.ndim:
110 | raise ValueError('subscripts must be complete')
111 | sidx = ones(len(self.vals))
112 | for i in range(self.ndim):
113 | sidx = np.logical_and(self.subs[i] == idx[i], sidx)
114 | vals = self.vals[sidx]
115 | if len(vals) == 0:
116 | vals = 0
117 | elif len(vals) > 1:
118 | if self.accumfun is None:
119 | raise ValueError('Duplicate entries without specified accumulation function')
120 | vals = self.accumfun(vals)
121 | return vals
122 |
123 | def __sub__(self, other):
124 | if isinstance(other, np.ndarray):
125 | res = -other
126 | res[self.subs] += self.vals
127 | else:
128 | raise NotImplementedError()
129 | return res
130 |
131 | def _sort(self):
132 | # TODO check performance
133 | subs = array(self.subs)
134 | sidx = lexsort(subs)
135 | self.subs = tuple(z.flatten()[sidx] for z in vsplit(subs, len(self.shape)))
136 | self.vals = self.vals[sidx]
137 | self.issorted = True
138 |
139 | def _ttm_compute(self, V, mode, transp):
140 | Z = self.unfold(mode, transp=True).tocsr()
141 | if transp:
142 | V = V.T
143 | Z = Z.dot(V.T)
144 | shape = copy(self.shape)
145 | shape[mode] = V.shape[0]
146 | if issparse_mat(Z):
147 | newT = unfolded_sptensor((Z.data, (Z.row, Z.col)), [mode], None, shape=shape).fold()
148 | else:
149 | newT = unfolded_dtensor(Z.T, mode, shape).fold()
150 |
151 | return newT
152 |
153 | def _ttv_compute(self, v, dims, vidx, remdims):
154 | nvals = self.vals
155 | nsubs = self.subs
156 | for i in range(len(dims)):
157 | idx = nsubs[dims[i]]
158 | w = v[vidx[i]]
159 | nvals = nvals * w[idx]
160 |
161 | # Case 1: all dimensions used -> return sum
162 | if len(remdims) == 0:
163 | return nvals.sum()
164 |
165 | nsubs = tuple(self.subs[i] for i in remdims)
166 | nshp = tuple(self.shape[i] for i in remdims)
167 |
168 | # Case 2: result is a vector
169 | if len(remdims) == 1:
170 | usubs = unique(nsubs[0])
171 | bins = usubs.searchsorted(nsubs[0])
172 | c = bincount(bins, weights=nvals)
173 | (nz,) = c.nonzero()
174 | return sptensor((usubs[nz],), c[nz], nshp)
175 |
176 | # Case 3: result is an array
177 | return sptensor(nsubs, nvals, shape=nshp, accumfun=np.sum)
178 |
179 | def _ttm_me_compute(self, V, edims, sdims, transp):
180 | """
181 | Assume Y = T x_i V_i for i = 1...n can fit into memory
182 | """
183 | shapeY = np.copy(self.shape)
184 |
185 | # Determine size of Y
186 | for n in np.union1d(edims, sdims):
187 | shapeY[n] = V[n].shape[1] if transp else V[n].shape[0]
188 |
189 | # Allocate Y (final result) and v (vectors for elementwise computations)
190 | Y = zeros(shapeY)
191 | shapeY = array(shapeY)
192 | v = [None for _ in range(len(edims))]
193 |
194 | for i in range(np.prod(shapeY[edims])):
195 | rsubs = unravel_index(shapeY[edims], i)
196 |
197 | def unfold(self, rdims, cdims=None, transp=False):
198 | if isinstance(rdims, type(1)):
199 | rdims = [rdims]
200 | if transp:
201 | cdims = rdims
202 | rdims = setdiff1d(range(self.ndim), cdims)[::-1]
203 | elif cdims is None:
204 | cdims = setdiff1d(range(self.ndim), rdims)[::-1]
205 | if not (arange(self.ndim) == sort(hstack((rdims, cdims)))).all():
206 | raise ValueError(
207 | 'Incorrect specification of dimensions (rdims: %s, cdims: %s)'
208 | % (str(rdims), str(cdims))
209 | )
210 | M = prod([self.shape[r] for r in rdims])
211 | N = prod([self.shape[c] for c in cdims])
212 | ridx = _build_idx(self.subs, self.vals, rdims, self.shape)
213 | cidx = _build_idx(self.subs, self.vals, cdims, self.shape)
214 | return unfolded_sptensor((self.vals, (ridx, cidx)), (M, N), rdims, cdims, self.shape)
215 |
216 | @inherit_docstring_from(tensor_mixin)
217 | def uttkrp(self, U, mode):
218 | R = U[1].shape[1] if mode == 0 else U[0].shape[1]
219 | #dims = list(range(0, mode)) + list(range(mode + 1, self.ndim))
220 | dims = from_to_without(0, self.ndim, mode)
221 | V = zeros((self.shape[mode], R))
222 | for r in range(R):
223 | Z = tuple(U[n][:, r] for n in dims)
224 | TZ = self.ttv(Z, mode, without=True)
225 | if isinstance(TZ, sptensor):
226 | V[TZ.subs, r] = TZ.vals
227 | else:
228 | V[:, r] = self.ttv(Z, mode, without=True)
229 | return V
230 |
231 | @inherit_docstring_from(tensor_mixin)
232 | def transpose(self, axes=None):
233 | """
234 | Compute transpose of sparse tensors.
235 |
236 | Parameters
237 | ----------
238 | axes : array_like of ints, optional
239 | Permute the axes according to the values given.
240 |
241 | Returns
242 | -------
243 | d : dtensor
244 | dtensor with axes permuted.
245 | """
246 | if axes is None:
247 | raise NotImplementedError(
248 | 'Sparse tensor transposition without axes argument is not supported'
249 | )
250 | nsubs = tuple([self.subs[idx] for idx in axes])
251 | nshape = [self.shape[idx] for idx in axes]
252 | return sptensor(nsubs, self.vals, nshape)
253 |
254 | def concatenate(self, tpl, axis=None):
255 | """
256 | Concatenates sparse tensors.
257 |
258 | Parameters
259 | ----------
260 | tpl : tuple of sparse tensors
261 | Tensors to be concatenated.
262 | axis : int, optional
263 | Axis along which concatenation should take place
264 | """
265 | if axis is None:
266 | raise NotImplementedError(
267 | 'Sparse tensor concatenation without axis argument is not supported'
268 | )
269 | T = self
270 | for i in range(1, len(tpl)):
271 | T = _single_concatenate(T, tpl[i], axis=axis)
272 | return T
273 |
274 | def norm(self):
275 | """
276 | Frobenius norm for tensors
277 |
278 | References
279 | ----------
280 | [Kolda and Bader, 2009; p.457]
281 | """
282 | return np.linalg.norm(self.vals)
283 |
284 | def toarray(self):
285 | A = zeros(self.shape)
286 | A.put(ravel_multi_index(self.subs, tuple(self.shape)), self.vals)
287 | return A
288 |
289 |
290 | class unfolded_sptensor(coo_matrix):
291 | """
292 | An unfolded sparse tensor.
293 |
294 | Data is stored in form of a sparse COO matrix.
295 | Unfolded_sptensor objects additionall hold information about the
296 | original tensor, such that re-folding the tensor into its original
297 | shape can be done easily.
298 |
299 | Unfolded_sptensor objects can be instantiated via
300 |
301 | Parameters
302 | ----------
303 | tpl : (data, (i, j)) tuple
304 | Construct sparse matrix from three arrays:
305 | 1. ``data[:]`` the entries of the matrix, in any order
306 | 2. ``i[:]`` the row indices of the matrix entries
307 | 3. ``j[:]`` the column indices of the matrix entries
308 | where ``A[i[k], j[k]] = data[k]``.
309 | shape : tuple of integers
310 | Shape of the unfolded tensor.
311 | rdims : array_like of integers
312 | Modes of the original tensor that are mapped onto rows.
313 | cdims : array_like of integers
314 | Modes of the original tensor that are mapped onto columns.
315 | ten_shape : tuple of integers
316 | Shape of the original tensor.
317 | dtype : np.dtype, optional
318 | Data type of the unfolded tensor.
319 | copy : boolean, optional
320 | If true, data and subscripts are copied.
321 |
322 | Returns
323 | -------
324 | M : unfolded_sptensor
325 | Sparse matrix in COO format where ``rdims`` are mapped to rows and
326 | ``cdims`` are mapped to columns of the matrix.
327 | """
328 |
329 | def __init__(self, tpl, shape, rdims, cdims, ten_shape, dtype=None, copy=False):
330 | self.ten_shape = array(ten_shape)
331 | if isinstance(rdims, int):
332 | rdims = [rdims]
333 | if cdims is None:
334 | cdims = setdiff1d(range(len(self.ten_shape)), rdims)[::-1]
335 | self.rdims = rdims
336 | self.cdims = cdims
337 | super(unfolded_sptensor, self).__init__(tpl, shape=shape, dtype=dtype, copy=copy)
338 |
339 | def fold(self):
340 | """
341 | Recreate original tensor by folding unfolded_sptensor according toc
342 | ``ten_shape``.
343 |
344 | Returns
345 | -------
346 | T : sptensor
347 | Sparse tensor that is created by refolding according to ``ten_shape``.
348 | """
349 | nsubs = zeros((len(self.data), len(self.ten_shape)), dtype=np.int)
350 | if len(self.rdims) > 0:
351 | nidx = unravel_index(self.row, self.ten_shape[self.rdims])
352 | for i in range(len(self.rdims)):
353 | nsubs[:, self.rdims[i]] = nidx[i]
354 | if len(self.cdims) > 0:
355 | nidx = unravel_index(self.col, self.ten_shape[self.cdims])
356 | for i in range(len(self.cdims)):
357 | nsubs[:, self.cdims[i]] = nidx[i]
358 | nsubs = [z.flatten() for z in hsplit(nsubs, len(self.ten_shape))]
359 | return sptensor(tuple(nsubs), self.data, self.ten_shape)
360 |
361 |
362 | def fromarray(A):
363 | """Create a sptensor from a dense numpy array"""
364 | subs = np.nonzero(A)
365 | vals = A[subs]
366 | return sptensor(subs, vals, shape=A.shape, dtype=A.dtype)
367 |
368 |
369 | def _single_concatenate(ten, other, axis):
370 | tshape = ten.shape
371 | oshape = other.shape
372 | if len(tshape) != len(oshape):
373 | raise ValueError("len(tshape) != len(oshape")
374 | oaxes = setdiff1d(range(len(tshape)), [axis])
375 | for i in oaxes:
376 | if tshape[i] != oshape[i]:
377 | raise ValueError("Dimensions must match")
378 | nsubs = [None for _ in range(len(tshape))]
379 | for i in oaxes:
380 | nsubs[i] = np.concatenate((ten.subs[i], other.subs[i]))
381 | nsubs[axis] = np.concatenate((
382 | ten.subs[axis], other.subs[axis] + tshape[axis]
383 | ))
384 | nvals = np.concatenate((ten.vals, other.vals))
385 | nshape = np.copy(tshape)
386 | nshape[axis] = tshape[axis] + oshape[axis]
387 | return sptensor(nsubs, nvals, nshape)
388 |
389 |
390 | def _build_idx(subs, vals, dims, tshape):
391 | shape = array([tshape[d] for d in dims], ndmin=1)
392 | dims = array(dims, ndmin=1)
393 | if len(shape) == 0:
394 | idx = ones(len(vals), dtype=vals.dtype)
395 | elif len(subs) == 0:
396 | idx = array(tuple())
397 | else:
398 | idx = ravel_multi_index(tuple(subs[i] for i in dims), shape)
399 | return idx
400 |
--------------------------------------------------------------------------------
/sktensor/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mnick/scikit-tensor/fe517e9661a08164b8d30d2dddf7c96aeeabcf36/sktensor/tests/__init__.py
--------------------------------------------------------------------------------
/sktensor/tests/sptensor_fixture.py:
--------------------------------------------------------------------------------
1 | from numpy import array
2 | import pytest
3 |
4 |
5 | @pytest.fixture
6 | def subs():
7 | return (
8 | array([0, 1, 0, 5, 7, 8]),
9 | array([2, 0, 4, 5, 3, 9]),
10 | array([0, 1, 2, 2, 1, 0])
11 | )
12 |
13 |
14 | @pytest.fixture
15 | def vals():
16 | return array([1, 2, 3, 4, 5, 6.1])
17 |
18 |
19 | @pytest.fixture
20 | def shape():
21 | return (10, 12, 3)
22 |
--------------------------------------------------------------------------------
/sktensor/tests/sptensor_rand_fixture.py:
--------------------------------------------------------------------------------
1 | from numpy.random import randint, seed
2 | import pytest
3 |
4 |
5 | @pytest.fixture
6 | def sptensor_seed():
7 | return seed(5)
8 |
9 |
10 | @pytest.fixture
11 | def sz():
12 | return 100
13 |
14 |
15 | @pytest.fixture
16 | def vals(sptensor_seed, sz):
17 | return randint(0, 100, sz)
18 |
19 |
20 | @pytest.fixture
21 | def shape():
22 | return (25, 11, 18, 7, 2)
23 |
24 |
25 | @pytest.fixture
26 | def subs(sptensor_seed, shape, sz):
27 | return tuple(randint(0, shape[i], sz) for i in range(len(shape)))
28 |
--------------------------------------------------------------------------------
/sktensor/tests/test_base.py:
--------------------------------------------------------------------------------
1 | from numpy import array
2 | from numpy.random import randn
3 | from sktensor.core import *
4 | from sktensor import dtensor, sptensor, ktensor
5 | from .ttm_fixture import T, U, Y
6 | from .sptensor_fixture import shape, vals, subs
7 |
8 |
9 | def test_check_multiplication_dims():
10 | ndims = 3
11 | M = 2
12 | assert ([1, 2] == check_multiplication_dims(0, ndims, M, without=True)).all()
13 | assert ([0, 2] == check_multiplication_dims(1, ndims, M, without=True)).all()
14 | assert ([0, 1] == check_multiplication_dims(2, ndims, M, without=True)).all()
15 |
16 |
17 | def test_khatrirao():
18 | A = array([
19 | [1, 2, 3],
20 | [4, 5, 6],
21 | [7, 8, 9]
22 | ])
23 | B = array([
24 | [1, 4, 7],
25 | [2, 5, 8],
26 | [3, 6, 9]
27 | ])
28 | C = array([
29 | [1, 8, 21],
30 | [2, 10, 24],
31 | [3, 12, 27],
32 | [4, 20, 42],
33 | [8, 25, 48],
34 | [12, 30, 54],
35 | [7, 32, 63],
36 | [14, 40, 72],
37 | [21, 48, 81]
38 | ])
39 |
40 | D = khatrirao((A, B))
41 | assert C.shape == D.shape
42 | assert (C == D).all()
43 |
44 |
45 | def test_dense_fold(T):
46 | X = dtensor(T)
47 | I, J, K = T.shape
48 | X1 = X[:, :, 0]
49 | X2 = X[:, :, 1]
50 |
51 | U = X.unfold(0)
52 | assert (3, 8) == U.shape
53 | for j in range(J):
54 | assert (U[:, j] == X1[:, j]).all()
55 | assert (U[:, j + J] == X2[:, j]).all()
56 |
57 | U = X.unfold(1)
58 | assert (4, 6) == U.shape
59 | for i in range(I):
60 | assert (U[:, i] == X1[i, :]).all()
61 | assert (U[:, i + I] == X2[i, :]).all()
62 |
63 | U = X.unfold(2)
64 | assert (2, 12) == U.shape
65 | for k in range(U.shape[1]):
66 | assert (U[:, k] == array([X1.flatten('F')[k], X2.flatten('F')[k]])).all()
67 |
68 |
69 | def test_dtensor_fold_unfold():
70 | sz = (10, 35, 3, 12)
71 | X = dtensor(randn(*sz))
72 | for i in range(4):
73 | U = X.unfold(i).fold()
74 | assert (X == U).all()
75 |
76 |
77 | def test_dtensor_ttm(T, U, Y):
78 | X = dtensor(T)
79 | Y2 = X.ttm(U, 0)
80 | assert (2, 4, 2) == Y2.shape
81 | assert (Y == Y2).all()
82 |
83 |
84 | def test_spttv(subs, vals, shape):
85 | #subs = (
86 | # array([0, 1, 0, 5, 7, 8]),
87 | # array([2, 0, 4, 5, 3, 9]),
88 | # array([0, 1, 2, 2, 1, 0])
89 | #)
90 | #vals = array([1, 1, 1, 1, 1, 1])
91 | S = sptensor(subs, vals, shape=shape)
92 | K = ktensor([randn(shape[0], 2), randn(shape[1], 2), randn(shape[2], 2)])
93 | K.innerprod(S)
94 |
--------------------------------------------------------------------------------
/sktensor/tests/test_dtensor.py:
--------------------------------------------------------------------------------
1 | from numpy import array
2 | from numpy.random import randn
3 | from sktensor.dtensor import dtensor
4 | from .ttm_fixture import T, U, Y
5 |
6 |
7 | def test_new():
8 | sz = (10, 23, 5)
9 | A = randn(*sz)
10 | T = dtensor(A)
11 | assert A.ndim == T.ndim
12 | assert A.shape == T.shape
13 | assert (A == T).all()
14 | assert (T == A).all()
15 |
16 |
17 | def test_dense_fold(T):
18 | X = dtensor(T)
19 | I, J, K = T.shape
20 | X1 = X[:, :, 0]
21 | X2 = X[:, :, 1]
22 |
23 | U = X.unfold(0)
24 | assert (3, 8) == U.shape
25 | for j in range(J):
26 | assert (U[:, j] == X1[:, j]).all()
27 | assert (U[:, j + J] == X2[:, j]).all()
28 |
29 | U = X.unfold(1)
30 | assert (4, 6) == U.shape
31 | for i in range(I):
32 | assert (U[:, i] == X1[i, :]).all()
33 | assert (U[:, i + I] == X2[i, :]).all()
34 |
35 | U = X.unfold(2)
36 | assert (2, 12) == U.shape
37 | for k in range(U.shape[1]):
38 | assert (U[:, k] == array([X1.flatten('F')[k], X2.flatten('F')[k]])).all()
39 |
40 |
41 | def test_dtensor_fold_unfold():
42 | sz = (10, 35, 3, 12)
43 | X = dtensor(randn(*sz))
44 | for i in range(4):
45 | U = X.unfold(i).fold()
46 | assert (X == U).all()
47 |
48 |
49 | def test_dtensor_ttm(T, Y, U):
50 | X = dtensor(T)
51 | Y2 = X.ttm(U, 0)
52 | assert (2, 4, 2) == Y2.shape
53 | assert (Y == Y2).all()
54 |
--------------------------------------------------------------------------------
/sktensor/tests/test_ktensor.py:
--------------------------------------------------------------------------------
1 | from numpy.random import randn
2 | from sktensor import ktensor
3 |
4 |
5 | def test_vectorization():
6 | rank = 5
7 | shape = (5, 27, 3, 13)
8 | U = [randn(s, rank) for s in shape]
9 | K = ktensor(U)
10 | v = K.tovec()
11 | K2 = v.toktensor()
12 |
13 | assert sum([s * rank for s in shape]) == len(v.v)
14 | assert K == K2
15 |
--------------------------------------------------------------------------------
/sktensor/tests/test_pyutils.py:
--------------------------------------------------------------------------------
1 | from sktensor.pyutils import *
2 |
3 |
4 | def test_from_to_without():
5 | frm, to, without = 2, 88, 47
6 | lst = list(range(frm, without)) + list(range(without + 1, to))
7 | assert lst == from_to_without(frm, to, without)
8 |
9 | rlst = list(range(to - 1, without, -1)) + list(range(without - 1, frm - 1,-1))
10 | assert rlst == from_to_without(frm, to, without, reverse=True)
11 | assert lst[::-1] == from_to_without(frm, to, without, reverse=True)
12 |
--------------------------------------------------------------------------------
/sktensor/tests/test_sptensor.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import numpy as np
3 | from numpy import ones, zeros, array, setdiff1d, allclose
4 | from numpy.random import randint
5 | from sktensor.dtensor import dtensor
6 | from sktensor.sptensor import sptensor, fromarray
7 | from .ttm_fixture import T, U, Y
8 | from .sptensor_rand_fixture import subs, vals, shape, sptensor_seed, sz
9 |
10 |
11 | def setup_diagonal():
12 | """
13 | Setup data for a 20x20x20 diagonal tensor
14 | """
15 | n = 20
16 | shape = (n, n, n)
17 | subs = [np.arange(0, shape[i]) for i in range(len(shape))]
18 | vals = ones(n)
19 | return tuple(subs), vals, shape
20 |
21 |
22 | def test_init(subs, vals, shape):
23 | """
24 | Creation of new sptensor objects
25 | """
26 | T = sptensor(subs, vals, shape)
27 | assert len(shape) == T.ndim
28 | assert (array(shape) == T.shape).all()
29 |
30 | T = sptensor(subs, vals)
31 | tshape = array(subs).max(axis=1) + 1
32 | assert len(subs) == len(T.shape)
33 | assert (tshape == array(T.shape)).all()
34 |
35 |
36 | def test_init_diagonal():
37 | subs, vals, shape = setup_diagonal()
38 | T = sptensor(subs, vals, shape)
39 | assert len(shape) == T.ndim
40 | assert (array(shape) == T.shape).all()
41 |
42 | T = sptensor(subs, vals)
43 | assert len(subs) == len(T.shape)
44 | assert (shape == array(T.shape)).all()
45 |
46 |
47 | def test_non2Dsubs():
48 | with pytest.raises(ValueError):
49 | sptensor(randint(0, 10, 18).reshape(3, 3, 2), ones(10))
50 |
51 |
52 | def test_nonEqualLength(subs):
53 | with pytest.raises(ValueError):
54 | sptensor(subs, ones(len(subs) + 1))
55 |
56 |
57 | def test_unfold(T, subs, vals, shape):
58 | Td = dtensor(zeros(shape, dtype=np.float32))
59 | Td[subs] = vals
60 |
61 | for i in range(len(shape)):
62 | rdims = [i]
63 | cdims = setdiff1d(range(len(shape)), rdims)[::-1]
64 | Md = Td.unfold(i)
65 |
66 | T = sptensor(subs, vals, shape, accumfun=lambda l: l[-1])
67 |
68 | Ms = T.unfold(rdims, cdims)
69 | assert Md.shape == Ms.shape
70 | assert (allclose(Md, Ms.toarray()))
71 |
72 | Ms = T.unfold(rdims)
73 | assert Md.shape == Ms.shape
74 | assert (allclose(Md, Ms.toarray()))
75 |
76 | Md = Md.T
77 | Ms = T.unfold(rdims, cdims, transp=True)
78 | assert Md.shape == Ms.shape
79 | assert (allclose(Md, Ms.toarray()))
80 |
81 |
82 | def test_fold(subs, vals, shape):
83 | T = sptensor(subs, vals, shape)
84 | for i in range(len(shape)):
85 | X = T.unfold([i]).fold()
86 | assert shape == tuple(T.shape)
87 | assert len(shape) == len(T.subs)
88 | assert len(subs) == len(T.subs)
89 | assert X == T
90 | for j in range(len(subs)):
91 | subs[j].sort()
92 | T.subs[j].sort()
93 | assert (subs[j] == T.subs[j]).all()
94 |
95 |
96 | def test_ttm(T, Y, U):
97 | S = sptensor(T.nonzero(), T.flatten(), T.shape)
98 | Y2 = S.ttm(U, 0)
99 | assert (2, 4, 2) == Y2.shape
100 | assert (Y == Y2).all()
101 |
102 |
103 | def test_ttv_sparse_result():
104 | # Test case by Andre Panisson to check return type of sptensor.ttv
105 | subs = (
106 | array([0, 1, 0, 5, 7, 8]),
107 | array([2, 0, 4, 5, 3, 9]),
108 | array([0, 1, 2, 2, 1, 0])
109 | )
110 | vals = array([1, 1, 1, 1, 1, 1])
111 | S = sptensor(subs, vals, shape=[10, 10, 3])
112 |
113 | sttv = S.ttv((zeros(10), zeros(10)), modes=[0, 1])
114 | assert type(sttv) == sptensor
115 | # sparse tensor should return only nonzero vals
116 | assert (allclose(np.array([]), sttv.vals))
117 | assert (allclose(np.array([]), sttv.subs))
118 | assert sttv.shape == (3,)
119 |
120 |
121 | def test_ttv(T):
122 | result = array([
123 | [70, 190],
124 | [80, 200],
125 | [90, 210]
126 | ])
127 |
128 | X = fromarray(T)
129 | v = array([1, 2, 3, 4])
130 | Xv = X.ttv(v, 1)
131 |
132 | assert (3, 2) == Xv.shape
133 | assert (Xv == result).all()
134 |
135 |
136 | def test_sttm_me(T, U):
137 | S = sptensor(T.nonzero(), T.flatten(), T.shape)
138 | S._ttm_me_compute(U, [1], [0], False)
139 |
140 |
141 | def test_sp_uttkrp(subs, vals, shape):
142 | # Test case by Andre Panisson, sparse ttv
143 | # see issue #3
144 | S = sptensor(subs, vals, shape)
145 | U = []
146 | for shp in shape:
147 | U.append(np.zeros((shp, 5)))
148 | SU = S.uttkrp(U, mode=0)
149 | assert SU.shape == (25, 5)
150 |
151 |
152 | def test_getitem():
153 | subs = (
154 | array([0, 1, 0, 5, 7, 8]),
155 | array([2, 0, 4, 5, 3, 9]),
156 | array([0, 1, 2, 2, 1, 0])
157 | )
158 | vals = array([1, 2, 3, 4, 5, 6])
159 | S = sptensor(subs, vals, shape=[10, 10, 3])
160 | assert 0 == S[1, 1, 1]
161 | assert 0 == S[1, 2, 3]
162 | assert 1 == S[0, 2, 0]
163 | assert 2 == S[1, 0, 1]
164 | assert 3 == S[0, 4, 2]
165 | assert 4 == S[5, 5, 2]
166 | assert 5 == S[7, 3, 1]
167 | assert 6 == S[8, 9, 0]
168 |
169 |
170 | def test_add():
171 | subs = (
172 | array([0, 1, 0]),
173 | array([2, 0, 2]),
174 | array([0, 1, 2])
175 | )
176 | vals = array([1, 2, 3])
177 | S = sptensor(subs, vals, shape=[3, 3, 3])
178 | D = np.arange(27).reshape(3, 3, 3)
179 | T = S - D
180 | for i in range(3):
181 | for j in range(3):
182 | for k in range(3):
183 | assert S[i, j, k] - D[i, j, k] == T[i, j, k]
184 |
--------------------------------------------------------------------------------
/sktensor/tests/test_tucker_hooi.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import logging
3 | from numpy import allclose
4 | from numpy.random import randn
5 | from scipy.sparse import rand as sprand
6 | from sktensor import tucker
7 | from sktensor.core import ttm
8 | from sktensor.dtensor import dtensor, unfolded_dtensor
9 | from sktensor.sptensor import unfolded_sptensor
10 | #from sktensor.rotation import orthomax
11 |
12 | logging.basicConfig(level=logging.INFO)
13 |
14 |
15 | def normalize(X):
16 | return X / X.sum(axis=0)
17 |
18 |
19 | def disabled_test_factorization():
20 | I, J, K, rank = 10, 20, 75, 5
21 | A = orthomax(randn(I, rank))
22 | B = orthomax(randn(J, rank))
23 | C = orthomax(randn(K, rank))
24 |
25 | core_real = dtensor(randn(rank, rank, rank))
26 | T = core_real.ttm([A, B, C])
27 | core, U = tucker.hooi(T, rank)
28 |
29 | assert allclose(T, ttm(core, U))
30 | assert allclose(A, orthomax(U[0]))
31 | assert allclose(B, orthomax(U[1]))
32 | assert allclose(C, orthomax(U[2]))
33 | assert allclose(core_real, core)
34 |
35 |
36 | def disabled_test_factorization_sparse():
37 | I, J, K, rank = 10, 20, 75, 5
38 | Tmat = sprand(I, J * K, 0.1).tocoo()
39 | T = unfolded_sptensor((Tmat.data, (Tmat.row, Tmat.col)), None, 0, [], (I, J, K)).fold()
40 | core, U = tucker.hooi(T, rank, maxIter=20)
41 |
42 | Tmat = Tmat.toarray()
43 | T = unfolded_dtensor(Tmat, 0, (I, J, K)).fold()
44 | core2, U2 = tucker.hooi(T, rank, maxIter=20)
45 |
46 | assert allclose(core2, core)
47 | for i in range(len(U)):
48 | assert allclose(U2[i], U[i])
49 |
--------------------------------------------------------------------------------
/sktensor/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | from ..utils import accum
2 | from numpy import array, allclose
3 |
4 |
5 | def test_accum():
6 | subs1 = array([0, 1, 1, 2, 2, 2])
7 | subs2 = array([0, 1, 1, 1, 2, 2])
8 | vals = array([1, 2, 3, 4, 5, 6])
9 | nvals, nsubs = accum((subs1, subs2), vals, with_subs=True)
10 | assert allclose(nvals, array([1, 5, 4, 11]))
11 | assert allclose(nsubs[0], array([0, 1, 2, 2]))
12 | assert allclose(nsubs[1], array([0, 1, 1, 2]))
13 |
14 | subs1 = array([0, 0, 1])
15 | subs2 = array([0, 0, 1])
16 | vals = array([1, 2, 3])
17 | nvals, nsubs = accum((subs1, subs2), vals, with_subs=True)
18 | assert allclose(nvals, array([3, 3]))
19 | assert allclose(nsubs[0], array([0, 1]))
20 | assert allclose(nsubs[1], array([0, 1]))
21 |
--------------------------------------------------------------------------------
/sktensor/tests/ttm_fixture.py:
--------------------------------------------------------------------------------
1 | from numpy import array, zeros
2 | import pytest
3 |
4 |
5 | @pytest.fixture
6 | def T():
7 | T = zeros((3, 4, 2))
8 | T[:, :, 0] = array([[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]])
9 | T[:, :, 1] = array([[13, 16, 19, 22], [14, 17, 20, 23], [15, 18, 21, 24]])
10 | return T
11 |
12 |
13 | @pytest.fixture
14 | def Y():
15 | Y = zeros((2, 4, 2))
16 | Y[:, :, 0] = array([[22, 49, 76, 103], [28, 64, 100, 136]])
17 | Y[:, :, 1] = array([[130, 157, 184, 211], [172, 208, 244, 280]])
18 | return Y
19 |
20 |
21 | @pytest.fixture
22 | def U():
23 | return array([[1, 3, 5], [2, 4, 6]])
24 |
--------------------------------------------------------------------------------
/sktensor/tucker.py:
--------------------------------------------------------------------------------
1 | # sktensor.tucker - Algorithms to compute Tucker decompositions
2 | # Copyright (C) 2013 Maximilian Nickel
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | import logging
18 | import time
19 | import numpy as np
20 | from numpy import array, ones, sqrt
21 | from numpy.random import rand
22 | from .pyutils import is_number
23 | from .core import ttm, nvecs, norm
24 |
25 | __all__ = [
26 | 'hooi',
27 | 'hosvd',
28 | ]
29 |
30 | _log = logging.getLogger('TUCKER')
31 | __DEF_MAXITER = 500
32 | __DEF_INIT = 'nvecs'
33 | __DEF_CONV = 1e-7
34 |
35 |
36 | def hooi(X, rank, **kwargs):
37 | """
38 | Compute Tucker decomposition of a tensor using Higher-Order Orthogonal
39 | Iterations.
40 |
41 | Parameters
42 | ----------
43 | X : tensor_mixin
44 | The tensor to be decomposed
45 | rank : array_like
46 | The rank of the decomposition for each mode of the tensor.
47 | The length of ``rank`` must match the number of modes of ``X``.
48 | init : {'random', 'nvecs'}, optional
49 | The initialization method to use.
50 | - random : Factor matrices are initialized randomly.
51 | - nvecs : Factor matrices are initialzed via HOSVD.
52 | default : 'nvecs'
53 |
54 | Examples
55 | --------
56 | Create dense tensor
57 |
58 | >>> T = np.zeros((3, 4, 2))
59 | >>> T[:, :, 0] = [[ 1, 4, 7, 10], [ 2, 5, 8, 11], [3, 6, 9, 12]]
60 | >>> T[:, :, 1] = [[13, 16, 19, 22], [14, 17, 20, 23], [15, 18, 21, 24]]
61 | >>> T = dtensor(T)
62 |
63 | Compute Tucker decomposition of ``T`` with n-rank [2, 3, 1] via higher-order
64 | orthogonal iterations
65 |
66 | >>> Y = hooi(T, [2, 3, 1], init='nvecs')
67 |
68 | Shape of the core tensor matches n-rank of the decomposition.
69 |
70 | >>> Y['core'].shape
71 | (2, 3, 1)
72 | >>> Y['U'][1].shape
73 | (3, 2)
74 |
75 | References
76 | ----------
77 | .. [1] L. De Lathauwer, B. De Moor, J. Vandewalle: On the best rank-1 and
78 | rank-(R_1, R_2, \ldots, R_N) approximation of higher order tensors;
79 | IEEE Trans. Signal Process. 49 (2001), pp. 2262-2271
80 | """
81 | # init options
82 | ainit = kwargs.pop('init', __DEF_INIT)
83 | maxIter = kwargs.pop('maxIter', __DEF_MAXITER)
84 | conv = kwargs.pop('conv', __DEF_CONV)
85 | dtype = kwargs.pop('dtype', X.dtype)
86 | if not len(kwargs) == 0:
87 | raise ValueError('Unknown keywords (%s)' % (kwargs.keys()))
88 |
89 | ndims = X.ndim
90 | if is_number(rank):
91 | rank = rank * ones(ndims)
92 |
93 | normX = norm(X)
94 |
95 | U = __init(ainit, X, ndims, rank, dtype)
96 | fit = 0
97 | exectimes = []
98 | for itr in range(maxIter):
99 | tic = time.clock()
100 | fitold = fit
101 |
102 | for n in range(ndims):
103 | Utilde = ttm(X, U, n, transp=True, without=True)
104 | U[n] = nvecs(Utilde, n, rank[n])
105 |
106 | # compute core tensor to get fit
107 | core = ttm(Utilde, U, n, transp=True)
108 |
109 | # since factors are orthonormal, compute fit on core tensor
110 | normresidual = sqrt(normX ** 2 - norm(core) ** 2)
111 |
112 | # fraction explained by model
113 | fit = 1 - (normresidual / normX)
114 | fitchange = abs(fitold - fit)
115 | exectimes.append(time.clock() - tic)
116 |
117 | _log.debug(
118 | '[%3d] fit: %.5f | delta: %7.1e | secs: %.5f'
119 | % (itr, fit, fitchange, exectimes[-1])
120 | )
121 | if itr > 1 and fitchange < conv:
122 | break
123 | return core, U
124 |
125 | def hosvd(X, rank, dims=None, dtype=None, compute_core=True):
126 | U = [None for _ in range(X.ndim)]
127 | if dims is None:
128 | dims = range(X.ndim)
129 | if dtype is None:
130 | dtype = X.dtype
131 | for d in dims:
132 | U[d] = array(nvecs(X, d, rank[d]), dtype=dtype)
133 | if compute_core:
134 | core = X.ttm(U, transp=True)
135 | return U, core
136 | else:
137 | return U
138 |
139 | def __init(init, X, N, rank, dtype):
140 | # Don't compute initial factor for first index, gets computed in
141 | # first iteration
142 | Uinit = [None]
143 | if isinstance(init, list):
144 | Uinit = init
145 | elif init == 'random':
146 | for n in range(1, N):
147 | Uinit.append(array(rand(X.shape[n], rank[n]), dtype=dtype))
148 | elif init == 'nvecs':
149 | Uinit = hosvd(X, rank, range(1, N), dtype=dtype, compute_core=False)
150 | return Uinit
151 |
--------------------------------------------------------------------------------
/sktensor/utils.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from numpy import cumprod, array, arange, zeros, floor, lexsort
3 |
4 |
5 | def accum(subs, vals, func=np.sum, issorted=False, with_subs=False):
6 | """
7 | NumPy implementation for Matlab's accumarray
8 | """
9 | # sort accmap for ediff if not sorted
10 | if not issorted:
11 | sidx = lexsort(subs, axis=0)
12 | subs = [sub[sidx] for sub in subs]
13 | vals = vals[sidx]
14 | idx = np.where(np.diff(subs).any(axis=0))[0] + 1
15 | idx = np.concatenate(([0], idx, [subs[0].shape[0]]))
16 |
17 | # create values array
18 | nvals = np.zeros(len(idx) - 1)
19 | for i in range(len(idx) - 1):
20 | nvals[i] = func(vals[idx[i]:idx[i + 1]])
21 |
22 | # return results
23 | if with_subs:
24 | return nvals, tuple(sub[idx[:-1]] for sub in subs)
25 | else:
26 | return nvals
27 |
28 |
29 | def unravel_dimension(shape, idx):
30 | if isinstance(idx, type(1)):
31 | idx = array([idx])
32 | k = [1] + list(cumprod(shape[:-1]))
33 | n = len(shape)
34 | subs = zeros((len(idx), n), dtype=np.int)
35 | for i in arange(n - 1, -1, -1):
36 | subs[:, i] = floor(idx / k[i])
37 | idx = idx % k[i]
38 | return subs
39 |
--------------------------------------------------------------------------------
/sktensor/version.py:
--------------------------------------------------------------------------------
1 | __version__ = '0.1'
2 |
--------------------------------------------------------------------------------